1 module magicalrainbows.utils;
2 
3 import std.traits;
4 
5 package:
6 
7 T read(T)(const ubyte[] input) if (isMutable!T)
8 	in(input.length == T.sizeof, "Mismatch between input buffer size and expected value size")
9 {
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 Precision redFP(Precision = double, Colour)(const Colour colour) if (hasRed!Colour) {
44 	return (cast(Precision)colour.red / maxRed!Colour);
45 }
46 void redFP(Precision, Colour)(ref Colour colour, Precision value) if (hasRed!Colour) {
47 	colour.red = cast(typeof(colour.red))(value * maxRed!Colour);
48 }
49 
50 Precision greenFP(Precision = double, Colour)(const Colour colour) if (hasGreen!Colour) {
51 	return (cast(Precision)colour.green / maxGreen!Colour);
52 }
53 void greenFP(Precision, Colour)(ref Colour colour, Precision value) if (hasGreen!Colour) {
54 	colour.green = cast(typeof(colour.green))(value * maxGreen!Colour);
55 }
56 
57 Precision blueFP(Precision = double, Colour)(const Colour colour) if (hasBlue!Colour) {
58 	return (cast(Precision)colour.blue / maxBlue!Colour);
59 }
60 void blueFP(Precision, Colour)(ref Colour colour, Precision value) if (hasBlue!Colour) {
61 	colour.blue = cast(typeof(colour.blue))(value * maxBlue!Colour);
62 }
63 
64 Precision alphaFP(Precision = double, Colour)(const Colour colour) if (hasAlpha!Colour) {
65 	return (cast(Precision)colour.alpha / maxAlpha!Colour);
66 }
67 void alphaFP(Precision, Colour)(ref Colour colour, Precision value) if (hasAlpha!Colour) {
68 	colour.alpha = cast(typeof(colour.alpha))(value * maxAlpha!Colour);
69 }
70 
71 @safe pure unittest {
72 	import magicalrainbows.formats : RGB888;
73 	import std.math : isClose;
74 	assert(RGB888(255, 128, 0).redFP() == 1.0);
75 	assert(RGB888(255, 128, 0).greenFP().isClose(0.5019607843137254));
76 	assert(RGB888(255, 128, 0).blueFP() == 0.0);
77 	assert(RGB888(0, 255, 128).redFP() == 0.0);
78 	assert(RGB888(0, 255, 128).greenFP() == 1.0);
79 	assert(RGB888(0, 255, 128).blueFP().isClose(0.5019607843137254));
80 	assert(RGB888(128, 0, 255).redFP().isClose(0.5019607843137254));
81 	assert(RGB888(128, 0, 255).greenFP() == 0.0);
82 	assert(RGB888(128, 0, 255).blueFP() == 1.0);
83 }
84 
85 template LinearFormatOf(ColourFormat, Precision) {
86 	static if (hasAlpha!ColourFormat) {
87 		import magicalrainbows.formats : AnalogRGBA;
88 		alias LinearFormatOf = AnalogRGBA!Precision;
89 	} else {
90 		import magicalrainbows.formats : AnalogRGB;
91 		alias LinearFormatOf = AnalogRGB!Precision;
92 	}
93 }
94 
95 LinearFormatOf!(Format, Precision) asLinearRGB(Precision = double, Format)(const Format colour) {
96 	static if (isDigital!Format) {
97 		const red = colour.redFP!Precision.toLinearRGB;
98 		const green = colour.greenFP!Precision.toLinearRGB;
99 		const blue = colour.blueFP!Precision.toLinearRGB;
100 		static if (hasAlpha!Format) {
101 			const alpha = colour.alphaFP!Precision.toLinearRGB;
102 		}
103 	} else {
104 		const red = colour.red.toLinearRGB;
105 		const green = colour.green.toLinearRGB;
106 		const blue = colour.blue.toLinearRGB;
107 		static if (hasAlpha!Format) {
108 			const alpha = colour.alpha.toLinearRGB;
109 		}
110 	}
111 	static if (hasAlpha!Format) {
112 		return LinearFormatOf!(Format, Precision)(red, green, blue, alpha);
113 	} else {
114 		return LinearFormatOf!(Format, Precision)(red, green, blue);
115 	}
116 }
117 
118 private Precision toLinearRGB(Precision)(const Precision input) @safe pure @nogc nothrow {
119 	return input > 0.03928 ?
120 		((input + 0.055) / 1.055) ^^ 2.4 :
121 		input / 12.92 ;
122 }
123 
124 private Precision gammaCorrect(Precision)(const Precision input, const Precision factor = 2.2) @safe pure @nogc nothrow {
125 	return input ^^ (1.0 / factor);
126 }
127 
128 T colourConvert(T, size_t Size1, size_t Size2, Source)(Source val ) {
129 	static if (Size1 > Size2) {
130 		return cast(T)(val << (Size1 - Size2));
131 	} else static if (Size1 < Size2) {
132 		return cast(T)(val >> (Size2 - Size1));
133 	} else {
134 		return cast(T)val;
135 	}
136 }
137 
138 mixin template colourConstructors() {
139 	this(uint red, uint green, uint blue) pure @safe
140 		in(red < (1<<redSize), "Red value out of range")
141 		in(green < (1<<greenSize), "Green value out of range")
142 		in(blue < (1<<blueSize), "Blue value out of range")
143 	{
144 		this.red = cast(typeof(this.red))red;
145 		this.green = cast(typeof(this.green))green;
146 		this.blue = cast(typeof(this.blue))blue;
147 	}
148 	this(Precision)(const AnalogRGB!Precision analog) pure @safe
149 		in(analog.red <= 1.0, "Red value out of range")
150 		in(analog.red >= 0.0, "Red value out of range")
151 		in(analog.green <= 1.0, "Green value out of range")
152 		in(analog.green >= 0.0, "Green value out of range")
153 		in(analog.blue <= 1.0, "Blue value out of range")
154 		in(analog.blue >= 0.0, "Blue value out of range")
155 	{
156 		this.red = cast(typeof(this.red))(analog.red * maxRed!(typeof(this)));
157 		this.green = cast(typeof(this.green))(analog.green * maxGreen!(typeof(this)));
158 		this.blue = cast(typeof(this.blue))(analog.blue * maxBlue!(typeof(this)));
159 		static if(hasAlpha!(typeof(this))) {
160 			this.alpha = maxAlpha!(typeof(this));
161 		}
162 	}
163 	T opCast(T: AnalogRGB!Precision, Precision)() const {
164 		return AnalogRGB(this.redFP, this.greenFP, this.blueFP);
165 	}
166 	static if (hasAlpha!(typeof(this))) {
167 		this(uint red, uint green, uint blue, uint alpha) pure @safe
168 			in(red < (1<<redSize), "Red value out of range")
169 			in(green < (1<<greenSize), "Green value out of range")
170 			in(blue < (1<<blueSize), "Blue value out of range")
171 			in(alpha < (1<<alphaSize), "Alpha value out of range")
172 		{
173 			this.red = cast(typeof(this.red))red;
174 			this.green = cast(typeof(this.green))green;
175 			this.blue = cast(typeof(this.blue))blue;
176 			this.alpha = cast(typeof(this.alpha))alpha;
177 		}
178 		this(Precision)(AnalogRGBA!Precision analog) pure @safe
179 			in(analog.red <= 1.0, "Red value out of range")
180 			in(analog.red >= 0.0, "Red value out of range")
181 			in(analog.green <= 1.0, "Green value out of range")
182 			in(analog.green >= 0.0, "Green value out of range")
183 			in(analog.blue <= 1.0, "Blue value out of range")
184 			in(analog.blue >= 0.0, "Blue value out of range")
185 			in(analog.alpha <= 1.0, "Blue value out of range")
186 			in(analog.alpha >= 0.0, "Blue value out of range")
187 		{
188 			this.red = cast(typeof(this.red))(analog.red * maxRed!(typeof(this)));
189 			this.green = cast(typeof(this.green))(analog.green * maxGreen!(typeof(this)));
190 			this.blue = cast(typeof(this.blue))(analog.blue * maxBlue!(typeof(this)));
191 			this.alpha = cast(typeof(this.alpha))(analog.alpha * maxAlpha!(typeof(this)));
192 		}
193 		T opCast(T: AnalogRGBA!Precision, Precision)() const {
194 			return AnalogRGBA(this.redFP, this.greenFP, this.blueFP, this.alphaFP);
195 		}
196 	} else {
197 		// Missing alpha channel is the equivalent of 100% opacity
198 		T opCast(T: AnalogRGBA!Precision, Precision)() const {
199 			return AnalogRGBA(this.redFP, this.greenFP, this.blueFP, 1.0);
200 		}
201 	}
202 }