1 /**
2 Color type and operations. Port of ae.utils.graphics.
3 
4 License:
5     This Source Code Form is subject to the terms of
6     the Mozilla Public License, v. 2.0. If a copy of
7     the MPL was not distributed with this file, You
8     can obtain one at http://mozilla.org/MPL/2.0/.
9  
10  Copyright: Vladimir Panteleev <vladimir@thecybershadow.net>
11  Copyright: Guillaume Piolat <contact@auburnsounds.com>
12  */
13 
14 module dplug.graphics.color;
15 
16 import std.traits;
17 import std.math;
18 import std.algorithm.comparison : min, max;
19 
20 import inteli.emmintrin;
21 
22 import dplug.core.math;
23 
24 
25 /// Evaluates to array of strings with name for each field.
26 @property string[] structFields(T)()
27 if (is(T == struct) || is(T == class))
28 {
29 	import std.string : split;
30 
31 	string[] fields;
32 	foreach (i, f; T.init.tupleof)
33 	{
34 		string field = T.tupleof[i].stringof;
35 		field = field.split(".")[$-1];
36 		fields ~= field;
37 	}
38 	return fields;
39 }
40 
41 
42 T itpl(T, U)(T low, T high, U r, U rLow, U rHigh)
43 {
44 	return cast(T)(low + (cast(Signed!T)high-cast(Signed!T)low) * (cast(Signed!U)r - cast(Signed!U)rLow) / (cast(Signed!U)rHigh - cast(Signed!U)rLow));
45 }
46 
47 auto sqr(T)(T x) { return x*x; }
48 
49 
50 void sort2(T)(ref T x, ref T y)
51 {
52     if (x > y)
53     {
54         T z = x;
55         x = y;
56         y = z;
57     }
58 }
59 
60 byte sign(T)(T x)
61 {
62     return x<0 ? -1 : (x>0 ? 1 : 0);
63 }
64 
65 /// Integer log2.
66 private ubyte ilog2(T)(T n)
67 {
68 	ubyte result = 0;
69 	while (n >>= 1)
70 		result++;
71 	return result;
72 }
73 
74 private T nextPowerOfTwo(T)(T x)
75 {
76 	x |= x >>  1;
77 	x |= x >>  2;
78 	x |= x >>  4;
79 	static if (T.sizeof > 1)
80 		x |= x >>  8;
81 	static if (T.sizeof > 2)
82 		x |= x >> 16;
83 	static if (T.sizeof > 4)
84 		x |= x >> 32;
85 	return x + 1;
86 }
87 
88 /// Like std.typecons.Tuple, but a template mixin.
89 /// Unlike std.typecons.Tuple, names may not be omitted - but repeating types may be.
90 /// Example: FieldList!(ubyte, "r", "g", "b", ushort, "a");
91 mixin template FieldList(Fields...)
92 {
93 	mixin(GenFieldList!(void, Fields));
94 }
95 
96 template GenFieldList(T, Fields...)
97 {
98 	static if (Fields.length == 0)
99 		enum GenFieldList = "";
100 	else
101 	{
102 		static if (is(typeof(Fields[0]) == string))
103 			enum GenFieldList = T.stringof ~ " " ~ Fields[0] ~ ";\n" ~ GenFieldList!(T, Fields[1..$]);
104 		else
105 			enum GenFieldList = GenFieldList!(Fields[0], Fields[1..$]);
106 	}
107 }
108 
109 unittest
110 {
111 	struct S
112 	{
113 		mixin FieldList!(ubyte, "r", "g", "b", ushort, "a");
114 	}
115 	S s;
116 	static assert(is(typeof(s.r) == ubyte));
117 	static assert(is(typeof(s.g) == ubyte));
118 	static assert(is(typeof(s.b) == ubyte));
119 	static assert(is(typeof(s.a) == ushort));
120 }
121 
122 
123 /// Return the number of bits used to store the value part, i.e.
124 /// T.sizeof*8 for integer parts and the mantissa size for
125 /// floating-point types.
126 template valueBits(T)
127 {
128 	static if (is(T : ulong))
129 		enum valueBits = T.sizeof * 8;
130 	else
131         static if (is(T : real))
132             enum valueBits = T.mant_dig;
133         else
134             static assert(false, "Don't know how many value bits there are in " ~ T.stringof);
135 }
136 
137 static assert(valueBits!uint == 32);
138 static assert(valueBits!double == 53);
139 
140 
141 
142 /// Instantiates to a color type.
143 /// FieldTuple is the color specifier, as parsed by
144 /// the FieldList template from ae.utils.meta.
145 /// By convention, each field's name indicates its purpose:
146 /// - x: padding
147 /// - a: alpha
148 /// - l: lightness (or grey, for monochrome images)
149 /// - others (r, g, b, etc.): color information
150 
151 // MAYDO: figure out if we need all these methods in the color type itself
152 //   - code such as gamma conversion needs to create color types
153 //   - ReplaceType can't copy methods
154 //   - even if we move out all conventional methods, that still leaves operator overloading
155 
156 struct Color(FieldTuple...)
157 {
158 	alias Spec = FieldTuple;
159 	mixin FieldList!FieldTuple;
160 
161 	// A "dumb" type to avoid cyclic references.
162 	private struct Fields { mixin FieldList!FieldTuple; }
163 
164 	/// Whether or not all channel fields have the same base type.
165 	// Only "true" supported for now, may change in the future (e.g. for 5:6:5)
166 	enum homogenous = true;
167 
168 	/// The number of fields in this color type.
169 	enum channels = Fields.init.tupleof.length;
170 
171 	static if (homogenous)
172 	{
173 		alias ChannelType = typeof(Fields.init.tupleof[0]);
174 		enum channelBits = valueBits!ChannelType;
175 	}
176 
177 	/// Return a Color instance with all fields set to "value".
178 	static typeof(this) monochrome(ChannelType value)
179 	{
180 		typeof(this) r;
181 		foreach (i, f; r.tupleof)
182 			r.tupleof[i] = value;
183 		return r;
184 	}
185 
186 	/// Interpolate between two colors.
187 	static typeof(this) itpl(P)(typeof(this) c0, typeof(this) c1, P p, P p0, P p1)
188 	{
189 		alias ExpandNumericType!(ChannelType, P.sizeof*8) U;
190 		alias Signed!U S;
191 		typeof(this) r;
192 		foreach (i, f; r.tupleof)
193 			static if (r.tupleof[i].stringof != "r.x") // skip padding
194 				r.tupleof[i] = cast(ChannelType).itpl(cast(U)c0.tupleof[i], cast(U)c1.tupleof[i], cast(S)p, cast(S)p0, cast(S)p1);
195 		return r;
196 	}
197 
198 	/// Construct an RGB color from a typical hex string.
199 	static if (is(typeof(this.r) == ubyte) && is(typeof(this.g) == ubyte) && is(typeof(this.b) == ubyte))
200 	{
201 		static typeof(this) fromHex(in char[] s)
202 		{
203 			import std.conv;
204 			import std.exception;
205 
206 			enforce(s.length == 6, "Invalid color string");
207 			typeof(this) c;
208 			c.r = s[0..2].to!ubyte(16);
209 			c.g = s[2..4].to!ubyte(16);
210 			c.b = s[4..6].to!ubyte(16);
211 			return c;
212 		}
213 
214 		string toHex() const
215 		{
216 			import std.string;
217 			return format("%02X%02X%02X", r, g, b);
218 		}
219 	}
220 
221 	/// Warning: overloaded operators preserve types and may cause overflows
222 	typeof(this) opUnary(string op)()
223 		if (op=="~" || op=="-")
224 	{
225 		typeof(this) r;
226 		foreach (i, f; r.tupleof)
227 			static if(r.tupleof[i].stringof != "r.x") // skip padding
228 				r.tupleof[i] = cast(typeof(r.tupleof[i])) mixin(op ~ `this.tupleof[i]`);
229 		return r;
230 	}
231 
232 	/// ditto
233 	typeof(this) opOpAssign(string op)(int o)
234 	{
235 		foreach (i, f; this.tupleof)
236 			static if(this.tupleof[i].stringof != "this.x") // skip padding
237 				this.tupleof[i] = cast(typeof(this.tupleof[i])) mixin(`this.tupleof[i]` ~ op ~ `=o`);
238 		return this;
239 	}
240 
241 	/// ditto
242 	typeof(this) opOpAssign(string op, T)(T o)
243 		if (is(T==struct) && structFields!T == structFields!Fields)
244 	{
245 		foreach (i, f; this.tupleof)
246 			static if(this.tupleof[i].stringof != "this.x") // skip padding
247 				this.tupleof[i] = cast(typeof(this.tupleof[i])) mixin(`this.tupleof[i]` ~ op ~ `=o.tupleof[i]`);
248 		return this;
249 	}
250 
251 	/// ditto
252 	typeof(this) opBinary(string op, T)(T o)
253 		if (op != "~")
254 	{
255 		auto r = this;
256 		mixin("r" ~ op ~ "=o;");
257 		return r;
258 	}
259 
260 	/// Apply a custom operation for each channel. Example:
261 	/// COLOR.op!q{(a + b) / 2}(colorA, colorB);
262 	static typeof(this) op(string expr, T...)(T values)
263 	{
264 		static assert(values.length <= 10);
265 
266 		string genVars(string channel)
267 		{
268 			string result;
269 			foreach (j, Tj; T)
270 			{
271 				static if (is(Tj == struct)) // TODO: tighter constraint (same color channels)?
272 					result ~= "auto " ~ cast(char)('a' + j) ~ " = values[" ~ cast(char)('0' + j) ~ "]." ~  channel ~ ";\n";
273 				else
274 					result ~= "auto " ~ cast(char)('a' + j) ~ " = values[" ~ cast(char)('0' + j) ~ "];\n";
275 			}
276 			return result;
277 		}
278 
279 		typeof(this) r;
280 		foreach (i, f; r.tupleof)
281 			{
282 				mixin(genVars(r.tupleof[i].stringof[2..$]));
283 				r.tupleof[i] = mixin(expr);
284 			}
285 		return r;
286 	}
287 
288 	T opCast(T)()
289 		if (is(T==struct) && structFields!T == structFields!Fields)
290 	{
291 		T t;
292 		foreach (i, f; this.tupleof)
293 			t.tupleof[i] = cast(typeof(t.tupleof[i])) this.tupleof[i];
294 		return t;
295 	}
296 
297 	/// Sum of all channels
298 	ExpandIntegerType!(ChannelType, ilog2(nextPowerOfTwo(channels))) sum()
299 	{
300 		typeof(return) result;
301 		foreach (i, f; this.tupleof)
302 			static if (this.tupleof[i].stringof != "this.x") // skip padding
303 				result += this.tupleof[i];
304 		return result;
305 	}
306 }
307 
308 // The "x" has the special meaning of "padding" and is ignored in some circumstances
309 alias Color!(ubyte  , "r", "g", "b"     ) RGB    ;
310 alias Color!(ubyte  , "r", "g", "b", "a") RGBA   ;
311 
312 
313 alias Color!(ubyte  , "l"               ) L8     ;
314 alias Color!(ushort , "l"               ) L16    ;
315 alias Color!(float ,  "l"               ) L32f   ;
316 
317 alias Color!(float  , "r", "g", "b"     ) RGBf   ;
318 alias Color!(float  , "r", "g", "b", "a") RGBAf  ;
319 
320 static assert(L32f.sizeof == 4);
321 static assert(RGBf.sizeof == 12);
322 static assert(RGBAf.sizeof == 16);
323 
324 unittest
325 {
326 	static assert(RGB.sizeof == 3);
327 	RGB[2] arr;
328 	static assert(arr.sizeof == 6);
329 
330 	RGB hex = RGB.fromHex("123456");
331 	assert(hex.r == 0x12 && hex.g == 0x34 && hex.b == 0x56);
332 
333 	assert(RGB(1, 2, 3) + RGB(4, 5, 6) == RGB(5, 7, 9));
334 
335 	RGB c = RGB(1, 1, 1);
336 	c += 1;
337 	assert(c == RGB(2, 2, 2));
338 	c += c;
339 	assert(c == RGB(4, 4, 4));
340 }
341 
342 unittest
343 {
344 	import std.conv;
345 
346 	L8 r;
347 
348 	r = L8.itpl(L8(100), L8(200), 15, 10, 20);
349 	assert(r ==  L8(150), text(r));
350 }
351 
352 
353 unittest
354 {
355 	Color!(real, "r", "g", "b") c;
356 }
357 
358 /// Obtains the type of each channel for homogenous colors.
359 template ChannelType(T)
360 {
361 	static if (is(T == struct))
362 		alias ChannelType = T.ChannelType;
363 	else
364 		alias ChannelType = T;
365 }
366 
367 /// Resolves to a Color instance with a different ChannelType.
368 template ChangeChannelType(COLOR, T)
369 	if (isNumeric!COLOR)
370 {
371 	alias ChangeChannelType = T;
372 }
373 
374 /// ditto
375 template ChangeChannelType(COLOR, T)
376 	if (is(COLOR : Color!Spec, Spec...))
377 {
378 	static assert(COLOR.homogenous, "Can't change ChannelType of non-homogenous Color");
379 	alias ChangeChannelType = Color!(T, COLOR.Spec[1..$]);
380 }
381 
382 static assert(is(ChangeChannelType!(int, ushort) == ushort));
383 
384 
385 /// Expand to a built-in numeric type of the same kind
386 /// (signed integer / unsigned integer / floating-point)
387 /// with at least the indicated number of bits of precision.
388 template ResizeNumericType(T, uint bits)
389 {
390 	static if (is(T : ulong))
391 		static if (isSigned!T)
392 			alias ResizeNumericType = SignedBitsType!bits;
393 		else
394 			alias ResizeNumericType = UnsignedBitsType!bits;
395 	else
396         static if (is(T : real))
397         {
398             static if (bits <= float.mant_dig)
399                 alias ResizeNumericType = float;
400             else
401                 static if (bits <= double.mant_dig)
402                     alias ResizeNumericType = double;
403                 else
404                     static if (bits <= real.mant_dig)
405                         alias ResizeNumericType = real;
406                     else
407                         static assert(0, "No floating-point type big enough to fit " ~ bits.stringof ~ " bits");
408         }
409         else
410             static assert(false, "Don't know how to resize type: " ~ T.stringof);
411 }
412 
413 static assert(is(ResizeNumericType!(float, double.mant_dig) == double));
414 
415 /// Expand to a built-in numeric type of the same kind
416 /// (signed integer / unsigned integer / floating-point)
417 /// with at least additionalBits more bits of precision.
418 alias ExpandNumericType(T, uint additionalBits) =
419     ResizeNumericType!(T, valueBits!T + additionalBits);
420 
421 /// Unsigned integer type big enough to fit N bits of precision.
422 template UnsignedBitsType(uint bits)
423 {
424 	static if (bits <= 8)
425 		alias ubyte UnsignedBitsType;
426 	else
427         static if (bits <= 16)
428             alias ushort UnsignedBitsType;
429         else
430             static if (bits <= 32)
431                 alias uint UnsignedBitsType;
432             else
433                 static if (bits <= 64)
434                     alias ulong UnsignedBitsType;
435                 else
436                     static assert(0, "No integer type big enough to fit " ~ bits.stringof ~ " bits");
437 }
438 
439 template SignedBitsType(uint bits)
440 {
441 	alias Signed!(UnsignedBitsType!bits) SignedBitsType;
442 }
443 
444 
445 /// Wrapper around ExpandNumericType to only expand numeric types.
446 template ExpandIntegerType(T, size_t bits)
447 {
448 	static if (is(T:real))
449 		alias ExpandIntegerType = T;
450 	else
451 		alias ExpandIntegerType = ExpandNumericType!(T, bits);
452 }
453 
454 alias ExpandChannelType(COLOR, int BYTES) =
455 	ChangeChannelType!(COLOR,
456 		ExpandNumericType!(ChannelType!COLOR, BYTES * 8));
457 
458 alias temp = ExpandChannelType!(RGB, 1);
459 //static assert(is(ExpandChannelType!(RGB, 1) == RGB16));
460 
461 unittest
462 {
463 	alias RGBf = ChangeChannelType!(RGB, float);
464 	auto rgb = RGB(1, 2, 3);
465 	import std.conv : to;
466 	auto rgbf = rgb.to!RGBf();
467 	assert(rgbf.r == 1f);
468 	assert(rgbf.g == 2f);
469 	assert(rgbf.b == 3f);
470 }
471 
472 // ***************************************************************************
473 
474 RGBA blendColor(RGBA fg, RGBA bg, ubyte alpha) pure nothrow @nogc
475 {
476     ubyte invAlpha = cast(ubyte)(~cast(int)alpha);
477     version(LDC)
478     {
479         __m128i alphaMask = _mm_set1_epi32( (invAlpha << 16) | alpha ); // [ alpha invAlpha... (4x)]
480         __m128i mmfg = _mm_cvtsi32_si128( *cast(int*)(&fg) );
481         __m128i mmbg = _mm_cvtsi32_si128( *cast(int*)(&bg) );
482         __m128i zero = _mm_setzero_si128();
483         __m128i colorMask = _mm_unpacklo_epi8(mmfg, mmbg); // [fg.r bg.r fg.g bg.g fg.b bg.b fg.a bg.a 0 (8x) ]
484         colorMask = _mm_unpacklo_epi8(colorMask, zero); // [fg.r bg.r fg.g bg.g fg.b bg.b fg.a bg.a ]
485         __m128i product = _mm_madd_epi16(colorMask, alphaMask); // [ fg[i]*alpha+bg[i]*invAlpha (4x) ]
486 
487         // To divide a ushort by 255, LLVM suggests to
488         // * sign multiply by 32897
489         // * right-shift logically by 23
490         // Thanks https://godbolt.org/
491         product *= _mm_set1_epi32(32897); // PERF: this leads to inefficient code with several pmul
492         product = _mm_srli_epi32(product, 23);
493         __m128i c = _mm_packs_epi32(product, zero);
494         c = _mm_packus_epi16(c, zero);
495         RGBA result = void;
496         *cast(int*)(&result) = c[0];
497         return result;
498     }
499     else
500     {
501         // PERF should be a lot to optimize there
502         
503         RGBA c = void;
504         c.r = cast(ubyte) ( ( (fg.r * alpha) + (bg.r * invAlpha)  ) / ubyte.max );
505         c.g = cast(ubyte) ( ( (fg.g * alpha) + (bg.g * invAlpha)  ) / ubyte.max );
506         c.b = cast(ubyte) ( ( (fg.b * alpha) + (bg.b * invAlpha)  ) / ubyte.max );
507         c.a = cast(ubyte) ( ( (fg.a * alpha) + (bg.a * invAlpha)  ) / ubyte.max );
508         return c;
509     }
510 }
511 
512 RGB blendColor(RGB fg, RGB bg, ubyte alpha) pure nothrow @nogc
513 {
514     ubyte invAlpha = cast(ubyte)(~cast(int)alpha);
515     RGB c = void;
516     c.r = cast(ubyte) ( ( (fg.r * alpha) + (bg.r * invAlpha)  ) / ubyte.max );
517     c.g = cast(ubyte) ( ( (fg.g * alpha) + (bg.g * invAlpha)  ) / ubyte.max );
518     c.b = cast(ubyte) ( ( (fg.b * alpha) + (bg.b * invAlpha)  ) / ubyte.max );
519     return c;
520 }
521 
522 L16 blendColor(L16 fg, L16 bg, ushort alpha) pure nothrow @nogc
523 {
524     ushort v = cast(ushort) ( ((fg.l * alpha) + (bg.l * cast(ushort)(~cast(int)alpha))) / ushort.max );
525     return L16(v);
526 }
527 
528