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 "SkAutoMalloc.h"
9#include "SkColorSpacePriv.h"
10#include "SkColorSpaceXformPriv.h"
11#include "SkColorSpace_Base.h"
12#include "SkColorSpace_XYZ.h"
13#include "SkEndian.h"
14#include "SkFixed.h"
15#include "SkICC.h"
16#include "SkICCPriv.h"
17#include "SkMD5.h"
18#include "SkString.h"
19#include "SkUtils.h"
20
21SkICC::SkICC(sk_sp<SkColorSpace> colorSpace)
22    : fColorSpace(std::move(colorSpace))
23{}
24
25sk_sp<SkICC> SkICC::Make(const void* ptr, size_t len) {
26    sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeICC(ptr, len);
27    if (!colorSpace) {
28        return nullptr;
29    }
30
31    return sk_sp<SkICC>(new SkICC(std::move(colorSpace)));
32}
33
34bool SkICC::toXYZD50(SkMatrix44* toXYZD50) const {
35    const SkMatrix44* m = as_CSB(fColorSpace)->toXYZD50();
36    if (!m) {
37        return false;
38    }
39
40    *toXYZD50 = *m;
41    return true;
42}
43
44bool SkICC::isNumericalTransferFn(SkColorSpaceTransferFn* coeffs) const {
45    return as_CSB(fColorSpace)->onIsNumericalTransferFn(coeffs);
46}
47
48static const int kDefaultTableSize = 512; // Arbitrary
49
50void fn_to_table(float* tablePtr, const SkColorSpaceTransferFn& fn) {
51    // Y = (aX + b)^g + e  for X >= d
52    // Y = cX + f          otherwise
53    for (int i = 0; i < kDefaultTableSize; i++) {
54        float x = ((float) i) / ((float) (kDefaultTableSize - 1));
55        if (x >= fn.fD) {
56            tablePtr[i] = clamp_0_1(powf(fn.fA * x + fn.fB, fn.fG) + fn.fE);
57        } else {
58            tablePtr[i] = clamp_0_1(fn.fC * x + fn.fF);
59        }
60    }
61}
62
63void copy_to_table(float* tablePtr, const SkGammas* gammas, int index) {
64    SkASSERT(gammas->isTable(index));
65    const float* ptr = gammas->table(index);
66    const size_t bytes = gammas->tableSize(index) * sizeof(float);
67    memcpy(tablePtr, ptr, bytes);
68}
69
70bool SkICC::rawTransferFnData(Tables* tables) const {
71    if (SkColorSpace_Base::Type::kA2B == as_CSB(fColorSpace)->type()) {
72        return false;
73    }
74    SkColorSpace_XYZ* colorSpace = (SkColorSpace_XYZ*) fColorSpace.get();
75
76    SkColorSpaceTransferFn fn;
77    if (this->isNumericalTransferFn(&fn)) {
78        tables->fStorage = SkData::MakeUninitialized(kDefaultTableSize * sizeof(float));
79        fn_to_table((float*) tables->fStorage->writable_data(), fn);
80        tables->fRed.fOffset = tables->fGreen.fOffset = tables->fBlue.fOffset = 0;
81        tables->fRed.fCount = tables->fGreen.fCount = tables->fBlue.fCount = kDefaultTableSize;
82        return true;
83    }
84
85    const SkGammas* gammas = colorSpace->gammas();
86    SkASSERT(gammas);
87    if (gammas->data(0) == gammas->data(1) && gammas->data(0) == gammas->data(2)) {
88        SkASSERT(gammas->isTable(0));
89        tables->fStorage = SkData::MakeUninitialized(gammas->tableSize(0) * sizeof(float));
90        copy_to_table((float*) tables->fStorage->writable_data(), gammas, 0);
91        tables->fRed.fOffset = tables->fGreen.fOffset = tables->fBlue.fOffset = 0;
92        tables->fRed.fCount = tables->fGreen.fCount = tables->fBlue.fCount = gammas->tableSize(0);
93        return true;
94    }
95
96    // Determine the storage size.
97    size_t storageSize = 0;
98    for (int i = 0; i < 3; i++) {
99        if (gammas->isTable(i)) {
100            storageSize += gammas->tableSize(i) * sizeof(float);
101        } else {
102            storageSize += kDefaultTableSize * sizeof(float);
103        }
104    }
105
106    // Fill in the tables.
107    tables->fStorage = SkData::MakeUninitialized(storageSize);
108    float* ptr = (float*) tables->fStorage->writable_data();
109    size_t offset = 0;
110    Channel rgb[3];
111    for (int i = 0; i < 3; i++) {
112        if (gammas->isTable(i)) {
113            copy_to_table(ptr, gammas, i);
114            rgb[i].fOffset = offset;
115            rgb[i].fCount = gammas->tableSize(i);
116            offset += rgb[i].fCount * sizeof(float);
117            ptr += rgb[i].fCount;
118            continue;
119        }
120
121        if (gammas->isNamed(i)) {
122            SkAssertResult(named_to_parametric(&fn, gammas->data(i).fNamed));
123        } else if (gammas->isValue(i)) {
124            value_to_parametric(&fn, gammas->data(i).fValue);
125        } else {
126            SkASSERT(gammas->isParametric(i));
127            fn = gammas->params(i);
128        }
129
130        fn_to_table(ptr, fn);
131        rgb[i].fOffset = offset;
132        rgb[i].fCount = kDefaultTableSize;
133        offset += kDefaultTableSize * sizeof(float);
134        ptr += kDefaultTableSize;
135    }
136
137    tables->fRed = rgb[0];
138    tables->fGreen = rgb[1];
139    tables->fBlue = rgb[2];
140    return true;
141}
142
143///////////////////////////////////////////////////////////////////////////////////////////////////
144
145static constexpr char kDescriptionTagBodyPrefix[12] =
146        { 'G', 'o', 'o', 'g', 'l', 'e', '/', 'S', 'k', 'i', 'a' , '/'};
147
148static constexpr size_t kICCDescriptionTagSize = 44;
149
150static_assert(kICCDescriptionTagSize ==
151              sizeof(kDescriptionTagBodyPrefix) + 2 * sizeof(SkMD5::Digest), "");
152static constexpr size_t kDescriptionTagBodySize = kICCDescriptionTagSize * 2;  // ascii->utf16be
153
154static_assert(SkIsAlign4(kDescriptionTagBodySize), "Description must be aligned to 4-bytes.");
155static constexpr uint32_t kDescriptionTagHeader[7] {
156    SkEndian_SwapBE32(kTAG_TextType),                        // Type signature
157    0,                                                       // Reserved
158    SkEndian_SwapBE32(1),                                    // Number of records
159    SkEndian_SwapBE32(12),                                   // Record size (must be 12)
160    SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
161    SkEndian_SwapBE32(kDescriptionTagBodySize),              // Length of string
162    SkEndian_SwapBE32(28),                                   // Offset of string
163};
164
165static constexpr uint32_t kWhitePointTag[5] {
166    SkEndian_SwapBE32(kXYZ_PCSSpace),
167    0,
168    SkEndian_SwapBE32(0x0000f6d6), // X = 0.96420 (D50)
169    SkEndian_SwapBE32(0x00010000), // Y = 1.00000 (D50)
170    SkEndian_SwapBE32(0x0000d32d), // Z = 0.82491 (D50)
171};
172
173// Google Inc. 2016 (UTF-16)
174static constexpr uint8_t kCopyrightTagBody[] = {
175        0x00, 0x47, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x67, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00,
176        0x49, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x2e, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31,
177        0x00, 0x36,
178};
179static_assert(SkIsAlign4(sizeof(kCopyrightTagBody)), "Copyright must be aligned to 4-bytes.");
180static constexpr uint32_t kCopyrightTagHeader[7] {
181    SkEndian_SwapBE32(kTAG_TextType),                        // Type signature
182    0,                                                       // Reserved
183    SkEndian_SwapBE32(1),                                    // Number of records
184    SkEndian_SwapBE32(12),                                   // Record size (must be 12)
185    SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
186    SkEndian_SwapBE32(sizeof(kCopyrightTagBody)),            // Length of string
187    SkEndian_SwapBE32(28),                                   // Offset of string
188};
189
190// We will write a profile with the minimum nine required tags.
191static constexpr uint32_t kICCNumEntries = 9;
192
193static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c');
194static constexpr uint32_t kTAG_desc_Bytes = sizeof(kDescriptionTagHeader) +
195                                            kDescriptionTagBodySize;
196static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize +
197                                             kICCNumEntries * kICCTagTableEntrySize;
198
199static constexpr uint32_t kTAG_XYZ_Bytes = 20;
200static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes;
201static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes;
202static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes;
203
204static constexpr uint32_t kTAG_TRC_Bytes = 40;
205static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes;
206static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset;
207static constexpr uint32_t kTAG_bTRC_Offset = kTAG_rTRC_Offset;
208
209static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't');
210static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + kTAG_TRC_Bytes;
211
212static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't');
213static constexpr uint32_t kTAG_cprt_Bytes = sizeof(kCopyrightTagHeader) +
214                                            sizeof(kCopyrightTagBody);
215static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes;
216
217static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes;
218
219static constexpr uint32_t kICCHeader[kICCHeaderSize / 4] {
220    SkEndian_SwapBE32(kICCProfileSize),  // Size of the profile
221    0,                                   // Preferred CMM type (ignored)
222    SkEndian_SwapBE32(0x02100000),       // Version 2.1
223    SkEndian_SwapBE32(kDisplay_Profile), // Display device profile
224    SkEndian_SwapBE32(kRGB_ColorSpace),  // RGB input color space
225    SkEndian_SwapBE32(kXYZ_PCSSpace),    // XYZ profile connection space
226    0, 0, 0,                             // Date and time (ignored)
227    SkEndian_SwapBE32(kACSP_Signature),  // Profile signature
228    0,                                   // Platform target (ignored)
229    0x00000000,                          // Flags: not embedded, can be used independently
230    0,                                   // Device manufacturer (ignored)
231    0,                                   // Device model (ignored)
232    0, 0,                                // Device attributes (ignored)
233    SkEndian_SwapBE32(1),                // Relative colorimetric rendering intent
234    SkEndian_SwapBE32(0x0000f6d6),       // D50 standard illuminant (X)
235    SkEndian_SwapBE32(0x00010000),       // D50 standard illuminant (Y)
236    SkEndian_SwapBE32(0x0000d32d),       // D50 standard illuminant (Z)
237    0,                                   // Profile creator (ignored)
238    0, 0, 0, 0,                          // Profile id checksum (ignored)
239    0, 0, 0, 0, 0, 0, 0,                 // Reserved (ignored)
240    SkEndian_SwapBE32(kICCNumEntries),   // Number of tags
241};
242
243static constexpr uint32_t kICCTagTable[3 * kICCNumEntries] {
244    // Profile description
245    SkEndian_SwapBE32(kTAG_desc),
246    SkEndian_SwapBE32(kTAG_desc_Offset),
247    SkEndian_SwapBE32(kTAG_desc_Bytes),
248
249    // rXYZ
250    SkEndian_SwapBE32(kTAG_rXYZ),
251    SkEndian_SwapBE32(kTAG_rXYZ_Offset),
252    SkEndian_SwapBE32(kTAG_XYZ_Bytes),
253
254    // gXYZ
255    SkEndian_SwapBE32(kTAG_gXYZ),
256    SkEndian_SwapBE32(kTAG_gXYZ_Offset),
257    SkEndian_SwapBE32(kTAG_XYZ_Bytes),
258
259    // bXYZ
260    SkEndian_SwapBE32(kTAG_bXYZ),
261    SkEndian_SwapBE32(kTAG_bXYZ_Offset),
262    SkEndian_SwapBE32(kTAG_XYZ_Bytes),
263
264    // rTRC
265    SkEndian_SwapBE32(kTAG_rTRC),
266    SkEndian_SwapBE32(kTAG_rTRC_Offset),
267    SkEndian_SwapBE32(kTAG_TRC_Bytes),
268
269    // gTRC
270    SkEndian_SwapBE32(kTAG_gTRC),
271    SkEndian_SwapBE32(kTAG_gTRC_Offset),
272    SkEndian_SwapBE32(kTAG_TRC_Bytes),
273
274    // bTRC
275    SkEndian_SwapBE32(kTAG_bTRC),
276    SkEndian_SwapBE32(kTAG_bTRC_Offset),
277    SkEndian_SwapBE32(kTAG_TRC_Bytes),
278
279    // White point
280    SkEndian_SwapBE32(kTAG_wtpt),
281    SkEndian_SwapBE32(kTAG_wtpt_Offset),
282    SkEndian_SwapBE32(kTAG_XYZ_Bytes),
283
284    // Copyright
285    SkEndian_SwapBE32(kTAG_cprt),
286    SkEndian_SwapBE32(kTAG_cprt_Offset),
287    SkEndian_SwapBE32(kTAG_cprt_Bytes),
288};
289
290static void write_xyz_tag(uint32_t* ptr, const SkMatrix44& toXYZ, int col) {
291    ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
292    ptr[1] = 0;
293    ptr[2] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(0, col)));
294    ptr[3] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(1, col)));
295    ptr[4] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(2, col)));
296}
297
298static void write_trc_tag(uint32_t* ptr, const SkColorSpaceTransferFn& fn) {
299    ptr[0] = SkEndian_SwapBE32(kTAG_ParaCurveType);
300    ptr[1] = 0;
301    ptr[2] = (uint32_t) (SkEndian_SwapBE16(kGABCDEF_ParaCurveType));
302    ptr[3] = SkEndian_SwapBE32(SkFloatToFixed(fn.fG));
303    ptr[4] = SkEndian_SwapBE32(SkFloatToFixed(fn.fA));
304    ptr[5] = SkEndian_SwapBE32(SkFloatToFixed(fn.fB));
305    ptr[6] = SkEndian_SwapBE32(SkFloatToFixed(fn.fC));
306    ptr[7] = SkEndian_SwapBE32(SkFloatToFixed(fn.fD));
307    ptr[8] = SkEndian_SwapBE32(SkFloatToFixed(fn.fE));
308    ptr[9] = SkEndian_SwapBE32(SkFloatToFixed(fn.fF));
309}
310
311static bool is_3x3(const SkMatrix44& toXYZD50) {
312    return 0.0f == toXYZD50.get(3, 0) && 0.0f == toXYZD50.get(3, 1) && 0.0f == toXYZD50.get(3, 2) &&
313           0.0f == toXYZD50.get(0, 3) && 0.0f == toXYZD50.get(1, 3) && 0.0f == toXYZD50.get(2, 3) &&
314           1.0f == toXYZD50.get(3, 3);
315}
316
317static bool nearly_equal(float x, float y) {
318    // A note on why I chose this tolerance:  transfer_fn_almost_equal() uses a
319    // tolerance of 0.001f, which doesn't seem to be enough to distinguish
320    // between similar transfer functions, for example: gamma2.2 and sRGB.
321    //
322    // If the tolerance is 0.0f, then this we can't distinguish between two
323    // different encodings of what is clearly the same colorspace.  Some
324    // experimentation with example files lead to this number:
325    static constexpr float kTolerance = 1.0f / (1 << 11);
326    return ::fabsf(x - y) <= kTolerance;
327}
328
329static bool nearly_equal(const SkColorSpaceTransferFn& u,
330                         const SkColorSpaceTransferFn& v) {
331    return nearly_equal(u.fG, v.fG)
332        && nearly_equal(u.fA, v.fA)
333        && nearly_equal(u.fB, v.fB)
334        && nearly_equal(u.fC, v.fC)
335        && nearly_equal(u.fD, v.fD)
336        && nearly_equal(u.fE, v.fE)
337        && nearly_equal(u.fF, v.fF);
338}
339
340static bool nearly_equal(const SkMatrix44& toXYZD50, const float standard[9]) {
341    return nearly_equal(toXYZD50.getFloat(0, 0), standard[0])
342        && nearly_equal(toXYZD50.getFloat(0, 1), standard[1])
343        && nearly_equal(toXYZD50.getFloat(0, 2), standard[2])
344        && nearly_equal(toXYZD50.getFloat(1, 0), standard[3])
345        && nearly_equal(toXYZD50.getFloat(1, 1), standard[4])
346        && nearly_equal(toXYZD50.getFloat(1, 2), standard[5])
347        && nearly_equal(toXYZD50.getFloat(2, 0), standard[6])
348        && nearly_equal(toXYZD50.getFloat(2, 1), standard[7])
349        && nearly_equal(toXYZD50.getFloat(2, 2), standard[8])
350        && nearly_equal(toXYZD50.getFloat(0, 3), 0.0f)
351        && nearly_equal(toXYZD50.getFloat(1, 3), 0.0f)
352        && nearly_equal(toXYZD50.getFloat(2, 3), 0.0f)
353        && nearly_equal(toXYZD50.getFloat(3, 0), 0.0f)
354        && nearly_equal(toXYZD50.getFloat(3, 1), 0.0f)
355        && nearly_equal(toXYZD50.getFloat(3, 2), 0.0f)
356        && nearly_equal(toXYZD50.getFloat(3, 3), 1.0f);
357}
358
359// Return nullptr if the color profile doen't have a special name.
360const char* get_color_profile_description(const SkColorSpaceTransferFn& fn,
361                                          const SkMatrix44& toXYZD50) {
362    bool srgb_xfer = nearly_equal(fn, gSRGB_TransferFn);
363    bool srgb_gamut = nearly_equal(toXYZD50, gSRGB_toXYZD50);
364    if (srgb_xfer && srgb_gamut) {
365        return "sRGB";
366    }
367    bool line_xfer = nearly_equal(fn, gLinear_TransferFn);
368    if (line_xfer && srgb_gamut) {
369        return "Linear Transfer with sRGB Gamut";
370    }
371    bool twoDotTwo = nearly_equal(fn, g2Dot2_TransferFn);
372    if (twoDotTwo && srgb_gamut) {
373        return "2.2 Transfer with sRGB Gamut";
374    }
375    if (twoDotTwo && nearly_equal(toXYZD50, gAdobeRGB_toXYZD50)) {
376        return "AdobeRGB";
377    }
378    bool dcip3_gamut = nearly_equal(toXYZD50, gDCIP3_toXYZD50);
379    if (srgb_xfer || line_xfer) {
380        if (srgb_xfer && dcip3_gamut) {
381            return "sRGB Transfer with DCI-P3 Gamut";
382        }
383        if (line_xfer && dcip3_gamut) {
384            return "Linear Transfer with DCI-P3 Gamut";
385        }
386        bool rec2020 = nearly_equal(toXYZD50, gRec2020_toXYZD50);
387        if (srgb_xfer && rec2020) {
388            return "sRGB Transfer with Rec-BT-2020 Gamut";
389        }
390        if (line_xfer && rec2020) {
391            return "Linear Transfer with Rec-BT-2020 Gamut";
392        }
393    }
394    if (dcip3_gamut && nearly_equal(fn, gDCIP3_TransferFn)) {
395        return "DCI-P3";
396    }
397    return nullptr;
398}
399
400static void get_color_profile_tag(char dst[kICCDescriptionTagSize],
401                                 const SkColorSpaceTransferFn& fn,
402                                 const SkMatrix44& toXYZD50) {
403    SkASSERT(dst);
404    if (const char* description = get_color_profile_description(fn, toXYZD50)) {
405        SkASSERT(strlen(description) < kICCDescriptionTagSize);
406        strncpy(dst, description, kICCDescriptionTagSize);
407        // "If the length of src is less than n, strncpy() writes additional
408        // null bytes to dest to ensure that a total of n bytes are written."
409    } else {
410        strncpy(dst, kDescriptionTagBodyPrefix, sizeof(kDescriptionTagBodyPrefix));
411        SkMD5 md5;
412        for (int i = 0; i < 3; ++i) {
413            for (int j = 0; j < 3; ++j) {
414                float value = toXYZD50.getFloat(i,j);
415                md5.write(&value, sizeof(value));
416            }
417        }
418        static_assert(sizeof(fn) == sizeof(float) * 7, "packed");
419        md5.write(&fn, sizeof(fn));
420        SkMD5::Digest digest;
421        md5.finish(digest);
422        char* ptr = dst + sizeof(kDescriptionTagBodyPrefix);
423        for (unsigned i = 0; i < sizeof(SkMD5::Digest); ++i) {
424            uint8_t byte = digest.data[i];
425            *ptr++ = SkHexadecimalDigits::gUpper[byte >> 4];
426            *ptr++ = SkHexadecimalDigits::gUpper[byte & 0xF];
427        }
428        SkASSERT(ptr == dst + kICCDescriptionTagSize);
429    }
430}
431
432SkString SkICCGetColorProfileTag(const SkColorSpaceTransferFn& fn,
433                                 const SkMatrix44& toXYZD50) {
434    char tag[kICCDescriptionTagSize];
435    get_color_profile_tag(tag, fn, toXYZD50);
436    size_t len = kICCDescriptionTagSize;
437    while (len > 0 && tag[len - 1] == '\0') {
438        --len;  // tag is padded out with zeros
439    }
440    SkASSERT(len != 0);
441    return SkString(tag, len);
442}
443
444// returns pointer just beyond where we just wrote.
445static uint8_t* string_copy_ascii_to_utf16be(uint8_t* dst, const char* src, size_t count) {
446    while (count-- > 0) {
447        *dst++ = 0;
448        *dst++ = (uint8_t)(*src++);
449    }
450    return dst;
451}
452
453sk_sp<SkData> SkICC::WriteToICC(const SkColorSpaceTransferFn& fn, const SkMatrix44& toXYZD50) {
454    if (!is_3x3(toXYZD50) || !is_valid_transfer_fn(fn)) {
455        return nullptr;
456    }
457
458    SkAutoMalloc profile(kICCProfileSize);
459    uint8_t* ptr = (uint8_t*) profile.get();
460
461    // Write profile header
462    memcpy(ptr, kICCHeader, sizeof(kICCHeader));
463    ptr += sizeof(kICCHeader);
464
465    // Write tag table
466    memcpy(ptr, kICCTagTable, sizeof(kICCTagTable));
467    ptr += sizeof(kICCTagTable);
468
469    // Write profile description tag
470    memcpy(ptr, kDescriptionTagHeader, sizeof(kDescriptionTagHeader));
471    ptr += sizeof(kDescriptionTagHeader);
472    {
473        char colorProfileTag[kICCDescriptionTagSize];
474        get_color_profile_tag(colorProfileTag, fn, toXYZD50);
475        ptr = string_copy_ascii_to_utf16be(ptr, colorProfileTag, kICCDescriptionTagSize);
476    }
477
478    // Write XYZ tags
479    write_xyz_tag((uint32_t*) ptr, toXYZD50, 0);
480    ptr += kTAG_XYZ_Bytes;
481    write_xyz_tag((uint32_t*) ptr, toXYZD50, 1);
482    ptr += kTAG_XYZ_Bytes;
483    write_xyz_tag((uint32_t*) ptr, toXYZD50, 2);
484    ptr += kTAG_XYZ_Bytes;
485
486    // Write TRC tag
487    write_trc_tag((uint32_t*) ptr, fn);
488    ptr += kTAG_TRC_Bytes;
489
490    // Write white point tag (must be D50)
491    memcpy(ptr, kWhitePointTag, sizeof(kWhitePointTag));
492    ptr += sizeof(kWhitePointTag);
493
494    // Write copyright tag
495    memcpy(ptr, kCopyrightTagHeader, sizeof(kCopyrightTagHeader));
496    ptr += sizeof(kCopyrightTagHeader);
497    memcpy(ptr, kCopyrightTagBody, sizeof(kCopyrightTagBody));
498    ptr += sizeof(kCopyrightTagBody);
499
500    SkASSERT(kICCProfileSize == ptr - (uint8_t*) profile.get());
501    return SkData::MakeFromMalloc(profile.release(), kICCProfileSize);
502}
503