SkColorSpace.cpp revision 0ed057542ce545da57cda92dc09c1da8b5fdc363
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 "SkEndian.h"
11#include "SkOnce.h"
12
13#define SkColorSpacePrintf(...)
14
15static bool color_space_almost_equal(float a, float b) {
16    return SkTAbs(a - b) < 0.01f;
17}
18
19//////////////////////////////////////////////////////////////////////////////////////////////////
20
21SkColorSpace::SkColorSpace(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Named named)
22    : fGammaNamed(gammaNamed)
23    , fToXYZD50(toXYZD50)
24    , fNamed(named)
25{}
26
27SkColorSpace_Base::SkColorSpace_Base(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Named named,
28                                     sk_sp<SkData> profileData)
29    : INHERITED(gammaNamed, toXYZD50, named)
30    , fGammas(nullptr)
31    , fProfileData(std::move(profileData))
32{}
33
34SkColorSpace_Base::SkColorSpace_Base(SkColorLookUpTable* colorLUT, sk_sp<SkGammas> gammas,
35                                     const SkMatrix44& toXYZD50, sk_sp<SkData> profileData)
36    : INHERITED(kNonStandard_GammaNamed, toXYZD50, kUnknown_Named)
37    , fColorLUT(colorLUT)
38    , fGammas(std::move(gammas))
39    , fProfileData(std::move(profileData))
40{}
41
42static constexpr float gSRGB_toXYZD50[] {
43    0.4358f, 0.2224f, 0.0139f,    // * R
44    0.3853f, 0.7170f, 0.0971f,    // * G
45    0.1430f, 0.0606f, 0.7139f,    // * B
46};
47
48static constexpr float gAdobeRGB_toXYZD50[] {
49    0.6098f, 0.3111f, 0.0195f,    // * R
50    0.2052f, 0.6257f, 0.0609f,    // * G
51    0.1492f, 0.0632f, 0.7448f,    // * B
52};
53
54/**
55 *  Checks if our toXYZ matrix is a close match to a known color gamut.
56 *
57 *  @param toXYZD50 transformation matrix deduced from profile data
58 *  @param standard 3x3 canonical transformation matrix
59 */
60static bool xyz_almost_equal(const SkMatrix44& toXYZD50, const float* standard) {
61    return color_space_almost_equal(toXYZD50.getFloat(0, 0), standard[0]) &&
62           color_space_almost_equal(toXYZD50.getFloat(0, 1), standard[1]) &&
63           color_space_almost_equal(toXYZD50.getFloat(0, 2), standard[2]) &&
64           color_space_almost_equal(toXYZD50.getFloat(1, 0), standard[3]) &&
65           color_space_almost_equal(toXYZD50.getFloat(1, 1), standard[4]) &&
66           color_space_almost_equal(toXYZD50.getFloat(1, 2), standard[5]) &&
67           color_space_almost_equal(toXYZD50.getFloat(2, 0), standard[6]) &&
68           color_space_almost_equal(toXYZD50.getFloat(2, 1), standard[7]) &&
69           color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) &&
70           color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) &&
71           color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) &&
72           color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) &&
73           color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) &&
74           color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) &&
75           color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) &&
76           color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f);
77}
78
79static void set_gamma_value(SkGammaCurve* gamma, float value) {
80    if (color_space_almost_equal(2.2f, value)) {
81        gamma->fNamed = SkColorSpace::k2Dot2Curve_GammaNamed;
82    } else if (color_space_almost_equal(1.0f, value)) {
83        gamma->fNamed = SkColorSpace::kLinear_GammaNamed;
84    } else if (color_space_almost_equal(0.0f, value)) {
85        SkColorSpacePrintf("Treating invalid zero gamma as linear.");
86        gamma->fNamed = SkColorSpace::kLinear_GammaNamed;
87    } else {
88        gamma->fValue = value;
89    }
90}
91
92sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(float values[3], const SkMatrix44& toXYZD50) {
93    SkGammaCurve curves[3];
94    set_gamma_value(&curves[0], values[0]);
95    set_gamma_value(&curves[1], values[1]);
96    set_gamma_value(&curves[2], values[2]);
97
98    GammaNamed gammaNamed = SkGammas::Named(curves);
99    if (kNonStandard_GammaNamed == gammaNamed) {
100        sk_sp<SkGammas> gammas(new SkGammas(std::move(curves[0]), std::move(curves[1]),
101                                            std::move(curves[2])));
102        return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, gammas, toXYZD50, nullptr));
103    }
104
105    return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50, nullptr);
106}
107
108sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(GammaNamed gammaNamed, const SkMatrix44& toXYZD50,
109                                              sk_sp<SkData> profileData) {
110    switch (gammaNamed) {
111        case kSRGB_GammaNamed:
112            if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
113                return SkColorSpace::NewNamed(kSRGB_Named);
114            }
115            break;
116        case k2Dot2Curve_GammaNamed:
117            if (xyz_almost_equal(toXYZD50, gAdobeRGB_toXYZD50)) {
118                return SkColorSpace::NewNamed(kAdobeRGB_Named);
119            }
120            break;
121        case kNonStandard_GammaNamed:
122            // This is not allowed.
123            return nullptr;
124        default:
125            break;
126    }
127
128    return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammaNamed, toXYZD50, kUnknown_Named,
129                                                     profileData));
130}
131
132sk_sp<SkColorSpace> SkColorSpace::NewRGB(GammaNamed gammaNamed, const SkMatrix44& toXYZD50) {
133    return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50, nullptr);
134}
135
136sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
137    static SkOnce sRGBOnce;
138    static SkColorSpace* sRGB;
139    static SkOnce adobeRGBOnce;
140    static SkColorSpace* adobeRGB;
141
142    switch (named) {
143        case kSRGB_Named: {
144            sRGBOnce([] {
145                SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
146                srgbToxyzD50.set3x3ColMajorf(gSRGB_toXYZD50);
147                sRGB = new SkColorSpace_Base(kSRGB_GammaNamed, srgbToxyzD50, kSRGB_Named, nullptr);
148            });
149            return sk_ref_sp(sRGB);
150        }
151        case kAdobeRGB_Named: {
152            adobeRGBOnce([] {
153                SkMatrix44 adobergbToxyzD50(SkMatrix44::kUninitialized_Constructor);
154                adobergbToxyzD50.set3x3ColMajorf(gAdobeRGB_toXYZD50);
155                adobeRGB = new SkColorSpace_Base(k2Dot2Curve_GammaNamed, adobergbToxyzD50,
156                                                 kAdobeRGB_Named, nullptr);
157            });
158            return sk_ref_sp(adobeRGB);
159        }
160        default:
161            break;
162    }
163    return nullptr;
164}
165
166///////////////////////////////////////////////////////////////////////////////////////////////////
167
168#include "SkFixed.h"
169#include "SkTemplates.h"
170
171#define return_if_false(pred, msg)                                   \
172    do {                                                             \
173        if (!(pred)) {                                               \
174            SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \
175            return false;                                            \
176        }                                                            \
177    } while (0)
178
179#define return_null(msg)                                             \
180    do {                                                             \
181        SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg));     \
182        return nullptr;                                              \
183    } while (0)
184
185static uint16_t read_big_endian_short(const uint8_t* ptr) {
186    return ptr[0] << 8 | ptr[1];
187}
188
189static uint32_t read_big_endian_uint(const uint8_t* ptr) {
190    return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
191}
192
193static int32_t read_big_endian_int(const uint8_t* ptr) {
194    return (int32_t) read_big_endian_uint(ptr);
195}
196
197// This is equal to the header size according to the ICC specification (128)
198// plus the size of the tag count (4).  We include the tag count since we
199// always require it to be present anyway.
200static constexpr size_t kICCHeaderSize = 132;
201
202// Contains a signature (4), offset (4), and size (4).
203static constexpr size_t kICCTagTableEntrySize = 12;
204
205static constexpr uint32_t kRGB_ColorSpace  = SkSetFourByteTag('R', 'G', 'B', ' ');
206static constexpr uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r');
207static constexpr uint32_t kInput_Profile   = SkSetFourByteTag('s', 'c', 'n', 'r');
208static constexpr uint32_t kOutput_Profile  = SkSetFourByteTag('p', 'r', 't', 'r');
209static constexpr uint32_t kXYZ_PCSSpace    = SkSetFourByteTag('X', 'Y', 'Z', ' ');
210static constexpr uint32_t kACSP_Signature  = SkSetFourByteTag('a', 'c', 's', 'p');
211
212struct ICCProfileHeader {
213    uint32_t fSize;
214
215    // No reason to care about the preferred color management module (ex: Adobe, Apple, etc.).
216    // We're always going to use this one.
217    uint32_t fCMMType_ignored;
218
219    uint32_t fVersion;
220    uint32_t fProfileClass;
221    uint32_t fInputColorSpace;
222    uint32_t fPCS;
223    uint32_t fDateTime_ignored[3];
224    uint32_t fSignature;
225
226    // Indicates the platform that this profile was created for (ex: Apple, Microsoft).  This
227    // doesn't really matter to us.
228    uint32_t fPlatformTarget_ignored;
229
230    // Flags can indicate:
231    // (1) Whether this profile was embedded in a file.  This flag is consistently wrong.
232    //     Ex: The profile came from a file but indicates that it did not.
233    // (2) Whether we are allowed to use the profile independently of the color data.  If set,
234    //     this may allow us to use the embedded profile for testing separate from the original
235    //     image.
236    uint32_t fFlags_ignored;
237
238    // We support many output devices.  It doesn't make sense to think about the attributes of
239    // the device in the context of the image profile.
240    uint32_t fDeviceManufacturer_ignored;
241    uint32_t fDeviceModel_ignored;
242    uint32_t fDeviceAttributes_ignored[2];
243
244    uint32_t fRenderingIntent;
245    int32_t  fIlluminantXYZ[3];
246
247    // We don't care who created the profile.
248    uint32_t fCreator_ignored;
249
250    // This is an MD5 checksum.  Could be useful for checking if profiles are equal.
251    uint32_t fProfileId_ignored[4];
252
253    // Reserved for future use.
254    uint32_t fReserved_ignored[7];
255
256    uint32_t fTagCount;
257
258    void init(const uint8_t* src, size_t len) {
259        SkASSERT(kICCHeaderSize == sizeof(*this));
260
261        uint32_t* dst = (uint32_t*) this;
262        for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) {
263            dst[i] = read_big_endian_uint(src);
264        }
265    }
266
267    bool valid() const {
268        return_if_false(fSize >= kICCHeaderSize, "Size is too small");
269
270        uint8_t majorVersion = fVersion >> 24;
271        return_if_false(majorVersion <= 4, "Unsupported version");
272
273        // These are the three basic classes of profiles that we might expect to see embedded
274        // in images.  Four additional classes exist, but they generally are used as a convenient
275        // way for CMMs to store calculated transforms.
276        return_if_false(fProfileClass == kDisplay_Profile ||
277                        fProfileClass == kInput_Profile ||
278                        fProfileClass == kOutput_Profile,
279                        "Unsupported profile");
280
281        // TODO (msarett):
282        // All the profiles we've tested so far use RGB as the input color space.
283        return_if_false(fInputColorSpace == kRGB_ColorSpace, "Unsupported color space");
284
285        // TODO (msarett):
286        // All the profiles we've tested so far use XYZ as the profile connection space.
287        return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space");
288
289        return_if_false(fSignature == kACSP_Signature, "Bad signature");
290
291        // TODO (msarett):
292        // Should we treat different rendering intents differently?
293        // Valid rendering intents include kPerceptual (0), kRelative (1),
294        // kSaturation (2), and kAbsolute (3).
295        return_if_false(fRenderingIntent <= 3, "Bad rendering intent");
296
297        return_if_false(color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[0]), 0.96420f) &&
298                        color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[1]), 1.00000f) &&
299                        color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[2]), 0.82491f),
300                        "Illuminant must be D50");
301
302        return_if_false(fTagCount <= 100, "Too many tags");
303
304        return true;
305    }
306};
307
308template <class T>
309static bool safe_add(T arg1, T arg2, size_t* result) {
310    SkASSERT(arg1 >= 0);
311    SkASSERT(arg2 >= 0);
312    if (arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) {
313        T sum = arg1 + arg2;
314        if (sum <= std::numeric_limits<size_t>::max()) {
315            *result = static_cast<size_t>(sum);
316            return true;
317        }
318    }
319    return false;
320}
321
322static bool safe_mul(uint32_t arg1, uint32_t arg2, uint32_t* result) {
323    uint64_t product64 = (uint64_t) arg1 * (uint64_t) arg2;
324    uint32_t product32 = (uint32_t) product64;
325    if (product32 != product64) {
326        return false;
327    }
328
329    *result = product32;
330    return true;
331}
332
333struct ICCTag {
334    uint32_t fSignature;
335    uint32_t fOffset;
336    uint32_t fLength;
337
338    const uint8_t* init(const uint8_t* src) {
339        fSignature = read_big_endian_uint(src);
340        fOffset = read_big_endian_uint(src + 4);
341        fLength = read_big_endian_uint(src + 8);
342        return src + 12;
343    }
344
345    bool valid(size_t len) {
346        size_t tagEnd;
347        return_if_false(safe_add(fOffset, fLength, &tagEnd),
348                        "Tag too large, overflows integer addition");
349        return_if_false(tagEnd <= len, "Tag too large for ICC profile");
350        return true;
351    }
352
353    const uint8_t* addr(const uint8_t* src) const {
354        return src + fOffset;
355    }
356
357    static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature) {
358        for (int i = 0; i < count; ++i) {
359            if (tags[i].fSignature == signature) {
360                return &tags[i];
361            }
362        }
363        return nullptr;
364    }
365};
366
367static constexpr uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z');
368static constexpr uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z');
369static constexpr uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z');
370static constexpr uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C');
371static constexpr uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C');
372static constexpr uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C');
373static constexpr uint32_t kTAG_A2B0 = SkSetFourByteTag('A', '2', 'B', '0');
374
375static bool load_xyz(float dst[3], const uint8_t* src, size_t len) {
376    if (len < 20) {
377        SkColorSpacePrintf("XYZ tag is too small (%d bytes)", len);
378        return false;
379    }
380
381    dst[0] = SkFixedToFloat(read_big_endian_int(src + 8));
382    dst[1] = SkFixedToFloat(read_big_endian_int(src + 12));
383    dst[2] = SkFixedToFloat(read_big_endian_int(src + 16));
384    SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]);
385    return true;
386}
387
388static constexpr uint32_t kTAG_CurveType     = SkSetFourByteTag('c', 'u', 'r', 'v');
389static constexpr uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a');
390
391static bool load_gammas(SkGammaCurve* gammas, uint32_t numGammas, const uint8_t* src, size_t len) {
392    for (uint32_t i = 0; i < numGammas; i++) {
393        if (len < 12) {
394            // FIXME (msarett):
395            // We could potentially return false here after correctly parsing *some* of the
396            // gammas correctly.  Should we somehow try to indicate a partial success?
397            SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
398            return false;
399        }
400
401        // We need to count the number of bytes in the tag, so we are able to move to the
402        // next tag on the next loop iteration.
403        size_t tagBytes;
404
405        uint32_t type = read_big_endian_uint(src);
406        switch (type) {
407            case kTAG_CurveType: {
408                uint32_t count = read_big_endian_uint(src + 8);
409
410                // tagBytes = 12 + 2 * count
411                // We need to do safe addition here to avoid integer overflow.
412                if (!safe_add(count, count, &tagBytes) ||
413                    !safe_add((size_t) 12, tagBytes, &tagBytes))
414                {
415                    SkColorSpacePrintf("Invalid gamma count");
416                    return false;
417                }
418
419                if (0 == count) {
420                    // Some tags require a gamma curve, but the author doesn't actually want
421                    // to transform the data.  In this case, it is common to see a curve with
422                    // a count of 0.
423                    gammas[i].fNamed = SkColorSpace::kLinear_GammaNamed;
424                    break;
425                } else if (len < tagBytes) {
426                    SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
427                    return false;
428                }
429
430                const uint16_t* table = (const uint16_t*) (src + 12);
431                if (1 == count) {
432                    // The table entry is the gamma (with a bias of 256).
433                    float value = (read_big_endian_short((const uint8_t*) table)) / 256.0f;
434                    set_gamma_value(&gammas[i], value);
435                    SkColorSpacePrintf("gamma %g\n", value);
436                    break;
437                }
438
439                // Check for frequently occurring sRGB curves.
440                // We do this by sampling a few values and see if they match our expectation.
441                // A more robust solution would be to compare each value in this curve against
442                // an sRGB curve to see if we remain below an error threshold.  At this time,
443                // we haven't seen any images in the wild that make this kind of
444                // calculation necessary.  We encounter identical gamma curves over and
445                // over again, but relatively few variations.
446                if (1024 == count) {
447                    // The magic values were chosen because they match a very common sRGB
448                    // gamma table and the less common Canon sRGB gamma table (which use
449                    // different rounding rules).
450                    if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
451                            3366 == read_big_endian_short((const uint8_t*) &table[257]) &&
452                            14116 == read_big_endian_short((const uint8_t*) &table[513]) &&
453                            34318 == read_big_endian_short((const uint8_t*) &table[768]) &&
454                            65535 == read_big_endian_short((const uint8_t*) &table[1023])) {
455                        gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
456                        break;
457                    }
458                } else if (26 == count) {
459                    // The magic values were chosen because they match a very common sRGB
460                    // gamma table.
461                    if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
462                            3062 == read_big_endian_short((const uint8_t*) &table[6]) &&
463                            12824 == read_big_endian_short((const uint8_t*) &table[12]) &&
464                            31237 == read_big_endian_short((const uint8_t*) &table[18]) &&
465                            65535 == read_big_endian_short((const uint8_t*) &table[25])) {
466                        gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
467                        break;
468                    }
469                } else if (4096 == count) {
470                    // The magic values were chosen because they match Nikon, Epson, and
471                    // LCMS sRGB gamma tables (all of which use different rounding rules).
472                    if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
473                            950 == read_big_endian_short((const uint8_t*) &table[515]) &&
474                            3342 == read_big_endian_short((const uint8_t*) &table[1025]) &&
475                            14079 == read_big_endian_short((const uint8_t*) &table[2051]) &&
476                            65535 == read_big_endian_short((const uint8_t*) &table[4095])) {
477                        gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
478                        break;
479                    }
480                }
481
482                // Otherwise, fill in the interpolation table.
483                gammas[i].fTableSize = count;
484                gammas[i].fTable = std::unique_ptr<float[]>(new float[count]);
485                for (uint32_t j = 0; j < count; j++) {
486                    gammas[i].fTable[j] =
487                            (read_big_endian_short((const uint8_t*) &table[j])) / 65535.0f;
488                }
489                break;
490            }
491            case kTAG_ParaCurveType: {
492                enum ParaCurveType {
493                    kExponential_ParaCurveType = 0,
494                    kGAB_ParaCurveType         = 1,
495                    kGABC_ParaCurveType        = 2,
496                    kGABDE_ParaCurveType       = 3,
497                    kGABCDEF_ParaCurveType     = 4,
498                };
499
500                // Determine the format of the parametric curve tag.
501                uint16_t format = read_big_endian_short(src + 8);
502                if (kExponential_ParaCurveType == format) {
503                    tagBytes = 12 + 4;
504                    if (len < tagBytes) {
505                        SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
506                        return false;
507                    }
508
509                    // Y = X^g
510                    int32_t g = read_big_endian_int(src + 12);
511                    set_gamma_value(&gammas[i], SkFixedToFloat(g));
512                } else {
513                    // Here's where the real parametric gammas start.  There are many
514                    // permutations of the same equations.
515                    //
516                    // Y = (aX + b)^g + c  for X >= d
517                    // Y = eX + f          otherwise
518                    //
519                    // We will fill in with zeros as necessary to always match the above form.
520                    float g = 0.0f, a = 0.0f, b = 0.0f, c = 0.0f, d = 0.0f, e = 0.0f, f = 0.0f;
521                    switch(format) {
522                        case kGAB_ParaCurveType: {
523                            tagBytes = 12 + 12;
524                            if (len < tagBytes) {
525                                SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
526                                return false;
527                            }
528
529                            // Y = (aX + b)^g  for X >= -b/a
530                            // Y = 0           otherwise
531                            g = SkFixedToFloat(read_big_endian_int(src + 12));
532                            a = SkFixedToFloat(read_big_endian_int(src + 16));
533                            if (0.0f == a) {
534                                return false;
535                            }
536
537                            b = SkFixedToFloat(read_big_endian_int(src + 20));
538                            d = -b / a;
539                            break;
540                        }
541                        case kGABC_ParaCurveType:
542                            tagBytes = 12 + 16;
543                            if (len < tagBytes) {
544                                SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
545                                return false;
546                            }
547
548                            // Y = (aX + b)^g + c  for X >= -b/a
549                            // Y = c               otherwise
550                            g = SkFixedToFloat(read_big_endian_int(src + 12));
551                            a = SkFixedToFloat(read_big_endian_int(src + 16));
552                            if (0.0f == a) {
553                                return false;
554                            }
555
556                            b = SkFixedToFloat(read_big_endian_int(src + 20));
557                            c = SkFixedToFloat(read_big_endian_int(src + 24));
558                            d = -b / a;
559                            f = c;
560                            break;
561                        case kGABDE_ParaCurveType:
562                            tagBytes = 12 + 20;
563                            if (len < tagBytes) {
564                                SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
565                                return false;
566                            }
567
568                            // Y = (aX + b)^g  for X >= d
569                            // Y = cX          otherwise
570                            g = SkFixedToFloat(read_big_endian_int(src + 12));
571                            a = SkFixedToFloat(read_big_endian_int(src + 16));
572                            b = SkFixedToFloat(read_big_endian_int(src + 20));
573                            d = SkFixedToFloat(read_big_endian_int(src + 28));
574                            e = SkFixedToFloat(read_big_endian_int(src + 24));
575                            break;
576                        case kGABCDEF_ParaCurveType:
577                            tagBytes = 12 + 28;
578                            if (len < tagBytes) {
579                                SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
580                                return false;
581                            }
582
583                            // Y = (aX + b)^g + c  for X >= d
584                            // Y = eX + f          otherwise
585                            // NOTE: The ICC spec writes "cX" in place of "eX" but I think
586                            //       it's a typo.
587                            g = SkFixedToFloat(read_big_endian_int(src + 12));
588                            a = SkFixedToFloat(read_big_endian_int(src + 16));
589                            b = SkFixedToFloat(read_big_endian_int(src + 20));
590                            c = SkFixedToFloat(read_big_endian_int(src + 24));
591                            d = SkFixedToFloat(read_big_endian_int(src + 28));
592                            e = SkFixedToFloat(read_big_endian_int(src + 32));
593                            f = SkFixedToFloat(read_big_endian_int(src + 36));
594                            break;
595                        default:
596                            SkColorSpacePrintf("Invalid parametric curve type\n");
597                            return false;
598                    }
599
600                    // Recognize and simplify a very common parametric representation of sRGB gamma.
601                    if (color_space_almost_equal(0.9479f, a) &&
602                            color_space_almost_equal(0.0521f, b) &&
603                            color_space_almost_equal(0.0000f, c) &&
604                            color_space_almost_equal(0.0405f, d) &&
605                            color_space_almost_equal(0.0774f, e) &&
606                            color_space_almost_equal(0.0000f, f) &&
607                            color_space_almost_equal(2.4000f, g)) {
608                        gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
609                    } else {
610                        // Fail on invalid gammas.
611                        if (d <= 0.0f) {
612                            // Y = (aX + b)^g + c  for always
613                            if (0.0f == a || 0.0f == g) {
614                                SkColorSpacePrintf("A or G is zero, constant gamma function "
615                                                   "is nonsense");
616                                return false;
617                            }
618                        } else if (d >= 1.0f) {
619                            // Y = eX + f          for always
620                            if (0.0f == e) {
621                                SkColorSpacePrintf("E is zero, constant gamma function is "
622                                                   "nonsense");
623                                return false;
624                            }
625                        } else if ((0.0f == a || 0.0f == g) && 0.0f == e) {
626                            SkColorSpacePrintf("A or G, and E are zero, constant gamma function "
627                                               "is nonsense");
628                            return false;
629                        }
630
631                        gammas[i].fG = g;
632                        gammas[i].fA = a;
633                        gammas[i].fB = b;
634                        gammas[i].fC = c;
635                        gammas[i].fD = d;
636                        gammas[i].fE = e;
637                        gammas[i].fF = f;
638                    }
639                }
640
641                break;
642            }
643            default:
644                SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
645                return false;
646        }
647
648        // Ensure that we have successfully read a gamma representation.
649        SkASSERT(gammas[i].isNamed() || gammas[i].isValue() || gammas[i].isTable() ||
650                 gammas[i].isParametric());
651
652        // Adjust src and len if there is another gamma curve to load.
653        if (i != numGammas - 1) {
654            // Each curve is padded to 4-byte alignment.
655            tagBytes = SkAlign4(tagBytes);
656            if (len < tagBytes) {
657                return false;
658            }
659
660            src += tagBytes;
661            len -= tagBytes;
662        }
663    }
664
665    return true;
666}
667
668static constexpr uint32_t kTAG_AtoBType = SkSetFourByteTag('m', 'A', 'B', ' ');
669
670bool load_color_lut(SkColorLookUpTable* colorLUT, uint32_t inputChannels, uint32_t outputChannels,
671                    const uint8_t* src, size_t len) {
672    // 16 bytes reserved for grid points, 2 for precision, 2 for padding.
673    // The color LUT data follows after this header.
674    static constexpr uint32_t kColorLUTHeaderSize = 20;
675    if (len < kColorLUTHeaderSize) {
676        SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len);
677        return false;
678    }
679    size_t dataLen = len - kColorLUTHeaderSize;
680
681    SkASSERT(inputChannels <= SkColorLookUpTable::kMaxChannels && 3 == outputChannels);
682    colorLUT->fInputChannels = inputChannels;
683    colorLUT->fOutputChannels = outputChannels;
684    uint32_t numEntries = 1;
685    for (uint32_t i = 0; i < inputChannels; i++) {
686        colorLUT->fGridPoints[i] = src[i];
687        if (0 == src[i]) {
688            SkColorSpacePrintf("Each input channel must have at least one grid point.");
689            return false;
690        }
691
692        if (!safe_mul(numEntries, src[i], &numEntries)) {
693            SkColorSpacePrintf("Too many entries in Color LUT.");
694            return false;
695        }
696    }
697
698    if (!safe_mul(numEntries, outputChannels, &numEntries)) {
699        SkColorSpacePrintf("Too many entries in Color LUT.");
700        return false;
701    }
702
703    // Space is provided for a maximum of the 16 input channels.  Now we determine the precision
704    // of the table values.
705    uint8_t precision = src[16];
706    switch (precision) {
707        case 1: // 8-bit data
708        case 2: // 16-bit data
709            break;
710        default:
711            SkColorSpacePrintf("Color LUT precision must be 8-bit or 16-bit.\n");
712            return false;
713    }
714
715    uint32_t clutBytes;
716    if (!safe_mul(numEntries, precision, &clutBytes)) {
717        SkColorSpacePrintf("Too many entries in Color LUT.");
718        return false;
719    }
720
721    if (dataLen < clutBytes) {
722        SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len);
723        return false;
724    }
725
726    // Movable struct colorLUT has ownership of fTable.
727    colorLUT->fTable = std::unique_ptr<float[]>(new float[numEntries]);
728    const uint8_t* ptr = src + kColorLUTHeaderSize;
729    for (uint32_t i = 0; i < numEntries; i++, ptr += precision) {
730        if (1 == precision) {
731            colorLUT->fTable[i] = ((float) ptr[i]) / 255.0f;
732        } else {
733            colorLUT->fTable[i] = ((float) read_big_endian_short(ptr)) / 65535.0f;
734        }
735    }
736
737    return true;
738}
739
740bool load_matrix(SkMatrix44* toXYZ, const uint8_t* src, size_t len) {
741    if (len < 48) {
742        SkColorSpacePrintf("Matrix tag is too small (%d bytes).", len);
743        return false;
744    }
745
746    // For this matrix to behave like our "to XYZ D50" matrices, it needs to be scaled.
747    constexpr float scale = 65535.0 / 32768.0;
748    float array[16];
749    array[ 0] = scale * SkFixedToFloat(read_big_endian_int(src));
750    array[ 1] = scale * SkFixedToFloat(read_big_endian_int(src + 4));
751    array[ 2] = scale * SkFixedToFloat(read_big_endian_int(src + 8));
752    array[ 3] = scale * SkFixedToFloat(read_big_endian_int(src + 36)); // translate R
753    array[ 4] = scale * SkFixedToFloat(read_big_endian_int(src + 12));
754    array[ 5] = scale * SkFixedToFloat(read_big_endian_int(src + 16));
755    array[ 6] = scale * SkFixedToFloat(read_big_endian_int(src + 20));
756    array[ 7] = scale * SkFixedToFloat(read_big_endian_int(src + 40)); // translate G
757    array[ 8] = scale * SkFixedToFloat(read_big_endian_int(src + 24));
758    array[ 9] = scale * SkFixedToFloat(read_big_endian_int(src + 28));
759    array[10] = scale * SkFixedToFloat(read_big_endian_int(src + 32));
760    array[11] = scale * SkFixedToFloat(read_big_endian_int(src + 44)); // translate B
761    array[12] = 0.0f;
762    array[13] = 0.0f;
763    array[14] = 0.0f;
764    array[15] = 1.0f;
765    toXYZ->setColMajorf(array);
766    return true;
767}
768
769bool load_a2b0(SkColorLookUpTable* colorLUT, SkGammaCurve* gammas, SkMatrix44* toXYZ,
770               const uint8_t* src, size_t len) {
771    if (len < 32) {
772        SkColorSpacePrintf("A to B tag is too small (%d bytes).", len);
773        return false;
774    }
775
776    uint32_t type = read_big_endian_uint(src);
777    if (kTAG_AtoBType != type) {
778        // FIXME (msarett): Need to support lut8Type and lut16Type.
779        SkColorSpacePrintf("Unsupported A to B tag type.\n");
780        return false;
781    }
782
783    // Read the number of channels.  The four bytes that we skipped are reserved and
784    // must be zero.
785    uint8_t inputChannels = src[8];
786    uint8_t outputChannels = src[9];
787    if (0 == inputChannels || inputChannels > SkColorLookUpTable::kMaxChannels ||
788            3 != outputChannels) {
789        // The color LUT assumes that there are at most 16 input channels.  For RGB
790        // profiles, output channels should be 3.
791        SkColorSpacePrintf("Too many input or output channels in A to B tag.\n");
792        return false;
793    }
794
795    // Read the offsets of each element in the A to B tag.  With the exception of A curves and
796    // B curves (which we do not yet support), we will handle these elements in the order in
797    // which they should be applied (rather than the order in which they occur in the tag).
798    // If the offset is non-zero it indicates that the element is present.
799    uint32_t offsetToACurves = read_big_endian_int(src + 28);
800    uint32_t offsetToBCurves = read_big_endian_int(src + 12);
801    if ((0 != offsetToACurves) || (0 != offsetToBCurves)) {
802        // FIXME (msarett): Handle A and B curves.
803        // Note that the A curve is technically required in order to have a color LUT.
804        // However, all the A curves I have seen so far have are just placeholders that
805        // don't actually transform the data.
806        SkColorSpacePrintf("Ignoring A and/or B curve.  Output may be wrong.\n");
807    }
808
809    uint32_t offsetToColorLUT = read_big_endian_int(src + 24);
810    if (0 != offsetToColorLUT && offsetToColorLUT < len) {
811        if (!load_color_lut(colorLUT, inputChannels, outputChannels, src + offsetToColorLUT,
812                            len - offsetToColorLUT)) {
813            SkColorSpacePrintf("Failed to read color LUT from A to B tag.\n");
814        }
815    }
816
817    uint32_t offsetToMCurves = read_big_endian_int(src + 20);
818    if (0 != offsetToMCurves && offsetToMCurves < len) {
819        if (!load_gammas(gammas, outputChannels, src + offsetToMCurves, len - offsetToMCurves)) {
820            SkColorSpacePrintf("Failed to read M curves from A to B tag.  Using linear gamma.\n");
821            gammas[0].fNamed = SkColorSpace::kLinear_GammaNamed;
822            gammas[1].fNamed = SkColorSpace::kLinear_GammaNamed;
823            gammas[2].fNamed = SkColorSpace::kLinear_GammaNamed;
824        }
825    }
826
827    uint32_t offsetToMatrix = read_big_endian_int(src + 16);
828    if (0 != offsetToMatrix && offsetToMatrix < len) {
829        if (!load_matrix(toXYZ, src + offsetToMatrix, len - offsetToMatrix)) {
830            SkColorSpacePrintf("Failed to read matrix from A to B tag.\n");
831            toXYZ->setIdentity();
832        }
833    }
834
835    return true;
836}
837
838sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* input, size_t len) {
839    if (!input || len < kICCHeaderSize) {
840        return_null("Data is null or not large enough to contain an ICC profile");
841    }
842
843    // Create our own copy of the input.
844    void* memory = sk_malloc_throw(len);
845    memcpy(memory, input, len);
846    sk_sp<SkData> data = SkData::MakeFromMalloc(memory, len);
847    const void* base = data->data();
848    const uint8_t* ptr = (const uint8_t*) base;
849
850    // Read the ICC profile header and check to make sure that it is valid.
851    ICCProfileHeader header;
852    header.init(ptr, len);
853    if (!header.valid()) {
854        return nullptr;
855    }
856
857    // Adjust ptr and len before reading the tags.
858    if (len < header.fSize) {
859        SkColorSpacePrintf("ICC profile might be truncated.\n");
860    } else if (len > header.fSize) {
861        SkColorSpacePrintf("Caller provided extra data beyond the end of the ICC profile.\n");
862        len = header.fSize;
863    }
864    ptr += kICCHeaderSize;
865    len -= kICCHeaderSize;
866
867    // Parse tag headers.
868    uint32_t tagCount = header.fTagCount;
869    SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount);
870    if (len < kICCTagTableEntrySize * tagCount) {
871        return_null("Not enough input data to read tag table entries");
872    }
873
874    SkAutoTArray<ICCTag> tags(tagCount);
875    for (uint32_t i = 0; i < tagCount; i++) {
876        ptr = tags[i].init(ptr);
877        SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24) & 0xFF,
878                (tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >>  8) & 0xFF,
879                (tags[i].fSignature >>  0) & 0xFF, tags[i].fOffset, tags[i].fLength);
880
881        if (!tags[i].valid(kICCHeaderSize + len)) {
882            return_null("Tag is too large to fit in ICC profile");
883        }
884    }
885
886    switch (header.fInputColorSpace) {
887        case kRGB_ColorSpace: {
888            // Recognize the rXYZ, gXYZ, and bXYZ tags.
889            const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ);
890            const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ);
891            const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ);
892            if (r && g && b) {
893                float toXYZ[9];
894                if (!load_xyz(&toXYZ[0], r->addr((const uint8_t*) base), r->fLength) ||
895                    !load_xyz(&toXYZ[3], g->addr((const uint8_t*) base), g->fLength) ||
896                    !load_xyz(&toXYZ[6], b->addr((const uint8_t*) base), b->fLength))
897                {
898                    return_null("Need valid rgb tags for XYZ space");
899                }
900                SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
901                mat.set3x3ColMajorf(toXYZ);
902
903                // It is not uncommon to see missing or empty gamma tags.  This indicates
904                // that we should use unit gamma.
905                SkGammaCurve curves[3];
906                r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC);
907                g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC);
908                b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC);
909                if (!r || !load_gammas(&curves[0], 1, r->addr((const uint8_t*) base), r->fLength))
910                {
911                    SkColorSpacePrintf("Failed to read R gamma tag.\n");
912                    curves[0].fNamed = SkColorSpace::kLinear_GammaNamed;
913                }
914                if (!g || !load_gammas(&curves[1], 1, g->addr((const uint8_t*) base), g->fLength))
915                {
916                    SkColorSpacePrintf("Failed to read G gamma tag.\n");
917                    curves[1].fNamed = SkColorSpace::kLinear_GammaNamed;
918                }
919                if (!b || !load_gammas(&curves[2], 1, b->addr((const uint8_t*) base), b->fLength))
920                {
921                    SkColorSpacePrintf("Failed to read B gamma tag.\n");
922                    curves[2].fNamed = SkColorSpace::kLinear_GammaNamed;
923                }
924
925                GammaNamed gammaNamed = SkGammas::Named(curves);
926                if (kNonStandard_GammaNamed == gammaNamed) {
927                    sk_sp<SkGammas> gammas = sk_make_sp<SkGammas>(std::move(curves[0]),
928                                                                  std::move(curves[1]),
929                                                                  std::move(curves[2]));
930                    return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, std::move(gammas),
931                                                                     mat, std::move(data)));
932                } else {
933                    return SkColorSpace_Base::NewRGB(gammaNamed, mat, std::move(data));
934                }
935            }
936
937            // Recognize color profile specified by A2B0 tag.
938            const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0);
939            if (a2b0) {
940                SkAutoTDelete<SkColorLookUpTable> colorLUT(new SkColorLookUpTable());
941                SkGammaCurve curves[3];
942                SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
943                if (!load_a2b0(colorLUT, curves, &toXYZ, a2b0->addr((const uint8_t*) base),
944                               a2b0->fLength)) {
945                    return_null("Failed to parse A2B0 tag");
946                }
947
948                GammaNamed gammaNamed = SkGammas::Named(curves);
949                if (colorLUT->fTable || kNonStandard_GammaNamed == gammaNamed) {
950                    sk_sp<SkGammas> gammas = sk_make_sp<SkGammas>(std::move(curves[0]),
951                                                                  std::move(curves[1]),
952                                                                  std::move(curves[2]));
953
954                    return sk_sp<SkColorSpace>(new SkColorSpace_Base(colorLUT.release(),
955                                                                     std::move(gammas), toXYZ,
956                                                                     std::move(data)));
957                } else {
958                    return SkColorSpace_Base::NewRGB(gammaNamed, toXYZ, std::move(data));
959                }
960            }
961        }
962        default:
963            break;
964    }
965
966    return_null("ICC profile contains unsupported colorspace");
967}
968
969///////////////////////////////////////////////////////////////////////////////////////////////////
970
971// We will write a profile with the minimum nine required tags.
972static constexpr uint32_t kICCNumEntries = 9;
973
974static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c');
975static constexpr uint32_t kTAG_desc_Bytes = 12;
976static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize + kICCNumEntries*kICCTagTableEntrySize;
977
978static constexpr uint32_t kTAG_XYZ_Bytes = 20;
979static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes;
980static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes;
981static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes;
982
983static constexpr uint32_t kTAG_TRC_Bytes = 14;
984static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes;
985static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset + SkAlign4(kTAG_TRC_Bytes);
986static constexpr uint32_t kTAG_bTRC_Offset = kTAG_gTRC_Offset + SkAlign4(kTAG_TRC_Bytes);
987
988static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't');
989static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + SkAlign4(kTAG_TRC_Bytes);
990
991static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't');
992static constexpr uint32_t kTAG_cprt_Bytes = 12;
993static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes;
994
995static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes;
996
997static constexpr uint32_t gICCHeader[kICCHeaderSize / 4] {
998    SkEndian_SwapBE32(kICCProfileSize),  // Size of the profile
999    0,                                   // Preferred CMM type (ignored)
1000    SkEndian_SwapBE32(0x02100000),       // Version 2.1
1001    SkEndian_SwapBE32(kDisplay_Profile), // Display device profile
1002    SkEndian_SwapBE32(kRGB_ColorSpace),  // RGB input color space
1003    SkEndian_SwapBE32(kXYZ_PCSSpace),    // XYZ profile connection space
1004    0, 0, 0,                             // Date and time (ignored)
1005    SkEndian_SwapBE32(kACSP_Signature),  // Profile signature
1006    0,                                   // Platform target (ignored)
1007    0x00000000,                          // Flags: not embedded, can be used independently
1008    0,                                   // Device manufacturer (ignored)
1009    0,                                   // Device model (ignored)
1010    0, 0,                                // Device attributes (ignored)
1011    SkEndian_SwapBE32(1),                // Relative colorimetric rendering intent
1012    SkEndian_SwapBE32(0x0000f6d6),       // D50 standard illuminant (X)
1013    SkEndian_SwapBE32(0x00010000),       // D50 standard illuminant (Y)
1014    SkEndian_SwapBE32(0x0000d32d),       // D50 standard illuminant (Z)
1015    0,                                   // Profile creator (ignored)
1016    0, 0, 0, 0,                          // Profile id checksum (ignored)
1017    0, 0, 0, 0, 0, 0, 0,                 // Reserved (ignored)
1018    SkEndian_SwapBE32(kICCNumEntries),   // Number of tags
1019};
1020
1021static constexpr uint32_t gICCTagTable[3 * kICCNumEntries] {
1022    // Profile description
1023    SkEndian_SwapBE32(kTAG_desc),
1024    SkEndian_SwapBE32(kTAG_desc_Offset),
1025    SkEndian_SwapBE32(kTAG_desc_Bytes),
1026
1027    // rXYZ
1028    SkEndian_SwapBE32(kTAG_rXYZ),
1029    SkEndian_SwapBE32(kTAG_rXYZ_Offset),
1030    SkEndian_SwapBE32(kTAG_XYZ_Bytes),
1031
1032    // gXYZ
1033    SkEndian_SwapBE32(kTAG_gXYZ),
1034    SkEndian_SwapBE32(kTAG_gXYZ_Offset),
1035    SkEndian_SwapBE32(kTAG_XYZ_Bytes),
1036
1037    // bXYZ
1038    SkEndian_SwapBE32(kTAG_bXYZ),
1039    SkEndian_SwapBE32(kTAG_bXYZ_Offset),
1040    SkEndian_SwapBE32(kTAG_XYZ_Bytes),
1041
1042    // rTRC
1043    SkEndian_SwapBE32(kTAG_rTRC),
1044    SkEndian_SwapBE32(kTAG_rTRC_Offset),
1045    SkEndian_SwapBE32(kTAG_TRC_Bytes),
1046
1047    // gTRC
1048    SkEndian_SwapBE32(kTAG_gTRC),
1049    SkEndian_SwapBE32(kTAG_gTRC_Offset),
1050    SkEndian_SwapBE32(kTAG_TRC_Bytes),
1051
1052    // bTRC
1053    SkEndian_SwapBE32(kTAG_bTRC),
1054    SkEndian_SwapBE32(kTAG_bTRC_Offset),
1055    SkEndian_SwapBE32(kTAG_TRC_Bytes),
1056
1057    // White point
1058    SkEndian_SwapBE32(kTAG_wtpt),
1059    SkEndian_SwapBE32(kTAG_wtpt_Offset),
1060    SkEndian_SwapBE32(kTAG_XYZ_Bytes),
1061
1062    // Copyright
1063    SkEndian_SwapBE32(kTAG_cprt),
1064    SkEndian_SwapBE32(kTAG_cprt_Offset),
1065    SkEndian_SwapBE32(kTAG_cprt_Bytes),
1066};
1067
1068static constexpr uint32_t kTAG_TextType = SkSetFourByteTag('m', 'l', 'u', 'c');
1069static constexpr uint32_t gEmptyTextTag[3] {
1070    SkEndian_SwapBE32(kTAG_TextType), // Type signature
1071    0,                                // Reserved
1072    0,                                // Zero records
1073};
1074
1075static void write_xyz_tag(uint32_t* ptr, const SkMatrix44& toXYZ, int row) {
1076    ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
1077    ptr[1] = 0;
1078    ptr[2] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 0)));
1079    ptr[3] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 1)));
1080    ptr[4] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 2)));
1081}
1082
1083static void write_trc_tag(uint32_t* ptr, float value) {
1084    ptr[0] = SkEndian_SwapBE32(kTAG_CurveType);
1085    ptr[1] = 0;
1086
1087    // Gamma will be specified with a single value.
1088    ptr[2] = SkEndian_SwapBE32(1);
1089
1090    // Convert gamma to 16-bit fixed point.
1091    uint16_t* ptr16 = (uint16_t*) (ptr + 3);
1092    ptr16[0] = SkEndian_SwapBE16((uint16_t) (value * 256.0f));
1093
1094    // Pad tag with zero.
1095    ptr16[1] = 0;
1096}
1097
1098static float get_gamma_value(const SkGammaCurve* curve) {
1099    switch (curve->fNamed) {
1100        case SkColorSpace::kSRGB_GammaNamed:
1101            // FIXME (msarett):
1102            // kSRGB cannot be represented by a value.  Here we fall through to 2.2f,
1103            // which is a close guess.  To be more accurate, we need to represent sRGB
1104            // gamma with a parametric curve.
1105        case SkColorSpace::k2Dot2Curve_GammaNamed:
1106            return 2.2f;
1107        case SkColorSpace::kLinear_GammaNamed:
1108            return 1.0f;
1109        default:
1110            SkASSERT(curve->isValue());
1111            return curve->fValue;
1112    }
1113}
1114
1115sk_sp<SkData> SkColorSpace_Base::writeToICC() const {
1116    // Return if this object was created from a profile, or if we have already serialized
1117    // the profile.
1118    if (fProfileData) {
1119        return fProfileData;
1120    }
1121
1122    // The client may create an SkColorSpace using an SkMatrix44, but currently we only
1123    // support writing profiles with 3x3 matrices.
1124    // TODO (msarett): Fix this!
1125    if (0.0f != fToXYZD50.getFloat(3, 0) || 0.0f != fToXYZD50.getFloat(3, 1) ||
1126        0.0f != fToXYZD50.getFloat(3, 2) || 0.0f != fToXYZD50.getFloat(0, 3) ||
1127        0.0f != fToXYZD50.getFloat(1, 3) || 0.0f != fToXYZD50.getFloat(2, 3))
1128    {
1129        return nullptr;
1130    }
1131
1132    SkAutoMalloc profile(kICCProfileSize);
1133    uint8_t* ptr = (uint8_t*) profile.get();
1134
1135    // Write profile header
1136    memcpy(ptr, gICCHeader, sizeof(gICCHeader));
1137    ptr += sizeof(gICCHeader);
1138
1139    // Write tag table
1140    memcpy(ptr, gICCTagTable, sizeof(gICCTagTable));
1141    ptr += sizeof(gICCTagTable);
1142
1143    // Write profile description tag
1144    memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag));
1145    ptr += sizeof(gEmptyTextTag);
1146
1147    // Write XYZ tags
1148    write_xyz_tag((uint32_t*) ptr, fToXYZD50, 0);
1149    ptr += kTAG_XYZ_Bytes;
1150    write_xyz_tag((uint32_t*) ptr, fToXYZD50, 1);
1151    ptr += kTAG_XYZ_Bytes;
1152    write_xyz_tag((uint32_t*) ptr, fToXYZD50, 2);
1153    ptr += kTAG_XYZ_Bytes;
1154
1155    // Write TRC tags
1156    GammaNamed gammaNamed = this->gammaNamed();
1157    if (kNonStandard_GammaNamed == gammaNamed) {
1158        write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->fRed));
1159        ptr += SkAlign4(kTAG_TRC_Bytes);
1160        write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->fGreen));
1161        ptr += SkAlign4(kTAG_TRC_Bytes);
1162        write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->fBlue));
1163        ptr += SkAlign4(kTAG_TRC_Bytes);
1164    } else {
1165        switch (gammaNamed) {
1166            case SkColorSpace::kSRGB_GammaNamed:
1167                // FIXME (msarett):
1168                // kSRGB cannot be represented by a value.  Here we fall through to 2.2f,
1169                // which is a close guess.  To be more accurate, we need to represent sRGB
1170                // gamma with a parametric curve.
1171            case SkColorSpace::k2Dot2Curve_GammaNamed:
1172                write_trc_tag((uint32_t*) ptr, 2.2f);
1173                ptr += SkAlign4(kTAG_TRC_Bytes);
1174                write_trc_tag((uint32_t*) ptr, 2.2f);
1175                ptr += SkAlign4(kTAG_TRC_Bytes);
1176                write_trc_tag((uint32_t*) ptr, 2.2f);
1177                ptr += SkAlign4(kTAG_TRC_Bytes);
1178                break;
1179            case SkColorSpace::kLinear_GammaNamed:
1180                write_trc_tag((uint32_t*) ptr, 1.0f);
1181                ptr += SkAlign4(kTAG_TRC_Bytes);
1182                write_trc_tag((uint32_t*) ptr, 1.0f);
1183                ptr += SkAlign4(kTAG_TRC_Bytes);
1184                write_trc_tag((uint32_t*) ptr, 1.0f);
1185                ptr += SkAlign4(kTAG_TRC_Bytes);
1186                break;
1187            default:
1188                SkASSERT(false);
1189                break;
1190        }
1191    }
1192
1193    // Write white point tag
1194    uint32_t* ptr32 = (uint32_t*) ptr;
1195    ptr32[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
1196    ptr32[1] = 0;
1197    // TODO (msarett): These values correspond to the D65 white point.  This may not always be
1198    //                 correct.
1199    ptr32[2] = SkEndian_SwapBE32(0x0000f351);
1200    ptr32[3] = SkEndian_SwapBE32(0x00010000);
1201    ptr32[4] = SkEndian_SwapBE32(0x000116cc);
1202    ptr += kTAG_XYZ_Bytes;
1203
1204    // Write copyright tag
1205    memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag));
1206
1207    // TODO (msarett): Should we try to hold onto the data so we can return immediately if
1208    //                 the client calls again?
1209    return SkData::MakeFromMalloc(profile.release(), kICCProfileSize);
1210}
1211