1 module magicalrainbows.utils;
2 
3 import std.traits;
4 
5 package:
6 
7 T read(T)(const ubyte[] input) if (isMutable!T) in {
8 	assert(input.length == T.sizeof, "Mismatch between input buffer size and expected value size");
9 } body {
10 	union Result {
11 		ubyte[T.sizeof] raw;
12 		T val;
13 	}
14 	Result result;
15 	result.raw = input;
16 	return result.val;
17 }
18 
19 ubyte[T.sizeof] asBytes(T)(T input) if (isMutable!T) {
20 	union Result {
21 		T val;
22 		ubyte[T.sizeof] raw;
23 	}
24 	return Result(input).raw;
25 }
26 
27 enum isColourFormat(T) = hasRed!T && hasGreen!T && hasBlue!T;
28 
29 enum hasRed(T) = __traits(hasMember, T, "redSize") && (T.redSize > 0);
30 enum hasGreen(T) = __traits(hasMember, T, "greenSize") && (T.greenSize > 0);
31 enum hasBlue(T) = __traits(hasMember, T, "blueSize") && (T.blueSize > 0);
32 enum hasAlpha(T) = __traits(hasMember, T, "alphaSize") && (T.alphaSize > 0);
33 
34 enum maxValue(ulong Bits) = (1<<Bits) - 1;
35 
36 enum maxRed(T) = maxValue!(T.redSize);
37 enum maxGreen(T) = maxValue!(T.greenSize);
38 enum maxBlue(T) = maxValue!(T.blueSize);
39 enum maxAlpha(T) = maxValue!(T.alphaSize);
40 
41 enum isDigital(T) = hasRed!T && hasGreen!T && hasBlue!T;
42 
43 real redFP(Colour)(const Colour colour) if (hasRed!Colour) {
44 	return (cast(real)colour.red / maxRed!Colour);
45 }
46 
47 real greenFP(Colour)(const Colour colour) if (hasGreen!Colour) {
48 	return (cast(real)colour.green / maxGreen!Colour);
49 }
50 
51 real blueFP(Colour)(const Colour colour) if (hasBlue!Colour) {
52 	return (cast(real)colour.blue / maxBlue!Colour);
53 }
54 real alphaFP(Colour)(const Colour colour) if (hasAlpha!Colour) {
55 	return (cast(real)colour.alpha / maxAlpha!Colour);
56 }
57 @safe pure unittest {
58 	import magicalrainbows.formats : RGB888;
59 	import std.math : approxEqual;
60 	assert(RGB888(255, 128, 0).redFP() == 1.0);
61 	assert(RGB888(255, 128, 0).greenFP().approxEqual(0.502));
62 	assert(RGB888(255, 128, 0).blueFP() == 0.0);
63 	assert(RGB888(0, 255, 128).redFP() == 0.0);
64 	assert(RGB888(0, 255, 128).greenFP() == 1.0);
65 	assert(RGB888(0, 255, 128).blueFP().approxEqual(0.502));
66 	assert(RGB888(128, 0, 255).redFP().approxEqual(0.502));
67 	assert(RGB888(128, 0, 255).greenFP() == 0.0);
68 	assert(RGB888(128, 0, 255).blueFP() == 1.0);
69 }
70 
71 auto asLinearRGB(Format)(const Format colour) {
72 	static if (isDigital!Format) {
73 		const red = colour.redFP.toLinearRGB;
74 		const green = colour.greenFP.toLinearRGB;
75 		const blue = colour.blueFP.toLinearRGB;
76 		static if (hasAlpha!Format) {
77 			const alpha = colour.alphaFP.toLinearRGB;
78 		}
79 	} else {
80 		const red = colour.red.toLinearRGB;
81 		const green = colour.green.toLinearRGB;
82 		const blue = colour.blue.toLinearRGB;
83 		static if (hasAlpha!Format) {
84 			const alpha = colour.alpha.toLinearRGB;
85 		}
86 	}
87 	static if (hasAlpha!Format) {
88 		import magicalrainbows.formats : AnalogRGBA;
89 		return AnalogRGBA(red, green, blue, alpha);
90 	} else {
91 		import magicalrainbows.formats : AnalogRGB;
92 		return AnalogRGB(red, green, blue);
93 	}
94 }
95 
96 private real toLinearRGB(const real input) @safe pure @nogc nothrow {
97 	return input > 0.03928 ?
98 		((input + 0.055) / 1.055) ^^ 2.4 :
99 		input / 12.92 ;
100 }
101 
102 private real gammaCorrect(const real input, const real factor = 2.2) @safe pure @nogc nothrow {
103 	return input ^^ (1.0 / factor);
104 }
105 
106 T colourConvert(T, size_t Size1, size_t Size2, Source)(Source val ) {
107 	static if (Size1 > Size2) {
108 		return cast(T)(val << (Size1 - Size2));
109 	} else static if (Size1 < Size2) {
110 		return cast(T)(val >> (Size2 - Size1));
111 	} else {
112 		return cast(T)val;
113 	}
114 }
115 
116 mixin template colourConstructors() {
117 	this(uint red, uint green, uint blue) pure @safe
118 		in(red < (1<<redSize), "Red value out of range")
119 		in(green < (1<<greenSize), "Green value out of range")
120 		in(blue < (1<<blueSize), "Blue value out of range")
121 	{
122 		this.red = cast(typeof(this.red))red;
123 		this.green = cast(typeof(this.green))green;
124 		this.blue = cast(typeof(this.blue))blue;
125 	}
126 	this(const AnalogRGB analog) pure @safe
127 		in(analog.red <= 1.0, "Red value out of range")
128 		in(analog.red >= 0.0, "Red value out of range")
129 		in(analog.green <= 1.0, "Green value out of range")
130 		in(analog.green >= 0.0, "Green value out of range")
131 		in(analog.blue <= 1.0, "Blue value out of range")
132 		in(analog.blue >= 0.0, "Blue value out of range")
133 	{
134 		this.red = cast(typeof(this.red))(analog.red * maxRed!(typeof(this)));
135 		this.green = cast(typeof(this.green))(analog.green * maxGreen!(typeof(this)));
136 		this.blue = cast(typeof(this.blue))(analog.blue * maxBlue!(typeof(this)));
137 	}
138 	auto opCast(T: AnalogRGB)() const {
139 		return AnalogRGB(this.redFP, this.greenFP, this.blueFP);
140 	}
141 	static if (hasAlpha!(typeof(this))) {
142 		this(uint red, uint green, uint blue, uint alpha) pure @safe
143 			in(red < (1<<redSize), "Red value out of range")
144 			in(green < (1<<greenSize), "Green value out of range")
145 			in(blue < (1<<blueSize), "Blue value out of range")
146 			in(alpha < (1<<alphaSize), "Alpha value out of range")
147 		{
148 			this.red = cast(typeof(this.red))red;
149 			this.green = cast(typeof(this.green))green;
150 			this.blue = cast(typeof(this.blue))blue;
151 			this.alpha = cast(typeof(this.alpha))alpha;
152 		}
153 		this(AnalogRGBA analog) pure @safe
154 			in(analog.red <= 1.0, "Red value out of range")
155 			in(analog.red >= 0.0, "Red value out of range")
156 			in(analog.green <= 1.0, "Green value out of range")
157 			in(analog.green >= 0.0, "Green value out of range")
158 			in(analog.blue <= 1.0, "Blue value out of range")
159 			in(analog.blue >= 0.0, "Blue value out of range")
160 			in(analog.alpha <= 1.0, "Blue value out of range")
161 			in(analog.alpha >= 0.0, "Blue value out of range")
162 		{
163 			this.red = cast(typeof(this.red))(analog.red * maxRed!(typeof(this)));
164 			this.green = cast(typeof(this.green))(analog.green * maxGreen!(typeof(this)));
165 			this.blue = cast(typeof(this.blue))(analog.blue * maxBlue!(typeof(this)));
166 			this.alpha = cast(typeof(this.alpha))(analog.alpha * maxAlpha!(typeof(this)));
167 		}
168 		auto opCast(T: AnalogRGBA)() const {
169 			return AnalogRGBA(this.redFP, this.greenFP, this.blueFP, this.alphaFP);
170 		}
171 	} else {
172 		// Missing alpha channel is the equivalent of 100% opacity
173 		auto opCast(T: AnalogRGBA)() const {
174 			return AnalogRGBA(this.redFP, this.greenFP, this.blueFP, 1.0);
175 		}
176 	}
177 }