mirror of
https://github.com/KrisKennaway/ii-pix.git
synced 2024-11-19 08:30:48 +00:00
ad9515dcf2
Use this to precompute a new ntsc palette with 256 entries (though only 84 unique colours) that are available by appropriate pixel sequences. Unfortunately the precomputed distance matrix for this palette is 4GB! Optimize the precomputation to be less memory hungry, while also making efficient use of the mmapped output file. Add support for dithering images using this 8-bit palette depth, i.e. to optimize for NTSC rendering. This often gives better image quality since more colours are available, especially when modulating areas of similar colour. Fix 140 pixel dithering and render the output including NTSC fringing instead of the unrealistic 140px output that doesn't include it. Add support for rendering output image using any target palette, which is useful e.g. for comparing how an 8-pixel NTSC rendered image will be displayed on an emulator using 4-pixel ntsc emulation (there is usually some colour bias, because the 8 pixel chroma blending tends to average away colours). Switch the output binary format to write AUX memory first, which matches the image format of other utilities.
391 lines
14 KiB
Python
391 lines
14 KiB
Python
"""RGB colour palettes to target for Apple II image conversions."""
|
|
|
|
import numpy as np
|
|
import image
|
|
|
|
|
|
class Palette:
|
|
RGB = {}
|
|
SRGB = None
|
|
DOTS = {}
|
|
DOTS_TO_INDEX = {}
|
|
DISTANCES_PATH = None
|
|
|
|
# How many successive screen pixels are used to compute output pixel
|
|
# palette index.
|
|
PALETTE_DEPTH = None
|
|
|
|
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,
|
|
len(self.SRGB)))
|
|
|
|
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)
|
|
|
|
# Maps palette values to screen dots. Note that these are the same as
|
|
# the binary index values in reverse order.
|
|
for i in range(1 << self.PALETTE_DEPTH):
|
|
self.DOTS[i] = tuple(
|
|
bool(i & (1 << j)) for j in range(self.PALETTE_DEPTH))
|
|
|
|
# Reverse mapping from screen dots to palette index.
|
|
self.DOTS_TO_INDEX = {}
|
|
for k, v in self.DOTS.items():
|
|
self.DOTS_TO_INDEX[v] = k
|
|
|
|
|
|
class ToHgrPalette(Palette):
|
|
DISTANCES_PATH = "data/distances_tohgr.data"
|
|
PALETTE_DEPTH = 4
|
|
|
|
# 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"
|
|
PALETTE_DEPTH = 4
|
|
|
|
# OpenEmulator
|
|
SRGB = {
|
|
0: np.array((0, 0, 0)), # Black
|
|
8: np.array((203, 0, 121)), # Magenta
|
|
4: np.array((99, 103, 0)), # Brown
|
|
12: np.array((244, 78, 0)), # Orange
|
|
2: np.array((0, 150, 0)), # Dark green
|
|
10: np.array((130, 130, 130)), # Grey2
|
|
6: np.array((0, 235, 0)), # Green
|
|
14: np.array((214, 218, 0)), # Yellow
|
|
1: np.array((20, 0, 246)), # Dark blue
|
|
9: np.array((230, 0, 244)), # Violet
|
|
5: np.array((130, 130, 130)), # Grey1
|
|
13: np.array((244, 105, 235)), # Pink
|
|
3: np.array((0, 174, 243)), # Med blue
|
|
11: np.array((160, 156, 244)), # Light blue
|
|
7: np.array((25, 243, 136)), # Aqua
|
|
15: np.array((244, 247, 244)), # White
|
|
}
|
|
|
|
|
|
class VirtualIIPalette(Palette):
|
|
DISTANCES_PATH = "data/distances_virtualii.data"
|
|
PALETTE_DEPTH = 4
|
|
|
|
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
|
|
}
|
|
|
|
|
|
class NTSCPalette(Palette):
|
|
DISTANCES_PATH = 'data/distances_ntsc.data'
|
|
PALETTE_DEPTH = 8
|
|
|
|
# Computed using ntsc_colours.py
|
|
SRGB = {
|
|
0: np.array((0, 0, 0)),
|
|
1: np.array((0, 0, 62)),
|
|
2: np.array((0, 18, 0)),
|
|
3: np.array((0, 3, 28)),
|
|
4: np.array((44, 14, 0)),
|
|
5: np.array((0, 0, 0)),
|
|
6: np.array((0, 32, 0)),
|
|
7: np.array((0, 18, 0)),
|
|
8: np.array((67, 0, 34)),
|
|
9: np.array((22, 0, 96)),
|
|
10: np.array((0, 0, 0)),
|
|
11: np.array((0, 0, 62)),
|
|
12: np.array((112, 0, 0)),
|
|
13: np.array((67, 0, 34)),
|
|
14: np.array((44, 14, 0)),
|
|
15: np.array((0, 0, 0)),
|
|
16: np.array((24, 54, 131)),
|
|
17: np.array((0, 40, 193)),
|
|
18: np.array((0, 73, 97)),
|
|
19: np.array((0, 58, 159)),
|
|
20: np.array((69, 69, 69)),
|
|
21: np.array((24, 54, 131)),
|
|
22: np.array((1, 87, 35)),
|
|
23: np.array((0, 73, 97)),
|
|
24: np.array((91, 36, 165)),
|
|
25: np.array((47, 22, 227)),
|
|
26: np.array((24, 54, 131)),
|
|
27: np.array((0, 40, 193)),
|
|
28: np.array((136, 50, 103)),
|
|
29: np.array((91, 36, 165)),
|
|
30: np.array((69, 69, 69)),
|
|
31: np.array((24, 54, 131)),
|
|
32: np.array((1, 87, 35)),
|
|
33: np.array((0, 73, 97)),
|
|
34: np.array((0, 105, 1)),
|
|
35: np.array((0, 91, 63)),
|
|
36: np.array((46, 101, 0)),
|
|
37: np.array((1, 87, 35)),
|
|
38: np.array((0, 120, 0)),
|
|
39: np.array((0, 105, 1)),
|
|
40: np.array((69, 69, 69)),
|
|
41: np.array((24, 54, 131)),
|
|
42: np.array((1, 87, 35)),
|
|
43: np.array((0, 73, 97)),
|
|
44: np.array((113, 83, 7)),
|
|
45: np.array((69, 69, 69)),
|
|
46: np.array((46, 101, 0)),
|
|
47: np.array((1, 87, 35)),
|
|
48: np.array((26, 142, 166)),
|
|
49: np.array((0, 127, 228)),
|
|
50: np.array((0, 160, 132)),
|
|
51: np.array((0, 146, 194)),
|
|
52: np.array((70, 156, 104)),
|
|
53: np.array((26, 142, 166)),
|
|
54: np.array((3, 174, 70)),
|
|
55: np.array((0, 160, 132)),
|
|
56: np.array((93, 124, 200)),
|
|
57: np.array((48, 109, 255)),
|
|
58: np.array((26, 142, 166)),
|
|
59: np.array((0, 127, 228)),
|
|
60: np.array((138, 138, 138)),
|
|
61: np.array((93, 124, 200)),
|
|
62: np.array((70, 156, 104)),
|
|
63: np.array((26, 142, 166)),
|
|
64: np.array((113, 83, 7)),
|
|
65: np.array((69, 69, 69)),
|
|
66: np.array((46, 101, 0)),
|
|
67: np.array((1, 87, 35)),
|
|
68: np.array((158, 97, 0)),
|
|
69: np.array((113, 83, 7)),
|
|
70: np.array((91, 116, 0)),
|
|
71: np.array((46, 101, 0)),
|
|
72: np.array((181, 65, 41)),
|
|
73: np.array((136, 50, 103)),
|
|
74: np.array((113, 83, 7)),
|
|
75: np.array((69, 69, 69)),
|
|
76: np.array((226, 79, 0)),
|
|
77: np.array((181, 65, 41)),
|
|
78: np.array((158, 97, 0)),
|
|
79: np.array((113, 83, 7)),
|
|
80: np.array((138, 138, 138)),
|
|
81: np.array((93, 124, 200)),
|
|
82: np.array((70, 156, 104)),
|
|
83: np.array((26, 142, 166)),
|
|
84: np.array((183, 152, 76)),
|
|
85: np.array((138, 138, 138)),
|
|
86: np.array((115, 171, 42)),
|
|
87: np.array((70, 156, 104)),
|
|
88: np.array((205, 120, 172)),
|
|
89: np.array((161, 105, 234)),
|
|
90: np.array((138, 138, 138)),
|
|
91: np.array((93, 124, 200)),
|
|
92: np.array((250, 134, 110)),
|
|
93: np.array((205, 120, 172)),
|
|
94: np.array((183, 152, 76)),
|
|
95: np.array((138, 138, 138)),
|
|
96: np.array((115, 171, 42)),
|
|
97: np.array((70, 156, 104)),
|
|
98: np.array((48, 189, 8)),
|
|
99: np.array((3, 174, 70)),
|
|
100: np.array((160, 185, 0)),
|
|
101: np.array((115, 171, 42)),
|
|
102: np.array((93, 203, 0)),
|
|
103: np.array((48, 189, 8)),
|
|
104: np.array((183, 152, 76)),
|
|
105: np.array((138, 138, 138)),
|
|
106: np.array((115, 171, 42)),
|
|
107: np.array((70, 156, 104)),
|
|
108: np.array((227, 167, 14)),
|
|
109: np.array((183, 152, 76)),
|
|
110: np.array((160, 185, 0)),
|
|
111: np.array((115, 171, 42)),
|
|
112: np.array((140, 225, 173)),
|
|
113: np.array((95, 211, 235)),
|
|
114: np.array((72, 244, 139)),
|
|
115: np.array((28, 229, 201)),
|
|
116: np.array((184, 240, 111)),
|
|
117: np.array((140, 225, 173)),
|
|
118: np.array((117, 255, 77)),
|
|
119: np.array((72, 244, 139)),
|
|
120: np.array((207, 207, 207)),
|
|
121: np.array((162, 193, 255)),
|
|
122: np.array((140, 225, 173)),
|
|
123: np.array((95, 211, 235)),
|
|
124: np.array((252, 221, 145)),
|
|
125: np.array((207, 207, 207)),
|
|
126: np.array((184, 240, 111)),
|
|
127: np.array((140, 225, 173)),
|
|
128: np.array((136, 50, 103)),
|
|
129: np.array((91, 36, 165)),
|
|
130: np.array((69, 69, 69)),
|
|
131: np.array((24, 54, 131)),
|
|
132: np.array((181, 65, 41)),
|
|
133: np.array((136, 50, 103)),
|
|
134: np.array((113, 83, 7)),
|
|
135: np.array((69, 69, 69)),
|
|
136: np.array((203, 32, 137)),
|
|
137: np.array((159, 18, 199)),
|
|
138: np.array((136, 50, 103)),
|
|
139: np.array((91, 36, 165)),
|
|
140: np.array((248, 47, 75)),
|
|
141: np.array((203, 32, 137)),
|
|
142: np.array((181, 65, 41)),
|
|
143: np.array((136, 50, 103)),
|
|
144: np.array((161, 105, 234)),
|
|
145: np.array((116, 91, 255)),
|
|
146: np.array((93, 124, 200)),
|
|
147: np.array((48, 109, 255)),
|
|
148: np.array((205, 120, 172)),
|
|
149: np.array((161, 105, 234)),
|
|
150: np.array((138, 138, 138)),
|
|
151: np.array((93, 124, 200)),
|
|
152: np.array((228, 87, 255)),
|
|
153: np.array((183, 73, 255)),
|
|
154: np.array((161, 105, 234)),
|
|
155: np.array((116, 91, 255)),
|
|
156: np.array((255, 101, 206)),
|
|
157: np.array((228, 87, 255)),
|
|
158: np.array((205, 120, 172)),
|
|
159: np.array((161, 105, 234)),
|
|
160: np.array((138, 138, 138)),
|
|
161: np.array((93, 124, 200)),
|
|
162: np.array((70, 156, 104)),
|
|
163: np.array((26, 142, 166)),
|
|
164: np.array((183, 152, 76)),
|
|
165: np.array((138, 138, 138)),
|
|
166: np.array((115, 171, 42)),
|
|
167: np.array((70, 156, 104)),
|
|
168: np.array((205, 120, 172)),
|
|
169: np.array((161, 105, 234)),
|
|
170: np.array((138, 138, 138)),
|
|
171: np.array((93, 124, 200)),
|
|
172: np.array((250, 134, 110)),
|
|
173: np.array((205, 120, 172)),
|
|
174: np.array((183, 152, 76)),
|
|
175: np.array((138, 138, 138)),
|
|
176: np.array((162, 193, 255)),
|
|
177: np.array((118, 178, 255)),
|
|
178: np.array((95, 211, 235)),
|
|
179: np.array((50, 197, 255)),
|
|
180: np.array((207, 207, 207)),
|
|
181: np.array((162, 193, 255)),
|
|
182: np.array((140, 225, 173)),
|
|
183: np.array((95, 211, 235)),
|
|
184: np.array((230, 174, 255)),
|
|
185: np.array((185, 160, 255)),
|
|
186: np.array((162, 193, 255)),
|
|
187: np.array((118, 178, 255)),
|
|
188: np.array((255, 189, 241)),
|
|
189: np.array((230, 174, 255)),
|
|
190: np.array((207, 207, 207)),
|
|
191: np.array((162, 193, 255)),
|
|
192: np.array((250, 134, 110)),
|
|
193: np.array((205, 120, 172)),
|
|
194: np.array((183, 152, 76)),
|
|
195: np.array((138, 138, 138)),
|
|
196: np.array((255, 148, 48)),
|
|
197: np.array((250, 134, 110)),
|
|
198: np.array((227, 167, 14)),
|
|
199: np.array((183, 152, 76)),
|
|
200: np.array((255, 116, 144)),
|
|
201: np.array((255, 101, 206)),
|
|
202: np.array((250, 134, 110)),
|
|
203: np.array((205, 120, 172)),
|
|
204: np.array((255, 130, 82)),
|
|
205: np.array((255, 116, 144)),
|
|
206: np.array((255, 148, 48)),
|
|
207: np.array((250, 134, 110)),
|
|
208: np.array((255, 189, 241)),
|
|
209: np.array((230, 174, 255)),
|
|
210: np.array((207, 207, 207)),
|
|
211: np.array((162, 193, 255)),
|
|
212: np.array((255, 203, 179)),
|
|
213: np.array((255, 189, 241)),
|
|
214: np.array((252, 221, 145)),
|
|
215: np.array((207, 207, 207)),
|
|
216: np.array((255, 171, 255)),
|
|
217: np.array((255, 156, 255)),
|
|
218: np.array((255, 189, 241)),
|
|
219: np.array((230, 174, 255)),
|
|
220: np.array((255, 185, 213)),
|
|
221: np.array((255, 171, 255)),
|
|
222: np.array((255, 203, 179)),
|
|
223: np.array((255, 189, 241)),
|
|
224: np.array((252, 221, 145)),
|
|
225: np.array((207, 207, 207)),
|
|
226: np.array((184, 240, 111)),
|
|
227: np.array((140, 225, 173)),
|
|
228: np.array((255, 236, 83)),
|
|
229: np.array((252, 221, 145)),
|
|
230: np.array((229, 254, 49)),
|
|
231: np.array((184, 240, 111)),
|
|
232: np.array((255, 203, 179)),
|
|
233: np.array((255, 189, 241)),
|
|
234: np.array((252, 221, 145)),
|
|
235: np.array((207, 207, 207)),
|
|
236: np.array((255, 218, 117)),
|
|
237: np.array((255, 203, 179)),
|
|
238: np.array((255, 236, 83)),
|
|
239: np.array((252, 221, 145)),
|
|
240: np.array((255, 255, 255)),
|
|
241: np.array((232, 255, 255)),
|
|
242: np.array((209, 255, 242)),
|
|
243: np.array((164, 255, 255)),
|
|
244: np.array((255, 255, 214)),
|
|
245: np.array((255, 255, 255)),
|
|
246: np.array((254, 255, 180)),
|
|
247: np.array((209, 255, 242)),
|
|
248: np.array((255, 255, 255)),
|
|
249: np.array((255, 244, 255)),
|
|
250: np.array((255, 255, 255)),
|
|
251: np.array((232, 255, 255)),
|
|
252: np.array((255, 255, 248)),
|
|
253: np.array((255, 255, 255)),
|
|
254: np.array((255, 255, 214)),
|
|
255: np.array((255, 255, 255)),
|
|
}
|
|
# 84 unique colours
|
|
|
|
|
|
PALETTES = {
|
|
'openemulator': OpenEmulatorPalette,
|
|
'virtualii': VirtualIIPalette,
|
|
'tohgr': ToHgrPalette,
|
|
'ntsc': NTSCPalette
|
|
}
|
|
|
|
DEFAULT_PALETTE = 'openemulator'
|