Add support for multiple palettes

This commit is contained in:
kris 2021-01-25 22:28:00 +00:00
parent 923ec0cf6d
commit dd8cf07c49
3 changed files with 114 additions and 68 deletions

View File

@ -48,9 +48,13 @@ def main():
"and looking ahead over next --lookahead pixels to optimize the "
"colour sequence.")
)
parser.add_argument(
'--palette', type=str, choices=list(palette_py.PALETTES.keys()),
default=palette_py.DEFAULT_PALETTE,
help="RGB colour palette to dither to.")
args = parser.parse_args()
palette = palette_py.Palette()
palette = palette_py.PALETTES[args.palette]()
if args.resolution == 560:
screen = screen_py.DHGR560Screen(palette)
lookahead = args.lookahead
@ -59,7 +63,6 @@ def main():
lookahead = 0
image = image_py.open(args.input)
# TODO: better performance with malloc'ed array?
resized = np.array(
image_py.resize(image, screen.X_RES, screen.Y_RES)).astype(np.float32)
if args.show_input:
@ -85,11 +88,11 @@ def main():
# Show output image
out_image = Image.fromarray(image_py.linear_to_srgb(output_rgb).astype(
np.uint8))
out_image = image_py.resize(out_image, 560, 384, srgb_output=True)
if args.show_output:
# out_image.show()
image_py.resize(out_image, 560, 384, srgb_output=True).show()
out_image.show()
outfile = os.path.join(os.path.splitext(args.output)[0] + "_preview.png")
outfile = os.path.join(os.path.splitext(args.output)[0] + "-preview.png")
out_image.save(outfile, "PNG")
with open(args.output, "wb") as f:

View File

