diff --git a/transcoder/screen.py b/transcoder/screen.py index a8cff5e..40f70fd 100644 --- a/transcoder/screen.py +++ b/transcoder/screen.py @@ -125,20 +125,6 @@ class MemoryMap: self.page_offset[page - self._page_start][offset] = val -@functools.lru_cache(None) -def _edit_distances(name: str, palette_id: pal.Palette) -> List[np.ndarray]: - """Load edit distance matrices for masked, shifted byte values. - - This is defined at module level to be a singleton. - """ - data = "transcoder/data/%s_palette_%d_edit_distance.pickle.bz2" % ( - name, - palette_id.value - ) - with bz2.open(data, "rb") as ed: - return pickle.load(ed) # type: List[np.ndarray] - - class Bitmap: """Packed 28-bit bitmap representation of (D)HGR screen memory. @@ -166,6 +152,9 @@ class Bitmap: # memory byte MASKED_BITS = None # type: np.uint64 + # XXX + PHASES = None # type: List[int] + def __init__( self, palette: pal.Palette, @@ -241,6 +230,10 @@ class Bitmap: def _byte_offsets(is_aux: bool) -> Tuple[int, int]: raise NotImplementedError + @classmethod + def to_dots(cls, masked_val: int, byte_offset: int) -> int: + raise NotImplementedError + def apply( self, page: int, @@ -253,7 +246,7 @@ class Bitmap: packed_offset = offset // 2 self.packed[page, packed_offset] = self.masked_update( - byte_offset, self.packed[page, packed_offset], np.uint64(value)) + byte_offset, self.packed[page, packed_offset], value) self._fix_scalar_neighbours(page, packed_offset, byte_offset) def _fix_scalar_neighbours( @@ -314,19 +307,43 @@ class Bitmap: shifted_right = np.roll(ary, 1, axis=1) self._fix_column_right(ary, shifted_right) + @classmethod @functools.lru_cache(None) - def edit_distances(self, palette_id: pal.Palette) -> List[np.ndarray]: + def edit_distances(cls, palette_id: pal.Palette) -> List[np.ndarray]: """Load edit distance matrices for masked, shifted byte values.""" - return _edit_distances(self.NAME, palette_id) + data = "transcoder/data/%s_palette_%d_edit_distance.pickle.bz2" % ( + cls.NAME, + palette_id.value + ) + with bz2.open(data, "rb") as ed: + dist = pickle.load(ed) # type: List[np.ndarray] + + # dist is an upper-triangular matrix of edit_distance(a, b) + # encoded as dist[(a << N) + b] = edit_distance(a, b) + # Because the distance metric is reflexive, + # edit_distance(b, a) = edit_distance(a, b) + + identity = np.arange(2 ** (2 * cls.MASKED_BITS), dtype=np.uint64) + # Swap values of form a << N + b to b << N + a + transpose = (identity >> cls.MASKED_BITS) + ( + (identity & np.uint64(2 ** cls.MASKED_BITS - 1)) << + cls.MASKED_BITS) + + for i in range(len(dist)): + dist[i][transpose] += dist[i][identity] + + return dist + + @classmethod def mask_and_shift_data( - self, + cls, data: IntOrArray, byte_offset: int) -> IntOrArray: """Masks and shifts data into the MASKED_BITS range.""" - res = (data & self.BYTE_MASKS[byte_offset]) >> ( - self.BYTE_SHIFTS[byte_offset]) - assert np.all(res <= 2 ** self.MASKED_BITS) + res = (data & cls.BYTE_MASKS[byte_offset]) >> ( + cls.BYTE_SHIFTS[byte_offset]) + assert np.all(res <= 2 ** cls.MASKED_BITS) return res # TODO: unit tests @@ -420,116 +437,146 @@ class HGRBitmap(Bitmap): MASKED_BITS = np.uint64(14) # 3 + 8 + 3 HEADER_BITS = np.uint64(3) - BODY_BITS = np.uint64(16) # 8 + 8 + # 7-bits doubled, plus possible shift from palette bit + BODY_BITS = np.uint64(15) FOOTER_BITS = np.uint64(3) + PHASES = [1, 3] + def __init__(self, palette: pal.Palette, main_memory: MemoryMap): super(HGRBitmap, self).__init__(palette, main_memory, None) - def _make_header(self, prev_col: IntOrArray) -> IntOrArray: - raise NotImplementedError + @staticmethod + def _make_header(col: IntOrArray) -> IntOrArray: + # Header format is bits 5,6,0 of previous byte + # i.e. offsets 16, 17, 11 + + # return (col & np.uint64(0b111 << 16)) >> np.uint64(16) + + return ( + (col & np.uint64(0b1 << 11)) >> np.uint64(9) ^ ( + (col & np.uint64(0b11 << 17)) >> np.uint64(17)) + ) def _body(self) -> np.ndarray: - raise NotImplementedError + # Body is in order + # a0 a1 a2 a3 a4 a5 a6 a7 b7 b0 b1 b2 b3 b4 b5 b6 + # so that a) the header and footer have the same order + # across the two byte offsets, and b) so that they + # can be extracted as contiguous bit ranges - def _make_footer(self, next_col: IntOrArray) -> IntOrArray: - raise NotImplementedError + even = self.main_memory.page_offset[:, 0::2].astype(np.uint64) + odd = self.main_memory.page_offset[:, 1::2].astype(np.uint64) - # XXX move to make_data_tables - def _pack(self) -> None: - """Pack main memory into (28+3)-bit uint64 array""" - - # 00000000001111111111222222222233 - # 01234567890123456789012345678901 - # AAAABBBBCCCCDDd - # AAAABBBBCCCCDd - # DDEEEEFFFFGGGGg - # dDDEEEEFFFFGGGg - - # Even, P0: store unshifted (0..14) - # Even, P1: store shifted << 1 (1..15) (only need 1..14) - - # Odd, P0: store shifted << 14 (14 .. 28) - set bit 14 as bit 0 of next - # byte - # Odd, p1: store shifted << 15 (15 .. 29) (only need 15 .. 28) - set - # bit 13 as bit 0 of next byte - - # Odd overflow only matters for even, P1 - # - bit 0 is either bit 14 if odd, P0 or bit 13 if odd, P1 - # - but these both come from the undoubled bit 6. - - main = self.main_memory.page_offset.astype(np.uint64) - - # Double 7-bit pixel data from a into 14-bit fat pixels, and extend MSB - # into 15-bits tohandle case when subsequent byte has palette bit set, - # i.e. is right-shifted by 1 dot. This only matters for even bytes - # with P=0 that are followed by odd bytes with P=1; in other cases - # this extra bit will be overwritten. - double = ( - # Bit pos 6 - ((main & 0x40) << 8) + ((main & 0x40) << 7) + ( - (main & 0x40) << 6)) + ( - # Bit pos 5 - ((main & 0x20) << 6) + ((main & 0x20) << 5)) + ( - # Bit pos 4 - ((main & 0x10) << 5) + ((main & 0x10) << 4)) + ( - # Bit pos 3 - ((main & 0x08) << 4) + ((main & 0x08) << 3)) + ( - # Bit pos 2 - ((main & 0x04) << 3) + ((main & 0x04) << 2)) + ( - # Bit pos 1 - ((main & 0x02) << 2) + ((main & 0x02) << 1)) + ( - # Bit pos 0 - ((main & 0x01) << 1) + (main & 0x01)) - - a_even = main[:, ::2] - a_odd = main[:, 1::2] - - double_even = double[:, ::2] - double_odd = double[:, 1::2] - - # Place even offsets at bits 1..15 (P=1) or 0..14 (P=0) - packed = np.where(a_even & 0x80, double_even << 1, double_even) - - # Place off offsets at bits 15..27 (P=1) or 14..27 (P=0) - packed = np.where( - a_odd & 0x80, - np.bitwise_xor( - np.bitwise_and(packed, (2 ** 15 - 1)), - double_odd << 15 - ), - np.bitwise_xor( - np.bitwise_and(packed, (2 ** 14 - 1)), - double_odd << 14 - ) + return ( + (even << 3) + ((odd & 0x7f) << 12) + ((odd & 0x80) << 4) ) - # Patch up even offsets with P=1 with extended bit from previous odd - # column + @staticmethod + def _make_footer(col: IntOrArray) -> IntOrArray: + # Footer format is bits 7,0,1 of next byte + # i.e. offsets 10,3,4 - previous_odd = np.roll(a_odd, 1, axis=1).astype(np.uint64) + return ( + (col & np.uint64(0b1 << 10)) >> np.uint64(10) ^ ( + (col & np.uint64(0b11 << 3)) >> np.uint64(2)) + ) << np.uint64(19) - packed = np.where( - a_even & 0x80, - # Truncate to 28-bits and set bit 0 from bit 6 of previous byte - np.bitwise_xor( - np.bitwise_and(packed, (2 ** 28 - 2)), - (previous_odd & (1 << 6)) >> 6 - ), - # Truncate to 28-bits - np.bitwise_and(packed, (2 ** 28 - 1)) - ) - - # Append first 3 bits of next even byte so we can correctly - # decode the effective colours at the end of the 28-bit tuple - trailing = np.roll(packed, -1, axis=1).astype(np.uint64) - - packed = np.bitwise_xor( - packed, - (trailing & 0b111) << 28 - ) - - self.packed = packed + # # XXX move to make_data_tables + # def _pack(self) -> None: + # """Pack main memory into (28+3)-bit uint64 array""" + # + # # 00000000001111111111222222222233 + # # 01234567890123456789012345678901 + # # AAAABBBBCCCCDDd + # # AAAABBBBCCCCDd + # # DDEEEEFFFFGGGGg + # # dDDEEEEFFFFGGGg + # + # # Even, P0: store unshifted (0..14) + # # Even, P1: store shifted << 1 (1..15) (only need 1..14) + # + # # Odd, P0: store shifted << 14 (14 .. 28) - set bit 14 as bit 0 of next + # # byte + # # Odd, p1: store shifted << 15 (15 .. 29) (only need 15 .. 28) - set + # # bit 13 as bit 0 of next byte + # + # # Odd overflow only matters for even, P1 + # # - bit 0 is either bit 14 if odd, P0 or bit 13 if odd, P1 + # # - but these both come from the undoubled bit 6. + # + # main = self.main_memory.page_offset.astype(np.uint64) + # + # # Double 7-bit pixel data from a into 14-bit fat pixels, and extend MSB + # # into 15-bits tohandle case when subsequent byte has palette bit set, + # # i.e. is right-shifted by 1 dot. This only matters for even bytes + # # with P=0 that are followed by odd bytes with P=1; in other cases + # # this extra bit will be overwritten. + # double = ( + # # Bit pos 6 + # ((main & 0x40) << 8) + ((main & 0x40) << 7) + ( + # (main & 0x40) << 6)) + ( + # # Bit pos 5 + # ((main & 0x20) << 6) + ((main & 0x20) << 5)) + ( + # # Bit pos 4 + # ((main & 0x10) << 5) + ((main & 0x10) << 4)) + ( + # # Bit pos 3 + # ((main & 0x08) << 4) + ((main & 0x08) << 3)) + ( + # # Bit pos 2 + # ((main & 0x04) << 3) + ((main & 0x04) << 2)) + ( + # # Bit pos 1 + # ((main & 0x02) << 2) + ((main & 0x02) << 1)) + ( + # # Bit pos 0 + # ((main & 0x01) << 1) + (main & 0x01)) + # + # a_even = main[:, ::2] + # a_odd = main[:, 1::2] + # + # double_even = double[:, ::2] + # double_odd = double[:, 1::2] + # + # # Place even offsets at bits 1..15 (P=1) or 0..14 (P=0) + # packed = np.where(a_even & 0x80, double_even << 1, double_even) + # + # # Place off offsets at bits 15..27 (P=1) or 14..27 (P=0) + # packed = np.where( + # a_odd & 0x80, + # np.bitwise_xor( + # np.bitwise_and(packed, (2 ** 15 - 1)), + # double_odd << 15 + # ), + # np.bitwise_xor( + # np.bitwise_and(packed, (2 ** 14 - 1)), + # double_odd << 14 + # ) + # ) + # + # # Patch up even offsets with P=1 with extended bit from previous odd + # # column + # + # previous_odd = np.roll(a_odd, 1, axis=1).astype(np.uint64) + # + # packed = np.where( + # a_even & 0x80, + # # Truncate to 28-bits and set bit 0 from bit 6 of previous byte + # np.bitwise_xor( + # np.bitwise_and(packed, (2 ** 28 - 2)), + # (previous_odd & (1 << 6)) >> 6 + # ), + # # Truncate to 28-bits + # np.bitwise_and(packed, (2 ** 28 - 1)) + # ) + # + # # Append first 3 bits of next even byte so we can correctly + # # decode the effective colours at the end of the 28-bit tuple + # trailing = np.roll(packed, -1, axis=1).astype(np.uint64) + # + # packed = np.bitwise_xor( + # packed, + # (trailing & 0b111) << 28 + # ) + # + # self.packed = packed @staticmethod @functools.lru_cache(None) @@ -547,6 +594,76 @@ class HGRBitmap(Bitmap): assert not is_aux return 0, 1 + @staticmethod + @functools.lru_cache(None) + def _double_pixels(int7: int) -> int: + + # Input bit 6 is repeated 3 times in case the neighbouring byte is + # delayed (right-shifted by one dot) due to the palette bit being set. + # Care needs to be taken to mask this out when overwriting. + double = ( + # Bit pos 6 + ((int7 & 0x40) << 8) + ((int7 & 0x40) << 7) + ( + (int7 & 0x40) << 6) + + # Bit pos 5 + ((int7 & 0x20) << 6) + ((int7 & 0x20) << 5) + + # Bit pos 4 + ((int7 & 0x10) << 5) + ((int7 & 0x10) << 4) + ( + # Bit pos 3 + ((int7 & 0x08) << 4) + ((int7 & 0x08) << 3) + + # Bit pos 2 + ((int7 & 0x04) << 3) + ((int7 & 0x04) << 2) + + # Bit pos 1 + ((int7 & 0x02) << 2) + ((int7 & 0x02) << 1) + + # Bit pos 0 + ((int7 & 0x01) << 1) + (int7 & 0x01)) + ) + + return double + + @classmethod + def to_dots(cls, masked_val: int, byte_offset: int) -> int: + + # Assert 14-bit representation + assert (masked_val & (2 ** 14 - 1)) == masked_val + + # Unpack hhHaaaaaaaABbbbbbbbFff + + # --> hhhaaaaaaaaaaaaaabbbb (P=0, P=0, P=0) + # hhhaaaaaaaaaaaaaabbbb (P=1, P=0, P=0) + # hhhhaaaaaaaaaaaaabbbb (P=1, P=1, P=0) + # hhhhaaaaaaaaaaaaaabbb (P=1, P=1, P=1) + + # Take top 3 bits from header (plus duplicated MSB) not 4, because if it + # is palette-shifted then we don't know what is in bit 0 + h = (masked_val & 0b111) << 5 + hp = (h & 0x80) >> 7 + res = cls._double_pixels(h & 0x7f) >> (11 - hp) + + if byte_offset == 0: + # Offset 0: hhHaaaaaaaABbb + b = (masked_val >> 3) & 0xff + bp = (b & 0x80) >> 7 + else: + # Offset 1: aaABbbbbbbbFff + bp = (masked_val >> 3) & 0x01 + b = ((masked_val >> 4) & 0x7f) ^ (bp << 7) + + # Mask out current contents in case we are overwriting the extended + # high bit from previous screen byte + res &= ~((2 ** 14 - 1) << (3 + bp)) + res ^= cls._double_pixels(b & 0x7f) << (3 + bp) + + f = ((masked_val >> 12) & 0b11) ^ ( + (masked_val >> 11) & 0b01) << 7 + fp = (f & 0x80) >> 7 + + # Mask out current contents in case we are overwriting the extended + # high bit from previous screen byte + res &= ~((2 ** 4 - 1) << (17 + fp)) + res ^= cls._double_pixels(f & 0x7f) << (17 + fp) + return res & (2 ** 21 - 1) + # XXX test @staticmethod def masked_update( @@ -558,12 +675,22 @@ class HGRBitmap(Bitmap): Does not patch up headers/footers of neighbouring columns. """ - # Mask out 8-bit value where update will go - masked_value = old_value & ( - ~np.uint64(0xff << (8 * byte_offset + 3))) + if byte_offset == 0: + # Mask out 8-bit value where update will go + masked_value = old_value & (~np.uint64(0xff << 3)) - update = new_value << np.uint64(8 * byte_offset + 3) - return masked_value ^ update + update = np.uint64(new_value) << np.uint64(3) + return masked_value ^ update + else: + # Mask out 8-bit value where update will go + masked_value = old_value & (~np.uint64(0xff << 11)) + + # shift palette bit into position 0 + shifted_new_value = ( + (new_value & 0x7f) << 1) ^ ( + (new_value & 0x80) >> 7) + update = np.uint64(shifted_new_value) << np.uint64(11) + return masked_value ^ update class DHGRBitmap(Bitmap): @@ -596,6 +723,16 @@ class DHGRBitmap(Bitmap): MASKED_BITS = np.uint64(13) + # NTSC clock phase at first masked bit + # Each DHGR byte offset has the same range of int13 possible + # values and nominal colour pixels, but with different initial + # phases: + # AUX 0: 0 (1 at start of 3-bit header) + # MAIN 0: 3 (0) + # AUX 1: 2 (3) + # MAIN 1: 1 (2) + PHASES = [1, 0, 3, 2] + def _body(self) -> np.ndarray: # Palette bit is unused for DHGR so mask it out aux = (self.aux_memory.page_offset & 0x7f).astype(np.uint64) @@ -651,6 +788,13 @@ class DHGRBitmap(Bitmap): return offsets + @classmethod + def to_dots(cls, masked_val: int, byte_offset: int) -> int: + # For DHGR the 13-bit masked value is already a 13-bit dot sequence + # so no need to transform it. + + return masked_val + @staticmethod def masked_update( byte_offset: int, diff --git a/transcoder/screen_test.py b/transcoder/screen_test.py index 8de9f03..eaf4cff 100644 --- a/transcoder/screen_test.py +++ b/transcoder/screen_test.py @@ -3,12 +3,10 @@ import unittest import numpy as np -from etaprogress.progress import ProgressBar -import colours -from palette import Palette, PALETTES import screen -import sys +import colours +from palette import Palette class TestDHGRBitmap(unittest.TestCase): @@ -16,50 +14,6 @@ class TestDHGRBitmap(unittest.TestCase): self.aux = screen.MemoryMap(screen_page=1) self.main = screen.MemoryMap(screen_page=1) - def test_edit_distances(self): - for p in PALETTES: - ed = screen._edit_distances("DHGR", p) - print(p) - - bar = ProgressBar((2 ** 13 * (2 ** 13 - 1)) / 2, max_width=80) - - cnt = 0 - for i in range(2 ** 13): - # Assert that self-distances are zero - - self.assertEqual(0, ed[0][(i << 13) + i]) - self.assertEqual(0, ed[1][(i << 13) + i]) - self.assertEqual(0, ed[2][(i << 13) + i]) - self.assertEqual(0, ed[3][(i << 13) + i]) - - # Assert that matrix is triangular - - for j in range(i): - - cnt += 1 - - if cnt % 10000 == 0: - bar.numerator = cnt - print(bar, end='\r') - sys.stdout.flush() - - self.assertEqual( - ed[0][(i << 13) + j], - ed[0][(j << 13) + i], - ) - self.assertEqual( - ed[1][(i << 13) + j], - ed[1][(j << 13) + i], - ) - self.assertEqual( - ed[2][(i << 13) + j], - ed[2][(j << 13) + i], - ) - self.assertEqual( - ed[3][(i << 13) + j], - ed[3][(j << 13) + i], - ) - def test_make_header(self): self.assertEqual( 0b100, @@ -376,406 +330,742 @@ def binary(a): return np.vectorize("{:032b}".format)(a) -# class TestHGRBitmap: # unittest.TestCase): -# def setUp(self) -> None: -# self.main = screen.MemoryMap(screen_page=1) -# -# def test_pixel_packing_p0_p0(self): -# # PDCCBBAA -# self.main.page_offset[0, 0] = 0b01000011 -# # PGGFFEED -# self.main.page_offset[0, 1] = 0b01000011 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# want = 0b1100000000111111000000001111 -# got = hgr.packed[0, 0] -# -# self.assertEqual( -# want, got, "\n%s\n%s" % (binary(want), binary(got)) -# ) -# -# def test_pixel_packing_p0_p1(self): -# # PDCCBBAA -# self.main.page_offset[0, 0] = 0b01000011 -# # PGGFFEED -# self.main.page_offset[0, 1] = 0b11000011 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# want = 0b1000000001111111000000001111 -# got = hgr.packed[0, 0] -# -# self.assertEqual( -# want, got, "\n%s\n%s" % (binary(want), binary(got)) -# ) -# -# def test_pixel_packing_p1_p0(self): -# # PDCCBBAA -# self.main.page_offset[0, 0] = 0b11000011 -# # PGGFFEED -# self.main.page_offset[0, 1] = 0b01000011 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# want = 0b1100000000111110000000011110 -# got = hgr.packed[0, 0] -# -# self.assertEqual( -# want, got, "\n%s\n%s" % (binary(want), binary(got)) -# ) -# -# def test_pixel_packing_p1_p1(self): -# # PDCCBBAA -# self.main.page_offset[0, 0] = 0b11000011 -# # PGGFFEED -# self.main.page_offset[0, 1] = 0b11000011 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# want = 0b1000000001111110000000011110 -# got = hgr.packed[0, 0] -# -# self.assertEqual( -# want, got, "\n%s\n%s" % (binary(want), binary(got)) -# ) -# -# def test_pixel_packing_p1_promote_p0(self): -# # PDCCBBAA -# self.main.page_offset[0, 0] = 0b00000000 -# # PGGFFEED -# self.main.page_offset[0, 1] = 0b01000000 -# -# # PDCCBBAA -# self.main.page_offset[0, 2] = 0b10000000 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# want = 0b0000000000000000000000000001 -# got = hgr.packed[0, 1] -# -# self.assertEqual( -# want, got, "\n%s\n%s" % (binary(want), binary(got)) -# ) -# -# def test_pixel_packing_p1_promote_p1(self): -# # PDCCBBAA -# self.main.page_offset[0, 0] = 0b00000000 -# # PGGFFEED -# self.main.page_offset[0, 1] = 0b11000000 -# -# # PDCCBBAA -# self.main.page_offset[0, 2] = 0b10000000 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# want = 0b0000000000000000000000000001 -# got = hgr.packed[0, 1] -# -# self.assertEqual( -# want, got, "\n%s\n%s" % (binary(want), binary(got)) -# ) -# -# 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 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# want = 0b000110011001100110011001100110011 -# got = hgr.packed[0, 0] -# -# self.assertEqual( -# want, got, "\n%s\n%s" % (binary(want), binary(got)) -# ) -# -# 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.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.int34_to_nominal_colour_pixels(hgr.packed[0, 0], -# colours.HGRColours) -# ) -# -# # See Figure 8.15 from Sather, "Understanding the Apple IIe" -# -# def test_nominal_colours_sather1(self): -# # Extend violet into light blue -# -# # PDCCBBAA -# self.main.page_offset[0, 0] = 0b01000000 -# # PGGFFEED -# self.main.page_offset[0, 1] = 0b10000000 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# self.assertEqual( -# ( -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.LIGHT_BLUE, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# ), -# colours.int28_to_nominal_colour_pixels(hgr.packed[0, 0], -# colours.HGRColours) -# ) -# -# def test_nominal_colours_sather2(self): -# # Cut off blue with black to produce dark blue -# -# # PDCCBBAA -# self.main.page_offset[0, 0] = 0b11000000 -# # PGGFFEED -# self.main.page_offset[0, 1] = 0b00000000 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# self.assertEqual( -# ( -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.DARK_BLUE, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# ), -# colours.int28_to_nominal_colour_pixels(hgr.packed[0, 0], -# colours.HGRColours) -# ) -# -# def test_nominal_colours_sather3(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 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# self.assertEqual( -# ( -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.AQUA, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# ), -# colours.int28_to_nominal_colour_pixels(hgr.packed[0, 0], -# colours.HGRColours) -# ) -# -# def test_nominal_colours_sather4(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 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# want = 0b0000000000000011100000000000 -# got = hgr.packed[0, 0] -# -# self.assertEqual( -# want, got, "\n%s\n%s" % (binary(want), binary(got)) -# ) -# -# # TODO: BROWN(0001)/VIOLET(1100) should reframe to PINK (1011) -# self.assertEqual( -# ( -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BROWN, -# colours.HGRColours.VIOLET, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# ), -# colours.int28_to_nominal_colour_pixels(hgr.packed[0, 0], -# colours.HGRColours) -# ) -# -# def test_nominal_colours_sather5(self): -# # Extend green into light brown -# -# # PDCCBBAA -# self.main.page_offset[0, 0] = 0b01000000 -# # PGGFFEED -# self.main.page_offset[0, 1] = 0b10000000 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# want = 0b0000000000000111000000000000 -# got = hgr.packed[0, 0] -# -# self.assertEqual( -# want, got, "\n%s\n%s" % (binary(want), binary(got)) -# ) -# -# # TODO: LIGHT_BLUE should reframe to PINK (1011) -# self.assertEqual( -# ( -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.LIGHT_BLUE, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# ), -# colours.int28_to_nominal_colour_pixels(hgr.packed[0, 0], -# colours.HGRColours) -# ) -# -# def test_nominal_colours_sather6(self): -# # Cut off orange with black to produce dark brown -# -# # PDCCBBAA -# self.main.page_offset[0, 0] = 0b11000000 -# # PGGFFEED -# self.main.page_offset[0, 1] = 0b00000000 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# want = 0b00000000000000010000000000000 -# got = hgr.packed[0, 0] -# -# self.assertEqual( -# want, got, "\n%s\n%s" % (binary(want), binary(got)) -# ) -# -# # TODO: DARK_BLUE should reframe to DARK_BROWN -# self.assertEqual( -# ( -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.DARK_BLUE, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# ), -# colours.int28_to_nominal_colour_pixels(hgr.packed[0, 0], -# colours.HGRColours) -# ) -# -# def test_nominal_colours_sather7(self): -# # Cut off orange with violet to produce pink -# -# # PDCCBBAA -# self.main.page_offset[0, 0] = 0b11000000 -# # PGGFFEED -# self.main.page_offset[0, 1] = 0b00000001 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# want = 0b00000000000001110000000000000 -# got = hgr.packed[0, 0] -# -# self.assertEqual( -# want, got, "\n%s\n%s" % (binary(want), binary(got)) -# ) -# -# # TODO: AQUA should reframe to PINK -# self.assertEqual( -# ( -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.AQUA, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# ), -# colours.int28_to_nominal_colour_pixels(hgr.packed[0, 0], -# colours.HGRColours) -# ) -# -# def test_nominal_colours_sather8(self): -# # Cut off white with black to produce aqua -# -# # PDCCBBAA -# self.main.page_offset[0, 0] = 0b11100000 -# # PGGFFEED -# self.main.page_offset[0, 1] = 0b00000000 -# -# hgr = screen.HGRBitmap( -# main_memory=self.main) -# -# want = 0b00000000000000011100000000000 -# got = hgr.packed[0, 0] -# -# self.assertEqual( -# want, got, "\n%s\n%s" % (binary(want), binary(got)) -# ) -# -# # TODO: BROWN/VIOLET should reframe to AQUA -# self.assertEqual( -# ( -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BROWN, -# colours.HGRColours.VIOLET, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# colours.HGRColours.BLACK, -# ), -# colours.int28_to_nominal_colour_pixels( -# hgr.packed[0, 0], colours.HGRColours) -# ) +class TestHGRBitmap(unittest.TestCase): + def setUp(self) -> None: + self.main = screen.MemoryMap(screen_page=1) + + def test_make_header(self): + self.assertEqual( + 0b111, + screen.HGRBitmap._make_header( + np.uint64(0b0001100000100000000000)) + ) + + # Now check palette bit ends up in right spot + self.assertEqual( + 0b100, + screen.HGRBitmap._make_header( + np.uint64(0b0000000000100000000000)) + ) + + def test_make_footer(self): + self.assertEqual( + 0b1110000000000000000000, + screen.HGRBitmap._make_footer( + np.uint64(0b0000000000010000011000)) + ) + + # Now check palette bit ends up in right spot + self.assertEqual( + 0b0010000000000000000000, + screen.HGRBitmap._make_footer( + np.uint64(0b0000000000010000000000)) + ) + + def test_pixel_packing_p0_p0(self): + # PDCCBBAA + self.main.page_offset[0, 0] = 0b01000011 + # PGGFFEED + self.main.page_offset[0, 1] = 0b01000011 + + hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) + + want = 0b0001000011001000011000 + got = hgr.packed[0, 0] + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + def test_pixel_packing_p0_p1(self): + # PDCCBBAA + self.main.page_offset[0, 0] = 0b01000011 + # PGGFFEED + self.main.page_offset[0, 1] = 0b11000011 + + hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) + + want = 0b0001000011101000011000 + got = hgr.packed[0, 0] + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + def test_pixel_packing_p1_p0(self): + # PDCCBBAA + self.main.page_offset[0, 0] = 0b11000011 + # PGGFFEED + self.main.page_offset[0, 1] = 0b01000011 + + hgr = screen.HGRBitmap(main_memory=self.main, palette=Palette.NTSC) + + want = 0b0001000011011000011000 + got = hgr.packed[0, 0] + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + def test_pixel_packing_p1_p1(self): + # 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_masked_update(self): + + 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)) + ) + + def test_double_pixels(self): + want = 0b111001100110011 + got = screen.HGRBitmap._double_pixels(0b1010101) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + def test_to_dots_offset_0(self): + # Header has P=0, Body has P=0 + want = 0b00000000000000000111 + got = screen.HGRBitmap.to_dots(0b00000000000011, 0) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=1, body has P=0 - cuts off + want = 0b00000000000000000111 + got = screen.HGRBitmap.to_dots(0b00000000000111, 0) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=1, body has P=1 + want = 0b00000000000000001111 + got = screen.HGRBitmap.to_dots(0b00010000000111, 0) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=1, body has P=1, footer has P=0 - cuts off body + want = 0b00010011001100111111 + got = screen.HGRBitmap.to_dots(0b00011010101111, 0) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=1, body has P=1, footer has P=1 + want = 0b00110011001100111111 + got = screen.HGRBitmap.to_dots(0b00111010101111, 0) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=1, body has P=1, footer has P=1 + want = 0b100110011001100111111 + got = screen.HGRBitmap.to_dots(0b10111010101111, 0) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=0, body has P=0, footer has P=1 + want = 0b100000000000000000000 + got = screen.HGRBitmap.to_dots(0b10100000000000, 0) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=0, body has P=0, footer has P=0 + want = 0b110000000000000000000 + got = screen.HGRBitmap.to_dots(0b10000000000000, 0) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + def test_to_dots_offset_1(self): + # Header has P=0, Body has P=0 + want = 0b000000000000000000111 + got = screen.HGRBitmap.to_dots(0b00000000000011, 1) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=1, body has P=0 - cuts off + want = 0b000000000000000000111 + got = screen.HGRBitmap.to_dots(0b00000000000111, 1) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=1, body has P=1 + want = 0b000000000000000001111 + got = screen.HGRBitmap.to_dots(0b00000000001111, 1) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=1, body has P=1, footer has P=0 - cuts off body + want = 0b000010011001100111111 + got = screen.HGRBitmap.to_dots(0b00010101011111, 1) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=1, body has P=1, footer has P=1 + want = 0b000110011001100111111 + got = screen.HGRBitmap.to_dots(0b00110101011111, 1) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=1, body has P=1, footer has P=1 + want = 0b100110011001100111111 + got = screen.HGRBitmap.to_dots(0b10110101011111, 1) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=0, body has P=0, footer has P=1 + want = 0b100000000000000000000 + got = screen.HGRBitmap.to_dots(0b10100000000000, 1) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + # Header has P=0, body has P=0, footer has P=0 + want = 0b110000000000000000000 + got = screen.HGRBitmap.to_dots(0b10000000000000, 1) + + self.assertEqual( + want, got, "\n%s\n%s" % (binary(want), binary(got)) + ) + + +class TestNominalColours(unittest.TestCase): + def setUp(self) -> None: + self.main = screen.MemoryMap(screen_page=1) + + self.maxDiff = None + + 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]) + ) + + # See Figure 8.15 from Sather, "Understanding the Apple IIe" + + 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_even_2(self): + # Cut off blue with black to produce dark blue + + # PDCCBBAA + self.main.page_offset[0, 0] = 0b11000000 + # 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.BLACK, + colours.HGRColours.BLACK, + colours.HGRColours.DARK_BLUE, # 0100 + colours.HGRColours.DARK_BLUE, + colours.HGRColours.DARK_BLUE, + 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 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_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_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_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_3(self): + # Cut off orange with violet to produce pink + + # PDCCBBAA + self.main.page_offset[0, 1] = 0b11000000 + # 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.BLACK, + colours.HGRColours.BLACK, + colours.HGRColours.BROWN, + colours.HGRColours.ORANGE, + colours.HGRColours.PINK, + colours.HGRColours.PINK, + colours.HGRColours.VIOLET, + ), + colours.dots_to_nominal_colour_pixels( + 18, dots, colours.HGRColours, + init_phase=screen.HGRBitmap.PHASES[1]) + ) + + def test_nominal_colours_sather_odd_4(self): + # Cut off white with black to produce aqua + + # PDCCBBAA + self.main.page_offset[0, 1] = 0b11100000 + # 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.DARK_BLUE, + colours.HGRColours.MED_BLUE, + colours.HGRColours.AQUA, + colours.HGRColours.AQUA, + colours.HGRColours.GREEN, + 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_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]) + ) if __name__ == '__main__':