Home | History | Annotate | Download | only in core
      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