├── .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 |
--------------------------------------------------------------------------------