ii-pix/dither_pattern.py
KrisKennaway 3aa29f2d2c
Add support for hi-res conversions (#11)
Hi-Res is essentially a more constrained version of Double Hi-Res, in which only about half of the 560 horizontal screen pixels can be independently addressed.

In particular an 8 bit byte in screen memory controls 14 or 15 screen pixels.  Bits 0-7 are doubled, and bit 8 shifts these 14 dots to the right if enabled.  In this case bit 7 of the previous byte is repeated a third time.

This means that we have to optimize all 8 bits at once and move forward in increments of 14 screen pixels.

There's also a timing difference that results in a phase shift of the NTSC colour signal, which means the mappings from dot patterns to effective colours are rotated.

Error diffusion seems to give best results if we only distribute about 2/3 of the quantization error according to the dither pattern.
2023-02-03 00:40:32 +00:00

94 lines
2.4 KiB
Python

"""Error diffusion dither patterns."""
import numpy as np
class DitherPattern:
PATTERN = None
ORIGIN = None
def __init__(self, error_fraction=1.0):
self.PATTERN *= error_fraction
class NoDither(DitherPattern):
"""No dithering."""
PATTERN = np.array(((0, 0), (0, 0)),
dtype=np.float32).reshape(2, 2) / np.float32(16)
ORIGIN = (0, 1)
class FloydSteinbergDither(DitherPattern):
"""Floyd-Steinberg dither."""
# 0 * 7
# 3 5 1
PATTERN = np.array(((0, 0, 7), (3, 5, 1)),
dtype=np.float32).reshape(2, 3) / np.float32(16)
ORIGIN = (0, 1)
class FloydSteinbergDither2(DitherPattern):
"""Floyd-Steinberg dither."""
# 0 * 7
# 3 5 1
PATTERN = np.array(
((0, 0, 0, 0, 0, 7),
(3, 5, 1, 0, 0, 0)),
dtype=np.float32).reshape(2, 6) / np.float32(16)
ORIGIN = (0, 2)
class BuckelsDither(DitherPattern):
"""Default dither from bmp2dhr."""
# 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)),
dtype=np.float32).reshape(3, 4) / np.float32(8)
ORIGIN = (0, 1)
class JarvisDither(DitherPattern):
"""Jarvis-Judice-Ninke dithering."""
# 0 0 X 7 5
# 3 5 7 5 3
# 1 3 5 3 1
PATTERN = np.array(((0, 0, 0, 7, 5), (3, 5, 7, 5, 3), (1, 3, 5, 3, 1)),
dtype=np.float32).reshape(3, 5) / np.float32(48)
ORIGIN = (0, 2)
class JarvisModifiedDither(DitherPattern):
"""Jarvis dithering, modified to diffuse errors to 4 forward x positions.
This works well for double hi-res dithering, since the "best" colour
match to a given pixel may only be accessible up to 4 x-positions further
on. Standard Jarvis dithering only propagates errors for 2 x-positions
in the forward direction, which means that errors may have diffused away
before we get to the pixel that can best take advantage of it.
"""
# 0 0 X 7 5
# 3 5 7 5 3
# 1 3 5 3 1
PATTERN = np.array((
(0, 0, 0, 15, 11, 7, 3),
(3, 5, 7, 5, 3, 1, 0),
(1, 3, 5, 3, 1, 0, 0)), dtype=np.float32).reshape(3, 7)
PATTERN /= np.sum(PATTERN)
ORIGIN = (0, 2)
PATTERNS = {
'floyd': FloydSteinbergDither,
'floyd2': FloydSteinbergDither2,
'floyd-steinberg': FloydSteinbergDither,
'buckels': BuckelsDither,
'jarvis': JarvisDither,
'jarvis-mod': JarvisModifiedDither,
'none': NoDither,
}
DEFAULT_PATTERN = 'floyd'