Reimplement NTSC conversion to closely match openemulator output. I

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.
This commit is contained in:
kris 2021-02-17 21:29:43 +00:00
parent e1dab91783
commit e0b732cdaa
4 changed files with 306 additions and 305 deletions

View File

@ -17,15 +17,19 @@ def main():
# For each sequence of 8 pixels, compute the RGB colour of the right-most
# pixel, using NTSC emulation.
for bits in range(256):
bits8 = np.empty((8,), dtype=np.bool)
for i in range(8):
bitmap[0, i] = bits & (1 << i)
bits8[i] = bits & (1 << i)
# Double Hi-Res has a timing shift that rotates the displayed bits one
# position with respect to NTSC phase.
bitmap[0, :] = bits8[[3, 0, 1, 2, 7, 4, 5, 6]]
ntsc = s.bitmap_to_ntsc(bitmap)
last_colour = ntsc[0, -1, :]
colours[bits] = last_colour
unique.add(tuple(last_colour))
print("%d: np.array((%d, %d, %d))," % (
bits, last_colour[0], last_colour[1], last_colour[2]))
bits, last_colour[0], last_colour[1], last_colour[2]))
print("}")
print("# %d unique colours" % len(unique))
@ -34,6 +38,7 @@ def main():
for x, hsv in enumerate(sorted([tuple(colour.RGB_to_HSV(c / 256)) for c in
colours.values()])):
im[0:128, x * 16: (x + 1) * 16, :] = colour.HSV_to_RGB(hsv) * 256
Image.fromarray(im).show()

View File

