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 
toXYZD50(SkMatrix44 * toXYZ_D50) const13 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  */
xyz_almost_equal(const SkMatrix44 & toXYZD50,const float * standard)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 
MakeRGB(SkGammaNamed gammaNamed,const SkMatrix44 & toXYZD50)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 
MakeRGB(RenderTargetGamma gamma,const SkMatrix44 & toXYZD50)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 
MakeRGB(const SkColorSpaceTransferFn & coeffs,const SkMatrix44 & toXYZD50)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 
MakeRGB(RenderTargetGamma gamma,Gamut gamut)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 
MakeRGB(const SkColorSpaceTransferFn & coeffs,Gamut gamut)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 
singleton_colorspace(SkGammaNamed gamma,const float to_xyz[9])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 
srgb()203 static SkColorSpace* srgb() {
204     static SkColorSpace* cs = singleton_colorspace(kSRGB_SkGammaNamed, gSRGB_toXYZD50);
205     return cs;
206 }
srgb_linear()207 static SkColorSpace* srgb_linear() {
208     static SkColorSpace* cs = singleton_colorspace(kLinear_SkGammaNamed, gSRGB_toXYZD50);
209     return cs;
210 }
211 
MakeSRGB()212 sk_sp<SkColorSpace> SkColorSpace::MakeSRGB() {
213     return sk_ref_sp(srgb());
214 }
215 
MakeSRGBLinear()216 sk_sp<SkColorSpace> SkColorSpace::MakeSRGBLinear() {
217     return sk_ref_sp(srgb_linear());
218 }
219 
220 ///////////////////////////////////////////////////////////////////////////////////////////////////
221 
type() const222 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 
gammaNamed() const230 SkGammaNamed SkColorSpace::gammaNamed() const {
231     return this->onGammaNamed();
232 }
233 
gammaCloseToSRGB() const234 bool SkColorSpace::gammaCloseToSRGB() const {
235     return this->onGammaCloseToSRGB();
236 }
237 
gammaIsLinear() const238 bool SkColorSpace::gammaIsLinear() const {
239     return this->onGammaIsLinear();
240 }
241 
isNumericalTransferFn(SkColorSpaceTransferFn * fn) const242 bool SkColorSpace::isNumericalTransferFn(SkColorSpaceTransferFn* fn) const {
243     return this->onIsNumericalTransferFn(fn);
244 }
245 
toXYZD50(SkMatrix44 * toXYZD50) const246 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 
toXYZD50() const256 const SkMatrix44* SkColorSpace::toXYZD50() const {
257     return this->onToXYZD50();
258 }
259 
fromXYZD50() const260 const SkMatrix44* SkColorSpace::fromXYZD50() const {
261     return this->onFromXYZD50();
262 }
263 
toXYZD50Hash() const264 uint32_t SkColorSpace::toXYZD50Hash() const {
265     return this->onToXYZD50Hash();
266 }
267 
isSRGB() const268 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 
PackColorSpaceHeader310     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 
writeToMemory(void * memory) const334 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 
serialize() const418 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 
Deserialize(const void * data,size_t length)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 
Equals(const SkColorSpace * src,const SkColorSpace * dst)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 
invert() const547 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