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
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 */
96static 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
115sk_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
143sk_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
154sk_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
186sk_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
192sk_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
198static SkColorSpace* gAdobeRGB;
199static SkColorSpace* gSRGB;
200static SkColorSpace* gSRGBLinear;
201
202sk_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
247sk_sp<SkColorSpace> SkColorSpace::MakeSRGB() {
248    return SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kSRGB_Named);
249}
250
251sk_sp<SkColorSpace> SkColorSpace::MakeSRGBLinear() {
252    return SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kSRGBLinear_Named);
253}
254
255///////////////////////////////////////////////////////////////////////////////////////////////////
256
257bool SkColorSpace::gammaCloseToSRGB() const {
258    return as_CSB(this)->onGammaCloseToSRGB();
259}
260
261bool SkColorSpace::gammaIsLinear() const {
262    return as_CSB(this)->onGammaIsLinear();
263}
264
265bool SkColorSpace::isNumericalTransferFn(SkColorSpaceTransferFn* fn) const {
266    return as_CSB(this)->onIsNumericalTransferFn(fn);
267}
268
269bool 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
279bool SkColorSpace::isSRGB() const {
280    return gSRGB == this;
281}
282
283///////////////////////////////////////////////////////////////////////////////////////////////////
284
285enum Version {
286    k0_Version, // Initial version, header + flags for matrix and profile
287};
288
289struct 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
338size_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
434sk_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
445sk_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
512bool 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
560SkColorSpaceTransferFn 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