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 }