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_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