@ -5,33 +5,7 @@ import image
class Palette:
RGB = None
SRGB = None
DOTS = None
def __init__(self):
# CIE2000 colour distance matrix from 24-bit RGB tuple to 4-bit
# palette colour.
self.distances = np.memmap("distances.npy", mode="r+",
dtype=np.uint8, shape=(16777216, 16))
# Default bmp2dhr palette
sRGB = {
0: np.array((0, 0, 0)), # Black
8: np.array((148, 12, 125)), # Magenta
4: np.array((99, 77, 0)), # Brown
12: np.array((249, 86, 29)), # Orange
2: np.array((51, 111, 0)), # Dark green
10: np.array((126, 126, 126)), # Grey2
6: np.array((67, 200, 0)), # Green
14: np.array((221, 206, 23)), # Yellow
1: np.array((32, 54, 212)), # Dark blue
9: np.array((188, 55, 255)), # Violet
5: np.array((126, 126, 126)), # Grey1
13: np.array((255, 129, 236)), # Pink
3: np.array((7, 168, 225)), # Med blue
11: np.array((158, 172, 255)), # Light blue
7: np.array((93, 248, 133)), # Aqua
15: np.array((255, 255, 255)), # White
}
DISTANCES_PATH = None
# Maps palette values to screen dots. Note that these are the same as
# the binary values in reverse order.
@ -57,8 +31,48 @@ class Palette:
for k, v in DOTS.items():
DOTS_TO_4BIT[v] = k
def __init__(self, load_distances=True):
if load_distances:
# CIE2000 colour distance matrix from 24-bit RGB tuple to 4-bit
# palette colour.
self.distances = np.memmap(self.DISTANCES_PATH, mode="r+",
dtype=np.uint8, shape=(16777216, 16))
self.RGB = {}
for k, v in self.SRGB.items():
self.RGB[k] = (np.clip(image.srgb_to_linear_array(v / 255), 0.0,
1.0) * 255).astype(np.uint8)
class ToHgrPalette(Palette):
DISTANCES_PATH = "data/distances_tohgr.data"
# Default tohgr/bmp2dhr palette
SRGB = {
0: np.array((0, 0, 0)), # Black
8: np.array((148, 12, 125)), # Magenta
4: np.array((99, 77, 0)), # Brown
12: np.array((249, 86, 29)), # Orange
2: np.array((51, 111, 0)), # Dark green
10: np.array((126, 126, 126)), # Grey2
6: np.array((67, 200, 0)), # Green
14: np.array((221, 206, 23)), # Yellow
1: np.array((32, 54, 212)), # Dark blue
9: np.array((188, 55, 255)), # Violet
5: np.array((126, 126, 126)), # Grey1
13: np.array((255, 129, 236)), # Pink
3: np.array((7, 168, 225)), # Med blue
11: np.array((158, 172, 255)), # Light blue
7: np.array((93, 248, 133)), # Aqua
15: np.array((255, 255, 255)), # White
}
class OpenEmulatorPalette(Palette):
DISTANCES_PATH = "data/distances_openemulator.data"
# OpenEmulator
sRGB = {
SRGB = {
0: np.array((0, 0, 0)), # Black
8: np.array((203, 0, 121)), # Magenta
4: np.array((99, 103, 0)), # Brown
@ -77,27 +91,34 @@ class Palette:
15: np.array((244, 247, 244)), # White
}
# # Virtual II (sRGB)
# sRGB = {
# (False, False, False, False): np.array((0, 0, 0)), # Black
# (False, False, False, True): np.array((231,36,66)), # Magenta
# (False, False, True, False): np.array((154,104,0)), # Brown
# (False, False, True, True): np.array((255,124,0)), # Orange
# (False, True, False, False): np.array((0,135,45)), # Dark green
# (False, True, False, True): np.array((104,104,104)), # Grey2 XXX
# (False, True, True, False): np.array((0,222,0)), # Green
# (False, True, True, True): np.array((255,252,0)), # Yellow
# (True, False, False, False): np.array((1,30,169)), # Dark blue
# (True, False, False, True): np.array((230,73,228)), # Violet
# (True, False, True, False): np.array((185,185,185)), # Grey1 XXX
# (True, False, True, True): np.array((255,171,153)), # Pink
# (True, True, False, False): np.array((47,69,255)), # Med blue
# (True, True, False, True): np.array((120,187,255)), # Light blue
# (True, True, True, False): np.array((83,250,208)), # Aqua
# (True, True, True, True): np.array((255, 255, 255)), # White
# }
RGB = {}
for k, v in sRGB.items():
RGB[k] = (np.clip(image.srgb_to_linear_array(v / 255), 0.0,
1.0) * 255).astype(
np.uint8)
class VirtualIIPalette(Palette):
DISTANCES_PATH = "data/distances_virtualii.data"
SRGB = {
0: np.array((0, 0, 0)), # Black
8: np.array((231, 36, 66)), # Magenta
4: np.array((154, 104, 0)), # Brown
12: np.array((255, 124, 0)), # Orange
2: np.array((0, 135, 45)), # Dark green
10: np.array((104, 104, 104)), # Grey2
6: np.array((0, 222, 0)), # Green
14: np.array((255, 252, 0)), # Yellow
1: np.array((1, 30, 169)), # Dark blue
9: np.array((230, 73, 228)), # Violet
5: np.array((185, 185, 185)), # Grey1
13: np.array((255, 171, 153)), # Pink
3: np.array((47, 69, 255)), # Med blue
11: np.array((120, 187, 255)), # Light blue
7: np.array((83, 250, 208)), # Aqua
15: np.array((255, 255, 255)), # White
}
PALETTES = {
'openemulator': OpenEmulatorPalette,
'virtualii': VirtualIIPalette,
'tohgr': ToHgrPalette,
}
DEFAULT_PALETTE = 'openemulator'

View File

@ -1,3 +1,4 @@
import argparse
import image
import palette as palette_py
import colour.difference
@ -14,14 +15,17 @@ def rgb_to_lab(rgb: np.ndarray):
return colour.XYZ_to_Lab(xyz)
def nearest_colours(palette):
diffs = np.empty((COLOURS ** 3, 16), dtype=np.float32)
def all_lab_colours():
all_rgb = np.array(tuple(np.ndindex(COLOURS, COLOURS, COLOURS)),
dtype=np.uint8)
all_lab = rgb_to_lab(all_rgb)
return rgb_to_lab(all_rgb)
for i, palette_rgb in palette.RGB.items():
print(i)
def nearest_colours(palette, all_lab):
diffs = np.empty((COLOURS ** 3, 16), dtype=np.float32)
for i, palette_rgb in sorted(palette.RGB.items()):
print("...palette colour %d" % i)
palette_lab = rgb_to_lab(palette_rgb)
diffs[:, i] = colour.difference.delta_E_CIE2000(all_lab, palette_lab)
@ -30,12 +34,30 @@ def nearest_colours(palette):
def main():
palette = palette_py.Palette()
n = nearest_colours(palette)
out = np.memmap(filename="distances_default.npy", mode="w+", dtype=np.uint8,
shape=n.shape)
out[:] = n[:]
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("Processing palette %s" % palette_name)
palette = palette_py.PALETTES[palette_name](load_distances=False)
n = nearest_colours(palette, all_lab)
out = np.memmap(filename=palette.DISTANCES_PATH, mode="w+",
dtype=np.uint8, shape=n.shape)
out[:] = n[:]
if __name__ == "__main__":
main()
main()