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>◼</font>
178 antiqueWhite = RGB8(250,235,215), /// <font color=antiqueWhite>◼</font>
179 aqua = RGB8(0,255,255), /// <font color=aqua>◼</font>
180 aquamarine = RGB8(127,255,212), /// <font color=aquamarine>◼</font>
181 azure = RGB8(240,255,255), /// <font color=azure>◼</font>
182 beige = RGB8(245,245,220), /// <font color=beige>◼</font>
183 bisque = RGB8(255,228,196), /// <font color=bisque>◼</font>
184 black = RGB8(0,0,0), /// <font color=black>◼</font>
185 blanchedAlmond = RGB8(255,235,205), /// <font color=blanchedAlmond>◼</font>
186 blue = RGB8(0,0,255), /// <font color=blue>◼</font>
187 blueViolet = RGB8(138,43,226), /// <font color=blueViolet>◼</font>
188 brown = RGB8(165,42,42), /// <font color=brown>◼</font>
189 burlyWood = RGB8(222,184,135), /// <font color=burlyWood>◼</font>
190 cadetBlue = RGB8(95,158,160), /// <font color=cadetBlue>◼</font>
191 chartreuse = RGB8(127,255,0), /// <font color=chartreuse>◼</font>
192 chocolate = RGB8(210,105,30), /// <font color=chocolate>◼</font>
193 coral = RGB8(255,127,80), /// <font color=coral>◼</font>
194 cornflowerBlue = RGB8(100,149,237), /// <font color=cornflowerBlue>◼</font>
195 cornsilk = RGB8(255,248,220), /// <font color=cornsilk>◼</font>
196 crimson = RGB8(220,20,60), /// <font color=crimson>◼</font>
197 cyan = RGB8(0,255,255), /// <font color=cyan>◼</font>
198 darkBlue = RGB8(0,0,139), /// <font color=darkBlue>◼</font>
199 darkCyan = RGB8(0,139,139), /// <font color=darkCyan>◼</font>
200 darkGoldenrod = RGB8(184,134,11), /// <font color=darkGoldenrod>◼</font>
201 darkGray = RGB8(169,169,169), /// <font color=darkGray>◼</font>
202 darkGrey = RGB8(169,169,169), /// <font color=darkGrey>◼</font>
203 darkGreen = RGB8(0,100,0), /// <font color=darkGreen>◼</font>
204 darkKhaki = RGB8(189,183,107), /// <font color=darkKhaki>◼</font>
205 darkMagenta = RGB8(139,0,139), /// <font color=darkMagenta>◼</font>
206 darkOliveGreen = RGB8(85,107,47), /// <font color=darkOliveGreen>◼</font>
207 darkOrange = RGB8(255,140,0), /// <font color=darkOrange>◼</font>
208 darkOrchid = RGB8(153,50,204), /// <font color=darkOrchid>◼</font>
209 darkRed = RGB8(139,0,0), /// <font color=darkRed>◼</font>
210 darkSalmon = RGB8(233,150,122), /// <font color=darkSalmon>◼</font>
211 darkSeaGreen = RGB8(143,188,143), /// <font color=darkSeaGreen>◼</font>
212 darkSlateBlue = RGB8(72,61,139), /// <font color=darkSlateBlue>◼</font>
213 darkSlateGray = RGB8(47,79,79), /// <font color=darkSlateGray>◼</font>
214 darkSlateGrey = RGB8(47,79,79), /// <font color=darkSlateGrey>◼</font>
215 darkTurquoise = RGB8(0,206,209), /// <font color=darkTurquoise>◼</font>
216 darkViolet = RGB8(148,0,211), /// <font color=darkViolet>◼</font>
217 deepPink = RGB8(255,20,147), /// <font color=deepPink>◼</font>
218 deepSkyBlue = RGB8(0,191,255), /// <font color=deepSkyBlue>◼</font>
219 dimGray = RGB8(105,105,105), /// <font color=dimGray>◼</font>
220 dimGrey = RGB8(105,105,105), /// <font color=dimGrey>◼</font>
221 dodgerBlue = RGB8(30,144,255), /// <font color=dodgerBlue>◼</font>
222 fireBrick = RGB8(178,34,34), /// <font color=fireBrick>◼</font>
223 floralWhite = RGB8(255,250,240), /// <font color=floralWhite>◼</font>
224 forestGreen = RGB8(34,139,34), /// <font color=forestGreen>◼</font>
225 fuchsia = RGB8(255,0,255), /// <font color=fuchsia>◼</font>
226 gainsboro = RGB8(220,220,220), /// <font color=gainsboro>◼</font>
227 ghostWhite = RGB8(248,248,255), /// <font color=ghostWhite>◼</font>
228 gold = RGB8(255,215,0), /// <font color=gold>◼</font>
229 goldenrod = RGB8(218,165,32), /// <font color=goldenrod>◼</font>
230 gray = RGB8(128,128,128), /// <font color=gray>◼</font>
231 grey = RGB8(128,128,128), /// <font color=grey>◼</font>
232 green = RGB8(0,128,0), /// <font color=green>◼</font>
233 greenYellow = RGB8(173,255,47), /// <font color=greenYellow>◼</font>
234 honeydew = RGB8(240,255,240), /// <font color=honeydew>◼</font>
235 hotPink = RGB8(255,105,180), /// <font color=hotPink>◼</font>
236 indianRed = RGB8(205,92,92), /// <font color=indianRed>◼</font>
237 indigo = RGB8(75,0,130), /// <font color=indigo>◼</font>
238 ivory = RGB8(255,255,240), /// <font color=ivory>◼</font>
239 khaki = RGB8(240,230,140), /// <font color=khaki>◼</font>
240 lavender = RGB8(230,230,250), /// <font color=lavender>◼</font>
241 lavenderBlush = RGB8(255,240,245), /// <font color=lavenderBlush>◼</font>
242 lawnGreen = RGB8(124,252,0), /// <font color=lawnGreen>◼</font>
243 lemonChiffon = RGB8(255,250,205), /// <font color=lemonChiffon>◼</font>
244 lightBlue = RGB8(173,216,230), /// <font color=lightBlue>◼</font>
245 lightCoral = RGB8(240,128,128), /// <font color=lightCoral>◼</font>
246 lightCyan = RGB8(224,255,255), /// <font color=lightCyan>◼</font>
247 lightGoldenrodYellow = RGB8(250,250,210), /// <font color=lightGoldenrodYellow>◼</font>
248 lightGray = RGB8(211,211,211), /// <font color=lightGray>◼</font>
249 lightGrey = RGB8(211,211,211), /// <font color=lightGrey>◼</font>
250 lightGreen = RGB8(144,238,144), /// <font color=lightGreen>◼</font>
251 lightPink = RGB8(255,182,193), /// <font color=lightPink>◼</font>
252 lightSalmon = RGB8(255,160,122), /// <font color=lightSalmon>◼</font>
253 lightSeaGreen = RGB8(32,178,170), /// <font color=lightSeaGreen>◼</font>
254 lightSkyBlue = RGB8(135,206,250), /// <font color=lightSkyBlue>◼</font>
255 lightSlateGray = RGB8(119,136,153), /// <font color=lightSlateGray>◼</font>
256 lightSlateGrey = RGB8(119,136,153), /// <font color=lightSlateGrey>◼</font>
257 lightSteelBlue = RGB8(176,196,222), /// <font color=lightSteelBlue>◼</font>
258 lightYellow = RGB8(255,255,224), /// <font color=lightYellow>◼</font>
259 lime = RGB8(0,255,0), /// <font color=lime>◼</font>
260 limeGreen = RGB8(50,205,50), /// <font color=limeGreen>◼</font>
261 linen = RGB8(250,240,230), /// <font color=linen>◼</font>
262 magenta = RGB8(255,0,255), /// <font color=magenta>◼</font>
263 maroon = RGB8(128,0,0), /// <font color=maroon>◼</font>
264 mediumAquamarine = RGB8(102,205,170), /// <font color=mediumAquamarine>◼</font>
265 mediumBlue = RGB8(0,0,205), /// <font color=mediumBlue>◼</font>
266 mediumOrchid = RGB8(186,85,211), /// <font color=mediumOrchid>◼</font>
267 mediumPurple = RGB8(147,112,219), /// <font color=mediumPurple>◼</font>
268 mediumSeaGreen = RGB8(60,179,113), /// <font color=mediumSeaGreen>◼</font>
269 mediumSlateBlue = RGB8(123,104,238), /// <font color=mediumSlateBlue>◼</font>
270 mediumSpringGreen = RGB8(0,250,154), /// <font color=mediumSpringGreen>◼</font>
271 mediumTurquoise = RGB8(72,209,204), /// <font color=mediumTurquoise>◼</font>
272 mediumVioletRed = RGB8(199,21,133), /// <font color=mediumVioletRed>◼</font>
273 midnightBlue = RGB8(25,25,112), /// <font color=midnightBlue>◼</font>
274 mintCream = RGB8(245,255,250), /// <font color=mintCream>◼</font>
275 mistyRose = RGB8(255,228,225), /// <font color=mistyRose>◼</font>
276 moccasin = RGB8(255,228,181), /// <font color=moccasin>◼</font>
277 navajoWhite = RGB8(255,222,173), /// <font color=navajoWhite>◼</font>
278 navy = RGB8(0,0,128), /// <font color=navy>◼</font>
279 oldLace = RGB8(253,245,230), /// <font color=oldLace>◼</font>
280 olive = RGB8(128,128,0), /// <font color=olive>◼</font>
281 oliveDrab = RGB8(107,142,35), /// <font color=oliveDrab>◼</font>
282 orange = RGB8(255,165,0), /// <font color=orange>◼</font>
283 orangeRed = RGB8(255,69,0), /// <font color=orangeRed>◼</font>
284 orchid = RGB8(218,112,214), /// <font color=orchid>◼</font>
285 paleGoldenrod = RGB8(238,232,170), /// <font color=paleGoldenrod>◼</font>
286 paleGreen = RGB8(152,251,152), /// <font color=paleGreen>◼</font>
287 paleTurquoise = RGB8(175,238,238), /// <font color=paleTurquoise>◼</font>
288 paleVioletRed = RGB8(219,112,147), /// <font color=paleVioletRed>◼</font>
289 papayaWhip = RGB8(255,239,213), /// <font color=papayaWhip>◼</font>
290 peachPuff = RGB8(255,218,185), /// <font color=peachPuff>◼</font>
291 peru = RGB8(205,133,63), /// <font color=peru>◼</font>
292 pink = RGB8(255,192,203), /// <font color=pink>◼</font>
293 plum = RGB8(221,160,221), /// <font color=plum>◼</font>
294 powderBlue = RGB8(176,224,230), /// <font color=powderBlue>◼</font>
295 purple = RGB8(128,0,128), /// <font color=purple>◼</font>
296 red = RGB8(255,0,0), /// <font color=red>◼</font>
297 rosyBrown = RGB8(188,143,143), /// <font color=rosyBrown>◼</font>
298 royalBlue = RGB8(65,105,225), /// <font color=royalBlue>◼</font>
299 saddleBrown = RGB8(139,69,19), /// <font color=saddleBrown>◼</font>
300 salmon = RGB8(250,128,114), /// <font color=salmon>◼</font>
301 sandyBrown = RGB8(244,164,96), /// <font color=sandyBrown>◼</font>
302 seaGreen = RGB8(46,139,87), /// <font color=seaGreen>◼</font>
303 seashell = RGB8(255,245,238), /// <font color=seashell>◼</font>
304 sienna = RGB8(160,82,45), /// <font color=sienna>◼</font>
305 silver = RGB8(192,192,192), /// <font color=silver>◼</font>
306 skyBlue = RGB8(135,206,235), /// <font color=skyBlue>◼</font>
307 slateBlue = RGB8(106,90,205), /// <font color=slateBlue>◼</font>
308 slateGray = RGB8(112,128,144), /// <font color=slateGray>◼</font>
309 slateGrey = RGB8(112,128,144), /// <font color=slateGrey>◼</font>
310 snow = RGB8(255,250,250), /// <font color=snow>◼</font>
311 springGreen = RGB8(0,255,127), /// <font color=springGreen>◼</font>
312 steelBlue = RGB8(70,130,180), /// <font color=steelBlue>◼</font>
313 tan = RGB8(210,180,140), /// <font color=tan>◼</font>
314 teal = RGB8(0,128,128), /// <font color=teal>◼</font>
315 thistle = RGB8(216,191,216), /// <font color=thistle>◼</font>
316 tomato = RGB8(255,99,71), /// <font color=tomato>◼</font>
317 turquoise = RGB8(64,224,208), /// <font color=turquoise>◼</font>
318 violet = RGB8(238,130,238), /// <font color=violet>◼</font>
319 wheat = RGB8(245,222,179), /// <font color=wheat>◼</font>
320 white = RGB8(255,255,255), /// <font color=white>◼</font>
321 whiteSmoke = RGB8(245,245,245), /// <font color=whiteSmoke>◼</font>
322 yellow = RGB8(255,255,0), /// <font color=yellow>◼</font>
323 yellowGreen = RGB8(154,205,50) /// <font color=yellowGreen>◼</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 }