Pack output in Apple II screen format and save as binary file.

This commit is contained in:
kris 2020-12-30 10:27:33 +00:00
parent 38d621a097
commit 2458bf98f7
1 changed files with 81 additions and 17 deletions

View File

@ -4,8 +4,8 @@ from PIL import Image
import numpy as np
# TODO:
# - output binary files that can be viewed on Apple II
# - use perceptual colour difference model
# - compare to bmp2dhr and a2bestpix
# - look ahead N pixels and compute all 2^N bit patterns, then minimize
# average error
# - optimize Dither.apply() critical path
@ -95,40 +95,104 @@ class FloydSteinbergDither(Dither):
ORIGIN = (0, 1)
class KennawayDither(Dither):
# 0 * 7 5 3 1
# 3 5 3 1 1 0
PATTERN = np.array(((0, 0, 7, 5, 3, 1), (3, 5, 3, 1, 1, 0)))
class BuckelsDither(Dither):
# 0 * 2 1
# 1 2 1 0
# 0 1 0 0
PATTERN = np.array(((0, 0, 2, 1), (1, 2, 1, 0), (0, 1, 0, 0)))
ORIGIN = (0, 1)
def dither(filename):
def open_image(filename: str) -> Image:
im = Image.open(filename)
if im.mode != "RGB":
im = im.convert("RGB")
im.resize((X_RES, Y_RES), resample=Image.LANCZOS)
im.show()
return im
# ditherer = FloydSteinbergDither()
ditherer = KennawayDither()
def dither_image(image: Image, dither: Dither) -> Image:
for y in range(Y_RES):
print(y)
newpixel = (0, 0, 0)
new_pixel = (0, 0, 0)
for x in range(X_RES):
oldpixel = im.getpixel((x, y))
newpixel = find_closest_color(oldpixel, newpixel, x)
im.putpixel((x, y), tuple(newpixel))
quant_error = oldpixel - newpixel
ditherer.apply(im, x, y, quant_error)
im.show()
old_pixel = image.getpixel((x, y))
new_pixel = find_closest_color(old_pixel, new_pixel, x)
image.putpixel((x, y), tuple(new_pixel))
quant_error = old_pixel - new_pixel
dither.apply(image, x, y, quant_error)
return image
class Screen:
def __init__(self, image: Image):
self.bitmap = np.zeros((Y_RES, X_RES), dtype=np.bool)
self.main = np.zeros(8192, dtype=np.uint8)
self.aux = np.zeros(8192, dtype=np.uint8)
for y in range(Y_RES):
for x in range(X_RES):
pixel = image.getpixel((x, y))
dots = DOTS[pixel]
phase = x % 4
self.bitmap[y, x] = dots[phase]
@staticmethod
def y_to_base_addr(y: int) -> int:
"""Maps y coordinate to screen memory base address."""
a = y // 64
d = y - 64 * a
b = d // 8
c = d - 8 * b
return 1024 * c + 128 * b + 40 * a
def pack(self):
# The DHGR display encodes 7 pixels across interleaved 4-byte sequences
# of AUX and MAIN memory, as follows:
# PBBBAAAA PDDCCCCB PFEEEEDD PGGGGFFF
# Aux N Main N Aux N+1 Main N+1 (N even)
main_col = np.zeros((Y_RES, X_RES // 14), dtype=np.uint8)
aux_col = np.zeros((Y_RES, X_RES // 14), dtype=np.uint8)
for byte_offset in range(80):
column = np.zeros(Y_RES, dtype=np.uint8)
for bit in range(7):
column |= (self.bitmap[:, 7 * byte_offset + bit].astype(
np.uint8) << bit)
if byte_offset % 2 == 0:
aux_col[:, byte_offset // 2] = column
else:
main_col[:, (byte_offset - 1) // 2] = column
for y in range(Y_RES):
addr = self.y_to_base_addr(y)
self.aux[addr:addr + 40] = aux_col[y, :]
self.main[addr:addr + 40] = main_col[y, :]
def main():
parser = argparse.ArgumentParser()
parser.add_argument("input", type=str, help="Input file to process")
parser.add_argument("output", type=str, help="Output file for ")
args = parser.parse_args()
dither(args.input)
image = open_image(args.input)
# image.show()
dither = FloydSteinbergDither()
# dither = BuckelsDither()
output = dither_image(image, dither)
output.show()
screen = Screen(output)
bitmap = Image.fromarray(screen.bitmap.astype('uint8') * 255)
# bitmap.show()
screen.pack()
with open("output.bin", "wb") as f:
f.write(screen.main)
f.write(screen.aux)
if __name__ == "__main__":