1 /**
2     Conversions, intended for internal library usage.
3   
4     Copyright: Copyright Guillaume Piolat 2023-2024.
5     License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, BSL-1.0)
6 */
7 module colors.conversions;
8 
9 import colors.types;
10 import colors.colorspace;
11 
12 // This is for internal use only.
13 
14 pure nothrow @nogc @safe:
15 
16 // Converting HSL to sRGB.
17 // Params: 
18 //    hue Hue as degrees 0..360.
19 //    sat Saturation as 0..1.0f.
20 //    light Lightness as 0..1.0f.
21 // Reference: https://www.w3.org/TR/css-color-4/#hsl-to-rgb
22 float[3] hslToRgb(float hue, float sat, float light) 
23 {
24     // blerg, TODO where to lift that limitation?
25     assert(hue >= -360 && hue <= 720.0f); 
26 
27     assert(sat >= 0.0f && sat <= 1.0f);
28     assert(light >= 0.0f && light <= 1.0f);
29 
30     //import core.stdc.stdio;
31     //debug printf("hslToRgb(%f, %f, %f)\n", hue, sat, light);
32     
33     // Convert hue to integer here.
34     // Round to single degree.
35     // FUTURE: round to 16th of degrees to support very fine hues.
36     int ihue = cast(int)(hue + 360 + 0.5f);
37     //debug printf("ihue = %d\n", ihue);
38     ihue = ihue % 360; // TODO: that doesn't sound very precise?
39     //debug printf("ihue = %d\n", ihue);
40     if (ihue < 0) ihue += 360;
41     assert(ihue >= 0 && ihue < 360);
42 
43     float ll = light;
44     if (ll > 1 - light)
45         ll = 1 - light;
46 
47     float amp = sat * ll;
48 
49     float f(int n) pure nothrow @nogc @safe
50     {
51         float k = (n + ihue / 30) % 12;
52         float D = k - 3;
53         if (D > 9-k) D = 9-k;
54         if (D > 1) D = 1;
55         if (D < -1) D = -1;
56         return light - amp * D;
57     }
58     return [f(0), f(8), f(4)];
59 }
60 
61 
62 // Reference: https://www.w3.org/TR/css-color-4/#rgb-to-hsl
63 // Note: this can return NaN as hue if there is no hue.
64 float[3] rgbToHsl (float red, float green, float blue) 
65 {
66     // TODO: how to deal with NaN here? clamp?
67     float max = (red > green) ? red : green;
68     max = (max > blue) ? max : blue;
69     float min = (red < green) ? red : green;
70     min = (min < blue) ? min : blue;
71 
72     float light = (min + max) * 0.5f;
73     float sat = 0;
74     float hue = float.nan;
75     float d = max - min;
76 
77     if (d != 0) 
78     {
79         if (light <= 0 || light >= 1)
80             sat = 0;
81         else
82         {
83             float one_m_light = 1.0f - light;
84             float mm = (light < one_m_light) ? light : one_m_light;
85             sat = d / mm;
86         }
87         if (max == red)
88         {
89             hue = (green - blue) / d + (green < blue ? 6 : 0); 
90         }
91         else if (max == green)
92         {
93             hue = (blue - red) / d + 2;
94         }
95         else
96         {
97             hue = (red - green) / d + 4;
98         }
99         hue = hue * 60;
100     }
101 
102     float[3] res = [hue, sat, light];
103     return res;
104 }
105 
106 
107 Colorspace getIntermediateColorspace(Colorspace source, 
108                                      Colorspace target)
109 {
110     with(Colorspace)
111     {
112         if (source <= rgba8 && target <= rgba8)
113             return rgba8;
114 
115         return rgbaf32;
116     }
117 }
118 
119 Color convertToIntermediate(Color c, Colorspace target) @trusted
120 {
121     Colorspace source = c.colorspace;
122     Color r = void;
123     r.assumeColorspace(target);
124 
125     switch(target) with (Colorspace)
126     {
127     case rgba8:
128         switch(source)
129         {
130             case rgba8:
131                 r._RGBA8 = c._RGBA8;
132                 break;
133 
134             default:
135                 break;
136 
137         }
138         break;
139 
140     case rgbaf32:
141         switch(source)
142         {
143             case rgba8:
144                 r._RGBAf.r = c._RGBA8.r / 255.0f;
145                 r._RGBAf.g = c._RGBA8.g / 255.0f;
146                 r._RGBAf.b = c._RGBA8.b / 255.0f;
147                 r._RGBAf.a = c._RGBA8.a / 255.0f;
148                 break;
149 
150             case rgba16:
151                 r._RGBAf.r = c._RGBA16.r / 65535.0f;
152                 r._RGBAf.g = c._RGBA16.g / 65535.0f;
153                 r._RGBAf.b = c._RGBA16.b / 65535.0f;
154                 r._RGBAf.a = c._RGBA16.a / 65535.0f;
155                 break;
156 
157             case rgbaf32:
158                 r._RGBAf = c._RGBAf;
159                 break;
160 
161             case hslaf32:
162                 // TODO: read in CSS what to do with large hue values, 
163                 // or NaN values, here.
164                 float[3] rgb = hslToRgb(c._HSLAf.h, c._HSLAf.s, c._HSLAf.l);                
165                 r._RGBAf.r = rgb[0];
166                 r._RGBAf.g = rgb[1];
167                 r._RGBAf.b = rgb[2];
168                 r._RGBAf.a = c._HSLAf.a;
169                 break;
170 
171             default:
172                 assert(false); // Not implemented or impossible
173         }
174         break;
175 
176         default:
177             assert(false); // This intermediate doesn't exist.
178     }
179     return r;
180 }
181 
182 // Internal use, convert from the intermediate format to the target format.
183 // This reduces bitdepth.
184 Color convertFromIntermediate(Color c, Colorspace target) @trusted
185 {
186     Colorspace source = c.colorspace;
187     Color r = void;
188     r.assumeColorspace(target);
189 
190     switch(source) with (Colorspace)
191     {
192         case rgba8:
193             switch(target)
194             {
195                 case rgba8:
196                     r._RGBA8 = c._RGBA8;
197                     break;
198 
199                 default:
200                     break;
201             }
202             break;
203 
204         case rgbaf32:
205 
206             // TODO: clamp and remove NaN?
207             switch(target)
208             {
209                 case rgba8:
210                     r._RGBA8.r = cast(ubyte)(0.5f+255.0f*c._RGBAf.r);
211                     r._RGBA8.g = cast(ubyte)(0.5f+255.0f*c._RGBAf.g);
212                     r._RGBA8.b = cast(ubyte)(0.5f+255.0f*c._RGBAf.b);
213                     r._RGBA8.a = cast(ubyte)(0.5f+255.0f*c._RGBAf.a);
214                     break;
215                 case rgba16:
216                     r._RGBA16.r = cast(ushort)(0.5f+65535.0f*c._RGBAf.r);
217                     r._RGBA16.g = cast(ushort)(0.5f+65535.0f*c._RGBAf.g);
218                     r._RGBA16.b = cast(ushort)(0.5f+65535.0f*c._RGBAf.b);
219                     r._RGBA16.a = cast(ushort)(0.5f+65535.0f*c._RGBAf.a);
220                     break;
221 
222                 case rgbaf32:
223                     r._RGBAf = c._RGBAf;
224                     break;
225 
226                 case hslaf32:
227 
228                     // TODO 
229                     assert(false);
230                     //break;
231 
232                 default:
233                     assert(false); // Not implemented or impossible
234             }
235             break;
236 
237         default:
238             assert(false); // This intermediate doesn't exist.
239     }
240     return r;
241 }
242 
243 void clamp_0_1(ref float x)
244 {
245     if (x < 0) x = 0;
246     if (x > 1) x = 1;
247 }
248 
249 void clamp_0_255(ref float x)
250 {
251     if (x < 0) x = 0;
252     if (x > 255) x = 255;
253 }