2019-07-02 21:40:50 +00:00
|
|
|
"""Apple II nominal display colours, represented by 4-bit dot sequences.
|
|
|
|
|
|
|
|
These are distinct from the effective colours that are actually displayed,
|
|
|
|
e.g. due to white/black coalescing and NTSC artifacting.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from typing import Tuple, Type
|
2019-06-12 21:12:26 +00:00
|
|
|
|
|
|
|
import enum
|
2019-07-02 21:40:50 +00:00
|
|
|
import functools
|
|
|
|
|
|
|
|
|
|
|
|
def ror(int4: int, howmany: int) -> int:
|
|
|
|
"""Rotate-right an int4 some number of times."""
|
|
|
|
res = int4
|
|
|
|
for _ in range(howmany):
|
|
|
|
res = _ror(res)
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
def _ror(int4: int) -> int:
|
|
|
|
return ((int4 & 0b1110) >> 1) ^ ((int4 & 0b0001) << 3)
|
|
|
|
|
2019-06-12 21:12:26 +00:00
|
|
|
|
2019-07-02 21:40:50 +00:00
|
|
|
def rol(int4: int, howmany: int) -> int:
|
|
|
|
"""Rotate-left an int4 some number of times."""
|
|
|
|
res = int4
|
|
|
|
for _ in range(howmany):
|
|
|
|
res = _rol(res)
|
2019-06-12 21:12:26 +00:00
|
|
|
|
2019-07-02 21:40:50 +00:00
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
def _rol(int4: int) -> int:
|
|
|
|
return ((int4 & 0b0111) << 1) ^ ((int4 & 0b1000) >> 3)
|
|
|
|
|
|
|
|
|
|
|
|
class NominalColours(enum.Enum):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class HGRColours(NominalColours):
|
2019-06-12 21:12:26 +00:00
|
|
|
# Value is memory bit order, which is opposite to screen order (bits
|
|
|
|
# ordered Left to Right on screen)
|
|
|
|
BLACK = 0b0000
|
2019-07-02 21:40:50 +00:00
|
|
|
MAGENTA = 0b0001
|
|
|
|
BROWN = 0b1000
|
|
|
|
ORANGE = 0b1001 # HGR colour
|
|
|
|
DARK_GREEN = 0b0100
|
|
|
|
GREY1 = 0b0101
|
|
|
|
GREEN = 0b1100 # HGR colour
|
|
|
|
YELLOW = 0b1101
|
|
|
|
DARK_BLUE = 0b0010
|
|
|
|
VIOLET = 0b0011 # HGR colour
|
|
|
|
GREY2 = 0b1010
|
|
|
|
PINK = 0b1011
|
|
|
|
MED_BLUE = 0b0110 # HGR colour
|
|
|
|
LIGHT_BLUE = 0b0111
|
|
|
|
AQUA = 0b1110
|
|
|
|
WHITE = 0b1111
|
|
|
|
|
|
|
|
|
|
|
|
class DHGRColours(NominalColours):
|
|
|
|
# DHGR 4-bit memory representation is right-rotated from the HGR video
|
|
|
|
# representation.
|
|
|
|
BLACK = 0b0000
|
2019-06-12 21:12:26 +00:00
|
|
|
MAGENTA = 0b1000
|
|
|
|
BROWN = 0b0100
|
2019-07-02 21:40:50 +00:00
|
|
|
ORANGE = 0b1100 # HGR colour
|
2019-06-12 21:12:26 +00:00
|
|
|
DARK_GREEN = 0b0010
|
|
|
|
GREY1 = 0b1010
|
2019-07-02 21:40:50 +00:00
|
|
|
GREEN = 0b0110 # HGR colour
|
2019-06-12 21:12:26 +00:00
|
|
|
YELLOW = 0b1110
|
|
|
|
DARK_BLUE = 0b0001
|
2019-07-02 21:40:50 +00:00
|
|
|
VIOLET = 0b1001 # HGR colour
|
2019-06-12 21:12:26 +00:00
|
|
|
GREY2 = 0b0101
|
|
|
|
PINK = 0b1101
|
2019-07-02 21:40:50 +00:00
|
|
|
MED_BLUE = 0b0011 # HGR colour
|
2019-06-12 21:12:26 +00:00
|
|
|
LIGHT_BLUE = 0b1011
|
|
|
|
AQUA = 0b0111
|
|
|
|
WHITE = 0b1111
|
|
|
|
|
2019-07-02 21:40:50 +00:00
|
|
|
|
|
|
|
@functools.lru_cache(None)
|
2019-07-07 20:13:28 +00:00
|
|
|
def dots_to_nominal_colour_pixels(
|
|
|
|
num_bits: int,
|
|
|
|
dots: int,
|
2019-07-02 21:40:50 +00:00
|
|
|
colours: Type[NominalColours],
|
2019-07-07 20:13:28 +00:00
|
|
|
init_phase: int = 1 # Such that phase = 0 at start of body
|
2019-07-02 21:40:50 +00:00
|
|
|
) -> Tuple[NominalColours]:
|
2019-07-07 20:13:28 +00:00
|
|
|
"""Sequence of num_bits nominal colour pixels via sliding 4-bit window.
|
2019-07-02 21:40:50 +00:00
|
|
|
|
|
|
|
Includes the 3-bit header that represents the trailing 3 bits of the
|
2019-07-07 20:13:28 +00:00
|
|
|
previous tuple body. i.e. storing a byte in aux even columns will also
|
2019-07-02 21:40:50 +00:00
|
|
|
influence the colours of the previous main odd column.
|
|
|
|
|
|
|
|
This naively models the NTSC colour artifacting.
|
|
|
|
|
2019-07-07 20:13:28 +00:00
|
|
|
TODO: Use a more careful analogue colour composition model to produce
|
|
|
|
effective pixel colours.
|
2019-07-02 21:40:50 +00:00
|
|
|
|
|
|
|
TODO: DHGR vs HGR colour differences can be modeled by changing init_phase
|
|
|
|
"""
|
|
|
|
res = []
|
|
|
|
|
2019-07-07 20:13:28 +00:00
|
|
|
shifted = dots
|
2019-07-02 21:40:50 +00:00
|
|
|
phase = init_phase
|
|
|
|
|
2019-07-07 20:13:28 +00:00
|
|
|
for i in range(num_bits):
|
2019-07-02 21:40:50 +00:00
|
|
|
colour = rol(shifted & 0b1111, phase)
|
|
|
|
res.append(colours(colour))
|
|
|
|
|
|
|
|
shifted >>= 1
|
|
|
|
phase += 1
|
|
|
|
if phase == 4:
|
|
|
|
phase = 0
|
|
|
|
|
|
|
|
return tuple(res)
|
|
|
|
|
|
|
|
|
|
|
|
@functools.lru_cache(None)
|
2019-07-07 20:13:28 +00:00
|
|
|
def dots_to_nominal_colour_pixel_values(
|
|
|
|
num_bits: int,
|
|
|
|
dots: int,
|
2019-07-02 21:40:50 +00:00
|
|
|
colours: Type[NominalColours],
|
2019-07-07 20:13:28 +00:00
|
|
|
init_phase: int = 1 # Such that phase = 0 at start of body
|
2019-07-02 21:40:50 +00:00
|
|
|
) -> Tuple[int]:
|
2019-07-07 20:13:28 +00:00
|
|
|
return tuple(p.value for p in dots_to_nominal_colour_pixels(
|
|
|
|
num_bits, dots, colours, init_phase
|
2019-07-02 21:40:50 +00:00
|
|
|
))
|
|
|
|
|