mirror of
https://github.com/KrisKennaway/ii-pix.git
synced 2024-11-07 22:06:34 +00:00
e0b732cdaa
can't figure out why contrast=2 is needed (openemulator uses a default value of 1, so there must be a factor of 2x somewhere), or where the slight hue rotation comes from - perhaps this is somehow introduced by the more complex band-pass filtering that openemulator does? I needed to also account for the DHGR timing difference that introduces a phase offset of 1 pixel between the memory values and displayed pixel timings. Fix a last-minute bug with palette precomputation.
88 lines
3.1 KiB
Python
88 lines
3.1 KiB
Python
"""Precompute CIE2000 perceptual colour distance matrices.
|
|
|
|
The matrix of delta-E values is computed for all pairs of 24-bit RGB values,
|
|
and Apple II target palette values. This is written out as a file that is
|
|
mmapped at runtime for efficient access. For a 16-colour target palette this
|
|
file is 256MB; for a 256-colour (NTSC) target palette it is 4GB.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
|
|
import image
|
|
import palette as palette_py
|
|
import colour.difference
|
|
import numpy as np
|
|
|
|
RGB_LEVELS = 256
|
|
# Largest possible value of delta_E_CIE2000 between two RGB values
|
|
DELTA_E_MAX = 120 # TODO: fine-tune
|
|
|
|
|
|
def rgb_to_lab(rgb: np.ndarray):
|
|
srgb = np.clip(
|
|
image.linear_to_srgb_array(rgb.astype(np.float32) / 255), 0.0,
|
|
1.0)
|
|
xyz = colour.sRGB_to_XYZ(srgb)
|
|
return colour.XYZ_to_Lab(xyz)
|
|
|
|
|
|
def all_lab_colours():
|
|
all_rgb = np.array(tuple(np.ndindex(RGB_LEVELS, RGB_LEVELS, RGB_LEVELS)),
|
|
dtype=np.uint8)
|
|
return rgb_to_lab(all_rgb)
|
|
|
|
|
|
def nearest_colours(palette, all_lab, diffs):
|
|
palette_size = len(palette.RGB)
|
|
palette_labs = np.empty((palette_size, 3), dtype=np.float)
|
|
for i, palette_rgb in palette.RGB.items():
|
|
palette_labs[i, :] = rgb_to_lab(palette_rgb)
|
|
|
|
print("Computing all 24-bit palette diffs:")
|
|
for i in range(palette_size):
|
|
print(" %d/%d" % (i, palette_size))
|
|
# Compute all palette diffs for a block of 65536 successive RGB
|
|
# source values at once, which bounds the memory use while also writing
|
|
# contiguously to the mmapped array.
|
|
diffs[i * (1 << 16):(i + 1) * (1 << 16), :] = (
|
|
colour.difference.delta_E_CIE2000(
|
|
all_lab[i * (1 << 16):(i + 1) * (
|
|
1 << 16)].reshape((1 << 16, 1, 3)),
|
|
palette_labs.reshape((1, palette_size, 3))) / DELTA_E_MAX *
|
|
255).astype(np.uint8)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--palette', type=str, choices=list(
|
|
palette_py.PALETTES.keys()),
|
|
default=palette_py.DEFAULT_PALETTE,
|
|
help="Palette for which to compute distance matrix.")
|
|
parser.add_argument('--all', type=bool, default=False,
|
|
help="Whether to compute distances for all palettes")
|
|
args = parser.parse_args()
|
|
|
|
if args.all:
|
|
palette_names = list(palette_py.PALETTES.keys())
|
|
else:
|
|
palette_names = [args.palette]
|
|
|
|
print("Precomputing matrix of all 24-bit LAB colours")
|
|
all_lab = all_lab_colours()
|
|
for palette_name in palette_names:
|
|
print("Creating distance file for palette %s" % palette_name)
|
|
palette = palette_py.PALETTES[palette_name](load_distances=False)
|
|
try:
|
|
os.mkdir(os.path.dirname(palette.DISTANCES_PATH))
|
|
except FileExistsError:
|
|
pass
|
|
out = np.memmap(filename=palette.DISTANCES_PATH, mode="w+",
|
|
dtype=np.uint8, shape=(RGB_LEVELS ** 3,
|
|
len(palette.RGB)))
|
|
nearest_colours(palette, all_lab, out)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|