SkColorSpace.cpp revision 959ccc1f3f49e1ddeb51c32c30ac4a2d94653856
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 SkColorSpace* sRGB;
131    static SkOnce adobeRGBOnce;
132    static 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 = new SkColorSpace_Base(kSRGB_GammaNamed, srgbToxyzD50, kSRGB_Named);
140            });
141            return sk_ref_sp(sRGB);
142        }
143        case kAdobeRGB_Named: {
144            adobeRGBOnce([] {
145                SkMatrix44 adobergbToxyzD50(SkMatrix44::kUninitialized_Constructor);
146                adobergbToxyzD50.set3x3RowMajorf(gAdobeRGB_toXYZD50);
147                adobeRGB = new SkColorSpace_Base(k2Dot2Curve_GammaNamed, adobergbToxyzD50,
148                                                 kAdobeRGB_Named);
149            });
150            return sk_ref_sp(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
203sk_sp<SkData> SkColorSpace::serialize() const {
204    // If we have a named profile, only write the enum.
205    switch (fNamed) {
206        case kSRGB_Named:
207        case kAdobeRGB_Named: {
208            sk_sp<SkData> data = SkData::MakeUninitialized(sizeof(ColorSpaceHeader));
209            *((ColorSpaceHeader*) data->writable_data()) =
210                    ColorSpaceHeader::Pack(k0_Version, fNamed, fGammaNamed, 0);
211            return data;
212        }
213        default:
214            break;
215    }
216
217    // If we have a named gamma, write the enum and the matrix.
218    switch (fGammaNamed) {
219        case kSRGB_GammaNamed:
220        case k2Dot2Curve_GammaNamed:
221        case kLinear_GammaNamed: {
222            sk_sp<SkData> data = SkData::MakeUninitialized(sizeof(ColorSpaceHeader) +
223                                                           12 * sizeof(float));
224            void* dataPtr = data->writable_data();
225
226            *((ColorSpaceHeader*) dataPtr) = ColorSpaceHeader::Pack(k0_Version, fNamed, fGammaNamed,
227                                                                    ColorSpaceHeader::kMatrix_Flag);
228            dataPtr = SkTAddOffset<void>(dataPtr, sizeof(ColorSpaceHeader));
229
230            fToXYZD50.as4x3ColMajorf((float*) dataPtr);
231            return data;
232        }
233        default:
234            break;
235    }
236
237    // If we do not have a named gamma, this must have been created from an ICC profile.
238    // Since we were unable to recognize the gamma, we will have saved the ICC data.
239    SkASSERT(as_CSB(this)->fProfileData);
240
241    size_t profileSize = as_CSB(this)->fProfileData->size();
242    if (SkAlign4(profileSize) != (uint32_t) SkAlign4(profileSize)) {
243        return nullptr;
244    }
245
246    sk_sp<SkData> data = SkData::MakeUninitialized(sizeof(ColorSpaceHeader) + sizeof(uint32_t) +
247                                                   SkAlign4(profileSize));
248    void* dataPtr = data->writable_data();
249
250    *((ColorSpaceHeader*) dataPtr) = ColorSpaceHeader::Pack(k0_Version, fNamed, fGammaNamed,
251                                                            ColorSpaceHeader::kICC_Flag);
252    dataPtr = SkTAddOffset<void>(dataPtr, sizeof(ColorSpaceHeader));
253
254    *((uint32_t*) dataPtr) = (uint32_t) SkAlign4(profileSize);
255    dataPtr = SkTAddOffset<void>(dataPtr, sizeof(uint32_t));
256
257    memcpy(dataPtr, as_CSB(this)->fProfileData->data(), profileSize);
258    memset(SkTAddOffset<void>(dataPtr, profileSize), 0, SkAlign4(profileSize) - profileSize);
259    return data;
260}
261
262sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) {
263    if (length < sizeof(ColorSpaceHeader)) {
264        return nullptr;
265    }
266
267    ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
268    data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
269    length -= sizeof(ColorSpaceHeader);
270    switch ((Named) header.fNamed) {
271        case kSRGB_Named:
272        case kAdobeRGB_Named:
273            return NewNamed((Named) header.fNamed);
274        default:
275            break;
276    }
277
278    switch ((GammaNamed) header.fGammaNamed) {
279        case kSRGB_GammaNamed:
280        case k2Dot2Curve_GammaNamed:
281        case kLinear_GammaNamed: {
282            if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) {
283                return nullptr;
284            }
285
286            SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
287            toXYZ.set4x3ColMajorf((const float*) data);
288            return NewRGB((GammaNamed) header.fGammaNamed, toXYZ);
289        }
290        default:
291            break;
292    }
293
294    if (ColorSpaceHeader::kICC_Flag != header.fFlags || length < sizeof(uint32_t)) {
295        return nullptr;
296    }
297
298    uint32_t profileSize = *((uint32_t*) data);
299    data = SkTAddOffset<const void>(data, sizeof(uint32_t));
300    length -= sizeof(uint32_t);
301    if (length < profileSize) {
302        return nullptr;
303    }
304
305    return NewICC(data, profileSize);
306}
307