1 // Written in the D programming language.
2 
3 /**
4     This module implements various _color type conversions.
5 
6     Authors:    Manu Evans
7     Copyright:  Copyright (c) 2015, Manu Evans.
8     License:    $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0)
9     Source:     $(PHOBOSSRC ggplotd/color/conv.d)
10 */
11 module ggplotd.color.conv;
12 
13 import ggplotd.color;
14 import ggplotd.color.rgb;
15 import ggplotd.color.xyz;
16 import ggplotd.color.hsx;
17 
18 import std.traits : isNumeric, isIntegral, isFloatingPoint, isSigned, isSomeChar, TemplateOf;
19 import std.typetuple : TypeTuple;
20 
21 @safe pure nothrow @nogc:
22 
23 
24 /**
25 Convert between color types.
26 */
27 To convertColor(To, From)(From color) if(isColor!To && isColor!From)
28 {
29     // no conversion is necessary
30     static if(is(To == From))
31         return color;
32 
33     // *** XYZ is the root type ***
34     else static if(isXYZ!From && isXYZ!To)
35     {
36         alias F = To.ComponentType;
37         return To(F(color.X), F(color.Y), F(color.Z));
38     }
39 
40     // following conversions come in triplets:
41     //   Type!U -> Type!V
42     //   Type -> Parent
43     //   Parent -> type
44 
45     // *** RGB triplet ***
46     else static if(isRGB!From && isRGB!To)
47     {
48         alias ToType = To.ComponentType;
49         alias FromType = From.ComponentType;
50 
51         auto src = color.tristimulusWithAlpha;
52 
53         static if(false && From.colorSpace == To.colorSpace && isIntegral!FromType && FromType.sizeof <= 2 &&
54                     (From.linear != To.linear || !is(FromType == ToType)))
55         {
56             alias WorkType = WorkingType!(FromType, ToType);
57             enum NumValues = 1 << (FromType.sizeof*8);
58 
59             // <= 16bit type conversion should use a look-up table
60             shared immutable ToType[NumValues] conversionTable = {
61                 ToType[NumValues] table = void;
62                 foreach(i; 0..NumValues)
63                 {
64                     WorkType v = convertPixelType!WorkType(cast(FromType)i);
65                     static if(From.linear == false)
66                         v = toLinear!(From.colorSpace)(v);
67                     static if(To.linear == false)
68                         v = toGamma!(To.colorSpace)(v);
69                     table[i] = convertPixelType!ToType(v);
70                 }
71                 return table;
72             }();
73 
74             static if(To.hasAlpha)
75                 return To(conversionTable[src[0]], conversionTable[src[0]], conversionTable[src[1]], conversionTable[src[2]], convertPixelType!ToType(src[3]));
76             else
77                 return To(conversionTable[src[0]], conversionTable[src[0]], conversionTable[src[1]], conversionTable[src[2]]);
78         }
79         else static if(From.colorSpace == To.colorSpace && From.linear == To.linear)
80         {
81             // color space is the same, just do type conversion
82             return To(convertPixelType!ToType(src[0]), convertPixelType!ToType(src[1]), convertPixelType!ToType(src[2]), convertPixelType!ToType(src[3]));
83         }
84         else
85         {
86             // unpack the working values
87             alias WorkType = WorkingType!(FromType, ToType);
88             WorkType r = convertPixelType!WorkType(src[0]);
89             WorkType g = convertPixelType!WorkType(src[1]);
90             WorkType b = convertPixelType!WorkType(src[2]);
91 
92             static if(From.linear == false)
93             {
94                 r = toLinear!(From.colorSpace)(r);
95                 g = toLinear!(From.colorSpace)(g);
96                 b = toLinear!(From.colorSpace)(b);
97             }
98             static if(From.colorSpace != To.colorSpace)
99             {
100                 enum toXYZ = RGBColorSpaceMatrix!(From.colorSpace, WorkType);
101                 enum toRGB = inverse(RGBColorSpaceMatrix!(To.colorSpace, WorkType));
102                 enum mat = multiply(toXYZ, toRGB);
103                 WorkType[3] v = multiply(mat, [r, g, b]);
104                 r = v[0]; g = v[1]; b = v[2];
105             }
106             static if(To.linear == false)
107             {
108                 r = toGamma!(To.colorSpace)(r);
109                 g = toGamma!(To.colorSpace)(g);
110                 b = toGamma!(To.colorSpace)(b);
111             }
112 
113             // convert and return the output
114             static if(To.hasAlpha)
115                 return To(convertPixelType!ToType(r), convertPixelType!ToType(g), convertPixelType!ToType(b), convertPixelType!ToType(src[3]));
116             else
117                 return To(convertPixelType!ToType(r), convertPixelType!ToType(g), convertPixelType!ToType(b));
118         }
119     }
120     else static if(isRGB!From && isXYZ!To)
121     {
122         alias ToType = To.ComponentType;
123         alias FromType = From.ComponentType;
124         alias WorkType = WorkingType!(FromType, ToType);
125 
126         // unpack the working values
127         auto src = color.tristimulus;
128         WorkType r = convertPixelType!WorkType(src[0]);
129         WorkType g = convertPixelType!WorkType(src[1]);
130         WorkType b = convertPixelType!WorkType(src[2]);
131 
132         static if(From.linear == false)
133         {
134             r = toLinear!(From.colorSpace)(r);
135             g = toLinear!(From.colorSpace)(g);
136             b = toLinear!(From.colorSpace)(b);
137         }
138 
139         // transform to XYZ
140         enum toXYZ = RGBColorSpaceMatrix!(From.colorSpace, WorkType);
141         WorkType[3] v = multiply(toXYZ, [r, g, b]);
142         return To(v[0], v[1], v[2]);
143     }
144     else static if(isXYZ!From && isRGB!To)
145     {
146         alias ToType = To.ComponentType;
147         alias FromType = From.ComponentType;
148         alias WorkType = WorkingType!(FromType, ToType);
149 
150         enum toRGB = inverse(RGBColorSpaceMatrix!(To.colorSpace, WorkType));
151         WorkType[3] v = multiply(toRGB, [ WorkType(color.X), WorkType(color.Y), WorkType(color.Z) ]);
152 
153         static if(To.linear == false)
154         {
155             v[0] = toGamma!(To.colorSpace)(v[0]);
156             v[1] = toGamma!(To.colorSpace)(v[1]);
157             v[2] = toGamma!(To.colorSpace)(v[2]);
158         }
159 
160         return To(convertPixelType!ToType(v[0]), convertPixelType!ToType(v[1]), convertPixelType!ToType(v[2]));
161     }
162 
163     // *** xyY triplet ***
164     else static if(isxyY!From && isxyY!To)
165     {
166         alias F = To.ComponentType;
167         return To(F(color.x), F(color.y), F(color.Y));
168     }
169     else static if(isxyY!From && isXYZ!To)
170     {
171         alias F = To.ComponentType;
172         if(color.y == F(0))
173             return To(F(0), F(0), F(0));
174         else
175             return To(F(color.x*color.Y/color.y), F(color.Y), F((F(1)-color.x-color.y)*color.Y/color.y));
176     }
177     else static if(isXYZ!From && isxyY!To)
178     {
179         alias F = To.ComponentType;
180         auto sum = color.X + color.Y + color.Z;
181         if(sum == F(0))
182             return To(WhitePoint!F.D65.x, WhitePoint!F.D65.y, F(0));
183         else
184             return To(F(color.X/sum), F(color.Y/sum), F(color.Y));
185     }
186 
187     // *** HSx triplet ***
188     else static if(isHSx!From && isHSx!To)
189     {
190         // HACK: cast through RGB (this works fine, but could be faster)
191         return cast(To)cast(From.ParentColourSpace)from;
192     }
193     else static if(isHSx!From && isRGB!To)
194     {
195         import std.math : abs;
196 
197         alias ToType = To.ComponentType;
198         alias WT = FloatTypeFor!ToType;
199 
200         auto c = color.tupleof;
201         WT h = convertPixelType!WT(c[0]);
202         WT s = convertPixelType!WT(c[1]);
203         WT x = convertPixelType!WT(c[2]);
204 
205         WT C, m;
206         static if(From.type == HSxType.HSV)
207         {
208             C = x*s;
209             m = x - C;
210         }
211         else static if(From.type == HSxType.HSL)
212         {
213             C = (1 - abs(2*x - 1))*s;
214             m = x - C/2;
215         }
216         else static if(From.type == HSxType.HSI)
217         {
218             C = s;
219             m = x - (r+g+b)*WT(1.0/3.0);
220         }
221         else static if(From.type == HSxType.HCY)
222         {
223             C = s;
224         }
225 
226         WT H = h/60;
227         WT X = C*(1 - abs(H%2.0 - 1));
228 
229         WT r, g, b;
230         if(H < 1)
231             r = C, g = X, b = 0;
232         else if(H < 2)
233             r = X, g = C, b = 0;
234         else if(H < 3)
235             r = 0, g = C, b = X;
236         else if(H < 4)
237             r = 0, g = X, b = C;
238         else if(H < 5)
239             r = X, g = 0, b = C;
240         else if(H < 6)
241             r = C, g = 0, b = X;
242 
243         static if(From.type == HSxType.HCY)
244         {
245             enum YAxis = RGBColorSpaceMatrix!(From.colorSpace, WT)[1];
246             m = x - (YAxis[0]*r + YAxis[1]*g + YAxis[2]*b); // Derive from Luma'
247         }
248 
249         return To(convertPixelType!ToType(r+m), convertPixelType!ToType(g+m), convertPixelType!ToType(b+m));
250     }
251     else static if(isRGB!From && isHSx!To)
252     {
253         import std.algorithm : min, max;
254         import std.math : abs;
255 
256         alias ToType = To.ComponentType;
257         alias WT = FloatTypeFor!ToType;
258 
259         auto c = color.tristimulus;
260         WT r = convertPixelType!WT(c[0]);
261         WT g = convertPixelType!WT(c[1]);
262         WT b = convertPixelType!WT(c[2]);
263 
264         WT M = max(r, g, b);
265         WT m = min(r, g, b);
266         WT C = M-m;
267 
268         // Calculate Hue
269         WT h;
270         if(C == 0)
271             h = 0;
272         else if(M == r)
273             h = WT(60) * ((g-b)/C % WT(6));
274         else if(M == g)
275             h = WT(60) * ((b-r)/C + WT(2));
276         else if(M == b)
277             h = WT(60) * ((r-g)/C + WT(4));
278 
279         WT s, x;
280         static if(To.type == HSxType.HSV)
281         {
282             x = M; // 'Value'
283             s = x == 0 ? WT(0) : C/x; // Saturation
284         }
285         else static if(To.type == HSxType.HSL)
286         {
287             x = (M + m)/WT(2); // Lightness
288             s = (x == 0 || x == 1) ? WT(0) : C/(1 - abs(2*x - 1)); // Saturation
289         }
290         else static if(To.type == HSxType.HSI)
291         {
292             x = (r + g + b)/WT(3); // Intensity
293             s = x == 0 ? WT(0) : 1 - m/x; // Saturation
294         }
295         else static if(To.type == HSxType.HCY)
296         {
297             enum YAxis = RGBColorSpaceMatrix!(To.colorSpace, WT)[1];
298             x = YAxis[0]*r + YAxis[1]*g + YAxis[2]*b; // Luma'
299             s = C; // Chroma
300         }
301 
302         return To(convertPixelType!ToType(h), convertPixelType!ToType(s), convertPixelType!ToType(x));
303     }
304 
305     // *** fallback plan ***
306     else
307     {
308         // cast along a conversion path to reach our target conversion
309         alias Path = ConversionPath!(From, To);
310         return convertColor!To(convertColor!(Path[0])(color));
311     }
312 }
313 
314 unittest
315 {
316     // test RGB format conversions
317     alias UnsignedRGB = RGB!("rgb", ubyte);
318     alias SignedRGBX = RGB!("rgbx", byte);
319     alias FloatRGBA = RGB!("rgba", float);
320 
321     static assert(cast(UnsignedRGB)SignedRGBX(0x20,0x30,-10)               == UnsignedRGB(0x40,0x60,0));
322     static assert(cast(UnsignedRGB)FloatRGBA(1,0.5,0,1)                    == UnsignedRGB(0xFF,0x80,0));
323     static assert(cast(UnsignedRGB)cast(FloatRGBA)UnsignedRGB(0xFF,0x80,0) == UnsignedRGB(0xFF,0x80,0));
324     static assert(cast(FloatRGBA)UnsignedRGB(0xFF,0x80,0)                  == FloatRGBA(1,float(0x80)/float(0xFF),0,0));
325     static assert(cast(FloatRGBA)SignedRGBX(127,-127,-128)                 == FloatRGBA(1,-1,-1,0));
326 
327     // test greyscale conversion
328     alias UnsignedL = RGB!("l", ubyte);
329 
330     // TODO... we can't test this properly since DMD can't CTFE the '^^' operator! >_<
331 
332     alias XYZf = XYZ!float;
333 
334     // test RGB conversions
335     alias sRGBA = RGB!("rgba", ubyte, false, RGBColorSpace.sRGB);
336     alias lRGBA = RGB!("rgba", ushort, true, RGBColorSpace.sRGB);
337     alias gRGBA = RGB!("rgba", byte, false, RGBColorSpace.sRGB_Gamma2_2);
338     alias sRGBAf = RGB!("rgba", float, false, RGBColorSpace.sRGB);
339     alias lRGBAf = RGB!("rgba", double, true, RGBColorSpace.sRGB);
340     alias gRGBAf = RGB!("rgba", float, false, RGBColorSpace.sRGB_Gamma2_2);
341 
342     assert(cast(lRGBA)sRGBA(0xFF, 0xFF, 0xFF, 0xFF)           == lRGBA(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF));
343     assert(cast(gRGBA)sRGBA(0xFF, 0x80, 0x01, 0xFF)           == gRGBA(0x7F, 0x3F, 0x03, 0x7F));
344     assert(cast(sRGBA)cast(XYZf)sRGBA(0xFF, 0xFF, 0xFF, 0xFF) == sRGBA(0xFF, 0xFF, 0xFF, 0));
345     //...
346 
347     // test PackedRGB conversions
348     //...
349 
350     // test xyY conversions
351     alias xyYf = xyY!float;
352 
353     static assert(cast(xyYf)XYZf(0.5, 1, 0.5) == xyYf(0.25, 0.5, 1));
354     static assert(cast(XYZf)xyYf(0.5, 0.5, 1) == XYZf(1, 1, 0));
355 
356     // check the degenerate cases
357     static assert(cast(xyYf)XYZf(0, 0, 0) == xyYf(WhitePoint!float.D65.x, WhitePoint!float.D65.y, 0));
358     static assert(cast(XYZf)xyYf(0.5, 0, 1) == XYZf(0, 0, 0));
359 
360     // test HSx conversions
361     alias HSVf = HSV!float;
362     alias HSLf = HSL!float;
363     alias HSIf = HSI!float;
364     alias HCYf = HCY!float;
365 
366     static assert(cast(HSVf)RGB8(255, 0, 0) == HSVf(0, 1, 1));
367     static assert(cast(RGB8)HSVf(0, 1, 1) == RGB8(255, 0, 0));
368     static assert(cast(RGB8)HSVf(60, 0.5, 0.5) == RGB8(128, 128, 64));
369 
370     static assert(cast(HSLf)RGB8(255, 0, 0) == HSLf(0, 1, 0.5));
371     static assert(cast(RGB8)HSLf(0, 1, 0.5) == RGB8(255, 0, 0));
372     static assert(cast(RGB8)HSLf(60, 0.5, 0.5) == RGB8(191, 191, 64));
373 
374     static assert(cast(HSIf)RGB8(255, 0, 0) == HSIf(0, 1, 1.0/3));
375     static assert(cast(HSIf)RGB8(255, 255, 0) == HSIf(60, 1, 2.0/3));
376 //    static assert(cast(RGB8)HSIf(0, 1, 1) == RGB8(1, 0, 0));
377 
378 //    pragma(msg, cast(RGB8)HCYf(0, 0, 1));
379 //    static assert(cast(HCYf)RGB8(255, 0, 0) == HCYf(0, 1, 1));
380 //    static assert(cast(RGB8)HCYf(0, 1, 1) == RGB8(1, 0, 0));
381 
382     //...
383 }
384 
385 
386 /**
387 * Create a color from hex strings in the standard forms: (#/$/0x)rgb/argb/rrggbb/aarrggbb
388 */
389 Color colorFromString(Color = RGB8, C)(const(C)[] hex) if(isSomeChar!C)
390 {
391     static ubyte val(C c)
392     {
393         if(c >= '0' && c <= '9')
394             return cast(ubyte)(c - '0');
395         else if(c >= 'a' && c <= 'f')
396             return cast(ubyte)(c - 'a' + 10);
397         else if(c >= 'A' && c <= 'F')
398             return cast(ubyte)(c - 'A' + 10);
399         else
400             assert(false, "Invalid hex string");
401     }
402 
403     if(hex.length > 0 && (hex[0] == '#' || hex[0] == '$'))
404         hex = hex[1..$];
405     else if(hex.length > 1 && (hex[0] == '0' && hex[1] == 'x'))
406         hex = hex[2..$];
407 
408     if(hex.length == 3)
409     {
410         ubyte r = val(hex[0]);
411         ubyte g = val(hex[1]);
412         ubyte b = val(hex[2]);
413         return cast(Color)RGB8(cast(ubyte)(r | (r << 4)), cast(ubyte)(g | (g << 4)), cast(ubyte)(b | (b << 4)));
414     }
415     if(hex.length == 4)
416     {
417         ubyte a = val(hex[0]);
418         ubyte r = val(hex[1]);
419         ubyte g = val(hex[2]);
420         ubyte b = val(hex[3]);
421         return cast(Color)RGBA8(cast(ubyte)(r | (r << 4)), cast(ubyte)(g | (g << 4)), cast(ubyte)(b | (b << 4)), cast(ubyte)(a | (a << 4)));
422     }
423     if(hex.length == 6)
424     {
425         ubyte r = cast(ubyte)(val(hex[0]) << 4) | val(hex[1]);
426         ubyte g = cast(ubyte)(val(hex[2]) << 4) | val(hex[3]);
427         ubyte b = cast(ubyte)(val(hex[4]) << 4) | val(hex[5]);
428         return cast(Color)RGB8(r, g, b);
429     }
430     if(hex.length == 8)
431     {
432         ubyte a = cast(ubyte)(val(hex[0]) << 4) | val(hex[1]);
433         ubyte r = cast(ubyte)(val(hex[2]) << 4) | val(hex[3]);
434         ubyte g = cast(ubyte)(val(hex[4]) << 4) | val(hex[5]);
435         ubyte b = cast(ubyte)(val(hex[6]) << 4) | val(hex[7]);
436         return cast(Color)RGBA8(r, g, b, a);
437     }
438     else
439     {
440         // TODO: should we look up colors from the W3C color table by name?
441 
442         assert(false, "Invalid hex string!");
443     }
444 }
445 
446 ///
447 unittest
448 {
449     // common hex formats supported:
450 
451     // 3 digits
452     static assert(colorFromString("F80") == RGB8(0xFF,0x88, 0x00));
453     static assert(colorFromString("#F80") == RGB8(0xFF,0x88, 0x00));
454     static assert(colorFromString("$F80") == RGB8(0xFF,0x88, 0x00));
455     static assert(colorFromString("0xF80") == RGB8(0xFF,0x88, 0x00));
456 
457     // 6 digits
458     static assert(colorFromString("FF8000") == RGB8(0xFF,0x80, 0x00));
459     static assert(colorFromString("#FF8000") == RGB8(0xFF,0x80, 0x00));
460     static assert(colorFromString("$FF8000") == RGB8(0xFF,0x80, 0x00));
461     static assert(colorFromString("0xFF8000") == RGB8(0xFF,0x80, 0x00));
462 
463     // 4/8 digita (/w alpha)
464     static assert(colorFromString!RGBA8("#8C41") == RGBA8(0xCC,0x44, 0x11, 0x88));
465     static assert(colorFromString!RGBA8("#80CC4401") == RGBA8(0xCC,0x44, 0x01, 0x80));
466 }
467 
468 
469 package:
470 
471 // convert between pixel data types
472 To convertPixelType(To, From)(From v) if(isNumeric!From && isNumeric!To)
473 {
474     static if(isIntegral!From && isIntegral!To)
475     {
476         // extending normalised integer types is not trivial
477         return convertNormInt!To(v);
478     }
479     else static if(isIntegral!From && isFloatingPoint!To)
480     {
481         import std.algorithm : max;
482         alias FP = FloatTypeFor!(From, To);
483         static if(isSigned!From) // max(c, -1) is the signed conversion followed by D3D, OpenGL, etc.
484             return To(max(v*FP(1.0/From.max), FP(-1.0)));
485         else
486             return To(v*FP(1.0/From.max));
487     }
488     else static if(isFloatingPoint!From && isIntegral!To)
489     {
490         alias FP = FloatTypeFor!(To, From);
491         // HACK: this is incomplete!
492         //       +0.5 rounding only works for positive numbers
493         //       we also need to clamp (saturate) [To.min, To.max]
494         return cast(To)(v*FP(To.max) + FP(0.5));
495     }
496     else
497         return To(v);
498 }
499 
500 
501 // converts directly between fixed-point color types, without doing float conversions
502 // ** this should be tested for performance; we can optimise the small->large conversions with table lookups
503 To convertNormInt(To, From)(From i) if(isIntegral!To && isIntegral!From)
504 {
505     import std.traits: isUnsigned, Unsigned;
506     template Iota(alias start, alias end)
507     {
508         static if(end == start)
509             alias Iota = TypeTuple!();
510         else
511             alias Iota = TypeTuple!(Iota!(start, end-1), end-1);
512     }
513     enum Bits(T) = T.sizeof*8;
514 
515     static if(isUnsigned!To && isUnsigned!From)
516     {
517         static if(Bits!To <= Bits!From)
518             return To(i >> (Bits!From-Bits!To));
519         else
520         {
521             To r;
522             enum numReps = Bits!To/Bits!From;
523             foreach(j; Iota!(0, numReps))
524                 r |= To(i) << (j*Bits!From);
525             return r;
526         }
527     }
528     else static if(isUnsigned!To)
529     {
530         if(i < 0) // if i is negative, return 0
531             return 0;
532         else
533         {
534             enum Sig = Bits!From-1;
535             static if(Bits!To < Bits!From)
536                 return cast(To)(i >> (Sig-Bits!To));
537             else
538             {
539                 To r;
540                 enum numReps = Bits!To/Sig;
541                 foreach(j; Iota!(1, numReps+1))
542                     r |= To(cast(Unsigned!From)(i&From.max)) << (Bits!To - j*Sig);
543                 enum remain = Bits!To - numReps*Sig;
544                 static if(remain)
545                     r |= cast(Unsigned!From)(i&From.max) >> (Sig - remain);
546                 return r;
547             }
548         }
549     }
550     else static if(isUnsigned!From)
551     {
552         static if(Bits!To <= Bits!From)
553             return To(i >> (Bits!From-Bits!To+1));
554         else
555         {
556             Unsigned!To r;
557             enum numReps = Bits!To/Bits!From;
558             foreach(j; Iota!(0, numReps))
559                 r |= Unsigned!To(i) << (j*Bits!From);
560             return To(r >> 1);
561         }
562     }
563     else
564     {
565         static if(Bits!To <= Bits!From)
566             return cast(To)(i >> (Bits!From-Bits!To));
567         else
568         {
569             enum Sig = Bits!From-1;
570             enum Fill = Bits!To - Bits!From;
571 
572             To r = To(i) << Fill;
573             enum numReps = Fill/Sig;
574             foreach(j; Iota!(1, numReps+1))
575                 r |= Unsigned!To(cast(Unsigned!From)(i&From.max)) << (Fill - j*Sig);
576             enum remain = Fill - numReps*Sig;
577             static if(remain)
578                 r |= cast(Unsigned!From)(i&From.max) >> (Sig - remain);
579             return r;
580         }
581     }
582 }
583 
584 unittest
585 {
586     // static asserts since these should all ctfe:
587 
588     // unsigned -> unsigned
589     static assert(convertNormInt!ubyte(ushort(0x3765)) == 0x37);
590     static assert(convertNormInt!ushort(ubyte(0x37)) == 0x3737);
591     static assert(convertNormInt!ulong(ubyte(0x35)) == 0x3535353535353535);
592 
593     // signed -> unsigned
594     static assert(convertNormInt!ubyte(short(-61)) == 0);
595     static assert(convertNormInt!ubyte(short(0x3795)) == 0x6F);
596     static assert(convertNormInt!ushort(byte(0x37)) == 0x6EDD);
597     static assert(convertNormInt!ulong(byte(0x35)) == 0x6AD5AB56AD5AB56A);
598 
599     // unsigned -> signed
600     static assert(convertNormInt!byte(ushort(0x3765)) == 0x1B);
601     static assert(convertNormInt!short(ubyte(0x37)) == 0x1B9B);
602     static assert(convertNormInt!long(ubyte(0x35)) == 0x1A9A9A9A9A9A9A9A);
603 
604     // signed -> signed
605     static assert(convertNormInt!byte(short(0x3795)) == 0x37);
606     static assert(convertNormInt!byte(short(-28672)) == -112);
607     static assert(convertNormInt!short(byte(0x37)) == 0x376E);
608     static assert(convertNormInt!short(byte(-109)) == -27866);
609     static assert(convertNormInt!long(byte(-45)) == -3195498973398505005);
610 }
611 
612 
613 // try and use the preferred float type
614 // if the int type exceeds the preferred float precision, we'll upgrade the float
615 template FloatTypeFor(IntType, RequestedFloat = float)
616 {
617     static if(IntType.sizeof > 2)
618         alias FloatTypeFor = double;
619     else
620         alias FloatTypeFor = RequestedFloat;
621 }
622 
623 // find the fastest type to do format conversion without losing precision
624 template WorkingType(From, To)
625 {
626     static if(isIntegral!From && isIntegral!To)
627     {
628         // small integer types can use float and not lose precision
629         static if(From.sizeof <= 2 && To.sizeof <= 2)
630             alias WorkingType = float;
631         else
632             alias WorkingType = double;
633     }
634     else static if(isIntegral!From && isFloatingPoint!To)
635         alias WorkingType = To;
636     else static if(isFloatingPoint!From && isIntegral!To)
637         alias WorkingType = FloatTypeFor!To;
638     else
639     {
640         static if(From.sizeof > To.sizeof)
641             alias WorkingType = From;
642         else
643             alias WorkingType = To;
644     }
645 }
646 
647 // find the conversion path from one distant type to another
648 template ConversionPath(From, To)
649 {
650     template isParentType(Parent, Of)
651     {
652         static if(isXYZ!Of)
653             enum isParentType = false;
654         else static if(isInstanceOf!(TemplateOf!Parent, Of.ParentColor))
655             enum isParentType = true;
656         else
657             enum isParentType = isParentType!(Parent, Of.ParentColor);
658     }
659 
660     template FindPath(From, To)
661     {
662         static if(isInstanceOf!(TemplateOf!To, From))
663             alias FindPath = TypeTuple!(To);
664         else static if(isParentType!(From, To))
665             alias FindPath = TypeTuple!(FindPath!(From, To.ParentColor), To);
666         else
667             alias FindPath = TypeTuple!(From, FindPath!(From.ParentColor, To));
668     }
669 
670     alias Path = FindPath!(From, To);
671     static if(Path.length == 1 && !is(Path[0] == From))
672         alias ConversionPath = Path;
673     else
674         alias ConversionPath = Path[1..$];
675 }