1 /* 2 * Copyright 2016 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "SkColorSpace.h" 9 #include "SkColorSpace_XYZ.h" 10 #include "SkColorSpacePriv.h" 11 #include "SkPoint3.h" 12 13 bool SkColorSpacePrimaries::toXYZD50(SkMatrix44* toXYZ_D50) const { 14 if (!is_zero_to_one(fRX) || !is_zero_to_one(fRY) || 15 !is_zero_to_one(fGX) || !is_zero_to_one(fGY) || 16 !is_zero_to_one(fBX) || !is_zero_to_one(fBY) || 17 !is_zero_to_one(fWX) || !is_zero_to_one(fWY)) 18 { 19 return false; 20 } 21 22 // First, we need to convert xy values (primaries) to XYZ. 23 SkMatrix primaries; 24 primaries.setAll( fRX, fGX, fBX, 25 fRY, fGY, fBY, 26 1.0f - fRX - fRY, 1.0f - fGX - fGY, 1.0f - fBX - fBY); 27 SkMatrix primariesInv; 28 if (!primaries.invert(&primariesInv)) { 29 return false; 30 } 31 32 // Assumes that Y is 1.0f. 33 SkVector3 wXYZ = SkVector3::Make(fWX / fWY, 1.0f, (1.0f - fWX - fWY) / fWY); 34 SkVector3 XYZ; 35 XYZ.fX = primariesInv[0] * wXYZ.fX + primariesInv[1] * wXYZ.fY + primariesInv[2] * wXYZ.fZ; 36 XYZ.fY = primariesInv[3] * wXYZ.fX + primariesInv[4] * wXYZ.fY + primariesInv[5] * wXYZ.fZ; 37 XYZ.fZ = primariesInv[6] * wXYZ.fX + primariesInv[7] * wXYZ.fY + primariesInv[8] * wXYZ.fZ; 38 SkMatrix toXYZ; 39 toXYZ.setAll(XYZ.fX, 0.0f, 0.0f, 40 0.0f, XYZ.fY, 0.0f, 41 0.0f, 0.0f, XYZ.fZ); 42 toXYZ.postConcat(primaries); 43 44 // Now convert toXYZ matrix to toXYZD50. 45 SkVector3 wXYZD50 = SkVector3::Make(0.96422f, 1.0f, 0.82521f); 46 47 // Calculate the chromatic adaptation matrix. We will use the Bradford method, thus 48 // the matrices below. The Bradford method is used by Adobe and is widely considered 49 // to be the best. 50 SkMatrix mA, mAInv; 51 mA.setAll(+0.8951f, +0.2664f, -0.1614f, 52 -0.7502f, +1.7135f, +0.0367f, 53 +0.0389f, -0.0685f, +1.0296f); 54 mAInv.setAll(+0.9869929f, -0.1470543f, +0.1599627f, 55 +0.4323053f, +0.5183603f, +0.0492912f, 56 -0.0085287f, +0.0400428f, +0.9684867f); 57 58 SkVector3 srcCone; 59 srcCone.fX = mA[0] * wXYZ.fX + mA[1] * wXYZ.fY + mA[2] * wXYZ.fZ; 60 srcCone.fY = mA[3] * wXYZ.fX + mA[4] * wXYZ.fY + mA[5] * wXYZ.fZ; 61 srcCone.fZ = mA[6] * wXYZ.fX + mA[7] * wXYZ.fY + mA[8] * wXYZ.fZ; 62 SkVector3 dstCone; 63 dstCone.fX = mA[0] * wXYZD50.fX + mA[1] * wXYZD50.fY + mA[2] * wXYZD50.fZ; 64 dstCone.fY = mA[3] * wXYZD50.fX + mA[4] * wXYZD50.fY + mA[5] * wXYZD50.fZ; 65 dstCone.fZ = mA[6] * wXYZD50.fX + mA[7] * wXYZD50.fY + mA[8] * wXYZD50.fZ; 66 67 SkMatrix DXToD50; 68 DXToD50.setIdentity(); 69 DXToD50[0] = dstCone.fX / srcCone.fX; 70 DXToD50[4] = dstCone.fY / srcCone.fY; 71 DXToD50[8] = dstCone.fZ / srcCone.fZ; 72 DXToD50.postConcat(mAInv); 73 DXToD50.preConcat(mA); 74 75 toXYZ.postConcat(DXToD50); 76 toXYZ_D50->set3x3(toXYZ[0], toXYZ[3], toXYZ[6], 77 toXYZ[1], toXYZ[4], toXYZ[7], 78 toXYZ[2], toXYZ[5], toXYZ[8]); 79 return true; 80 } 81 82 /////////////////////////////////////////////////////////////////////////////////////////////////// 83 84 /** 85 * Checks if our toXYZ matrix is a close match to a known color gamut. 86 * 87 * @param toXYZD50 transformation matrix deduced from profile data 88 * @param standard 3x3 canonical transformation matrix 89 */ 90 static bool xyz_almost_equal(const SkMatrix44& toXYZD50, const float* standard) { 91 return color_space_almost_equal(toXYZD50.getFloat(0, 0), standard[0]) && 92 color_space_almost_equal(toXYZD50.getFloat(0, 1), standard[1]) && 93 color_space_almost_equal(toXYZD50.getFloat(0, 2), standard[2]) && 94 color_space_almost_equal(toXYZD50.getFloat(1, 0), standard[3]) && 95 color_space_almost_equal(toXYZD50.getFloat(1, 1), standard[4]) && 96 color_space_almost_equal(toXYZD50.getFloat(1, 2), standard[5]) && 97 color_space_almost_equal(toXYZD50.getFloat(2, 0), standard[6]) && 98 color_space_almost_equal(toXYZD50.getFloat(2, 1), standard[7]) && 99 color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) && 100 color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) && 101 color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) && 102 color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) && 103 color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) && 104 color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) && 105 color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) && 106 color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f); 107 } 108 109 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50) 110 { 111 switch (gammaNamed) { 112 case kSRGB_SkGammaNamed: 113 if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) { 114 return SkColorSpace::MakeSRGB(); 115 } 116 break; 117 #ifdef SK_SUPPORT_LEGACY_ADOBE_XYZ 118 case k2Dot2Curve_SkGammaNamed: 119 if (xyz_almost_equal(toXYZD50, gAdobeRGB_toXYZD50)) { 120 SkMatrix44 adobe44(SkMatrix44::kUninitialized_Constructor); 121 adobe44.set3x3RowMajorf(gAdobeRGB_toXYZD50); 122 return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(k2Dot2Curve_SkGammaNamed, adobe44)); 123 } 124 break; 125 #endif 126 case kLinear_SkGammaNamed: 127 if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) { 128 return SkColorSpace::MakeSRGBLinear(); 129 } 130 break; 131 case kNonStandard_SkGammaNamed: 132 // This is not allowed. 133 return nullptr; 134 default: 135 break; 136 } 137 138 return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed, toXYZD50)); 139 } 140 141 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(RenderTargetGamma gamma, const SkMatrix44& toXYZD50) { 142 switch (gamma) { 143 case kLinear_RenderTargetGamma: 144 return SkColorSpace::MakeRGB(kLinear_SkGammaNamed, toXYZD50); 145 case kSRGB_RenderTargetGamma: 146 return SkColorSpace::MakeRGB(kSRGB_SkGammaNamed, toXYZD50); 147 default: 148 return nullptr; 149 } 150 } 151 152 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const SkColorSpaceTransferFn& coeffs, 153 const SkMatrix44& toXYZD50) { 154 if (!is_valid_transfer_fn(coeffs)) { 155 return nullptr; 156 } 157 158 if (is_almost_srgb(coeffs)) { 159 return SkColorSpace::MakeRGB(kSRGB_SkGammaNamed, toXYZD50); 160 } 161 162 if (is_almost_2dot2(coeffs)) { 163 return SkColorSpace::MakeRGB(k2Dot2Curve_SkGammaNamed, toXYZD50); 164 } 165 166 if (is_almost_linear(coeffs)) { 167 return SkColorSpace::MakeRGB(kLinear_SkGammaNamed, toXYZD50); 168 } 169 170 void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn)); 171 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(3)); 172 SkColorSpaceTransferFn* fn = SkTAddOffset<SkColorSpaceTransferFn>(memory, sizeof(SkGammas)); 173 *fn = coeffs; 174 SkGammas::Data data; 175 data.fParamOffset = 0; 176 for (int channel = 0; channel < 3; ++channel) { 177 gammas->fType[channel] = SkGammas::Type::kParam_Type; 178 gammas->fData[channel] = data; 179 } 180 return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed, 181 std::move(gammas), toXYZD50, nullptr)); 182 } 183 184 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(RenderTargetGamma gamma, Gamut gamut) { 185 SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor); 186 to_xyz_d50(&toXYZD50, gamut); 187 return SkColorSpace::MakeRGB(gamma, toXYZD50); 188 } 189 190 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const SkColorSpaceTransferFn& coeffs, Gamut gamut) { 191 SkMatrix44 toXYZD50(SkMatrix44::kUninitialized_Constructor); 192 to_xyz_d50(&toXYZD50, gamut); 193 return SkColorSpace::MakeRGB(coeffs, toXYZD50); 194 } 195 196 static SkColorSpace* singleton_colorspace(SkGammaNamed gamma, const float to_xyz[9]) { 197 SkMatrix44 m44(SkMatrix44::kUninitialized_Constructor); 198 m44.set3x3RowMajorf(to_xyz); 199 (void)m44.getType(); // Force typemask to be computed to avoid races. 200 return new SkColorSpace_XYZ(gamma, m44); 201 } 202 203 static SkColorSpace* srgb() { 204 static SkColorSpace* cs = singleton_colorspace(kSRGB_SkGammaNamed, gSRGB_toXYZD50); 205 return cs; 206 } 207 static SkColorSpace* srgb_linear() { 208 static SkColorSpace* cs = singleton_colorspace(kLinear_SkGammaNamed, gSRGB_toXYZD50); 209 return cs; 210 } 211 212 sk_sp<SkColorSpace> SkColorSpace::MakeSRGB() { 213 return sk_ref_sp(srgb()); 214 } 215 216 sk_sp<SkColorSpace> SkColorSpace::MakeSRGBLinear() { 217 return sk_ref_sp(srgb_linear()); 218 } 219 220 /////////////////////////////////////////////////////////////////////////////////////////////////// 221 222 SkColorSpace::Type SkColorSpace::type() const { 223 const SkMatrix44* m = this->toXYZD50(); 224 if (m) { 225 return m->isScale() ? kGray_Type : kRGB_Type; 226 } 227 return this->onIsCMYK() ? kCMYK_Type : kRGB_Type; 228 } 229 230 SkGammaNamed SkColorSpace::gammaNamed() const { 231 return this->onGammaNamed(); 232 } 233 234 bool SkColorSpace::gammaCloseToSRGB() const { 235 return this->onGammaCloseToSRGB(); 236 } 237 238 bool SkColorSpace::gammaIsLinear() const { 239 return this->onGammaIsLinear(); 240 } 241 242 bool SkColorSpace::isNumericalTransferFn(SkColorSpaceTransferFn* fn) const { 243 return this->onIsNumericalTransferFn(fn); 244 } 245 246 bool SkColorSpace::toXYZD50(SkMatrix44* toXYZD50) const { 247 const SkMatrix44* matrix = this->onToXYZD50(); 248 if (matrix) { 249 *toXYZD50 = *matrix; 250 return true; 251 } 252 253 return false; 254 } 255 256 const SkMatrix44* SkColorSpace::toXYZD50() const { 257 return this->onToXYZD50(); 258 } 259 260 const SkMatrix44* SkColorSpace::fromXYZD50() const { 261 return this->onFromXYZD50(); 262 } 263 264 uint32_t SkColorSpace::toXYZD50Hash() const { 265 return this->onToXYZD50Hash(); 266 } 267 268 bool SkColorSpace::isSRGB() const { 269 return srgb() == this; 270 } 271 272 /////////////////////////////////////////////////////////////////////////////////////////////////// 273 274 enum Version { 275 k0_Version, // Initial version, header + flags for matrix and profile 276 }; 277 278 enum NamedColorSpace { 279 kSRGB_NamedColorSpace, 280 // No longer a singleton, preserved to support reading data from branches m65 and older 281 kAdobeRGB_NamedColorSpace, 282 kSRGBLinear_NamedColorSpace, 283 }; 284 285 struct ColorSpaceHeader { 286 /** 287 * It is only valid to set zero or one flags. 288 * Setting multiple flags is invalid. 289 */ 290 291 /** 292 * If kMatrix_Flag is set, we will write 12 floats after the header. 293 */ 294 static constexpr uint8_t kMatrix_Flag = 1 << 0; 295 296 /** 297 * If kICC_Flag is set, we will write an ICC profile after the header. 298 * The ICC profile will be written as a uint32 size, followed immediately 299 * by the data (padded to 4 bytes). 300 */ 301 static constexpr uint8_t kICC_Flag = 1 << 1; 302 303 /** 304 * If kTransferFn_Flag is set, we will write 19 floats after the header. 305 * The first seven represent the transfer fn, and the next twelve are the 306 * matrix. 307 */ 308 static constexpr uint8_t kTransferFn_Flag = 1 << 3; 309 310 static ColorSpaceHeader Pack(Version version, uint8_t named, uint8_t gammaNamed, uint8_t flags) 311 { 312 ColorSpaceHeader header; 313 314 SkASSERT(k0_Version == version); 315 header.fVersion = (uint8_t) version; 316 317 SkASSERT(named <= kSRGBLinear_NamedColorSpace); 318 header.fNamed = (uint8_t) named; 319 320 SkASSERT(gammaNamed <= kNonStandard_SkGammaNamed); 321 header.fGammaNamed = (uint8_t) gammaNamed; 322 323 SkASSERT(flags <= kTransferFn_Flag); 324 header.fFlags = flags; 325 return header; 326 } 327 328 uint8_t fVersion; // Always zero 329 uint8_t fNamed; // Must be a SkColorSpace::Named 330 uint8_t fGammaNamed; // Must be a SkGammaNamed 331 uint8_t fFlags; 332 }; 333 334 size_t SkColorSpace::writeToMemory(void* memory) const { 335 // Start by trying the serialization fast path. If we haven't saved ICC profile data, 336 // we must have a profile that we can serialize easily. 337 if (!this->onProfileData()) { 338 // Profile data is mandatory for A2B0 color spaces, so we must be XYZ. 339 SkASSERT(this->toXYZD50()); 340 // If we have a named profile, only write the enum. 341 const SkGammaNamed gammaNamed = this->gammaNamed(); 342 if (this == srgb()) { 343 if (memory) { 344 *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack( 345 k0_Version, kSRGB_NamedColorSpace, gammaNamed, 0); 346 } 347 return sizeof(ColorSpaceHeader); 348 } else if (this == srgb_linear()) { 349 if (memory) { 350 *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack( 351 k0_Version, kSRGBLinear_NamedColorSpace, gammaNamed, 0); 352 } 353 return sizeof(ColorSpaceHeader); 354 } 355 356 // If we have a named gamma, write the enum and the matrix. 357 switch (gammaNamed) { 358 case kSRGB_SkGammaNamed: 359 case k2Dot2Curve_SkGammaNamed: 360 case kLinear_SkGammaNamed: { 361 if (memory) { 362 *((ColorSpaceHeader*) memory) = 363 ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed, 364 ColorSpaceHeader::kMatrix_Flag); 365 memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader)); 366 this->toXYZD50()->as3x4RowMajorf((float*) memory); 367 } 368 return sizeof(ColorSpaceHeader) + 12 * sizeof(float); 369 } 370 default: { 371 SkColorSpaceTransferFn transferFn; 372 SkAssertResult(this->isNumericalTransferFn(&transferFn)); 373 374 if (memory) { 375 *((ColorSpaceHeader*) memory) = 376 ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed, 377 ColorSpaceHeader::kTransferFn_Flag); 378 memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader)); 379 380 *(((float*) memory) + 0) = transferFn.fA; 381 *(((float*) memory) + 1) = transferFn.fB; 382 *(((float*) memory) + 2) = transferFn.fC; 383 *(((float*) memory) + 3) = transferFn.fD; 384 *(((float*) memory) + 4) = transferFn.fE; 385 *(((float*) memory) + 5) = transferFn.fF; 386 *(((float*) memory) + 6) = transferFn.fG; 387 memory = SkTAddOffset<void>(memory, 7 * sizeof(float)); 388 389 this->toXYZD50()->as3x4RowMajorf((float*) memory); 390 } 391 392 return sizeof(ColorSpaceHeader) + 19 * sizeof(float); 393 } 394 } 395 } 396 397 // Otherwise, serialize the ICC data. 398 size_t profileSize = this->onProfileData()->size(); 399 if (SkAlign4(profileSize) != (uint32_t) SkAlign4(profileSize)) { 400 return 0; 401 } 402 403 if (memory) { 404 *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(k0_Version, 0, 405 kNonStandard_SkGammaNamed, 406 ColorSpaceHeader::kICC_Flag); 407 memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader)); 408 409 *((uint32_t*) memory) = (uint32_t) SkAlign4(profileSize); 410 memory = SkTAddOffset<void>(memory, sizeof(uint32_t)); 411 412 memcpy(memory, this->onProfileData()->data(), profileSize); 413 memset(SkTAddOffset<void>(memory, profileSize), 0, SkAlign4(profileSize) - profileSize); 414 } 415 return sizeof(ColorSpaceHeader) + sizeof(uint32_t) + SkAlign4(profileSize); 416 } 417 418 sk_sp<SkData> SkColorSpace::serialize() const { 419 size_t size = this->writeToMemory(nullptr); 420 if (0 == size) { 421 return nullptr; 422 } 423 424 sk_sp<SkData> data = SkData::MakeUninitialized(size); 425 this->writeToMemory(data->writable_data()); 426 return data; 427 } 428 429 sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) { 430 if (length < sizeof(ColorSpaceHeader)) { 431 return nullptr; 432 } 433 434 ColorSpaceHeader header = *((const ColorSpaceHeader*) data); 435 data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader)); 436 length -= sizeof(ColorSpaceHeader); 437 if (0 == header.fFlags) { 438 switch ((NamedColorSpace)header.fNamed) { 439 case kSRGB_NamedColorSpace: 440 return SkColorSpace::MakeSRGB(); 441 case kSRGBLinear_NamedColorSpace: 442 return SkColorSpace::MakeSRGBLinear(); 443 case kAdobeRGB_NamedColorSpace: 444 return SkColorSpace::MakeRGB(g2Dot2_TransferFn, SkColorSpace::kAdobeRGB_Gamut); 445 } 446 } 447 448 switch ((SkGammaNamed) header.fGammaNamed) { 449 case kSRGB_SkGammaNamed: 450 case k2Dot2Curve_SkGammaNamed: 451 case kLinear_SkGammaNamed: { 452 if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) { 453 return nullptr; 454 } 455 456 SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); 457 toXYZ.set3x4RowMajorf((const float*) data); 458 return SkColorSpace::MakeRGB((SkGammaNamed) header.fGammaNamed, toXYZ); 459 } 460 default: 461 break; 462 } 463 464 switch (header.fFlags) { 465 case ColorSpaceHeader::kICC_Flag: { 466 if (length < sizeof(uint32_t)) { 467 return nullptr; 468 } 469 470 uint32_t profileSize = *((uint32_t*) data); 471 data = SkTAddOffset<const void>(data, sizeof(uint32_t)); 472 length -= sizeof(uint32_t); 473 if (length < profileSize) { 474 return nullptr; 475 } 476 477 return MakeICC(data, profileSize); 478 } 479 case ColorSpaceHeader::kTransferFn_Flag: { 480 if (length < 19 * sizeof(float)) { 481 return nullptr; 482 } 483 484 SkColorSpaceTransferFn transferFn; 485 transferFn.fA = *(((const float*) data) + 0); 486 transferFn.fB = *(((const float*) data) + 1); 487 transferFn.fC = *(((const float*) data) + 2); 488 transferFn.fD = *(((const float*) data) + 3); 489 transferFn.fE = *(((const float*) data) + 4); 490 transferFn.fF = *(((const float*) data) + 5); 491 transferFn.fG = *(((const float*) data) + 6); 492 data = SkTAddOffset<const void>(data, 7 * sizeof(float)); 493 494 SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); 495 toXYZ.set3x4RowMajorf((const float*) data); 496 return SkColorSpace::MakeRGB(transferFn, toXYZ); 497 } 498 default: 499 return nullptr; 500 } 501 } 502 503 bool SkColorSpace::Equals(const SkColorSpace* src, const SkColorSpace* dst) { 504 if (src == dst) { 505 return true; 506 } 507 508 if (!src || !dst) { 509 return false; 510 } 511 512 const SkData* srcData = src->onProfileData(); 513 const SkData* dstData = dst->onProfileData(); 514 if (srcData || dstData) { 515 if (srcData && dstData) { 516 return srcData->size() == dstData->size() && 517 0 == memcmp(srcData->data(), dstData->data(), srcData->size()); 518 } 519 520 return false; 521 } 522 523 // Profiles are mandatory for A2B0 color spaces, so these must be XYZ 524 if (src->gammaNamed() != dst->gammaNamed()) { 525 return false; 526 } 527 528 switch (src->gammaNamed()) { 529 case kSRGB_SkGammaNamed: 530 case k2Dot2Curve_SkGammaNamed: 531 case kLinear_SkGammaNamed: 532 if (src->toXYZD50Hash() == dst->toXYZD50Hash()) { 533 SkASSERT(*src->toXYZD50() == *dst->toXYZD50() && "Hash collision"); 534 return true; 535 } 536 return false; 537 default: 538 // It is unlikely that we will reach this case. 539 sk_sp<SkData> serializedSrcData = src->serialize(); 540 sk_sp<SkData> serializedDstData = dst->serialize(); 541 return serializedSrcData->size() == serializedDstData->size() && 542 0 == memcmp(serializedSrcData->data(), serializedDstData->data(), 543 serializedSrcData->size()); 544 } 545 } 546 547 SkColorSpaceTransferFn SkColorSpaceTransferFn::invert() const { 548 // Original equation is: y = (ax + b)^g + e for x >= d 549 // y = cx + f otherwise 550 // 551 // so 1st inverse is: (y - e)^(1/g) = ax + b 552 // x = ((y - e)^(1/g) - b) / a 553 // 554 // which can be re-written as: x = (1/a)(y - e)^(1/g) - b/a 555 // x = ((1/a)^g)^(1/g) * (y - e)^(1/g) - b/a 556 // x = ([(1/a)^g]y + [-((1/a)^g)e]) ^ [1/g] + [-b/a] 557 // 558 // and 2nd inverse is: x = (y - f) / c 559 // which can be re-written as: x = [1/c]y + [-f/c] 560 // 561 // and now both can be expressed in terms of the same parametric form as the 562 // original - parameters are enclosed in square brackets. 563 SkColorSpaceTransferFn inv = { 0, 0, 0, 0, 0, 0, 0 }; 564 565 // find inverse for linear segment (if possible) 566 if (!transfer_fn_almost_equal(0.f, fC)) { 567 inv.fC = 1.f / fC; 568 inv.fF = -fF / fC; 569 } else { 570 // otherwise assume it should be 0 as it is the lower segment 571 // as y = f is a constant function 572 } 573 574 // find inverse for the other segment (if possible) 575 if (transfer_fn_almost_equal(0.f, fA) || transfer_fn_almost_equal(0.f, fG)) { 576 // otherwise assume it should be 1 as it is the top segment 577 // as you can't invert the constant functions y = b^g + e, or y = 1 + e 578 inv.fG = 1.f; 579 inv.fE = 1.f; 580 } else { 581 inv.fG = 1.f / fG; 582 inv.fA = powf(1.f / fA, fG); 583 inv.fB = -inv.fA * fE; 584 inv.fE = -fB / fA; 585 } 586 inv.fD = fC * fD + fF; 587 588 return inv; 589 } 590