@ -121,263 +121,263 @@ class NTSCPalette(Palette):
# 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)),
1: np.array((0, 0, 115)),
2: np.array((0, 37, 0)),
3: np.array((0, 23, 88)),
4: np.array((14, 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)),
6: np.array((0, 52, 0)),
7: np.array((0, 37, 0)),
8: np.array((64, 0, 26)),
9: np.array((49, 0, 141)),
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)),
11: np.array((0, 0, 115)),
12: np.array((79, 0, 0)),
13: np.array((64, 0, 26)),
14: np.array((14, 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)),
16: np.array((49, 48, 178)),
17: np.array((34, 34, 255)),
18: np.array((0, 86, 152)),
19: np.array((0, 71, 255)),
20: np.array((63, 63, 63)),
21: np.array((49, 48, 178)),
22: np.array((0, 101, 37)),
23: np.array((0, 86, 152)),
24: np.array((113, 10, 204)),
25: np.array((98, 0, 255)),
26: np.array((49, 48, 178)),
27: np.array((34, 34, 255)),
28: np.array((128, 25, 89)),
29: np.array((113, 10, 204)),
30: np.array((63, 63, 63)),
31: np.array((49, 48, 178)),
32: np.array((0, 101, 37)),
33: np.array((0, 86, 152)),
34: np.array((0, 139, 11)),
35: np.array((0, 124, 126)),
36: np.array((13, 116, 0)),
37: np.array((0, 101, 37)),
38: np.array((0, 154, 0)),
39: np.array((0, 139, 11)),
40: np.array((63, 63, 63)),
41: np.array((49, 48, 178)),
42: np.array((0, 101, 37)),
43: np.array((0, 86, 152)),
44: np.array((78, 78, 0)),
45: np.array((63, 63, 63)),
46: np.array((13, 116, 0)),
47: np.array((0, 101, 37)),
48: np.array((48, 150, 216)),
49: np.array((33, 135, 255)),
50: np.array((0, 188, 190)),
51: np.array((0, 173, 255)),
52: np.array((62, 165, 101)),
53: np.array((48, 150, 216)),
54: np.array((0, 203, 75)),
55: np.array((0, 188, 190)),
56: np.array((112, 112, 242)),
57: np.array((98, 97, 255)),
58: np.array((48, 150, 216)),
59: np.array((33, 135, 255)),
60: np.array((127, 127, 127)),
61: np.array((112, 112, 242)),
62: np.array((62, 165, 101)),
63: np.array((48, 150, 216)),
64: np.array((78, 78, 0)),
65: np.array((63, 63, 63)),
66: np.array((13, 116, 0)),
67: np.array((0, 101, 37)),
68: np.array((93, 93, 0)),
69: np.array((78, 78, 0)),
70: np.array((28, 131, 0)),
71: np.array((13, 116, 0)),
72: np.array((142, 40, 0)),
73: np.array((128, 25, 89)),
74: np.array((78, 78, 0)),
75: np.array((63, 63, 63)),
76: np.array((157, 55, 0)),
77: np.array((142, 40, 0)),
78: np.array((93, 93, 0)),
79: np.array((78, 78, 0)),
80: np.array((127, 127, 127)),
81: np.array((112, 112, 242)),
82: np.array((62, 165, 101)),
83: np.array((48, 150, 216)),
84: np.array((142, 142, 12)),
85: np.array((127, 127, 127)),
86: np.array((77, 180, 0)),
87: np.array((62, 165, 101)),
88: np.array((192, 89, 153)),
89: np.array((177, 74, 255)),
90: np.array((127, 127, 127)),
91: np.array((112, 112, 242)),
92: np.array((206, 104, 38)),
93: np.array((192, 89, 153)),
94: np.array((142, 142, 12)),
95: np.array((127, 127, 127)),
96: np.array((77, 180, 0)),
97: np.array((62, 165, 101)),
98: np.array((13, 218, 0)),
99: np.array((0, 203, 75)),
100: np.array((92, 195, 0)),
101: np.array((77, 180, 0)),
102: np.array((27, 233, 0)),
103: np.array((13, 218, 0)),
104: np.array((142, 142, 12)),
105: np.array((127, 127, 127)),
106: np.array((77, 180, 0)),
107: np.array((62, 165, 101)),
108: np.array((156, 157, 0)),
109: np.array((142, 142, 12)),
110: np.array((92, 195, 0)),
111: np.array((77, 180, 0)),
112: np.array((126, 229, 165)),
113: np.array((112, 214, 255)),
114: np.array((62, 255, 138)),
115: np.array((47, 252, 253)),
116: np.array((141, 244, 50)),
117: np.array((126, 229, 165)),
118: np.array((76, 255, 23)),
119: np.array((62, 255, 138)),
120: np.array((191, 191, 191)),
121: np.array((176, 176, 255)),
122: np.array((126, 229, 165)),
123: np.array((112, 214, 255)),
124: np.array((205, 206, 76)),
125: np.array((191, 191, 191)),
126: np.array((141, 244, 50)),
127: np.array((126, 229, 165)),
128: np.array((128, 25, 89)),
129: np.array((113, 10, 204)),
130: np.array((63, 63, 63)),
131: np.array((49, 48, 178)),
132: np.array((142, 40, 0)),
133: np.array((128, 25, 89)),
134: np.array((78, 78, 0)),
135: np.array((63, 63, 63)),
136: np.array((192, 0, 116)),
137: np.array((178, 0, 231)),
138: np.array((128, 25, 89)),
139: np.array((113, 10, 204)),
140: np.array((207, 2, 1)),
141: np.array((192, 0, 116)),
142: np.array((142, 40, 0)),
143: np.array((128, 25, 89)),
144: np.array((177, 74, 255)),
145: np.array((162, 59, 255)),
146: np.array((112, 112, 242)),
147: np.array((98, 97, 255)),
148: np.array((192, 89, 153)),
149: np.array((177, 74, 255)),
150: np.array((127, 127, 127)),
151: np.array((112, 112, 242)),
152: np.array((241, 36, 255)),
153: np.array((227, 21, 255)),
154: np.array((177, 74, 255)),
155: np.array((162, 59, 255)),
156: np.array((255, 51, 179)),
157: np.array((241, 36, 255)),
158: np.array((192, 89, 153)),
159: np.array((177, 74, 255)),
160: np.array((127, 127, 127)),
161: np.array((112, 112, 242)),
162: np.array((62, 165, 101)),
163: np.array((48, 150, 216)),
164: np.array((142, 142, 12)),
165: np.array((127, 127, 127)),
166: np.array((77, 180, 0)),
167: np.array((62, 165, 101)),
168: np.array((192, 89, 153)),
169: np.array((177, 74, 255)),
170: np.array((127, 127, 127)),
171: np.array((112, 112, 242)),
172: np.array((206, 104, 38)),
173: np.array((192, 89, 153)),
174: np.array((142, 142, 12)),
175: np.array((127, 127, 127)),
176: np.array((176, 176, 255)),
177: np.array((161, 161, 255)),
178: np.array((112, 214, 255)),
179: np.array((97, 199, 255)),
180: np.array((191, 191, 191)),
181: np.array((176, 176, 255)),
182: np.array((126, 229, 165)),
183: np.array((112, 214, 255)),
184: np.array((241, 138, 255)),
185: np.array((226, 123, 255)),
186: np.array((176, 176, 255)),
187: np.array((161, 161, 255)),
188: np.array((255, 153, 217)),
189: np.array((241, 138, 255)),
190: np.array((191, 191, 191)),
191: np.array((176, 176, 255)),
192: np.array((206, 104, 38)),
193: np.array((192, 89, 153)),
194: np.array((142, 142, 12)),
195: np.array((127, 127, 127)),
196: np.array((221, 119, 0)),
197: np.array((206, 104, 38)),
198: np.array((156, 157, 0)),
199: np.array((142, 142, 12)),
200: np.array((255, 66, 64)),
201: np.array((255, 51, 179)),
202: np.array((206, 104, 38)),
203: np.array((192, 89, 153)),
204: np.array((255, 81, 0)),
205: np.array((255, 66, 64)),
206: np.array((221, 119, 0)),
207: np.array((206, 104, 38)),
208: np.array((255, 153, 217)),
209: np.array((241, 138, 255)),
210: np.array((191, 191, 191)),
211: np.array((176, 176, 255)),
212: np.array((255, 168, 102)),
213: np.array((255, 153, 217)),
214: np.array((205, 206, 76)),
215: np.array((191, 191, 191)),
216: np.array((255, 115, 243)),
217: np.array((255, 100, 255)),
218: np.array((255, 153, 217)),
219: np.array((241, 138, 255)),
220: np.array((255, 130, 128)),
221: np.array((255, 115, 243)),
222: np.array((255, 168, 102)),
223: np.array((255, 153, 217)),
224: np.array((205, 206, 76)),
225: np.array((191, 191, 191)),
226: np.array((141, 244, 50)),
227: np.array((126, 229, 165)),
228: np.array((220, 220, 0)),
229: np.array((205, 206, 76)),
230: np.array((156, 255, 0)),
231: np.array((141, 244, 50)),
232: np.array((255, 168, 102)),
233: np.array((255, 153, 217)),
234: np.array((205, 206, 76)),
235: np.array((191, 191, 191)),
236: np.array((255, 183, 0)),
237: np.array((255, 168, 102)),
238: np.array((220, 220, 0)),
239: np.array((205, 206, 76)),
240: np.array((254, 255, 255)),
241: np.array((240, 240, 255)),
242: np.array((190, 255, 228)),
243: np.array((175, 255, 255)),
244: np.array((255, 255, 139)),
245: np.array((254, 255, 255)),
246: np.array((205, 255, 113)),
247: np.array((190, 255, 228)),
248: np.array((255, 217, 255)),
249: np.array((255, 202, 255)),
250: np.array((254, 255, 255)),
251: np.array((240, 240, 255)),
252: np.array((255, 231, 166)),
253: np.array((255, 217, 255)),
254: np.array((255, 255, 139)),
255: np.array((254, 255, 255)),
}
# 84 unique colours
# 85 unique colours
PALETTES = {

View File

@ -34,7 +34,7 @@ def all_lab_colours():
def nearest_colours(palette, all_lab, diffs):
palette_size = len(palette)
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)
@ -78,7 +78,8 @@ def main():
except FileExistsError:
pass
out = np.memmap(filename=palette.DISTANCES_PATH, mode="w+",
dtype=np.uint8, shape=(RGB_LEVELS ** 3, len(palette)))
dtype=np.uint8, shape=(RGB_LEVELS ** 3,
len(palette.RGB)))
nearest_colours(palette, all_lab, out)

