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 }