Add support for multiple palettes
This commit is contained in:
parent
923ec0cf6d
commit
dd8cf07c49
13
convert.py
13
convert.py
|
@ -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:
|
||||
|
|
125
palette.py
125
palette.py
|
@ -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'
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue