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