SkColorSpace.cpp revision bbf251bf225489a0939fff6df938035a290f4d16
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
15bool 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
86SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkData> profileData)
87    : fProfileData(std::move(profileData))
88{}
89
90static constexpr float gSRGB_toXYZD50[] {
91    0.4358f, 0.3853f, 0.1430f,    // Rx, Gx, Bx
92    0.2224f, 0.7170f, 0.0606f,    // Ry, Gy, Gz
93    0.0139f, 0.0971f, 0.7139f,    // Rz, Gz, Bz
94};
95
96static constexpr float gAdobeRGB_toXYZD50[] {
97    0.6098f, 0.2052f, 0.1492f,    // Rx, Gx, Bx
98    0.3111f, 0.6257f, 0.0632f,    // Ry, Gy, By
99    0.0195f, 0.0609f, 0.7448f,    // Rz, Gz, Bz
100};
101
102/**
103 *  Checks if our toXYZ matrix is a close match to a known color gamut.
104 *
105 *  @param toXYZD50 transformation matrix deduced from profile data
106 *  @param standard 3x3 canonical transformation matrix
107 */
108static bool xyz_almost_equal(const SkMatrix44& toXYZD50, const float* standard) {
109    return color_space_almost_equal(toXYZD50.getFloat(0, 0), standard[0]) &&
110           color_space_almost_equal(toXYZD50.getFloat(0, 1), standard[1]) &&
111           color_space_almost_equal(toXYZD50.getFloat(0, 2), standard[2]) &&
112           color_space_almost_equal(toXYZD50.getFloat(1, 0), standard[3]) &&
113           color_space_almost_equal(toXYZD50.getFloat(1, 1), standard[4]) &&
114           color_space_almost_equal(toXYZD50.getFloat(1, 2), standard[5]) &&
115           color_space_almost_equal(toXYZD50.getFloat(2, 0), standard[6]) &&
116           color_space_almost_equal(toXYZD50.getFloat(2, 1), standard[7]) &&
117           color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) &&
118           color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) &&
119           color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) &&
120           color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) &&
121           color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) &&
122           color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) &&
123           color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) &&
124           color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f);
125}
126
127sk_sp<SkColorSpace> SkColorSpace::NewRGB(const float values[3], const SkMatrix44& toXYZD50) {
128    if (0.0f > values[0] || 0.0f > values[1] || 0.0f > values[2]) {
129        return nullptr;
130    }
131
132    SkGammaNamed gammaNamed = kNonStandard_SkGammaNamed;
133    if (color_space_almost_equal(2.2f, values[0]) &&
134            color_space_almost_equal(2.2f, values[1]) &&
135            color_space_almost_equal(2.2f, values[2])) {
136        gammaNamed = k2Dot2Curve_SkGammaNamed;
137    } else if (color_space_almost_equal(1.0f, values[0]) &&
138            color_space_almost_equal(1.0f, values[1]) &&
139            color_space_almost_equal(1.0f, values[2])) {
140        gammaNamed = kLinear_SkGammaNamed;
141    }
142
143    if (kNonStandard_SkGammaNamed == gammaNamed) {
144        sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas());
145        gammas->fRedType = SkGammas::Type::kValue_Type;
146        gammas->fGreenType = SkGammas::Type::kValue_Type;
147        gammas->fBlueType = SkGammas::Type::kValue_Type;
148        gammas->fRedData.fValue = values[0];
149        gammas->fGreenData.fValue = values[1];
150        gammas->fBlueData.fValue = values[2];
151        return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed,
152                                                        gammas, toXYZD50, nullptr));
153    }
154
155    return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50);
156}
157
158sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50) {
159    switch (gammaNamed) {
160        case kSRGB_SkGammaNamed:
161            if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
162                return SkColorSpace::NewNamed(kSRGB_Named);
163            }
164            break;
165        case k2Dot2Curve_SkGammaNamed:
166            if (xyz_almost_equal(toXYZD50, gAdobeRGB_toXYZD50)) {
167                return SkColorSpace::NewNamed(kAdobeRGB_Named);
168            }
169            break;
170        case kLinear_SkGammaNamed:
171            if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
172                return SkColorSpace::NewNamed(kSRGBLinear_Named);
173            }
174            break;
175        case kNonStandard_SkGammaNamed:
176            // This is not allowed.
177            return nullptr;
178        default:
179            break;
180    }
181
182    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed, toXYZD50));
183}
184
185sk_sp<SkColorSpace> SkColorSpace::NewRGB(RenderTargetGamma gamma, const SkMatrix44& toXYZD50) {
186    switch (gamma) {
187        case kLinear_RenderTargetGamma:
188            return SkColorSpace_Base::NewRGB(kLinear_SkGammaNamed, toXYZD50);
189        case kSRGB_RenderTargetGamma:
190            return SkColorSpace_Base::NewRGB(kSRGB_SkGammaNamed, toXYZD50);
191        default:
192            return nullptr;
193    }
194}
195
196sk_sp<SkColorSpace> SkColorSpace::NewRGB(const SkColorSpaceTransferFn& coeffs,
197                                         const SkMatrix44& toXYZD50) {
198    if (!is_valid_transfer_fn(coeffs)) {
199        return nullptr;
200    }
201
202    if (is_almost_srgb(coeffs)) {
203        return SkColorSpace::NewRGB(kSRGB_RenderTargetGamma, toXYZD50);
204    }
205
206    if (is_almost_2dot2(coeffs)) {
207        return SkColorSpace_Base::NewRGB(k2Dot2Curve_SkGammaNamed, toXYZD50);
208    }
209
210    void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn));
211    sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas());
212    SkColorSpaceTransferFn* fn = SkTAddOffset<SkColorSpaceTransferFn>(memory, sizeof(SkGammas));
213    *fn = coeffs;
214    gammas->fRedType = SkGammas::Type::kParam_Type;
215    gammas->fGreenType = SkGammas::Type::kParam_Type;
216    gammas->fBlueType = SkGammas::Type::kParam_Type;
217
218    SkGammas::Data data;
219    data.fParamOffset = 0;
220    gammas->fRedData = data;
221    gammas->fGreenData = data;
222    gammas->fBlueData = data;
223    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed,
224                                                    std::move(gammas), toXYZD50, nullptr));
225}
226
227static SkColorSpace* gAdobeRGB;
228static SkColorSpace* gSRGB;
229static SkColorSpace* gSRGBLinear;
230
231sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
232    static SkOnce sRGBOnce;
233    static SkOnce adobeRGBOnce;
234    static SkOnce sRGBLinearOnce;
235
236    switch (named) {
237        case kSRGB_Named: {
238            sRGBOnce([] {
239                SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
240                srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50);
241
242                // Force the mutable type mask to be computed.  This avoids races.
243                (void)srgbToxyzD50.getType();
244                gSRGB = new SkColorSpace_XYZ(kSRGB_SkGammaNamed, srgbToxyzD50);
245            });
246            return sk_ref_sp<SkColorSpace>(gSRGB);
247        }
248        case kAdobeRGB_Named: {
249            adobeRGBOnce([] {
250                SkMatrix44 adobergbToxyzD50(SkMatrix44::kUninitialized_Constructor);
251                adobergbToxyzD50.set3x3RowMajorf(gAdobeRGB_toXYZD50);
252
253                // Force the mutable type mask to be computed.  This avoids races.
254                (void)adobergbToxyzD50.getType();
255                gAdobeRGB = new SkColorSpace_XYZ(k2Dot2Curve_SkGammaNamed, adobergbToxyzD50);
256            });
257            return sk_ref_sp<SkColorSpace>(gAdobeRGB);
258        }
259        case kSRGBLinear_Named: {
260            sRGBLinearOnce([] {
261                SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
262                srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50);
263
264                // Force the mutable type mask to be computed.  This avoids races.
265                (void)srgbToxyzD50.getType();
266                gSRGBLinear = new SkColorSpace_XYZ(kLinear_SkGammaNamed, srgbToxyzD50);
267            });
268            return sk_ref_sp<SkColorSpace>(gSRGBLinear);
269        }
270        default:
271            break;
272    }
273    return nullptr;
274}
275
276///////////////////////////////////////////////////////////////////////////////////////////////////
277
278bool SkColorSpace::gammaCloseToSRGB() const {
279    return as_CSB(this)->onGammaCloseToSRGB();
280}
281
282bool SkColorSpace::gammaIsLinear() const {
283    return as_CSB(this)->onGammaIsLinear();
284}
285
286///////////////////////////////////////////////////////////////////////////////////////////////////
287
288enum Version {
289    k0_Version, // Initial version, header + flags for matrix and profile
290};
291
292struct ColorSpaceHeader {
293    /**
294     *  It is only valid to set zero or one flags.
295     *  Setting multiple flags is invalid.
296     */
297
298    /**
299     *  If kMatrix_Flag is set, we will write 12 floats after the header.
300     */
301    static constexpr uint8_t kMatrix_Flag     = 1 << 0;
302
303    /**
304     *  If kICC_Flag is set, we will write an ICC profile after the header.
305     *  The ICC profile will be written as a uint32 size, followed immediately
306     *  by the data (padded to 4 bytes).
307     */
308    static constexpr uint8_t kICC_Flag        = 1 << 1;
309
310    /**
311     *  If kFloatGamma_Flag is set, we will write 15 floats after the header.
312     *  The first three are the gamma values, and the next twelve are the
313     *  matrix.
314     */
315    static constexpr uint8_t kFloatGamma_Flag = 1 << 2;
316
317    /**
318     *  If kTransferFn_Flag is set, we will write 19 floats after the header.
319     *  The first seven represent the transfer fn, and the next twelve are the
320     *  matrix.
321     */
322    static constexpr uint8_t kTransferFn_Flag = 1 << 3;
323
324    static ColorSpaceHeader Pack(Version version, uint8_t named, uint8_t gammaNamed, uint8_t flags)
325    {
326        ColorSpaceHeader header;
327
328        SkASSERT(k0_Version == version);
329        header.fVersion = (uint8_t) version;
330
331        SkASSERT(named <= SkColorSpace::kSRGBLinear_Named);
332        header.fNamed = (uint8_t) named;
333
334        SkASSERT(gammaNamed <= kNonStandard_SkGammaNamed);
335        header.fGammaNamed = (uint8_t) gammaNamed;
336
337        SkASSERT(flags <= kTransferFn_Flag);
338        header.fFlags = flags;
339        return header;
340    }
341
342    uint8_t fVersion;    // Always zero
343    uint8_t fNamed;      // Must be a SkColorSpace::Named
344    uint8_t fGammaNamed; // Must be a SkGammaNamed
345    uint8_t fFlags;      // Some combination of the flags listed above
346};
347
348size_t SkColorSpace::writeToMemory(void* memory) const {
349    // Start by trying the serialization fast path.  If we haven't saved ICC profile data,
350    // we must have a profile that we can serialize easily.
351    if (!as_CSB(this)->fProfileData) {
352        // Profile data is mandatory for A2B0 color spaces.
353        SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(this)->type());
354        const SkColorSpace_XYZ* thisXYZ = static_cast<const SkColorSpace_XYZ*>(this);
355        // If we have a named profile, only write the enum.
356        const SkGammaNamed gammaNamed = thisXYZ->gammaNamed();
357        if (this == gSRGB) {
358            if (memory) {
359                *((ColorSpaceHeader*) memory) =
360                        ColorSpaceHeader::Pack(k0_Version, kSRGB_Named, gammaNamed, 0);
361            }
362            return sizeof(ColorSpaceHeader);
363        } else if (this == gAdobeRGB) {
364            if (memory) {
365                *((ColorSpaceHeader*) memory) =
366                        ColorSpaceHeader::Pack(k0_Version, kAdobeRGB_Named, gammaNamed, 0);
367            }
368            return sizeof(ColorSpaceHeader);
369        } else if (this == gSRGBLinear) {
370            if (memory) {
371                *((ColorSpaceHeader*) memory) =
372                        ColorSpaceHeader::Pack(k0_Version, kSRGBLinear_Named, gammaNamed, 0);
373            }
374            return sizeof(ColorSpaceHeader);
375        }
376
377        // If we have a named gamma, write the enum and the matrix.
378        switch (gammaNamed) {
379            case kSRGB_SkGammaNamed:
380            case k2Dot2Curve_SkGammaNamed:
381            case kLinear_SkGammaNamed: {
382                if (memory) {
383                    *((ColorSpaceHeader*) memory) =
384                            ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed,
385                                                   ColorSpaceHeader::kMatrix_Flag);
386                    memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
387                    thisXYZ->toXYZD50()->as3x4RowMajorf((float*) memory);
388                }
389                return sizeof(ColorSpaceHeader) + 12 * sizeof(float);
390            }
391            default:
392                const SkGammas* gammas = thisXYZ->gammas();
393                SkASSERT(gammas);
394                if (gammas->isValue(0) && gammas->isValue(1) && gammas->isValue(2)) {
395                    if (memory) {
396                        *((ColorSpaceHeader*) memory) =
397                                ColorSpaceHeader::Pack(k0_Version, 0, thisXYZ->fGammaNamed,
398                                                       ColorSpaceHeader::kFloatGamma_Flag);
399                        memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
400
401                        *(((float*) memory) + 0) = gammas->fRedData.fValue;
402                        *(((float*) memory) + 1) = gammas->fGreenData.fValue;
403                        *(((float*) memory) + 2) = gammas->fBlueData.fValue;
404                        memory = SkTAddOffset<void>(memory, 3 * sizeof(float));
405
406                        thisXYZ->fToXYZD50.as3x4RowMajorf((float*) memory);
407                    }
408
409                    return sizeof(ColorSpaceHeader) + 15 * sizeof(float);
410                } else {
411                    SkASSERT(gammas->isParametric(0));
412                    SkASSERT(gammas->isParametric(1));
413                    SkASSERT(gammas->isParametric(2));
414                    SkASSERT(gammas->data(0) == gammas->data(1));
415                    SkASSERT(gammas->data(0) == gammas->data(2));
416
417                    if (memory) {
418                        *((ColorSpaceHeader*) memory) =
419                                ColorSpaceHeader::Pack(k0_Version, 0, thisXYZ->fGammaNamed,
420                                                       ColorSpaceHeader::kTransferFn_Flag);
421                        memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
422
423                        *(((float*) memory) + 0) = gammas->params(0).fA;
424                        *(((float*) memory) + 1) = gammas->params(0).fB;
425                        *(((float*) memory) + 2) = gammas->params(0).fC;
426                        *(((float*) memory) + 3) = gammas->params(0).fD;
427                        *(((float*) memory) + 4) = gammas->params(0).fE;
428                        *(((float*) memory) + 5) = gammas->params(0).fF;
429                        *(((float*) memory) + 6) = gammas->params(0).fG;
430                        memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
431
432                        thisXYZ->fToXYZD50.as3x4RowMajorf((float*) memory);
433                    }
434
435                    return sizeof(ColorSpaceHeader) + 19 * sizeof(float);
436                }
437        }
438    }
439
440    // Otherwise, serialize the ICC data.
441    size_t profileSize = as_CSB(this)->fProfileData->size();
442    if (SkAlign4(profileSize) != (uint32_t) SkAlign4(profileSize)) {
443        return 0;
444    }
445
446    if (memory) {
447        *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(k0_Version, 0,
448                                                               kNonStandard_SkGammaNamed,
449                                                               ColorSpaceHeader::kICC_Flag);
450        memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
451
452        *((uint32_t*) memory) = (uint32_t) SkAlign4(profileSize);
453        memory = SkTAddOffset<void>(memory, sizeof(uint32_t));
454
455        memcpy(memory, as_CSB(this)->fProfileData->data(), profileSize);
456        memset(SkTAddOffset<void>(memory, profileSize), 0, SkAlign4(profileSize) - profileSize);
457    }
458    return sizeof(ColorSpaceHeader) + sizeof(uint32_t) + SkAlign4(profileSize);
459}
460
461sk_sp<SkData> SkColorSpace::serialize() const {
462    size_t size = this->writeToMemory(nullptr);
463    if (0 == size) {
464        return nullptr;
465    }
466
467    sk_sp<SkData> data = SkData::MakeUninitialized(size);
468    this->writeToMemory(data->writable_data());
469    return data;
470}
471
472sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
473    if (length < sizeof(ColorSpaceHeader)) {
474        return nullptr;
475    }
476
477    ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
478    data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
479    length -= sizeof(ColorSpaceHeader);
480    if (0 == header.fFlags) {
481        return NewNamed((Named) header.fNamed);
482    }
483
484    switch ((SkGammaNamed) header.fGammaNamed) {
485        case kSRGB_SkGammaNamed:
486        case k2Dot2Curve_SkGammaNamed:
487        case kLinear_SkGammaNamed: {
488            if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) {
489                return nullptr;
490            }
491
492            SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
493            toXYZ.set3x4RowMajorf((const float*) data);
494            return SkColorSpace_Base::NewRGB((SkGammaNamed) header.fGammaNamed, toXYZ);
495        }
496        default:
497            break;
498    }
499
500    switch (header.fFlags) {
501        case ColorSpaceHeader::kICC_Flag: {
502            if (length < sizeof(uint32_t)) {
503                return nullptr;
504            }
505
506            uint32_t profileSize = *((uint32_t*) data);
507            data = SkTAddOffset<const void>(data, sizeof(uint32_t));
508            length -= sizeof(uint32_t);
509            if (length < profileSize) {
510                return nullptr;
511            }
512
513            return NewICC(data, profileSize);
514        }
515        case ColorSpaceHeader::kFloatGamma_Flag: {
516            if (length < 15 * sizeof(float)) {
517                return nullptr;
518            }
519
520            float gammas[3];
521            gammas[0] = *(((const float*) data) + 0);
522            gammas[1] = *(((const float*) data) + 1);
523            gammas[2] = *(((const float*) data) + 2);
524            data = SkTAddOffset<const void>(data, 3 * sizeof(float));
525
526            SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
527            toXYZ.set3x4RowMajorf((const float*) data);
528            return SkColorSpace::NewRGB(gammas, toXYZ);
529        }
530        case ColorSpaceHeader::kTransferFn_Flag: {
531            if (length < 19 * sizeof(float)) {
532                return nullptr;
533            }
534
535            SkColorSpaceTransferFn transferFn;
536            transferFn.fA = *(((const float*) data) + 0);
537            transferFn.fB = *(((const float*) data) + 1);
538            transferFn.fC = *(((const float*) data) + 2);
539            transferFn.fD = *(((const float*) data) + 3);
540            transferFn.fE = *(((const float*) data) + 4);
541            transferFn.fF = *(((const float*) data) + 5);
542            transferFn.fG = *(((const float*) data) + 6);
543            data = SkTAddOffset<const void>(data, 7 * sizeof(float));
544
545            SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
546            toXYZ.set3x4RowMajorf((const float*) data);
547            return SkColorSpace::NewRGB(transferFn, toXYZ);
548        }
549        default:
550            return nullptr;
551    }
552}
553
554bool SkColorSpace::Equals(const SkColorSpace* src, const SkColorSpace* dst) {
555    if (src == dst) {
556        return true;
557    }
558
559    if (!src || !dst) {
560        return false;
561    }
562
563    SkData* srcData = as_CSB(src)->fProfileData.get();
564    SkData* dstData = as_CSB(dst)->fProfileData.get();
565    if (srcData || dstData) {
566        if (srcData && dstData) {
567            return srcData->size() == dstData->size() &&
568                   0 == memcmp(srcData->data(), dstData->data(), srcData->size());
569        }
570
571        return false;
572    }
573
574    // profiles are mandatory for A2B0 color spaces
575    SkASSERT(as_CSB(src)->type() == SkColorSpace_Base::Type::kXYZ);
576    const SkColorSpace_XYZ* srcXYZ = static_cast<const SkColorSpace_XYZ*>(src);
577    const SkColorSpace_XYZ* dstXYZ = static_cast<const SkColorSpace_XYZ*>(dst);
578
579    if (srcXYZ->gammaNamed() != dstXYZ->gammaNamed()) {
580        return false;
581    }
582
583    switch (srcXYZ->gammaNamed()) {
584        case kSRGB_SkGammaNamed:
585        case k2Dot2Curve_SkGammaNamed:
586        case kLinear_SkGammaNamed:
587            if (srcXYZ->toXYZD50Hash() == dstXYZ->toXYZD50Hash()) {
588                SkASSERT(*srcXYZ->toXYZD50() == *dstXYZ->toXYZD50() && "Hash collision");
589                return true;
590            }
591            return false;
592        default:
593            // It is unlikely that we will reach this case.
594            sk_sp<SkData> serializedSrcData = src->serialize();
595            sk_sp<SkData> serializedDstData = dst->serialize();
596            return serializedSrcData->size() == serializedDstData->size() &&
597                   0 == memcmp(serializedSrcData->data(), serializedDstData->data(),
598                               serializedSrcData->size());
599    }
600}
601