SkColorSpace.cpp revision 1cf632500a854d01b789ea22c1cf7967cfb6f570
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 "SkAtomics.h" 9#include "SkColorSpace.h" 10 11static inline bool SkFloatIsFinite(float x) { return 0 == x * 0; } 12 13// 14// SkFloat3x3 15// 16// In memory order, values are a, b, c, d, e, f, g, h, i 17// 18// When applied to a color component vector (e.g. [ r, r, r ] or [ g, g, g ] we do 19// 20// [ r r r ] * [ a b c ] + [ g g g ] * [ d e f ] + [ b b b ] * [ g h i ] 21// 22// Thus in our point-on-the-right notation, the matrix looks like 23// 24// [ a d g ] [ r ] 25// [ b e h ] * [ g ] 26// [ c f i ] [ b ] 27// 28static SkFloat3x3 concat(const SkFloat3x3& left, const SkFloat3x3& rite) { 29 SkFloat3x3 result; 30 for (int row = 0; row < 3; ++row) { 31 for (int col = 0; col < 3; ++col) { 32 double tmp = 0; 33 for (int i = 0; i < 3; ++i) { 34 tmp += (double)left.fMat[row + i * 3] * rite.fMat[i + col * 3]; 35 } 36 result.fMat[row + col * 3] = (double)tmp; 37 } 38 } 39 return result; 40} 41 42static double det(const SkFloat3x3& m) { 43 return (double)m.fMat[0] * m.fMat[4] * m.fMat[8] + 44 (double)m.fMat[3] * m.fMat[7] * m.fMat[2] + 45 (double)m.fMat[6] * m.fMat[1] * m.fMat[5] - 46 (double)m.fMat[0] * m.fMat[7] * m.fMat[5] - 47 (double)m.fMat[3] * m.fMat[1] * m.fMat[8] - 48 (double)m.fMat[6] * m.fMat[4] * m.fMat[2]; 49} 50 51static double det2x2(const SkFloat3x3& m, int a, int b, int c, int d) { 52 return (double)m.fMat[a] * m.fMat[b] - (double)m.fMat[c] * m.fMat[d]; 53} 54 55static SkFloat3x3 invert(const SkFloat3x3& m) { 56 double d = det(m); 57 SkASSERT(SkFloatIsFinite((float)d)); 58 double scale = 1 / d; 59 SkASSERT(SkFloatIsFinite((float)scale)); 60 61 return {{ 62 (float)(scale * det2x2(m, 4, 8, 5, 7)), 63 (float)(scale * det2x2(m, 7, 2, 8, 1)), 64 (float)(scale * det2x2(m, 1, 5, 2, 4)), 65 66 (float)(scale * det2x2(m, 6, 5, 8, 3)), 67 (float)(scale * det2x2(m, 0, 8, 2, 6)), 68 (float)(scale * det2x2(m, 3, 2, 5, 0)), 69 70 (float)(scale * det2x2(m, 3, 7, 4, 6)), 71 (float)(scale * det2x2(m, 6, 1, 7, 0)), 72 (float)(scale * det2x2(m, 0, 4, 1, 3)), 73 }}; 74} 75 76void SkFloat3::dump() const { 77 SkDebugf("[%7.4f %7.4f %7.4f]\n", fVec[0], fVec[1], fVec[2]); 78} 79 80void SkFloat3x3::dump() const { 81 SkDebugf("[%7.4f %7.4f %7.4f] [%7.4f %7.4f %7.4f] [%7.4f %7.4f %7.4f]\n", 82 fMat[0], fMat[1], fMat[2], 83 fMat[3], fMat[4], fMat[5], 84 fMat[6], fMat[7], fMat[8]); 85} 86 87////////////////////////////////////////////////////////////////////////////////////////////////// 88 89static int32_t gUniqueColorSpaceID; 90 91SkColorSpace::SkColorSpace(const SkFloat3x3& toXYZD50, const SkFloat3& gamma, Named named) 92 : fToXYZD50(toXYZD50) 93 , fGamma(gamma) 94 , fUniqueID(sk_atomic_inc(&gUniqueColorSpaceID)) 95 , fNamed(named) 96{ 97 for (int i = 0; i < 3; ++i) { 98 SkASSERT(SkFloatIsFinite(gamma.fVec[i])); 99 for (int j = 0; j < 3; ++j) { 100 SkASSERT(SkFloatIsFinite(toXYZD50.fMat[3*i + j])); 101 } 102 } 103} 104 105sk_sp<SkColorSpace> SkColorSpace::NewRGB(const SkFloat3x3& toXYZD50, const SkFloat3& gamma) { 106 for (int i = 0; i < 3; ++i) { 107 if (!SkFloatIsFinite(gamma.fVec[i]) || gamma.fVec[i] < 0) { 108 return nullptr; 109 } 110 for (int j = 0; j < 3; ++j) { 111 if (!SkFloatIsFinite(toXYZD50.fMat[3*i + j])) { 112 return nullptr; 113 } 114 } 115 } 116 117 // check the matrix for invertibility 118 float d = det(toXYZD50); 119 if (!SkFloatIsFinite(d) || !SkFloatIsFinite(1 / d)) { 120 return nullptr; 121 } 122 123 return sk_sp<SkColorSpace>(new SkColorSpace(toXYZD50, gamma, kUnknown_Named)); 124} 125 126void SkColorSpace::dump() const { 127 fToXYZD50.dump(); 128 fGamma.dump(); 129} 130 131////////////////////////////////////////////////////////////////////////////////////////////////// 132 133const SkFloat3 gDevice_gamma {{ 0, 0, 0 }}; 134const SkFloat3x3 gDevice_toXYZD50 {{ 135 1, 0, 0, 136 0, 1, 0, 137 0, 0, 1 138}}; 139 140const SkFloat3 gSRGB_gamma {{ 2.2f, 2.2f, 2.2f }}; 141const SkFloat3x3 gSRGB_toXYZD50 {{ 142 0.4358f, 0.2224f, 0.0139f, // * R 143 0.3853f, 0.7170f, 0.0971f, // * G 144 0.1430f, 0.0606f, 0.7139f, // * B 145}}; 146 147sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) { 148 switch (named) { 149 case kDevice_Named: 150 return sk_sp<SkColorSpace>(new SkColorSpace(gDevice_toXYZD50, gDevice_gamma, 151 kDevice_Named)); 152 case kSRGB_Named: 153 return sk_sp<SkColorSpace>(new SkColorSpace(gSRGB_toXYZD50, gSRGB_gamma, kSRGB_Named)); 154 default: 155 break; 156 } 157 return nullptr; 158} 159 160/////////////////////////////////////////////////////////////////////////////////////////////////// 161 162#include "SkFixed.h" 163#include "SkTemplates.h" 164 165#define SkColorSpacePrintf(...) 166 167#define return_if_false(pred, msg) \ 168 do { \ 169 if (!(pred)) { \ 170 SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \ 171 return false; \ 172 } \ 173 } while (0) 174 175#define return_null(msg) \ 176 do { \ 177 SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \ 178 return nullptr; \ 179 } while (0) 180 181static uint16_t read_big_endian_short(const uint8_t* ptr) { 182 return ptr[0] << 8 | ptr[1]; 183} 184 185static uint32_t read_big_endian_int(const uint8_t* ptr) { 186 return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; 187} 188 189// This is equal to the header size according to the ICC specification (128) 190// plus the size of the tag count (4). We include the tag count since we 191// always require it to be present anyway. 192static const size_t kICCHeaderSize = 132; 193 194// Contains a signature (4), offset (4), and size (4). 195static const size_t kICCTagTableEntrySize = 12; 196 197static const uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' '); 198static const uint32_t kGray_ColorSpace = SkSetFourByteTag('G', 'R', 'A', 'Y'); 199 200struct ICCProfileHeader { 201 // TODO (msarett): 202 // Can we ignore less of these fields? 203 uint32_t fSize; 204 uint32_t fCMMType_ignored; 205 uint32_t fVersion; 206 uint32_t fClassProfile; 207 uint32_t fColorSpace; 208 uint32_t fPCS; 209 uint32_t fDateTime_ignored[3]; 210 uint32_t fSignature; 211 uint32_t fPlatformTarget_ignored; 212 uint32_t fFlags_ignored; 213 uint32_t fManufacturer_ignored; 214 uint32_t fDeviceModel_ignored; 215 uint32_t fDeviceAttributes_ignored[2]; 216 uint32_t fRenderingIntent; 217 uint32_t fIlluminantXYZ_ignored[3]; 218 uint32_t fCreator_ignored; 219 uint32_t fProfileId_ignored[4]; 220 uint32_t fReserved_ignored[7]; 221 uint32_t fTagCount; 222 223 void init(const uint8_t* src, size_t len) { 224 SkASSERT(kICCHeaderSize == sizeof(*this)); 225 226 uint32_t* dst = (uint32_t*) this; 227 for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) { 228 dst[i] = read_big_endian_int(src); 229 } 230 } 231 232 bool valid() const { 233 // TODO (msarett): 234 // For now it's nice to fail loudly on invalid inputs. But, can we 235 // recover from some of these errors? 236 237 return_if_false(fSize >= kICCHeaderSize, "Size is too small"); 238 239 uint8_t majorVersion = fVersion >> 24; 240 return_if_false(majorVersion <= 4, "Unsupported version"); 241 242 const uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r'); 243 const uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r'); 244 const uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r'); 245 // TODO (msarett): 246 // Should we also support DeviceLink, ColorSpace, Abstract, or NamedColor? 247 return_if_false(fClassProfile == kDisplay_Profile || 248 fClassProfile == kInput_Profile || 249 fClassProfile == kOutput_Profile, 250 "Unsupported class profile"); 251 252 // TODO (msarett): 253 // There are many more color spaces that we could try to support. 254 return_if_false(fColorSpace == kRGB_ColorSpace || fColorSpace == kGray_ColorSpace, 255 "Unsupported color space"); 256 257 const uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' '); 258 // TODO (msarett): 259 // Can we support PCS LAB as well? 260 return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space"); 261 262 return_if_false(fSignature == SkSetFourByteTag('a', 'c', 's', 'p'), "Bad signature"); 263 264 // TODO (msarett): 265 // Should we treat different rendering intents differently? 266 // Valid rendering intents include kPerceptual (0), kRelative (1), 267 // kSaturation (2), and kAbsolute (3). 268 return_if_false(fRenderingIntent <= 3, "Bad rendering intent"); 269 270 return_if_false(fTagCount <= 100, "Too many tags"); 271 272 return true; 273 } 274}; 275 276struct ICCTag { 277 uint32_t fSignature; 278 uint32_t fOffset; 279 uint32_t fLength; 280 281 const uint8_t* init(const uint8_t* src) { 282 fSignature = read_big_endian_int(src); 283 fOffset = read_big_endian_int(src + 4); 284 fLength = read_big_endian_int(src + 8); 285 return src + 12; 286 } 287 288 bool valid(size_t len) { 289 return_if_false(fOffset + fLength <= len, "Tag too large for ICC profile"); 290 return true; 291 } 292 293 const uint8_t* addr(const uint8_t* src) const { 294 return src + fOffset; 295 } 296 297 static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature) { 298 for (int i = 0; i < count; ++i) { 299 if (tags[i].fSignature == signature) { 300 return &tags[i]; 301 } 302 } 303 return nullptr; 304 } 305}; 306 307// TODO (msarett): 308// Should we recognize more tags? 309static const uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z'); 310static const uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z'); 311static const uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z'); 312static const uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C'); 313static const uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C'); 314static const uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C'); 315 316bool load_xyz(float dst[3], const uint8_t* src, size_t len) { 317 if (len < 20) { 318 SkColorSpacePrintf("XYZ tag is too small (%d bytes)", len); 319 return false; 320 } 321 322 dst[0] = SkFixedToFloat(read_big_endian_int(src + 8)); 323 dst[1] = SkFixedToFloat(read_big_endian_int(src + 12)); 324 dst[2] = SkFixedToFloat(read_big_endian_int(src + 16)); 325 SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]); 326 return true; 327} 328 329static const uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', 'v'); 330static const uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a'); 331 332static bool load_gamma(float* gamma, const uint8_t* src, size_t len) { 333 if (len < 14) { 334 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); 335 return false; 336 } 337 338 uint32_t type = read_big_endian_int(src); 339 switch (type) { 340 case kTAG_CurveType: { 341 uint32_t count = read_big_endian_int(src + 8); 342 if (0 == count) { 343 return false; 344 } 345 346 const uint16_t* table = (const uint16_t*) (src + 12); 347 if (1 == count) { 348 // Table entry is the exponent (bias 256). 349 uint16_t value = read_big_endian_short((const uint8_t*) table); 350 *gamma = value / 256.0f; 351 SkColorSpacePrintf("gamma %d %g\n", value, *gamma); 352 return true; 353 } 354 355 // Check length again if we have a table. 356 if (len < 12 + 2 * count) { 357 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); 358 return false; 359 } 360 361 // Print the interpolation table. For now, we ignore this and guess 2.2f. 362 for (uint32_t i = 0; i < count; i++) { 363 SkColorSpacePrintf("curve[%d] %d\n", i, 364 read_big_endian_short((const uint8_t*) &table[i])); 365 } 366 367 *gamma = 2.2f; 368 return true; 369 } 370 case kTAG_ParaCurveType: 371 // Guess 2.2f. 372 SkColorSpacePrintf("parametric curve\n"); 373 *gamma = 2.2f; 374 return true; 375 default: 376 SkColorSpacePrintf("Unsupported gamma tag type %d\n", type); 377 return false; 378 } 379} 380 381sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* base, size_t len) { 382 const uint8_t* ptr = (const uint8_t*) base; 383 384 if (len < kICCHeaderSize) { 385 return_null("Data is not large enough to contain an ICC profile"); 386 } 387 388 // Read the ICC profile header and check to make sure that it is valid. 389 ICCProfileHeader header; 390 header.init(ptr, len); 391 if (!header.valid()) { 392 return nullptr; 393 } 394 395 // Adjust ptr and len before reading the tags. 396 if (len < header.fSize) { 397 SkColorSpacePrintf("ICC profile might be truncated.\n"); 398 } else if (len > header.fSize) { 399 SkColorSpacePrintf("Caller provided extra data beyond the end of the ICC profile.\n"); 400 len = header.fSize; 401 } 402 ptr += kICCHeaderSize; 403 len -= kICCHeaderSize; 404 405 // Parse tag headers. 406 uint32_t tagCount = header.fTagCount; 407 SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount); 408 if (len < kICCTagTableEntrySize * tagCount) { 409 return_null("Not enough input data to read tag table entries"); 410 } 411 412 SkAutoTArray<ICCTag> tags(tagCount); 413 for (uint32_t i = 0; i < tagCount; i++) { 414 ptr = tags[i].init(ptr); 415 SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24) & 0xFF, 416 (tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >> 8) & 0xFF, 417 (tags[i].fSignature >> 0) & 0xFF, tags[i].fOffset, tags[i].fLength); 418 419 if (!tags[i].valid(kICCHeaderSize + len)) { 420 return_null("Tag is too large to fit in ICC profile"); 421 } 422 } 423 424 // Load our XYZ and gamma matrices. 425 SkFloat3x3 toXYZ; 426 SkFloat3 gamma {{ 1.0f, 1.0f, 1.0f }}; 427 switch (header.fColorSpace) { 428 case kRGB_ColorSpace: { 429 const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ); 430 const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ); 431 const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ); 432 if (!r || !g || !b) { 433 return_null("Need rgb tags for XYZ space"); 434 } 435 436 if (!load_xyz(&toXYZ.fMat[0], r->addr((const uint8_t*) base), r->fLength) || 437 !load_xyz(&toXYZ.fMat[3], g->addr((const uint8_t*) base), g->fLength) || 438 !load_xyz(&toXYZ.fMat[6], b->addr((const uint8_t*) base), b->fLength)) 439 { 440 return_null("Need valid rgb tags for XYZ space"); 441 } 442 443 r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC); 444 g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC); 445 b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC); 446 if (!r || !load_gamma(&gamma.fVec[0], r->addr((const uint8_t*) base), r->fLength)) { 447 SkColorSpacePrintf("Failed to read R gamma tag.\n"); 448 } 449 if (!g || !load_gamma(&gamma.fVec[1], g->addr((const uint8_t*) base), g->fLength)) { 450 SkColorSpacePrintf("Failed to read G gamma tag.\n"); 451 } 452 if (!b || !load_gamma(&gamma.fVec[2], b->addr((const uint8_t*) base), b->fLength)) { 453 SkColorSpacePrintf("Failed to read B gamma tag.\n"); 454 } 455 return SkColorSpace::NewRGB(toXYZ, gamma); 456 } 457 default: 458 break; 459 } 460 461 return_null("ICC profile contains unsupported colorspace"); 462} 463 464/////////////////////////////////////////////////////////////////////////////////////////////////// 465 466SkColorSpace::Result SkColorSpace::Concat(const SkColorSpace* src, const SkColorSpace* dst, 467 SkFloat3x3* result) { 468 if (!src || !dst || (src->named() == kDevice_Named) || (src->named() == dst->named())) { 469 if (result) { 470 *result = {{ 1, 0, 0, 0, 1, 0, 0, 0, 1 }}; 471 } 472 return kIdentity_Result; 473 } 474 if (result) { 475 *result = concat(src->fToXYZD50, invert(dst->fToXYZD50)); 476 } 477 return kNormal_Result; 478} 479 480#include "SkColor.h" 481#include "SkNx.h" 482#include "SkPM4f.h" 483 484void SkApply3x3ToPM4f(const SkFloat3x3& m, const SkPM4f src[], SkPM4f dst[], int count) { 485 SkASSERT(1 == SkPM4f::G); 486 SkASSERT(3 == SkPM4f::A); 487 488 Sk4f cr, cg, cb; 489 cg = Sk4f::Load(m.fMat + 3); 490 if (0 == SkPM4f::R) { 491 SkASSERT(2 == SkPM4f::B); 492 cr = Sk4f::Load(m.fMat + 0); 493 cb = Sk4f(m.fMat[6], m.fMat[7], m.fMat[8], 0); 494 } else { 495 SkASSERT(0 == SkPM4f::B); 496 SkASSERT(2 == SkPM4f::R); 497 cb = Sk4f::Load(m.fMat + 0); 498 cr = Sk4f(m.fMat[6], m.fMat[7], m.fMat[8], 0); 499 } 500 cr = cr * Sk4f(1, 1, 1, 0); 501 cg = cg * Sk4f(1, 1, 1, 0); 502 cb = cb * Sk4f(1, 1, 1, 0); 503 504 for (int i = 0; i < count; ++i) { 505 Sk4f r = Sk4f(src[i].fVec[SkPM4f::R]); 506 Sk4f g = Sk4f(src[i].fVec[SkPM4f::G]); 507 Sk4f b = Sk4f(src[i].fVec[SkPM4f::B]); 508 Sk4f a = Sk4f(0, 0, 0, src[i].fVec[SkPM4f::A]); 509 (cr * r + cg * g + cb * b + a).store(&dst[i]); 510 } 511} 512 513/////////////////////////////////////////////////////////////////////////////////////////////////// 514 515void SkColorSpace::Test() { 516 SkFloat3x3 mat {{ 2, 0, 0, 0, 3, 0, 0, 0, 4 }}; 517 SkFloat3x3 inv = invert(mat); 518 mat.dump(); 519 inv.dump(); 520 concat(mat, inv).dump(); 521 concat(inv, mat).dump(); 522 SkDebugf("\n"); 523 524 mat = gSRGB_toXYZD50; 525 inv = invert(mat); 526 mat.dump(); 527 inv.dump(); 528 concat(mat, inv).dump(); 529 concat(inv, mat).dump(); 530 SkDebugf("\n"); 531 532 sk_sp<SkColorSpace> cs0(SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named)); 533 sk_sp<SkColorSpace> cs1(SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named)); 534 535 cs0->dump(); 536 cs1->dump(); 537 SkFloat3x3 xform; 538 (void)SkColorSpace::Concat(cs0.get(), cs1.get(), &xform); 539 xform.dump(); 540 SkDebugf("\n"); 541} 542 543// D65 white point of Rec. 709 [8] are: 544// 545// D65 white-point in unit luminance XYZ = 0.9505, 1.0000, 1.0890 546// 547// R G B white 548// x 0.640 0.300 0.150 0.3127 549// y 0.330 0.600 0.060 0.3290 550// z 0.030 0.100 0.790 0.3582 551