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