def test_apply(self): """Test that header, body and footer are placed correctly.""" hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) hgr.apply(0, 0, False, 0b11000011) hgr.apply(0, 1, False, 0b11000011) want = 0b1000011111000011000 got = hgr.packed[0, 0] self.assertEqual( want, got, "\n%s\n%s" % (binary(want), binary(got)) ) # Now check with 4 consecutive bytes, i.e. even/odd pair plus the # neighbouring header/footer. hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) hgr.apply(1, 197, False, 128) hgr.apply(1, 198, False, 143) hgr.apply(1, 199, False, 192) hgr.apply(1, 200, False, 128) want = 0b0011000000110001111100 got = hgr.packed[1, 199 // 2] self.assertEqual( want, got, "\n%s\n%s" % (binary(want), binary(got)) )
def __init__(self, frame_grabber: FrameGrabber, ticks_per_second: float, mode: VideoMode = VideoMode.HGR, palette: Palette = Palette.NTSC): self.mode = mode # type: VideoMode self.frame_grabber = frame_grabber # type: FrameGrabber self.ticks_per_second = ticks_per_second # type: float self.ticks_per_frame = (self.ticks_per_second / frame_grabber.input_frame_rate) # type: float self.frame_number = 0 # type: int self.palette = palette # type: Palette # Initialize empty screen self.memory_map = screen.MemoryMap( screen_page=1) # type: screen.MemoryMap if self.mode == mode.DHGR: self.aux_memory_map = screen.MemoryMap( screen_page=1) # type: screen.MemoryMap self.pixelmap = screen.DHGRBitmap(palette=palette, main_memory=self.memory_map, aux_memory=self.aux_memory_map) else: self.pixelmap = screen.HGRBitmap( palette=palette, main_memory=self.memory_map, ) # Accumulates pending edit weights across frames self.update_priority = np.zeros((32, 256), dtype=np.int) if self.mode == mode.DHGR: self.aux_update_priority = np.zeros((32, 256), dtype=np.int)
def test_pixel_packing_p1_p1(self): """Screen byte packing happens correctly with P=1, P=1 palette bits.""" # PDCCBBAA self.main.page_offset[0, 0] = 0b11000011 # PGGFFEED self.main.page_offset[0, 1] = 0b11000011 hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) want = 0b1000011111000011000 got = hgr.packed[0, 0] self.assertEqual( want, got, "\n%s\n%s" % (binary(want), binary(got)) )
def test_nominal_colours_sather_odd_5(self): """Cut off blue-black with violet to produce bright violet. "Bright" here is because the sequence of pixels has high intensity Blue-Blue-Light Blue-Light Blue-Violet-Violet. """ # PDCCBBAA self.main.page_offset[0, 1] = 0b10100000 # PGGFFEED self.main.page_offset[0, 2] = 0b00000001 self.hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) masked = int(screen.HGRBitmap.mask_and_shift_data( self.hgr.packed[0, 0], byte_offset=1)) dots = screen.HGRBitmap.to_dots(masked, byte_offset=1) self.assertEqual( ( colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.DARK_BLUE, colours.HGRColours.MED_BLUE, colours.HGRColours.MED_BLUE, colours.HGRColours.LIGHT_BLUE, colours.HGRColours.LIGHT_BLUE, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET ), colours.dots_to_nominal_colour_pixels( 18, dots, colours.HGRColours, init_phase=screen.HGRBitmap.PHASES[1]) )
def test_nominal_colours_sather_even_5(self): """Cut off orange-black with green to produce bright green. "Bright" here is because the sequence of pixels has high intensity Orange-Orange-Yellow-Yellow-Green-Green.""" # PDCCBBAA self.main.page_offset[0, 0] = 0b10100000 # PGGFFEED self.main.page_offset[0, 1] = 0b00000001 self.hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) masked = int(screen.HGRBitmap.mask_and_shift_data( self.hgr.packed[0, 0], byte_offset=0)) dots = screen.HGRBitmap.to_dots(masked, byte_offset=0) self.assertEqual( ( colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BROWN, # 0001 colours.HGRColours.ORANGE, # 1001 colours.HGRColours.ORANGE, # 1001 colours.HGRColours.YELLOW, # 1011 colours.HGRColours.YELLOW, # 1011 colours.HGRColours.GREEN, # 0011 colours.HGRColours.GREEN, # 0011 ), colours.dots_to_nominal_colour_pixels( 18, dots, colours.HGRColours, init_phase=screen.HGRBitmap.PHASES[0]) )
def test_nominal_colours_sather_even_1(self): """Extend violet into light blue.""" # PDCCBBAA self.main.page_offset[0, 0] = 0b01000000 # PGGFFEED self.main.page_offset[0, 1] = 0b10000000 self.hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) masked = int(screen.HGRBitmap.mask_and_shift_data( self.hgr.packed[0, 0], byte_offset=0)) dots = screen.HGRBitmap.to_dots(masked, byte_offset=0) self.assertEqual( ( colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.MAGENTA, # 1000 colours.HGRColours.VIOLET, # 1100 colours.HGRColours.LIGHT_BLUE, # 1110 colours.HGRColours.LIGHT_BLUE, # 1110 colours.HGRColours.MED_BLUE, # 0110 # last repeated bit from byte 0 colours.HGRColours.DARK_GREEN, # 0010 ), colours.dots_to_nominal_colour_pixels( 18, dots, colours.HGRColours, init_phase=screen.HGRBitmap.PHASES[0]) )
def test_nominal_colours_sather_odd_2(self): """Cut off orange with black to produce dark brown.""" # PDCCBBAA self.main.page_offset[0, 1] = 0b11000000 # PGGFFEED self.main.page_offset[0, 2] = 0b00000000 self.hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) masked = int(screen.HGRBitmap.mask_and_shift_data( self.hgr.packed[0, 0], byte_offset=1)) dots = screen.HGRBitmap.to_dots(masked, byte_offset=1) self.assertEqual( ( colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BROWN, colours.HGRColours.BROWN, colours.HGRColours.BROWN, colours.HGRColours.BROWN, colours.HGRColours.BLACK, ), colours.dots_to_nominal_colour_pixels( 18, dots, colours.HGRColours, init_phase=screen.HGRBitmap.PHASES[1]) )
def test_nominal_colours_sather_odd_1(self): """Extend green into light brown.""" # PDCCBBAA self.main.page_offset[0, 1] = 0b01000000 # PGGFFEED self.main.page_offset[0, 2] = 0b10000000 self.hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) masked = int(screen.HGRBitmap.mask_and_shift_data( self.hgr.packed[0, 0], byte_offset=1)) dots = screen.HGRBitmap.to_dots(masked, byte_offset=1) self.assertEqual( ( colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.DARK_GREEN, colours.HGRColours.GREEN, colours.HGRColours.YELLOW, colours.HGRColours.YELLOW, colours.HGRColours.ORANGE, colours.HGRColours.MAGENTA, ), colours.dots_to_nominal_colour_pixels( 18, dots, colours.HGRColours, init_phase=screen.HGRBitmap.PHASES[1]) )
def test_nominal_colours_sather_even_4(self): """Cut off white with black to produce pink.""" # PDCCBBAA self.main.page_offset[0, 0] = 0b11100000 # PGGFFEED self.main.page_offset[0, 1] = 0b00000000 self.hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) masked = int(screen.HGRBitmap.mask_and_shift_data( self.hgr.packed[0, 0], byte_offset=0)) dots = screen.HGRBitmap.to_dots(masked, byte_offset=0) self.assertEqual( ( colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BROWN, colours.HGRColours.ORANGE, colours.HGRColours.PINK, colours.HGRColours.PINK, colours.HGRColours.VIOLET, colours.HGRColours.DARK_BLUE, colours.HGRColours.BLACK, ), colours.dots_to_nominal_colour_pixels( 18, dots, colours.HGRColours, init_phase=screen.HGRBitmap.PHASES[0]) )
def test_nominal_colours_sather_even_3(self): """Cut off blue with green to produce aqua.""" # PDCCBBAA self.main.page_offset[0, 0] = 0b11000000 # PGGFFEED self.main.page_offset[0, 1] = 0b00000001 self.hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) masked = int(screen.HGRBitmap.mask_and_shift_data( self.hgr.packed[0, 0], byte_offset=0)) dots = screen.HGRBitmap.to_dots(masked, byte_offset=0) self.assertEqual( ( colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.BLACK, colours.HGRColours.DARK_BLUE, colours.HGRColours.MED_BLUE, colours.HGRColours.AQUA, colours.HGRColours.AQUA, colours.HGRColours.GREEN, ), colours.dots_to_nominal_colour_pixels( 18, dots, colours.HGRColours, init_phase=screen.HGRBitmap.PHASES[0]) )
def _index_changes(self, source: screen.MemoryMap, target: screen.MemoryMap, update_priority: np.array, is_aux: True) -> Iterator[Tuple[int, int, List[int]]]: """Transform encoded screen to sequence of change tuples.""" if self.mode == VideoMode.DHGR: if is_aux: target_pixelmap = screen.DHGRBitmap( main_memory=self.memory_map, aux_memory=target, palette=self.palette) else: target_pixelmap = screen.DHGRBitmap( main_memory=target, aux_memory=self.aux_memory_map, palette=self.palette) else: target_pixelmap = screen.HGRBitmap(main_memory=target, palette=self.palette) diff_weights = target_pixelmap.diff_weights(self.pixelmap, is_aux) # Don't bother storing into screen holes diff_weights[screen.SCREEN_HOLES] = 0 # Clear any update priority entries that have resolved themselves # with new frame update_priority[diff_weights == 0] = 0 update_priority += diff_weights priorities = self._heapify_priorities(update_priority) content_deltas = {} while priorities: pri, _, page, offset = heapq.heappop(priorities) assert not screen.SCREEN_HOLES[page, offset], ( "Attempted to store into screen hole at (%d, %d)" % (page, offset)) # Check whether we've already cleared this diff while processing # an earlier opcode if update_priority[page, offset] == 0: continue offsets = [offset] content = target.page_offset[page, offset] if self.mode == VideoMode.DHGR: # DHGR palette bit not expected to be set assert content < 0x80 # Clear priority for the offset we're emitting update_priority[page, offset] = 0 diff_weights[page, offset] = 0 # Update memory maps source.page_offset[page, offset] = content self.pixelmap.apply(page, offset, is_aux, content) # Make sure we don't emit this offset as a side-effect of some # other offset later. for cd in content_deltas.values(): cd[page, offset] = 0 # TODO: what if we add another content_deltas entry later? # We might clobber it again # Need to find 3 more offsets to fill this opcode for err, o in self._compute_error(page, content, target_pixelmap, diff_weights, content_deltas, is_aux): assert o != offset assert not screen.SCREEN_HOLES[page, o], ( "Attempted to store into screen hole at (%d, %d)" % (page, o)) if update_priority[page, o] == 0: # Someone already resolved this diff. continue # Make sure we don't end up considering this (page, offset) # again until the next image frame. Even if a better match # comes along, it's probably better to fix up some other byte. # TODO: or should we recompute it with new error? for cd in content_deltas.values(): cd[page, o] = 0 byte_offset = target_pixelmap.byte_offset(o, is_aux) old_packed = target_pixelmap.packed[page, o // 2] p = target_pixelmap.byte_pair_difference( byte_offset, old_packed, content) # Update priority for the offset we're emitting update_priority[page, o] = p source.page_offset[page, o] = content self.pixelmap.apply(page, o, is_aux, content) if p: # This content byte introduced an error, so put back on the # heap in case we can get back to fixing it exactly # during this frame. Otherwise we'll get to it later. heapq.heappush(priorities, (-p, random.getrandbits(8), page, o)) offsets.append(o) if len(offsets) == 3: break # Pad to 4 if we didn't find enough for _ in range(len(offsets), 4): offsets.append(offsets[0]) yield (page + 32, content, offsets) # # TODO: there is still a bug causing residual diffs when we have # # apparently run out of work to do if not np.array_equal(source.page_offset, target.page_offset): diffs = np.nonzero(source.page_offset != target.page_offset) for i in range(len(diffs[0])): diff_p = diffs[0][i] diff_o = diffs[1][i] # For HGR, 0x00 or 0x7f may be visually equivalent to the same # bytes with high bit set (depending on neighbours), so skip # them if (source.page_offset[diff_p, diff_o] & 0x7f) == 0 and \ (target.page_offset[diff_p, diff_o] & 0x7f) == 0: continue if (source.page_offset[diff_p, diff_o] & 0x7f) == 0x7f and \ (target.page_offset[diff_p, diff_o] & 0x7f) == 0x7f: continue print("Diff at (%d, %d): %d != %d" % (diff_p, diff_o, source.page_offset[diff_p, diff_o], target.page_offset[diff_p, diff_o])) # assert False # If we run out of things to do, pad forever content = target.page_offset[0, 0] while True: yield (32, content, [0, 0, 0, 0])
def test_nominal_colours(self): # PDCCBBAA self.main.page_offset[0, 0] = 0b01010101 # PGGFFEED self.main.page_offset[0, 1] = 0b00101010 # PDCCBBAA self.main.page_offset[0, 2] = 0b01010101 self.hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) want = 0b0100101010001010101000 got = self.hgr.packed[0, 0] self.assertEqual( want, got, "\n%s\n%s" % (binary(want), binary(got)) ) masked = int(screen.HGRBitmap.mask_and_shift_data( self.hgr.packed[0, 0], byte_offset=0)) dots = screen.HGRBitmap.to_dots(masked, byte_offset=0) self.assertEqual( ( colours.HGRColours.MAGENTA, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, ), colours.dots_to_nominal_colour_pixels( 18, dots, colours.HGRColours, init_phase=screen.HGRBitmap.PHASES[0]) ) # Now check byte offset 1 masked = int(screen.HGRBitmap.mask_and_shift_data( self.hgr.packed[0, 0], byte_offset=1)) dots = screen.HGRBitmap.to_dots(masked, byte_offset=1) self.assertEqual( ( colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, colours.HGRColours.VIOLET, ), colours.dots_to_nominal_colour_pixels( 18, dots, colours.HGRColours, init_phase=screen.HGRBitmap.PHASES[1]) )