SkColorSpace.cpp revision 94d7872ab3b0a32675b8e826637665fd3ada5b35
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 "SkColorSpacePriv.h"
11#include "SkOnce.h"
12
13SkColorSpace::SkColorSpace(const SkMatrix44& toXYZD50)
14    : fToXYZD50(toXYZD50)
15{}
16
17SkColorSpace_Base::SkColorSpace_Base(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50)
18    : INHERITED(toXYZD50)
19    , fGammaNamed(gammaNamed)
20    , fGammas(nullptr)
21    , fProfileData(nullptr)
22    , fFromXYZD50(SkMatrix44::kUninitialized_Constructor)
23{}
24
25SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkColorLookUpTable> colorLUT, SkGammaNamed gammaNamed,
26                                     sk_sp<SkGammas> gammas, const SkMatrix44& toXYZD50,
27                                     sk_sp<SkData> profileData)
28    : INHERITED(toXYZD50)
29    , fColorLUT(std::move(colorLUT))
30    , fGammaNamed(gammaNamed)
31    , fGammas(std::move(gammas))
32    , fProfileData(std::move(profileData))
33    , fFromXYZD50(SkMatrix44::kUninitialized_Constructor)
34{}
35
36static constexpr float gSRGB_toXYZD50[] {
37    0.4358f, 0.3853f, 0.1430f,    // Rx, Gx, Bx
38    0.2224f, 0.7170f, 0.0606f,    // Ry, Gy, Gz
39    0.0139f, 0.0971f, 0.7139f,    // Rz, Gz, Bz
40};
41
42static constexpr float gAdobeRGB_toXYZD50[] {
43    0.6098f, 0.2052f, 0.1492f,    // Rx, Gx, Bx
44    0.3111f, 0.6257f, 0.0632f,    // Ry, Gy, By
45    0.0195f, 0.0609f, 0.7448f,    // Rz, Gz, Bz
46};
47
48/**
49 *  Checks if our toXYZ matrix is a close match to a known color gamut.
50 *
51 *  @param toXYZD50 transformation matrix deduced from profile data
52 *  @param standard 3x3 canonical transformation matrix
53 */
54static bool xyz_almost_equal(const SkMatrix44& toXYZD50, const float* standard) {
55    return color_space_almost_equal(toXYZD50.getFloat(0, 0), standard[0]) &&
56           color_space_almost_equal(toXYZD50.getFloat(0, 1), standard[1]) &&
57           color_space_almost_equal(toXYZD50.getFloat(0, 2), standard[2]) &&
58           color_space_almost_equal(toXYZD50.getFloat(1, 0), standard[3]) &&
59           color_space_almost_equal(toXYZD50.getFloat(1, 1), standard[4]) &&
60           color_space_almost_equal(toXYZD50.getFloat(1, 2), standard[5]) &&
61           color_space_almost_equal(toXYZD50.getFloat(2, 0), standard[6]) &&
62           color_space_almost_equal(toXYZD50.getFloat(2, 1), standard[7]) &&
63           color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) &&
64           color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) &&
65           color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) &&
66           color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) &&
67           color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) &&
68           color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) &&
69           color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) &&
70           color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f);
71}
72
73sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(const float values[3], const SkMatrix44& toXYZD50) {
74    if (0.0f > values[0] || 0.0f > values[1] || 0.0f > values[2]) {
75        return nullptr;
76    }
77
78    SkGammaNamed gammaNamed = kNonStandard_SkGammaNamed;
79    if (color_space_almost_equal(2.2f, values[0]) &&
80            color_space_almost_equal(2.2f, values[1]) &&
81            color_space_almost_equal(2.2f, values[2])) {
82        gammaNamed = k2Dot2Curve_SkGammaNamed;
83    } else if (color_space_almost_equal(1.0f, values[0]) &&
84            color_space_almost_equal(1.0f, values[1]) &&
85            color_space_almost_equal(1.0f, values[2])) {
86        gammaNamed = kLinear_SkGammaNamed;
87    }
88
89    if (kNonStandard_SkGammaNamed == gammaNamed) {
90        sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas());
91        gammas->fRedType = SkGammas::Type::kValue_Type;
92        gammas->fGreenType = SkGammas::Type::kValue_Type;
93        gammas->fBlueType = SkGammas::Type::kValue_Type;
94        gammas->fRedData.fValue = values[0];
95        gammas->fGreenData.fValue = values[1];
96        gammas->fBlueData.fValue = values[2];
97        return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, kNonStandard_SkGammaNamed, gammas,
98                                                         toXYZD50, nullptr));
99    }
100
101    return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50);
102}
103
104sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50) {
105    switch (gammaNamed) {
106        case kSRGB_SkGammaNamed:
107            if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
108                return SkColorSpace::NewNamed(kSRGB_Named);
109            }
110            break;
111        case k2Dot2Curve_SkGammaNamed:
112            if (xyz_almost_equal(toXYZD50, gAdobeRGB_toXYZD50)) {
113                return SkColorSpace::NewNamed(kAdobeRGB_Named);
114            }
115            break;
116        case kNonStandard_SkGammaNamed:
117            // This is not allowed.
118            return nullptr;
119        default:
120            break;
121    }
122
123    return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammaNamed, toXYZD50));
124}
125
126sk_sp<SkColorSpace> SkColorSpace::NewRGB(RenderTargetGamma gamma, const SkMatrix44& toXYZD50) {
127    switch (gamma) {
128        case kLinear_RenderTargetGamma:
129            return SkColorSpace_Base::NewRGB(kLinear_SkGammaNamed, toXYZD50);
130        case kSRGB_RenderTargetGamma:
131            return SkColorSpace_Base::NewRGB(kSRGB_SkGammaNamed, toXYZD50);
132        default:
133            return nullptr;
134    }
135}
136
137static SkColorSpace* gAdobeRGB;
138static SkColorSpace* gSRGB;
139
140sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
141    static SkOnce sRGBOnce;
142    static SkOnce adobeRGBOnce;
143
144    switch (named) {
145        case kSRGB_Named: {
146            sRGBOnce([] {
147                SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
148                srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50);
149
150                // Force the mutable type mask to be computed.  This avoids races.
151                (void)srgbToxyzD50.getType();
152                gSRGB = new SkColorSpace_Base(kSRGB_SkGammaNamed, srgbToxyzD50);
153            });
154            return sk_ref_sp<SkColorSpace>(gSRGB);
155        }
156        case kAdobeRGB_Named: {
157            adobeRGBOnce([] {
158                SkMatrix44 adobergbToxyzD50(SkMatrix44::kUninitialized_Constructor);
159                adobergbToxyzD50.set3x3RowMajorf(gAdobeRGB_toXYZD50);
160
161                // Force the mutable type mask to be computed.  This avoids races.
162                (void)adobergbToxyzD50.getType();
163                gAdobeRGB = new SkColorSpace_Base(k2Dot2Curve_SkGammaNamed, adobergbToxyzD50);
164            });
165            return sk_ref_sp<SkColorSpace>(gAdobeRGB);
166        }
167        default:
168            break;
169    }
170    return nullptr;
171}
172
173sk_sp<SkColorSpace> SkColorSpace::makeLinearGamma() {
174    if (this->gammaIsLinear()) {
175        return sk_ref_sp(this);
176    }
177    return SkColorSpace_Base::NewRGB(kLinear_SkGammaNamed, fToXYZD50);
178}
179
180///////////////////////////////////////////////////////////////////////////////////////////////////
181
182bool SkColorSpace::gammaCloseToSRGB() const {
183    return kSRGB_SkGammaNamed == as_CSB(this)->fGammaNamed ||
184           k2Dot2Curve_SkGammaNamed == as_CSB(this)->fGammaNamed;
185}
186
187bool SkColorSpace::gammaIsLinear() const {
188    return kLinear_SkGammaNamed == as_CSB(this)->fGammaNamed;
189}
190
191const SkMatrix44& SkColorSpace_Base::fromXYZD50() const {
192    fFromXYZOnce([this] {
193        if (!fToXYZD50.invert(&fFromXYZD50)) {
194            // If a client gives us a dst gamut with a transform that we can't invert, we will
195            // simply give them back a transform to sRGB gamut.
196            SkDEBUGFAIL("Non-invertible XYZ matrix, defaulting to sRGB");
197            SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
198            srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50);
199            srgbToxyzD50.invert(&fFromXYZD50);
200        }
201    });
202    return fFromXYZD50;
203}
204
205///////////////////////////////////////////////////////////////////////////////////////////////////
206
207enum Version {
208    k0_Version, // Initial version, header + flags for matrix and profile
209};
210
211struct ColorSpaceHeader {
212    /**
213     *  If kMatrix_Flag is set, we will write 12 floats after the header.
214     *  Should not be set at the same time as the kICC_Flag or kFloatGamma_Flag.
215     */
216    static constexpr uint8_t kMatrix_Flag     = 1 << 0;
217
218    /**
219     *  If kICC_Flag is set, we will write an ICC profile after the header.
220     *  The ICC profile will be written as a uint32 size, followed immediately
221     *  by the data (padded to 4 bytes).
222     *  Should not be set at the same time as the kMatrix_Flag or kFloatGamma_Flag.
223     */
224    static constexpr uint8_t kICC_Flag        = 1 << 1;
225
226    /**
227     *  If kFloatGamma_Flag is set, we will write 15 floats after the header.
228     *  The first three are the gamma values, and the next twelve are the
229     *  matrix.
230     *  Should not be set at the same time as the kICC_Flag or kMatrix_Flag.
231     */
232    static constexpr uint8_t kFloatGamma_Flag = 1 << 2;
233
234    static ColorSpaceHeader Pack(Version version, uint8_t named, uint8_t gammaNamed, uint8_t flags)
235    {
236        ColorSpaceHeader header;
237
238        SkASSERT(k0_Version == version);
239        header.fVersion = (uint8_t) version;
240
241        SkASSERT(named <= SkColorSpace::kAdobeRGB_Named);
242        header.fNamed = (uint8_t) named;
243
244        SkASSERT(gammaNamed <= kNonStandard_SkGammaNamed);
245        header.fGammaNamed = (uint8_t) gammaNamed;
246
247        SkASSERT(flags <= kFloatGamma_Flag);
248        header.fFlags = flags;
249        return header;
250    }
251
252    uint8_t fVersion;    // Always zero
253    uint8_t fNamed;      // Must be a SkColorSpace::Named
254    uint8_t fGammaNamed; // Must be a SkGammaNamed
255    uint8_t fFlags;      // Some combination of the flags listed above
256};
257
258size_t SkColorSpace::writeToMemory(void* memory) const {
259    // Start by trying the serialization fast path.  If we haven't saved ICC profile data,
260    // we must have a profile that we can serialize easily.
261    if (!as_CSB(this)->fProfileData) {
262        // If we have a named profile, only write the enum.
263        if (this == gSRGB) {
264            if (memory) {
265                *((ColorSpaceHeader*) memory) =
266                        ColorSpaceHeader::Pack(k0_Version, kSRGB_Named,
267                                               as_CSB(this)->fGammaNamed, 0);
268            }
269            return sizeof(ColorSpaceHeader);
270        } else if (this == gAdobeRGB) {
271            if (memory) {
272                *((ColorSpaceHeader*) memory) =
273                        ColorSpaceHeader::Pack(k0_Version, kAdobeRGB_Named,
274                                               as_CSB(this)->fGammaNamed, 0);
275            }
276        }
277
278        // If we have a named gamma, write the enum and the matrix.
279        switch (as_CSB(this)->fGammaNamed) {
280            case kSRGB_SkGammaNamed:
281            case k2Dot2Curve_SkGammaNamed:
282            case kLinear_SkGammaNamed: {
283                if (memory) {
284                    *((ColorSpaceHeader*) memory) =
285                            ColorSpaceHeader::Pack(k0_Version, 0, as_CSB(this)->fGammaNamed,
286                                                   ColorSpaceHeader::kMatrix_Flag);
287                    memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
288                    fToXYZD50.as3x4RowMajorf((float*) memory);
289                }
290                return sizeof(ColorSpaceHeader) + 12 * sizeof(float);
291            }
292            default:
293                // Otherwise, write the gamma values and the matrix.
294                if (memory) {
295                    *((ColorSpaceHeader*) memory) =
296                            ColorSpaceHeader::Pack(k0_Version, 0, as_CSB(this)->fGammaNamed,
297                                                   ColorSpaceHeader::kFloatGamma_Flag);
298                    memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
299
300                    const SkGammas* gammas = as_CSB(this)->gammas();
301                    SkASSERT(gammas);
302                    SkASSERT(SkGammas::Type::kValue_Type == gammas->fRedType &&
303                             SkGammas::Type::kValue_Type == gammas->fGreenType &&
304                             SkGammas::Type::kValue_Type == gammas->fBlueType);
305                    *(((float*) memory) + 0) = gammas->fRedData.fValue;
306                    *(((float*) memory) + 1) = gammas->fGreenData.fValue;
307                    *(((float*) memory) + 2) = gammas->fBlueData.fValue;
308                    memory = SkTAddOffset<void>(memory, 3 * sizeof(float));
309
310                    fToXYZD50.as3x4RowMajorf((float*) memory);
311                }
312                return sizeof(ColorSpaceHeader) + 15 * sizeof(float);
313        }
314    }
315
316    // Otherwise, serialize the ICC data.
317    size_t profileSize = as_CSB(this)->fProfileData->size();
318    if (SkAlign4(profileSize) != (uint32_t) SkAlign4(profileSize)) {
319        return 0;
320    }
321
322    if (memory) {
323        *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(k0_Version, 0,
324                                                               kNonStandard_SkGammaNamed,
325                                                               ColorSpaceHeader::kICC_Flag);
326        memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
327
328        *((uint32_t*) memory) = (uint32_t) SkAlign4(profileSize);
329        memory = SkTAddOffset<void>(memory, sizeof(uint32_t));
330
331        memcpy(memory, as_CSB(this)->fProfileData->data(), profileSize);
332        memset(SkTAddOffset<void>(memory, profileSize), 0, SkAlign4(profileSize) - profileSize);
333    }
334    return sizeof(ColorSpaceHeader) + sizeof(uint32_t) + SkAlign4(profileSize);
335}
336
337sk_sp<SkData> SkColorSpace::serialize() const {
338    size_t size = this->writeToMemory(nullptr);
339    if (0 == size) {
340        return nullptr;
341    }
342
343    sk_sp<SkData> data = SkData::MakeUninitialized(size);
344    this->writeToMemory(data->writable_data());
345    return data;
346}
347
348sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
349    if (length < sizeof(ColorSpaceHeader)) {
350        return nullptr;
351    }
352
353    ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
354    data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
355    length -= sizeof(ColorSpaceHeader);
356    if (0 == header.fFlags) {
357        return NewNamed((Named) header.fNamed);
358    }
359
360    switch ((SkGammaNamed) header.fGammaNamed) {
361        case kSRGB_SkGammaNamed:
362        case k2Dot2Curve_SkGammaNamed:
363        case kLinear_SkGammaNamed: {
364            if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) {
365                return nullptr;
366            }
367
368            SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
369            toXYZ.set3x4RowMajorf((const float*) data);
370            return SkColorSpace_Base::NewRGB((SkGammaNamed) header.fGammaNamed, toXYZ);
371        }
372        default:
373            break;
374    }
375
376    switch (header.fFlags) {
377        case ColorSpaceHeader::kICC_Flag: {
378            if (length < sizeof(uint32_t)) {
379                return nullptr;
380            }
381
382            uint32_t profileSize = *((uint32_t*) data);
383            data = SkTAddOffset<const void>(data, sizeof(uint32_t));
384            length -= sizeof(uint32_t);
385            if (length < profileSize) {
386                return nullptr;
387            }
388
389            return NewICC(data, profileSize);
390        }
391        case ColorSpaceHeader::kFloatGamma_Flag: {
392            if (length < 15 * sizeof(float)) {
393                return nullptr;
394            }
395
396            float gammas[3];
397            gammas[0] = *(((const float*) data) + 0);
398            gammas[1] = *(((const float*) data) + 1);
399            gammas[2] = *(((const float*) data) + 2);
400            data = SkTAddOffset<const void>(data, 3 * sizeof(float));
401
402            SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
403            toXYZ.set3x4RowMajorf((const float*) data);
404            return SkColorSpace_Base::NewRGB(gammas, toXYZ);
405        }
406        default:
407            return nullptr;
408    }
409}
410
411bool SkColorSpace::Equals(const SkColorSpace* src, const SkColorSpace* dst) {
412    if (src == dst) {
413        return true;
414    }
415
416    if (!src || !dst) {
417        return false;
418    }
419
420    SkData* srcData = as_CSB(src)->fProfileData.get();
421    SkData* dstData = as_CSB(dst)->fProfileData.get();
422    if (srcData || dstData) {
423        if (srcData && dstData) {
424            return srcData->size() == dstData->size() &&
425                   0 == memcmp(srcData->data(), dstData->data(), srcData->size());
426        }
427
428        return false;
429    }
430
431    // It's important to check fProfileData before named gammas.  Some profiles may have named
432    // gammas, but also include other wacky features that cause us to save the data.
433    switch (as_CSB(src)->fGammaNamed) {
434        case kSRGB_SkGammaNamed:
435        case k2Dot2Curve_SkGammaNamed:
436        case kLinear_SkGammaNamed:
437            return (as_CSB(src)->fGammaNamed == as_CSB(dst)->fGammaNamed) &&
438                   (src->fToXYZD50 == dst->fToXYZD50);
439        default:
440            if (as_CSB(src)->fGammaNamed != as_CSB(dst)->fGammaNamed) {
441                return false;
442            }
443
444            // It is unlikely that we will reach this case.
445            sk_sp<SkData> srcData = src->serialize();
446            sk_sp<SkData> dstData = dst->serialize();
447            return srcData->size() == dstData->size() &&
448                   0 == memcmp(srcData->data(), dstData->data(), srcData->size());
449    }
450}
451