├── .editorconfig ├── .gitignore ├── README.md ├── dub.json └── std └── experimental ├── color ├── colorspace.d ├── hsx.d ├── lab.d ├── package.d ├── packedrgb.d ├── rgb.d └── xyz.d └── normint.d /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | ; Standard conventions for the D programming language repository are: 4 | ; no tab characters 5 | ; indents are by 4 spaces 6 | ; line endings are LF 7 | ; no trailing whitespace 8 | ; last character in file is an LF 9 | [*] 10 | indent_style = space 11 | indent_size = 4 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | charset = utf-8 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub/* 2 | dub.selections.json 3 | __* 4 | build/* 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | std.experimental.color 2 | ====================== 3 | 4 | Development of std.exerpimental.color, intended for inclusion in the D standard library. 5 | 6 | Currently supported colorspaces: 7 | - XYZ, xyY 8 | - RGB (sRGB, gamma, linear, custom colourspace; primaries, whitepoint, compression ramp) 9 | - HSV, HSL, HSI, HCY, HWB, HCG 10 | - Lab, LCh 11 | 12 | Implements comprehensive conversion between supported colourspaces. 13 | Flexible design supports addition of user-supplied colourspaces, with full conversion and interoperability. 14 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "color", 3 | "description": "Color library for the D programming language", 4 | "homepage": "https://github.com/TurkeyMan/color", 5 | "authors": ["Manu Evans"], 6 | "copyright": "Copyright (c) 2015 Manu Evans", 7 | "license": "Boost", 8 | "importPaths": ["."], 9 | "targetType": "library", 10 | "sourcePaths": ["."], 11 | "configurations": [ 12 | { 13 | "name": "default" 14 | }, 15 | { 16 | "name": "sourceLibrary", 17 | "targetType": "sourceLibrary" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /std/experimental/color/colorspace.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | This module defines and operates on standard color spaces. 5 | 6 | Authors: Manu Evans 7 | Copyright: Copyright (c) 2016, Manu Evans. 8 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 | Source: $(PHOBOSSRC std/experimental/color/_colorspace.d) 10 | */ 11 | module std.experimental.color.colorspace; 12 | 13 | import std.experimental.color; 14 | import std.experimental.color.xyz; 15 | 16 | import std.traits : isFloatingPoint; 17 | 18 | version(unittest) 19 | import std.math : abs; 20 | 21 | @safe pure nothrow @nogc: 22 | 23 | import std.range : iota; 24 | import std.algorithm : reduce; 25 | 26 | 27 | /** White points of $(LINK2 https://en.wikipedia.org/wiki/Standard_illuminant, standard illuminants). */ 28 | template WhitePoint(F) if (isFloatingPoint!F) 29 | { 30 | /** */ 31 | enum WhitePoint 32 | { 33 | /** Incandescent / Tungsten */ 34 | A = xyY!F(0.44757, 0.40745, 1.00000), 35 | /** [obsolete] Direct sunlight at noon */ 36 | B = xyY!F(0.34842, 0.35161, 1.00000), 37 | /** [obsolete] Average / North sky Daylight */ 38 | C = xyY!F(0.31006, 0.31616, 1.00000), 39 | /** Horizon Light, ICC profile PCS (Profile connection space) */ 40 | D50 = xyY!F(0.34567, 0.35850, 1.00000), 41 | /** Mid-morning / Mid-afternoon Daylight */ 42 | D55 = xyY!F(0.33242, 0.34743, 1.00000), 43 | /** Noon Daylight: Television, sRGB color space */ 44 | D65 = xyY!F(0.31271, 0.32902, 1.00000), 45 | /** North sky Daylight */ 46 | D75 = xyY!F(0.29902, 0.31485, 1.00000), 47 | /** Used by Japanese NTSC */ 48 | D93 = xyY!F(0.28486, 0.29322, 1.00000), 49 | /** Equal energy */ 50 | E = xyY!F(1.0/3.0, 1.0/3.0, 1.00000), 51 | /** Daylight Fluorescent */ 52 | F1 = xyY!F(0.31310, 0.33727, 1.00000), 53 | /** Cool White Fluorescent */ 54 | F2 = xyY!F(0.37208, 0.37529, 1.00000), 55 | /** White Fluorescent */ 56 | F3 = xyY!F(0.40910, 0.39430, 1.00000), 57 | /** Warm White Fluorescent */ 58 | F4 = xyY!F(0.44018, 0.40329, 1.00000), 59 | /** Daylight Fluorescent */ 60 | F5 = xyY!F(0.31379, 0.34531, 1.00000), 61 | /** Lite White Fluorescent */ 62 | F6 = xyY!F(0.37790, 0.38835, 1.00000), 63 | /** D65 simulator, Daylight simulator */ 64 | F7 = xyY!F(0.31292, 0.32933, 1.00000), 65 | /** D50 simulator, Sylvania F40 Design 50 */ 66 | F8 = xyY!F(0.34588, 0.35875, 1.00000), 67 | /** Cool White Deluxe Fluorescent */ 68 | F9 = xyY!F(0.37417, 0.37281, 1.00000), 69 | /** Philips TL85, Ultralume 50 */ 70 | F10 = xyY!F(0.34609, 0.35986, 1.00000), 71 | /** Philips TL84, Ultralume 40 */ 72 | F11 = xyY!F(0.38052, 0.37713, 1.00000), 73 | /** Philips TL83, Ultralume 30 */ 74 | F12 = xyY!F(0.43695, 0.40441, 1.00000), 75 | /** DCI-P3 digital cinema projector */ 76 | DCI = xyY!F(0.31400, 0.35100, 1.00000) 77 | } 78 | } 79 | 80 | 81 | /** 82 | Enum of common RGB color spaces. 83 | */ 84 | enum RGBColorSpace 85 | { 86 | /** sRGB */ 87 | sRGB, 88 | /** sRGB approximation using gamma 2.2 */ 89 | sRGB_Gamma2_2, 90 | 91 | /** NTSC Colorimetry (1953) */ 92 | Colorimetry, 93 | /** NTSC SMPTE/C (1987) (ITU-R BT.601) */ 94 | NTSC, 95 | /** Japanese NTSC (1987) (ITU-R BT.601) */ 96 | NTSC_J, 97 | /** PAL/SECAM (ITU-R BT.601) */ 98 | PAL_SECAM, 99 | /** HDTV (ITU-R BT.709) */ 100 | HDTV, 101 | /** UHDTV (ITU-R BT.2020) */ 102 | UHDTV, 103 | 104 | /** Adobe RGB */ 105 | AdobeRGB, 106 | /** Wide Gamut RGB */ 107 | WideGamutRGB, 108 | /** Apple RGB */ 109 | AppleRGB, 110 | /** ProPhoto */ 111 | ProPhoto, 112 | /** CIE RGB */ 113 | CIERGB, 114 | /** Best RGB */ 115 | BestRGB, 116 | /** Beta RGB */ 117 | BetaRGB, 118 | /** Bruce RGB */ 119 | BruceRGB, 120 | /** Color Match RGB */ 121 | ColorMatchRGB, 122 | /** DonRGB 4 */ 123 | DonRGB4, 124 | /** Ekta Space PS5 */ 125 | EktaSpacePS5, 126 | 127 | /** DCI-P3 Theater */ 128 | DCI_P3_Theater, 129 | /** DCI-P3 D65 */ 130 | DCI_P3_D65, 131 | /** DCI-P3 Apple */ 132 | DCI_P3_Apple, 133 | } 134 | 135 | 136 | /** 137 | Chromatic adaptation method. 138 | */ 139 | enum ChromaticAdaptationMethod 140 | { 141 | /** Direct method, no correction for cone response. */ 142 | XYZ, 143 | /** Bradford method. Considered by most experts to be the best. */ 144 | Bradford, 145 | /** Von Kries method. */ 146 | VonKries 147 | } 148 | 149 | 150 | /** 151 | Parameters that define an RGB color space.$(BR) 152 | $(D_INLINECODE F) is the float type that should be used for the colors and gamma functions. 153 | */ 154 | struct RGBColorSpaceDesc(F) if (isFloatingPoint!F) 155 | { 156 | /** Gamma conversion function type. */ 157 | alias GammaFunc = F function(F v) pure nothrow @nogc @safe; 158 | 159 | /** Color space name. */ 160 | string name; 161 | 162 | /** Function that converts a linear luminance to gamma space. */ 163 | GammaFunc toGamma; 164 | /** Function that converts a gamma luminance to linear space. */ 165 | GammaFunc toLinear; 166 | 167 | /** White point. */ 168 | xyY!F white; 169 | /** Red point. */ 170 | xyY!F red; 171 | /** Green point. */ 172 | xyY!F green; 173 | /** Blue point. */ 174 | xyY!F blue; 175 | } 176 | 177 | /** 178 | Color space descriptor for the specified color space. 179 | */ 180 | RGBColorSpaceDesc!F rgbColorSpaceDef(F = double)(RGBColorSpace colorSpace) if (isFloatingPoint!F) 181 | { 182 | return rgbColorSpaceDefs!F[colorSpace]; 183 | } 184 | 185 | /** 186 | RGB to XYZ color space transformation matrix.$(BR) 187 | $(D_INLINECODE cs) describes the source RGB color space. 188 | */ 189 | F[3][3] rgbToXyzMatrix(F = double)(RGBColorSpaceDesc!F cs) if (isFloatingPoint!F) 190 | { 191 | static XYZ!F toXYZ(xyY!F c) { return c.y == F(0) ? XYZ!F() : XYZ!F(c.x/c.y, F(1), (F(1)-c.x-c.y)/c.y); } 192 | 193 | // build a matrix from the 3 color vectors 194 | auto r = toXYZ(cs.red); 195 | auto g = toXYZ(cs.green); 196 | auto b = toXYZ(cs.blue); 197 | F[3][3] m = [[ r.X, g.X, b.X], 198 | [ r.Y, g.Y, b.Y], 199 | [ r.Z, g.Z, b.Z]]; 200 | 201 | // multiply by the whitepoint 202 | F[3] w = [ toXYZ(cs.white).tupleof ]; 203 | auto s = multiply(inverse(m), w); 204 | 205 | // return colorspace matrix (RGB -> XYZ) 206 | return [[ r.X*s[0], g.X*s[1], b.X*s[2] ], 207 | [ r.Y*s[0], g.Y*s[1], b.Y*s[2] ], 208 | [ r.Z*s[0], g.Z*s[1], b.Z*s[2] ]]; 209 | } 210 | 211 | /** 212 | XYZ to RGB color space transformation matrix.$(BR) 213 | $(D_INLINECODE cs) describes the target RGB color space. 214 | */ 215 | F[3][3] xyzToRgbMatrix(F = double)(RGBColorSpaceDesc!F cs) if (isFloatingPoint!F) 216 | { 217 | return inverse(rgbToXyzMatrix(cs)); 218 | } 219 | 220 | /** 221 | Generate a chromatic adaptation matrix from $(D_INLINECODE srcWhite) to $(D_INLINECODE destWhite). 222 | 223 | Chromatic adaptation is the process of transforming colors relative to a particular white point to some other white point. 224 | Information about chromatic adaptation can be found at $(LINK2 https://en.wikipedia.org/wiki/Chromatic_adaptation, wikipedia). 225 | */ 226 | F[3][3] chromaticAdaptationMatrix(ChromaticAdaptationMethod method = ChromaticAdaptationMethod.Bradford, F = double)(xyY!F srcWhite, xyY!F destWhite) if (isFloatingPoint!F) 227 | { 228 | enum Ma = chromaticAdaptationMatrices!F[method]; 229 | enum iMa = inverse!F(Ma); 230 | auto XYZs = convertColor!(XYZ!F)(srcWhite); 231 | auto XYZd = convertColor!(XYZ!F)(destWhite); 232 | F[3] Ws = [ XYZs.X, XYZs.Y, XYZs.Z ]; 233 | F[3] Wd = [ XYZd.X, XYZd.Y, XYZd.Z ]; 234 | auto s = multiply!F(Ma, Ws); 235 | auto d = multiply!F(Ma, Wd); 236 | F[3][3] t = [[d[0]/s[0], F(0), F(0) ], 237 | [F(0), d[1]/s[1], F(0) ], 238 | [F(0), F(0), d[2]/s[2]]]; 239 | return multiply!F(multiply!F(iMa, t), Ma); 240 | } 241 | 242 | /** Linear to hybrid linear-gamma transfer function. The function and parameters are detailed in the example below. */ 243 | T linearToHybridGamma(double a, double b, double s, double e, T)(T v) if (isFloatingPoint!T) 244 | { 245 | if (v <= T(b)) 246 | return v*T(s); 247 | else 248 | return T(a)*v^^T(e) - T(a - 1); 249 | } 250 | /// 251 | unittest 252 | { 253 | // sRGB parameters 254 | enum a = 1.055; 255 | enum b = 0.0031308; 256 | enum s = 12.92; 257 | enum e = 1/2.4; 258 | 259 | double v = 0.5; 260 | 261 | // the gamma function 262 | if (v <= b) 263 | v = v*s; 264 | else 265 | v = a*v^^e - (a - 1); 266 | 267 | assert(abs(v - linearToHybridGamma!(a, b, s, e)(0.5)) < double.epsilon); 268 | } 269 | 270 | /** Hybrid linear-gamma to linear transfer function. The function and parameters are detailed in the example below. */ 271 | T hybridGammaToLinear(double a, double b, double s, double e, T)(T v) if (isFloatingPoint!T) 272 | { 273 | if (v <= T(b*s)) 274 | return v * T(1/s); 275 | else 276 | return ((v + T(a - 1)) * T(1/a))^^T(e); 277 | } 278 | /// 279 | unittest 280 | { 281 | // sRGB parameters 282 | enum a = 1.055; 283 | enum b = 0.0031308; 284 | enum s = 12.92; 285 | enum e = 2.4; 286 | 287 | double v = 0.5; 288 | 289 | // the gamma function 290 | if (v <= b*s) 291 | v = v/s; 292 | else 293 | v = ((v + (a - 1)) / a)^^e; 294 | 295 | assert(abs(v - hybridGammaToLinear!(a, b, s, e)(0.5)) < double.epsilon); 296 | } 297 | 298 | /** Linear to sRGB transfer function. */ 299 | alias linearTosRGB(F) = linearToHybridGamma!(1.055, 0.0031308, 12.92, 1/2.4, F); 300 | /** sRGB to linear transfer function. */ 301 | alias sRGBToLinear(F) = hybridGammaToLinear!(1.055, 0.0031308, 12.92, 2.4, F); 302 | 303 | /** Linear to Rec.601 transfer function. Note, Rec.709 also uses this same function.*/ 304 | alias linearToRec601(F) = linearToHybridGamma!(1.099, 0.018, 4.5, 0.45, F); 305 | /** Rec.601 to linear transfer function. Note, Rec.709 also uses this same function. */ 306 | alias rec601ToLinear(F) = hybridGammaToLinear!(1.099, 0.018, 4.5, 1/0.45, F); 307 | /** Linear to Rec.2020 transfer function. */ 308 | alias linearToRec2020(F) = linearToHybridGamma!(1.09929682680944, 0.018053968510807, 4.5, 0.45, F); 309 | /** Rec.2020 to linear transfer function. */ 310 | alias rec2020ToLinear(F) = hybridGammaToLinear!(1.09929682680944, 0.018053968510807, 4.5, 1/0.45, F); 311 | 312 | /** Linear to gamma transfer function. */ 313 | T linearToGamma(double gamma, T)(T v) if (isFloatingPoint!T) 314 | { 315 | return v^^T(1.0/gamma); 316 | } 317 | /** Linear to gamma transfer function. */ 318 | T linearToGamma(T)(T v, T gamma) if (isFloatingPoint!T) 319 | { 320 | return v^^T(1.0/gamma); 321 | } 322 | 323 | /** Gamma to linear transfer function. */ 324 | T gammaToLinear(double gamma, T)(T v) if (isFloatingPoint!T) 325 | { 326 | return v^^T(gamma); 327 | } 328 | /** Gamma to linear transfer function. */ 329 | T gammaToLinear(T)(T v, T gamma) if (isFloatingPoint!T) 330 | { 331 | return v^^T(gamma); 332 | } 333 | 334 | 335 | package: 336 | 337 | __gshared immutable RGBColorSpaceDesc!F[RGBColorSpace.max + 1] rgbColorSpaceDefs(F) = [ 338 | RGBColorSpaceDesc!F("sRGB", &linearTosRGB!F, &sRGBToLinear!F, WhitePoint!F.D65, xyY!F(0.6400, 0.3300, 0.212656), xyY!F(0.3000, 0.6000, 0.715158), xyY!F(0.1500, 0.0600, 0.072186)), 339 | RGBColorSpaceDesc!F("sRGB simple", &linearToGamma!(2.2, F), &gammaToLinear!(2.2, F), WhitePoint!F.D65, xyY!F(0.6400, 0.3300, 0.212656), xyY!F(0.3000, 0.6000, 0.715158), xyY!F(0.1500, 0.0600, 0.072186)), 340 | 341 | RGBColorSpaceDesc!F("Colorimetry", &linearToRec601!F, &rec601ToLinear!F, WhitePoint!F.C, xyY!F(0.6700, 0.3300, 0.299000), xyY!F(0.2100, 0.7100, 0.587000), xyY!F(0.1400, 0.0800, 0.114000)), 342 | RGBColorSpaceDesc!F("NTSC", &linearToRec601!F, &rec601ToLinear!F, WhitePoint!F.D65, xyY!F(0.6300, 0.3400, 0.299000), xyY!F(0.3100, 0.5950, 0.587000), xyY!F(0.1550, 0.0700, 0.114000)), 343 | RGBColorSpaceDesc!F("NTSC-J", &linearToRec601!F, &rec601ToLinear!F, WhitePoint!F.D93, xyY!F(0.6300, 0.3400, 0.299000), xyY!F(0.3100, 0.5950, 0.587000), xyY!F(0.1550, 0.0700, 0.114000)), 344 | RGBColorSpaceDesc!F("PAL/SECAM", &linearToRec601!F, &rec601ToLinear!F, WhitePoint!F.D65, xyY!F(0.6400, 0.3300, 0.299000), xyY!F(0.2900, 0.6000, 0.587000), xyY!F(0.1500, 0.0600, 0.114000)), 345 | RGBColorSpaceDesc!F("HDTV", &linearToRec601!F, &rec601ToLinear!F, WhitePoint!F.D65, xyY!F(0.6400, 0.3300, 0.212600), xyY!F(0.3000, 0.6000, 0.715200), xyY!F(0.1500, 0.0600, 0.072200)), 346 | RGBColorSpaceDesc!F("UHDTV", &linearToRec2020!F, &rec2020ToLinear!F, WhitePoint!F.D65, xyY!F(0.7080, 0.2920, 0.262700), xyY!F(0.1700, 0.7970, 0.678000), xyY!F(0.1310, 0.0460, 0.059300)), 347 | 348 | RGBColorSpaceDesc!F("Adobe RGB", &linearToGamma!(2.2, F), &gammaToLinear!(2.2, F), WhitePoint!F.D65, xyY!F(0.6400, 0.3300, 0.297361), xyY!F(0.2100, 0.7100, 0.627355), xyY!F(0.1500, 0.0600, 0.075285)), 349 | RGBColorSpaceDesc!F("Wide Gamut RGB", &linearToGamma!(2.2, F), &gammaToLinear!(2.2, F), WhitePoint!F.D50, xyY!F(0.7350, 0.2650, 0.258187), xyY!F(0.1150, 0.8260, 0.724938), xyY!F(0.1570, 0.0180, 0.016875)), 350 | RGBColorSpaceDesc!F("Apple RGB", &linearToGamma!(1.8, F), &gammaToLinear!(1.8, F), WhitePoint!F.D65, xyY!F(0.6250, 0.3400, 0.244634), xyY!F(0.2800, 0.5950, 0.672034), xyY!F(0.1550, 0.0700, 0.083332)), 351 | RGBColorSpaceDesc!F("ProPhoto", &linearToGamma!(1.8, F), &gammaToLinear!(1.8, F), WhitePoint!F.D50, xyY!F(0.7347, 0.2653, 0.288040), xyY!F(0.1596, 0.8404, 0.711874), xyY!F(0.0366, 0.0001, 0.000086)), 352 | RGBColorSpaceDesc!F("CIE RGB", &linearToGamma!(2.2, F), &gammaToLinear!(2.2, F), WhitePoint!F.E, xyY!F(0.7350, 0.2650, 0.176204), xyY!F(0.2740, 0.7170, 0.812985), xyY!F(0.1670, 0.0090, 0.010811)), 353 | // RGBColorSpaceDesc!F("CIE RGB", &linearToGamma!(2.2, F), &gammaToLinear!(2.2, F), WhitePoint!F.E, xyY!F(0.7347, 0.2653), xyY!F(0.2738, 0.7174), xyY!F(0.1666, 0.0089)), // another source shows slightly different primaries 354 | RGBColorSpaceDesc!F("Best RGB", &linearToGamma!(2.2, F), &gammaToLinear!(2.2, F), WhitePoint!F.D50, xyY!F(0.7347, 0.2653, 0.228457), xyY!F(0.2150, 0.7750, 0.737352), xyY!F(0.1300, 0.0350, 0.034191)), 355 | RGBColorSpaceDesc!F("Beta RGB", &linearToGamma!(2.2, F), &gammaToLinear!(2.2, F), WhitePoint!F.D50, xyY!F(0.6888, 0.3112, 0.303273), xyY!F(0.1986, 0.7551, 0.663786), xyY!F(0.1265, 0.0352, 0.032941)), 356 | RGBColorSpaceDesc!F("Bruce RGB", &linearToGamma!(2.2, F), &gammaToLinear!(2.2, F), WhitePoint!F.D65, xyY!F(0.6400, 0.3300, 0.240995), xyY!F(0.2800, 0.6500, 0.683554), xyY!F(0.1500, 0.0600, 0.075452)), 357 | RGBColorSpaceDesc!F("Color Match RGB", &linearToGamma!(1.8, F), &gammaToLinear!(1.8, F), WhitePoint!F.D50, xyY!F(0.6300, 0.3400, 0.274884), xyY!F(0.2950, 0.6050, 0.658132), xyY!F(0.1500, 0.0750, 0.066985)), 358 | RGBColorSpaceDesc!F("DonRGB 4", &linearToGamma!(2.2, F), &gammaToLinear!(2.2, F), WhitePoint!F.D50, xyY!F(0.6960, 0.3000, 0.278350), xyY!F(0.2150, 0.7650, 0.687970), xyY!F(0.1300, 0.0350, 0.033680)), 359 | RGBColorSpaceDesc!F("Ekta Space PS5", &linearToGamma!(2.2, F), &gammaToLinear!(2.2, F), WhitePoint!F.D50, xyY!F(0.6950, 0.3050, 0.260629), xyY!F(0.2600, 0.7000, 0.734946), xyY!F(0.1100, 0.0050, 0.004425)), 360 | 361 | RGBColorSpaceDesc!F("DCI-P3 Theater", &linearToGamma!(2.6, F), &gammaToLinear!(2.6, F), WhitePoint!F.DCI, xyY!F(0.6800, 0.3200, 0.228975), xyY!F(0.2650, 0.6900, 0.691739), xyY!F(0.1500, 0.0600, 0.079287)), 362 | RGBColorSpaceDesc!F("DCI-P3 D65", &linearToGamma!(2.6, F), &gammaToLinear!(2.6, F), WhitePoint!F.D65, xyY!F(0.6800, 0.3200, 0.228973), xyY!F(0.2650, 0.6900, 0.691752), xyY!F(0.1500, 0.0600, 0.079275)), 363 | RGBColorSpaceDesc!F("DCI-P3 Apple", &linearTosRGB!F, &sRGBToLinear!F, WhitePoint!F.D65, xyY!F(0.6800, 0.3200, 0.228973), xyY!F(0.2650, 0.6900, 0.691752), xyY!F(0.1500, 0.0600, 0.079275)), 364 | ]; 365 | 366 | __gshared immutable F[3][3][ChromaticAdaptationMethod.max + 1] chromaticAdaptationMatrices(F) = [ 367 | // XYZ (identity) matrix 368 | [[ F(1), F(0), F(0) ], 369 | [ F(0), F(1), F(0) ], 370 | [ F(0), F(0), F(1) ]], 371 | // Bradford matrix 372 | [[ F( 0.8951000), F( 0.2664000), F(-0.1614000) ], 373 | [ F(-0.7502000), F( 1.7135000), F( 0.0367000) ], 374 | [ F( 0.0389000), F(-0.0685000), F( 1.0296000) ]], 375 | // Von Kries matrix 376 | [[ F( 0.4002400), F( 0.7076000), F(-0.0808100) ], 377 | [ F(-0.2263000), F( 1.1653200), F( 0.0457000) ], 378 | [ F( 0.0000000), F( 0.0000000), F( 0.9182200) ]] 379 | ]; 380 | 381 | // 3d linear algebra functions (this would ideally live somewhere else...) 382 | F[3] multiply(F)(F[3][3] m1, F[3] v) 383 | { 384 | return [ m1[0][0]*v[0] + m1[0][1]*v[1] + m1[0][2]*v[2], 385 | m1[1][0]*v[0] + m1[1][1]*v[1] + m1[1][2]*v[2], 386 | m1[2][0]*v[0] + m1[2][1]*v[1] + m1[2][2]*v[2] ]; 387 | } 388 | 389 | F[3][3] multiply(F)(F[3][3] m1, F[3][3] m2) 390 | { 391 | return [[ m1[0][0]*m2[0][0] + m1[0][1]*m2[1][0] + m1[0][2]*m2[2][0], 392 | m1[0][0]*m2[0][1] + m1[0][1]*m2[1][1] + m1[0][2]*m2[2][1], 393 | m1[0][0]*m2[0][2] + m1[0][1]*m2[1][2] + m1[0][2]*m2[2][2] ], 394 | [ m1[1][0]*m2[0][0] + m1[1][1]*m2[1][0] + m1[1][2]*m2[2][0], 395 | m1[1][0]*m2[0][1] + m1[1][1]*m2[1][1] + m1[1][2]*m2[2][1], 396 | m1[1][0]*m2[0][2] + m1[1][1]*m2[1][2] + m1[1][2]*m2[2][2] ], 397 | [ m1[2][0]*m2[0][0] + m1[2][1]*m2[1][0] + m1[2][2]*m2[2][0], 398 | m1[2][0]*m2[0][1] + m1[2][1]*m2[1][1] + m1[2][2]*m2[2][1], 399 | m1[2][0]*m2[0][2] + m1[2][1]*m2[1][2] + m1[2][2]*m2[2][2] ]]; 400 | } 401 | 402 | F[3][3] transpose(F)(F[3][3] m) 403 | { 404 | return [[ m[0][0], m[1][0], m[2][0] ], 405 | [ m[0][1], m[1][1], m[2][1] ], 406 | [ m[0][2], m[1][2], m[2][2] ]]; 407 | } 408 | 409 | F determinant(F)(F[3][3] m) 410 | { 411 | return m[0][0] * (m[1][1]*m[2][2] - m[2][1]*m[1][2]) - 412 | m[0][1] * (m[1][0]*m[2][2] - m[1][2]*m[2][0]) + 413 | m[0][2] * (m[1][0]*m[2][1] - m[1][1]*m[2][0]); 414 | } 415 | 416 | F[3][3] inverse(F)(F[3][3] m) 417 | { 418 | F det = determinant(m); 419 | assert(det != 0, "Matrix is not invertible!"); 420 | 421 | F invDet = F(1)/det; 422 | return [[ (m[1][1]*m[2][2] - m[2][1]*m[1][2]) * invDet, 423 | (m[0][2]*m[2][1] - m[0][1]*m[2][2]) * invDet, 424 | (m[0][1]*m[1][2] - m[0][2]*m[1][1]) * invDet ], 425 | [ (m[1][2]*m[2][0] - m[1][0]*m[2][2]) * invDet, 426 | (m[0][0]*m[2][2] - m[0][2]*m[2][0]) * invDet, 427 | (m[1][0]*m[0][2] - m[0][0]*m[1][2]) * invDet ], 428 | [ (m[1][0]*m[2][1] - m[2][0]*m[1][1]) * invDet, 429 | (m[2][0]*m[0][1] - m[0][0]*m[2][1]) * invDet, 430 | (m[0][0]*m[1][1] - m[1][0]*m[0][1]) * invDet ]]; 431 | } 432 | -------------------------------------------------------------------------------- /std/experimental/color/hsx.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | This module implements $(LINK2 https://en.wikipedia.org/wiki/HSL_and_HSV, HSV), 5 | $(LINK2 https://en.wikipedia.org/wiki/HSL_and_HSV, HSL), 6 | $(LINK2 https://en.wikipedia.org/wiki/HSL_and_HSV, HSI), 7 | $(LINK2 https://en.wikipedia.org/wiki/HSL_and_HSV, HCY), 8 | $(LINK2 https://en.wikipedia.org/wiki/HWB_color_model, HWB), 9 | $(LINK2 https://www.npmjs.com/package/hcg-color, HCG) _color types. 10 | 11 | This family of _color spaces represent various cylindrical mappings of the RGB color space. 12 | 13 | Authors: Manu Evans 14 | Copyright: Copyright (c) 2015, Manu Evans. 15 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0) 16 | Source: $(PHOBOSSRC std/experimental/color/_hsx.d) 17 | */ 18 | module std.experimental.color.hsx; 19 | 20 | import std.experimental.color; 21 | import std.experimental.color.rgb; 22 | import std.experimental.color.colorspace : RGBColorSpace, RGBColorSpaceDesc, rgbColorSpaceDef; 23 | import std.experimental.normint; 24 | 25 | import std.traits : isInstanceOf, isFloatingPoint, isUnsigned, Unqual; 26 | import std.typetuple : TypeTuple; 27 | import std.math : PI; 28 | 29 | @safe pure nothrow @nogc: 30 | 31 | /** 32 | Detect whether $(D_INLINECODE T) is a member of the HSx color family. 33 | */ 34 | enum isHSx(T) = isInstanceOf!(HSx, T); 35 | 36 | /// 37 | unittest 38 | { 39 | static assert(isHSx!(HSV!ushort) == true); 40 | static assert(isHSx!RGB8 == false); 41 | static assert(isHSx!string == false); 42 | } 43 | 44 | /** 45 | Alias for a HSV (HSB) color. 46 | */ 47 | alias HSV(CT = float, RGBColorSpace cs = RGBColorSpace.sRGB) = HSx!(HSxType.HSV, CT, cs); 48 | 49 | /** 50 | Alias for a HSL color. 51 | */ 52 | alias HSL(CT = float, RGBColorSpace cs = RGBColorSpace.sRGB) = HSx!(HSxType.HSL, CT, cs); 53 | 54 | /** 55 | Alias for a HSI color. 56 | */ 57 | alias HSI(CT = float, RGBColorSpace cs = RGBColorSpace.sRGB) = HSx!(HSxType.HSI, CT, cs); 58 | 59 | /** 60 | Alias for a HCY' color. 61 | */ 62 | alias HCY(CT = float, RGBColorSpace cs = RGBColorSpace.sRGB) = HSx!(HSxType.HCY, CT, cs); 63 | 64 | /** 65 | Alias for a HWB color. 66 | */ 67 | alias HWB(CT = float, RGBColorSpace cs = RGBColorSpace.sRGB) = HSx!(HSxType.HWB, CT, cs); 68 | 69 | /** 70 | Alias for a HCG color. 71 | */ 72 | alias HCG(CT = float, RGBColorSpace cs = RGBColorSpace.sRGB) = HSx!(HSxType.HCG, CT, cs); 73 | 74 | /** 75 | Define a HSx family color type. 76 | */ 77 | enum HSxType 78 | { 79 | /** Hue-saturation-value (aka HSB: Hue-saturation-brightness) */ 80 | HSV, 81 | /** Hue-saturation-lightness */ 82 | HSL, 83 | /** Hue-saturation-intensity */ 84 | HSI, 85 | /** Hue-chroma-luma */ 86 | HCY, 87 | /** Hue-white-black */ 88 | HWB, 89 | /** Hue-chroma-grey */ 90 | HCG 91 | } 92 | 93 | /** 94 | HSx color space is used to describe a suite of angular color spaces including HSL, HSV, HSI, HCY. 95 | 96 | Params: type_ = A type from the HSxType enum. 97 | ComponentType_ = Type for the color channels. May be unsigned integer or floating point type. 98 | colorSpace_ = Color will be within the specified RGB color space. 99 | */ 100 | struct HSx(HSxType type_, ComponentType_ = float, RGBColorSpace colorSpace_ = RGBColorSpace.sRGB) if (isFloatingPoint!ComponentType_ || isUnsigned!ComponentType_) 101 | { 102 | @safe pure nothrow @nogc: 103 | 104 | static if (isFloatingPoint!ComponentType_) 105 | { 106 | /** Type of the hue components. */ 107 | alias HueType = ComponentType_; 108 | /** Type of the s and x components. */ 109 | alias ComponentType = ComponentType_; 110 | } 111 | else 112 | { 113 | /** Type of the hue components. */ 114 | alias HueType = ComponentType_; 115 | /** Type of the s and x components. */ 116 | alias ComponentType = NormalizedInt!ComponentType_; 117 | } 118 | 119 | /** The parent RGB color space. */ 120 | enum colorSpace = colorSpace_; 121 | /** The parent RGB color space descriptor. */ 122 | enum RGBColorSpaceDesc!F colorSpaceDesc(F = double) = rgbColorSpaceDef!F(colorSpace_); 123 | /** The color type from the HSx family. */ 124 | enum HSxType type = type_; 125 | 126 | // mixin the color channels according to the type 127 | mixin("HueType " ~ Components!type[0] ~ " = 0;"); 128 | mixin("ComponentType " ~ Components!type[1] ~ " = 0;"); 129 | mixin("ComponentType " ~ Components!type[2] ~ " = 0;"); 130 | 131 | /** Get hue angle in degrees. */ 132 | @property double degrees() const 133 | { 134 | static if (!isFloatingPoint!ComponentType_) 135 | return h * (360/(ComponentType_.max + 1.0)); 136 | else 137 | return (h < 0 ? 1 + h%1 : h%1) * 360; 138 | } 139 | /** Set hue angle in degrees. */ 140 | @property void degrees(double angle) 141 | { 142 | static if (!isFloatingPoint!ComponentType_) 143 | h = cast(ComponentType_)(angle * ((ComponentType_.max + 1.0)/360)); 144 | else 145 | h = angle * 1.0/360; 146 | } 147 | 148 | /** Get hue angle in radians. */ 149 | @property double radians() const 150 | { 151 | static if (!isFloatingPoint!ComponentType_) 152 | return h * ((PI*2)/(ComponentType_.max + 1.0)); 153 | else 154 | return (h < 0 ? 1 + h%1 : h%1) * (PI*2); 155 | } 156 | /** Set hue angle in radians. */ 157 | @property void radians(double angle) 158 | { 159 | static if (!isFloatingPoint!ComponentType_) 160 | h = cast(ComponentType_)(angle * ((ComponentType_.max + 1.0)/(PI*2))); 161 | else 162 | h = angle * 1.0/(PI*2); 163 | } 164 | 165 | /** Construct a color from hsx components. */ 166 | this(HueType h, ComponentType s, ComponentType x) 167 | { 168 | mixin("this." ~ Components!type[0] ~ " = h;"); 169 | mixin("this." ~ Components!type[1] ~ " = s;"); 170 | mixin("this." ~ Components!type[2] ~ " = x;"); 171 | } 172 | 173 | static if (!isFloatingPoint!ComponentType_) 174 | { 175 | /** Construct a color from hsx components. */ 176 | this(HueType h, ComponentType.IntType s, ComponentType.IntType x) 177 | { 178 | mixin("this." ~ Components!type[0] ~ " = h;"); 179 | mixin("this." ~ Components!type[1] ~ " = ComponentType(s);"); 180 | mixin("this." ~ Components!type[2] ~ " = ComponentType(x);"); 181 | } 182 | } 183 | 184 | /** 185 | Cast to other color types. 186 | 187 | This cast is a convenience which simply forwards the call to convertColor. 188 | */ 189 | Color opCast(Color)() const if (isColor!Color) 190 | { 191 | return convertColor!Color(this); 192 | } 193 | 194 | 195 | package: 196 | 197 | alias ParentColor = RGB!("rgb", ComponentType_, false, colorSpace_); 198 | 199 | static To convertColorImpl(To, From)(From color) if (isHSx!From && isHSx!To) 200 | { 201 | // HACK: cast through RGB (this works fine, but could be faster) 202 | return convertColorImpl!(To)(convertColorImpl!(From.ParentColor)(color)); 203 | } 204 | unittest 205 | { 206 | static assert(convertColorImpl!(HSL!float)(HSV!float(1.0/6, 1, 1)) == HSL!float(1.0/6, 1, 0.5)); 207 | 208 | static assert(convertColorImpl!(HSV!float)(HSL!float(1.0/6, 1, 0.5)) == HSV!float(1.0/6, 1, 1)); 209 | 210 | static assert(convertColorImpl!(HSI!float)(HSV!float(0, 1, 1)) == HSI!float(0, 1, 1.0/3)); 211 | static assert(convertColorImpl!(HSI!float)(HSV!float(1.0/6, 1, 1)) == HSI!float(1.0/6, 1, 2.0/3)); 212 | 213 | // TODO: HCY (needs approx ==) 214 | } 215 | 216 | static To convertColorImpl(To, From)(From color) if (isHSx!From && isRGB!To) 217 | { 218 | import std.math : abs; 219 | 220 | alias ToType = To.ComponentType; 221 | alias WT = FloatTypeFor!ToType; 222 | 223 | auto c = color.tupleof; 224 | WT h = cast(WT)color.degrees; 225 | WT s = cast(WT)c[1]; 226 | WT x = cast(WT)c[2]; 227 | 228 | static if (isFloatingPoint!ComponentType_) 229 | { 230 | // clamp s and x 231 | import std.algorithm.comparison : clamp; 232 | s = clamp(s, 0, 1); 233 | x = clamp(x, 0, 1); 234 | } 235 | 236 | WT C, m; 237 | static if (From.type == HSxType.HSV) 238 | { 239 | C = x*s; 240 | m = x - C; 241 | } 242 | else static if (From.type == HSxType.HSL) 243 | { 244 | C = (1 - abs(2*x - 1))*s; 245 | m = x - C/2; 246 | } 247 | else static if (From.type == HSxType.HSI) 248 | { 249 | C = s; 250 | } 251 | else static if (From.type == HSxType.HCY) 252 | { 253 | C = s; 254 | } 255 | else static if (From.type == HSxType.HWB) 256 | { 257 | WT t = s + x; 258 | if (t > 1) 259 | { 260 | // normalise W/B 261 | s /= t; 262 | x /= t; 263 | } 264 | s = x == 1 ? 0 : 1 - (s / (1 - x)); // saturation 265 | x = 1 - x; // 'value' 266 | 267 | C = x*s; 268 | m = x - C; 269 | } 270 | else static if (From.type == HSxType.HCG) 271 | { 272 | C = s; 273 | m = x * (1 - C); 274 | } 275 | 276 | WT H = h/60; 277 | WT X = C*(1 - abs(H%2.0 - 1)); 278 | 279 | WT r, g, b; 280 | if (H < 1) 281 | r = C, g = X, b = 0; 282 | else if (H < 2) 283 | r = X, g = C, b = 0; 284 | else if (H < 3) 285 | r = 0, g = C, b = X; 286 | else if (H < 4) 287 | r = 0, g = X, b = C; 288 | else if (H < 5) 289 | r = X, g = 0, b = C; 290 | else if (H < 6) 291 | r = C, g = 0, b = X; 292 | 293 | static if (From.type == HSxType.HSI) 294 | { 295 | m = x - (r+g+b)*WT(1.0/3.0); 296 | } 297 | else static if (From.type == HSxType.HCY) 298 | { 299 | m = x - toGrayscale!(false, colorSpace_, WT)(r, g, b); // Derive from Luma' 300 | } 301 | 302 | return To(cast(ToType)(r+m), cast(ToType)(g+m), cast(ToType)(b+m)); 303 | } 304 | unittest 305 | { 306 | static assert(convertColorImpl!(RGB8)(HSV!float(0, 1, 1)) == RGB8(255, 0, 0)); 307 | static assert(convertColorImpl!(RGB8)(HSV!float(1.0/6, 0.5, 0.5)) == RGB8(128, 128, 64)); 308 | 309 | static assert(convertColorImpl!(RGB8)(HSL!float(0, 1, 0.5)) == RGB8(255, 0, 0)); 310 | static assert(convertColorImpl!(RGB8)(HSL!float(1.0/6, 0.5, 0.5)) == RGB8(191, 191, 64)); 311 | } 312 | 313 | static To convertColorImpl(To, From)(From color) if (isRGB!From && isHSx!To) 314 | { 315 | import std.algorithm : min, max, clamp; 316 | import std.math : abs; 317 | 318 | alias ToType = To.ComponentType; 319 | alias WT = FloatTypeFor!ToType; 320 | 321 | auto c = color.tristimulus; 322 | WT r = cast(WT)c[0]; 323 | WT g = cast(WT)c[1]; 324 | WT b = cast(WT)c[2]; 325 | 326 | static if (isFloatingPoint!ComponentType_) 327 | { 328 | // clamp r, g, b 329 | r = clamp(r, 0, 1); 330 | g = clamp(g, 0, 1); 331 | b = clamp(b, 0, 1); 332 | } 333 | 334 | WT M = max(r, g, b); 335 | WT m = min(r, g, b); 336 | WT C = M-m; 337 | 338 | // Calculate Hue 339 | WT h; 340 | if (C == 0) 341 | h = 0; 342 | else if (M == r) 343 | h = WT(1.0/6) * ((g-b)/C % WT(6)); 344 | else if (M == g) 345 | h = WT(1.0/6) * ((b-r)/C + WT(2)); 346 | else if (M == b) 347 | h = WT(1.0/6) * ((r-g)/C + WT(4)); 348 | 349 | WT s, x; 350 | static if (To.type == HSxType.HSV) 351 | { 352 | x = M; // 'Value' 353 | s = x == 0 ? WT(0) : C/x; // Saturation 354 | } 355 | else static if (To.type == HSxType.HSL) 356 | { 357 | x = (M + m)/WT(2); // Lightness 358 | s = (x == 0 || x == 1) ? WT(0) : C/(1 - abs(2*x - 1)); // Saturation 359 | } 360 | else static if (To.type == HSxType.HSI) 361 | { 362 | x = (r + g + b)/WT(3); // Intensity 363 | s = x == 0 ? WT(0) : 1 - m/x; // Saturation 364 | } 365 | else static if (To.type == HSxType.HCY) 366 | { 367 | x = toGrayscale!(false, colorSpace_, WT)(r, g, b); // Calculate Luma' using the proper coefficients 368 | s = C; // Chroma 369 | } 370 | else static if (To.type == HSxType.HWB) 371 | { 372 | s = M == 0 ? WT(0) : C/M; // Saturation 373 | s = (1 - s)*M; // White 374 | x = 1 - M; // Black 375 | } 376 | else static if (To.type == HSxType.HCG) 377 | { 378 | s = C; 379 | x = m / (1 - C); 380 | } 381 | 382 | static if (!isFloatingPoint!ToType) 383 | h = h * WT(ToType.max + 1.0); 384 | 385 | return To(cast(ToType)h, cast(ToType)s, cast(ToType)x); 386 | } 387 | unittest 388 | { 389 | static assert(convertColorImpl!(HSV!float)(RGB8(255, 0, 0)) == HSV!float(0, 1, 1)); 390 | static assert(convertColorImpl!(HSL!float)(RGB8(255, 0, 0)) == HSL!float(0, 1, 0.5)); 391 | static assert(convertColorImpl!(HSI!float)(RGB8(255, 0, 0)) == HSI!float(0, 1, 1.0/3)); 392 | static assert(convertColorImpl!(HSI!float)(RGB8(255, 255, 0)) == HSI!float(1.0/6, 1, 2.0/3)); 393 | // static assert(convertColorImpl!(HCY!float)(RGB8(255, 0, 0)) == HCY!float(0, 1, 1)); 394 | } 395 | 396 | private: 397 | template Components(HSxType type) 398 | { 399 | static if (type == HSxType.HSV) 400 | alias Components = TypeTuple!("h","s","v"); 401 | else static if (type == HSxType.HSL) 402 | alias Components = TypeTuple!("h","s","l"); 403 | else static if (type == HSxType.HSI) 404 | alias Components = TypeTuple!("h","s","i"); 405 | else static if (type == HSxType.HCY) 406 | alias Components = TypeTuple!("h","c","y"); 407 | else static if (type == HSxType.HWB) 408 | alias Components = TypeTuple!("h","w","b"); 409 | else static if (type == HSxType.HCG) 410 | alias Components = TypeTuple!("h","c","g"); 411 | } 412 | alias AllComponents = Components!type_; 413 | } 414 | 415 | /// 416 | unittest 417 | { 418 | // HSV color with float components 419 | alias HSVf = HSV!float; 420 | 421 | HSVf c = HSVf(3.1415, 1, 0.5); 422 | 423 | // test HSV operators and functions 424 | } 425 | /// 426 | unittest 427 | { 428 | // HSL color with float components 429 | alias HSLf = HSL!float; 430 | 431 | HSLf c = HSLf(3.1415, 1, 0.5); 432 | 433 | // test HSL operators and functions 434 | } 435 | /// 436 | unittest 437 | { 438 | // HSI color with float components 439 | alias HSIf = HSI!float; 440 | 441 | HSIf c = HSIf(3.1415, 1, 0.5); 442 | 443 | // test HSI operators and functions 444 | } 445 | /// 446 | unittest 447 | { 448 | // HCY color with float components 449 | alias HCYf = HCY!float; 450 | 451 | HCYf c = HCYf(3.1415, 1, 0.5); 452 | 453 | // test HCY operators and functions 454 | } 455 | -------------------------------------------------------------------------------- /std/experimental/color/lab.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | This module implements the $(LINK2 https://en.wikipedia.org/wiki/Lab_color_space, CIE Lab) and 5 | $(LINK2 https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:__CIELCh_or_CIEHLC, LCh) _color types. 6 | 7 | Lab is full-spectrum, absolute, vector _color space, with the specific goal of human perceptual uniformity. 8 | 9 | Authors: Manu Evans 10 | Copyright: Copyright (c) 2015, Manu Evans. 11 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0) 12 | Source: $(PHOBOSSRC std/experimental/color/_lab.d) 13 | */ 14 | module std.experimental.color.lab; 15 | 16 | import std.experimental.color; 17 | import std.experimental.color.xyz : XYZ, isXYZ; 18 | import std.experimental.color.colorspace : WhitePoint; 19 | 20 | import std.traits: isFloatingPoint, Unqual; 21 | import std.typetuple: TypeTuple; 22 | import std.math : sin, cos, sqrt, atan2, PI, M_1_PI; 23 | 24 | 25 | @safe: pure: nothrow: @nogc: 26 | 27 | /** 28 | Detect whether $(D_INLINECODE T) is a L*a*b* color. 29 | */ 30 | enum isLab(T) = isInstanceOf!(Lab, T); 31 | 32 | /** 33 | Detect whether $(D_INLINECODE T) is an L*C*h° color. 34 | */ 35 | enum isLCh(T) = isInstanceOf!(LCh, T); 36 | 37 | 38 | /** 39 | A CIE L*a*b* color, parameterised for component type and white point. 40 | 41 | Lab is a color space that describes all colors visible to the human eye and was created to serve as a device-independent model to be used as a reference. 42 | L* represents the lightness of the color; L* = 0 yields black and L* = 100 indicates diffuse white, specular white may be higher. 43 | a* represents the position between red/magenta and green; negative values indicate green while positive values indicate magenta. 44 | b* represents the position between yellow and blue; negative values indicate blue and positive values indicate yellow. 45 | 46 | Lab is often found using default white point D50, but it is also common to use D65 when interacting with sRGB images. 47 | */ 48 | struct Lab(F = float, alias whitePoint_ = (WhitePoint!F.D50)) if (isFloatingPoint!F) 49 | { 50 | @safe: pure: nothrow: @nogc: 51 | 52 | /** Type of the color components. */ 53 | alias ComponentType = F; 54 | 55 | /** The color components that were specified. */ 56 | enum whitePoint = whitePoint_; 57 | 58 | /** L* (lightness) component. */ 59 | F L = 0; 60 | /** a* component. Negative values indicate green, positive values indicate magenta. */ 61 | F a = 0; 62 | /** b* component. Negative values indicate blue, positive values indicate yellow. */ 63 | F b = 0; 64 | 65 | /** Construct a color from L*a*b* values. */ 66 | this(F L, F a, F b) 67 | { 68 | this.L = L; 69 | this.a = a; 70 | this.b = b; 71 | } 72 | 73 | /** Returns the perceptual distance between the specifies colors. */ 74 | F perceptualDistance(G)(Lab!G c) const 75 | { 76 | alias WT = WorkingType!(F, G); 77 | return sqrt((WT(c.L) - WT(L))^^2 + (WT(c.a) - WT(a))^^2 + (WT(c.b) - WT(b))^^2); 78 | } 79 | 80 | /** 81 | Cast to other color types. 82 | 83 | This cast is a convenience which simply forwards the call to convertColor. 84 | */ 85 | Color opCast(Color)() const if (isColor!Color) 86 | { 87 | return convertColor!Color(this); 88 | } 89 | 90 | /** Unary operators. */ 91 | typeof(this) opUnary(string op)() const if (op == "+" || op == "-" || (op == "~" && is(ComponentType == NormalizedInt!U, U))) 92 | { 93 | Unqual!(typeof(this)) res = this; 94 | foreach (c; AllComponents) 95 | mixin(ComponentExpression!("res._ = #_;", c, op)); 96 | return res; 97 | } 98 | /** Binary operators. */ 99 | typeof(this) opBinary(string op)(typeof(this) rh) const if (op == "+" || op == "-" || op == "*") 100 | { 101 | Unqual!(typeof(this)) res = this; 102 | foreach (c; AllComponents) 103 | mixin(ComponentExpression!("res._ #= rh._;", c, op)); 104 | return res; 105 | } 106 | /** Binary operators. */ 107 | typeof(this) opBinary(string op, S)(S rh) const if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^")) 108 | { 109 | Unqual!(typeof(this)) res = this; 110 | foreach (c; AllComponents) 111 | mixin(ComponentExpression!("res._ #= rh;", c, op)); 112 | return res; 113 | } 114 | /** Binary assignment operators. */ 115 | ref typeof(this) opOpAssign(string op)(typeof(this) rh) if (op == "+" || op == "-" || op == "*") 116 | { 117 | foreach (c; AllComponents) 118 | mixin(ComponentExpression!("_ #= rh._;", c, op)); 119 | return this; 120 | } 121 | /** Binary assignment operators. */ 122 | ref typeof(this) opOpAssign(string op, S)(S rh) if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^")) 123 | { 124 | foreach (c; AllComponents) 125 | mixin(ComponentExpression!("_ #= rh;", c, op)); 126 | return this; 127 | } 128 | 129 | package: 130 | 131 | alias ParentColor = XYZ!ComponentType; 132 | 133 | static To convertColorImpl(To, From)(From color) if (isLab!From && isLab!To) 134 | { 135 | static if (From.whitePoint == To.whitePoint) 136 | { 137 | // same whitepoint, just a format conversion 138 | return To(To.ComponentType(L), To.ComponentType(a), To.ComponentType(b)); 139 | } 140 | else 141 | { 142 | // we'll need to pipe through XYZ to adjust the whitepoint 143 | auto xyz = cast(XYZ!(To.ComponentType))this; 144 | return cast(To)xyz; 145 | } 146 | } 147 | 148 | static To convertColorImpl(To, From)(From color) if (isLab!From && isXYZ!To) 149 | { 150 | alias WT = WorkingType!(From, To); 151 | 152 | enum w = cast(XYZ!WT)whitePoint; 153 | 154 | static WT f(WT v) 155 | { 156 | if (v > WT(0.206893)) 157 | return v^^WT(3); 158 | else 159 | return (v - WT(16.0/116))*WT(1/7.787); 160 | } 161 | 162 | WT Y = (color.L + 16)*WT(1.0/116); 163 | WT X = color.a*WT(1.0/500) + Y; 164 | WT Z = -color.b*WT(1.0/200) + Y; 165 | 166 | X = w.X * f(X); 167 | Y = w.Y * f(Y); 168 | Z = w.Z * f(Z); 169 | 170 | return To(X, Y, Z); 171 | } 172 | 173 | static To convertColorImpl(To, From)(From color) if (isXYZ!From && isLab!To) 174 | { 175 | alias WT = WorkingType!(From, To); 176 | 177 | enum w = cast(XYZ!WT)whitePoint; 178 | 179 | static WT f(WT v) 180 | { 181 | if (v > WT(0.008856)) 182 | return v^^WT(1.0/3); 183 | else 184 | return WT(7.787)*v + WT(16.0/116); 185 | } 186 | 187 | WT X = f(color.X / w.X); 188 | WT Y = f(color.Y / w.Y); 189 | WT Z = f(color.Z / w.Z); 190 | 191 | return To(116*Y - 16, 500*(X - Y), 200*(Y - Z)); 192 | } 193 | 194 | private: 195 | alias AllComponents = TypeTuple!("L", "a", "b"); 196 | } 197 | 198 | 199 | /** 200 | A CIE L*C*h° color, parameterised for component type and white point. 201 | The LCh color space is a Lab cube color space, where instead of cartesian coordinates a*, b*, the cylindrical coordinates C* (chroma) and h° (hue angle) are specified. The CIELab lightness L* remains unchanged. 202 | */ 203 | struct LCh(F = float, alias whitePoint_ = (WhitePoint!F.D50)) if (isFloatingPoint!F) 204 | { 205 | @safe: pure: nothrow: @nogc: 206 | 207 | /** Type of the color components. */ 208 | alias ComponentType = F; 209 | 210 | /** The color components that were specified. */ 211 | enum whitePoint = whitePoint_; 212 | 213 | /** L* (lightness) component. */ 214 | F L = 0; 215 | /** C* (chroma) component. */ 216 | F C = 0; 217 | /** h° (hue) component, in degrees. */ 218 | F h = 0; 219 | 220 | /** Get hue angle in radians. */ 221 | @property F radians() const 222 | { 223 | return h * F((1.0/180)*PI); 224 | } 225 | /** Set hue angle in radians. */ 226 | @property void radians(F angle) 227 | { 228 | h = angle * F(M_1_PI*180); 229 | } 230 | 231 | /** 232 | Cast to other color types. 233 | 234 | This cast is a convenience which simply forwards the call to convertColor. 235 | */ 236 | Color opCast(Color)() const if (isColor!Color) 237 | { 238 | return convertColor!Color(this); 239 | } 240 | 241 | 242 | package: 243 | 244 | alias ParentColor = Lab!(F, whitePoint_); 245 | 246 | static To convertColorImpl(To, From)(From color) if (isLCh!From && isLCh!To) 247 | { 248 | static if (From.whitePoint == To.whitePoint) 249 | { 250 | // same whitepoint, just a format conversion 251 | return To(To.ComponentType(L), To.ComponentType(C), To.ComponentType(h)); 252 | } 253 | else 254 | { 255 | // we'll need to pipe through XYZ to adjust the whitepoint 256 | auto xyz = cast(XYZ!(To.ComponentType))this; 257 | return cast(To)xyz; 258 | } 259 | } 260 | 261 | static To convertColorImpl(To, From)(From color) if (isLCh!From && isLab!To) 262 | { 263 | alias WT = WorkingType!(From, To); 264 | 265 | WT a = cos(color.h*WT(1.0/180*PI)) * color.C; 266 | WT b = sin(color.h*WT(1.0/180*PI)) * color.C; 267 | 268 | return To(color.L, a, b); 269 | } 270 | 271 | static To convertColorImpl(To, From)(From color) if (isLab!From && isLCh!To) 272 | { 273 | alias WT = WorkingType!(From, To); 274 | 275 | WT C = sqrt(color.a^^2 + color.b^^2); 276 | WT h = atan2(color.b, color.a); 277 | if (h >= 0) 278 | h = h*WT(M_1_PI*180); 279 | else 280 | h = 360 + h*WT(M_1_PI*180); 281 | 282 | return To(color.L, C, h); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /std/experimental/color/package.d: -------------------------------------------------------------------------------- 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), /// 178 | antiqueWhite = RGB8(250,235,215), /// 179 | aqua = RGB8(0,255,255), /// 180 | aquamarine = RGB8(127,255,212), /// 181 | azure = RGB8(240,255,255), /// 182 | beige = RGB8(245,245,220), /// 183 | bisque = RGB8(255,228,196), /// 184 | black = RGB8(0,0,0), /// 185 | blanchedAlmond = RGB8(255,235,205), /// 186 | blue = RGB8(0,0,255), /// 187 | blueViolet = RGB8(138,43,226), /// 188 | brown = RGB8(165,42,42), /// 189 | burlyWood = RGB8(222,184,135), /// 190 | cadetBlue = RGB8(95,158,160), /// 191 | chartreuse = RGB8(127,255,0), /// 192 | chocolate = RGB8(210,105,30), /// 193 | coral = RGB8(255,127,80), /// 194 | cornflowerBlue = RGB8(100,149,237), /// 195 | cornsilk = RGB8(255,248,220), /// 196 | crimson = RGB8(220,20,60), /// 197 | cyan = RGB8(0,255,255), /// 198 | darkBlue = RGB8(0,0,139), /// 199 | darkCyan = RGB8(0,139,139), /// 200 | darkGoldenrod = RGB8(184,134,11), /// 201 | darkGray = RGB8(169,169,169), /// 202 | darkGrey = RGB8(169,169,169), /// 203 | darkGreen = RGB8(0,100,0), /// 204 | darkKhaki = RGB8(189,183,107), /// 205 | darkMagenta = RGB8(139,0,139), /// 206 | darkOliveGreen = RGB8(85,107,47), /// 207 | darkOrange = RGB8(255,140,0), /// 208 | darkOrchid = RGB8(153,50,204), /// 209 | darkRed = RGB8(139,0,0), /// 210 | darkSalmon = RGB8(233,150,122), /// 211 | darkSeaGreen = RGB8(143,188,143), /// 212 | darkSlateBlue = RGB8(72,61,139), /// 213 | darkSlateGray = RGB8(47,79,79), /// 214 | darkSlateGrey = RGB8(47,79,79), /// 215 | darkTurquoise = RGB8(0,206,209), /// 216 | darkViolet = RGB8(148,0,211), /// 217 | deepPink = RGB8(255,20,147), /// 218 | deepSkyBlue = RGB8(0,191,255), /// 219 | dimGray = RGB8(105,105,105), /// 220 | dimGrey = RGB8(105,105,105), /// 221 | dodgerBlue = RGB8(30,144,255), /// 222 | fireBrick = RGB8(178,34,34), /// 223 | floralWhite = RGB8(255,250,240), /// 224 | forestGreen = RGB8(34,139,34), /// 225 | fuchsia = RGB8(255,0,255), /// 226 | gainsboro = RGB8(220,220,220), /// 227 | ghostWhite = RGB8(248,248,255), /// 228 | gold = RGB8(255,215,0), /// 229 | goldenrod = RGB8(218,165,32), /// 230 | gray = RGB8(128,128,128), /// 231 | grey = RGB8(128,128,128), /// 232 | green = RGB8(0,128,0), /// 233 | greenYellow = RGB8(173,255,47), /// 234 | honeydew = RGB8(240,255,240), /// 235 | hotPink = RGB8(255,105,180), /// 236 | indianRed = RGB8(205,92,92), /// 237 | indigo = RGB8(75,0,130), /// 238 | ivory = RGB8(255,255,240), /// 239 | khaki = RGB8(240,230,140), /// 240 | lavender = RGB8(230,230,250), /// 241 | lavenderBlush = RGB8(255,240,245), /// 242 | lawnGreen = RGB8(124,252,0), /// 243 | lemonChiffon = RGB8(255,250,205), /// 244 | lightBlue = RGB8(173,216,230), /// 245 | lightCoral = RGB8(240,128,128), /// 246 | lightCyan = RGB8(224,255,255), /// 247 | lightGoldenrodYellow = RGB8(250,250,210), /// 248 | lightGray = RGB8(211,211,211), /// 249 | lightGrey = RGB8(211,211,211), /// 250 | lightGreen = RGB8(144,238,144), /// 251 | lightPink = RGB8(255,182,193), /// 252 | lightSalmon = RGB8(255,160,122), /// 253 | lightSeaGreen = RGB8(32,178,170), /// 254 | lightSkyBlue = RGB8(135,206,250), /// 255 | lightSlateGray = RGB8(119,136,153), /// 256 | lightSlateGrey = RGB8(119,136,153), /// 257 | lightSteelBlue = RGB8(176,196,222), /// 258 | lightYellow = RGB8(255,255,224), /// 259 | lime = RGB8(0,255,0), /// 260 | limeGreen = RGB8(50,205,50), /// 261 | linen = RGB8(250,240,230), /// 262 | magenta = RGB8(255,0,255), /// 263 | maroon = RGB8(128,0,0), /// 264 | mediumAquamarine = RGB8(102,205,170), /// 265 | mediumBlue = RGB8(0,0,205), /// 266 | mediumOrchid = RGB8(186,85,211), /// 267 | mediumPurple = RGB8(147,112,219), /// 268 | mediumSeaGreen = RGB8(60,179,113), /// 269 | mediumSlateBlue = RGB8(123,104,238), /// 270 | mediumSpringGreen = RGB8(0,250,154), /// 271 | mediumTurquoise = RGB8(72,209,204), /// 272 | mediumVioletRed = RGB8(199,21,133), /// 273 | midnightBlue = RGB8(25,25,112), /// 274 | mintCream = RGB8(245,255,250), /// 275 | mistyRose = RGB8(255,228,225), /// 276 | moccasin = RGB8(255,228,181), /// 277 | navajoWhite = RGB8(255,222,173), /// 278 | navy = RGB8(0,0,128), /// 279 | oldLace = RGB8(253,245,230), /// 280 | olive = RGB8(128,128,0), /// 281 | oliveDrab = RGB8(107,142,35), /// 282 | orange = RGB8(255,165,0), /// 283 | orangeRed = RGB8(255,69,0), /// 284 | orchid = RGB8(218,112,214), /// 285 | paleGoldenrod = RGB8(238,232,170), /// 286 | paleGreen = RGB8(152,251,152), /// 287 | paleTurquoise = RGB8(175,238,238), /// 288 | paleVioletRed = RGB8(219,112,147), /// 289 | papayaWhip = RGB8(255,239,213), /// 290 | peachPuff = RGB8(255,218,185), /// 291 | peru = RGB8(205,133,63), /// 292 | pink = RGB8(255,192,203), /// 293 | plum = RGB8(221,160,221), /// 294 | powderBlue = RGB8(176,224,230), /// 295 | purple = RGB8(128,0,128), /// 296 | red = RGB8(255,0,0), /// 297 | rosyBrown = RGB8(188,143,143), /// 298 | royalBlue = RGB8(65,105,225), /// 299 | saddleBrown = RGB8(139,69,19), /// 300 | salmon = RGB8(250,128,114), /// 301 | sandyBrown = RGB8(244,164,96), /// 302 | seaGreen = RGB8(46,139,87), /// 303 | seashell = RGB8(255,245,238), /// 304 | sienna = RGB8(160,82,45), /// 305 | silver = RGB8(192,192,192), /// 306 | skyBlue = RGB8(135,206,235), /// 307 | slateBlue = RGB8(106,90,205), /// 308 | slateGray = RGB8(112,128,144), /// 309 | slateGrey = RGB8(112,128,144), /// 310 | snow = RGB8(255,250,250), /// 311 | springGreen = RGB8(0,255,127), /// 312 | steelBlue = RGB8(70,130,180), /// 313 | tan = RGB8(210,180,140), /// 314 | teal = RGB8(0,128,128), /// 315 | thistle = RGB8(216,191,216), /// 316 | tomato = RGB8(255,99,71), /// 317 | turquoise = RGB8(64,224,208), /// 318 | violet = RGB8(238,130,238), /// 319 | wheat = RGB8(245,222,179), /// 320 | white = RGB8(255,255,255), /// 321 | whiteSmoke = RGB8(245,245,245), /// 322 | yellow = RGB8(255,255,0), /// 323 | yellowGreen = RGB8(154,205,50) /// 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 | } 641 | -------------------------------------------------------------------------------- /std/experimental/color/packedrgb.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | This module implements the packed RGB _color type. 5 | 6 | It is common in computer graphics to perform compression of image data to save 7 | runtime memory. There is a very wide variety of common compressed image formats. 8 | This module aims to support all of them! 9 | 10 | Authors: Manu Evans 11 | Copyright: Copyright (c) 2015, Manu Evans. 12 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0) 13 | Source: $(PHOBOSSRC std/experimental/color/_packedrgb.d) 14 | */ 15 | module std.experimental.color.packedrgb; 16 | 17 | import std.experimental.color; 18 | import std.experimental.color.rgb; 19 | import std.experimental.color.colorspace : RGBColorSpace; 20 | import std.experimental.normint; 21 | 22 | import std.traits : isNumeric, isFloatingPoint, isSigned, isUnsigned, Unsigned; 23 | 24 | @safe pure nothrow: 25 | 26 | 27 | /** 28 | Detect whether $(D_INLINECODE T) is a packed RGB color. 29 | */ 30 | enum isPackedRGB(T) = isInstanceOf!(PackedRGB, T); 31 | 32 | /// 33 | unittest 34 | { 35 | static assert(isPackedRGB!(PackedRGB!("rgb_5_6_5", ubyte)) == true); 36 | static assert(isPackedRGB!(PackedRGB!("rgba_s10_s10_s10_u2", short)) == true); 37 | static assert(isPackedRGB!(PackedRGB!("rg_f16_f16", float)) == true); 38 | static assert(isPackedRGB!(PackedRGB!("rgb_f11_f11_f10", float)) == true); 39 | static assert(isPackedRGB!(PackedRGB!("rgb_9_9_9_e5", float)) == true); 40 | static assert(isPackedRGB!(PackedRGB!("rgb_f10_s4_u2", float)) == true); 41 | static assert(isPackedRGB!int == false); 42 | } 43 | 44 | 45 | /** Component info struct. */ 46 | struct ComponentInfo 47 | { 48 | /** Type of the component. */ 49 | enum ComponentType : ubyte 50 | { 51 | /** Component is unsigned normalized integer. */ 52 | Unsigned, 53 | /** Component is signed normalized integer. */ 54 | Signed, 55 | /** Component is floating point. Floats with less than 16 bits precision are unsigned. */ 56 | Float, 57 | /** Component is floating point mantissa only. */ 58 | Mantissa, 59 | /** Component is floating point exponent only. */ 60 | Exponent, 61 | } 62 | 63 | /** First bit, starting from bit 0 (LSB). */ 64 | ubyte offset; 65 | /** Number of bits. */ 66 | ubyte bits; 67 | /** Component type. */ 68 | ComponentType type; 69 | } 70 | 71 | /** Buffer used for bit-packing. */ 72 | struct Buffer(size_t N) 73 | { 74 | @safe pure nothrow @nogc: 75 | 76 | private 77 | { 78 | static if (N >= 8 && (N & 7) == 0) 79 | ulong[N/8] data; 80 | else static if (N >= 4 && (N & 3) == 0) 81 | uint[N/4] data; 82 | else static if (N >= 2 && (N & 1) == 0) 83 | ushort[N/2] data; 84 | else 85 | ubyte[N] data; 86 | } 87 | 88 | /** Read bits from the buffer. */ 89 | @property uint bits(size_t Offset, size_t Bits)() const 90 | { 91 | enum Index = Offset / ElementWidth; 92 | enum ElementOffset = Offset % ElementWidth; 93 | static assert(Offset+Bits <= data.sizeof*8, "Bits are outside of data range"); 94 | static assert(Index == (Offset+Bits-1) / ElementWidth, "Bits may not straddle element boundaries"); 95 | return (data[Index] >> ElementOffset) & ((1UL << Bits)-1); 96 | } 97 | 98 | /** Write bits to the buffer. */ 99 | @property void bits(size_t Offset, size_t Bits)(uint value) 100 | { 101 | enum Index = Offset / ElementWidth; 102 | enum ElementOffset = Offset % ElementWidth; 103 | static assert(Offset+Bits <= data.sizeof*8, "Bits are outside of data range"); 104 | static assert(Index == (Offset+Bits-1) / ElementWidth, "Bits may not straddle element boundaries"); 105 | data[Index] |= (value & ((1UL << Bits)-1)) << ElementOffset; 106 | } 107 | 108 | /** Element width for multi-element buffers. */ 109 | enum ElementWidth = data[0].sizeof*8; 110 | } 111 | 112 | /** 113 | A packed RGB color, parameterised with format, unpacked component type, and color space specification. 114 | 115 | Params: format_ = Format of the packed color.$(BR) 116 | Format shall be arranged for instance: "rgba_10_10_10_2" for 10 bits each RGB, and 2 bits alpha, starting from the least significant bit.$(BR) 117 | Formats may specify packed floats: "rgba_f16_f16_f16_f16" for an RGBA half-precision float color.$(BR) 118 | Low-precision floats are supported: "rgb_f11_f11_f10" for an RGB partial-precision floating point format. Floats with less than 16 bits always have a 5 bit exponent, and no sign bit.$(BR) 119 | Formats may specify a shared exponent: "rgb_9_9_9_e5" for 9 mantissa bits each RGB, and a 5 bit shared exponent.$(BR) 120 | Formats may specify the signed-ness for integer components: "rgb_s5_s5_s5_u1" for 5 bit signed RGB, and a 1 bit unsigned alpha. The 'u' is optional, default is assumed to be unsigned.$(BR) 121 | Formats may contain a combination of the color channels r, g, b, l, a, x, in any order. Color channels l, and r, g, b are mutually exclusive, and may not appear together in the same color. 122 | ComponentType_ = Type for the unpacked color channels. May be a basic integer or floating point type. 123 | colorSpace_ = Color will be within the specified color space. 124 | */ 125 | struct PackedRGB(string format_, ComponentType_, RGBColorSpace colorSpace_ = RGBColorSpace.sRGB) 126 | if (isNumeric!ComponentType_) 127 | { 128 | @safe pure nothrow @nogc: 129 | 130 | // RGB colors may only contain components 'rgb', or 'l' (luminance) 131 | // They may also optionally contain an 'a' (alpha) component, and 'x' (unused) components 132 | static assert(allIn!("rgblax", components), "Invalid Color component '"d ~ notIn!("rgblax", components) ~ "'. RGB colors may only contain components: r, g, b, l, a, x"d); 133 | static assert(anyIn!("rgbal", components), "RGB colors must contain at least one component of r, g, b, l, a."); 134 | static assert(!canFind!(components, 'l') || !anyIn!("rgb", components), "RGB colors may not contain rgb AND luminance components together."); 135 | 136 | /** The unpacked color type. */ 137 | alias UnpackedColor = RGB!(components, ComponentType_, false, colorSpace_); 138 | 139 | /** The packed color format. */ 140 | enum format = format_; 141 | 142 | /** The color components that were specified. */ 143 | enum string components = GetComponents!format_; 144 | 145 | /** Bit assignments for each component. */ 146 | enum ComponentInfo[components.length] componentInfo = GetComponentInfos!AllInfos; 147 | /** Shared exponent bits. */ 148 | enum ComponentInfo sharedExponent = GetSharedExponent!AllInfos; 149 | /** If the format has a shared exponent. */ 150 | enum bool hasSharedExponent = sharedExponent.bits > 0; 151 | 152 | /** The colors color space. */ 153 | enum RGBColorSpace colorSpace = colorSpace_; 154 | /** The color space descriptor. */ 155 | enum RGBColorSpaceDesc!F colorSpaceDesc(F = double) = rgbColorSpaceDef!F(colorSpace_); 156 | 157 | /** Number of bits per element. */ 158 | enum BitsPerElement = numBits(AllInfos); 159 | /** The raw packed data. */ 160 | Buffer!(BitsPerElement/8) data; 161 | 162 | /** Test if a particular component is present. */ 163 | enum bool hasComponent(char c) = canFind!(components, c); 164 | /** If the color has alpha. */ 165 | enum bool hasAlpha = hasComponent!'a'; 166 | 167 | /** The unpacked color. */ 168 | @property ParentColor unpacked() 169 | { 170 | return convertColorImpl!(ParentColor)(this); 171 | } 172 | 173 | /** Construct a color from RGB and optional alpha values. */ 174 | this(UnpackedColor color) 175 | { 176 | this = cast(typeof(this))color; 177 | } 178 | 179 | /** 180 | Cast to other color types. 181 | 182 | This cast is a convenience which simply forwards the call to convertColor. 183 | */ 184 | Color opCast(Color)() const if (isColor!Color) 185 | { 186 | return convertColor!Color(this); 187 | } 188 | 189 | // comparison 190 | bool opEquals(typeof(this) rh) const 191 | { 192 | // TODO: mask out 'x' component 193 | return data.data[] == rh.data.data[]; 194 | } 195 | 196 | 197 | package: 198 | 199 | alias ParentColor = UnpackedColor; 200 | 201 | static To convertColorImpl(To, From)(From color) if (isPackedRGB!From && isPackedRGB!To) 202 | { 203 | static if (From.colorSpace == To.colorSpace) 204 | { 205 | auto t = convertColorImpl!(From.ParentColor)(color); 206 | return convertColorImpl!To(t); 207 | } 208 | else 209 | { 210 | auto t = convertColorImpl!(From.ParentColor)(color); 211 | return convertColorImpl!To(cast(To.ParentColor)t); 212 | } 213 | } 214 | 215 | static To convertColorImpl(To, From)(From color) @trusted if (isPackedRGB!From && isRGB!To) 216 | { 217 | // target component type might be NormalizedInt 218 | static if (!isNumeric!(To.ComponentType)) 219 | alias ToType = To.ComponentType.IntType; 220 | else 221 | alias ToType = To.ComponentType; 222 | 223 | // if the color has a shared exponent 224 | static if (From.hasSharedExponent) 225 | int exp = cast(int)color.data.bits!(cast(size_t)From.sharedExponent.offset, cast(size_t)From.sharedExponent.bits) - ExpBias!(cast(size_t)From.sharedExponent.bits); 226 | 227 | To r; 228 | foreach (i; Iota!(0, From.componentInfo.length)) 229 | { 230 | // 'x' components are padding, no need to do work for them! 231 | static if (To.components[i] != 'x') 232 | { 233 | enum info = From.componentInfo[i]; 234 | enum size_t NumBits = info.bits; 235 | 236 | uint bits = color.data.bits!(cast(size_t)info.offset, NumBits); 237 | 238 | static if (info.type == ComponentInfo.ComponentType.Unsigned || 239 | info.type == ComponentInfo.ComponentType.Signed) 240 | { 241 | enum Signed = info.type == ComponentInfo.ComponentType.Signed; 242 | static if (isFloatingPoint!ToType) 243 | ToType c = normBitsToFloat!(NumBits, Signed, ToType)(bits); 244 | else 245 | ToType c = cast(ToType)convertNormBits!(NumBits, Signed, ToType.sizeof*8, isSigned!ToType, Unsigned!ToType)(bits); 246 | } 247 | else static if (info.type == ComponentInfo.ComponentType.Float) 248 | { 249 | static assert(NumBits >= 6, "Needs at least 6 bits for a float!"); 250 | 251 | // TODO: investigate a better way to select signed-ness in the format spec, maybe 'sf10', or 's10e5'? 252 | enum bool Signed = NumBits >= 16; 253 | 254 | // TODO: investigate a way to specify exponent bits in the format spec, maybe 'f10e3'? 255 | enum Exponent = 5; 256 | enum ExpBias = ExpBias!Exponent; 257 | enum Mantissa = NumBits - Exponent - (Signed ? 1 : 0); 258 | 259 | uint exponent = ((bits >> Mantissa) & BitsUMax!Exponent) - ExpBias + 127; 260 | uint mantissa = (bits & BitsUMax!Mantissa) << (23 - Mantissa); 261 | 262 | uint u = (Signed && (bits & SignBit!NumBits) ? SignBit!32 : 0) | (exponent << 23) | mantissa; 263 | static if (isFloatingPoint!ToType) 264 | ToType c = *cast(float*)&u; 265 | else 266 | ToType c = floatToNormInt!ToType(*cast(float*)&u); 267 | } 268 | else static if (info.type == ComponentInfo.ComponentType.Mantissa) 269 | { 270 | uint scale = (0x7F + (exp - info.bits)) << 23; 271 | static if (isFloatingPoint!ToType) 272 | ToType c = bits * *cast(float*)&scale; 273 | else 274 | ToType c = floatToNormInt!ToType(bits * *cast(float*)&scale); 275 | } 276 | mixin("r." ~ components[i] ~ " = To.ComponentType(c);"); 277 | } 278 | } 279 | return r; 280 | } 281 | 282 | static To convertColorImpl(To, From)(From color) @trusted if (isRGB!From && isPackedRGB!To) 283 | { 284 | // target component type might be NormalizedInt 285 | static if (!isNumeric!(From.ComponentType)) 286 | alias FromType = From.ComponentType.IntType; 287 | else 288 | alias FromType = From.ComponentType; 289 | 290 | To res; 291 | 292 | // if the color has a shared exponent 293 | static if (To.hasSharedExponent) 294 | { 295 | import std.algorithm : min, max, clamp; 296 | 297 | // prepare exponent... 298 | template SmallestMantissa(ComponentInfo[] Components) 299 | { 300 | template Impl(size_t i) 301 | { 302 | static if (i == Components.length) 303 | alias Impl = TypeTuple!(); 304 | else 305 | alias Impl = TypeTuple!(Components[i].bits, Impl!(i+1)); 306 | } 307 | enum SmallestMantissa = min(Impl!0); 308 | } 309 | enum MantBits = SmallestMantissa!(To.componentInfo); 310 | enum ExpBits = To.sharedExponent.bits; 311 | enum ExpBias = ExpBias!ExpBits; 312 | enum MaxExp = BitsUMax!ExpBits; 313 | 314 | // the maximum representable value is the one represented by the smallest mantissa 315 | enum MaxVal = cast(float)(BitsUMax!MantBits * (1<<(MaxExp-ExpBias))) / (1<> 23) & 0xFF) - 127; 328 | int sexp = max(-ExpBias - 1, maxc_exp) + 1 + ExpBias; 329 | assert(sexp >= 0 && sexp <= MaxExp); 330 | 331 | res.data.bits!(cast(size_t)To.sharedExponent.offset, cast(size_t)To.sharedExponent.bits) = cast(uint)sexp; 332 | } 333 | 334 | foreach (i; Iota!(0, To.componentInfo.length)) 335 | { 336 | // 'x' components are padding, no need to do work for them! 337 | static if (To.components[i] != 'x') 338 | { 339 | enum info = To.componentInfo[i]; 340 | enum size_t NumBits = info.bits; 341 | 342 | static if (info.type == ComponentInfo.ComponentType.Unsigned || 343 | info.type == ComponentInfo.ComponentType.Signed) 344 | { 345 | static if (isFloatingPoint!FromType) 346 | mixin("FromType c = color." ~ components[i] ~ ";"); 347 | else 348 | mixin("FromType c = color." ~ components[i] ~ ".value;"); 349 | 350 | enum Signed = info.type == ComponentInfo.ComponentType.Signed; 351 | static if (isFloatingPoint!FromType) 352 | uint bits = floatToNormBits!(NumBits, Signed)(c); 353 | else 354 | uint bits = convertNormBits!(FromType.sizeof*8, isSigned!FromType, NumBits, Signed)(cast(Unsigned!FromType)c); 355 | } 356 | else static if (info.type == ComponentInfo.ComponentType.Float) 357 | { 358 | static assert(NumBits >= 6, "Needs at least 6 bits for a float!"); 359 | 360 | // TODO: investigate a better way to select signed-ness in the format spec, maybe 'sf10', or 's10e5'? 361 | enum bool Signed = NumBits >= 16; 362 | 363 | // TODO: investigate a way to specify exponent bits in the format spec, maybe 'f10e3'? 364 | enum Exponent = 5; 365 | enum ExpBias = ExpBias!Exponent; 366 | enum Mantissa = NumBits - Exponent - (Signed ? 1 : 0); 367 | 368 | mixin("float f = cast(float)color." ~ components[i] ~ ";"); 369 | uint u = *cast(uint*)&f; 370 | 371 | int exponent = ((u >> 23) & 0xFF) - 127 + ExpBias; 372 | uint mantissa = (u >> (23 - Mantissa)) & BitsUMax!Mantissa; 373 | if (exponent < 0) 374 | { 375 | exponent = 0; 376 | mantissa = 0; 377 | } 378 | uint bits = (Signed && (u & SignBit!32) ? SignBit!NumBits : 0) | (exponent << Mantissa) | mantissa; 379 | } 380 | else static if (info.type == ComponentInfo.ComponentType.Mantissa) 381 | { 382 | // TODO: we could easily support signed values here... 383 | 384 | uint denom_u = cast(uint)(127 + sexp - ExpBias - NumBits) << 23; 385 | float denom = *cast(float*)&denom_u; 386 | 387 | mixin("float c = clamp(cast(float)color." ~ To.components[i] ~ ", 0.0f, MaxVal);"); 388 | uint bits = cast(uint)cast(int)(c / denom + 0.5f); 389 | assert(bits <= BitsUMax!NumBits); 390 | } 391 | 392 | res.data.bits!(cast(size_t)info.offset, NumBits) = bits; 393 | } 394 | } 395 | return res; 396 | } 397 | 398 | private: 399 | enum AllInfos = ParseFormat!format_; 400 | } 401 | 402 | 403 | private: 404 | 405 | // lots of logic to parse the format string 406 | template GetComponents(string format) 407 | { 408 | string get(string s) 409 | { 410 | foreach (i; 0..s.length) 411 | { 412 | if (s[i] == '_') 413 | return s[0..i]; 414 | } 415 | assert(false); 416 | } 417 | enum string GetComponents = get(format); 418 | } 419 | 420 | template GetComponentInfos(ComponentInfo[] infos) 421 | { 422 | template Impl(ComponentInfo[] infos, size_t i) 423 | { 424 | static if (i == infos.length) 425 | enum Impl = infos; 426 | else static if (infos[i].type == ComponentInfo.ComponentType.Exponent) 427 | enum Impl = infos[0..i] ~ infos[i+1..$]; 428 | else 429 | enum Impl = Impl!(infos, i+1); 430 | } 431 | enum GetComponentInfos = Impl!(infos, 0); 432 | } 433 | 434 | template GetSharedExponent(ComponentInfo[] infos) 435 | { 436 | template Impl(ComponentInfo[] infos, size_t i) 437 | { 438 | static if (i == infos.length) 439 | enum Impl = ComponentInfo(0, 0, ComponentInfo.ComponentType.Unsigned); 440 | else static if (infos[i].type == ComponentInfo.ComponentType.Exponent) 441 | enum Impl = infos[i]; 442 | else 443 | enum Impl = Impl!(infos, i+1); 444 | } 445 | enum GetSharedExponent = Impl!(infos, 0); 446 | } 447 | 448 | template ParseFormat(string format) 449 | { 450 | // parse the format string into component infos 451 | ComponentInfo[] impl(string s) pure nothrow @safe 452 | { 453 | static int parseInt(ref string str) pure nothrow @nogc @safe 454 | { 455 | int n = 0; 456 | while (str.length && str[0] >= '0' && str[0] <= '9') 457 | { 458 | n = n*10 + (str[0] - '0'); 459 | str = str[1..$]; 460 | } 461 | return n; 462 | } 463 | 464 | while (s.length && s[0] != '_') 465 | s = s[1..$]; 466 | 467 | ComponentInfo[] infos; 468 | 469 | int offset = 0; 470 | bool hasSharedExp = false; 471 | while (s.length && s[0] == '_') 472 | { 473 | s = s[1..$]; 474 | assert(s.length); 475 | 476 | char c = 0; 477 | if (!(s[0] >= '0' && s[0] <= '9')) 478 | { 479 | c = s[0]; 480 | s = s[1..$]; 481 | } 482 | 483 | int i = parseInt(s); 484 | assert(i > 0); 485 | 486 | infos ~= ComponentInfo(cast(ubyte)offset, cast(ubyte)i, ComponentInfo.ComponentType.Unsigned); 487 | 488 | if (c) 489 | { 490 | if (c == 'e' && !hasSharedExp) 491 | { 492 | infos[$-1].type = ComponentInfo.ComponentType.Exponent; 493 | hasSharedExp = true; 494 | } 495 | else if (c == 'f') 496 | infos[$-1].type = ComponentInfo.ComponentType.Float; 497 | else if (c == 's') 498 | infos[$-1].type = ComponentInfo.ComponentType.Signed; 499 | else if (c == 'u') 500 | infos[$-1].type = ComponentInfo.ComponentType.Unsigned; 501 | else 502 | assert(false); 503 | } 504 | 505 | offset += i; 506 | } 507 | assert(s.length == 0); 508 | 509 | if (hasSharedExp) 510 | { 511 | foreach (ref c; infos) 512 | { 513 | assert(c.type != ComponentInfo.ComponentType.Float && c.type != ComponentInfo.ComponentType.Signed); 514 | if (c.type != ComponentInfo.ComponentType.Exponent) 515 | c.type = ComponentInfo.ComponentType.Mantissa; 516 | } 517 | } 518 | 519 | return infos; 520 | } 521 | enum ParseFormat = impl(format); 522 | } 523 | 524 | template Iota(alias start, alias end) 525 | { 526 | static if (end == start) 527 | alias Iota = TypeTuple!(); 528 | else 529 | alias Iota = TypeTuple!(Iota!(start, end-1), end-1); 530 | } 531 | 532 | int numBits(ComponentInfo[] infos) pure nothrow @nogc @safe 533 | { 534 | int bits; 535 | foreach (i; infos) 536 | bits += i.bits; 537 | int slop = bits % 8; 538 | if (slop) 539 | bits += 8 - slop; 540 | return bits; 541 | } 542 | 543 | enum ExpBias(size_t n) = (1 << (n-1)) - 1; 544 | -------------------------------------------------------------------------------- /std/experimental/color/rgb.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | This module implements the $(LINK2 https://en.wikipedia.org/wiki/RGB_color_space, RGB) _color type. 5 | 6 | RGB is the most common expression of colors used in computing, where a _color is specified as some 7 | amount of red, green and blue primaries. 8 | 9 | RGB is highly parametric, and comes in many shapes and sizes, with the most common being 10 | $(LINK2 https://en.wikipedia.org/wiki/SRGB, sRGB), which is conventionally used on 11 | computer monitors, and standard for use on the web. 12 | 13 | RGB colors require the RGB _color space parameters to be defined to be considered 'absolute' colors. 14 | 15 | Authors: Manu Evans 16 | Copyright: Copyright (c) 2015, Manu Evans. 17 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0) 18 | Source: $(PHOBOSSRC std/experimental/color/_rgb.d) 19 | */ 20 | module std.experimental.color.rgb; 21 | 22 | import std.experimental.color; 23 | import std.experimental.color.colorspace; 24 | import std.experimental.color.xyz : XYZ, isXYZ; 25 | import std.experimental.normint; 26 | 27 | import std.traits : isInstanceOf, isNumeric, isIntegral, isFloatingPoint, isSomeChar, Unqual; 28 | import std.typetuple : TypeTuple; 29 | import std.typecons : tuple; 30 | 31 | @safe pure nothrow @nogc: 32 | 33 | 34 | /** 35 | Detect whether $(D_INLINECODE T) is an RGB color. 36 | */ 37 | enum isRGB(T) = isInstanceOf!(RGB, T); 38 | 39 | /// 40 | unittest 41 | { 42 | static assert(isRGB!(RGB!("bgr", ushort)) == true); 43 | static assert(isRGB!LA8 == true); 44 | static assert(isRGB!int == false); 45 | } 46 | 47 | 48 | // DEBATE: which should it be? 49 | template defaultAlpha(T) 50 | { 51 | /+ 52 | enum defaultAlpha = isFloatingPoint!T ? T(1) : T.max; 53 | +/ 54 | enum defaultAlpha = T(0); 55 | } 56 | 57 | 58 | /** 59 | An RGB color, parameterised with components, component type, and color space specification. 60 | 61 | Params: components_ = Components that shall be available. Struct is populated with components in the order specified.$(BR) 62 | Valid components are:$(BR) 63 | "r" = red$(BR) 64 | "g" = green$(BR) 65 | "b" = blue$(BR) 66 | "a" = alpha$(BR) 67 | "l" = luminance$(BR) 68 | "x" = placeholder/padding (no significant value) 69 | ComponentType_ = Type for the color channels. May be a basic integer or floating point type. 70 | linear_ = Color is stored with linear luminance. 71 | colorSpace_ = Color will be within the specified color space. 72 | */ 73 | struct RGB(string components_, ComponentType_, bool linear_ = false, RGBColorSpace colorSpace_ = RGBColorSpace.sRGB) 74 | if (isNumeric!ComponentType_) 75 | { 76 | @safe pure: 77 | 78 | /** Construct a color from a string. */ 79 | this(C)(const(C)[] str) if (isSomeChar!C) 80 | { 81 | this = colorFromString!(typeof(this))(str); 82 | } 83 | /// 84 | unittest 85 | { 86 | static assert(RGB8("#8000FF") == RGB8(0x80,0x00,0xFF)); 87 | static assert(RGBA8("#908000FF") == RGBA8(0x80,0x00,0xFF,0x90)); 88 | } 89 | 90 | nothrow @nogc: 91 | 92 | // RGB colors may only contain components 'rgb', or 'l' (luminance) 93 | // They may also optionally contain an 'a' (alpha) component, and 'x' (unused) components 94 | static assert(allIn!("rgblax", components), "Invalid Color component '"d ~ notIn!("rgblax", components) ~ "'. RGB colors may only contain components: r, g, b, l, a, x"d); 95 | static assert(anyIn!("rgbal", components), "RGB colors must contain at least one component of r, g, b, l, a."); 96 | static assert(!canFind!(components, 'l') || !anyIn!("rgb", components), "RGB colors may not contain rgb AND luminance components together."); 97 | 98 | static if (isFloatingPoint!ComponentType_) 99 | { 100 | /** Type of the color components. */ 101 | alias ComponentType = ComponentType_; 102 | } 103 | else 104 | { 105 | /** Type of the color components. */ 106 | alias ComponentType = NormalizedInt!ComponentType_; 107 | } 108 | 109 | /** The color components that were specified. */ 110 | enum string components = components_; 111 | /** The colors color space. */ 112 | enum RGBColorSpace colorSpace = colorSpace_; 113 | /** The color space descriptor. */ 114 | enum RGBColorSpaceDesc!F colorSpaceDesc(F = double) = rgbColorSpaceDef!F(colorSpace_); 115 | /** If the color is stored linearly (without gamma applied). */ 116 | enum bool linear = linear_; 117 | 118 | 119 | // mixin will emit members for components 120 | template Components(string components) 121 | { 122 | static if (components.length == 0) 123 | enum Components = ""; 124 | else 125 | enum Components = ComponentType.stringof ~ ' ' ~ components[0] ~ " = 0;\n" ~ Components!(components[1..$]); 126 | } 127 | mixin(Components!components); 128 | 129 | /** Test if a particular component is present. */ 130 | enum bool hasComponent(char c) = mixin("is(typeof(this."~c~"))"); 131 | /** If the color has alpha. */ 132 | enum bool hasAlpha = hasComponent!'a'; 133 | 134 | 135 | /** Return the RGB tristimulus values as a tuple. 136 | These will always be ordered (R, G, B). 137 | Any color channels not present will be 0. */ 138 | @property auto tristimulus() const 139 | { 140 | static if (hasComponent!'l') 141 | { 142 | return tuple(l, l, l); 143 | } 144 | else 145 | { 146 | static if (!hasComponent!'r') 147 | enum r = ComponentType(0); 148 | static if (!hasComponent!'g') 149 | enum g = ComponentType(0); 150 | static if (!hasComponent!'b') 151 | enum b = ComponentType(0); 152 | return tuple(r, g, b); 153 | } 154 | } 155 | /// 156 | unittest 157 | { 158 | // tristimulus returns tuple of R, G, B 159 | static assert(BGR8(255, 128, 10).tristimulus == tuple(NormalizedInt!ubyte(255), NormalizedInt!ubyte(128), NormalizedInt!ubyte(10))); 160 | } 161 | 162 | /** Return the RGB tristimulus values + alpha as a tuple. 163 | These will always be ordered (R, G, B, A). */ 164 | @property auto tristimulusWithAlpha() const 165 | { 166 | static if (!hasAlpha) 167 | enum a = defaultAlpha!ComponentType; 168 | return tuple(tristimulus.expand, a); 169 | } 170 | /// 171 | unittest 172 | { 173 | // tristimulusWithAlpha returns tuple of R, G, B, A 174 | static assert(BGRA8(255, 128, 10, 80).tristimulusWithAlpha == tuple(NormalizedInt!ubyte(255), NormalizedInt!ubyte(128), NormalizedInt!ubyte(10), NormalizedInt!ubyte(80))); 175 | } 176 | 177 | /** Construct a color from RGB and optional alpha values. */ 178 | this(ComponentType r, ComponentType g, ComponentType b, ComponentType a = defaultAlpha!ComponentType) 179 | { 180 | foreach (c; TypeTuple!("r","g","b","a")) 181 | mixin(ComponentExpression!("this._ = _;", c, null)); 182 | static if (canFind!(components, 'l')) 183 | this.l = toGrayscale!(linear, colorSpace)(r, g, b); // ** Contentious? I this this is most useful 184 | } 185 | 186 | /** Construct a color from a luminance and optional alpha value. */ 187 | this(ComponentType l, ComponentType a = defaultAlpha!ComponentType) 188 | { 189 | foreach (c; TypeTuple!("l","r","g","b")) 190 | mixin(ComponentExpression!("this._ = l;", c, null)); 191 | static if (canFind!(components, 'a')) 192 | this.a = a; 193 | } 194 | 195 | static if (!isFloatingPoint!ComponentType_) 196 | { 197 | /** Construct a color from RGB and optional alpha values. */ 198 | this(ComponentType.IntType r, ComponentType.IntType g, ComponentType.IntType b, ComponentType.IntType a = defaultAlpha!(ComponentType.IntType)) 199 | { 200 | foreach (c; TypeTuple!("r","g","b","a")) 201 | mixin(ComponentExpression!("this._ = ComponentType(_);", c, null)); 202 | static if (canFind!(components, 'l')) 203 | this.l = toGrayscale!(linear, colorSpace)(ComponentType(r), ComponentType(g), ComponentType(b)); // ** Contentious? I this this is most useful 204 | } 205 | 206 | /** Construct a color from a luminance and optional alpha value. */ 207 | this(ComponentType.IntType l, ComponentType.IntType a = defaultAlpha!(ComponentType.IntType)) 208 | { 209 | foreach (c; TypeTuple!("l","r","g","b")) 210 | mixin(ComponentExpression!("this._ = ComponentType(l);", c, null)); 211 | static if (canFind!(components, 'a')) 212 | this.a = ComponentType(a); 213 | } 214 | } 215 | 216 | /** 217 | Cast to other color types. 218 | 219 | This cast is a convenience which simply forwards the call to convertColor. 220 | */ 221 | Color opCast(Color)() const if (isColor!Color) 222 | { 223 | return convertColor!Color(this); 224 | } 225 | 226 | // comparison 227 | bool opEquals(typeof(this) rh) const 228 | { 229 | // this is required to exclude 'x' components from equality comparisons 230 | return tristimulusWithAlpha == rh.tristimulusWithAlpha; 231 | } 232 | 233 | /** Unary operators. */ 234 | typeof(this) opUnary(string op)() const if (op == "+" || op == "-" || (op == "~" && is(ComponentType == NormalizedInt!U, U))) 235 | { 236 | Unqual!(typeof(this)) res = this; 237 | foreach (c; AllComponents) 238 | mixin(ComponentExpression!("res._ = #_;", c, op)); 239 | return res; 240 | } 241 | /// 242 | unittest 243 | { 244 | static assert(+UVW8(1,2,3) == UVW8(1,2,3)); 245 | static assert(-UVW8(1,2,3) == UVW8(-1,-2,-3)); 246 | 247 | static assert(~RGB8(1,2,3) == RGB8(0xFE,0xFD,0xFC)); 248 | static assert(~UVW8(1,2,3) == UVW8(~1,~2,~3)); 249 | } 250 | 251 | /** Binary operators. */ 252 | typeof(this) opBinary(string op)(typeof(this) rh) const if (op == "+" || op == "-" || op == "*") 253 | { 254 | Unqual!(typeof(this)) res = this; 255 | foreach (c; AllComponents) 256 | mixin(ComponentExpression!("res._ #= rh._;", c, op)); 257 | return res; 258 | } 259 | /// 260 | unittest 261 | { 262 | static assert(RGB8(10,20,30) + RGB8(4,5,6) == RGB8(14,25,36)); 263 | static assert(UVW8(10,20,30) + UVW8(4,5,6) == UVW8(14,25,36)); 264 | static assert(RGBAf32(10,20,30,40) + RGBAf32(4,5,6,7) == RGBAf32(14,25,36,47)); 265 | 266 | static assert(RGB8(10,20,30) - RGB8(4,5,6) == RGB8(6,15,24)); 267 | static assert(UVW8(10,20,30) - UVW8(4,5,6) == UVW8(6,15,24)); 268 | static assert(RGBAf32(10,20,30,40) - RGBAf32(4,5,6,7) == RGBAf32(6,15,24,33)); 269 | 270 | static assert(RGB8(10,20,30) * RGB8(128,128,128) == RGB8(5,10,15)); 271 | static assert(UVW8(10,20,30) * UVW8(-64,-64,-64) == UVW8(-5,-10,-15)); 272 | static assert(RGBAf32(10,20,30,40) * RGBAf32(0,1,2,3) == RGBAf32(0,20,60,120)); 273 | } 274 | 275 | /** Binary operators. */ 276 | typeof(this) opBinary(string op, S)(S rh) const if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^")) 277 | { 278 | Unqual!(typeof(this)) res = this; 279 | foreach (c; AllComponents) 280 | mixin(ComponentExpression!("res._ #= rh;", c, op)); 281 | return res; 282 | } 283 | /// 284 | unittest 285 | { 286 | static assert(RGB8(10,20,30) * 2 == RGB8(20,40,60)); 287 | static assert(UVW8(10,20,30) * 2 == UVW8(20,40,60)); 288 | static assert(RGBAf32(10,20,30,40) * 2 == RGBAf32(20,40,60,80)); 289 | 290 | static assert(RGB8(10,20,30) / 2 == RGB8(5,10,15)); 291 | static assert(UVW8(-10,-20,-30) / 2 == UVW8(-5,-10,-15)); 292 | static assert(RGBAf32(10,20,30,40) / 2 == RGBAf32(5,10,15,20)); 293 | 294 | static assert(RGB8(10,20,30) * 2.0 == RGB8(20,40,60)); 295 | static assert(UVW8(10,20,30) * 2.0 == UVW8(20,40,60)); 296 | static assert(RGBAf32(10,20,30,40) * 2.0 == RGBAf32(20,40,60,80)); 297 | static assert(RGB8(10,20,30) * 0.5 == RGB8(5,10,15)); 298 | static assert(UVW8(-10,-20,-30) * 0.5 == UVW8(-5,-10,-15)); 299 | static assert(RGBAf32(5,10,15,20) * 0.5 == RGBAf32(2.5,5,7.5,10)); 300 | 301 | static assert(RGB8(10,20,30) / 2.0 == RGB8(5,10,15)); 302 | static assert(UVW8(-10,-20,-30) / 2.0 == UVW8(-5,-10,-15)); 303 | static assert(RGBAf32(10,20,30,40) / 2.0 == RGBAf32(5,10,15,20)); 304 | static assert(RGB8(10,20,30) / 0.5 == RGB8(20,40,60)); 305 | static assert(UVW8(10,20,30) / 0.5 == UVW8(20,40,60)); 306 | static assert(RGBAf32(10,20,30,40) / 0.5 == RGBAf32(20,40,60,80)); 307 | } 308 | 309 | /** Binary assignment operators. */ 310 | ref typeof(this) opOpAssign(string op)(typeof(this) rh) if (op == "+" || op == "-" || op == "*") 311 | { 312 | foreach (c; AllComponents) 313 | mixin(ComponentExpression!("_ #= rh._;", c, op)); 314 | return this; 315 | } 316 | 317 | /** Binary assignment operators. */ 318 | ref typeof(this) opOpAssign(string op, S)(S rh) if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^")) 319 | { 320 | foreach (c; AllComponents) 321 | mixin(ComponentExpression!("_ #= rh;", c, op)); 322 | return this; 323 | } 324 | 325 | package: 326 | 327 | alias ParentColor = XYZ!(FloatTypeFor!ComponentType); 328 | 329 | static To convertColorImpl(To, From)(From color) if (isRGB!From && isRGB!To) 330 | { 331 | alias ToType = To.ComponentType; 332 | alias FromType = From.ComponentType; 333 | 334 | auto src = color.tristimulusWithAlpha; 335 | 336 | static if (From.colorSpace == To.colorSpace && From.linear == To.linear) 337 | { 338 | // color space is the same, just do type conversion 339 | return To(cast(ToType)src[0], cast(ToType)src[1], cast(ToType)src[2], cast(ToType)src[3]); 340 | } 341 | else 342 | { 343 | // unpack the working values 344 | alias WorkType = WorkingType!(FromType, ToType); 345 | WorkType r = cast(WorkType)src[0]; 346 | WorkType g = cast(WorkType)src[1]; 347 | WorkType b = cast(WorkType)src[2]; 348 | 349 | static if (From.linear == false) 350 | { 351 | r = toLinear!(From.colorSpace)(r); 352 | g = toLinear!(From.colorSpace)(g); 353 | b = toLinear!(From.colorSpace)(b); 354 | } 355 | static if (From.colorSpace != To.colorSpace) 356 | { 357 | enum toXYZ = rgbToXyzMatrix(From.colorSpaceDesc!WorkType); 358 | enum toRGB = xyzToRgbMatrix(To.colorSpaceDesc!WorkType); 359 | enum mat = multiply(toXYZ, toRGB); 360 | WorkType[3] v = multiply(mat, [r, g, b]); 361 | r = v[0]; g = v[1]; b = v[2]; 362 | } 363 | static if (To.linear == false) 364 | { 365 | r = toGamma!(To.colorSpace)(r); 366 | g = toGamma!(To.colorSpace)(g); 367 | b = toGamma!(To.colorSpace)(b); 368 | } 369 | 370 | // convert and return the output 371 | static if (To.hasAlpha) 372 | return To(cast(ToType)r, cast(ToType)g, cast(ToType)b, cast(ToType)src[3]); 373 | else 374 | return To(cast(ToType)r, cast(ToType)g, cast(ToType)b); 375 | } 376 | } 377 | unittest 378 | { 379 | // test RGB format conversions 380 | alias UnsignedRGB = RGB!("rgb", ubyte); 381 | alias SignedRGBX = RGB!("rgbx", byte); 382 | alias FloatRGBA = RGB!("rgba", float); 383 | 384 | static assert(convertColorImpl!(UnsignedRGB)(SignedRGBX(0x20,0x30,-10)) == UnsignedRGB(0x40,0x60,0)); 385 | static assert(convertColorImpl!(UnsignedRGB)(FloatRGBA(1,0.5,0,1)) == UnsignedRGB(0xFF,0x80,0)); 386 | static assert(convertColorImpl!(FloatRGBA)(UnsignedRGB(0xFF,0x80,0)) == FloatRGBA(1,float(0x80)/float(0xFF),0,0)); 387 | static assert(convertColorImpl!(FloatRGBA)(SignedRGBX(127,-127,-128)) == FloatRGBA(1,-1,-1,0)); 388 | 389 | static assert(convertColorImpl!(UnsignedRGB)(convertColorImpl!(FloatRGBA)(UnsignedRGB(0xFF,0x80,0))) == UnsignedRGB(0xFF,0x80,0)); 390 | 391 | // test greyscale conversion 392 | alias UnsignedL = RGB!("l", ubyte); 393 | static assert(cast(UnsignedL)UnsignedRGB(0xFF,0x20,0x40) == UnsignedL(82)); 394 | 395 | // TODO: we can't test this properly since DMD can't CTFE the '^^' operator! >_< 396 | 397 | alias sRGBA = RGB!("rgba", ubyte, false, RGBColorSpace.sRGB); 398 | 399 | // test linear conversion 400 | alias lRGBA = RGB!("rgba", ushort, true, RGBColorSpace.sRGB); 401 | assert(convertColorImpl!(lRGBA)(sRGBA(0xFF, 0xFF, 0xFF, 0xFF)) == lRGBA(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF)); 402 | 403 | // test gamma conversion 404 | alias gRGBA = RGB!("rgba", byte, false, RGBColorSpace.sRGB_Gamma2_2); 405 | assert(convertColorImpl!(gRGBA)(sRGBA(0xFF, 0x80, 0x01, 0xFF)) == gRGBA(0x7F, 0x3F, 0x03, 0x7F)); 406 | } 407 | 408 | static To convertColorImpl(To, From)(From color) if (isRGB!From && isXYZ!To) 409 | { 410 | alias ToType = To.ComponentType; 411 | alias FromType = From.ComponentType; 412 | alias WorkType = WorkingType!(FromType, ToType); 413 | 414 | // unpack the working values 415 | auto src = color.tristimulus; 416 | WorkType r = cast(WorkType)src[0]; 417 | WorkType g = cast(WorkType)src[1]; 418 | WorkType b = cast(WorkType)src[2]; 419 | 420 | static if (From.linear == false) 421 | { 422 | r = toLinear!(From.colorSpace)(r); 423 | g = toLinear!(From.colorSpace)(g); 424 | b = toLinear!(From.colorSpace)(b); 425 | } 426 | 427 | // transform to XYZ 428 | enum toXYZ = rgbToXyzMatrix(From.colorSpaceDesc!WorkType); 429 | WorkType[3] v = multiply(toXYZ, [r, g, b]); 430 | return To(v[0], v[1], v[2]); 431 | } 432 | unittest 433 | { 434 | // TODO: needs approx == 435 | } 436 | 437 | static To convertColorImpl(To, From)(From color) if (isXYZ!From && isRGB!To) 438 | { 439 | alias ToType = To.ComponentType; 440 | alias FromType = From.ComponentType; 441 | alias WorkType = WorkingType!(FromType, ToType); 442 | 443 | enum toRGB = xyzToRgbMatrix(To.colorSpaceDesc!WorkType); 444 | WorkType[3] v = multiply(toRGB, [ WorkType(color.X), WorkType(color.Y), WorkType(color.Z) ]); 445 | 446 | static if (To.linear == false) 447 | { 448 | v[0] = toGamma!(To.colorSpace)(v[0]); 449 | v[1] = toGamma!(To.colorSpace)(v[1]); 450 | v[2] = toGamma!(To.colorSpace)(v[2]); 451 | } 452 | 453 | return To(cast(ToType)v[0], cast(ToType)v[1], cast(ToType)v[2]); 454 | } 455 | unittest 456 | { 457 | // TODO: needs approx == 458 | } 459 | 460 | private: 461 | alias AllComponents = TypeTuple!("l","r","g","b","a"); 462 | } 463 | 464 | 465 | /** Convert a value from gamma compressed space to linear. */ 466 | T toLinear(RGBColorSpace src, T)(T v) if (isFloatingPoint!T) 467 | { 468 | enum ColorSpace = rgbColorSpaceDefs!T[src]; 469 | return ColorSpace.toLinear(v); 470 | } 471 | /** Convert a value to gamma compressed space. */ 472 | T toGamma(RGBColorSpace src, T)(T v) if (isFloatingPoint!T) 473 | { 474 | enum ColorSpace = rgbColorSpaceDefs!T[src]; 475 | return ColorSpace.toGamma(v); 476 | } 477 | 478 | /** Convert a color to linear space. */ 479 | auto toLinear(C)(C color) if (isRGB!C) 480 | { 481 | return cast(RGB!(C.components, C.ComponentType, true, C.colorSpace))color; 482 | } 483 | /** Convert a color to gamma space. */ 484 | auto toGamma(C)(C color) if (isRGB!C) 485 | { 486 | return cast(RGB!(C.components, C.ComponentType, false, C.colorSpace))color; 487 | } 488 | 489 | 490 | package: 491 | 492 | T toGrayscale(bool linear, RGBColorSpace colorSpace = RGBColorSpace.sRGB, T)(T r, T g, T b) pure if (isFloatingPoint!T) 493 | { 494 | static if (linear) 495 | { 496 | // calculate the luminance (Y) value correctly by multiplying the Y row of the XYZ matrix with the color 497 | enum YAxis = rgbColorSpaceDef!T(colorSpace).rgbToXyzMatrix()[1]; 498 | return YAxis[0]*r + YAxis[1]*g + YAxis[2]*b; 499 | } 500 | else static if (colorSpace == RGBColorSpace.Colorimetry || 501 | colorSpace == RGBColorSpace.NTSC || 502 | colorSpace == RGBColorSpace.NTSC_J || 503 | colorSpace == RGBColorSpace.PAL_SECAM) 504 | { 505 | // For color spaces which are used in standard color TV and video systems such as PAL/SECAM, and 506 | // NTSC, a nonlinear luma component (Y') is calculated directly from gamma-compressed primary 507 | // intensities as a weighted sum, which can be calculated quickly without the gamma expansion and 508 | // compression used in colorimetric grayscale calculations. 509 | // The Rec.601 luma (Y') component is computed as: 510 | return T(0.299)*r + T(0.587)*g + T(0.114)*b; 511 | } 512 | else static if (colorSpace == RGBColorSpace.HDTV) 513 | { 514 | // The Rec.709 standard used for HDTV uses different color coefficients. 515 | // These happen to be the same as sRGB, but applied to the gamma compressed signal direcetly. 516 | return T(0.2126)*r + T(0.7152)*g + T(0.0722)*b; 517 | } 518 | else 519 | { 520 | // Edge-case: What to do?! Approximate, or perform gamma conversions? 521 | // The TV standards have defined approximations, so let's continue to roll with that pattern. 522 | // We'll continue the Rec.709 pattern, except using appropriate coefficients for the color space. 523 | enum YAxis = rgbColorSpaceDef!T(colorSpace).rgbToXyzMatrix()[1]; 524 | return YAxis[0]*r + YAxis[1]*g + YAxis[2]*b; 525 | } 526 | } 527 | T toGrayscale(bool linear, RGBColorSpace colorSpace = RGBColorSpace.sRGB, T)(T r, T g, T b) pure if (is(T == NormalizedInt!U, U)) 528 | { 529 | alias F = FloatTypeFor!T; 530 | return T(toGrayscale!(linear, colorSpace)(cast(F)r, cast(F)g, cast(F)b)); 531 | } 532 | 533 | 534 | // helpers to parse color components from color component string 535 | template canFind(string s, char c) 536 | { 537 | static if (s.length == 0) 538 | enum canFind = false; 539 | else 540 | enum canFind = s[0] == c || canFind!(s[1..$], c); 541 | } 542 | template allIn(string s, string chars) 543 | { 544 | static if (chars.length == 0) 545 | enum allIn = true; 546 | else 547 | enum allIn = canFind!(s, chars[0]) && allIn!(s, chars[1..$]); 548 | } 549 | template anyIn(string s, string chars) 550 | { 551 | static if (chars.length == 0) 552 | enum anyIn = false; 553 | else 554 | enum anyIn = canFind!(s, chars[0]) || anyIn!(s, chars[1..$]); 555 | } 556 | template notIn(string s, string chars) 557 | { 558 | static if (chars.length == 0) 559 | enum notIn = char(0); 560 | else static if (!canFind!(s, chars[0])) 561 | enum notIn = chars[0]; 562 | else 563 | enum notIn = notIn!(s, chars[1..$]); 564 | } 565 | 566 | unittest 567 | { 568 | static assert(canFind!("string", 'i')); 569 | static assert(!canFind!("string", 'x')); 570 | static assert(allIn!("string", "sgi")); 571 | static assert(!allIn!("string", "sgix")); 572 | static assert(anyIn!("string", "sx")); 573 | static assert(!anyIn!("string", "x")); 574 | } 575 | -------------------------------------------------------------------------------- /std/experimental/color/xyz.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | This module implements $(LINK2 https://en.wikipedia.org/wiki/CIE_1931_color_space, CIE XYZ) and 5 | $(LINK2 https://en.wikipedia.org/wiki/CIE_1931_color_space#CIE_xy_chromaticity_diagram_and_the_CIE_xyY_color_space, xyY) 6 | _color types. 7 | 8 | These _color spaces represent the simplest expression of the full-spectrum of human visible _color. 9 | No attempts are made to support perceptual uniformity, or meaningful _color blending within these _color spaces. 10 | They are most useful as an absolute representation of human visible colors, and a centre point for _color space 11 | conversions. 12 | 13 | Authors: Manu Evans 14 | Copyright: Copyright (c) 2015, Manu Evans. 15 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0) 16 | Source: $(PHOBOSSRC std/experimental/color/_xyz.d) 17 | */ 18 | module std.experimental.color.xyz; 19 | 20 | import std.experimental.color; 21 | version(unittest) 22 | import std.experimental.color.colorspace : WhitePoint; 23 | 24 | import std.traits : isInstanceOf, isFloatingPoint, Unqual; 25 | import std.typetuple : TypeTuple; 26 | import std.typecons : tuple; 27 | 28 | @safe pure nothrow @nogc: 29 | 30 | 31 | /** 32 | Detect whether $(D_INLINECODE T) is an XYZ color. 33 | */ 34 | enum isXYZ(T) = isInstanceOf!(XYZ, T); 35 | 36 | /// 37 | unittest 38 | { 39 | static assert(isXYZ!(XYZ!float) == true); 40 | static assert(isXYZ!(xyY!double) == false); 41 | static assert(isXYZ!int == false); 42 | } 43 | 44 | 45 | /** 46 | Detect whether $(D_INLINECODE T) is an xyY color. 47 | */ 48 | enum isxyY(T) = isInstanceOf!(xyY, T); 49 | 50 | /// 51 | unittest 52 | { 53 | static assert(isxyY!(xyY!float) == true); 54 | static assert(isxyY!(XYZ!double) == false); 55 | static assert(isxyY!int == false); 56 | } 57 | 58 | 59 | /** 60 | A CIE 1931 XYZ color, parameterised for component type. 61 | */ 62 | struct XYZ(F = float) if (isFloatingPoint!F) 63 | { 64 | @safe pure nothrow @nogc: 65 | 66 | /** Type of the color components. */ 67 | alias ComponentType = F; 68 | 69 | /** X value. */ 70 | F X = 0; 71 | /** Y value. */ 72 | F Y = 0; 73 | /** Z value. */ 74 | F Z = 0; 75 | 76 | /** Return the XYZ tristimulus values as a tuple. */ 77 | @property auto tristimulus() const 78 | { 79 | return tuple(X, Y, Z); 80 | } 81 | 82 | /** Construct a color from XYZ values. */ 83 | this(ComponentType X, ComponentType Y, ComponentType Z) 84 | { 85 | this.X = X; 86 | this.Y = Y; 87 | this.Z = Z; 88 | } 89 | 90 | /** 91 | Cast to other color types. 92 | 93 | This cast is a convenience which simply forwards the call to convertColor. 94 | */ 95 | Color opCast(Color)() const if (isColor!Color) 96 | { 97 | return convertColor!Color(this); 98 | } 99 | 100 | /** Unary operators. */ 101 | typeof(this) opUnary(string op)() const if (op == "+" || op == "-" || (op == "~" && is(ComponentType == NormalizedInt!U, U))) 102 | { 103 | Unqual!(typeof(this)) res = this; 104 | foreach (c; AllComponents) 105 | mixin(ComponentExpression!("res._ = #_;", c, op)); 106 | return res; 107 | } 108 | /** Binary operators. */ 109 | typeof(this) opBinary(string op)(typeof(this) rh) const if (op == "+" || op == "-" || op == "*") 110 | { 111 | Unqual!(typeof(this)) res = this; 112 | foreach (c; AllComponents) 113 | mixin(ComponentExpression!("res._ #= rh._;", c, op)); 114 | return res; 115 | } 116 | /** Binary operators. */ 117 | typeof(this) opBinary(string op, S)(S rh) const if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^")) 118 | { 119 | Unqual!(typeof(this)) res = this; 120 | foreach (c; AllComponents) 121 | mixin(ComponentExpression!("res._ #= rh;", c, op)); 122 | return res; 123 | } 124 | /** Binary assignment operators. */ 125 | ref typeof(this) opOpAssign(string op)(typeof(this) rh) if (op == "+" || op == "-" || op == "*") 126 | { 127 | foreach (c; AllComponents) 128 | mixin(ComponentExpression!("_ #= rh._;", c, op)); 129 | return this; 130 | } 131 | /** Binary assignment operators. */ 132 | ref typeof(this) opOpAssign(string op, S)(S rh) if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^")) 133 | { 134 | foreach (c; AllComponents) 135 | mixin(ComponentExpression!("_ #= rh;", c, op)); 136 | return this; 137 | } 138 | 139 | 140 | package: 141 | 142 | static To convertColorImpl(To, From)(From color) if (isXYZ!From && isXYZ!To) 143 | { 144 | alias F = To.ComponentType; 145 | return To(F(color.X), F(color.Y), F(color.Z)); 146 | } 147 | unittest 148 | { 149 | static assert(convertColorImpl!(XYZ!float)(XYZ!double(1, 2, 3)) == XYZ!float(1, 2, 3)); 150 | static assert(convertColorImpl!(XYZ!double)(XYZ!float(1, 2, 3)) == XYZ!double(1, 2, 3)); 151 | } 152 | 153 | private: 154 | alias AllComponents = TypeTuple!("X", "Y", "Z"); 155 | } 156 | 157 | /// 158 | unittest 159 | { 160 | // CIE XYZ 1931 color with float components 161 | alias XYZf = XYZ!float; 162 | 163 | XYZf c = XYZf(0.8, 1, 1.2); 164 | 165 | // tristimulus() returns a tuple of the components 166 | assert(c.tristimulus == tuple(c.X, c.Y, c.Z)); 167 | 168 | // test XYZ operators and functions 169 | static assert(XYZf(0, 0.5, 0) + XYZf(0.5, 0.5, 1) == XYZf(0.5, 1, 1)); 170 | static assert(XYZf(0.5, 0.5, 1) * 100.0 == XYZf(50, 50, 100)); 171 | } 172 | 173 | 174 | /** 175 | A CIE 1931 xyY color, parameterised for component type. 176 | */ 177 | struct xyY(F = float) if (isFloatingPoint!F) 178 | { 179 | @safe pure nothrow @nogc: 180 | 181 | /** Type of the color components. */ 182 | alias ComponentType = F; 183 | 184 | /** x coordinate. */ 185 | F x = 0; 186 | /** y coordinate. */ 187 | F y = 0; 188 | /** Y value (luminance). */ 189 | F Y = 0; 190 | 191 | /** Construct a color from xyY values. */ 192 | this(ComponentType x, ComponentType y, ComponentType Y) 193 | { 194 | this.x = x; 195 | this.y = y; 196 | this.Y = Y; 197 | } 198 | 199 | /** 200 | Cast to other color types. 201 | 202 | This cast is a convenience which simply forwards the call to convertColor. 203 | */ 204 | Color opCast(Color)() const if (isColor!Color) 205 | { 206 | return convertColor!Color(this); 207 | } 208 | 209 | /** Unary operators. */ 210 | typeof(this) opUnary(string op)() const if (op == "+" || op == "-" || (op == "~" && is(ComponentType == NormalizedInt!U, U))) 211 | { 212 | Unqual!(typeof(this)) res = this; 213 | foreach (c; AllComponents) 214 | mixin(ComponentExpression!("res._ = #_;", c, op)); 215 | return res; 216 | } 217 | 218 | /** Binary operators. */ 219 | typeof(this) opBinary(string op)(typeof(this) rh) const if (op == "+" || op == "-" || op == "*") 220 | { 221 | Unqual!(typeof(this)) res = this; 222 | foreach (c; AllComponents) 223 | mixin(ComponentExpression!("res._ #= rh._;", c, op)); 224 | return res; 225 | } 226 | 227 | /** Binary operators. */ 228 | typeof(this) opBinary(string op, S)(S rh) const if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^")) 229 | { 230 | Unqual!(typeof(this)) res = this; 231 | foreach (c; AllComponents) 232 | mixin(ComponentExpression!("res._ #= rh;", c, op)); 233 | return res; 234 | } 235 | 236 | /** Binary assignment operators. */ 237 | ref typeof(this) opOpAssign(string op)(typeof(this) rh) if (op == "+" || op == "-" || op == "*") 238 | { 239 | foreach (c; AllComponents) 240 | mixin(ComponentExpression!("_ #= rh._;", c, op)); 241 | return this; 242 | } 243 | 244 | /** Binary assignment operators. */ 245 | ref typeof(this) opOpAssign(string op, S)(S rh) if (isColorScalarType!S && (op == "*" || op == "/" || op == "%" || op == "^^")) 246 | { 247 | foreach (c; AllComponents) 248 | mixin(ComponentExpression!("_ #= rh;", c, op)); 249 | return this; 250 | } 251 | 252 | 253 | package: 254 | 255 | alias ParentColor = XYZ!ComponentType; 256 | 257 | static To convertColorImpl(To, From)(From color) if (isxyY!From && isxyY!To) 258 | { 259 | alias F = To.ComponentType; 260 | return To(F(color.x), F(color.y), F(color.Y)); 261 | } 262 | unittest 263 | { 264 | static assert(convertColorImpl!(xyY!float)(xyY!double(1, 2, 3)) == xyY!float(1, 2, 3)); 265 | static assert(convertColorImpl!(xyY!double)(xyY!float(1, 2, 3)) == xyY!double(1, 2, 3)); 266 | } 267 | 268 | static To convertColorImpl(To, From)(From color) if (isxyY!From && isXYZ!To) 269 | { 270 | alias F = To.ComponentType; 271 | if (color.y == 0) 272 | return To(F(0), F(0), F(0)); 273 | else 274 | return To(F((color.Y/color.y)*color.x), F(color.Y), F((color.Y/color.y)*(1-color.x-color.y))); 275 | } 276 | unittest 277 | { 278 | static assert(convertColorImpl!(XYZ!float)(xyY!float(0.5, 0.5, 1)) == XYZ!float(1, 1, 0)); 279 | 280 | // degenerate case 281 | static assert(convertColorImpl!(XYZ!float)(xyY!float(0.5, 0, 1)) == XYZ!float(0, 0, 0)); 282 | } 283 | 284 | static To convertColorImpl(To, From)(From color) if (isXYZ!From && isxyY!To) 285 | { 286 | alias F = To.ComponentType; 287 | auto sum = color.X + color.Y + color.Z; 288 | if (sum == 0) 289 | return To(WhitePoint!F.D65.x, WhitePoint!F.D65.y, F(0)); 290 | else 291 | return To(F(color.X/sum), F(color.Y/sum), F(color.Y)); 292 | } 293 | unittest 294 | { 295 | static assert(convertColorImpl!(xyY!float)(XYZ!float(0.5, 1, 0.5)) == xyY!float(0.25, 0.5, 1)); 296 | 297 | // degenerate case 298 | static assert(convertColorImpl!(xyY!float)(XYZ!float(0, 0, 0)) == xyY!float(WhitePoint!float.D65.x, WhitePoint!float.D65.y, 0)); 299 | } 300 | 301 | private: 302 | alias AllComponents = TypeTuple!("x", "y", "Y"); 303 | } 304 | 305 | /// 306 | unittest 307 | { 308 | // CIE xyY 1931 color with double components 309 | alias xyYd = xyY!double; 310 | 311 | xyYd c = xyYd(0.4, 0.5, 1); 312 | 313 | // test xyY operators and functions 314 | static assert(xyYd(0, 0.5, 0) + xyYd(0.5, 0.5, 1) == xyYd(0.5, 1, 1)); 315 | static assert(xyYd(0.5, 0.5, 1) * 100.0 == xyYd(50, 50, 100)); 316 | } 317 | -------------------------------------------------------------------------------- /std/experimental/normint.d: -------------------------------------------------------------------------------- 1 | // Written in the D programming language. 2 | 3 | /** 4 | This module implements support for normalized integers. 5 | 6 | Authors: Manu Evans 7 | Copyright: Copyright (c) 2015, Manu Evans. 8 | License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 | Source: $(PHOBOSSRC std/experimental/_normint.d) 10 | */ 11 | module std.experimental.normint; 12 | 13 | import std.traits : isIntegral, isSigned, isUnsigned, isFloatingPoint, Unsigned; 14 | 15 | @safe pure nothrow @nogc: 16 | 17 | /** 18 | Check if $(D_INLINECODE I) is a valid NormalizedInt type. 19 | Valid integers are $(D_INLINECODE (u)byte), $(D_INLINECODE (u)short), $(D_INLINECODE (u)int). $(D_INLINECODE (u)long) is not supported. 20 | */ 21 | enum isNormalizedIntegralType(I) = isIntegral!I && I.sizeof < 8; 22 | 23 | 24 | /** 25 | Normalized integers express a fractional range of values within an integer data type. 26 | 27 | Unsigned integers map the values $(D_INLINECODE [0, I.max]) to the fractional values $(D_INLINECODE [0.0, 1.0]) in equal increments.$(BR) 28 | Signed integers represent the values $(D_INLINECODE [-I.max, I.max]) to fractional values $(D_INLINECODE [-1.0, 1.0]) in equal increments. $(D_INLINECODE I.min) is outside the nominal integer range and is clamped to represent $(D_INLINECODE -1.0). 29 | 30 | Params: $(D_INLINECODE I) = $(D_INLINECODE (u)byte), $(D_INLINECODE (u)short), $(D_INLINECODE (u)int). $(D_INLINECODE (u)long) is not supported. 31 | */ 32 | struct NormalizedInt(I) if (isNormalizedIntegralType!I) 33 | { 34 | @safe: 35 | static import std.algorithm.comparison; 36 | 37 | string toString() const 38 | { 39 | import std.conv : to; 40 | return to!string(cast(double)this); 41 | } 42 | 43 | pure nothrow @nogc: 44 | 45 | /** The actual value */ 46 | I value; 47 | 48 | /** Integral storage type. */ 49 | alias IntType = I; 50 | 51 | /** Maximum integral value. */ 52 | enum I max = I.max; 53 | /** Minimum integral value. */ 54 | enum I min = isSigned!I ? -cast(int)I.max : 0; 55 | 56 | /** Maximum floating point value. */ 57 | enum max_float = 1.0; 58 | /** Minimum floating point value. */ 59 | enum min_float = isSigned!I ? -1.0 : 0.0; 60 | 61 | /** Construct a $(D_INLINECODE NormalizedInt) from an integer representation. */ 62 | this(I value) 63 | { 64 | this.value = value; 65 | } 66 | 67 | /** Construct a $(D_INLINECODE NormalizedInt) from a floating point representation. The value is clamped to the range $(D_INLINECODE [min, max]). */ 68 | this(F)(F value) if (isFloatingPoint!F) 69 | { 70 | this.value = floatToNormInt!I(value); 71 | } 72 | 73 | /** Unary operators. */ 74 | NormalizedInt!I opUnary(string op)() const 75 | { 76 | static if (op == "-" && isUnsigned!I) 77 | return NormalizedInt!I(0); // unsigned negate saturates at 0 78 | else 79 | return NormalizedInt!I(mixin("cast(I)" ~ op ~ "cast(WorkInt!I)value")); 80 | } 81 | 82 | /** Binary operators. */ 83 | NormalizedInt!I opBinary(string op)(NormalizedInt!I rh) const if (op == "+" || op == "-") 84 | { 85 | auto r = mixin("cast(WorkInt!I)value " ~ op ~ " rh.value"); 86 | r = std.algorithm.comparison.min(r, max); 87 | static if (op == "-") 88 | r = std.algorithm.comparison.max(r, min); 89 | return NormalizedInt!I(cast(I)r); 90 | } 91 | 92 | /** Binary operators. */ 93 | NormalizedInt!I opBinary(string op)(NormalizedInt!I rh) const if (op == "*" || op == "^^") 94 | { 95 | static if (is(I == ubyte) && op == "*") 96 | { 97 | uint r = cast(uint)value * rh.value; 98 | r = r * 0x1011 >> 20; 99 | return NormalizedInt!I(cast(I)r); 100 | } 101 | else static if (is(I == ushort) && op == "*") 102 | { 103 | ulong r = cast(ulong)value * rh.value; 104 | r = r * 0x10_0011 >> 36; 105 | return NormalizedInt!I(cast(I)r); 106 | } 107 | // *** SLOW PATH *** 108 | // do it with floats 109 | else static if (op == "*") 110 | { 111 | // use a post-multiply divide; less muls! 112 | double a = value; 113 | double b = rh.value; 114 | static if (isSigned!I) 115 | { 116 | a = std.algorithm.comparison.max(a, cast(double)min); 117 | b = std.algorithm.comparison.max(b, cast(double)min); 118 | } 119 | double r = a * b * (1.0/max); 120 | return NormalizedInt!I(cast(I)r); 121 | } 122 | else 123 | { 124 | // pow must be normalised first. 125 | double a = value * (1.0/max); 126 | double b = rh.value * (1.0/max); 127 | static if (isSigned!I) 128 | { 129 | a = std.algorithm.comparison.max(a, -1.0); 130 | b = std.algorithm.comparison.max(b, -1.0); 131 | } 132 | double r = a^^b * double(max); 133 | if (isUnsigned!I || r >= 0) 134 | return NormalizedInt!I(cast(I)(r + 0.50)); 135 | else 136 | return NormalizedInt!I(cast(I)(r - 0.50)); 137 | } 138 | } 139 | 140 | /** Binary operators. */ 141 | NormalizedInt!I opBinary(string op)(NormalizedInt!I rh) const if (op == "/" || op == "%") 142 | { 143 | return mixin("this " ~ op ~ " cast(FloatTypeFor!I)rh"); 144 | } 145 | 146 | /** Binary operators. */ 147 | NormalizedInt!I opBinary(string op, T)(T rh) const if (isNormalizedIntegralType!T && op == "*") 148 | { 149 | return NormalizedInt!I(cast(I)std.algorithm.comparison.clamp(cast(WorkInt!I)value * rh, min, max)); 150 | } 151 | 152 | /** Binary operators. */ 153 | NormalizedInt!I opBinary(string op, T)(T rh) const if (isNormalizedIntegralType!T && (op == "/" || op == "%")) 154 | { 155 | return NormalizedInt!I(cast(I)mixin("value " ~ op ~ " rh")); 156 | } 157 | 158 | /** Binary operators. */ 159 | NormalizedInt!I opBinary(string op, F)(F rh) const if (isFloatingPoint!F && (op == "*" || op == "/" || op == "%" || op == "^^")) 160 | { 161 | return NormalizedInt!I(mixin("(cast(F)this) " ~ op ~ " rh")); 162 | } 163 | 164 | /** Binary operators. */ 165 | NormalizedInt!I opBinary(string op)(NormalizedInt!I rh) const if (op == "|" || op == "&" || op == "^") 166 | { 167 | return NormalizedInt!I(cast(I)(mixin("value " ~ op ~ " rh.value"))); 168 | } 169 | 170 | /** Binary operators. */ 171 | NormalizedInt!I opBinary(string op)(int rh) const if (op == "|" || op == "&" || op == "^" || op == "<<" || op == ">>" || op == ">>>") 172 | { 173 | return NormalizedInt!I(cast(I)(mixin("value " ~ op ~ " rh"))); 174 | } 175 | 176 | /** Equality operators. */ 177 | bool opEquals(NormalizedInt!I rh) const 178 | { 179 | return value == rh.value; 180 | } 181 | /** 182 | Integral equality operator.$(BR) 183 | If $(D_INLINECODE rh) is outside of the integral range, $(D_INLINECODE rh) will not be clamped and comparison will return $(D_INLINECODE false). 184 | */ 185 | bool opEquals(T)(T rh) const if (isNormalizedIntegralType!T) 186 | { 187 | return value == rh; 188 | } 189 | /** 190 | Floating point equality operator.$(BR) 191 | If $(D_INLINECODE rh) is outside of the nominal range, $(D_INLINECODE rh) will not be clamped and comparison will return $(D_INLINECODE false). 192 | */ 193 | bool opEquals(F)(F rh) const if (isFloatingPoint!F) 194 | { 195 | return cast(F)this == rh; 196 | } 197 | 198 | /** Comparison operators. */ 199 | int opCmp(NormalizedInt!I rh) const 200 | { 201 | return value - rh.value; 202 | } 203 | /** Comparison operators. */ 204 | int opCmp(T)(T rh) const if (isNormalizedIntegralType!T) 205 | { 206 | return value - rh; 207 | } 208 | /** Comparison operators. */ 209 | int opCmp(F)(F rh) const if (isFloatingPoint!F) 210 | { 211 | F f = cast(F)this; 212 | return f < rh ? -1 : (f > rh ? 1 : 0); 213 | } 214 | 215 | /** Binary assignment operators. */ 216 | ref NormalizedInt!I opOpAssign(string op, T)(T rh) if (is(T == NormalizedInt!I) || isFloatingPoint!T || isNormalizedIntegralType!T) 217 | { 218 | this = mixin("this " ~ op ~ "rh"); 219 | return this; 220 | } 221 | 222 | /** Cast between $(D_INLINECODE NormalizedInt) types. */ 223 | NormInt opCast(NormInt)() const if (is(NormInt == NormalizedInt!T, T)) 224 | { 225 | static if (is(NormInt == NormalizedInt!T, T)) 226 | return NormInt(convertNormInt!T(value)); 227 | else 228 | static assert(false, "Shouldn't be possible!"); 229 | } 230 | 231 | /** Floating point cast operator. */ 232 | F opCast(F)() const if (isFloatingPoint!F) 233 | { 234 | return normIntToFloat!F(value); 235 | } 236 | } 237 | /// 238 | unittest 239 | { 240 | auto x = NormalizedInt!ubyte(200); 241 | auto y = NormalizedInt!ubyte(50); 242 | 243 | auto z = x + y; assert(z == 250); // add as expected 244 | z += y; assert(z == 255); // overflow saturates 245 | assert(cast(float)z == 1.0); // maximum value is floating point 1.0 246 | z -= x; assert(z == 55); // subtract as expected 247 | z -= x; assert(z == 0); // underflow saturates 248 | z = y * 2; assert(z == 100); // multiply by integer 249 | z = x * 0.5; assert(z == 100); // multiply by float 250 | z *= 3; assert(z == 255); // multiply overflow saturates 251 | z *= y; assert(z == 50); // multiply is performed in normalized space 252 | z *= y; assert(z == 9); // multiplication rounds *down* 253 | z /= 2; assert(z == 4); // division works as expected, rounds down 254 | z /= y; assert(z == 20); // division is performed in normalized space 255 | z /= y * y; assert(z == 255); // division overflow saturates 256 | z = -z; assert(z == 0); // unsigned negation saturates at zero 257 | 258 | auto u = NormalizedInt!short(-1.0); 259 | auto v = NormalizedInt!short(0.5); 260 | 261 | auto w = cast(NormalizedInt!short)x; assert(w == 25700); // casting to higher precision extends bit-pattern to preserve uniformity 262 | w = -w; assert(w == -25700); // negation works as expected 263 | w *= 2; assert(w == -32767 && w == -1.0); // overflow saturates 264 | w *= -0.5; assert(w == 16384 && w > 0.5); // 0.5 is not exactly representable (odd number of positive integers) 265 | w = w^^0.0; assert(w == 1.0); // pow as expected 266 | 267 | // check floating poing comparisons 268 | static assert(NormalizedInt!ubyte(0xFF) == 1.0); 269 | static assert(NormalizedInt!ubyte(0x00) == 0.0); 270 | static assert(NormalizedInt!ubyte(0x80) > 0.5); 271 | static assert(NormalizedInt!ubyte(0x7F) < 0.5); 272 | 273 | static assert(NormalizedInt!byte(127) == 1.0); 274 | static assert(NormalizedInt!byte(-127) == -1.0); 275 | static assert(NormalizedInt!byte(-128) == -1.0); 276 | static assert(NormalizedInt!byte(0x00) == 0.0); 277 | static assert(NormalizedInt!byte(0x40) > 0.5); 278 | static assert(NormalizedInt!byte(0x3F) < 0.5); 279 | } 280 | 281 | 282 | /** Convert values between normalized integer types. */ 283 | To convertNormInt(To, From)(From i) if (isIntegral!From && isIntegral!To) 284 | { 285 | return cast(To)convertNormBits!(From.sizeof*8, isSigned!From, To.sizeof*8, isSigned!To, Unsigned!To, Unsigned!From)(i); 286 | } 287 | /// 288 | unittest 289 | { 290 | // unsigned -> unsigned 291 | static assert(convertNormInt!ubyte(ushort(0x3765)) == 0x37); 292 | static assert(convertNormInt!ushort(ubyte(0x37)) == 0x3737); 293 | static assert(convertNormInt!uint(ubyte(0x35)) == 0x35353535); 294 | 295 | // signed -> unsigned 296 | static assert(convertNormInt!ubyte(short(-61)) == 0); 297 | static assert(convertNormInt!ubyte(short(0x3795)) == 0x6F); 298 | static assert(convertNormInt!ushort(byte(0x37)) == 0x6EDD); 299 | static assert(convertNormInt!uint(byte(0x35)) == 0x6AD5AB56); 300 | 301 | // unsigned -> signed 302 | static assert(convertNormInt!byte(ushort(0x3765)) == 0x1B); 303 | static assert(convertNormInt!short(ubyte(0x37)) == 0x1B9B); 304 | static assert(convertNormInt!int(ubyte(0x35)) == 0x1A9A9A9A); 305 | 306 | // signed -> signed 307 | static assert(convertNormInt!short(byte(-127)) == -32767); 308 | static assert(convertNormInt!short(byte(-128)) == -32767); 309 | static assert(convertNormInt!byte(short(0x3795)) == 0x37); 310 | static assert(convertNormInt!byte(short(-28672)) == -112); 311 | static assert(convertNormInt!short(byte(0x37)) == 0x376E); 312 | static assert(convertNormInt!short(byte(-109)) == -28123); 313 | } 314 | 315 | /** Convert a float to a normalized integer. */ 316 | To floatToNormInt(To, From)(From f) if (isFloatingPoint!From && isIntegral!To) 317 | { 318 | return cast(To)floatToNormBits!(To.sizeof*8, isSigned!To, Unsigned!To)(f); 319 | } 320 | /// 321 | unittest 322 | { 323 | static assert(floatToNormInt!ubyte(0.5) == 0x80); 324 | } 325 | 326 | /** Convert a normalized integer to a float. */ 327 | To normIntToFloat(To, From)(From i) if (isIntegral!From && isFloatingPoint!To) 328 | { 329 | return normBitsToFloat!(From.sizeof*8, isSigned!From, To)(cast(Unsigned!From)i); 330 | } 331 | /// 332 | unittest 333 | { 334 | static assert(normIntToFloat!(double, ubyte)(0xFF) == 1.0); 335 | } 336 | 337 | package: 338 | 339 | // try and use the preferred float type 340 | // if the int type exceeds the preferred float precision, we'll upgrade the float 341 | template FloatTypeFor(IntType, RequestedFloat = float) 342 | { 343 | static if (IntType.sizeof > 2) 344 | alias FloatTypeFor = double; 345 | else 346 | alias FloatTypeFor = RequestedFloat; 347 | } 348 | 349 | enum BitsUMax(size_t n) = (1L << n)-1; 350 | enum BitsSMax(size_t n) = (1 << (n-1))-1; 351 | enum SignBit(size_t n) = (1 << (n-1)); 352 | 353 | //pragma(inline, true) // Error: cannot inline function 354 | T floatToNormBits(size_t bits, bool signed, T = uint, F)(F f) pure nothrow @nogc @safe if (isUnsigned!T && isFloatingPoint!F) 355 | { 356 | static if(bits == 1) 357 | { 358 | static if (!signed) 359 | return f >= 0.5 ? 1 : 0; 360 | else 361 | return f <= -0.5 ? 1 : 0; 362 | } 363 | else static if (!signed) 364 | { 365 | if(f >= 1) 366 | return BitsUMax!bits; 367 | else if(f <= 0) 368 | return 0; 369 | return cast(T)(f*BitsUMax!bits + 0.5); 370 | } 371 | else 372 | { 373 | if (f >= 0) 374 | { 375 | if(f >= 1) 376 | return BitsSMax!bits; 377 | return cast(T)(f*BitsSMax!bits + 0.5); 378 | } 379 | if(f <= -1) 380 | return -BitsSMax!bits & BitsUMax!bits; 381 | return cast(T)(f*BitsSMax!bits - 0.5) & BitsUMax!bits; 382 | } 383 | } 384 | unittest 385 | { 386 | // float unpacking 387 | static assert(floatToNormBits!(1, false)(0.0) == 0); 388 | static assert(floatToNormBits!(1, false)(0.3) == 0); 389 | static assert(floatToNormBits!(1, false)(0.5) == 1); // round to nearest 390 | static assert(floatToNormBits!(1, false)(1.0) == 1); 391 | static assert(floatToNormBits!(1, false)(2.0) == 1); 392 | static assert(floatToNormBits!(1, false)(-1.0) == 0); 393 | static assert(floatToNormBits!(1, false)(-2.0) == 0); 394 | 395 | static assert(floatToNormBits!(2, false)(0.0) == 0); 396 | static assert(floatToNormBits!(2, false)(0.3) == 1); 397 | static assert(floatToNormBits!(2, false)(0.5) == 2); 398 | static assert(floatToNormBits!(2, false)(1.0) == 3); 399 | static assert(floatToNormBits!(2, false)(2.0) == 3); 400 | static assert(floatToNormBits!(2, false)(-1.0) == 0); 401 | static assert(floatToNormBits!(2, false)(-2.0) == 0); 402 | 403 | static assert(floatToNormBits!(6, false)(0.0) == 0); 404 | static assert(floatToNormBits!(6, false)(0.3) == 19); 405 | static assert(floatToNormBits!(6, false)(0.5) == 32); 406 | static assert(floatToNormBits!(6, false)(1.0) == 63); 407 | static assert(floatToNormBits!(6, false)(2.0) == 63); 408 | static assert(floatToNormBits!(6, false)(-1.0) == 0); 409 | static assert(floatToNormBits!(6, false)(-2.0) == 0); 410 | 411 | static assert(floatToNormBits!(9, true)(0.0) == 0x00); 412 | static assert(floatToNormBits!(9, true)(0.3) == 0x4D); 413 | static assert(floatToNormBits!(9, true)(0.5) == 0x80); 414 | static assert(floatToNormBits!(9, true)(1.0) == 0xFF); 415 | static assert(floatToNormBits!(9, true)(2.0) == 0xFF); 416 | static assert(floatToNormBits!(9, true)(-0.5) == 0x180); 417 | static assert(floatToNormBits!(9, true)(-1.0) == 0x101); 418 | static assert(floatToNormBits!(9, true)(-2.0) == 0x101); 419 | } 420 | 421 | pragma(inline, true) 422 | F normBitsToFloat(size_t bits, bool signed, F = float)(uint v) pure nothrow @nogc @safe if (isFloatingPoint!F) 423 | { 424 | static if (!signed) 425 | return v / F(BitsUMax!bits); 426 | else static if (bits == 1) 427 | return v ? F(-1.0) : F(0.0); 428 | else 429 | { 430 | import std.algorithm.comparison : max; 431 | return max((cast(int)(v << (32 - bits)) >> (32 - bits)) / F(BitsSMax!bits), F(-1)); 432 | } 433 | } 434 | unittest 435 | { 436 | // float unpacking 437 | static assert(normBitsToFloat!(1, false, float)(0) == 0); 438 | static assert(normBitsToFloat!(1, false, float)(1) == 1); 439 | static assert(normBitsToFloat!(1, true, float)(0) == 0); 440 | static assert(normBitsToFloat!(1, true, float)(1) == -1); 441 | 442 | static assert(normBitsToFloat!(2, true, float)(0) == 0); 443 | static assert(normBitsToFloat!(2, true, float)(1) == 1); 444 | static assert(normBitsToFloat!(2, true, float)(2) == -1); 445 | static assert(normBitsToFloat!(2, true, float)(3) == -1); 446 | 447 | static assert(normBitsToFloat!(3, true, float)(0) == 0); 448 | static assert(normBitsToFloat!(3, true, float)(1) == 1.0/3); 449 | static assert(normBitsToFloat!(3, true, float)(2) == 2.0/3); 450 | static assert(normBitsToFloat!(3, true, float)(3) == 1); 451 | static assert(normBitsToFloat!(3, true, float)(4) == -1); 452 | static assert(normBitsToFloat!(3, true, float)(5) == -1); 453 | static assert(normBitsToFloat!(3, true, float)(6) == -2.0/3); 454 | static assert(normBitsToFloat!(3, true, float)(7) == -1.0/3); 455 | } 456 | 457 | pragma(inline, true) 458 | T convertNormBits(size_t srcBits, bool srcSigned, size_t destBits, bool destSigned, T = uint, S)(S v) pure nothrow @nogc @safe if (isUnsigned!T && isUnsigned!S) 459 | { 460 | // TODO: this should be tested for performance. 461 | // we can optimise the small->large conversions with table lookups? 462 | 463 | // hack for 1-bit src 464 | static if (srcBits == 1) 465 | { 466 | static if (!srcSigned && !destSigned) 467 | return v ? BitsUMax!destBits : 0; 468 | else static if (!srcSigned && destSigned) 469 | return v ? BitsSMax!destBits : 0; 470 | else static if (srcSigned && !destSigned) 471 | return 0; // always clamp to zero 472 | else static if (srcSigned && destSigned) 473 | return v ? SignBit!destBits : 0; 474 | } 475 | else static if (!destSigned) 476 | { 477 | static if (!srcSigned) 478 | { 479 | static if (destBits > srcBits) 480 | { 481 | // up conversion is tricky 482 | template BitRepeat(size_t srcWidth, size_t destWidth) 483 | { 484 | template Impl(size_t i) 485 | { 486 | static if (i < srcWidth) 487 | enum Impl = 1 << i; 488 | else 489 | enum Impl = (1 << i) | Impl!(i - srcWidth); 490 | } 491 | enum BitRepeat = Impl!(destWidth - srcWidth); 492 | } 493 | 494 | enum reps = destBits / srcBits; 495 | static if (reps <= 1) 496 | T r = cast(T)(v << (destBits - srcBits)); 497 | else static if (reps == 2) // TODO: benchmark if imul is faster for reps == 2... 498 | T r = cast(T)((v << (destBits - srcBits)) | (v << (destBits - srcBits*2))); 499 | else static if (reps > 2) 500 | T r = cast(T)(v * BitRepeat!(srcBits, destBits)); 501 | static if (destBits%srcBits != 0) 502 | r |= cast(T)(v >> (srcBits - destBits%srcBits)); 503 | return r; 504 | } 505 | else 506 | return cast(T)(v >> (srcBits - destBits)); 507 | } 508 | else 509 | { 510 | // signed -> unsigned 511 | if (v & SignBit!srcBits) // if src is negative, clamp to 0 512 | return 0; 513 | else 514 | return convertNormBits!(srcBits - 1, false, destBits, destSigned, T, S)(v); 515 | } 516 | } 517 | else 518 | { 519 | static if (!srcSigned) 520 | { 521 | // unsigned -> signed 522 | return convertNormBits!(srcBits, false, destBits - 1, false, T, S)(v); 523 | } 524 | else 525 | { 526 | if (v & SignBit!srcBits) 527 | return cast(T)-cast(WorkInt!T)convertNormBits!(srcBits - 1, false, destBits - 1, false, T, S)(((v ^ SignBit!srcBits) == 0 ? cast(S)~cast(WorkInt!S)v : cast(S)-cast(WorkInt!S)v) & BitsSMax!srcBits) & BitsUMax!destBits; 528 | else 529 | return convertNormBits!(srcBits - 1, false, destBits - 1, false, T, S)(v); 530 | } 531 | } 532 | } 533 | unittest 534 | { 535 | // unsigned -> unsigned int 536 | static assert(convertNormBits!(1, false, 8, false, ubyte)(0u) == 0x00); 537 | static assert(convertNormBits!(1, false, 8, false, ubyte)(1u) == 0xFF); 538 | static assert(convertNormBits!(1, false, 30, false, uint)(1u) == 0x3FFFFFFF); 539 | 540 | static assert(convertNormBits!(2, false, 8, false, ubyte)(0u) == 0x00); 541 | static assert(convertNormBits!(2, false, 8, false, ubyte)(1u) == 0x55); 542 | static assert(convertNormBits!(2, false, 4, false, ubyte)(2u) == 0x0A); 543 | static assert(convertNormBits!(2, false, 8, false, ubyte)(3u) == 0xFF); 544 | static assert(convertNormBits!(2, false, 28, false, uint)(2u) == 0x0AAAAAAA); 545 | static assert(convertNormBits!(2, false, 32, false, uint)(3u) == 0xFFFFFFFF); 546 | 547 | static assert(convertNormBits!(3, false, 8, false, ubyte)(0u) == 0x00); // 0b00000000 548 | static assert(convertNormBits!(3, false, 8, false, ubyte)(1u) == 0x24); // 0b00100100 549 | static assert(convertNormBits!(3, false, 8, false, ubyte)(2u) == 0x49); // 0b01001001 550 | static assert(convertNormBits!(3, false, 8, false, ubyte)(3u) == 0x6D); // 0b01101101 551 | static assert(convertNormBits!(3, false, 8, false, ubyte)(4u) == 0x92); // 0b10010010 552 | static assert(convertNormBits!(3, false, 8, false, ubyte)(5u) == 0xB6); // 0b10110110 553 | static assert(convertNormBits!(3, false, 8, false, ubyte)(6u) == 0xDB); // 0b11011011 554 | static assert(convertNormBits!(3, false, 8, false, ubyte)(7u) == 0xFF); // 0b11111111 555 | static assert(convertNormBits!(3, false, 32, false, uint)(4u) == 0x92492492); 556 | static assert(convertNormBits!(3, false, 32, false, uint)(7u) == 0xFFFFFFFF); 557 | 558 | // unsigned -> signed int 559 | static assert(convertNormBits!(1, false, 8, true, ubyte)(0u) == 0x00); 560 | static assert(convertNormBits!(1, false, 8, true, ubyte)(1u) == 0x7F); 561 | static assert(convertNormBits!(1, false, 32, true, uint)(1u) == 0x7FFFFFFF); 562 | 563 | static assert(convertNormBits!(2, false, 8, true, ubyte)(0u) == 0x00); 564 | static assert(convertNormBits!(2, false, 8, true, ubyte)(1u) == 0x2A); 565 | static assert(convertNormBits!(2, false, 8, true, ubyte)(2u) == 0x55); 566 | static assert(convertNormBits!(2, false, 8, true, ubyte)(3u) == 0x7F); 567 | static assert(convertNormBits!(2, false, 32, true, uint)(2u) == 0x55555555); 568 | static assert(convertNormBits!(2, false, 32, true, uint)(3u) == 0x7FFFFFFF); 569 | 570 | static assert(convertNormBits!(3, false, 8, true, ubyte)(0u) == 0x00); // 0b00000000 571 | static assert(convertNormBits!(3, false, 8, true, ubyte)(1u) == 0x12); // 0b00010010 572 | static assert(convertNormBits!(3, false, 8, true, ubyte)(2u) == 0x24); // 0b00100100 573 | static assert(convertNormBits!(3, false, 8, true, ubyte)(3u) == 0x36); // 0b00110110 574 | static assert(convertNormBits!(3, false, 8, true, ubyte)(4u) == 0x49); // 0b01001001 575 | static assert(convertNormBits!(3, false, 8, true, ubyte)(5u) == 0x5B); // 0b01011011 576 | static assert(convertNormBits!(3, false, 8, true, ubyte)(6u) == 0x6D); // 0b01101101 577 | static assert(convertNormBits!(3, false, 8, true, ubyte)(7u) == 0x7F); // 0b01111111 578 | static assert(convertNormBits!(3, false, 32, true, uint)(4u) == 0x49249249); 579 | static assert(convertNormBits!(3, false, 32, true, uint)(7u) == 0x7FFFFFFF); 580 | 581 | // signed -> unsigned int 582 | static assert(convertNormBits!(1, true, 8, false, ubyte)(0u) == 0x00); 583 | static assert(convertNormBits!(1, true, 8, false, ubyte)(1u) == 0x00); 584 | static assert(convertNormBits!(1, true, 32, false, uint)(1u) == 0x00000000); 585 | 586 | static assert(convertNormBits!(2, true, 8, false, ubyte)(0u) == 0x00); 587 | static assert(convertNormBits!(2, true, 8, false, ubyte)(1u) == 0xFF); 588 | static assert(convertNormBits!(2, true, 8, false, ubyte)(2u) == 0x0); 589 | static assert(convertNormBits!(2, true, 8, false, ubyte)(3u) == 0x0); 590 | static assert(convertNormBits!(2, true, 32, false, uint)(1u) == 0xFFFFFFFF); 591 | static assert(convertNormBits!(2, true, 32, false, uint)(3u) == 0x00000000); 592 | 593 | static assert(convertNormBits!(3, true, 8, false, ubyte)(0u) == 0x00); // 0b00000000 594 | static assert(convertNormBits!(3, true, 8, false, ubyte)(1u) == 0x55); // 0b01010101 595 | static assert(convertNormBits!(3, true, 8, false, ubyte)(2u) == 0xAA); // 0b10101010 596 | static assert(convertNormBits!(3, true, 8, false, ubyte)(3u) == 0xFF); // 0b11111111 597 | static assert(convertNormBits!(3, true, 8, false, ubyte)(4u) == 0x00); // 0b00000000 598 | static assert(convertNormBits!(3, true, 8, false, ubyte)(5u) == 0x00); // 0b00000000 599 | static assert(convertNormBits!(3, true, 8, false, ubyte)(6u) == 0x00); // 0b00000000 600 | static assert(convertNormBits!(3, true, 8, false, ubyte)(7u) == 0x00); // 0b00000000 601 | static assert(convertNormBits!(3, true, 32, false, uint)(2u) == 0xAAAAAAAA); 602 | static assert(convertNormBits!(3, true, 32, false, uint)(7u) == 0x00000000); 603 | 604 | // signed -> signed int 605 | static assert(convertNormBits!(1, true, 8, true, ubyte)(0u) == 0x00); 606 | static assert(convertNormBits!(1, true, 8, true, ubyte)(1u) == 0x80); 607 | static assert(convertNormBits!(1, true, 32, true, uint)(1u) == 0x80000000); 608 | 609 | static assert(convertNormBits!(2, true, 8, true, ubyte)(0u) == 0x00); 610 | static assert(convertNormBits!(2, true, 8, true, ubyte)(1u) == 0x7F); 611 | static assert(convertNormBits!(2, true, 8, true, ubyte)(2u) == 0x81); 612 | static assert(convertNormBits!(2, true, 8, true, ubyte)(3u) == 0x81); 613 | static assert(convertNormBits!(2, true, 32, true, uint)(1u) == 0x7FFFFFFF); 614 | static assert(convertNormBits!(2, true, 32, true, uint)(3u) == 0x80000001); 615 | 616 | static assert(convertNormBits!(3, true, 8, true, ubyte)(0u) == 0x00); 617 | static assert(convertNormBits!(3, true, 8, true, ubyte)(1u) == 0x2A); 618 | static assert(convertNormBits!(3, true, 8, true, ubyte)(2u) == 0x55); 619 | static assert(convertNormBits!(3, true, 8, true, ubyte)(3u) == 0x7F); 620 | static assert(convertNormBits!(3, true, 8, true, ubyte)(4u) == 0x81); 621 | static assert(convertNormBits!(3, true, 8, true, ubyte)(5u) == 0x81); 622 | static assert(convertNormBits!(3, true, 8, true, ubyte)(6u) == 0xAB); 623 | static assert(convertNormBits!(3, true, 8, true, ubyte)(7u) == 0xD6); 624 | static assert(convertNormBits!(3, true, 32, true, uint)(2u) == 0x55555555); 625 | static assert(convertNormBits!(3, true, 32, true, uint)(4u) == 0x80000001); 626 | static assert(convertNormBits!(3, true, 32, true, uint)(5u) == 0x80000001); 627 | static assert(convertNormBits!(3, true, 32, true, uint)(7u) == 0xD5555556); 628 | } 629 | 630 | 631 | private: 632 | 633 | template WorkInt(I) 634 | { 635 | static if (I.sizeof < 4) 636 | alias WorkInt = int; // normal integer promotion for small int's 637 | else static if (is(I == ulong)) 638 | alias WorkInt = ulong; // ulong has no larger signed type 639 | else 640 | alias WorkInt = long; // (u)int promotes to long, so we can overflow and saturate instead of wrapping 641 | } 642 | unittest 643 | { 644 | static assert(is(WorkInt!short == int)); 645 | static assert(is(WorkInt!ubyte == int)); 646 | static assert(is(WorkInt!uint == long)); 647 | static assert(is(WorkInt!long == long)); 648 | static assert(is(WorkInt!ulong == ulong)); 649 | } 650 | 651 | // all tests 652 | unittest 653 | { 654 | // construction 655 | static assert(NormalizedInt!ubyte(100) == 100); 656 | 657 | static assert(NormalizedInt!ubyte(1.0) == 255); 658 | static assert(NormalizedInt!ubyte(2.0) == 255); 659 | static assert(NormalizedInt!ubyte(0.5) == 128); 660 | static assert(NormalizedInt!ubyte(-1.0) == 0); 661 | static assert(NormalizedInt!byte(-1.0) == -127); 662 | static assert(NormalizedInt!byte(-2.0) == -127); 663 | 664 | // unary operators 665 | static assert(+NormalizedInt!ubyte(0.5) == 128); 666 | static assert(-NormalizedInt!ubyte(0.5) == 0); 667 | static assert(~NormalizedInt!ubyte(0.5) == 127); 668 | static assert(+NormalizedInt!byte(0.5) == 64); 669 | static assert(-NormalizedInt!byte(0.5) == -64); 670 | static assert(~NormalizedInt!byte(0.5) == -65); 671 | 672 | // binary operators 673 | static assert(NormalizedInt!ubyte(1.0) + NormalizedInt!ubyte(0.5) == 255); 674 | static assert(NormalizedInt!ubyte(1.0) - NormalizedInt!ubyte(0.5) == 127); 675 | static assert(NormalizedInt!ubyte(0.5) - NormalizedInt!ubyte(1.0) == 0); 676 | static assert(NormalizedInt!byte(1.0) + NormalizedInt!byte(0.5) == 127); 677 | static assert(NormalizedInt!byte(1.0) - NormalizedInt!byte(0.5) == 63); 678 | static assert(NormalizedInt!byte(-0.5) - NormalizedInt!byte(1.0) == -127); 679 | 680 | // ubyte (has a fast no-float path) 681 | static assert(NormalizedInt!ubyte(0xFF) * NormalizedInt!ubyte(0xFF) == 255); 682 | static assert(NormalizedInt!ubyte(0xFF) * NormalizedInt!ubyte(0xFE) == 254); 683 | static assert(NormalizedInt!ubyte(0xFF) * NormalizedInt!ubyte(0x80) == 128); 684 | static assert(NormalizedInt!ubyte(0xFF) * NormalizedInt!ubyte(0x40) == 64); 685 | static assert(NormalizedInt!ubyte(0xFF) * NormalizedInt!ubyte(0x02) == 2); 686 | static assert(NormalizedInt!ubyte(0xFF) * NormalizedInt!ubyte(0x01) == 1); 687 | static assert(NormalizedInt!ubyte(0x80) * NormalizedInt!ubyte(0xFF) == 128); 688 | static assert(NormalizedInt!ubyte(0x80) * NormalizedInt!ubyte(0xFE) == 127); 689 | static assert(NormalizedInt!ubyte(0x80) * NormalizedInt!ubyte(0x80) == 64); 690 | static assert(NormalizedInt!ubyte(0x80) * NormalizedInt!ubyte(0x40) == 32); 691 | static assert(NormalizedInt!ubyte(0x80) * NormalizedInt!ubyte(0x02) == 1); 692 | static assert(NormalizedInt!ubyte(0x80) * NormalizedInt!ubyte(0x01) == 0); 693 | static assert(NormalizedInt!ubyte(0x40) * NormalizedInt!ubyte(0xFF) == 64); 694 | static assert(NormalizedInt!ubyte(0x40) * NormalizedInt!ubyte(0xFE) == 63); 695 | static assert(NormalizedInt!ubyte(0x40) * NormalizedInt!ubyte(0x80) == 32); 696 | static assert(NormalizedInt!ubyte(0x40) * NormalizedInt!ubyte(0x40) == 16); 697 | static assert(NormalizedInt!ubyte(0x40) * NormalizedInt!ubyte(0x02) == 0); 698 | static assert(NormalizedInt!ubyte(0x40) * NormalizedInt!ubyte(0x01) == 0); 699 | 700 | // positive byte 701 | static assert(NormalizedInt!byte(cast(byte)0x7F) * NormalizedInt!byte(cast(byte)0x7F) == 127); 702 | static assert(NormalizedInt!byte(cast(byte)0x7F) * NormalizedInt!byte(cast(byte)0x7E) == 126); 703 | static assert(NormalizedInt!byte(cast(byte)0x7F) * NormalizedInt!byte(cast(byte)0x40) == 64); 704 | static assert(NormalizedInt!byte(cast(byte)0x7F) * NormalizedInt!byte(cast(byte)0x02) == 2); 705 | static assert(NormalizedInt!byte(cast(byte)0x7F) * NormalizedInt!byte(cast(byte)0x01) == 1); 706 | static assert(NormalizedInt!byte(cast(byte)0x40) * NormalizedInt!byte(cast(byte)0x7F) == 64); 707 | static assert(NormalizedInt!byte(cast(byte)0x40) * NormalizedInt!byte(cast(byte)0x7E) == 63); 708 | static assert(NormalizedInt!byte(cast(byte)0x40) * NormalizedInt!byte(cast(byte)0x40) == 32); 709 | static assert(NormalizedInt!byte(cast(byte)0x40) * NormalizedInt!byte(cast(byte)0x02) == 1); 710 | static assert(NormalizedInt!byte(cast(byte)0x40) * NormalizedInt!byte(cast(byte)0x01) == 0); 711 | static assert(NormalizedInt!byte(cast(byte)0x20) * NormalizedInt!byte(cast(byte)0x7F) == 32); 712 | static assert(NormalizedInt!byte(cast(byte)0x20) * NormalizedInt!byte(cast(byte)0x7E) == 31); 713 | static assert(NormalizedInt!byte(cast(byte)0x20) * NormalizedInt!byte(cast(byte)0x40) == 16); 714 | static assert(NormalizedInt!byte(cast(byte)0x20) * NormalizedInt!byte(cast(byte)0x02) == 0); 715 | static assert(NormalizedInt!byte(cast(byte)0x20) * NormalizedInt!byte(cast(byte)0x01) == 0); 716 | // negative byte 717 | static assert(NormalizedInt!byte(cast(byte)0x81) * NormalizedInt!byte(cast(byte)0x7F) == -127); 718 | static assert(NormalizedInt!byte(cast(byte)0x81) * NormalizedInt!byte(cast(byte)0x7E) == -126); 719 | static assert(NormalizedInt!byte(cast(byte)0x81) * NormalizedInt!byte(cast(byte)0x40) == -64); 720 | static assert(NormalizedInt!byte(cast(byte)0x81) * NormalizedInt!byte(cast(byte)0x02) == -2); 721 | static assert(NormalizedInt!byte(cast(byte)0x81) * NormalizedInt!byte(cast(byte)0x01) == -1); 722 | static assert(NormalizedInt!byte(cast(byte)0xC0) * NormalizedInt!byte(cast(byte)0x7F) == -64); 723 | static assert(NormalizedInt!byte(cast(byte)0xC0) * NormalizedInt!byte(cast(byte)0x7E) == -63); 724 | static assert(NormalizedInt!byte(cast(byte)0xC0) * NormalizedInt!byte(cast(byte)0x40) == -32); 725 | static assert(NormalizedInt!byte(cast(byte)0xC0) * NormalizedInt!byte(cast(byte)0x02) == -1); 726 | static assert(NormalizedInt!byte(cast(byte)0xC0) * NormalizedInt!byte(cast(byte)0x01) == 0); 727 | static assert(NormalizedInt!byte(cast(byte)0xE0) * NormalizedInt!byte(cast(byte)0x7F) == -32); 728 | static assert(NormalizedInt!byte(cast(byte)0xE0) * NormalizedInt!byte(cast(byte)0x7E) == -31); 729 | static assert(NormalizedInt!byte(cast(byte)0xE0) * NormalizedInt!byte(cast(byte)0x40) == -16); 730 | static assert(NormalizedInt!byte(cast(byte)0xE0) * NormalizedInt!byte(cast(byte)0x02) == 0); 731 | static assert(NormalizedInt!byte(cast(byte)0xE0) * NormalizedInt!byte(cast(byte)0x01) == 0); 732 | 733 | // ushort (has a fast no-float path) 734 | static assert(NormalizedInt!ushort(0xFFFF) * NormalizedInt!ushort(0xFFFF) == 0xFFFF); 735 | static assert(NormalizedInt!ushort(0xFFFF) * NormalizedInt!ushort(0xFFFE) == 0xFFFE); 736 | static assert(NormalizedInt!ushort(0xFFFF) * NormalizedInt!ushort(0x8000) == 0x8000); 737 | static assert(NormalizedInt!ushort(0xFFFF) * NormalizedInt!ushort(0x4000) == 0x4000); 738 | static assert(NormalizedInt!ushort(0xFFFF) * NormalizedInt!ushort(0x0002) == 0x0002); 739 | static assert(NormalizedInt!ushort(0xFFFF) * NormalizedInt!ushort(0x0001) == 0x0001); 740 | static assert(NormalizedInt!ushort(0x8000) * NormalizedInt!ushort(0xFFFF) == 0x8000); 741 | static assert(NormalizedInt!ushort(0x8000) * NormalizedInt!ushort(0xFFFE) == 0x7FFF); 742 | static assert(NormalizedInt!ushort(0x8000) * NormalizedInt!ushort(0x8000) == 0x4000); 743 | static assert(NormalizedInt!ushort(0x8000) * NormalizedInt!ushort(0x4000) == 0x2000); 744 | static assert(NormalizedInt!ushort(0x8000) * NormalizedInt!ushort(0x0002) == 0x0001); 745 | static assert(NormalizedInt!ushort(0x8000) * NormalizedInt!ushort(0x0001) == 0x0000); 746 | static assert(NormalizedInt!ushort(0x4000) * NormalizedInt!ushort(0xFFFF) == 0x4000); 747 | static assert(NormalizedInt!ushort(0x4000) * NormalizedInt!ushort(0xFFFE) == 0x3FFF); 748 | static assert(NormalizedInt!ushort(0x4000) * NormalizedInt!ushort(0x8000) == 0x2000); 749 | static assert(NormalizedInt!ushort(0x4000) * NormalizedInt!ushort(0x4000) == 0x1000); 750 | static assert(NormalizedInt!ushort(0x4000) * NormalizedInt!ushort(0x0002) == 0x0000); 751 | static assert(NormalizedInt!ushort(0x4000) * NormalizedInt!ushort(0x0001) == 0x0000); 752 | 753 | // uint 754 | static assert(NormalizedInt!uint(0xFFFFFFFF) * NormalizedInt!uint(0xFFFFFFFF) == 0xFFFFFFFF); 755 | static assert(NormalizedInt!uint(0xFFFFFFFF) * NormalizedInt!uint(0xFFFFFFFE) == 0xFFFFFFFE); 756 | static assert(NormalizedInt!uint(0xFFFFFFFF) * NormalizedInt!uint(0x80000000) == 0x80000000); 757 | static assert(NormalizedInt!uint(0xFFFFFFFF) * NormalizedInt!uint(0x40000000) == 0x40000000); 758 | static assert(NormalizedInt!uint(0xFFFFFFFF) * NormalizedInt!uint(0x00000002) == 0x00000002); 759 | static assert(NormalizedInt!uint(0xFFFFFFFF) * NormalizedInt!uint(0x00000001) == 0x00000001); 760 | static assert(NormalizedInt!uint(0x80000000) * NormalizedInt!uint(0xFFFFFFFF) == 0x80000000); 761 | static assert(NormalizedInt!uint(0x80000000) * NormalizedInt!uint(0xFFFFFFFE) == 0x7FFFFFFF); 762 | static assert(NormalizedInt!uint(0x80000000) * NormalizedInt!uint(0x80000000) == 0x40000000); 763 | static assert(NormalizedInt!uint(0x80000000) * NormalizedInt!uint(0x40000000) == 0x20000000); 764 | static assert(NormalizedInt!uint(0x80000000) * NormalizedInt!uint(0x00000002) == 0x00000001); 765 | static assert(NormalizedInt!uint(0x80000000) * NormalizedInt!uint(0x00000001) == 0x00000000); 766 | static assert(NormalizedInt!uint(0x40000000) * NormalizedInt!uint(0xFFFFFFFF) == 0x40000000); 767 | static assert(NormalizedInt!uint(0x40000000) * NormalizedInt!uint(0xFFFFFFFE) == 0x3FFFFFFF); 768 | static assert(NormalizedInt!uint(0x40000000) * NormalizedInt!uint(0x80000000) == 0x20000000); 769 | static assert(NormalizedInt!uint(0x40000000) * NormalizedInt!uint(0x40000000) == 0x10000000); 770 | static assert(NormalizedInt!uint(0x40000000) * NormalizedInt!uint(0x00000002) == 0x00000000); 771 | static assert(NormalizedInt!uint(0x40000000) * NormalizedInt!uint(0x00000001) == 0x00000000); 772 | 773 | // int 774 | static assert(NormalizedInt!int(0x80000001) * NormalizedInt!int(0x7FFFFFFF) == 0x80000001); 775 | static assert(NormalizedInt!int(0x80000001) * NormalizedInt!int(0x7FFFFFFE) == 0x80000002); 776 | static assert(NormalizedInt!int(0x80000001) * NormalizedInt!int(0x40000000) == 0xC0000000); 777 | static assert(NormalizedInt!int(0x80000001) * NormalizedInt!int(0x00000002) == 0xFFFFFFFE); 778 | static assert(NormalizedInt!int(0x80000001) * NormalizedInt!int(0x00000001) == 0xFFFFFFFF); 779 | static assert(NormalizedInt!int(0xC0000000) * NormalizedInt!int(0x7FFFFFFF) == 0xC0000000); 780 | static assert(NormalizedInt!int(0xC0000000) * NormalizedInt!int(0x7FFFFFFE) == 0xC0000001); 781 | static assert(NormalizedInt!int(0xC0000000) * NormalizedInt!int(0x40000000) == 0xE0000000); 782 | static assert(NormalizedInt!int(0xC0000000) * NormalizedInt!int(0x00000002) == 0xFFFFFFFF); 783 | static assert(NormalizedInt!int(0xC0000000) * NormalizedInt!int(0x00000001) == 0x00000000); 784 | static assert(NormalizedInt!int(0xE0000000) * NormalizedInt!int(0x7FFFFFFF) == 0xE0000000); 785 | static assert(NormalizedInt!int(0xE0000000) * NormalizedInt!int(0x7FFFFFFE) == 0xE0000001); 786 | static assert(NormalizedInt!int(0xE0000000) * NormalizedInt!int(0x40000000) == 0xF0000000); 787 | static assert(NormalizedInt!int(0xE0000000) * NormalizedInt!int(0x00000002) == 0x00000000); 788 | static assert(NormalizedInt!int(0xE0000000) * NormalizedInt!int(0x00000001) == 0x00000000); 789 | 790 | static assert(NormalizedInt!ubyte(0x80) / NormalizedInt!ubyte(0xFF) == 0x80); 791 | static assert(NormalizedInt!ubyte(0x80) / NormalizedInt!ubyte(0x80) == 0xFF); 792 | 793 | static assert(NormalizedInt!ubyte(0x80) % NormalizedInt!ubyte(0xFF) == 0x80); 794 | static assert(NormalizedInt!ubyte(0x80) % NormalizedInt!ubyte(0x80) == 0); 795 | 796 | // binary vs int 797 | static assert(NormalizedInt!ubyte(0x40) * 2 == 0x80); 798 | static assert(NormalizedInt!ubyte(0x80) * 2 == 0xFF); 799 | static assert(NormalizedInt!ubyte(0xFF) * 2 == 0xFF); 800 | static assert(NormalizedInt!byte(32) * 2 == 64); 801 | static assert(NormalizedInt!byte(64) * 2 == 127); 802 | static assert(NormalizedInt!byte(127) * 2 == 127); 803 | static assert(NormalizedInt!byte(-32) * 2 == -64); 804 | static assert(NormalizedInt!byte(-64) * 2 == -127); 805 | static assert(NormalizedInt!byte(-127) * 2 == -127); 806 | static assert(NormalizedInt!byte(-32) * -2 == 64); 807 | static assert(NormalizedInt!byte(-64) * -2 == 127); 808 | static assert(NormalizedInt!uint(0xFFFFFFFF) * 2 == 0xFFFFFFFF); 809 | static assert(NormalizedInt!int(0x7FFFFFFF) * -2 == 0x80000001); 810 | 811 | static assert(NormalizedInt!ubyte(0x40) / 2 == 0x20); 812 | static assert(NormalizedInt!ubyte(0xFF) / 2 == 0x7F); 813 | 814 | static assert(NormalizedInt!ubyte(0x40) % 2 == 0); 815 | static assert(NormalizedInt!ubyte(0xFF) % 2 == 1); 816 | 817 | // binary vs float 818 | static assert(NormalizedInt!ubyte(0x40) * 2.0 == 0x80); 819 | static assert(NormalizedInt!ubyte(0x80) * 2.0 == 0xFF); 820 | static assert(NormalizedInt!ubyte(0xFF) * 2.0 == 0xFF); 821 | static assert(NormalizedInt!byte(32) * 2.0 == 64); 822 | static assert(NormalizedInt!byte(64) * 2.0 == 127); 823 | static assert(NormalizedInt!byte(127) * 2.0 == 127); 824 | static assert(NormalizedInt!byte(-32) * 2.0 == -64); 825 | static assert(NormalizedInt!byte(-64) * 2.0 == -127); 826 | static assert(NormalizedInt!byte(-127) * 2.0 == -127); 827 | static assert(NormalizedInt!byte(-32) * -2.0 == 64); 828 | static assert(NormalizedInt!byte(-64) * -2.0 == 127); 829 | static assert(NormalizedInt!int(0x7FFFFFFF) * -2.0 == 0x80000001); 830 | 831 | static assert(NormalizedInt!ubyte(0x40) * 0.5 == 0x20); 832 | static assert(NormalizedInt!ubyte(0x80) * 0.5 == 0x40); 833 | static assert(NormalizedInt!ubyte(0xFF) * 0.5 == 0x80); 834 | static assert(NormalizedInt!byte(32) * 0.5 == 16); 835 | static assert(NormalizedInt!byte(64) * 0.5 == 32); 836 | static assert(NormalizedInt!byte(127) * 0.5 == 64); 837 | static assert(NormalizedInt!byte(-32) * 0.5 == -16); 838 | static assert(NormalizedInt!byte(-64) * 0.5 == -32); 839 | static assert(NormalizedInt!byte(-127) * 0.5 == -64); 840 | static assert(NormalizedInt!byte(-32) * -0.5 == 16); 841 | static assert(NormalizedInt!byte(-64) * -0.5 == 32); 842 | 843 | static assert(NormalizedInt!ubyte(0xFF) / 2.0 == 0x80); 844 | static assert(NormalizedInt!ubyte(0xFF) % 0.5 == 0); 845 | 846 | // bitwise operators 847 | static assert((NormalizedInt!uint(0x80) | NormalizedInt!uint(0x08)) == 0x88); 848 | static assert((NormalizedInt!uint(0xF0) & NormalizedInt!uint(0x81)) == 0x80); 849 | static assert((NormalizedInt!uint(0x81) ^ NormalizedInt!uint(0x80)) == 0x01); 850 | 851 | static assert(NormalizedInt!uint(0x08000000) << 2 == 0x20000000); 852 | static assert(NormalizedInt!int(0x80000000) >> 7 == 0xFF000000); 853 | static assert(NormalizedInt!int(0x80000000) >>> 7 == 0x01000000); 854 | 855 | // casts 856 | // up cast 857 | static assert(cast(NormalizedInt!ushort)NormalizedInt!ubyte(0xFF) == 0xFFFF); 858 | static assert(cast(NormalizedInt!ushort)NormalizedInt!ubyte(0x81) == 0x8181); 859 | 860 | // down cast 861 | static assert(cast(NormalizedInt!ubyte)NormalizedInt!ushort(0xFFFF) == 0xFF); 862 | static assert(cast(NormalizedInt!ubyte)NormalizedInt!ushort(0x9F37) == 0x9F); 863 | 864 | // signed -> unsigned 865 | static assert(cast(NormalizedInt!ubyte)NormalizedInt!byte(127) == 0xFF); 866 | static assert(cast(NormalizedInt!ushort)NormalizedInt!byte(127) == 0xFFFF); 867 | static assert(cast(NormalizedInt!ubyte)NormalizedInt!byte(-127) == 0); 868 | static assert(cast(NormalizedInt!ubyte)NormalizedInt!byte(-128) == 0); 869 | static assert(cast(NormalizedInt!ushort)NormalizedInt!byte(-127) == 0); 870 | static assert(cast(NormalizedInt!ubyte)NormalizedInt!short(-32767) == 0); 871 | static assert(cast(NormalizedInt!ubyte)NormalizedInt!short(-32768) == 0); 872 | 873 | // unsigned -> signed 874 | static assert(cast(NormalizedInt!byte)NormalizedInt!ubyte(0xFF) == 0x7F); 875 | static assert(cast(NormalizedInt!byte)NormalizedInt!ubyte(0x83) == 0x41); 876 | static assert(cast(NormalizedInt!short)NormalizedInt!ubyte(0xFF) == 0x7FFF); 877 | static assert(cast(NormalizedInt!short)NormalizedInt!ubyte(0x83) == 0x41C1); 878 | static assert(cast(NormalizedInt!byte)NormalizedInt!ushort(0xFFFF) == 0x7F); 879 | static assert(cast(NormalizedInt!byte)NormalizedInt!ushort(0x83F7) == 0x41); 880 | 881 | // signed -> signed 882 | static assert(cast(NormalizedInt!short)NormalizedInt!byte(127) == 32767); 883 | static assert(cast(NormalizedInt!byte)NormalizedInt!short(32767) == 127); 884 | static assert(cast(NormalizedInt!short)NormalizedInt!byte(-127) == -32767); 885 | static assert(cast(NormalizedInt!byte)NormalizedInt!short(-32767) == -127); 886 | } 887 | --------------------------------------------------------------------------------