SkColorSpace.cpp revision dc27a648d2ff23b2e96232c00c15976c46e1d48d
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 "SkColorSpace.h" 9#include "SkColorSpace_Base.h" 10#include "SkEndian.h" 11#include "SkOnce.h" 12 13static bool color_space_almost_equal(float a, float b) { 14 return SkTAbs(a - b) < 0.01f; 15} 16 17////////////////////////////////////////////////////////////////////////////////////////////////// 18 19SkColorSpace::SkColorSpace(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Named named) 20 : fGammaNamed(gammaNamed) 21 , fToXYZD50(toXYZD50) 22 , fNamed(named) 23{} 24 25SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkGammas> gammas, const SkMatrix44& toXYZD50, 26 Named named, sk_sp<SkData> profileData) 27 : INHERITED(kNonStandard_GammaNamed, toXYZD50, named) 28 , fGammas(std::move(gammas)) 29 , fProfileData(std::move(profileData)) 30{} 31 32SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkGammas> gammas, GammaNamed gammaNamed, 33 const SkMatrix44& toXYZD50, Named named, 34 sk_sp<SkData> profileData) 35 : INHERITED(gammaNamed, toXYZD50, named) 36 , fGammas(std::move(gammas)) 37 , fProfileData(std::move(profileData)) 38{} 39 40SkColorSpace_Base::SkColorSpace_Base(SkColorLookUpTable* colorLUT, sk_sp<SkGammas> gammas, 41 const SkMatrix44& toXYZD50, sk_sp<SkData> profileData) 42 : INHERITED(kNonStandard_GammaNamed, toXYZD50, kUnknown_Named) 43 , fColorLUT(colorLUT) 44 , fGammas(std::move(gammas)) 45 , fProfileData(std::move(profileData)) 46{} 47 48static constexpr float gSRGB_toXYZD50[] { 49 0.4358f, 0.2224f, 0.0139f, // * R 50 0.3853f, 0.7170f, 0.0971f, // * G 51 0.1430f, 0.0606f, 0.7139f, // * B 52}; 53 54static constexpr float gAdobeRGB_toXYZD50[] { 55 0.6098f, 0.3111f, 0.0195f, // * R 56 0.2052f, 0.6257f, 0.0609f, // * G 57 0.1492f, 0.0632f, 0.7448f, // * B 58}; 59 60/** 61 * Checks if our toXYZ matrix is a close match to a known color gamut. 62 * 63 * @param toXYZD50 transformation matrix deduced from profile data 64 * @param standard 3x3 canonical transformation matrix 65 */ 66static bool xyz_almost_equal(const SkMatrix44& toXYZD50, const float* standard) { 67 return color_space_almost_equal(toXYZD50.getFloat(0, 0), standard[0]) && 68 color_space_almost_equal(toXYZD50.getFloat(0, 1), standard[1]) && 69 color_space_almost_equal(toXYZD50.getFloat(0, 2), standard[2]) && 70 color_space_almost_equal(toXYZD50.getFloat(1, 0), standard[3]) && 71 color_space_almost_equal(toXYZD50.getFloat(1, 1), standard[4]) && 72 color_space_almost_equal(toXYZD50.getFloat(1, 2), standard[5]) && 73 color_space_almost_equal(toXYZD50.getFloat(2, 0), standard[6]) && 74 color_space_almost_equal(toXYZD50.getFloat(2, 1), standard[7]) && 75 color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) && 76 color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) && 77 color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) && 78 color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) && 79 color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) && 80 color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) && 81 color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) && 82 color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f); 83} 84 85static SkOnce g2Dot2CurveGammasOnce; 86static SkGammas* g2Dot2CurveGammas; 87static SkOnce gLinearGammasOnce; 88static SkGammas* gLinearGammas; 89 90sk_sp<SkColorSpace> SkColorSpace::NewRGB(const float gammaVals[3], const SkMatrix44& toXYZD50) { 91 return SkColorSpace_Base::NewRGB(gammaVals, toXYZD50, nullptr); 92} 93 94sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(const float gammaVals[3], const SkMatrix44& toXYZD50, 95 sk_sp<SkData> profileData) { 96 sk_sp<SkGammas> gammas = nullptr; 97 GammaNamed gammaNamed = kNonStandard_GammaNamed; 98 99 // Check if we really have sRGB or Adobe RGB 100 if (color_space_almost_equal(2.2f, gammaVals[0]) && 101 color_space_almost_equal(2.2f, gammaVals[1]) && 102 color_space_almost_equal(2.2f, gammaVals[2])) 103 { 104 g2Dot2CurveGammasOnce([] { 105 g2Dot2CurveGammas = new SkGammas(2.2f, 2.2f, 2.2f); 106 }); 107 gammas = sk_ref_sp(g2Dot2CurveGammas); 108 gammaNamed = k2Dot2Curve_GammaNamed; 109 110 if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) { 111 return SkColorSpace::NewNamed(kSRGB_Named); 112 } else if (xyz_almost_equal(toXYZD50, gAdobeRGB_toXYZD50)) { 113 return SkColorSpace::NewNamed(kAdobeRGB_Named); 114 } 115 } else if (color_space_almost_equal(1.0f, gammaVals[0]) && 116 color_space_almost_equal(1.0f, gammaVals[1]) && 117 color_space_almost_equal(1.0f, gammaVals[2])) 118 { 119 gLinearGammasOnce([] { 120 gLinearGammas = new SkGammas(1.0f, 1.0f, 1.0f); 121 }); 122 gammas = sk_ref_sp(gLinearGammas); 123 gammaNamed = kLinear_GammaNamed; 124 } 125 126 if (!gammas) { 127 gammas = sk_sp<SkGammas>(new SkGammas(gammaVals[0], gammaVals[1], gammaVals[2])); 128 } 129 return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammas, gammaNamed, toXYZD50, kUnknown_Named, 130 std::move(profileData))); 131} 132 133sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) { 134 static SkOnce sRGBOnce; 135 static SkColorSpace* sRGB; 136 static SkOnce adobeRGBOnce; 137 static SkColorSpace* adobeRGB; 138 139 switch (named) { 140 case kSRGB_Named: { 141 g2Dot2CurveGammasOnce([] { 142 g2Dot2CurveGammas = new SkGammas(2.2f, 2.2f, 2.2f); 143 }); 144 145 sRGBOnce([] { 146 SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor); 147 srgbToxyzD50.set3x3ColMajorf(gSRGB_toXYZD50); 148 sRGB = new SkColorSpace_Base(sk_ref_sp(g2Dot2CurveGammas), k2Dot2Curve_GammaNamed, 149 srgbToxyzD50, kSRGB_Named, nullptr); 150 }); 151 return sk_ref_sp(sRGB); 152 } 153 case kAdobeRGB_Named: { 154 g2Dot2CurveGammasOnce([] { 155 g2Dot2CurveGammas = new SkGammas(2.2f, 2.2f, 2.2f); 156 }); 157 158 adobeRGBOnce([] { 159 SkMatrix44 adobergbToxyzD50(SkMatrix44::kUninitialized_Constructor); 160 adobergbToxyzD50.set3x3ColMajorf(gAdobeRGB_toXYZD50); 161 adobeRGB = new SkColorSpace_Base(sk_ref_sp(g2Dot2CurveGammas), 162 k2Dot2Curve_GammaNamed, adobergbToxyzD50, 163 kAdobeRGB_Named, nullptr); 164 }); 165 return sk_ref_sp(adobeRGB); 166 } 167 default: 168 break; 169 } 170 return nullptr; 171} 172 173/////////////////////////////////////////////////////////////////////////////////////////////////// 174 175#include "SkFixed.h" 176#include "SkTemplates.h" 177 178#define SkColorSpacePrintf(...) 179 180#define return_if_false(pred, msg) \ 181 do { \ 182 if (!(pred)) { \ 183 SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \ 184 return false; \ 185 } \ 186 } while (0) 187 188#define return_null(msg) \ 189 do { \ 190 SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \ 191 return nullptr; \ 192 } while (0) 193 194static uint16_t read_big_endian_short(const uint8_t* ptr) { 195 return ptr[0] << 8 | ptr[1]; 196} 197 198static uint32_t read_big_endian_uint(const uint8_t* ptr) { 199 return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; 200} 201 202static int32_t read_big_endian_int(const uint8_t* ptr) { 203 return (int32_t) read_big_endian_uint(ptr); 204} 205 206// This is equal to the header size according to the ICC specification (128) 207// plus the size of the tag count (4). We include the tag count since we 208// always require it to be present anyway. 209static constexpr size_t kICCHeaderSize = 132; 210 211// Contains a signature (4), offset (4), and size (4). 212static constexpr size_t kICCTagTableEntrySize = 12; 213 214static constexpr uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' '); 215static constexpr uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r'); 216static constexpr uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r'); 217static constexpr uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r'); 218static constexpr uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' '); 219static constexpr uint32_t kACSP_Signature = SkSetFourByteTag('a', 'c', 's', 'p'); 220 221struct ICCProfileHeader { 222 uint32_t fSize; 223 224 // No reason to care about the preferred color management module (ex: Adobe, Apple, etc.). 225 // We're always going to use this one. 226 uint32_t fCMMType_ignored; 227 228 uint32_t fVersion; 229 uint32_t fProfileClass; 230 uint32_t fInputColorSpace; 231 uint32_t fPCS; 232 uint32_t fDateTime_ignored[3]; 233 uint32_t fSignature; 234 235 // Indicates the platform that this profile was created for (ex: Apple, Microsoft). This 236 // doesn't really matter to us. 237 uint32_t fPlatformTarget_ignored; 238 239 // Flags can indicate: 240 // (1) Whether this profile was embedded in a file. This flag is consistently wrong. 241 // Ex: The profile came from a file but indicates that it did not. 242 // (2) Whether we are allowed to use the profile independently of the color data. If set, 243 // this may allow us to use the embedded profile for testing separate from the original 244 // image. 245 uint32_t fFlags_ignored; 246 247 // We support many output devices. It doesn't make sense to think about the attributes of 248 // the device in the context of the image profile. 249 uint32_t fDeviceManufacturer_ignored; 250 uint32_t fDeviceModel_ignored; 251 uint32_t fDeviceAttributes_ignored[2]; 252 253 uint32_t fRenderingIntent; 254 int32_t fIlluminantXYZ[3]; 255 256 // We don't care who created the profile. 257 uint32_t fCreator_ignored; 258 259 // This is an MD5 checksum. Could be useful for checking if profiles are equal. 260 uint32_t fProfileId_ignored[4]; 261 262 // Reserved for future use. 263 uint32_t fReserved_ignored[7]; 264 265 uint32_t fTagCount; 266 267 void init(const uint8_t* src, size_t len) { 268 SkASSERT(kICCHeaderSize == sizeof(*this)); 269 270 uint32_t* dst = (uint32_t*) this; 271 for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) { 272 dst[i] = read_big_endian_uint(src); 273 } 274 } 275 276 bool valid() const { 277 return_if_false(fSize >= kICCHeaderSize, "Size is too small"); 278 279 uint8_t majorVersion = fVersion >> 24; 280 return_if_false(majorVersion <= 4, "Unsupported version"); 281 282 // These are the three basic classes of profiles that we might expect to see embedded 283 // in images. Four additional classes exist, but they generally are used as a convenient 284 // way for CMMs to store calculated transforms. 285 return_if_false(fProfileClass == kDisplay_Profile || 286 fProfileClass == kInput_Profile || 287 fProfileClass == kOutput_Profile, 288 "Unsupported profile"); 289 290 // TODO (msarett): 291 // All the profiles we've tested so far use RGB as the input color space. 292 return_if_false(fInputColorSpace == kRGB_ColorSpace, "Unsupported color space"); 293 294 // TODO (msarett): 295 // All the profiles we've tested so far use XYZ as the profile connection space. 296 return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space"); 297 298 return_if_false(fSignature == kACSP_Signature, "Bad signature"); 299 300 // TODO (msarett): 301 // Should we treat different rendering intents differently? 302 // Valid rendering intents include kPerceptual (0), kRelative (1), 303 // kSaturation (2), and kAbsolute (3). 304 return_if_false(fRenderingIntent <= 3, "Bad rendering intent"); 305 306 return_if_false(color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[0]), 0.96420f) && 307 color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[1]), 1.00000f) && 308 color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[2]), 0.82491f), 309 "Illuminant must be D50"); 310 311 return_if_false(fTagCount <= 100, "Too many tags"); 312 313 return true; 314 } 315}; 316 317struct ICCTag { 318 uint32_t fSignature; 319 uint32_t fOffset; 320 uint32_t fLength; 321 322 const uint8_t* init(const uint8_t* src) { 323 fSignature = read_big_endian_uint(src); 324 fOffset = read_big_endian_uint(src + 4); 325 fLength = read_big_endian_uint(src + 8); 326 return src + 12; 327 } 328 329 bool valid(size_t len) { 330 return_if_false(fOffset + fLength <= len, "Tag too large for ICC profile"); 331 return true; 332 } 333 334 const uint8_t* addr(const uint8_t* src) const { 335 return src + fOffset; 336 } 337 338 static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature) { 339 for (int i = 0; i < count; ++i) { 340 if (tags[i].fSignature == signature) { 341 return &tags[i]; 342 } 343 } 344 return nullptr; 345 } 346}; 347 348static constexpr uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z'); 349static constexpr uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z'); 350static constexpr uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z'); 351static constexpr uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C'); 352static constexpr uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C'); 353static constexpr uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C'); 354static constexpr uint32_t kTAG_A2B0 = SkSetFourByteTag('A', '2', 'B', '0'); 355 356bool load_xyz(float dst[3], const uint8_t* src, size_t len) { 357 if (len < 20) { 358 SkColorSpacePrintf("XYZ tag is too small (%d bytes)", len); 359 return false; 360 } 361 362 dst[0] = SkFixedToFloat(read_big_endian_int(src + 8)); 363 dst[1] = SkFixedToFloat(read_big_endian_int(src + 12)); 364 dst[2] = SkFixedToFloat(read_big_endian_int(src + 16)); 365 SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]); 366 return true; 367} 368 369template <class T> 370static bool safe_add(T arg1, T arg2, size_t* result) { 371 SkASSERT(arg1 >= 0); 372 SkASSERT(arg2 >= 0); 373 if (arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) { 374 T sum = arg1 + arg2; 375 if (sum <= std::numeric_limits<size_t>::max()) { 376 *result = static_cast<size_t>(sum); 377 return true; 378 } 379 } 380 return false; 381} 382 383static constexpr uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', 'v'); 384static constexpr uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a'); 385 386bool load_gammas(SkGammaCurve* gammas, uint32_t numGammas, const uint8_t* src, size_t len) { 387 for (uint32_t i = 0; i < numGammas; i++) { 388 if (len < 12) { 389 // FIXME (msarett): 390 // We could potentially return false here after correctly parsing *some* of the 391 // gammas correctly. Should we somehow try to indicate a partial success? 392 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); 393 return false; 394 } 395 396 // We need to count the number of bytes in the tag, so we are able to move to the 397 // next tag on the next loop iteration. 398 size_t tagBytes; 399 400 uint32_t type = read_big_endian_uint(src); 401 switch (type) { 402 case kTAG_CurveType: { 403 uint32_t count = read_big_endian_uint(src + 8); 404 405 // tagBytes = 12 + 2 * count 406 // We need to do safe addition here to avoid integer overflow. 407 if (!safe_add(count, count, &tagBytes) || 408 !safe_add((size_t) 12, tagBytes, &tagBytes)) 409 { 410 SkColorSpacePrintf("Invalid gamma count"); 411 return false; 412 } 413 414 if (0 == count) { 415 // Some tags require a gamma curve, but the author doesn't actually want 416 // to transform the data. In this case, it is common to see a curve with 417 // a count of 0. 418 gammas[i].fValue = 1.0f; 419 break; 420 } else if (len < tagBytes) { 421 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); 422 return false; 423 } 424 425 const uint16_t* table = (const uint16_t*) (src + 12); 426 if (1 == count) { 427 // The table entry is the gamma (with a bias of 256). 428 uint16_t value = read_big_endian_short((const uint8_t*) table); 429 gammas[i].fValue = value / 256.0f; 430 if (0.0f == gammas[i].fValue) { 431 SkColorSpacePrintf("Cannot have zero gamma value"); 432 return false; 433 } 434 SkColorSpacePrintf("gamma %d %g\n", value, gammas[i].fValue); 435 break; 436 } 437 438 // Check for frequently occurring curves and use a fast approximation. 439 // We do this by sampling a few values and see if they match our expectation. 440 // A more robust solution would be to compare each value in this curve against 441 // a 2.2f curve see if we remain below an error threshold. At this time, 442 // we haven't seen any images in the wild that make this kind of 443 // calculation necessary. We encounter identical gamma curves over and 444 // over again, but relatively few variations. 445 if (1024 == count) { 446 // The magic values were chosen because they match a very common sRGB 447 // gamma table and the less common Canon sRGB gamma table (which use 448 // different rounding rules). 449 if (0 == read_big_endian_short((const uint8_t*) &table[0]) && 450 3366 == read_big_endian_short((const uint8_t*) &table[257]) && 451 14116 == read_big_endian_short((const uint8_t*) &table[513]) && 452 34318 == read_big_endian_short((const uint8_t*) &table[768]) && 453 65535 == read_big_endian_short((const uint8_t*) &table[1023])) { 454 gammas[i].fValue = 2.2f; 455 break; 456 } 457 } else if (26 == count) { 458 // The magic values were chosen because they match a very common sRGB 459 // gamma table. 460 if (0 == read_big_endian_short((const uint8_t*) &table[0]) && 461 3062 == read_big_endian_short((const uint8_t*) &table[6]) && 462 12824 == read_big_endian_short((const uint8_t*) &table[12]) && 463 31237 == read_big_endian_short((const uint8_t*) &table[18]) && 464 65535 == read_big_endian_short((const uint8_t*) &table[25])) { 465 gammas[i].fValue = 2.2f; 466 break; 467 } 468 } else if (4096 == count) { 469 // The magic values were chosen because they match Nikon, Epson, and 470 // LCMS sRGB gamma tables (all of which use different rounding rules). 471 if (0 == read_big_endian_short((const uint8_t*) &table[0]) && 472 950 == read_big_endian_short((const uint8_t*) &table[515]) && 473 3342 == read_big_endian_short((const uint8_t*) &table[1025]) && 474 14079 == read_big_endian_short((const uint8_t*) &table[2051]) && 475 65535 == read_big_endian_short((const uint8_t*) &table[4095])) { 476 gammas[i].fValue = 2.2f; 477 break; 478 } 479 } 480 481 // Otherwise, fill in the interpolation table. 482 gammas[i].fTableSize = count; 483 gammas[i].fTable = std::unique_ptr<float[]>(new float[count]); 484 for (uint32_t j = 0; j < count; j++) { 485 gammas[i].fTable[j] = 486 (read_big_endian_short((const uint8_t*) &table[j])) / 65535.0f; 487 } 488 break; 489 } 490 case kTAG_ParaCurveType: { 491 enum ParaCurveType { 492 kExponential_ParaCurveType = 0, 493 kGAB_ParaCurveType = 1, 494 kGABC_ParaCurveType = 2, 495 kGABDE_ParaCurveType = 3, 496 kGABCDEF_ParaCurveType = 4, 497 }; 498 499 // Determine the format of the parametric curve tag. 500 uint16_t format = read_big_endian_short(src + 8); 501 if (kExponential_ParaCurveType == format) { 502 tagBytes = 12 + 4; 503 if (len < tagBytes) { 504 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); 505 return false; 506 } 507 508 // Y = X^g 509 int32_t g = read_big_endian_int(src + 12); 510 gammas[i].fValue = SkFixedToFloat(g); 511 } else { 512 // Here's where the real parametric gammas start. There are many 513 // permutations of the same equations. 514 // 515 // Y = (aX + b)^g + c for X >= d 516 // Y = eX + f otherwise 517 // 518 // We will fill in with zeros as necessary to always match the above form. 519 float g = 0.0f, a = 0.0f, b = 0.0f, c = 0.0f, d = 0.0f, e = 0.0f, f = 0.0f; 520 switch(format) { 521 case kGAB_ParaCurveType: { 522 tagBytes = 12 + 12; 523 if (len < tagBytes) { 524 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); 525 return false; 526 } 527 528 // Y = (aX + b)^g for X >= -b/a 529 // Y = 0 otherwise 530 g = SkFixedToFloat(read_big_endian_int(src + 12)); 531 a = SkFixedToFloat(read_big_endian_int(src + 16)); 532 if (0.0f == a) { 533 return false; 534 } 535 536 b = SkFixedToFloat(read_big_endian_int(src + 20)); 537 d = -b / a; 538 break; 539 } 540 case kGABC_ParaCurveType: 541 tagBytes = 12 + 16; 542 if (len < tagBytes) { 543 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); 544 return false; 545 } 546 547 // Y = (aX + b)^g + c for X >= -b/a 548 // Y = c otherwise 549 g = SkFixedToFloat(read_big_endian_int(src + 12)); 550 a = SkFixedToFloat(read_big_endian_int(src + 16)); 551 if (0.0f == a) { 552 return false; 553 } 554 555 b = SkFixedToFloat(read_big_endian_int(src + 20)); 556 c = SkFixedToFloat(read_big_endian_int(src + 24)); 557 d = -b / a; 558 f = c; 559 break; 560 case kGABDE_ParaCurveType: 561 tagBytes = 12 + 20; 562 if (len < tagBytes) { 563 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); 564 return false; 565 } 566 567 // Y = (aX + b)^g for X >= d 568 // Y = cX otherwise 569 g = SkFixedToFloat(read_big_endian_int(src + 12)); 570 a = SkFixedToFloat(read_big_endian_int(src + 16)); 571 b = SkFixedToFloat(read_big_endian_int(src + 20)); 572 d = SkFixedToFloat(read_big_endian_int(src + 28)); 573 e = SkFixedToFloat(read_big_endian_int(src + 24)); 574 break; 575 case kGABCDEF_ParaCurveType: 576 tagBytes = 12 + 28; 577 if (len < tagBytes) { 578 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); 579 return false; 580 } 581 582 // Y = (aX + b)^g + c for X >= d 583 // Y = eX + f otherwise 584 // NOTE: The ICC spec writes "cX" in place of "eX" but I think 585 // it's a typo. 586 g = SkFixedToFloat(read_big_endian_int(src + 12)); 587 a = SkFixedToFloat(read_big_endian_int(src + 16)); 588 b = SkFixedToFloat(read_big_endian_int(src + 20)); 589 c = SkFixedToFloat(read_big_endian_int(src + 24)); 590 d = SkFixedToFloat(read_big_endian_int(src + 28)); 591 e = SkFixedToFloat(read_big_endian_int(src + 32)); 592 f = SkFixedToFloat(read_big_endian_int(src + 36)); 593 break; 594 default: 595 SkColorSpacePrintf("Invalid parametric curve type\n"); 596 return false; 597 } 598 599 // Recognize and simplify a very common parametric representation of sRGB gamma. 600 if (color_space_almost_equal(0.9479f, a) && 601 color_space_almost_equal(0.0521f, b) && 602 color_space_almost_equal(0.0000f, c) && 603 color_space_almost_equal(0.0405f, d) && 604 color_space_almost_equal(0.0774f, e) && 605 color_space_almost_equal(0.0000f, f) && 606 color_space_almost_equal(2.4000f, g)) { 607 gammas[i].fValue = 2.2f; 608 } else { 609 // Fail on invalid gammas. 610 if (d <= 0.0f) { 611 // Y = (aX + b)^g + c for always 612 if (0.0f == a || 0.0f == g) { 613 SkColorSpacePrintf("A or G is zero, constant gamma function " 614 "is nonsense"); 615 return false; 616 } 617 } else if (d >= 1.0f) { 618 // Y = eX + f for always 619 if (0.0f == e) { 620 SkColorSpacePrintf("E is zero, constant gamma function is " 621 "nonsense"); 622 return false; 623 } 624 } else if ((0.0f == a || 0.0f == g) && 0.0f == e) { 625 SkColorSpacePrintf("A or G, and E are zero, constant gamma function " 626 "is nonsense"); 627 return false; 628 } 629 630 gammas[i].fG = g; 631 gammas[i].fA = a; 632 gammas[i].fB = b; 633 gammas[i].fC = c; 634 gammas[i].fD = d; 635 gammas[i].fE = e; 636 gammas[i].fF = f; 637 } 638 } 639 640 break; 641 } 642 default: 643 SkColorSpacePrintf("Unsupported gamma tag type %d\n", type); 644 return false; 645 } 646 647 // Ensure that we have successfully read a gamma representation. 648 SkASSERT(gammas[i].isValue() || gammas[i].isTable() || gammas[i].isParametric()); 649 650 // Adjust src and len if there is another gamma curve to load. 651 if (i != numGammas - 1) { 652 // Each curve is padded to 4-byte alignment. 653 tagBytes = SkAlign4(tagBytes); 654 if (len < tagBytes) { 655 return false; 656 } 657 658 src += tagBytes; 659 len -= tagBytes; 660 } 661 } 662 663 return true; 664} 665 666static constexpr uint32_t kTAG_AtoBType = SkSetFourByteTag('m', 'A', 'B', ' '); 667 668bool load_color_lut(SkColorLookUpTable* colorLUT, uint32_t inputChannels, uint32_t outputChannels, 669 const uint8_t* src, size_t len) { 670 if (len < 20) { 671 SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len); 672 return false; 673 } 674 675 SkASSERT(inputChannels <= SkColorLookUpTable::kMaxChannels && 3 == outputChannels); 676 colorLUT->fInputChannels = inputChannels; 677 colorLUT->fOutputChannels = outputChannels; 678 uint32_t numEntries = 1; 679 for (uint32_t i = 0; i < inputChannels; i++) { 680 colorLUT->fGridPoints[i] = src[i]; 681 numEntries *= src[i]; 682 } 683 numEntries *= outputChannels; 684 685 // Space is provided for a maximum of the 16 input channels. Now we determine the precision 686 // of the table values. 687 uint8_t precision = src[16]; 688 switch (precision) { 689 case 1: // 8-bit data 690 case 2: // 16-bit data 691 break; 692 default: 693 SkColorSpacePrintf("Color LUT precision must be 8-bit or 16-bit.\n", len); 694 return false; 695 } 696 697 if (len < 20 + numEntries * precision) { 698 SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len); 699 return false; 700 } 701 702 // Movable struct colorLUT has ownership of fTable. 703 colorLUT->fTable = std::unique_ptr<float[]>(new float[numEntries]); 704 const uint8_t* ptr = src + 20; 705 for (uint32_t i = 0; i < numEntries; i++, ptr += precision) { 706 if (1 == precision) { 707 colorLUT->fTable[i] = ((float) ptr[i]) / 255.0f; 708 } else { 709 colorLUT->fTable[i] = ((float) read_big_endian_short(ptr)) / 65535.0f; 710 } 711 } 712 713 return true; 714} 715 716bool load_matrix(SkMatrix44* toXYZ, const uint8_t* src, size_t len) { 717 if (len < 48) { 718 SkColorSpacePrintf("Matrix tag is too small (%d bytes).", len); 719 return false; 720 } 721 722 // For this matrix to behave like our "to XYZ D50" matrices, it needs to be scaled. 723 constexpr float scale = 65535.0 / 32768.0; 724 float array[16]; 725 array[ 0] = scale * SkFixedToFloat(read_big_endian_int(src)); 726 array[ 1] = scale * SkFixedToFloat(read_big_endian_int(src + 4)); 727 array[ 2] = scale * SkFixedToFloat(read_big_endian_int(src + 8)); 728 array[ 3] = scale * SkFixedToFloat(read_big_endian_int(src + 36)); // translate R 729 array[ 4] = scale * SkFixedToFloat(read_big_endian_int(src + 12)); 730 array[ 5] = scale * SkFixedToFloat(read_big_endian_int(src + 16)); 731 array[ 6] = scale * SkFixedToFloat(read_big_endian_int(src + 20)); 732 array[ 7] = scale * SkFixedToFloat(read_big_endian_int(src + 40)); // translate G 733 array[ 8] = scale * SkFixedToFloat(read_big_endian_int(src + 24)); 734 array[ 9] = scale * SkFixedToFloat(read_big_endian_int(src + 28)); 735 array[10] = scale * SkFixedToFloat(read_big_endian_int(src + 32)); 736 array[11] = scale * SkFixedToFloat(read_big_endian_int(src + 44)); // translate B 737 array[12] = 0.0f; 738 array[13] = 0.0f; 739 array[14] = 0.0f; 740 array[15] = 1.0f; 741 toXYZ->setColMajorf(array); 742 return true; 743} 744 745bool load_a2b0(SkColorLookUpTable* colorLUT, SkGammaCurve* gammas, SkMatrix44* toXYZ, 746 const uint8_t* src, size_t len) { 747 if (len < 32) { 748 SkColorSpacePrintf("A to B tag is too small (%d bytes).", len); 749 return false; 750 } 751 752 uint32_t type = read_big_endian_uint(src); 753 if (kTAG_AtoBType != type) { 754 // FIXME (msarett): Need to support lut8Type and lut16Type. 755 SkColorSpacePrintf("Unsupported A to B tag type.\n"); 756 return false; 757 } 758 759 // Read the number of channels. The four bytes that we skipped are reserved and 760 // must be zero. 761 uint8_t inputChannels = src[8]; 762 uint8_t outputChannels = src[9]; 763 if (0 == inputChannels || inputChannels > SkColorLookUpTable::kMaxChannels || 764 3 != outputChannels) { 765 // The color LUT assumes that there are at most 16 input channels. For RGB 766 // profiles, output channels should be 3. 767 SkColorSpacePrintf("Too many input or output channels in A to B tag.\n"); 768 return false; 769 } 770 771 // Read the offsets of each element in the A to B tag. With the exception of A curves and 772 // B curves (which we do not yet support), we will handle these elements in the order in 773 // which they should be applied (rather than the order in which they occur in the tag). 774 // If the offset is non-zero it indicates that the element is present. 775 uint32_t offsetToACurves = read_big_endian_int(src + 28); 776 uint32_t offsetToBCurves = read_big_endian_int(src + 12); 777 if ((0 != offsetToACurves) || (0 != offsetToBCurves)) { 778 // FIXME (msarett): Handle A and B curves. 779 // Note that the A curve is technically required in order to have a color LUT. 780 // However, all the A curves I have seen so far have are just placeholders that 781 // don't actually transform the data. 782 SkColorSpacePrintf("Ignoring A and/or B curve. Output may be wrong.\n"); 783 } 784 785 uint32_t offsetToColorLUT = read_big_endian_int(src + 24); 786 if (0 != offsetToColorLUT && offsetToColorLUT < len) { 787 if (!load_color_lut(colorLUT, inputChannels, outputChannels, src + offsetToColorLUT, 788 len - offsetToColorLUT)) { 789 SkColorSpacePrintf("Failed to read color LUT from A to B tag.\n"); 790 } 791 } 792 793 uint32_t offsetToMCurves = read_big_endian_int(src + 20); 794 if (0 != offsetToMCurves && offsetToMCurves < len) { 795 if (!load_gammas(gammas, outputChannels, src + offsetToMCurves, len - offsetToMCurves)) { 796 SkColorSpacePrintf("Failed to read M curves from A to B tag.\n"); 797 } 798 } 799 800 uint32_t offsetToMatrix = read_big_endian_int(src + 16); 801 if (0 != offsetToMatrix && offsetToMatrix < len) { 802 if (!load_matrix(toXYZ, src + offsetToMatrix, len - offsetToMatrix)) { 803 SkColorSpacePrintf("Failed to read matrix from A to B tag.\n"); 804 } 805 } 806 807 return true; 808} 809 810sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* input, size_t len) { 811 if (len < kICCHeaderSize) { 812 return_null("Data is not large enough to contain an ICC profile"); 813 } 814 815 // Create our own copy of the input. 816 void* memory = sk_malloc_throw(len); 817 memcpy(memory, input, len); 818 sk_sp<SkData> data = SkData::MakeFromMalloc(memory, len); 819 const void* base = data->data(); 820 const uint8_t* ptr = (const uint8_t*) base; 821 822 // Read the ICC profile header and check to make sure that it is valid. 823 ICCProfileHeader header; 824 header.init(ptr, len); 825 if (!header.valid()) { 826 return nullptr; 827 } 828 829 // Adjust ptr and len before reading the tags. 830 if (len < header.fSize) { 831 SkColorSpacePrintf("ICC profile might be truncated.\n"); 832 } else if (len > header.fSize) { 833 SkColorSpacePrintf("Caller provided extra data beyond the end of the ICC profile.\n"); 834 len = header.fSize; 835 } 836 ptr += kICCHeaderSize; 837 len -= kICCHeaderSize; 838 839 // Parse tag headers. 840 uint32_t tagCount = header.fTagCount; 841 SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount); 842 if (len < kICCTagTableEntrySize * tagCount) { 843 return_null("Not enough input data to read tag table entries"); 844 } 845 846 SkAutoTArray<ICCTag> tags(tagCount); 847 for (uint32_t i = 0; i < tagCount; i++) { 848 ptr = tags[i].init(ptr); 849 SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24) & 0xFF, 850 (tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >> 8) & 0xFF, 851 (tags[i].fSignature >> 0) & 0xFF, tags[i].fOffset, tags[i].fLength); 852 853 if (!tags[i].valid(kICCHeaderSize + len)) { 854 return_null("Tag is too large to fit in ICC profile"); 855 } 856 } 857 858 switch (header.fInputColorSpace) { 859 case kRGB_ColorSpace: { 860 // Recognize the rXYZ, gXYZ, and bXYZ tags. 861 const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ); 862 const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ); 863 const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ); 864 if (r && g && b) { 865 float toXYZ[9]; 866 if (!load_xyz(&toXYZ[0], r->addr((const uint8_t*) base), r->fLength) || 867 !load_xyz(&toXYZ[3], g->addr((const uint8_t*) base), g->fLength) || 868 !load_xyz(&toXYZ[6], b->addr((const uint8_t*) base), b->fLength)) 869 { 870 return_null("Need valid rgb tags for XYZ space"); 871 } 872 873 // It is not uncommon to see missing or empty gamma tags. This indicates 874 // that we should use unit gamma. 875 SkGammaCurve curves[3]; 876 r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC); 877 g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC); 878 b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC); 879 if (!r || !load_gammas(&curves[0], 1, r->addr((const uint8_t*) base), r->fLength)) 880 { 881 SkColorSpacePrintf("Failed to read R gamma tag.\n"); 882 } 883 if (!g || !load_gammas(&curves[1], 1, g->addr((const uint8_t*) base), g->fLength)) 884 { 885 SkColorSpacePrintf("Failed to read G gamma tag.\n"); 886 } 887 if (!b || !load_gammas(&curves[2], 1, b->addr((const uint8_t*) base), b->fLength)) 888 { 889 SkColorSpacePrintf("Failed to read B gamma tag.\n"); 890 } 891 892 sk_sp<SkGammas> gammas(new SkGammas(std::move(curves[0]), std::move(curves[1]), 893 std::move(curves[2]))); 894 SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor); 895 mat.set3x3ColMajorf(toXYZ); 896 if (gammas->isValues()) { 897 // When we have values, take advantage of the NewFromRGB initializer. 898 // This allows us to check for canonical sRGB and Adobe RGB. 899 float gammaVals[3]; 900 gammaVals[0] = gammas->fRed.fValue; 901 gammaVals[1] = gammas->fGreen.fValue; 902 gammaVals[2] = gammas->fBlue.fValue; 903 return SkColorSpace_Base::NewRGB(gammaVals, mat, std::move(data)); 904 } else { 905 return sk_sp<SkColorSpace>(new SkColorSpace_Base(std::move(gammas), mat, 906 kUnknown_Named, 907 std::move(data))); 908 } 909 } 910 911 // Recognize color profile specified by A2B0 tag. 912 const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0); 913 if (a2b0) { 914 SkAutoTDelete<SkColorLookUpTable> colorLUT(new SkColorLookUpTable()); 915 SkGammaCurve curves[3]; 916 SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); 917 if (!load_a2b0(colorLUT, curves, &toXYZ, a2b0->addr((const uint8_t*) base), 918 a2b0->fLength)) { 919 return_null("Failed to parse A2B0 tag"); 920 } 921 922 sk_sp<SkGammas> gammas(new SkGammas(std::move(curves[0]), std::move(curves[1]), 923 std::move(curves[2]))); 924 if (colorLUT->fTable) { 925 return sk_sp<SkColorSpace>(new SkColorSpace_Base(colorLUT.release(), 926 std::move(gammas), toXYZ, 927 std::move(data))); 928 } else if (gammas->isValues()) { 929 // When we have values, take advantage of the NewFromRGB initializer. 930 // This allows us to check for canonical sRGB and Adobe RGB. 931 float gammaVals[3]; 932 gammaVals[0] = gammas->fRed.fValue; 933 gammaVals[1] = gammas->fGreen.fValue; 934 gammaVals[2] = gammas->fBlue.fValue; 935 return SkColorSpace_Base::NewRGB(gammaVals, toXYZ, std::move(data)); 936 } else { 937 return sk_sp<SkColorSpace>(new SkColorSpace_Base(std::move(gammas), toXYZ, 938 kUnknown_Named, 939 std::move(data))); 940 } 941 } 942 } 943 default: 944 break; 945 } 946 947 return_null("ICC profile contains unsupported colorspace"); 948} 949 950/////////////////////////////////////////////////////////////////////////////////////////////////// 951 952// We will write a profile with the minimum nine required tags. 953static constexpr uint32_t kICCNumEntries = 9; 954 955static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c'); 956static constexpr uint32_t kTAG_desc_Bytes = 12; 957static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize + kICCNumEntries*kICCTagTableEntrySize; 958 959static constexpr uint32_t kTAG_XYZ_Bytes = 20; 960static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes; 961static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes; 962static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes; 963 964static constexpr uint32_t kTAG_TRC_Bytes = 14; 965static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes; 966static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset + SkAlign4(kTAG_TRC_Bytes); 967static constexpr uint32_t kTAG_bTRC_Offset = kTAG_gTRC_Offset + SkAlign4(kTAG_TRC_Bytes); 968 969static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't'); 970static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + SkAlign4(kTAG_TRC_Bytes); 971 972static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't'); 973static constexpr uint32_t kTAG_cprt_Bytes = 12; 974static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes; 975 976static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes; 977 978static constexpr uint32_t gICCHeader[kICCHeaderSize / 4] { 979 SkEndian_SwapBE32(kICCProfileSize), // Size of the profile 980 0, // Preferred CMM type (ignored) 981 SkEndian_SwapBE32(0x02100000), // Version 2.1 982 SkEndian_SwapBE32(kDisplay_Profile), // Display device profile 983 SkEndian_SwapBE32(kRGB_ColorSpace), // RGB input color space 984 SkEndian_SwapBE32(kXYZ_PCSSpace), // XYZ profile connection space 985 0, 0, 0, // Date and time (ignored) 986 SkEndian_SwapBE32(kACSP_Signature), // Profile signature 987 0, // Platform target (ignored) 988 0x00000000, // Flags: not embedded, can be used independently 989 0, // Device manufacturer (ignored) 990 0, // Device model (ignored) 991 0, 0, // Device attributes (ignored) 992 SkEndian_SwapBE32(1), // Relative colorimetric rendering intent 993 SkEndian_SwapBE32(0x0000f6d6), // D50 standard illuminant (X) 994 SkEndian_SwapBE32(0x00010000), // D50 standard illuminant (Y) 995 SkEndian_SwapBE32(0x0000d32d), // D50 standard illuminant (Z) 996 0, // Profile creator (ignored) 997 0, 0, 0, 0, // Profile id checksum (ignored) 998 0, 0, 0, 0, 0, 0, 0, // Reserved (ignored) 999 SkEndian_SwapBE32(kICCNumEntries), // Number of tags 1000}; 1001 1002static constexpr uint32_t gICCTagTable[3 * kICCNumEntries] { 1003 // Profile description 1004 SkEndian_SwapBE32(kTAG_desc), 1005 SkEndian_SwapBE32(kTAG_desc_Offset), 1006 SkEndian_SwapBE32(kTAG_desc_Bytes), 1007 1008 // rXYZ 1009 SkEndian_SwapBE32(kTAG_rXYZ), 1010 SkEndian_SwapBE32(kTAG_rXYZ_Offset), 1011 SkEndian_SwapBE32(kTAG_XYZ_Bytes), 1012 1013 // gXYZ 1014 SkEndian_SwapBE32(kTAG_gXYZ), 1015 SkEndian_SwapBE32(kTAG_gXYZ_Offset), 1016 SkEndian_SwapBE32(kTAG_XYZ_Bytes), 1017 1018 // bXYZ 1019 SkEndian_SwapBE32(kTAG_bXYZ), 1020 SkEndian_SwapBE32(kTAG_bXYZ_Offset), 1021 SkEndian_SwapBE32(kTAG_XYZ_Bytes), 1022 1023 // rTRC 1024 SkEndian_SwapBE32(kTAG_rTRC), 1025 SkEndian_SwapBE32(kTAG_rTRC_Offset), 1026 SkEndian_SwapBE32(kTAG_TRC_Bytes), 1027 1028 // gTRC 1029 SkEndian_SwapBE32(kTAG_gTRC), 1030 SkEndian_SwapBE32(kTAG_gTRC_Offset), 1031 SkEndian_SwapBE32(kTAG_TRC_Bytes), 1032 1033 // bTRC 1034 SkEndian_SwapBE32(kTAG_bTRC), 1035 SkEndian_SwapBE32(kTAG_bTRC_Offset), 1036 SkEndian_SwapBE32(kTAG_TRC_Bytes), 1037 1038 // White point 1039 SkEndian_SwapBE32(kTAG_wtpt), 1040 SkEndian_SwapBE32(kTAG_wtpt_Offset), 1041 SkEndian_SwapBE32(kTAG_XYZ_Bytes), 1042 1043 // Copyright 1044 SkEndian_SwapBE32(kTAG_cprt), 1045 SkEndian_SwapBE32(kTAG_cprt_Offset), 1046 SkEndian_SwapBE32(kTAG_cprt_Bytes), 1047}; 1048 1049static constexpr uint32_t kTAG_TextType = SkSetFourByteTag('m', 'l', 'u', 'c'); 1050static constexpr uint32_t gEmptyTextTag[3] { 1051 SkEndian_SwapBE32(kTAG_TextType), // Type signature 1052 0, // Reserved 1053 0, // Zero records 1054}; 1055 1056static void write_xyz_tag(uint32_t* ptr, const SkMatrix44& toXYZ, int row) { 1057 ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace); 1058 ptr[1] = 0; 1059 ptr[2] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 0))); 1060 ptr[3] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 1))); 1061 ptr[4] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 2))); 1062} 1063 1064static void write_trc_tag(uint32_t* ptr, float value) { 1065 ptr[0] = SkEndian_SwapBE32(kTAG_CurveType); 1066 ptr[1] = 0; 1067 1068 // Gamma will be specified with a single value. 1069 ptr[2] = SkEndian_SwapBE32(1); 1070 1071 // Convert gamma to 16-bit fixed point. 1072 uint16_t* ptr16 = (uint16_t*) (ptr + 3); 1073 ptr16[0] = SkEndian_SwapBE16((uint16_t) (value * 256.0f)); 1074 1075 // Pad tag with zero. 1076 ptr16[1] = 0; 1077} 1078 1079sk_sp<SkData> SkColorSpace_Base::writeToICC() const { 1080 // Return if this object was created from a profile, or if we have already serialized 1081 // the profile. 1082 if (fProfileData) { 1083 return fProfileData; 1084 } 1085 1086 // The client may create an SkColorSpace using an SkMatrix44, but currently we only 1087 // support writing profiles with 3x3 matrices. 1088 // TODO (msarett): Fix this! 1089 if (0.0f != fToXYZD50.getFloat(3, 0) || 0.0f != fToXYZD50.getFloat(3, 1) || 1090 0.0f != fToXYZD50.getFloat(3, 2) || 0.0f != fToXYZD50.getFloat(0, 3) || 1091 0.0f != fToXYZD50.getFloat(1, 3) || 0.0f != fToXYZD50.getFloat(2, 3)) 1092 { 1093 return nullptr; 1094 } 1095 1096 SkAutoMalloc profile(kICCProfileSize); 1097 uint8_t* ptr = (uint8_t*) profile.get(); 1098 1099 // Write profile header 1100 memcpy(ptr, gICCHeader, sizeof(gICCHeader)); 1101 ptr += sizeof(gICCHeader); 1102 1103 // Write tag table 1104 memcpy(ptr, gICCTagTable, sizeof(gICCTagTable)); 1105 ptr += sizeof(gICCTagTable); 1106 1107 // Write profile description tag 1108 memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag)); 1109 ptr += sizeof(gEmptyTextTag); 1110 1111 // Write XYZ tags 1112 write_xyz_tag((uint32_t*) ptr, fToXYZD50, 0); 1113 ptr += kTAG_XYZ_Bytes; 1114 write_xyz_tag((uint32_t*) ptr, fToXYZD50, 1); 1115 ptr += kTAG_XYZ_Bytes; 1116 write_xyz_tag((uint32_t*) ptr, fToXYZD50, 2); 1117 ptr += kTAG_XYZ_Bytes; 1118 1119 // Write TRC tags 1120 SkASSERT(as_CSB(this)->fGammas->fRed.isValue()); 1121 write_trc_tag((uint32_t*) ptr, as_CSB(this)->fGammas->fRed.fValue); 1122 ptr += SkAlign4(kTAG_TRC_Bytes); 1123 SkASSERT(as_CSB(this)->fGammas->fGreen.isValue()); 1124 write_trc_tag((uint32_t*) ptr, as_CSB(this)->fGammas->fGreen.fValue); 1125 ptr += SkAlign4(kTAG_TRC_Bytes); 1126 SkASSERT(as_CSB(this)->fGammas->fBlue.isValue()); 1127 write_trc_tag((uint32_t*) ptr, as_CSB(this)->fGammas->fBlue.fValue); 1128 ptr += SkAlign4(kTAG_TRC_Bytes); 1129 1130 // Write white point tag 1131 uint32_t* ptr32 = (uint32_t*) ptr; 1132 ptr32[0] = SkEndian_SwapBE32(kXYZ_PCSSpace); 1133 ptr32[1] = 0; 1134 // TODO (msarett): These values correspond to the D65 white point. This may not always be 1135 // correct. 1136 ptr32[2] = SkEndian_SwapBE32(0x0000f351); 1137 ptr32[3] = SkEndian_SwapBE32(0x00010000); 1138 ptr32[4] = SkEndian_SwapBE32(0x000116cc); 1139 ptr += kTAG_XYZ_Bytes; 1140 1141 // Write copyright tag 1142 memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag)); 1143 1144 // TODO (msarett): Should we try to hold onto the data so we can return immediately if 1145 // the client calls again? 1146 return SkData::MakeFromMalloc(profile.release(), kICCProfileSize); 1147} 1148