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