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 
toXYZD50(SkMatrix44 * toXYZ_D50) const15 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 
SkColorSpace_Base(sk_sp<SkData> profileData)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  */
xyz_almost_equal(const SkMatrix44 & toXYZD50,const float * standard)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 
MakeRGB(SkGammaNamed gammaNamed,const SkMatrix44 & toXYZD50)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 
MakeRGB(RenderTargetGamma gamma,const SkMatrix44 & toXYZD50)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 
MakeRGB(const SkColorSpaceTransferFn & coeffs,const SkMatrix44 & toXYZD50)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 
MakeRGB(RenderTargetGamma gamma,Gamut gamut)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 
MakeRGB(const SkColorSpaceTransferFn & coeffs,Gamut gamut)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 
MakeNamed(Named named)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 
MakeSRGB()247 sk_sp<SkColorSpace> SkColorSpace::MakeSRGB() {
248     return SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kSRGB_Named);
249 }
250 
MakeSRGBLinear()251 sk_sp<SkColorSpace> SkColorSpace::MakeSRGBLinear() {
252     return SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kSRGBLinear_Named);
253 }
254 
255 ///////////////////////////////////////////////////////////////////////////////////////////////////
256 
gammaCloseToSRGB() const257 bool SkColorSpace::gammaCloseToSRGB() const {
258     return as_CSB(this)->onGammaCloseToSRGB();
259 }
260 
gammaIsLinear() const261 bool SkColorSpace::gammaIsLinear() const {
262     return as_CSB(this)->onGammaIsLinear();
263 }
264 
isNumericalTransferFn(SkColorSpaceTransferFn * fn) const265 bool SkColorSpace::isNumericalTransferFn(SkColorSpaceTransferFn* fn) const {
266     return as_CSB(this)->onIsNumericalTransferFn(fn);
267 }
268 
toXYZD50(SkMatrix44 * toXYZD50) const269 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 
isSRGB() const279 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 
PackColorSpaceHeader314     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 
writeToMemory(void * memory) const338 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 
serialize() const434 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 
Deserialize(const void * data,size_t length)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 
Equals(const SkColorSpace * src,const SkColorSpace * dst)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 
invert() const560 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