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