iv.vfs: "z" in file mode now means "unconditionally use gzip"; made mode parser simplier
[iv.d.git] / color.d
blob90e282f4dab91f3b21dabf7bbbff28351feff814
1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module iv.color;
20 // color in sRGB space
21 struct RGB8 {
22 public:
23 string toString () const nothrow @trusted {
24 import core.stdc.stdio : snprintf;
25 char[256] buf = void;
26 auto len = snprintf(buf.ptr, buf.length, "RGB8(%u,%u,%u)", cast(uint)r, cast(uint)g, cast(uint)b);
27 return buf[0..len].idup;
30 public pure nothrow @safe @nogc:
31 ubyte r=0, g=0, b=0;
33 this (int ar, int ag, int ab) pure nothrow @safe @nogc { pragma(inline, true); r = clampToByte(ar); g = clampToByte(ag); b = clampToByte(ab); }
34 this() (in auto ref RGB8 c) pure nothrow @safe @nogc { pragma(inline, true); r = c.r; g = c.g; b = c.b; }
35 this() (in auto ref SRGB c) pure nothrow @safe @nogc { pragma(inline, true); r = clampToByte(cast(int)(c.r*255.0)); g = clampToByte(cast(int)(c.g*255.0)); b = clampToByte(cast(int)(c.b*255.0)); }
36 this() (in auto ref CLAB c) pure nothrow @safe @nogc { pragma(inline, true); this = cast(RGB8)cast(CXYZD65)c; }
37 this(C) (in auto ref C c) pure nothrow @safe @nogc if (is(C : CXYZImpl!M, string M) || is(C == CHSL)) { pragma(inline, true); this = cast(RGB8)c; }
39 ref auto opAssign() (in auto ref RGB8 c) { pragma(inline, true); r = c.r; g = c.g; b = c.b; return this; }
40 ref auto opAssign() (in auto ref SRGB c) { pragma(inline, true); this = cast(RGB8)c; return this; }
41 ref auto opAssign() (in auto ref CLAB c) { pragma(inline, true); this = cast(RGB8)cast(CXYZD65)c; return this; }
42 ref auto opAssign(C) (in auto ref C c) if (is(C : CXYZImpl!M, string M) || is(C == CHSL)) { pragma(inline, true); this = cast(RGB8)c; return this; }
44 SRGB opCast(T:RGB8) () const nothrow @safe @nogc { pragma(inline, true); return RGB8(r, g, b); }
45 SRGB opCast(T:SRGB) () const nothrow @safe @nogc { pragma(inline, true); return SRGB(this); }
46 CLAB opCast(T:CLAB) () const nothrow @safe @nogc { pragma(inline, true); return CLAB(CXYZD65(this)); }
47 C opCast(C) () const nothrow @safe @nogc if (is(C : CXYZImpl!M, string M) || is(C == CHSL)) { pragma(inline, true); return C(this); }
49 // CIE76
50 float distance(C) (in auto ref C c) const if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CLAB) || is(C == CHSL)) { pragma(inline, true); return (cast(CLAB)this).distance(c); }
51 float distanceSquared(C) (in auto ref C c) const if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CLAB) || is(C == CHSL)) { pragma(inline, true); return (cast(CLAB)this).distanceSquared(c); }
53 static:
54 // this is actually branch-less for ints on x86, and even for longs on x86_64
55 static ubyte clampToByte(T) (T n) pure nothrow @safe @nogc if (__traits(isIntegral, T)) {
56 static if (__VERSION__ > 2067) pragma(inline, true);
57 static if (T.sizeof == 2 || T.sizeof == 4) {
58 static if (__traits(isUnsigned, T)) {
59 return cast(ubyte)(n&0xff|(255-((-cast(int)(n < 256))>>24)));
60 } else {
61 n &= -cast(int)(n >= 0);
62 return cast(ubyte)(n|((255-cast(int)n)>>31));
64 } else static if (T.sizeof == 1) {
65 static assert(__traits(isUnsigned, T), "clampToByte: signed byte? no, really?");
66 return cast(ubyte)n;
67 } else static if (T.sizeof == 8) {
68 static if (__traits(isUnsigned, T)) {
69 return cast(ubyte)(n&0xff|(255-((-cast(long)(n < 256))>>56)));
70 } else {
71 n &= -cast(long)(n >= 0);
72 return cast(ubyte)(n|((255-cast(long)n)>>63));
74 } else {
75 static assert(false, "clampToByte: integer too big");
81 // color in sRGB space
82 struct SRGB {
83 public:
84 string toString () const nothrow @trusted {
85 import core.stdc.stdio : snprintf;
86 char[256] buf = void;
87 auto len = snprintf(buf.ptr, buf.length, "SRGB(%g,%g,%g)", cast(double)r, cast(double)g, cast(double)b);
88 return buf[0..len].idup;
91 public pure nothrow @safe @nogc:
92 float r=0, g=0, b=0; // [0..1]
94 this (in float ar, in float ag, in float ab) pure nothrow @safe @nogc { pragma(inline, true); r = ar; g = ag; b = ab; }
95 this() (in auto ref RGB8 c) pure nothrow @safe @nogc { pragma(inline, true); r = c.r/255.0; g = c.g/255.0; b = c.b/255.0; }
96 this() (in auto ref SRGB c) pure nothrow @safe @nogc { pragma(inline, true); r = c.r; g = c.g; b = c.b; }
97 this() (in auto ref CLAB c) pure nothrow @safe @nogc { pragma(inline, true); this = cast(SRGB)cast(CXYZD65)c; }
98 this(C) (in auto ref C c) pure nothrow @safe @nogc if (is(C : CXYZImpl!M, string M) || is(C == CHSL)) { pragma(inline, true); this = cast(SRGB)c; }
100 ref auto opAssign() (in auto ref RGB8 c) { pragma(inline, true); r = c.r/255.0; g = c.g/255.0; b = c.b/255.0; return this; }
101 ref auto opAssign() (in auto ref SRGB c) { pragma(inline, true); r = c.r; g = c.g; b = c.b; return this; }
102 ref auto opAssign() (in auto ref CLAB c) { pragma(inline, true); this = cast(SRGB)cast(CXYZD65)c; return this; }
103 ref auto opAssign(C) (in auto ref C c) if (is(C : CXYZImpl!M, string M) || is(C == CHSL)) { pragma(inline, true); this = cast(SRGB)c; return this; }
105 RGB8 opCast(T:RGB8) () const nothrow @safe @nogc { pragma(inline, true); return RGB8(cast(int)(r*255.0+0.5), cast(int)(g*255.0+0.5), cast(int)(b*255.0+0.5)); }
106 SRGB opCast(T:SRGB) () const nothrow @safe @nogc { pragma(inline, true); return SRGB(r, g, b); }
107 CLAB opCast(T:CLAB) () const nothrow @safe @nogc { pragma(inline, true); return CLAB(CXYZD65(this)); }
108 C opCast(C) () const nothrow @safe @nogc if (is(C : CXYZImpl!M, string M) || is(C == CHSL)) { pragma(inline, true); return C(this); }
110 // CIE76
111 float distance(C) (in auto ref C c) const if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CLAB) || is(C == CHSL)) { pragma(inline, true); return (cast(CLAB)this).distance(c); }
112 float distanceSquared(C) (in auto ref C c) const if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CLAB) || is(C == CHSL)) { pragma(inline, true); return (cast(CLAB)this).distanceSquared(c); }
116 alias CXYZD65 = CXYZImpl!"d65"; // color in CIE Standard Illuminant D65
117 alias CXYZLinear = CXYZImpl!"linear"; // color in linear space
119 // color in linear space
120 struct CXYZImpl(string mode) {
121 public:
122 static assert(mode == "d65" || mode == "linear", "invalid CXYZ mode: '"~mode~"'");
123 alias MyType = CXYZImpl!mode;
125 public:
126 string toString () const nothrow @trusted {
127 import core.stdc.stdio : snprintf;
128 char[256] buf = void;
129 auto len = snprintf(buf.ptr, buf.length, "CXYZ[%s](%g,%g,%g)", mode.ptr, cast(double)x, cast(double)y, cast(double)z);
130 return buf[0..len].idup;
133 public pure nothrow @safe @nogc:
134 float x=0, y=0, z=0; // [0..1]
136 this (in float ax, in float ay, in float az) { pragma(inline, true); x = ax; y = ay; z = az; }
137 this() (in auto ref MyType c) { pragma(inline, true); x = c.x; y = c.y; z = c.z; }
138 this() (in auto CLAB c) { pragma(inline, true); this = cast(MyType)c; }
139 this() (in auto CHSL c) { pragma(inline, true); this = cast(MyType)c; }
140 this() (in auto ref RGB8 c) {
141 static if (mode == "d65") {
142 immutable double rl = valueFromGamma(c.r/255.0);
143 immutable double gl = valueFromGamma(c.g/255.0);
144 immutable double bl = valueFromGamma(c.b/255.0);
145 // observer = 2degs, illuminant = D65
146 x = rl*0.4124+gl*0.3576+bl*0.1805;
147 y = rl*0.2126+gl*0.7152+bl*0.0722;
148 z = rl*0.0193+gl*0.1192+bl*0.9505;
149 } else {
150 x = valueFromGamma(c.r/255.0);
151 y = valueFromGamma(c.g/255.0);
152 z = valueFromGamma(c.b/255.0);
155 this() (in auto ref SRGB c) {
156 static if (mode == "d65") {
157 immutable double rl = valueFromGamma(c.r);
158 immutable double gl = valueFromGamma(c.g);
159 immutable double bl = valueFromGamma(c.b);
160 // observer = 2degs, illuminant = D65
161 x = rl*0.4124+gl*0.3576+bl*0.1805;
162 y = rl*0.2126+gl*0.7152+bl*0.0722;
163 z = rl*0.0193+gl*0.1192+bl*0.9505;
164 } else {
165 x = valueFromGamma(c.r);
166 y = valueFromGamma(c.g);
167 z = valueFromGamma(c.b);
171 ref auto opAssign() (in auto ref MyType c) { pragma(inline, true); x = c.x; y = c.y; z = c.z; return this; }
172 ref auto opAssign(C) (in auto ref C c) if (is(C == RGB8) || is(C == SRGB) || is(C == CHSL)) { pragma(inline, true); this = cast(MyType)c; return this; }
173 static if (mode == "d65") ref auto opAssign() (in auto CLAB c) { pragma(inline, true); this = cast(MyType)c; return this; }
174 static if (mode == "linear") ref auto opAssign() (in auto CLAB c) { pragma(inline, true); this = cast(MyType)cast(SRGB)c; return this; }
176 MyType opCast(T:MyType) () const { pragma(inline, true); return MyType(x, y, z); }
177 static if (mode == "d65") CLAB opCast(T:CLAB) () { pragma(inline, true); return CLAB(this); }
179 RGB8 opCast(T:RGB8) () const {
180 static if (mode == "d65") {
181 immutable double xs = x* 3.2406+y*-1.5372+z*-0.4986;
182 immutable double ys = x*-0.9689+y* 1.8758+z* 0.0415;
183 immutable double zs = x* 0.0557+y*-0.2040+z* 1.0570;
184 return RGB8(cast(int)(valueFromLinear(xs)*255.0+0.5), cast(int)(valueFromLinear(ys)*255.0+0.5), cast(int)(valueFromLinear(zs)*255.0+0.5));
185 } else {
186 return SRGB(cast(int)(valueFromLinear(x)*255.0+0.5), cast(int)(valueFromLinear(y)*255.0+0.5), cast(int)(valueFromLinear(z)*255.0+0.5));
190 SRGB opCast(T:SRGB) () const {
191 static if (mode == "d65") {
192 immutable double xs = x* 3.2406+y*-1.5372+z*-0.4986;
193 immutable double ys = x*-0.9689+y* 1.8758+z* 0.0415;
194 immutable double zs = x* 0.0557+y*-0.2040+z* 1.0570;
195 return SRGB(valueFromLinear(xs), valueFromLinear(ys), valueFromLinear(zs));
196 } else {
197 return SRGB(valueFromLinear(x), valueFromLinear(y), valueFromLinear(z));
201 MyType lighten (in float n) const { pragma(inline, true); return MyType(clamp(x+n), clamp(y+n), clamp(z+n)); }
202 MyType darken (in float n) const { pragma(inline, true); return MyType(clamp(x-n), clamp(y-n), clamp(z-n)); }
204 // CIE76
205 float distance(C) (in auto ref C c) const if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CLAB) || is(C == CHSL)) { pragma(inline, true); return (cast(CLAB)this).distance(c); }
206 float distanceSquared(C) (in auto ref C c) const if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CLAB) || is(C == CHSL)) { pragma(inline, true); return (cast(CLAB)this).distanceSquared(c); }
208 public:
209 static T clamp(T) (in T a) { pragma(inline, true); return (a < 0 ? 0 : a > 1 ? 1 : a); }
211 // gamma to linear conversion
212 // value should be in [0..1] range
213 static T valueFromGamma(T : real) (T v) {
214 import std.math : pow;
215 return (v > 0.04045 ? pow((v+0.055)/1.055, 2.4) : v/12.92);
218 // linear to gamma conversion
219 // value should be in [0..1] range
220 static T valueFromLinear(T : real) (T v) {
221 import std.math : pow;
222 return (v > 0.0031308 ? 1.055*pow(v, (1.0/2.4))-0.055 : 12.92*v);
227 // color in CIE Lab space
228 struct CLAB {
229 public:
230 string toString () const nothrow @trusted {
231 import core.stdc.stdio : snprintf;
232 char[256] buf = void;
233 auto len = snprintf(buf.ptr, buf.length, "CLAB(%g,%g,%g)", cast(double)l, cast(double)a, cast(double)b);
234 return buf[0..len].idup;
237 public pure nothrow @safe @nogc:
238 float l=0, a=0, b=0; // *NOT* [0..1]
240 this (in float al, in float aa, in float ab) { pragma(inline, true); l = al; a = aa; b = ab; }
241 this(C) (in auto ref C c) if (is(C == SRGB) || is(C == RGB8)) { pragma(inline, true); this = cast(CLAB)c; }
243 this() (in auto ref CXYZD65 c) {
244 import std.math : pow;
246 double xs = c.x/95.047;
247 double ys = c.y/100.0;
248 double zs = c.z/108.883;
250 xs = (xs > 0.008856 ? pow(xs, 1.0/3.0) : (7.787*xs)+16.0/116.0);
251 ys = (ys > 0.008856 ? pow(ys, 1.0/3.0) : (7.787*ys)+16.0/116.0);
252 zs = (zs > 0.008856 ? pow(zs, 1.0/3.0) : (7.787*zs)+16.0/116.0);
254 l = cast(float)((116.0*ys)-16.0);
255 a = cast(float)(500.0*(xs-ys));
256 b = cast(float)(200.0*(ys-zs));
259 ref auto opAssign() (in auto ref CLAB c) { pragma(inline, true); l = c.l; a = c.a; b = c.b; return this; }
260 ref auto opAssign(C) (in auto ref C c) if (is(C == RGB8) || is(C == SRGB) || is(C == CXYZD65) || is(C == CHSL)) { pragma(inline, true); this = cast(CLAB)c; return this; }
261 ref auto opAssign() (in auto ref CXYZLinear c) { pragma(inline, true); this = cast(CLAB)cast(SRGB)c; return this; }
263 CLAB opCast(T:CLAB) () const { pragma(inline, true); return CLAB(l, a, b); }
264 SRGB opCast(T:SRGB) () const { pragma(inline, true); return SRGB(this); }
265 RGB8 opCast(T:RGB8) () const { pragma(inline, true); return RGB8(this); }
266 RGB8 opCast(T:CHSL) () const { pragma(inline, true); return CHSL(this); }
267 CXYZLinear opCast(T:CXYZLinear) () const { pragma(inline, true); return CXYZLinear(cast(SRGB)this); }
269 CXYZD65 opCast(T:CXYZD65) () const {
270 immutable double ys = (l+16.0)/116.0;
271 immutable double xs = (a/500.0)+ys;
272 immutable double zs = ys-(b/200.0);
274 immutable double x3 = xs*xs*xs;
275 immutable double y3 = ys*ys*ys;
276 immutable double z3 = zs*zs*zs;
278 return CXYZD65(
279 (x3 > 0.008856 ? x3 : (xs-16.0/116.0)/7.787)*95.047,
280 (y3 > 0.008856 ? y3 : (ys-16.0/116.0)/7.787)*100.000,
281 (z3 > 0.008856 ? z3 : (zs-16.0/116.0)/7.787)*108.883,
285 // CIE76
286 float distance() (in auto ref CLAB c) const { pragma(inline, true); import std.math : sqrt; return sqrt((l-c.l)*(l-c.l)+(a-c.a)*(a-c.a)+(b-c.b)*(b-c.b)); }
287 float distance(C) (in auto ref C c) const if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CHSL)) { pragma(inline, true); return distance(cast(CLAB)c); }
288 float distanceSquared() (in auto ref CLAB c) const { pragma(inline, true); return (l-c.l)*(l-c.l)+(a-c.a)*(a-c.a)+(b-c.b)*(b-c.b); }
289 float distanceSquared(C) (in auto ref C c) const if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CHSL)) { pragma(inline, true); return distanceSquared(cast(CLAB)c); }
293 // Hue/Saturation/Lighting color
294 struct CHSL {
295 public:
296 string toString () const nothrow @trusted {
297 import core.stdc.stdio : snprintf;
298 char[256] buf = void;
299 auto len = snprintf(buf.ptr, buf.length, "CHSL(%g,%g,%g)", cast(double)h, cast(double)s, cast(double)l);
300 return buf[0..len].idup;
303 private nothrow @safe @nogc:
304 void fromColor(C) (in auto ref C c) pure {
305 static if (is(C == SRGB)) {
306 enum Weighted = true;
307 immutable double r1 = clamp(c.r);
308 immutable double g1 = clamp(c.g);
309 immutable double b1 = clamp(c.b);
310 } else static if (is(C == RGB8)) {
311 enum Weighted = true;
312 immutable double r1 = c.r/255.0;
313 immutable double g1 = c.g/255.0;
314 immutable double b1 = c.b/255.0;
315 } else static if (is(C : CXYZImpl!M, string M)) {
316 enum Weighted = false;
317 immutable double r1 = clamp(c.r);
318 immutable double g1 = clamp(c.g);
319 immutable double b1 = clamp(c.b);
320 } else {
321 immutable cc = cast(CXYZD65)c;
322 enum Weighted = false;
323 immutable double r1 = clamp(cc.r);
324 immutable double g1 = clamp(cc.g);
325 immutable double b1 = clamp(cc.b);
328 double maxColor = r1;
329 if (g1 > maxColor) maxColor = g1;
330 if (b1 > maxColor) maxColor = b1;
332 double minColor = r1;
333 if (g1 < minColor) minColor = g1;
334 if (b1 < minColor) minColor = b1;
336 static if (Weighted && false) {
337 // the colors don't affect the eye equally
338 // this is a little more accurate than plain HSL numbers
339 l = 0.2126*r1+0.7152*g1+0.0722*b1;
340 } else {
341 l = (maxColor+minColor)/2.0;
343 if (maxColor != minColor) {
344 if (l < 0.5) {
345 s = (maxColor-minColor)/(maxColor+minColor);
346 } else {
347 s = (maxColor-minColor)/(2.0-maxColor-minColor);
349 if (r1 == maxColor) {
350 h = (g1-b1)/(maxColor-minColor);
351 } else if(g1 == maxColor) {
352 h = 2.0+(b1-r1)/(maxColor-minColor);
353 } else {
354 h = 4.0+(r1-g1)/(maxColor-minColor);
358 h = h*60;
359 if (h < 0) h += 360;
360 h /= 360;
363 C toColor(C) () const {
364 static double hue (double h, double m1, double m2) pure nothrow @safe @nogc {
365 if (h < 0) h += 1;
366 if (h > 1) h -= 1;
367 if (h < 1.0/6.0) return m1+(m2-m1)*h*6.0;
368 if (h < 3.0/6.0) return m2;
369 if (h < 4.0/6.0) return m1+(m2-m1)*(2.0/3.0-h)*6.0;
370 return m1;
372 import std.math : modf;
373 real tmpi = void;
374 double sh = modf(h, tmpi);
375 if (sh < 0.0f) sh += 1.0f;
376 double ss = clamp(s);
377 double sl = clamp(l);
378 immutable double m2 = (sl <= 0.5 ? sl*(1+ss) : sl+ss-sl*ss);
379 immutable double m1 = 2*sl-m2;
381 static if (is(C == SRGB)) {
382 return SRGB(
383 clamp(hue(sh+1.0/3.0, m1, m2)),
384 clamp(hue(sh, m1, m2)),
385 clamp(hue(sh-1.0/3.0, m1, m2)),
387 } else static if (is(C == RGB8)) {
388 return RGB8(
389 cast(int)(hue(sh+1.0/3.0, m1, m2)*255.0),
390 cast(int)(hue(sh, m1, m2)*255.0),
391 cast(int)(hue(sh-1.0/3.0, m1, m2)*255.0),
393 } else static if (is(C : CXYZImpl!M, string M)) {
394 return C(
395 clamp(hue(sh+1.0/3.0, m1, m2)),
396 clamp(hue(sh, m1, m2)),
397 clamp(hue(sh-1.0/3.0, m1, m2)),
399 } else {
400 return cast(C)CXYZD65(
401 clamp(hue(sh+1.0/3.0, m1, m2)),
402 clamp(hue(sh, m1, m2)),
403 clamp(hue(sh-1.0/3.0, m1, m2)),
408 public nothrow @safe @nogc:
409 float h = 0, s = 0, l = 0; // [0..1]
411 this (in float ah, in float as, in float al) { pragma(inline, true); h = ah; s = as; l = al; }
412 this() (in auto ref CHSL c) { pragma(inline, true); h = c.h; s = c.s; l = c.l; }
413 this(C) (in auto ref C c) if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CLAB)) { pragma(inline, true); fromColor(c); }
415 ref opAssign(C:CHSL) (in auto ref C c) { pragma(inline, true); h = c.h; s = c.s; l = c.l; return this; }
416 ref opAssign(C) (in auto ref C c) if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CLAB)) { pragma(inline, true); fromColor(c); return this; }
418 CHSL opCast(C:CHSL) () const { pragma(inline, true); return CHSL(h, s, l); }
419 C opCast(C) () const if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CLAB)) { pragma(inline, true); return toColor!C; }
421 //CHSL darken (in float n) const { pragma(inline, true); return CHSL(clamp(h*n), s, l); }
423 // CIE76
424 float distance(C) (in auto ref C c) const if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CLAB)) { pragma(inline, true); return (cast(CLAB)this).distance(c); }
425 float distanceSquared(C) (in auto ref C c) const if (is(C : CXYZImpl!M, string M) || is(C == SRGB) || is(C == RGB8) || is(C == CLAB)) { pragma(inline, true); return (cast(CLAB)this).distanceSquared(c); }
427 public:
428 static T clamp(T) (in T a) { pragma(inline, true); return (a < 0 ? 0 : a > 1 ? 1 : a); }
432 version(iv_color_unittest) unittest {
433 import std.stdio;
435 auto s0 = SRGB(1, 128/255.0, 0);
436 auto l0 = cast(CXYZD65)s0;
437 auto l1 = cast(CXYZLinear)s0;
438 auto s1 = cast(SRGB)l0;
439 auto s2 = cast(SRGB)l1;
440 writeln("s0=", s0, " : ", cast(RGB8)s0);
441 writeln("l0=", l0);
442 writeln("l1=", l1);
443 writeln("s1=", s1);
444 writeln("s2=", s2);
446 writeln("black XYZ=", cast(CXYZD65)SRGB(0, 0, 0));
447 writeln("white XYZ=", cast(CXYZD65)SRGB(1, 1, 1));
448 writeln("MUST BE =CXYZ(0.9642,1,0.8249)");
449 //writeln("white XYZ=", cast(linear)sRGB(1));
451 auto lab = cast(CLAB)s0;
452 writeln("srgb->lab->srgb: ", cast(SRGB)lab, " : ", cast(RGB8)lab);
453 writeln("rgb: ", s0, " : ", cast(RGB8)s0);
454 writeln("lab: ", lab);
455 writeln("rgb: ", cast(SRGB)lab);
456 lab.l -= 1;
457 auto z1 = cast(SRGB)lab; //cast(SRGB)CLAB(lab.l-0.01, lab.a, lab.b);
458 writeln("rgbX: ", z1, " : ", cast(RGB8)z1);
459 writeln("xxx: ", cast(CLAB)cast(CXYZD65)RGB8(255-16, 128-16, 0));
463 writeln("============");
464 auto s0 = RGB8(255, 127, 0);
465 writeln("*s0: ", s0, " : ", cast(RGB8)s0);
466 auto h0 = cast(CHSL)s0;
467 writeln("*h0: ", h0);
468 h0.h *= 0.9;
469 writeln("*s1: ", h0, " : ", cast(RGB8)h0);
470 writeln(RGB8(255-25, 127-25, 0-25));
474 writeln("============");
475 auto s0 = cast(CXYZD65)RGB8(255, 127, 0);
476 writeln("*s0: ", s0, " : ", cast(RGB8)s0);
477 auto s1 = s0.darken(0.1);
478 writeln("*s1: ", s0, " : ", cast(RGB8)s0);
479 writeln(RGB8(255-25, 127-25, 0-25));