blob: 1f92424aff3cca56ee5c90b8caa4b872b33ee0e5 [file] [log] [blame]
Ravi Mistry113d05f2016-12-17 01:31:03 +00001/*
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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "include/core/SkICC.h"
9#include "include/private/SkFixed.h"
10#include "src/core/SkAutoMalloc.h"
11#include "src/core/SkColorSpacePriv.h"
12#include "src/core/SkEndian.h"
13#include "src/core/SkICCPriv.h"
14#include "src/core/SkMD5.h"
15#include "src/core/SkUtils.h"
Ravi Mistry113d05f2016-12-17 01:31:03 +000016
Hal Canary7a808d62017-06-16 13:38:34 -040017static constexpr char kDescriptionTagBodyPrefix[12] =
18 { 'G', 'o', 'o', 'g', 'l', 'e', '/', 'S', 'k', 'i', 'a' , '/'};
Hal Canarya8565e52017-06-19 12:43:28 -040019
20static constexpr size_t kICCDescriptionTagSize = 44;
21
22static_assert(kICCDescriptionTagSize ==
23 sizeof(kDescriptionTagBodyPrefix) + 2 * sizeof(SkMD5::Digest), "");
24static constexpr size_t kDescriptionTagBodySize = kICCDescriptionTagSize * 2; // ascii->utf16be
Hal Canary7a808d62017-06-16 13:38:34 -040025
26static_assert(SkIsAlign4(kDescriptionTagBodySize), "Description must be aligned to 4-bytes.");
Matt Sarett1bfcf882016-12-19 18:37:34 -050027static constexpr uint32_t kDescriptionTagHeader[7] {
28 SkEndian_SwapBE32(kTAG_TextType), // Type signature
29 0, // Reserved
30 SkEndian_SwapBE32(1), // Number of records
31 SkEndian_SwapBE32(12), // Record size (must be 12)
32 SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
Hal Canary7a808d62017-06-16 13:38:34 -040033 SkEndian_SwapBE32(kDescriptionTagBodySize), // Length of string
Matt Sarett1bfcf882016-12-19 18:37:34 -050034 SkEndian_SwapBE32(28), // Offset of string
35};
36
37static constexpr uint32_t kWhitePointTag[5] {
38 SkEndian_SwapBE32(kXYZ_PCSSpace),
39 0,
40 SkEndian_SwapBE32(0x0000f6d6), // X = 0.96420 (D50)
41 SkEndian_SwapBE32(0x00010000), // Y = 1.00000 (D50)
42 SkEndian_SwapBE32(0x0000d32d), // Z = 0.82491 (D50)
43};
44
45// Google Inc. 2016 (UTF-16)
46static constexpr uint8_t kCopyrightTagBody[] = {
Mike Klein5b58b7c2018-05-22 13:47:52 +000047 0x00, 0x47, 0x00, 0x6f,
48 0x00, 0x6f, 0x00, 0x67,
49 0x00, 0x6c, 0x00, 0x65,
50 0x00, 0x20, 0x00, 0x49,
51 0x00, 0x6e, 0x00, 0x63,
52 0x00, 0x2e, 0x00, 0x20,
53 0x00, 0x32, 0x00, 0x30,
54 0x00, 0x31, 0x00, 0x36,
Matt Sarett1bfcf882016-12-19 18:37:34 -050055};
56static_assert(SkIsAlign4(sizeof(kCopyrightTagBody)), "Copyright must be aligned to 4-bytes.");
57static constexpr uint32_t kCopyrightTagHeader[7] {
58 SkEndian_SwapBE32(kTAG_TextType), // Type signature
59 0, // Reserved
60 SkEndian_SwapBE32(1), // Number of records
61 SkEndian_SwapBE32(12), // Record size (must be 12)
62 SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
63 SkEndian_SwapBE32(sizeof(kCopyrightTagBody)), // Length of string
64 SkEndian_SwapBE32(28), // Offset of string
65};
66
Matt Sarett8e042862016-12-19 09:43:30 -050067// We will write a profile with the minimum nine required tags.
68static constexpr uint32_t kICCNumEntries = 9;
69
70static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c');
Matt Sarett1bfcf882016-12-19 18:37:34 -050071static constexpr uint32_t kTAG_desc_Bytes = sizeof(kDescriptionTagHeader) +
Hal Canary7a808d62017-06-16 13:38:34 -040072 kDescriptionTagBodySize;
Matt Sarett1bfcf882016-12-19 18:37:34 -050073static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize +
74 kICCNumEntries * kICCTagTableEntrySize;
Matt Sarett8e042862016-12-19 09:43:30 -050075
76static constexpr uint32_t kTAG_XYZ_Bytes = 20;
77static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes;
78static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes;
79static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes;
80
Matt Sarett1bfcf882016-12-19 18:37:34 -050081static constexpr uint32_t kTAG_TRC_Bytes = 40;
Matt Sarett8e042862016-12-19 09:43:30 -050082static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes;
Matt Sarett1bfcf882016-12-19 18:37:34 -050083static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset;
84static constexpr uint32_t kTAG_bTRC_Offset = kTAG_rTRC_Offset;
Matt Sarett8e042862016-12-19 09:43:30 -050085
86static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't');
Matt Sarett1bfcf882016-12-19 18:37:34 -050087static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + kTAG_TRC_Bytes;
Matt Sarett8e042862016-12-19 09:43:30 -050088
89static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't');
Matt Sarett1bfcf882016-12-19 18:37:34 -050090static constexpr uint32_t kTAG_cprt_Bytes = sizeof(kCopyrightTagHeader) +
91 sizeof(kCopyrightTagBody);
Matt Sarett8e042862016-12-19 09:43:30 -050092static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes;
93
94static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes;
95
Matt Sarett1bfcf882016-12-19 18:37:34 -050096static constexpr uint32_t kICCHeader[kICCHeaderSize / 4] {
Matt Sarett8e042862016-12-19 09:43:30 -050097 SkEndian_SwapBE32(kICCProfileSize), // Size of the profile
98 0, // Preferred CMM type (ignored)
Brian Osman98eb2972021-10-05 16:24:42 -040099 SkEndian_SwapBE32(0x04300000), // Version 4.3
Matt Sarett8e042862016-12-19 09:43:30 -0500100 SkEndian_SwapBE32(kDisplay_Profile), // Display device profile
101 SkEndian_SwapBE32(kRGB_ColorSpace), // RGB input color space
102 SkEndian_SwapBE32(kXYZ_PCSSpace), // XYZ profile connection space
103 0, 0, 0, // Date and time (ignored)
104 SkEndian_SwapBE32(kACSP_Signature), // Profile signature
105 0, // Platform target (ignored)
106 0x00000000, // Flags: not embedded, can be used independently
107 0, // Device manufacturer (ignored)
108 0, // Device model (ignored)
109 0, 0, // Device attributes (ignored)
110 SkEndian_SwapBE32(1), // Relative colorimetric rendering intent
111 SkEndian_SwapBE32(0x0000f6d6), // D50 standard illuminant (X)
112 SkEndian_SwapBE32(0x00010000), // D50 standard illuminant (Y)
113 SkEndian_SwapBE32(0x0000d32d), // D50 standard illuminant (Z)
114 0, // Profile creator (ignored)
115 0, 0, 0, 0, // Profile id checksum (ignored)
116 0, 0, 0, 0, 0, 0, 0, // Reserved (ignored)
117 SkEndian_SwapBE32(kICCNumEntries), // Number of tags
118};
119
Matt Sarett1bfcf882016-12-19 18:37:34 -0500120static constexpr uint32_t kICCTagTable[3 * kICCNumEntries] {
Matt Sarett8e042862016-12-19 09:43:30 -0500121 // Profile description
122 SkEndian_SwapBE32(kTAG_desc),
123 SkEndian_SwapBE32(kTAG_desc_Offset),
124 SkEndian_SwapBE32(kTAG_desc_Bytes),
125
126 // rXYZ
127 SkEndian_SwapBE32(kTAG_rXYZ),
128 SkEndian_SwapBE32(kTAG_rXYZ_Offset),
129 SkEndian_SwapBE32(kTAG_XYZ_Bytes),
130
131 // gXYZ
132 SkEndian_SwapBE32(kTAG_gXYZ),
133 SkEndian_SwapBE32(kTAG_gXYZ_Offset),
134 SkEndian_SwapBE32(kTAG_XYZ_Bytes),
135
136 // bXYZ
137 SkEndian_SwapBE32(kTAG_bXYZ),
138 SkEndian_SwapBE32(kTAG_bXYZ_Offset),
139 SkEndian_SwapBE32(kTAG_XYZ_Bytes),
140
141 // rTRC
142 SkEndian_SwapBE32(kTAG_rTRC),
143 SkEndian_SwapBE32(kTAG_rTRC_Offset),
144 SkEndian_SwapBE32(kTAG_TRC_Bytes),
145
146 // gTRC
147 SkEndian_SwapBE32(kTAG_gTRC),
148 SkEndian_SwapBE32(kTAG_gTRC_Offset),
149 SkEndian_SwapBE32(kTAG_TRC_Bytes),
150
151 // bTRC
152 SkEndian_SwapBE32(kTAG_bTRC),
153 SkEndian_SwapBE32(kTAG_bTRC_Offset),
154 SkEndian_SwapBE32(kTAG_TRC_Bytes),
155
156 // White point
157 SkEndian_SwapBE32(kTAG_wtpt),
158 SkEndian_SwapBE32(kTAG_wtpt_Offset),
159 SkEndian_SwapBE32(kTAG_XYZ_Bytes),
160
161 // Copyright
162 SkEndian_SwapBE32(kTAG_cprt),
163 SkEndian_SwapBE32(kTAG_cprt_Offset),
164 SkEndian_SwapBE32(kTAG_cprt_Bytes),
165};
166
Brian Osmanff500082017-12-18 10:14:12 -0500167// This is like SkFloatToFixed, but rounds to nearest, preserving as much accuracy as possible
168// when going float -> fixed -> float (it has the same accuracy when going fixed -> float -> fixed).
Ravi Mistry2c98edf2020-12-10 11:57:59 -0500169// The use of double is necessary to accommodate the full potential 32-bit mantissa of the 16.16
Brian Osmanff500082017-12-18 10:14:12 -0500170// SkFixed value, and so avoiding rounding problems with float. Also, see the comment in SkFixed.h.
171static SkFixed float_round_to_fixed(float x) {
Brian Osmanff500082017-12-18 10:14:12 -0500172 return sk_float_saturate2int((float)floor((double)x * SK_Fixed1 + 0.5));
Brian Osmanff500082017-12-18 10:14:12 -0500173}
174
Brian Osman5deadca2019-01-24 12:18:17 -0500175static void write_xyz_tag(uint32_t* ptr, const skcms_Matrix3x3& toXYZD50, int col) {
Matt Sarett8e042862016-12-19 09:43:30 -0500176 ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
177 ptr[1] = 0;
Brian Osman5deadca2019-01-24 12:18:17 -0500178 ptr[2] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[0][col]));
179 ptr[3] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[1][col]));
180 ptr[4] = SkEndian_SwapBE32(float_round_to_fixed(toXYZD50.vals[2][col]));
Matt Sarett8e042862016-12-19 09:43:30 -0500181}
182
Brian Osman5deadca2019-01-24 12:18:17 -0500183static void write_trc_tag(uint32_t* ptr, const skcms_TransferFunction& fn) {
Matt Sarett1bfcf882016-12-19 18:37:34 -0500184 ptr[0] = SkEndian_SwapBE32(kTAG_ParaCurveType);
Matt Sarett8e042862016-12-19 09:43:30 -0500185 ptr[1] = 0;
Matt Sarett1bfcf882016-12-19 18:37:34 -0500186 ptr[2] = (uint32_t) (SkEndian_SwapBE16(kGABCDEF_ParaCurveType));
Brian Osman5deadca2019-01-24 12:18:17 -0500187 ptr[3] = SkEndian_SwapBE32(float_round_to_fixed(fn.g));
188 ptr[4] = SkEndian_SwapBE32(float_round_to_fixed(fn.a));
189 ptr[5] = SkEndian_SwapBE32(float_round_to_fixed(fn.b));
190 ptr[6] = SkEndian_SwapBE32(float_round_to_fixed(fn.c));
191 ptr[7] = SkEndian_SwapBE32(float_round_to_fixed(fn.d));
192 ptr[8] = SkEndian_SwapBE32(float_round_to_fixed(fn.e));
193 ptr[9] = SkEndian_SwapBE32(float_round_to_fixed(fn.f));
Matt Sarett8e042862016-12-19 09:43:30 -0500194}
195
Hal Canarya8565e52017-06-19 12:43:28 -0400196static bool nearly_equal(float x, float y) {
197 // A note on why I chose this tolerance: transfer_fn_almost_equal() uses a
198 // tolerance of 0.001f, which doesn't seem to be enough to distinguish
199 // between similar transfer functions, for example: gamma2.2 and sRGB.
200 //
201 // If the tolerance is 0.0f, then this we can't distinguish between two
202 // different encodings of what is clearly the same colorspace. Some
203 // experimentation with example files lead to this number:
204 static constexpr float kTolerance = 1.0f / (1 << 11);
205 return ::fabsf(x - y) <= kTolerance;
206}
Hal Canary7a808d62017-06-16 13:38:34 -0400207
Brian Osman5deadca2019-01-24 12:18:17 -0500208static bool nearly_equal(const skcms_TransferFunction& u,
Brian Osman82ebe042019-01-04 17:03:00 -0500209 const skcms_TransferFunction& v) {
Brian Osman5deadca2019-01-24 12:18:17 -0500210 return nearly_equal(u.g, v.g)
211 && nearly_equal(u.a, v.a)
212 && nearly_equal(u.b, v.b)
213 && nearly_equal(u.c, v.c)
214 && nearly_equal(u.d, v.d)
215 && nearly_equal(u.e, v.e)
216 && nearly_equal(u.f, v.f);
Hal Canarya8565e52017-06-19 12:43:28 -0400217}
218
Brian Osman5deadca2019-01-24 12:18:17 -0500219static bool nearly_equal(const skcms_Matrix3x3& u, const skcms_Matrix3x3& v) {
220 for (int r = 0; r < 3; r++) {
221 for (int c = 0; c < 3; c++) {
222 if (!nearly_equal(u.vals[r][c], v.vals[r][c])) {
223 return false;
224 }
Mike Klein5b58b7c2018-05-22 13:47:52 +0000225 }
226 }
227 return true;
Hal Canarya8565e52017-06-19 12:43:28 -0400228}
229
230// Return nullptr if the color profile doen't have a special name.
Brian Osman5deadca2019-01-24 12:18:17 -0500231const char* get_color_profile_description(const skcms_TransferFunction& fn,
232 const skcms_Matrix3x3& toXYZD50) {
Brian Osman82ebe042019-01-04 17:03:00 -0500233 bool srgb_xfer = nearly_equal(fn, SkNamedTransferFn::kSRGB);
Brian Osman5deadca2019-01-24 12:18:17 -0500234 bool srgb_gamut = nearly_equal(toXYZD50, SkNamedGamut::kSRGB);
Hal Canarya8565e52017-06-19 12:43:28 -0400235 if (srgb_xfer && srgb_gamut) {
236 return "sRGB";
237 }
Brian Osman82ebe042019-01-04 17:03:00 -0500238 bool line_xfer = nearly_equal(fn, SkNamedTransferFn::kLinear);
Hal Canarya8565e52017-06-19 12:43:28 -0400239 if (line_xfer && srgb_gamut) {
240 return "Linear Transfer with sRGB Gamut";
241 }
Brian Osman82ebe042019-01-04 17:03:00 -0500242 bool twoDotTwo = nearly_equal(fn, SkNamedTransferFn::k2Dot2);
Hal Canarya8565e52017-06-19 12:43:28 -0400243 if (twoDotTwo && srgb_gamut) {
244 return "2.2 Transfer with sRGB Gamut";
245 }
Brian Osman5deadca2019-01-24 12:18:17 -0500246 if (twoDotTwo && nearly_equal(toXYZD50, SkNamedGamut::kAdobeRGB)) {
Hal Canarya8565e52017-06-19 12:43:28 -0400247 return "AdobeRGB";
248 }
Mike Kleinb147ace2020-01-16 11:11:06 -0600249 bool display_p3 = nearly_equal(toXYZD50, SkNamedGamut::kDisplayP3);
Hal Canarya8565e52017-06-19 12:43:28 -0400250 if (srgb_xfer || line_xfer) {
Mike Kleinb147ace2020-01-16 11:11:06 -0600251 if (srgb_xfer && display_p3) {
252 return "sRGB Transfer with Display P3 Gamut";
Hal Canary7a808d62017-06-16 13:38:34 -0400253 }
Mike Kleinb147ace2020-01-16 11:11:06 -0600254 if (line_xfer && display_p3) {
255 return "Linear Transfer with Display P3 Gamut";
Hal Canarya8565e52017-06-19 12:43:28 -0400256 }
Brian Osman5deadca2019-01-24 12:18:17 -0500257 bool rec2020 = nearly_equal(toXYZD50, SkNamedGamut::kRec2020);
Hal Canarya8565e52017-06-19 12:43:28 -0400258 if (srgb_xfer && rec2020) {
259 return "sRGB Transfer with Rec-BT-2020 Gamut";
260 }
261 if (line_xfer && rec2020) {
262 return "Linear Transfer with Rec-BT-2020 Gamut";
263 }
264 }
Hal Canarya8565e52017-06-19 12:43:28 -0400265 return nullptr;
266}
267
268static void get_color_profile_tag(char dst[kICCDescriptionTagSize],
Mike Kleinb78729a2019-12-02 12:43:11 -0600269 const skcms_TransferFunction& fn,
270 const skcms_Matrix3x3& toXYZD50) {
Hal Canarya8565e52017-06-19 12:43:28 -0400271 SkASSERT(dst);
272 if (const char* description = get_color_profile_description(fn, toXYZD50)) {
273 SkASSERT(strlen(description) < kICCDescriptionTagSize);
Mike Kleinb78729a2019-12-02 12:43:11 -0600274
275 // Without these extra (), GCC would warn us something like
276 // ... sepecified bound 44 equals destination size ...
277 // which, yeah, is exactly what we're trying to do, copy the string
278 // and zero the rest of the destination if any. Sheesh.
279 (strncpy(dst, description, kICCDescriptionTagSize));
Hal Canarya8565e52017-06-19 12:43:28 -0400280 // "If the length of src is less than n, strncpy() writes additional
281 // null bytes to dest to ensure that a total of n bytes are written."
282 } else {
Mike Kleinb78729a2019-12-02 12:43:11 -0600283 memcpy(dst, kDescriptionTagBodyPrefix, sizeof(kDescriptionTagBodyPrefix));
Hal Canary7a808d62017-06-16 13:38:34 -0400284 SkMD5 md5;
Brian Osman5deadca2019-01-24 12:18:17 -0500285 md5.write(&toXYZD50, sizeof(toXYZD50));
Hal Canary7a808d62017-06-16 13:38:34 -0400286 static_assert(sizeof(fn) == sizeof(float) * 7, "packed");
287 md5.write(&fn, sizeof(fn));
Hal Canary0f2f5222019-04-03 10:13:45 -0400288 SkMD5::Digest digest = md5.finish();
Hal Canarya8565e52017-06-19 12:43:28 -0400289 char* ptr = dst + sizeof(kDescriptionTagBodyPrefix);
Hal Canary7a808d62017-06-16 13:38:34 -0400290 for (unsigned i = 0; i < sizeof(SkMD5::Digest); ++i) {
Hal Canarya8565e52017-06-19 12:43:28 -0400291 uint8_t byte = digest.data[i];
292 *ptr++ = SkHexadecimalDigits::gUpper[byte >> 4];
293 *ptr++ = SkHexadecimalDigits::gUpper[byte & 0xF];
Hal Canary7a808d62017-06-16 13:38:34 -0400294 }
Hal Canarya8565e52017-06-19 12:43:28 -0400295 SkASSERT(ptr == dst + kICCDescriptionTagSize);
Hal Canary7a808d62017-06-16 13:38:34 -0400296 }
Hal Canary7a808d62017-06-16 13:38:34 -0400297}
298
Brian Osman5deadca2019-01-24 12:18:17 -0500299sk_sp<SkData> SkWriteICCProfile(const skcms_TransferFunction& fn,
300 const skcms_Matrix3x3& toXYZD50) {
Brian Osman11e6aa82019-10-16 13:58:42 -0400301 // We can't encode HDR transfer functions in ICC
302 if (classify_transfer_fn(fn) != sRGBish_TF) {
Matt Sarett8e042862016-12-19 09:43:30 -0500303 return nullptr;
304 }
305
306 SkAutoMalloc profile(kICCProfileSize);
307 uint8_t* ptr = (uint8_t*) profile.get();
308
309 // Write profile header
Matt Sarett1bfcf882016-12-19 18:37:34 -0500310 memcpy(ptr, kICCHeader, sizeof(kICCHeader));
311 ptr += sizeof(kICCHeader);
Matt Sarett8e042862016-12-19 09:43:30 -0500312
313 // Write tag table
Matt Sarett1bfcf882016-12-19 18:37:34 -0500314 memcpy(ptr, kICCTagTable, sizeof(kICCTagTable));
315 ptr += sizeof(kICCTagTable);
Matt Sarett8e042862016-12-19 09:43:30 -0500316
317 // Write profile description tag
Hal Canarya8565e52017-06-19 12:43:28 -0400318 memcpy(ptr, kDescriptionTagHeader, sizeof(kDescriptionTagHeader));
319 ptr += sizeof(kDescriptionTagHeader);
320 {
321 char colorProfileTag[kICCDescriptionTagSize];
322 get_color_profile_tag(colorProfileTag, fn, toXYZD50);
Mike Klein5b58b7c2018-05-22 13:47:52 +0000323
324 // ASCII --> big-endian UTF-16.
325 for (size_t i = 0; i < kICCDescriptionTagSize; i++) {
326 *ptr++ = 0;
327 *ptr++ = colorProfileTag[i];
328 }
Hal Canarya8565e52017-06-19 12:43:28 -0400329 }
Matt Sarett8e042862016-12-19 09:43:30 -0500330
331 // Write XYZ tags
332 write_xyz_tag((uint32_t*) ptr, toXYZD50, 0);
333 ptr += kTAG_XYZ_Bytes;
334 write_xyz_tag((uint32_t*) ptr, toXYZD50, 1);
335 ptr += kTAG_XYZ_Bytes;
336 write_xyz_tag((uint32_t*) ptr, toXYZD50, 2);
337 ptr += kTAG_XYZ_Bytes;
338
Matt Sarett1bfcf882016-12-19 18:37:34 -0500339 // Write TRC tag
340 write_trc_tag((uint32_t*) ptr, fn);
341 ptr += kTAG_TRC_Bytes;
Matt Sarett8e042862016-12-19 09:43:30 -0500342
Matt Sarett1bfcf882016-12-19 18:37:34 -0500343 // Write white point tag (must be D50)
344 memcpy(ptr, kWhitePointTag, sizeof(kWhitePointTag));
345 ptr += sizeof(kWhitePointTag);
Matt Sarett8e042862016-12-19 09:43:30 -0500346
347 // Write copyright tag
Matt Sarett1bfcf882016-12-19 18:37:34 -0500348 memcpy(ptr, kCopyrightTagHeader, sizeof(kCopyrightTagHeader));
349 ptr += sizeof(kCopyrightTagHeader);
350 memcpy(ptr, kCopyrightTagBody, sizeof(kCopyrightTagBody));
351 ptr += sizeof(kCopyrightTagBody);
Matt Sarett8e042862016-12-19 09:43:30 -0500352
Matt Sarett1bfcf882016-12-19 18:37:34 -0500353 SkASSERT(kICCProfileSize == ptr - (uint8_t*) profile.get());
Matt Sarett8e042862016-12-19 09:43:30 -0500354 return SkData::MakeFromMalloc(profile.release(), kICCProfileSize);
355}