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