The OpenD Programming Language

1 // Written in the D programming language.
2 
3 /**
4 This package defines human-visible colors in various formats.
5 
6 RGB _color formats are particularly flexible to express typical RGB image data
7 in a wide variety of common formats without having to write adapters.
8 
9 It is intended that this library facilitate a common API that allows a variety
10 of image and multimedia libraries to interact more seamlessly, without need
11 for constant conversions between custom, library-defined _color data types.
12 
13 This package pays very careful attention to correctness with respect to
14 _color space definitions, and correct handling of _color space conversions.
15 For best results, users should also pay careful attention to _color space
16 selection when working with _color data, and the rest will follow.
17 A crash course on understanding _color space can be found at
18 $(LINK2 https://en.wikipedia.org/wiki/Color_space, wikipedia).
19 
20 More information regarding specific _color spaces can be found in their
21 respective modules.
22 
23 All types and functions offered in this package are $(D_INLINECODE pure),
24 $(D_INLINECODE nothrow), $(D_INLINECODE @safe) and $(D_INLINECODE @nogc).
25 It is intended to be useful by realtime or memory-contrained systems such as
26 video games, games consoles or mobile devices.
27 
28 
29 Expressing images:
30 
31 Images may be expressed in a variety of ways, but a simple way may be to use
32 std.experimental.ndslice to produce simple n-dimensional images.
33 
34 -------
35 import std.experimental.color;
36 import std.experimental.ndslice;
37 
38 auto imageBuffer = new RGB8[height*width];
39 auto image = imageBuffer.sliced(height, width);
40 
41 foreach(ref row; image)
42 {
43     foreach(ref pixel; row)
44     {
45         pixel = Colors.white;
46     }
47 }
48 -------
49 
50 Use of ndslice this way allows the use of n-dimentional slices to produce
51 sub-images.
52 
53 
54 Implement custom _color type:
55 
56 The library is extensible such that users or libraries can easily supply
57 their own custom _color formats and expect comprehensive conversion and
58 interaction with any other libraries or code that makes use of
59 std.experimental._color.
60 
61 The requirement for a user _color type is to specify a 'parent' _color space,
62 and expose at least a set of conversion functions to/from that parent.
63 
64 For instance, HSV is a cylindrical representation of RGB colors, so the
65 'parent' _color type in this case is said to be RGB.
66 If your custom _color space is not derivative of an existing _color space,
67 then you should provide conversion between CIE XYZ, which can most simply
68 express all of human-visible _color.
69 
70 -------
71 struct HueOnlyColor
72 {
73     alias ParentColor = HSV!float;
74 
75     static To convertColorImpl(To, From)(From color) if (is(From == HueOnlyColor) && isHSx!To)
76     {
77         return To(color.hue, 1.0, 1.0); // assume maximum saturation, maximum lightness
78     }
79 
80     static To convertColorImpl(To, From)(From color) if (isHSx!From && is(To == HueOnlyColor))
81     {
82         return HueOnlyColor(color.h); // just keep the hue
83     }
84 
85 private:
86     float hue;
87 }
88 
89 static assert(isColor!HueOnlyColor == true, "This is all that is required to create a valid color type");
90 -------
91 
92 If your _color type has template args, it may also be necessary to produce a
93 third convertColorImpl function that converts between instantiations with
94 different template args.
95 
96 
97 Authors:    Manu Evans
98 Copyright:  Copyright (c) 2015, Manu Evans.
99 License:    $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0)
100 Source:     $(PHOBOSSRC std/experimental/color/_package.d)
101 */
102 module std.experimental.color;
103 
104 import std.experimental.color.rgb;
105 import std.experimental.color.xyz : XYZ;
106 import std.experimental.normint;
107 
108 import std.traits : isNumeric, isFloatingPoint, isSomeChar, Unqual;
109 
110 
111 /**
112 Detect whether $(D_INLINECODE T) is a color type compatible with std.experimental.color.
113 */
114 enum isColor(T) = __traits(compiles, convertColor!(XYZ!float)(T.init));
115 
116 ///
117 unittest
118 {
119     import std.experimental.color.rgb;
120     import std.experimental.color.hsx;
121     import std.experimental.color.xyz;
122 
123     static assert(isColor!RGB8 == true);
124     static assert(isColor!(XYZ!float) == true);
125     static assert(isColor!(HSL!float) == true);
126     static assert(isColor!float == false);
127 }
128 
129 /**
130 Detect whether $(D_INLINECODE T) is a valid color component type.
131 */
132 enum isColorComponentType(T) = isFloatingPoint!T || is(T == NormalizedInt!U, U);
133 
134 /**
135 Detect whether $(D_INLINECODE T) can represent a color component.
136 */
137 enum isColorScalarType(T) = isNumeric!T || is(T == NormalizedInt!U, U);
138 
139 
140 // declare some common color types
141 
142 /** 24 bit RGB color type with 8 bits per channel. */
143 alias RGB8 =    RGB!("rgb", ubyte);
144 /** 32 bit RGB color type with 8 bits per channel. */
145 alias RGBX8 =   RGB!("rgbx", ubyte);
146 /** 32 bit RGB + alpha color type with 8 bits per channel. */
147 alias RGBA8 =   RGB!("rgba", ubyte);
148 
149 /** Floating point RGB color type. */
150 alias RGBf32 =  RGB!("rgb", float);
151 /** Floating point RGB + alpha color type. */
152 alias RGBAf32 = RGB!("rgba", float);
153 
154 /** 24 bit BGR color type with 8 bits per channel. */
155 alias BGR8 =    RGB!("bgr", ubyte);
156 /** 32 bit BGR color type with 8 bits per channel. */
157 alias BGRX8 =   RGB!("bgrx", ubyte);
158 /** 32 bit BGR + alpha color type with 8 bits per channel. */
159 alias BGRA8 =   RGB!("bgra", ubyte);
160 
161 /** 8 bit luminance-only color type. */
162 alias L8 =      RGB!("l", ubyte);
163 /** 8 bit alpha-only color type. */
164 alias A8 =      RGB!("a", ubyte);
165 /** 16 bit luminance + alpha color type with 8 bits per channel. */
166 alias LA8 =     RGB!("la", ubyte);
167 
168 /** 16 bit signed UV color type with 8 bits per channel. */
169 alias UV8 =     RGB!("rg", byte);
170 /** 24 bit signed UVW color type with 8 bits per channel. */
171 alias UVW8 =    RGB!("rgb", byte);
172 
173 
174 /** Set of colors defined by X11, adopted by the W3C, SVG, and other popular libraries. */
175 enum Colors
176 {
177     aliceBlue            = RGB8(240,248,255), /// <font color=aliceBlue>&#x25FC;</font>
178     antiqueWhite         = RGB8(250,235,215), /// <font color=antiqueWhite>&#x25FC;</font>
179     aqua                 = RGB8(0,255,255),   /// <font color=aqua>&#x25FC;</font>
180     aquamarine           = RGB8(127,255,212), /// <font color=aquamarine>&#x25FC;</font>
181     azure                = RGB8(240,255,255), /// <font color=azure>&#x25FC;</font>
182     beige                = RGB8(245,245,220), /// <font color=beige>&#x25FC;</font>
183     bisque               = RGB8(255,228,196), /// <font color=bisque>&#x25FC;</font>
184     black                = RGB8(0,0,0),       /// <font color=black>&#x25FC;</font>
185     blanchedAlmond       = RGB8(255,235,205), /// <font color=blanchedAlmond>&#x25FC;</font>
186     blue                 = RGB8(0,0,255),     /// <font color=blue>&#x25FC;</font>
187     blueViolet           = RGB8(138,43,226),  /// <font color=blueViolet>&#x25FC;</font>
188     brown                = RGB8(165,42,42),   /// <font color=brown>&#x25FC;</font>
189     burlyWood            = RGB8(222,184,135), /// <font color=burlyWood>&#x25FC;</font>
190     cadetBlue            = RGB8(95,158,160),  /// <font color=cadetBlue>&#x25FC;</font>
191     chartreuse           = RGB8(127,255,0),   /// <font color=chartreuse>&#x25FC;</font>
192     chocolate            = RGB8(210,105,30),  /// <font color=chocolate>&#x25FC;</font>
193     coral                = RGB8(255,127,80),  /// <font color=coral>&#x25FC;</font>
194     cornflowerBlue       = RGB8(100,149,237), /// <font color=cornflowerBlue>&#x25FC;</font>
195     cornsilk             = RGB8(255,248,220), /// <font color=cornsilk>&#x25FC;</font>
196     crimson              = RGB8(220,20,60),   /// <font color=crimson>&#x25FC;</font>
197     cyan                 = RGB8(0,255,255),   /// <font color=cyan>&#x25FC;</font>
198     darkBlue             = RGB8(0,0,139),     /// <font color=darkBlue>&#x25FC;</font>
199     darkCyan             = RGB8(0,139,139),   /// <font color=darkCyan>&#x25FC;</font>
200     darkGoldenrod        = RGB8(184,134,11),  /// <font color=darkGoldenrod>&#x25FC;</font>
201     darkGray             = RGB8(169,169,169), /// <font color=darkGray>&#x25FC;</font>
202     darkGrey             = RGB8(169,169,169), /// <font color=darkGrey>&#x25FC;</font>
203     darkGreen            = RGB8(0,100,0),     /// <font color=darkGreen>&#x25FC;</font>
204     darkKhaki            = RGB8(189,183,107), /// <font color=darkKhaki>&#x25FC;</font>
205     darkMagenta          = RGB8(139,0,139),   /// <font color=darkMagenta>&#x25FC;</font>
206     darkOliveGreen       = RGB8(85,107,47),   /// <font color=darkOliveGreen>&#x25FC;</font>
207     darkOrange           = RGB8(255,140,0),   /// <font color=darkOrange>&#x25FC;</font>
208     darkOrchid           = RGB8(153,50,204),  /// <font color=darkOrchid>&#x25FC;</font>
209     darkRed              = RGB8(139,0,0),     /// <font color=darkRed>&#x25FC;</font>
210     darkSalmon           = RGB8(233,150,122), /// <font color=darkSalmon>&#x25FC;</font>
211     darkSeaGreen         = RGB8(143,188,143), /// <font color=darkSeaGreen>&#x25FC;</font>
212     darkSlateBlue        = RGB8(72,61,139),   /// <font color=darkSlateBlue>&#x25FC;</font>
213     darkSlateGray        = RGB8(47,79,79),    /// <font color=darkSlateGray>&#x25FC;</font>
214     darkSlateGrey        = RGB8(47,79,79),    /// <font color=darkSlateGrey>&#x25FC;</font>
215     darkTurquoise        = RGB8(0,206,209),   /// <font color=darkTurquoise>&#x25FC;</font>
216     darkViolet           = RGB8(148,0,211),   /// <font color=darkViolet>&#x25FC;</font>
217     deepPink             = RGB8(255,20,147),  /// <font color=deepPink>&#x25FC;</font>
218     deepSkyBlue          = RGB8(0,191,255),   /// <font color=deepSkyBlue>&#x25FC;</font>
219     dimGray              = RGB8(105,105,105), /// <font color=dimGray>&#x25FC;</font>
220     dimGrey              = RGB8(105,105,105), /// <font color=dimGrey>&#x25FC;</font>
221     dodgerBlue           = RGB8(30,144,255),  /// <font color=dodgerBlue>&#x25FC;</font>
222     fireBrick            = RGB8(178,34,34),   /// <font color=fireBrick>&#x25FC;</font>
223     floralWhite          = RGB8(255,250,240), /// <font color=floralWhite>&#x25FC;</font>
224     forestGreen          = RGB8(34,139,34),   /// <font color=forestGreen>&#x25FC;</font>
225     fuchsia              = RGB8(255,0,255),   /// <font color=fuchsia>&#x25FC;</font>
226     gainsboro            = RGB8(220,220,220), /// <font color=gainsboro>&#x25FC;</font>
227     ghostWhite           = RGB8(248,248,255), /// <font color=ghostWhite>&#x25FC;</font>
228     gold                 = RGB8(255,215,0),   /// <font color=gold>&#x25FC;</font>
229     goldenrod            = RGB8(218,165,32),  /// <font color=goldenrod>&#x25FC;</font>
230     gray                 = RGB8(128,128,128), /// <font color=gray>&#x25FC;</font>
231     grey                 = RGB8(128,128,128), /// <font color=grey>&#x25FC;</font>
232     green                = RGB8(0,128,0),     /// <font color=green>&#x25FC;</font>
233     greenYellow          = RGB8(173,255,47),  /// <font color=greenYellow>&#x25FC;</font>
234     honeydew             = RGB8(240,255,240), /// <font color=honeydew>&#x25FC;</font>
235     hotPink              = RGB8(255,105,180), /// <font color=hotPink>&#x25FC;</font>
236     indianRed            = RGB8(205,92,92),   /// <font color=indianRed>&#x25FC;</font>
237     indigo               = RGB8(75,0,130),    /// <font color=indigo>&#x25FC;</font>
238     ivory                = RGB8(255,255,240), /// <font color=ivory>&#x25FC;</font>
239     khaki                = RGB8(240,230,140), /// <font color=khaki>&#x25FC;</font>
240     lavender             = RGB8(230,230,250), /// <font color=lavender>&#x25FC;</font>
241     lavenderBlush        = RGB8(255,240,245), /// <font color=lavenderBlush>&#x25FC;</font>
242     lawnGreen            = RGB8(124,252,0),   /// <font color=lawnGreen>&#x25FC;</font>
243     lemonChiffon         = RGB8(255,250,205), /// <font color=lemonChiffon>&#x25FC;</font>
244     lightBlue            = RGB8(173,216,230), /// <font color=lightBlue>&#x25FC;</font>
245     lightCoral           = RGB8(240,128,128), /// <font color=lightCoral>&#x25FC;</font>
246     lightCyan            = RGB8(224,255,255), /// <font color=lightCyan>&#x25FC;</font>
247     lightGoldenrodYellow = RGB8(250,250,210), /// <font color=lightGoldenrodYellow>&#x25FC;</font>
248     lightGray            = RGB8(211,211,211), /// <font color=lightGray>&#x25FC;</font>
249     lightGrey            = RGB8(211,211,211), /// <font color=lightGrey>&#x25FC;</font>
250     lightGreen           = RGB8(144,238,144), /// <font color=lightGreen>&#x25FC;</font>
251     lightPink            = RGB8(255,182,193), /// <font color=lightPink>&#x25FC;</font>
252     lightSalmon          = RGB8(255,160,122), /// <font color=lightSalmon>&#x25FC;</font>
253     lightSeaGreen        = RGB8(32,178,170),  /// <font color=lightSeaGreen>&#x25FC;</font>
254     lightSkyBlue         = RGB8(135,206,250), /// <font color=lightSkyBlue>&#x25FC;</font>
255     lightSlateGray       = RGB8(119,136,153), /// <font color=lightSlateGray>&#x25FC;</font>
256     lightSlateGrey       = RGB8(119,136,153), /// <font color=lightSlateGrey>&#x25FC;</font>
257     lightSteelBlue       = RGB8(176,196,222), /// <font color=lightSteelBlue>&#x25FC;</font>
258     lightYellow          = RGB8(255,255,224), /// <font color=lightYellow>&#x25FC;</font>
259     lime                 = RGB8(0,255,0),     /// <font color=lime>&#x25FC;</font>
260     limeGreen            = RGB8(50,205,50),   /// <font color=limeGreen>&#x25FC;</font>
261     linen                = RGB8(250,240,230), /// <font color=linen>&#x25FC;</font>
262     magenta              = RGB8(255,0,255),   /// <font color=magenta>&#x25FC;</font>
263     maroon               = RGB8(128,0,0),     /// <font color=maroon>&#x25FC;</font>
264     mediumAquamarine     = RGB8(102,205,170), /// <font color=mediumAquamarine>&#x25FC;</font>
265     mediumBlue           = RGB8(0,0,205),     /// <font color=mediumBlue>&#x25FC;</font>
266     mediumOrchid         = RGB8(186,85,211),  /// <font color=mediumOrchid>&#x25FC;</font>
267     mediumPurple         = RGB8(147,112,219), /// <font color=mediumPurple>&#x25FC;</font>
268     mediumSeaGreen       = RGB8(60,179,113),  /// <font color=mediumSeaGreen>&#x25FC;</font>
269     mediumSlateBlue      = RGB8(123,104,238), /// <font color=mediumSlateBlue>&#x25FC;</font>
270     mediumSpringGreen    = RGB8(0,250,154),   /// <font color=mediumSpringGreen>&#x25FC;</font>
271     mediumTurquoise      = RGB8(72,209,204),  /// <font color=mediumTurquoise>&#x25FC;</font>
272     mediumVioletRed      = RGB8(199,21,133),  /// <font color=mediumVioletRed>&#x25FC;</font>
273     midnightBlue         = RGB8(25,25,112),   /// <font color=midnightBlue>&#x25FC;</font>
274     mintCream            = RGB8(245,255,250), /// <font color=mintCream>&#x25FC;</font>
275     mistyRose            = RGB8(255,228,225), /// <font color=mistyRose>&#x25FC;</font>
276     moccasin             = RGB8(255,228,181), /// <font color=moccasin>&#x25FC;</font>
277     navajoWhite          = RGB8(255,222,173), /// <font color=navajoWhite>&#x25FC;</font>
278     navy                 = RGB8(0,0,128),     /// <font color=navy>&#x25FC;</font>
279     oldLace              = RGB8(253,245,230), /// <font color=oldLace>&#x25FC;</font>
280     olive                = RGB8(128,128,0),   /// <font color=olive>&#x25FC;</font>
281     oliveDrab            = RGB8(107,142,35),  /// <font color=oliveDrab>&#x25FC;</font>
282     orange               = RGB8(255,165,0),   /// <font color=orange>&#x25FC;</font>
283     orangeRed            = RGB8(255,69,0),    /// <font color=orangeRed>&#x25FC;</font>
284     orchid               = RGB8(218,112,214), /// <font color=orchid>&#x25FC;</font>
285     paleGoldenrod        = RGB8(238,232,170), /// <font color=paleGoldenrod>&#x25FC;</font>
286     paleGreen            = RGB8(152,251,152), /// <font color=paleGreen>&#x25FC;</font>
287     paleTurquoise        = RGB8(175,238,238), /// <font color=paleTurquoise>&#x25FC;</font>
288     paleVioletRed        = RGB8(219,112,147), /// <font color=paleVioletRed>&#x25FC;</font>
289     papayaWhip           = RGB8(255,239,213), /// <font color=papayaWhip>&#x25FC;</font>
290     peachPuff            = RGB8(255,218,185), /// <font color=peachPuff>&#x25FC;</font>
291     peru                 = RGB8(205,133,63),  /// <font color=peru>&#x25FC;</font>
292     pink                 = RGB8(255,192,203), /// <font color=pink>&#x25FC;</font>
293     plum                 = RGB8(221,160,221), /// <font color=plum>&#x25FC;</font>
294     powderBlue           = RGB8(176,224,230), /// <font color=powderBlue>&#x25FC;</font>
295     purple               = RGB8(128,0,128),   /// <font color=purple>&#x25FC;</font>
296     red                  = RGB8(255,0,0),     /// <font color=red>&#x25FC;</font>
297     rosyBrown            = RGB8(188,143,143), /// <font color=rosyBrown>&#x25FC;</font>
298     royalBlue            = RGB8(65,105,225),  /// <font color=royalBlue>&#x25FC;</font>
299     saddleBrown          = RGB8(139,69,19),   /// <font color=saddleBrown>&#x25FC;</font>
300     salmon               = RGB8(250,128,114), /// <font color=salmon>&#x25FC;</font>
301     sandyBrown           = RGB8(244,164,96),  /// <font color=sandyBrown>&#x25FC;</font>
302     seaGreen             = RGB8(46,139,87),   /// <font color=seaGreen>&#x25FC;</font>
303     seashell             = RGB8(255,245,238), /// <font color=seashell>&#x25FC;</font>
304     sienna               = RGB8(160,82,45),   /// <font color=sienna>&#x25FC;</font>
305     silver               = RGB8(192,192,192), /// <font color=silver>&#x25FC;</font>
306     skyBlue              = RGB8(135,206,235), /// <font color=skyBlue>&#x25FC;</font>
307     slateBlue            = RGB8(106,90,205),  /// <font color=slateBlue>&#x25FC;</font>
308     slateGray            = RGB8(112,128,144), /// <font color=slateGray>&#x25FC;</font>
309     slateGrey            = RGB8(112,128,144), /// <font color=slateGrey>&#x25FC;</font>
310     snow                 = RGB8(255,250,250), /// <font color=snow>&#x25FC;</font>
311     springGreen          = RGB8(0,255,127),   /// <font color=springGreen>&#x25FC;</font>
312     steelBlue            = RGB8(70,130,180),  /// <font color=steelBlue>&#x25FC;</font>
313     tan                  = RGB8(210,180,140), /// <font color=tan>&#x25FC;</font>
314     teal                 = RGB8(0,128,128),   /// <font color=teal>&#x25FC;</font>
315     thistle              = RGB8(216,191,216), /// <font color=thistle>&#x25FC;</font>
316     tomato               = RGB8(255,99,71),   /// <font color=tomato>&#x25FC;</font>
317     turquoise            = RGB8(64,224,208),  /// <font color=turquoise>&#x25FC;</font>
318     violet               = RGB8(238,130,238), /// <font color=violet>&#x25FC;</font>
319     wheat                = RGB8(245,222,179), /// <font color=wheat>&#x25FC;</font>
320     white                = RGB8(255,255,255), /// <font color=white>&#x25FC;</font>
321     whiteSmoke           = RGB8(245,245,245), /// <font color=whiteSmoke>&#x25FC;</font>
322     yellow               = RGB8(255,255,0),   /// <font color=yellow>&#x25FC;</font>
323     yellowGreen          = RGB8(154,205,50)   /// <font color=yellowGreen>&#x25FC;</font>
324 }
325 
326 
327 /**
328 Convert between _color types.
329 
330 Conversion is always supported between any pair of valid _color types.
331 Colour types usually implement only direct conversion between their immediate 'parent' _color type.
332 In the case of distantly related colors, convertColor will follow a conversion path via
333 intermediate representations such that it is able to perform the conversion.
334 
335 For instance, a conversion from HSV to Lab necessary follows the conversion path: HSV -> RGB -> XYZ -> Lab.
336 
337 Params: color = A _color in some source format.
338 Returns: $(D_INLINECODE color) converted to the target format.
339 */
340 To convertColor(To, From)(From color) @safe pure nothrow @nogc
341 {
342     // cast along a conversion path to reach our target conversion
343     alias Path = ConversionPath!(From, To);
344 
345     // no conversion is necessary
346     static if (Path.length == 0)
347         return color;
348     else static if (Path.length > 1)
349     {
350         // we need to recurse to trace a path via the first common ancestor
351         static if (__traits(compiles, From.convertColorImpl!(Path[0])(color)))
352             return convertColor!To(From.convertColorImpl!(Path[0])(color));
353         else
354             return convertColor!To(To.convertColorImpl!(Path[0])(color));
355     }
356     else
357     {
358         static if (__traits(compiles, From.convertColorImpl!(Path[0])(color)))
359             return From.convertColorImpl!(Path[0])(color);
360         else
361             return To.convertColorImpl!(Path[0])(color);
362     }
363 }
364 ///
365 unittest
366 {
367     assert(convertColor!(RGBA8)(convertColor!(XYZ!float)(RGBA8(0xFF, 0xFF, 0xFF, 0xFF))) == RGBA8(0xFF, 0xFF, 0xFF, 0));
368 }
369 
370 
371 /**
372 Create a color from a string.
373 
374 Params: str = A string representation of a _color.$(BR)
375               May be a hex _color in the standard forms: (#/$)rgb/argb/rrggbb/aarrggbb$(BR)
376               May also be the name of any _color from the $(D_INLINECODE Colors) enum.
377 Returns: The _color expressed by the string.
378 Throws: Throws $(D_INLINECODE std.conv.ConvException) if the string is invalid.
379 */
380 Color colorFromString(Color = RGB8)(scope const(char)[] str) pure @safe
381 {
382     import std.conv : ConvException;
383 
384     uint error;
385     auto r = colorFromStringImpl(str, error);
386 
387     if (error > 0)
388     {
389         if (error == 1)
390             throw new ConvException("Hex string has invalid length");
391         throw new ConvException("String is not a valid color");
392     }
393 
394     return cast(Color)r;
395 }
396 
397 /**
398 Create a color from a string.
399 
400 This version of the function is $(D_INLINECODE nothrow), $(D_INLINECODE @nogc).
401 
402 Params: str = A string representation of a _color.$(BR)
403               May be a hex _color in the standard forms: (#/$)rgb/argb/rrggbb/aarrggbb$(BR)
404               May also be the name of any _color from the $(D_INLINECODE Colors) enum.
405         color = Receives the _color expressed by the string.
406 Returns: $(D_INLINECODE true) if a _color was successfully parsed from the string, $(D_INLINECODE false) otherwise.
407 */
408 bool colorFromString(Color = RGB8)(scope const(char)[] str, out Color color) pure nothrow @safe @nogc
409 {
410     uint error;
411     auto r = colorFromStringImpl(str, error);
412     if (!error)
413     {
414         color = cast(Color)r;
415         return true;
416     }
417     return false;
418 }
419 
420 ///
421 unittest
422 {
423     // common hex formats supported:
424 
425     // 3 digits
426     assert(colorFromString("F80") == RGB8(0xFF, 0x88, 0x00));
427     assert(colorFromString("#F80") == RGB8(0xFF, 0x88, 0x00));
428     assert(colorFromString("$F80") == RGB8(0xFF, 0x88, 0x00));
429 
430     // 6 digits
431     assert(colorFromString("FF8000") == RGB8(0xFF, 0x80, 0x00));
432     assert(colorFromString("#FF8000") == RGB8(0xFF, 0x80, 0x00));
433     assert(colorFromString("$FF8000") == RGB8(0xFF, 0x80, 0x00));
434 
435     // 4/8 digita (/w alpha)
436     assert(colorFromString!RGBA8("#8C41") == RGBA8(0xCC, 0x44, 0x11, 0x88));
437     assert(colorFromString!RGBA8("#80CC4401") == RGBA8(0xCC, 0x44, 0x01, 0x80));
438 
439     // named colors (case-insensitive)
440     assert(colorFromString("red") == RGB8(0xFF, 0x0, 0x0));
441     assert(colorFromString("WHITE") == RGB8(0xFF, 0xFF, 0xFF));
442     assert(colorFromString("LightGoldenrodYellow") == RGB8(250,250,210));
443 
444     // parse failure
445     RGB8 c;
446     assert(colorFromString("Ultraviolet", c) == false);
447 }
448 
449 
450 package:
451 
452 import std.traits : isInstanceOf, TemplateOf;
453 import std.typetuple : TypeTuple;
454 
455 RGBA8 colorFromStringImpl(scope const(char)[] str, out uint error) pure nothrow @safe @nogc
456 {
457     static const(char)[] getHex(const(char)[] hex) pure nothrow @nogc @safe
458     {
459         if (hex.length > 0 && (hex[0] == '#' || hex[0] == '$'))
460             hex = hex[1..$];
461         foreach (i; 0 .. hex.length)
462         {
463             if (!(hex[i] >= '0' && hex[i] <= '9' || hex[i] >= 'a' && hex[i] <= 'f' || hex[i] >= 'A' && hex[i] <= 'F'))
464                 return null;
465         }
466         return hex;
467     }
468 
469     const(char)[] hex = getHex(str);
470     if (hex)
471     {
472         static ubyte val(char c) pure nothrow @nogc @safe
473         {
474             if (c >= '0' && c <= '9')
475                 return cast(ubyte)(c - '0');
476             else if (c >= 'a' && c <= 'f')
477                 return cast(ubyte)(c - 'a' + 10);
478             else
479                 return cast(ubyte)(c - 'A' + 10);
480         }
481 
482         if (hex.length == 3)
483         {
484             ubyte r = val(hex[0]);
485             ubyte g = val(hex[1]);
486             ubyte b = val(hex[2]);
487             return RGBA8(cast(ubyte)(r | (r << 4)), cast(ubyte)(g | (g << 4)), cast(ubyte)(b | (b << 4)), 0);
488         }
489         if (hex.length == 4)
490         {
491             ubyte a = val(hex[0]);
492             ubyte r = val(hex[1]);
493             ubyte g = val(hex[2]);
494             ubyte b = val(hex[3]);
495             return RGBA8(cast(ubyte)(r | (r << 4)), cast(ubyte)(g | (g << 4)), cast(ubyte)(b | (b << 4)), cast(ubyte)(a | (a << 4)));
496         }
497         if (hex.length == 6)
498         {
499             ubyte r = cast(ubyte)(val(hex[0]) << 4) | val(hex[1]);
500             ubyte g = cast(ubyte)(val(hex[2]) << 4) | val(hex[3]);
501             ubyte b = cast(ubyte)(val(hex[4]) << 4) | val(hex[5]);
502             return RGBA8(r, g, b, 0);
503         }
504         if (hex.length == 8)
505         {
506             ubyte a = cast(ubyte)(val(hex[0]) << 4) | val(hex[1]);
507             ubyte r = cast(ubyte)(val(hex[2]) << 4) | val(hex[3]);
508             ubyte g = cast(ubyte)(val(hex[4]) << 4) | val(hex[5]);
509             ubyte b = cast(ubyte)(val(hex[6]) << 4) | val(hex[7]);
510             return RGBA8(r, g, b, a);
511         }
512 
513         error = 1;
514         return RGBA8();
515     }
516 
517     // need to write a string compare, since phobos is not nothrow @nogc, etc...
518     static bool streqi(const(char)[] a, const(char)[] b)
519     {
520         if (a.length != b.length)
521             return false;
522         foreach(i; 0 .. a.length)
523         {
524             auto c1 = (a[i] >= 'A' && a[i] <= 'Z') ? a[i] | 0x20 : a[i];
525             auto c2 = (b[i] >= 'A' && b[i] <= 'Z') ? b[i] | 0x20 : b[i];
526             if(c1 != c2)
527                 return false;
528         }
529         return true;
530     }
531 
532     foreach (k; __traits(allMembers, Colors))
533     {
534         if (streqi(str, k))
535             mixin("return cast(RGBA8)Colors." ~ k ~ ";");
536     }
537 
538     error = 2;
539     return RGBA8();
540 }
541 
542 // find the fastest type to do format conversion without losing precision
543 template WorkingType(From, To)
544 {
545     static if (isFloatingPoint!From && isFloatingPoint!To)
546     {
547         static if (From.sizeof > To.sizeof)
548             alias WorkingType = From;
549         else
550             alias WorkingType = To;
551     }
552     else static if (isFloatingPoint!To)
553         alias WorkingType = To;
554     else static if (isFloatingPoint!From)
555         alias WorkingType = FloatTypeFor!To;
556     else
557     {
558         // small integer types can use float and not lose precision
559         static if (From.sizeof <= 2 && To.sizeof <= 2)
560             alias WorkingType = float;
561         else
562             alias WorkingType = double;
563     }
564 }
565 
566 private template isParentType(Parent, Of)
567 {
568     static if (!is(Of.ParentColor))
569         enum isParentType = false;
570     else static if (isInstanceOf!(TemplateOf!Parent, Of.ParentColor))
571         enum isParentType = true;
572     else
573         enum isParentType = isParentType!(Parent, Of.ParentColor);
574 }
575 
576 private template FindPath(From, To)
577 {
578     static if (isInstanceOf!(TemplateOf!To, From))
579         alias FindPath = TypeTuple!(To);
580     else static if (isParentType!(From, To))
581         alias FindPath = TypeTuple!(FindPath!(From, To.ParentColor), To);
582     else static if (is(From.ParentColor))
583         alias FindPath = TypeTuple!(From, FindPath!(From.ParentColor, To));
584     else
585         static assert(false, "Shouldn't be here!");
586 }
587 
588 // find the conversion path from one distant type to another
589 template ConversionPath(From, To)
590 {
591     static if (is(Unqual!From == Unqual!To))
592         alias ConversionPath = TypeTuple!();
593     else
594     {
595         alias Path = FindPath!(Unqual!From, Unqual!To);
596         static if (Path.length == 1 && !is(Path[0] == From))
597             alias ConversionPath = Path;
598         else
599             alias ConversionPath = Path[1..$];
600     }
601 }
602 unittest
603 {
604     import std.experimental.color.hsx;
605     import std.experimental.color.lab;
606     import std.experimental.color.xyz;
607 
608     // dest indirect conversion paths
609     static assert(is(ConversionPath!(XYZ!float, const XYZ!float) == TypeTuple!()));
610     static assert(is(ConversionPath!(RGB8, RGB8) == TypeTuple!()));
611 
612     static assert(is(ConversionPath!(XYZ!float, XYZ!double) == TypeTuple!(XYZ!double)));
613     static assert(is(ConversionPath!(xyY!float, XYZ!float) == TypeTuple!(XYZ!float)));
614     static assert(is(ConversionPath!(xyY!float, XYZ!double) == TypeTuple!(XYZ!double)));
615     static assert(is(ConversionPath!(XYZ!float, xyY!float) == TypeTuple!(xyY!float)));
616     static assert(is(ConversionPath!(XYZ!float, xyY!double) == TypeTuple!(xyY!double)));
617 
618     static assert(is(ConversionPath!(HSL!float, XYZ!float) == TypeTuple!(RGB!("rgb", float, false), XYZ!float)));
619     static assert(is(ConversionPath!(LCh!float, HSI!double) == TypeTuple!(Lab!float, XYZ!double, RGB!("rgb", double), HSI!double)));
620     static assert(is(ConversionPath!(shared HSI!double, immutable LCh!float) == TypeTuple!(RGB!("rgb", double), XYZ!float, Lab!float, LCh!float)));
621 }
622 
623 // build mixin code to perform expresions per-element
624 template ComponentExpression(string expression, string component, string op)
625 {
626     template BuildExpression(string e, string c, string op)
627     {
628         static if (e.length == 0)
629             enum BuildExpression = "";
630         else static if (e[0] == '_')
631             enum BuildExpression = c ~ BuildExpression!(e[1..$], c, op);
632         else static if (e[0] == '#')
633             enum BuildExpression = op ~ BuildExpression!(e[1..$], c, op);
634         else
635             enum BuildExpression = e[0] ~ BuildExpression!(e[1..$], c, op);
636     }
637     enum ComponentExpression =
638         "static if (is(typeof(this." ~ component ~ ")))" ~ "\n\t" ~
639             BuildExpression!(expression, component, op);
640 }