1 module magicalrainbows.formats;
2 
3 import magicalrainbows.utils;
4 
5 import std.algorithm;
6 import std.bitmanip;
7 import std.conv;
8 import std.range;
9 
10 
11 struct BGR555 { //XBBBBBGG GGGRRRRR
12 	enum redSize = 5;
13 	enum greenSize = 5;
14 	enum blueSize = 5;
15 	enum alphaSize = 0;
16 	mixin colourConstructors;
17 	mixin(bitfields!(
18 		uint, "red", redSize,
19 		uint, "green", greenSize,
20 		uint, "blue", blueSize,
21 		bool, "padding", 1));
22 }
23 
24 @safe pure unittest {
25 	with(BGR555(AnalogRGB(1.0, 0.5, 0.0))) {
26 		assert(red == 31);
27 		assert(green == 15);
28 		assert(blue == 0);
29 	}
30 }
31 
32 struct BGR565 { //BBBBBGGG GGGRRRRR
33 	enum redSize = 5;
34 	enum greenSize = 6;
35 	enum blueSize = 5;
36 	enum alphaSize = 0;
37 	mixin colourConstructors;
38 	mixin(bitfields!(
39 		uint, "red", redSize,
40 		uint, "green", greenSize,
41 		uint, "blue", blueSize));
42 }
43 
44 struct BGR222 { //00BBGGRR
45 	enum redSize = 2;
46 	enum greenSize = 2;
47 	enum blueSize = 2;
48 	enum alphaSize = 0;
49 	mixin colourConstructors;
50 	mixin(bitfields!(
51 		uint, "red", redSize,
52 		uint, "green", greenSize,
53 		uint, "blue", blueSize,
54 		ubyte, "padding", 2));
55 }
56 struct BGR333MD { //0000BBB0 GGG0RRR0
57 	enum redSize = 3;
58 	enum greenSize = 3;
59 	enum blueSize = 3;
60 	enum alphaSize = 0;
61 	mixin colourConstructors;
62 	mixin(bitfields!(
63 		ubyte, "padding0", 1,
64 		uint, "red", redSize,
65 		ubyte, "padding1", 1,
66 		uint, "green", greenSize,
67 		ubyte, "padding2", 1,
68 		uint, "blue", blueSize,
69 		ubyte, "padding3", 4));
70 }
71 
72 struct RGB888 { //RRRRRRRR GGGGGGGG BBBBBBBB
73 	enum redSize = 8;
74 	enum greenSize = 8;
75 	enum blueSize = 8;
76 	enum alphaSize = 0;
77 	mixin colourConstructors;
78 	ubyte red;
79 	ubyte green;
80 	ubyte blue;
81 }
82 
83 struct RGBA8888 { //RRRRRRRR GGGGGGGG BBBBBBBB AAAAAAAA
84 	enum redSize = 8;
85 	enum greenSize = 8;
86 	enum blueSize = 8;
87 	enum alphaSize = 8;
88 	mixin colourConstructors;
89 	ubyte red;
90 	ubyte green;
91 	ubyte blue;
92 	ubyte alpha;
93 }
94 @safe pure unittest {
95 	with(RGBA8888(AnalogRGBA(1.0, 0.5, 0.0, 0.0))) {
96 		assert(red == 255);
97 		assert(green == 127);
98 		assert(blue == 0);
99 		assert(alpha == 0);
100 	}
101 	with(RGBA8888(255, 128, 0, 0)) {
102 		assert(red == 255);
103 		assert(green == 128);
104 		assert(blue == 0);
105 		assert(alpha == 0);
106 	}
107 }
108 
109 struct AnalogRGB {
110 	real red;
111 	real green;
112 	real blue;
113 }
114 
115 struct AnalogRGBA {
116 	real red;
117 	real green;
118 	real blue;
119 	real alpha;
120 }
121 
122 struct HSV {
123     real hue;
124     real saturation;
125     real value;
126     @safe invariant {
127     	assert(hue >= 0);
128     	assert(saturation >= 0);
129     	assert(value >= 0);
130     	assert(hue <= 1.0);
131     	assert(saturation <= 1.0);
132     	assert(value <= 1.0);
133     }
134 }
135 
136 auto toHSV(Format)(Format input) if (isColourFormat!Format) {
137 	import std.algorithm.comparison : max, min;
138 	import std.math : approxEqual;
139     HSV result;
140     const red = input.redFP;
141     const green = input.greenFP;
142     const blue = input.blueFP;
143     const minimum = min(red, green, blue);
144     const maximum = max(red, green, blue);
145     const delta = maximum - minimum;
146 
147     result.value = maximum;
148     if (delta < 0.00001)
149     {
150         result.saturation = 0;
151         result.hue = 0;
152         return result;
153     }
154     if (maximum > 0.0) {
155         result.saturation = delta / maximum;
156     }
157 
158     if (approxEqual(red, maximum)) {
159         result.hue = (green - blue) / delta; //yellow, magenta
160     } else if (approxEqual(green, maximum)) {
161         result.hue = 2.0 + (blue - red) / delta; //cyan,  yellow
162     } else {
163         result.hue = 4.0 + (red - green) / delta; //magenta, cyan
164     }
165 
166     result.hue /= 6.0;
167     if (result.hue < 0.0) {
168 		result.hue += 1.0;
169     }
170     assert(result.hue >= 0.0);
171 
172     return result;
173 }
174 ///
175 @safe unittest {
176 	import std.math : approxEqual;
177 	with(RGB888(0, 0, 0).toHSV) {
178 		assert(hue == 0);
179 		assert(saturation == 0);
180 		assert(value == 0);
181 	}
182 	with(RGB888(0, 128, 192).toHSV) {
183 		assert(hue.approxEqual(0.5555555));
184 		assert(saturation.approxEqual(1.0));
185 		assert(value.approxEqual(0.752941));
186 	}
187 	with(RGB888(255, 255, 0).toHSV) {
188 		assert(hue.approxEqual(0.166667));
189 		assert(saturation.approxEqual(1.0));
190 		assert(value.approxEqual(1.0));
191 	}
192 	with(RGB888(255, 0, 0).toHSV) {
193 		assert(hue.approxEqual(0.0));
194 		assert(saturation.approxEqual(1.0));
195 		assert(value.approxEqual(1.0));
196 	}
197 	with(RGB888(255, 0, 255).toHSV) {
198 		assert(hue.approxEqual(0.833333));
199 		assert(saturation.approxEqual(1.0));
200 		assert(value.approxEqual(1.0));
201 	}
202 	with(RGB888(0, 255, 0).toHSV) {
203 		assert(hue.approxEqual(0.333333));
204 		assert(saturation.approxEqual(1.0));
205 		assert(value.approxEqual(1.0));
206 	}
207 	with(RGB888(0, 0, 255).toHSV) {
208 		assert(hue.approxEqual(0.666667));
209 		assert(saturation.approxEqual(1.0));
210 		assert(value.approxEqual(1.0));
211 	}
212 }
213 
214 auto toRGB(Format = RGB888)(HSV input) @safe if (isColourFormat!Format) {
215     if(input.saturation <= 0.0) {
216         return Format(AnalogRGB(input.value, input.value, input.value));
217     }
218     real hh = input.hue * 6.0;
219     if(hh > 6.0) {
220 		hh	 = 0.0;
221     }
222     auto i = cast(long)hh;
223     real ff = hh - i;
224     real p = input.value * (1.0 - input.saturation);
225     real q = input.value * (1.0 - (input.saturation * ff));
226     real t = input.value * (1.0 - (input.saturation * (1.0 - ff)));
227 
228     assert(p <= 1.0);
229     assert(q <= 1.0);
230     assert(t <= 1.0);
231     AnalogRGB rgb;
232     switch(i) {
233 		case 0:
234 			rgb = AnalogRGB(input.value, t, p);
235 			break;
236 		case 1:
237 			rgb = AnalogRGB(q, input.value, p);
238 			break;
239 		case 2:
240 			rgb = AnalogRGB(p, input.value, t);
241 			break;
242 		case 3:
243 			rgb = AnalogRGB(p, q, input.value);
244 			break;
245 		case 4:
246 			rgb = AnalogRGB(t, p, input.value);
247 			break;
248 		case 5:
249 		default:
250 			rgb = AnalogRGB(input.value, p, q);
251 			break;
252     }
253     return Format(rgb);
254 }
255 ///
256 @safe unittest {
257 	with(HSV(0, 0, 0).toRGB!RGB888) {
258 		assert(red == 0);
259 		assert(green == 0);
260 		assert(blue == 0);
261 	}
262 	with(HSV(0, 0, 0.5).toRGB!RGB888) {
263 		assert(red == 127);
264 		assert(green == 127);
265 		assert(blue == 127);
266 	}
267 	with(HSV(0.5555555, 1.0, 0.752941).toRGB!RGB888) {
268 		assert(red == 0);
269 		assert(green == 128);
270 		assert(blue == 191);
271 	}
272 	with(HSV(0.166666667, 1.0, 1.0).toRGB!RGB888) {
273 		assert(red == 254);
274 		assert(green == 255);
275 		assert(blue == 0);
276 	}
277 	with(HSV(0.0, 1.0, 1.0).toRGB!RGB888) {
278 		assert(red == 255);
279 		assert(green == 0);
280 		assert(blue == 0);
281 	}
282 	with(HSV(0.83333333, 1.0, 1.0).toRGB!RGB888) {
283 		assert(red == 254);
284 		assert(green == 0);
285 		assert(blue == 255);
286 	}
287 	with(HSV(0.33333333, 1.0, 1.0).toRGB!RGB888) {
288 		assert(red == 0);
289 		assert(green == 255);
290 		assert(blue == 0);
291 	}
292 	with(HSV(0.66666667, 1.0, 1.0).toRGB!RGB888) {
293 		assert(red == 0);
294 		assert(green == 0);
295 		assert(blue == 255);
296 	}
297 	with(HSV(0.41666667, 1.0, 1.0).toRGB!RGB888) {
298 		assert(red == 0);
299 		assert(green == 255);
300 		assert(blue == 127);
301 	}
302 	with(HSV(0.91666667, 1.0, 1.0).toRGB!RGB888) {
303 		assert(red == 255);
304 		assert(green == 0);
305 		assert(blue == 127);
306 	}
307 }
308 
309 struct ColourPair(FG, BG) if (isColourFormat!FG && isColourFormat!BG) {
310 	FG foreground;
311 	BG background;
312 	auto contrast() const @safe pure {
313 		import magicalrainbows.properties : contrast;
314 		return contrast(foreground, background);
315 	}
316 	bool meetsWCAGAACriteria() const @safe pure {
317 		return contrast >= 4.5;
318 	}
319 	bool meetsWCAGAAACriteria() const @safe pure {
320 		return contrast >= 7.0;
321 	}
322 }
323 
324 auto colourPair(FG, BG)(FG foreground, BG background) if (isColourFormat!FG && isColourFormat!BG) {
325 	return ColourPair!(FG, BG)(foreground, background);
326 }
327 ///
328 @safe pure unittest {
329 	import std.math : approxEqual;
330 	with(colourPair(RGB888(0, 0, 0), RGB888(255, 255, 255))) {
331 		assert(contrast.approxEqual(21.0));
332 		assert(meetsWCAGAACriteria);
333 		assert(meetsWCAGAAACriteria);
334 	}
335 	with(colourPair(RGB888(255, 255, 255), RGB888(255, 255, 255))) {
336 		assert(contrast.approxEqual(1.0));
337 		assert(!meetsWCAGAACriteria);
338 		assert(!meetsWCAGAAACriteria);
339 	}
340 	with(colourPair(RGB888(0, 128, 255), RGB888(0, 0, 0))) {
341 		assert(contrast.approxEqual(5.5316));
342 		assert(meetsWCAGAACriteria);
343 		assert(!meetsWCAGAAACriteria);
344 	}
345 	with(colourPair(BGR555(0, 16, 31), RGB888(0, 0, 0))) {
346 		assert(contrast.approxEqual(5.7236));
347 		assert(meetsWCAGAACriteria);
348 		assert(!meetsWCAGAAACriteria);
349 	}
350 }
351 
352 auto convert(To, From)(From from) if (isColourFormat!From && isColourFormat!To) {
353 	static if (is(To == From)) {
354 		return from;
355 	} else {
356 		To output;
357 		static if (hasRed!From && hasRed!To) {
358 			output.red = colourConvert!(typeof(output.red), To.redSize, From.redSize)(from.red);
359 		}
360 		static if (hasGreen!From && hasGreen!To) {
361 			output.green = colourConvert!(typeof(output.green), To.greenSize, From.greenSize)(from.green);
362 		}
363 		static if (hasBlue!From && hasBlue!To) {
364 			output.blue = colourConvert!(typeof(output.blue), To.blueSize, From.blueSize)(from.blue);
365 		}
366 		static if (hasAlpha!From && hasAlpha!To) {
367 			output.alpha = colourConvert!(typeof(output.alpha), To.alphaSize, From.alphaSize)(from.alpha);
368 		} else static if (hasAlpha!To) {
369 			output.alpha = output.alpha.max;
370 		}
371 		return output;
372 	}
373 }
374 ///
375 @safe pure unittest {
376 	assert(BGR555(31,31,31).convert!RGB888 == RGB888(248, 248, 248));
377 	assert(BGR555(0, 0, 0).convert!RGB888 == RGB888(0, 0, 0));
378 	assert(RGB888(248, 248, 248).convert!BGR555 == BGR555(31,31,31));
379 	assert(RGB888(0, 0, 0).convert!BGR555 == BGR555(0, 0, 0));
380 }
381 
382 Format fromHex(Format = RGB888)(const string colour) @safe pure if (isColourFormat!Format) {
383 	Format output;
384 	string tmpStr = colour[];
385 	if (colour.empty) {
386 		throw new Exception("Cannot parse an empty string");
387 	}
388 	if (tmpStr.front == '#') {
389 		tmpStr.popFront();
390 	}
391 	enum alphaAdjustment = hasAlpha!Format ? 1 : 0;
392 	if (tmpStr.length == 3 + alphaAdjustment) {
393 		auto tmp = tmpStr[0].repeat(2);
394 		output.red = tmp.parse!ubyte(16);
395 		tmp = tmpStr[1].repeat(2);
396 		output.green = tmp.parse!ubyte(16);
397 		tmp = tmpStr[2].repeat(2);
398 		output.blue = tmp.parse!ubyte(16);
399 		static if (hasAlpha!Format) {
400 			tmp = tmpStr[3].repeat(2);
401 			output.alpha = tmp.parse!ubyte(16);
402 		}
403 	} else if (tmpStr.length == (3 + alphaAdjustment) * 2) {
404 		auto tmp = tmpStr[0..2];
405 		output.red = tmp.parse!ubyte(16);
406 		tmp = tmpStr[2..4];
407 		output.green = tmp.parse!ubyte(16);
408 		tmp = tmpStr[4..6];
409 		output.blue = tmp.parse!ubyte(16);
410 		static if (hasAlpha!Format) {
411 			tmp = tmpStr[6..8];
412 			output.alpha = tmp.parse!ubyte(16);
413 		}
414 	}
415 	return output;
416 }
417 ///
418 @safe pure unittest {
419 	assert("#000000".fromHex == RGB888(0, 0, 0));
420 	assert("#FFFFFF".fromHex == RGB888(255, 255, 255));
421 	assert("FFFFFF".fromHex == RGB888(255, 255, 255));
422 	assert("#FFF".fromHex == RGB888(255, 255, 255));
423 	assert("#888".fromHex == RGB888(0x88, 0x88, 0x88));
424 	assert("888".fromHex == RGB888(0x88, 0x88, 0x88));
425 	assert("#FFFFFFFF".fromHex!RGBA8888 == RGBA8888(255, 255, 255, 255));
426 	assert("#FFFF".fromHex!RGBA8888 == RGBA8888(255, 255, 255, 255));
427 }
428 
429 //TODO: can we express these with fractions instead?
430 
431 //Assumptions:
432 // R,G,B,Y [0, 255]
433 // Pb,Pr [-127.5, 127.5]
434 enum YPbPrSDTVToRGBMatrix = [
435 	[1.0, 0.0, 1.402],
436 	[1.0, -0.344, -0.714],
437 	[1.0, 1.772, 0.0],
438 ];
439 //Assumptions:
440 // R,G,B,Y [0, 255]
441 // Pb,Pr [-127.5, 127.5]
442 enum YPbPrHDTVToRGBMatrix = [
443 	[1.0, 0.0, 1.575],
444 	[1.0, -0.187, -0.468],
445 	[1.0, 1.856, 0.0],
446 ];
447 
448 //Assumptions:
449 // R,G,B,Y [0, 255]
450 // Pb,Pr [-127.5, 127.5]
451 enum RGBToYPbPrSDTVMatrix = [
452 	[0.299, 0.587, 0.114],
453 	[-0.169, -0.331, 0.5],
454 	[0.5, -0.419, -0.081],
455 ];
456 
457 //Assumptions:
458 // R,G,B,Y [0, 255]
459 // Pb,Pr [-127.5, 127.5]
460 enum RGBToYPbPrHDTVMatrix = [
461 	[0.213, 0.715, 0.072],
462 	[-0.115, -0.385, 0.5],
463 	[0.5, -0.454, -0.046],
464 ];
465 
466 struct YPbPr {
467 	double Y;
468 	double Pb;
469 	double Pr;
470 }
471 
472 //Assumptions:
473 // R,G,B,Y [0, 1]
474 // U [-0.436, 0.436]
475 // V [-0.615, 0.615]
476 enum RGBToYUVMatrix = [
477 	[0.299, 0.587, 0.114],
478 	[-0.147, -0.289, 0.436],
479 	[0.615, -0.515, -0.100]
480 ];
481 
482 //Assumptions:
483 // R,G,B,Y [0, 1]
484 // U [-0.436, 0.436]
485 // V [-0.615, 0.615]
486 enum YUVToRGBMatrix = [
487 	[1.0, 0.0, 1.140],
488 	[1.0, -0.395, -0.581],
489 	[1.0, 2.032, 0.0],
490 ];
491 
492 struct YUV {
493 	double Y;
494 	double Cb;
495 	double Cr;
496 }
497 
498 enum YCbCrSDTVVector = [[16], [128], [128]];
499 alias YCbCrHDTVVector = YCbCrSDTVVector;
500 enum YCbCrFullRangeVector = [[0], [128], [128]];
501 
502 //Assumptions:
503 // R,G,B [0, 255]
504 // Y [16, 235]
505 // Cb,Cr [16, 240]
506 enum YCbCrSDTVToRGBMatrix = [
507 	[1.164, 0.0, 1.596],
508 	[1.164, -0.392, -0.813],
509 	[1.164, 2.017, 0.0],
510 ];
511 
512 //Assumptions:
513 // R,G,B [0, 255]
514 // Y [16, 235]
515 // Cb,Cr [16, 240]
516 enum YCbCrHDTVToRGBMatrix = [
517 	[1.164, 0.0, 1.793],
518 	[1.164, -0.213, -0.533],
519 	[1.164, 2.112, 0.0],
520 ];
521 
522 //Assumptions:
523 // R,G,B,Y,Cb,Cr [0, 255]
524 enum YCbCrFullRangeToRGBMatrix = [
525 	[1.0, 0.0, 1.4],
526 	[1.0, -0.343, -0.711],
527 	[1.0, 1.765, 0.0],
528 ];
529 
530 //Assumptions:
531 // R,G,B [0, 255]
532 // Y [16, 235]
533 // Cb,Cr [16, 240]
534 enum RGBToYCbCrSDTVMatrix = [
535 	[0.257, 0.504, 0.098],
536 	[-0.148, -0.291, 0.439],
537 	[0.439, -0.368, -0.071],
538 ];
539 
540 //Assumptions:
541 // R,G,B [0, 255]
542 // Y [16, 235]
543 // Cb,Cr [16, 240]
544 enum RGBToYCbCrHDTVMatrix = [
545 	[0.183, 0.614, 0.062],
546 	[-0.101, -0.339, 0.439],
547 	[0.439, -0.399, -0.040],
548 ];
549 
550 //Assumptions:
551 // R,G,B,Y,Cb,Cr [0, 255]
552 enum RGBToYCbCrFullRangeMatrix = [
553 	[0.299, 0.587, 0.114],
554 	[-0.169, -0.331, 0.500],
555 	[0.500, -0.419, -0.081],
556 ];
557 
558 struct YCbCr {
559 	double Y;
560 	double Cb;
561 	double Cr;
562 }