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