View File

@ -3,6 +3,7 @@
import numpy as np
import palette as palette_py
# TODO: rename "4bit" variable naming now that we also have palettes with 8 bit
# depth.
@ -83,77 +84,71 @@ class Screen:
raise NotImplementedError
@staticmethod
def _sin(pos, phase0=4):
def _sin(pos, phase0=0):
x = pos % 12 + phase0
return 8 * np.sin(x * 2 * np.pi / 12)
return np.sin(x * 2 * np.pi / 12)
@staticmethod
def _cos(pos, phase0=4):
def _cos(pos, phase0=0):
x = pos % 12 + phase0
return 8 * np.cos(x * 2 * np.pi / 12)
return np.cos(x * 2 * np.pi / 12)
def _read(self, line, pos):
if pos < 0:
return 0
# Sather says black level is 0.36V and white level 1.1V, but this
# doesn't seem to be right (they correspond to values -29 and +33)
# which means that 0101 grey has Y value ~0, i.e. is black. These are
# only mentioned as labels on figure 8.2 though.
#
# _The Apple II Circuit description_ by W. Gayler gives black=0.5V
# and white=2.0V which is much more plausible.
#
# Conversion is given by floor((voltage-0.518)*1000/12)-15
return 108 if line[pos] else 0 # -16
return 1 if line[pos] else 0
def bitmap_to_ntsc(self, bitmap: np.ndarray) -> np.ndarray:
"""
See http://forums.nesdev.com/viewtopic.php?p=172329#p172329
"""
y_width = 12
i_width = 24
q_width = 24
u_width = 24
v_width = 24
contrast = 167941
saturation = 144044
contrast = 1
# TODO: where does this come from? OpenEmulator looks like it should
# use a value of 1.0 by default.
saturation = 2
# Fudge factor to make colours line up with OpenEmulator
# TODO: where does this come from - is it due to the band-pass
# filtering they do?
hue = -0.3
yr = contrast / y_width
ir = contrast * 1.994681e-6 * saturation / i_width
qr = contrast * 9.915742e-7 * saturation / q_width
yg = contrast / y_width
ig = contrast * 9.151351e-8 * saturation / i_width
qg = contrast * -6.334805e-7 * saturation / q_width
yb = contrast / y_width
ib = contrast * -1.012984e-6 * saturation / i_width
qb = contrast * 1.667217e-6 * saturation / q_width
# Apply effect of saturation
yuv_to_rgb = np.array(
((1, 0, 0), (0, saturation, 0), (0, 0, saturation)), dtype=np.float)
# Apply hue phase rotation
yuv_to_rgb = np.matmul(np.array(
((1, 0, 0), (0, np.cos(hue), np.sin(hue)), (0, -np.sin(hue),
np.cos(hue)))),
yuv_to_rgb)
# Y'UV to R'G'B' conversion
yuv_to_rgb = np.matmul(np.array(
((1, 0, 1.13983), (1, -0.39465, -.58060), (1, 2.03211, 0))),
yuv_to_rgb)
# Apply effect of contrast
yuv_to_rgb *= contrast
out_rgb = np.empty((bitmap.shape[0], bitmap.shape[1] * 3, 3),
dtype=np.uint8)
for y in range(bitmap.shape[0]):
ysum = 0
isum = 0
qsum = 0
usum = 0
vsum = 0
line = np.repeat(bitmap[y], 3)
# color = y // (192//16)
# line = np.repeat(np.tile((color & 1, color & 2, color & 4,
# color & 8), 140), 3)
for x in range(bitmap.shape[1] * 3):
ysum += self._read(line, x) - self._read(line, x - y_width)
isum += self._read(line, x) * self._cos(x) - self._read(
line, x - i_width) * self._cos((x - i_width))
qsum += self._read(line, x) * self._sin(x) - self._read(
line, x - q_width) * self._sin((x - q_width))
r = min(255, max(0, ysum * yr + isum * ir + qsum * qr) /
65536)
g = min(255,
max(0, (ysum * yg + isum * ig + qsum * qg) / 65536))
b = min(255,
max(0, (ysum * yb + isum * ib + qsum * qb) / 65536))
usum += self._read(line, x) * self._sin(x) - self._read(
line, x - u_width) * self._sin((x - u_width))
vsum += self._read(line, x) * self._cos(x) - self._read(
line, x - v_width) * self._cos((x - v_width))
rgb = np.matmul(
yuv_to_rgb, np.array(
(ysum / y_width, usum / u_width,
vsum / v_width)).reshape((3, 1))).reshape(3)
r = min(255, max(0, rgb[0] * 255))
g = min(255, max(0, rgb[1] * 255))
b = min(255, max(0, rgb[2] * 255))
out_rgb[y, x, :] = (r, g, b)
return out_rgb