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 20 SkICC::SkICC(sk_sp<SkColorSpace> colorSpace) 21 : fColorSpace(std::move(colorSpace)) 22 {} 23 24 sk_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 33 bool SkICC::toXYZD50(SkMatrix44* toXYZD50) const { 34 return fColorSpace->toXYZD50(toXYZD50); 35 } 36 37 bool SkICC::isNumericalTransferFn(SkColorSpaceTransferFn* coeffs) const { 38 return fColorSpace->isNumericalTransferFn(coeffs); 39 } 40 41 static const int kDefaultTableSize = 512; // Arbitrary 42 43 void 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 56 void 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 63 bool 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 138 static constexpr char kDescriptionTagBodyPrefix[12] = 139 { 'G', 'o', 'o', 'g', 'l', 'e', '/', 'S', 'k', 'i', 'a' , '/'}; 140 141 static constexpr size_t kICCDescriptionTagSize = 44; 142 143 static_assert(kICCDescriptionTagSize == 144 sizeof(kDescriptionTagBodyPrefix) + 2 * sizeof(SkMD5::Digest), ""); 145 static constexpr size_t kDescriptionTagBodySize = kICCDescriptionTagSize * 2; // ascii->utf16be 146 147 static_assert(SkIsAlign4(kDescriptionTagBodySize), "Description must be aligned to 4-bytes."); 148 static 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 158 static 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) 167 static 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 }; 172 static_assert(SkIsAlign4(sizeof(kCopyrightTagBody)), "Copyright must be aligned to 4-bytes."); 173 static 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. 184 static constexpr uint32_t kICCNumEntries = 9; 185 186 static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c'); 187 static constexpr uint32_t kTAG_desc_Bytes = sizeof(kDescriptionTagHeader) + 188 kDescriptionTagBodySize; 189 static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize + 190 kICCNumEntries * kICCTagTableEntrySize; 191 192 static constexpr uint32_t kTAG_XYZ_Bytes = 20; 193 static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes; 194 static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes; 195 static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes; 196 197 static constexpr uint32_t kTAG_TRC_Bytes = 40; 198 static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes; 199 static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset; 200 static constexpr uint32_t kTAG_bTRC_Offset = kTAG_rTRC_Offset; 201 202 static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't'); 203 static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + kTAG_TRC_Bytes; 204 205 static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't'); 206 static constexpr uint32_t kTAG_cprt_Bytes = sizeof(kCopyrightTagHeader) + 207 sizeof(kCopyrightTagBody); 208 static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes; 209 210 static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes; 211 212 static 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 236 static 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. 287 static SkFixed float_round_to_fixed(float x) { 288 return sk_float_saturate2int((float)floor((double)x * SK_Fixed1 + 0.5)); 289 } 290 291 static 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 299 static 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 312 static 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 318 static 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 330 static 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 341 static 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. 361 const 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 401 static 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 433 SkString 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. 446 static 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 454 sk_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