1 /** 2 Monomorphic color type, to represent any tristimulus + alpha. 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.types; 8 9 //import std.math; 10 import colors.conversions; 11 import colors.colorspace; 12 import colors.parser; 13 14 15 pure nothrow @nogc @safe: 16 17 18 /** 19 The Color type is a tagged union that can hold one color in a 20 CSS-defined colorspace. This correspond to both "specified", 21 "computed", and "used" colors in CSS specification. 22 In typical use, you'll want to map to sRGB 32-bit RGBA quadruplet, 23 and the function for this is called: `toRGBA8()`. 24 Reference: https://www.w3.org/TR/css-color-4/ 25 */ 26 struct Color 27 { 28 pure nothrow @nogc @safe: 29 30 // Note: accessing these representation is dangerous, only one of 31 // them is meaningful according to `colorspace`. 32 union 33 { 34 // Efficient representation go there: 35 RGBA8 _RGBA8; 36 RGBA16 _RGBA16; 37 38 // CSS spec recommends: 39 // "(16bit, half-float, or float per component is recommended 40 // for internal storage). 41 // Values must be rounded towards +∞, not truncated." 42 // CSS-compatible types below, with more precision. 43 // Note that the CSS types can contain NaN, unlike the 44 // efficient representations, and it's meaningful. 45 46 RGBAf _RGBAf; 47 HSLAf _HSLAf; 48 } 49 50 /** 51 Build from CSS color string. 52 */ 53 this(const(char)[] cssColor) 54 { 55 this = color(cssColor); 56 } 57 58 /** 59 Build from a 8-bit sRGB quadruplet. 60 */ 61 this(RGBA8 c) 62 { 63 this._RGBA8 = c; 64 this._colorspace = Colorspace.rgba8; 65 } 66 67 /** 68 Build from a 16-bit sRGB quadruplet. 69 */ 70 this(RGBA16 c) 71 { 72 this._RGBA16 = c; 73 this._colorspace = Colorspace.rgba16; 74 } 75 76 77 /** 78 Build from a 32-bit float sRGB quadruplet. 79 */ 80 this(RGBAf c) 81 { 82 this._RGBAf = c; 83 this._colorspace = Colorspace.rgbaf32; 84 } 85 86 /** 87 Get colorspace tag. This is the tag of this tagged union. 88 */ 89 Colorspace colorspace() const 90 { 91 return _colorspace; 92 } 93 94 /** 95 Unsafe cast of colorspace. Normally you never need this. 96 */ 97 void assumeColorspace(Colorspace colorspace) @system 98 { 99 _colorspace = colorspace; 100 } 101 102 /** 103 Returns: A 8-bit tristimulus sRGB color, with alpha. 104 */ 105 RGBA8 toRGBA8() const 106 { 107 Color c = this.toColorSpace(Colorspace.rgba8); 108 return c._RGBA8; 109 } 110 111 /** 112 Returns: A 16-bit tristimulus sRGB color, with alpha. 113 */ 114 RGBA16 toRGBA16() const 115 { 116 Color c = this.toColorSpace(Colorspace.rgba16); 117 return c._RGBA16; 118 } 119 120 /** 121 A 32-bit float normalized tristimulus sRGB color, with alpha. 122 */ 123 RGBAf toRGBAf() const 124 { 125 Color c = this.toColorSpace(Colorspace.rgbaf32); 126 return c._RGBAf; 127 } 128 129 private: 130 /** 131 Colorspace in the type. Which means `Color` is not meant for 132 storage, but for intermediate computation and user experience. 133 */ 134 Colorspace _colorspace; 135 } 136 unittest 137 { 138 Color c = Color("cyan"); 139 } 140 141 142 /** 143 The `rgb` function is the same as in CSS color specifications. 144 145 Expected values ranges from 0 to 255. 146 147 Instead of being either numbers or percentages, all values here 148 are assumed to be numbers from 0 to 255. However, internally the 149 color will be stored with higher accuracy, as per CSS spec. 150 151 Params: 152 red = Red value in 0 to 255.0f. 153 green = Green value in 0 to 255.0f. 154 blue = Blue value in 0 to 255.0f. 155 alpha = Alpha value in 0 to 1.0f (opacity). 156 */ 157 Color rgb(float red, float green, float blue, float alpha = 1.0f) 158 { 159 clamp_0_255(red); 160 clamp_0_255(green); 161 clamp_0_255(blue); 162 clamp_0_1(alpha); 163 Color c; 164 c._RGBAf.r = red / 255.0f; 165 c._RGBAf.g = green / 255.0f; 166 c._RGBAf.b = blue / 255.0f; 167 c._RGBAf.a = alpha; 168 c._colorspace = Colorspace.rgbaf32; 169 return c; 170 } 171 172 173 /** 174 In CSS, the `rgba` function is simply in alias of `rgb`. You can 175 specify an alpha to `rgb`, or omit it with `rgba`. 176 */ 177 alias rgba = rgb; 178 179 180 /** 181 The `hsl` function is the same as in CSS color specifications. 182 183 Expected values ranges from 0 to 255. 184 185 Params: 186 hueDegrees = Hue value (0 = red, 60 = yellow, 240 = blue). 187 = Can wrap around. 188 sat = Saturation value in 0 to 1.0f. 189 light = Light value in 0 to 1.0f. 190 alpha = Alpha value in 0 to 1.0f (opacity). 191 */ 192 Color hsl(float hueDegrees, 193 float sat, 194 float light, 195 float alpha = 1.0f) 196 { 197 // TODO: should clamp or fmod hueDegrees here? 198 clamp_0_1(sat); 199 clamp_0_1(light); 200 clamp_0_1(alpha); 201 Color c; 202 c._HSLAf.h = hueDegrees, 203 c._HSLAf.s = sat; 204 c._HSLAf.l = light; 205 c._HSLAf.a = alpha; 206 c._colorspace = Colorspace.hslaf32; 207 return c; 208 } 209 210 211 /** 212 In CSS, the `hsla` function is simply in alias of `rgb`. You can 213 specify an alpha to `rgb`, or omit it with `rgba`. 214 */ 215 alias hsla = hsl; 216 217 218 /** 219 Convert a `Color` in-place to another colorspace. 220 */ 221 Color toColorSpace(const(Color) color, Colorspace target) 222 { 223 // CSS mandates that we preserve at least 16-bit of precision. 224 225 Colorspace from = color.colorspace; 226 227 if (target == Colorspace.unknown) 228 { 229 // Not supposed to happen, every Color are convertible to any 230 // other space (though the semantics could change). 231 assert(false); 232 } 233 234 if (from == target) 235 return color; // already there 236 237 Colorspace inter = getIntermediateColorspace(from, target); 238 239 if (from == inter) 240 { 241 return convertFromIntermediate(color, target); 242 } 243 else if (target == inter) 244 { 245 return convertToIntermediate(color, target); 246 } 247 else 248 { 249 Color c = convertToIntermediate(color, inter); 250 return convertFromIntermediate(c, target); 251 } 252 } 253