From 2bbd65a0791bdd645d382595a0ef3c32d27ec039 Mon Sep 17 00:00:00 2001 From: kris Date: Mon, 25 Jan 2021 23:16:46 +0000 Subject: [PATCH] Add some comments and docstrings --- convert.py | 12 ++++++------ dither_pattern.py | 2 ++ image.py | 3 ++- palette.py | 2 ++ precompute_distance.py | 7 +++++++ screen.py | 19 +++++++++++++++++++ 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/convert.py b/convert.py index 377c299..74994a7 100644 --- a/convert.py +++ b/convert.py @@ -1,3 +1,5 @@ +"""Image converter to Apple II Double Hi-Res format.""" + import argparse import os.path import time @@ -13,11 +15,8 @@ import screen as screen_py # TODO: -# - support alternate palettes properly -# - compare to bmp2dhr and a2bestpix # - support LR/DLR # - support HGR -# - README def main(): @@ -62,6 +61,7 @@ def main(): screen = screen_py.DHGR140Screen(palette) lookahead = 0 + # Open and resize source image image = image_py.open(args.input) resized = np.array( image_py.resize(image, screen.X_RES, screen.Y_RES)).astype(np.float32) @@ -70,10 +70,10 @@ def main(): dither = dither_pattern.PATTERNS[args.dither]() - start = time.time() + # start = time.time() output_4bit, output_rgb = dither_pyx.dither_image( screen, resized, dither, lookahead) - print(time.time() - start) + # print(time.time() - start) if args.resolution == 140: # Show un-fringed 140px output image @@ -92,9 +92,9 @@ def main(): if args.show_output: out_image.show() + # Save Double hi-res image outfile = os.path.join(os.path.splitext(args.output)[0] + "-preview.png") out_image.save(outfile, "PNG") - with open(args.output, "wb") as f: f.write(bytes(screen.main)) f.write(bytes(screen.aux)) diff --git a/dither_pattern.py b/dither_pattern.py index 0092454..7380cf0 100644 --- a/dither_pattern.py +++ b/dither_pattern.py @@ -1,3 +1,5 @@ +"""Error diffusion dither patterns.""" + import numpy as np diff --git a/image.py b/image.py index 1e7a554..4b45d55 100644 --- a/image.py +++ b/image.py @@ -1,3 +1,5 @@ +"""Image transformation functions.""" + import numpy as np from PIL import Image @@ -34,7 +36,6 @@ def resize(image: Image, x_res, y_res, srgb_output: bool = False) -> Image: # Convert to linear RGB before rescaling so that colour interpolation is # in linear space linear = srgb_to_linear(np.asarray(image)).astype(np.uint8) - # linear = np.asarray(image).astype(np.uint8) res = Image.fromarray(linear).resize((x_res, y_res), Image.LANCZOS) if srgb_output: return Image.fromarray( diff --git a/palette.py b/palette.py index 2d53988..108c056 100644 --- a/palette.py +++ b/palette.py @@ -1,3 +1,5 @@ +"""RGB colour palettes to target for Apple II image conversions.""" + import numpy as np import image diff --git a/precompute_distance.py b/precompute_distance.py index f59a906..4a70100 100644 --- a/precompute_distance.py +++ b/precompute_distance.py @@ -1,3 +1,10 @@ +"""Precompute CIE2000 perceptual colour distance matrices. + +The matrix of delta-E values is computed for all pairs of 24-bit RGB values, +and 4-bit Apple II target palette. This is a 256MB file that is mmapped at +runtime for efficient access. +""" + import argparse import image import palette as palette_py diff --git a/screen.py b/screen.py index 02ecffb..021f18a 100644 --- a/screen.py +++ b/screen.py @@ -1,3 +1,5 @@ +"""Representation of Apple II screen memory.""" + import numpy as np import palette as palette_py @@ -23,9 +25,15 @@ class Screen: return 1024 * c + 128 * b + 40 * a def _image_to_bitmap(self, image: np.ndarray) -> np.ndarray: + """Converts 4-bit image to 2-bit image bitmap. + + Each 4-bit colour value maps to a sliding window of 4 successive pixels, + via x%4. + """ raise NotImplementedError def pack(self, image: np.ndarray): + """Packs an image into memory format (8k AUX + 8K MAIN).""" bitmap = self._image_to_bitmap(image) # The DHGR display encodes 7 pixels across interleaved 4-byte sequences # of AUX and MAIN memory, as follows: @@ -53,6 +61,12 @@ class Screen: return bitmap def bitmap_to_image_rgb(self, bitmap: np.ndarray) -> np.ndarray: + """Convert our 2-bit bitmap image into a RGB image. + + Colour at every pixel is determined by the value of a 4-bit sliding + window indexed by x % 4, which gives the index into our 16-colour RGB + palette. + """ image_rgb = np.empty((192, 560, 3), dtype=np.uint8) for y in range(self.Y_RES): pixel = [False, False, False, False] @@ -63,6 +77,7 @@ class Screen: return image_rgb def pixel_palette_options(self, last_pixel_4bit, x: int): + """Returns available colours for given x pos and 4-bit colour of x-1""" raise NotImplementedError @@ -85,6 +100,7 @@ class DHGR140Screen(Screen): return bitmap def pixel_palette_options(self, last_pixel_4bit, x: int): + # All 16 colour choices are available at every x position. return ( np.array(list(self.palette.RGB.keys()), dtype=np.uint8), np.array(list(self.palette.RGB.values()), dtype=np.uint8)) @@ -107,6 +123,9 @@ class DHGR560Screen(Screen): return bitmap def pixel_palette_options(self, last_pixel_4bit, x: int): + # The two available colours for position x are given by the 4-bit + # value of position x-1, and the 4-bit value produced by toggling the + # value of the x % 4 bit (the current value of NTSC phase) last_dots = self.palette.DOTS[last_pixel_4bit] other_dots = list(last_dots) other_dots[x % 4] = not other_dots[x % 4]