mirror of
https://github.com/KrisKennaway/ii-vision.git
synced 2025-08-05 07:24:24 +00:00
Checkpoint
This commit is contained in:
@@ -71,6 +71,13 @@ class DHGRColours(NominalColours):
|
||||
WHITE = 0b1111
|
||||
|
||||
|
||||
class MonoColours(NominalColours):
|
||||
"""XXX """
|
||||
|
||||
BLACK = 0b0
|
||||
WHITE = 0b1
|
||||
|
||||
|
||||
def ror(int4: int, howmany: int) -> int:
|
||||
"""Rotate-right an int4 some number of times."""
|
||||
res = int4
|
||||
@@ -147,3 +154,34 @@ def dots_to_nominal_colour_pixel_values(
|
||||
num_bits, dots, colours, init_phase
|
||||
))
|
||||
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def dots_to_mono_pixels(
|
||||
num_bits: int,
|
||||
dots: int,
|
||||
colours: Type[MonoColours],
|
||||
) -> Tuple[MonoColours]:
|
||||
"""Sequence of num_bits mono pixels.
|
||||
|
||||
"""
|
||||
res = []
|
||||
|
||||
shifted = dots
|
||||
for i in range(num_bits):
|
||||
colour = shifted & 0b1
|
||||
res.append(colours(colour))
|
||||
|
||||
shifted >>= 1
|
||||
|
||||
return tuple(res)
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def dots_to_mono_pixel_values(
|
||||
num_bits: int,
|
||||
dots: int,
|
||||
colours: Type[MonoColours],
|
||||
) -> Tuple[int]:
|
||||
""""Sequence of num_bits nominal colour values via sliding 4-bit window."""
|
||||
|
||||
return tuple(p.value for p in dots_to_mono_pixels(num_bits, dots, colours))
|
||||
|
||||
|
@@ -81,24 +81,28 @@ class FileFrameGrabber(FrameGrabber):
|
||||
self.filename, self.video_mode, self.palette)
|
||||
os.makedirs(frame_dir, exist_ok=True)
|
||||
|
||||
global _converter
|
||||
if self.video_mode == VideoMode.DHGR_MONO:
|
||||
_converter = DHGRMonoFrameConverter(frame_dir)
|
||||
converter = DHGRMonoFrameConverter(frame_dir)
|
||||
elif self.video_mode == VideoMode.DHGR:
|
||||
# XXX support palette
|
||||
_converter = DHGRFrameConverter(frame_dir=frame_dir)
|
||||
converter = DHGRFrameConverter(frame_dir=frame_dir)
|
||||
elif self.video_mode == VideoMode.HGR:
|
||||
_converter = HGRFrameConverter(
|
||||
converter = HGRFrameConverter(
|
||||
frame_dir=frame_dir, palette_value=self.palette.value)
|
||||
|
||||
pool = multiprocessing.Pool(10)
|
||||
for main, aux in pool.imap(_converter.convert, self._frame_extractor(
|
||||
pool = multiprocessing.Pool(
|
||||
10, initializer=init_converter, initargs=(converter,))
|
||||
for main, aux in pool.imap(converter.convert, self._frame_extractor(
|
||||
frame_dir), chunksize=1):
|
||||
main_map = screen.FlatMemoryMap(
|
||||
screen_page=1, data=main).to_memory_map()
|
||||
aux_map = screen.FlatMemoryMap(
|
||||
screen_page=1, data=aux).to_memory_map() if aux else None
|
||||
|
||||
if aux is not None:
|
||||
aux_map = screen.FlatMemoryMap(
|
||||
screen_page=1, data=aux).to_memory_map()
|
||||
else:
|
||||
aux_map = None
|
||||
# print("main %s" % main_map.page_offset)
|
||||
# print("aux %s" % aux_map.page_offset)
|
||||
yield main_map, aux_map
|
||||
|
||||
|
||||
@@ -106,6 +110,11 @@ class FileFrameGrabber(FrameGrabber):
|
||||
_converter = None # type:Optional[FrameConverter]
|
||||
|
||||
|
||||
def init_converter(converter):
|
||||
global _converter
|
||||
_converter = converter
|
||||
|
||||
|
||||
class FrameConverter:
|
||||
def __init__(self, frame_dir):
|
||||
self.frame_dir = frame_dir
|
||||
@@ -210,7 +219,9 @@ class DHGRMonoFrameConverter(FrameConverter):
|
||||
os.stat(dhrfile)
|
||||
except FileNotFoundError:
|
||||
subprocess.call([
|
||||
"python", "convert.py", "dhr_mono", bmpfile, dhrfile
|
||||
# XXX
|
||||
"python3.10", "/Volumes/Stuff/apple2/dither/convert.py",
|
||||
"dhr_mono", "--no-show-output", bmpfile, dhrfile
|
||||
])
|
||||
# os.remove(bmpfile)
|
||||
|
||||
|
@@ -39,7 +39,8 @@ class EditDistanceParams:
|
||||
# Smallest substitution value is ~20 from palette.diff_matrices, i.e.
|
||||
# we always prefer to transpose 2 pixels rather than substituting colours.
|
||||
# TODO: is quality really better allowing transposes?
|
||||
transpose_costs = np.ones((128, 128), dtype=np.float64)
|
||||
# XXX is 1 appropriate weight for mono?
|
||||
transpose_costs = np.ones((128, 128), dtype=np.float64) * 50
|
||||
|
||||
# These will be filled in later
|
||||
substitute_costs = np.zeros((128, 128), dtype=np.float64)
|
||||
@@ -58,8 +59,8 @@ def compute_diff_matrix(pal: Type[palette.BasePalette]):
|
||||
|
||||
Specifically CIE2000 delta values for this palette.
|
||||
"""
|
||||
dm = np.ndarray(shape=(16, 16), dtype=np.int32)
|
||||
|
||||
palette_size = len(pal.RGB)
|
||||
dm = np.ndarray(shape=(palette_size, palette_size), dtype=np.int32)
|
||||
for colour1, a in pal.RGB.items():
|
||||
alab = colormath.color_conversions.convert_color(
|
||||
a, colormath.color_objects.LabColor)
|
||||
@@ -78,9 +79,11 @@ def compute_substitute_costs(pal: Type[palette.BasePalette]):
|
||||
|
||||
diff_matrix = compute_diff_matrix(pal)
|
||||
|
||||
palette_size = len(pal.RGB)
|
||||
|
||||
# Penalty for changing colour
|
||||
for i, c in enumerate(PIXEL_CHARS):
|
||||
for j, d in enumerate(PIXEL_CHARS):
|
||||
for i, c in enumerate(PIXEL_CHARS[:palette_size]):
|
||||
for j, d in enumerate(PIXEL_CHARS[:palette_size]):
|
||||
cost = diff_matrix[i, j]
|
||||
edp.substitute_costs[(ord(c), ord(d))] = cost
|
||||
edp.substitute_costs[(ord(d), ord(c))] = cost
|
||||
@@ -112,7 +115,7 @@ def edit_distance(
|
||||
def compute_edit_distance(
|
||||
edp: EditDistanceParams,
|
||||
bitmap_cls: Type[screen.Bitmap],
|
||||
nominal_colours: Type[colours.NominalColours]
|
||||
nominal_colours: Type[colours.NominalColours], is_mono: bool = False
|
||||
) -> np.ndarray:
|
||||
"""Computes edit distance matrix between all pairs of pixel strings.
|
||||
|
||||
@@ -146,12 +149,18 @@ def compute_edit_distance(
|
||||
for o, ph in enumerate(bitmap_cls.PHASES):
|
||||
# Compute this in the outer loop since it's invariant under j
|
||||
first_dots = bitmap_cls.to_dots(i, byte_offset=o)
|
||||
first_pixels = pixel_string(
|
||||
colours.dots_to_nominal_colour_pixel_values(
|
||||
num_dots, first_dots, nominal_colours,
|
||||
init_phase=ph)
|
||||
)
|
||||
|
||||
if is_mono:
|
||||
first_pixel_values = colours.dots_to_mono_pixel_values(
|
||||
num_dots, first_dots, nominal_colours)
|
||||
|
||||
else:
|
||||
first_pixel_values = (
|
||||
colours.dots_to_nominal_colour_pixel_values(
|
||||
num_dots, first_dots, nominal_colours,
|
||||
init_phase=ph)
|
||||
)
|
||||
first_pixels = pixel_string(first_pixel_values)
|
||||
# Matrix is symmetrical with zero diagonal so only need to compute
|
||||
# upper triangle
|
||||
for j in range(i):
|
||||
@@ -164,11 +173,19 @@ def compute_edit_distance(
|
||||
pair = pair_base + np.uint64(j)
|
||||
|
||||
second_dots = bitmap_cls.to_dots(j, byte_offset=o)
|
||||
second_pixels = pixel_string(
|
||||
colours.dots_to_nominal_colour_pixel_values(
|
||||
num_dots, second_dots, nominal_colours,
|
||||
init_phase=ph)
|
||||
)
|
||||
|
||||
if is_mono:
|
||||
second_pixel_values = colours.dots_to_mono_pixel_values(
|
||||
num_dots, second_dots, nominal_colours)
|
||||
|
||||
else:
|
||||
second_pixel_values = (
|
||||
colours.dots_to_nominal_colour_pixel_values(
|
||||
num_dots, second_dots, nominal_colours,
|
||||
init_phase=ph)
|
||||
)
|
||||
second_pixels = pixel_string(second_pixel_values)
|
||||
|
||||
edit[o, pair] = edit_distance(
|
||||
edp, first_pixels, second_pixels, error=False)
|
||||
|
||||
@@ -183,7 +200,9 @@ def make_edit_distance(
|
||||
):
|
||||
"""Write file containing (D)HGR edit distance matrix for a palette."""
|
||||
|
||||
dist = compute_edit_distance(edp, bitmap_cls, nominal_colours)
|
||||
is_mono = pal.ID == palette.Palette.MONO
|
||||
|
||||
dist = compute_edit_distance(edp, bitmap_cls, nominal_colours, is_mono)
|
||||
data = "transcoder/data/%s_palette_%d_edit_distance.npz" % (
|
||||
bitmap_cls.NAME, pal.ID.value)
|
||||
np.savez_compressed(data, edit_distance=dist)
|
||||
@@ -192,12 +211,16 @@ def make_edit_distance(
|
||||
def main():
|
||||
for p in palette.PALETTES.values():
|
||||
print("Processing palette %s" % p)
|
||||
# TODO: still worth using error distance matrices?
|
||||
edp = compute_substitute_costs(p)
|
||||
|
||||
# TODO: still worth using error distance matrices?
|
||||
|
||||
make_edit_distance(p, edp, screen.HGRBitmap, colours.HGRColours)
|
||||
make_edit_distance(p, edp, screen.DHGRBitmap, colours.DHGRColours)
|
||||
if p.ID == palette.Palette.MONO:
|
||||
make_edit_distance(p, edp, screen.DHGRMonoBitmap,
|
||||
colours.MonoColours)
|
||||
else:
|
||||
#make_edit_distance(p, edp, screen.HGRBitmap, colours.HGRColours)
|
||||
#make_edit_distance(p, edp, screen.DHGRBitmap, colours.DHGRColours)
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@@ -85,8 +85,7 @@ class Movie:
|
||||
palette=self.palette
|
||||
)
|
||||
elif self.video_mode == VideoMode.DHGR_MONO:
|
||||
# XXX
|
||||
target_pixelmap = screen.DHGRBitmap(
|
||||
target_pixelmap = screen.DHGRMonoBitmap(
|
||||
main_memory=main,
|
||||
aux_memory=aux,
|
||||
palette=self.palette
|
||||
|
@@ -5,7 +5,7 @@ from typing import Dict, Type
|
||||
|
||||
import colormath.color_objects
|
||||
|
||||
from colours import HGRColours
|
||||
from colours import HGRColours, MonoColours
|
||||
|
||||
# Type annotation
|
||||
RGB = colormath.color_objects.sRGBColor
|
||||
@@ -18,9 +18,11 @@ def rgb(r, g, b):
|
||||
class Palette(enum.Enum):
|
||||
"""BMP2DHR palette numbers."""
|
||||
|
||||
# XXX don't use BMP2DHR palette values here
|
||||
UNKNOWN = -1
|
||||
IIGS = 0
|
||||
NTSC = 5
|
||||
MONO = 1
|
||||
|
||||
|
||||
class BasePalette:
|
||||
@@ -54,6 +56,15 @@ class NTSCPalette(BasePalette):
|
||||
}
|
||||
|
||||
|
||||
class MonoPalette(BasePalette):
|
||||
ID = Palette.MONO
|
||||
|
||||
RGB = {
|
||||
MonoColours.BLACK: rgb(0, 0, 0),
|
||||
MonoColours.WHITE: rgb(255, 255, 255)
|
||||
}
|
||||
|
||||
|
||||
class IIGSPalette(BasePalette):
|
||||
ID = Palette.IIGS
|
||||
|
||||
@@ -80,5 +91,6 @@ class IIGSPalette(BasePalette):
|
||||
|
||||
PALETTES = {
|
||||
Palette.IIGS: IIGSPalette,
|
||||
Palette.NTSC: NTSCPalette
|
||||
Palette.NTSC: NTSCPalette,
|
||||
Palette.MONO: MonoPalette
|
||||
} # type: Dict[Palette, Type[BasePalette]]
|
||||
|
@@ -184,7 +184,8 @@ class Bitmap:
|
||||
self.SCREEN_BYTES = np.uint64(len(self.BYTE_MASKS)) # type: np.uint64
|
||||
|
||||
self.packed = np.empty(
|
||||
shape=(32, 128), dtype=np.uint64) # type: np.ndarray
|
||||
shape=(32, np.uint64(256) // self.SCREEN_BYTES), dtype=np.uint64
|
||||
) # type: np.ndarray
|
||||
self._pack()
|
||||
|
||||
# TODO: don't leak headers/footers across screen rows. We should be using
|
||||
@@ -213,17 +214,24 @@ class Bitmap:
|
||||
# decode the effective colours at the beginning of the 22-bit tuple
|
||||
prev_col = np.roll(body, 1, axis=1).astype(np.uint64)
|
||||
header = self._make_header(prev_col)
|
||||
# Don't leak header across page boundaries
|
||||
header[:, 0] = 0
|
||||
if header.shape[1]:
|
||||
# Don't leak header across page boundaries
|
||||
header[:, 0] = 0
|
||||
|
||||
# Append first 3 bits of next even byte so we can correctly
|
||||
# decode the effective colours at the end of the 22-bit tuple
|
||||
next_col = np.roll(body, -1, axis=1).astype(np.uint64)
|
||||
footer = self._make_footer(next_col)
|
||||
# Don't leak footer across page boundaries
|
||||
footer[:, -1] = 0
|
||||
if footer.shape[1]:
|
||||
# Don't leak footer across page boundaries
|
||||
footer[:, -1] = 0
|
||||
|
||||
self.packed = header ^ body ^ footer
|
||||
packed = body
|
||||
if header.shape[1]:
|
||||
packed ^= header
|
||||
if footer.shape[1]:
|
||||
packed ^= footer
|
||||
self.packed = packed
|
||||
|
||||
@staticmethod
|
||||
def masked_update(
|
||||
@@ -262,12 +270,14 @@ class Bitmap:
|
||||
"""Update packed representation of changing main/aux memory."""
|
||||
|
||||
byte_offset = self.byte_offset(offset, is_aux)
|
||||
packed_offset = offset // 2
|
||||
packed_offset = int(offset // self.SCREEN_BYTES)
|
||||
|
||||
self.packed[page, packed_offset] = self.masked_update(
|
||||
byte_offset, self.packed[page, packed_offset], value)
|
||||
self._fix_scalar_neighbours(page, packed_offset, byte_offset)
|
||||
|
||||
assert self.packed[page, packed_offset] < 128
|
||||
|
||||
if is_aux:
|
||||
self.aux_memory.write(page, offset, value)
|
||||
else:
|
||||
@@ -280,12 +290,15 @@ class Bitmap:
|
||||
byte_offset: int) -> None:
|
||||
"""Fix up column headers/footers when updating a (page, offset)."""
|
||||
|
||||
if byte_offset == 0 and offset > 0:
|
||||
if byte_offset == 0 and offset > 0 and self.HEADER_BITS:
|
||||
self.packed[page, offset - 1] = self._fix_column_left(
|
||||
self.packed[page, offset - 1],
|
||||
self.packed[page, offset]
|
||||
)
|
||||
elif byte_offset == (self.SCREEN_BYTES - 1) and offset < 127:
|
||||
elif (
|
||||
byte_offset == (self.SCREEN_BYTES - 1) and offset < 127 and
|
||||
self.FOOTER_BITS
|
||||
):
|
||||
# Need to also update the 3-bit header of the next column
|
||||
self.packed[page, offset + 1] = self._fix_column_right(
|
||||
self.packed[page, offset + 1],
|
||||
@@ -330,12 +343,12 @@ class Bitmap:
|
||||
|
||||
# Propagate new value into neighbouring byte headers/footers if
|
||||
# necessary
|
||||
if byte_offset == 0:
|
||||
if byte_offset == 0 and self.HEADER_BITS:
|
||||
# Need to also update the footer of the preceding column
|
||||
shifted_left = np.roll(ary, -1, axis=1)
|
||||
self._fix_column_left(ary, shifted_left)
|
||||
|
||||
elif byte_offset == (self.SCREEN_BYTES - 1):
|
||||
elif byte_offset == (self.SCREEN_BYTES - 1) and self.FOOTER_BITS:
|
||||
# Need to also update the header of the next column
|
||||
shifted_right = np.roll(ary, 1, axis=1)
|
||||
self._fix_column_right(ary, shifted_right)
|
||||
@@ -429,22 +442,32 @@ class Bitmap:
|
||||
if content is not None:
|
||||
compare_packed = self.masked_update(o, source_packed, content)
|
||||
self._fix_array_neighbours(compare_packed, o)
|
||||
# print("content = %s" % content)
|
||||
else:
|
||||
compare_packed = source_packed
|
||||
|
||||
# print("source_packed %s" % source_packed)
|
||||
# print("compare %s" % compare_packed)
|
||||
# print("packed %s" % self.packed)
|
||||
# Pixels influenced by byte offset o
|
||||
source_pixels = self.mask_and_shift_data(compare_packed, o)
|
||||
target_pixels = self.mask_and_shift_data(self.packed, o)
|
||||
aux_offset = 0 if is_aux else 1 # XXX
|
||||
source_pixels = self.mask_and_shift_data(
|
||||
compare_packed[:, aux_offset::2], o)
|
||||
target_pixels = self.mask_and_shift_data(
|
||||
self.packed[:, aux_offset::2], o)
|
||||
|
||||
# Concatenate N-bit source and target into 2N-bit values
|
||||
pair = (source_pixels << self.MASKED_BITS) + target_pixels
|
||||
dist = self.edit_distances(self.palette)[o][pair].reshape(
|
||||
pair.shape)
|
||||
dists.append(dist)
|
||||
# print(source_pixels, target_pixels)
|
||||
# print(dist)
|
||||
# assert False
|
||||
|
||||
# Interleave even/odd columns
|
||||
diff[:, 0::2] = dists[0]
|
||||
diff[:, 1::2] = dists[1]
|
||||
for i in range(len(offsets)):
|
||||
# Interleave columns
|
||||
diff[:, i::len(offsets)] = dists[i]
|
||||
|
||||
return diff
|
||||
|
||||
@@ -468,28 +491,35 @@ class Bitmap:
|
||||
diff = np.ndarray((256,), dtype=np.int32)
|
||||
|
||||
offsets = self._byte_offsets(is_aux)
|
||||
# print(source_packed, target_packed)
|
||||
# assert source_packed.dtype == np.uint64, source_packed.dtype
|
||||
# assert target_packed.dtype == np.uint64, target_packed.dtype
|
||||
|
||||
dists = []
|
||||
for o in offsets:
|
||||
if content is not None:
|
||||
compare_packed = self.masked_update(o, source_packed, content)
|
||||
# print(content, source_packed, compare_packed)
|
||||
self._fix_array_neighbours(compare_packed, o)
|
||||
# print(compare_packed)
|
||||
else:
|
||||
compare_packed = source_packed
|
||||
|
||||
# Pixels influenced by byte offset o
|
||||
source_pixels = self.mask_and_shift_data(compare_packed, o)
|
||||
target_pixels = self.mask_and_shift_data(target_packed, o)
|
||||
|
||||
aux_offset = 0 if is_aux else 1 # XXX
|
||||
source_pixels = self.mask_and_shift_data(
|
||||
compare_packed[:, aux_offset::2], o)
|
||||
target_pixels = self.mask_and_shift_data(
|
||||
target_packed[:, aux_offset::2], o)
|
||||
# Concatenate N-bit source and target into 2N-bit values
|
||||
pair = (source_pixels << self.MASKED_BITS) + target_pixels
|
||||
dist = self.edit_distances(self.palette)[o][pair].reshape(
|
||||
pair.shape)
|
||||
dists.append(dist)
|
||||
|
||||
# Interleave even/odd columns
|
||||
diff[0::2] = dists[0]
|
||||
diff[1::2] = dists[1]
|
||||
for i in range(len(offsets)):
|
||||
# Interleave columns
|
||||
diff[i::len(offsets)] = dists[i]
|
||||
|
||||
return diff
|
||||
|
||||
@@ -1005,3 +1035,118 @@ class DHGRBitmap(Bitmap):
|
||||
update = (new_value & np.uint64(0x7f)) << np.uint64(
|
||||
7 * byte_offset + 3)
|
||||
return masked_value ^ update
|
||||
|
||||
|
||||
class DHGRMonoBitmap(Bitmap):
|
||||
"""Packed bitmap representation of DHGR mono screen memory.
|
||||
|
||||
Unlike the colour cases, where the display colour of a pixel is influenced
|
||||
by the pixels to the left of it, the mono representation is trivial.
|
||||
|
||||
With this masked representation, we can precompute an edit distance for the
|
||||
pixel changes resulting from all possible DHGR byte stores, see
|
||||
make_edit_distance.py.
|
||||
|
||||
XXX
|
||||
The edit distance matrix is encoded by concatenating the 13-bit source
|
||||
and target masked values into a 26-bit pair, which indexes into the
|
||||
edit_distance array to give the corresponding edit distance.
|
||||
"""
|
||||
|
||||
NAME = 'DHGR_MONO'
|
||||
|
||||
# Packed representation is 0 + 14 + 0 = 14 bits
|
||||
HEADER_BITS = np.uint64(0)
|
||||
BODY_BITS = np.uint64(7)
|
||||
FOOTER_BITS = np.uint64(0)
|
||||
|
||||
# Masked representation selecting the influence of each byte offset
|
||||
MASKED_BITS = np.uint64(7) # 7-bit body + 0-bit header + 0-bit footer
|
||||
|
||||
# Masking is 1:1 with screen dots
|
||||
MASKED_DOTS = np.uint64(7)
|
||||
|
||||
BYTE_MASKS = [
|
||||
np.uint64(0b1111111),
|
||||
]
|
||||
|
||||
BYTE_SHIFTS = [np.uint64(0)]
|
||||
|
||||
PHASES = [0]
|
||||
|
||||
@staticmethod
|
||||
def _make_header(col: IntOrArray) -> IntOrArray:
|
||||
"""Extract upper XXX bits of body for header of next column."""
|
||||
# XXX return None
|
||||
if isinstance(col, np.ndarray):
|
||||
return np.zeros((col.shape[0], 0), dtype=col.dtype)
|
||||
return 0
|
||||
|
||||
def _body(self) -> np.ndarray:
|
||||
"""Pack related screen bytes into an efficient representation.
|
||||
|
||||
For DHGR we first strip off the (unused) palette bit to produce
|
||||
7-bit values, then interleave aux and main memory columns and pack
|
||||
these 7-bit values into 28-bits. This sequentially encodes 7 4-bit
|
||||
DHGR pixels, which is the "repeating unit" of the DHGR screen, and
|
||||
in a form that is convenient to operate on.
|
||||
|
||||
We also shift to make room for the 3-bit header.
|
||||
"""
|
||||
|
||||
# Palette bit is unused for DHGR so mask it out
|
||||
aux = (self.aux_memory.page_offset & 0x7f).astype(np.uint8)
|
||||
main = (self.main_memory.page_offset & 0x7f).astype(np.uint8)
|
||||
|
||||
body = np.empty((aux.shape[0], aux.shape[1] + main.shape[1]),
|
||||
dtype=np.uint8)
|
||||
body[:, 0::2] = aux
|
||||
body[:, 1::2] = main
|
||||
# print(body)
|
||||
return body
|
||||
|
||||
@staticmethod
|
||||
def _make_footer(col: IntOrArray) -> IntOrArray:
|
||||
"""Extract lower XXX bits of body for footer of previous column."""
|
||||
# XXX return None
|
||||
if isinstance(col, np.ndarray):
|
||||
return np.zeros((col.shape[0], 0), dtype=col.dtype)
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
@functools.lru_cache(None)
|
||||
def byte_offset(page_offset: int, is_aux: bool) -> int:
|
||||
"""Returns 0..3 packed byte offset for a given page_offset and is_aux"""
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
@functools.lru_cache(None)
|
||||
def _byte_offsets(is_aux: bool) -> Tuple[int, int]:
|
||||
return (0,)
|
||||
|
||||
@classmethod
|
||||
def to_dots(cls, masked_val: int, byte_offset: int) -> int:
|
||||
"""Convert masked representation to bit sequence of display dots.
|
||||
|
||||
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,
|
||||
old_value: IntOrArray,
|
||||
new_value: np.uint8) -> IntOrArray:
|
||||
"""Update int/array to store new value at byte_offset in every entry.
|
||||
|
||||
Does not patch up headers/footers of neighbouring columns.
|
||||
"""
|
||||
|
||||
if isinstance(old_value, np.ndarray):
|
||||
res = np.empty_like(old_value)
|
||||
res.fill(new_value & np.uint64(0x7f))
|
||||
return res
|
||||
else:
|
||||
return new_value & np.uint64(0x7f)
|
||||
|
@@ -37,24 +37,31 @@ class Video:
|
||||
# Initialize empty screen
|
||||
self.memory_map = screen.MemoryMap(
|
||||
screen_page=1) # type: screen.MemoryMap
|
||||
if self.mode == mode.DHGR:
|
||||
if self.mode in {VideoMode.DHGR, VideoMode.DHGR_MONO}:
|
||||
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
|
||||
)
|
||||
if self.mode == VideoMode.DHGR:
|
||||
self.pixelmap = screen.DHGRBitmap(
|
||||
palette=palette,
|
||||
main_memory=self.memory_map,
|
||||
aux_memory=self.aux_memory_map
|
||||
)
|
||||
else:
|
||||
self.pixelmap = screen.DHGRMonoBitmap(
|
||||
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,
|
||||
)
|
||||
# print("pix %s" % self.pixelmap.packed)
|
||||
|
||||
# Accumulates pending edit weights across frames
|
||||
self.update_priority = np.zeros((32, 256), dtype=np.int32)
|
||||
if self.mode == mode.DHGR:
|
||||
if self.mode in {VideoMode.DHGR, VideoMode.DHGR_MONO}:
|
||||
self.aux_update_priority = np.zeros((32, 256), dtype=np.int32)
|
||||
|
||||
# Indicates whether we have run out of work for the main/aux banks.
|
||||
@@ -101,7 +108,7 @@ class Video:
|
||||
) -> Iterator[Tuple[int, int, List[int]]]:
|
||||
"""Transform encoded screen to sequence of change tuples."""
|
||||
|
||||
if self.mode == VideoMode.DHGR and is_aux:
|
||||
if self.mode in {VideoMode.DHGR, VideoMode.DHGR_MONO} and is_aux:
|
||||
target = target_pixelmap.aux_memory
|
||||
else:
|
||||
target = target_pixelmap.main_memory
|
||||
@@ -132,7 +139,7 @@ class Video:
|
||||
|
||||
offsets = [offset]
|
||||
content = target.page_offset[page, offset]
|
||||
if self.mode == VideoMode.DHGR:
|
||||
if self.mode in {VideoMode.DHGR, VideoMode.DHGR_MONO}:
|
||||
# DHGR palette bit not expected to be set
|
||||
assert content < 0x80
|
||||
|
||||
@@ -206,44 +213,44 @@ class Video:
|
||||
# deterministic point in time when we can assert that all diffs should
|
||||
# have been resolved.
|
||||
# TODO: add flag to enable debug assertions
|
||||
# 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 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've finished both main and aux pages, there should be no residual
|
||||
# # diffs in packed representation
|
||||
# all_done = self.out_of_work[True] and self.out_of_work[False]
|
||||
# if all_done and not np.array_equal(self.pixelmap.packed,
|
||||
# target_pixelmap.packed):
|
||||
# diffs = np.nonzero(
|
||||
# self.pixelmap.packed != target_pixelmap.packed)
|
||||
# print("is_aux: %s" % is_aux)
|
||||
# for i in range(len(diffs[0])):
|
||||
# diff_p = diffs[0][i]
|
||||
# diff_o = diffs[1][i]
|
||||
# print("(%d, %d): got %d want %d" % (
|
||||
# diff_p, diff_o, self.pixelmap.packed[diff_p, diff_o],
|
||||
# target_pixelmap.packed[diff_p, diff_o]))
|
||||
# assert False
|
||||
all_done = self.out_of_work[True] and self.out_of_work[False]
|
||||
if all_done and not np.array_equal(self.pixelmap.packed,
|
||||
target_pixelmap.packed):
|
||||
diffs = np.nonzero(
|
||||
self.pixelmap.packed != target_pixelmap.packed)
|
||||
print("is_aux: %s" % is_aux)
|
||||
for i in range(len(diffs[0])):
|
||||
diff_p = diffs[0][i]
|
||||
diff_o = diffs[1][i]
|
||||
print("(%d, %d): got %d want %d" % (
|
||||
diff_p, diff_o, self.pixelmap.packed[diff_p, diff_o],
|
||||
target_pixelmap.packed[diff_p, diff_o]))
|
||||
assert False
|
||||
|
||||
# If we run out of things to do, pad forever
|
||||
content = target.page_offset[0, 0]
|
||||
|
Reference in New Issue
Block a user