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