The OpenD Programming Language

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 }