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