2019-07-02 21:40:50 +00:00
|
|
|
"""Apple II nominal display colours, represented by 4-bit dot sequences.
|
|
|
|
|
2019-07-11 22:40:00 +00:00
|
|
|
These are the "asymptotic" colours as displayed in e.g. continuous runs of
|
|
|
|
pixels. The effective colours that are actually displayed are not discrete,
|
|
|
|
due to NTSC artifacting being a continuous process.
|
2019-07-02 21:40:50 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
class NominalColours(enum.Enum):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class HGRColours(NominalColours):
|
2019-07-11 22:40:00 +00:00
|
|
|
"""Map from 4-bit dot representation to DHGR pixel colours.
|
|
|
|
|
|
|
|
Dots are in memory bit order (MSB -> LSB), which is opposite to screen
|
|
|
|
order (LSB -> MSB is ordered left-to-right on the screen)
|
|
|
|
|
|
|
|
Note that these are right-rotated from the HGR mapping, because of a
|
|
|
|
1-tick phase difference in the colour reference signal for DHGR vs HGR
|
|
|
|
"""
|
2019-06-12 21:12:26 +00:00
|
|
|
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):
|
2019-07-11 22:40:00 +00:00
|
|
|
"""Map from 4-bit dot representation to DHGR pixel colours.
|
|
|
|
|
|
|
|
Dots are in memory bit order (MSB -> LSB), which is opposite to screen
|
|
|
|
order (LSB -> MSB is ordered left-to-right on the screen)
|
|
|
|
|
|
|
|
Note that these are right-rotated from the HGR mapping, because of a
|
|
|
|
1-tick phase difference in the colour reference signal for DHGR vs HGR
|
|
|
|
"""
|
|
|
|
|
2019-07-02 21:40:50 +00:00
|
|
|
# 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
|
|
|
|
2019-07-11 22:40:00 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
def rol(int4: int, howmany: int) -> int:
|
|
|
|
"""Rotate-left an int4 some number of times."""
|
|
|
|
res = int4
|
|
|
|
for _ in range(howmany):
|
|
|
|
res = _rol(res)
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
def _rol(int4: int) -> int:
|
|
|
|
return ((int4 & 0b0111) << 1) ^ ((int4 & 0b1000) >> 3)
|
|
|
|
|
|
|
|
|
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-11 22:40:00 +00:00
|
|
|
previous tuple body. e.g. for DHGR, storing a byte in aux even columns
|
|
|
|
will also influence the colours of the previous main odd column.
|
2019-07-02 21:40:50 +00:00
|
|
|
|
2019-07-11 22:40:00 +00:00
|
|
|
This naively models (approximates) the NTSC colour artifacting.
|
2019-07-02 21:40:50 +00:00
|
|
|
|
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-11 22:40:00 +00:00
|
|
|
""""Sequence of num_bits nominal colour values via sliding 4-bit window."""
|
|
|
|
|
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
|
|
|
))
|
|
|
|
|