OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2016 Google Inc. | 2 * Copyright 2016 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "SkAtomics.h" | 8 #include "SkAtomics.h" |
9 #include "SkColorSpace.h" | 9 #include "SkColorSpace.h" |
10 | 10 |
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
151 case kSRGB_Named: | 151 case kSRGB_Named: |
152 return new SkColorSpace(gSRGB_toXYZD50, gSRGB_gamma, kSRGB_Named); | 152 return new SkColorSpace(gSRGB_toXYZD50, gSRGB_gamma, kSRGB_Named); |
153 default: | 153 default: |
154 break; | 154 break; |
155 } | 155 } |
156 return nullptr; | 156 return nullptr; |
157 } | 157 } |
158 | 158 |
159 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | 159 ////////////////////////////////////////////////////////////////////////////////
/////////////////// |
160 | 160 |
| 161 #include "SkFixed.h" |
| 162 #include "SkTemplates.h" |
| 163 |
| 164 #define SkColorSpacePrintf(...) |
| 165 |
| 166 #define return_if_false(pred, msg) \ |
| 167 do { \ |
| 168 if (!(pred)) { \ |
| 169 SkColorSpacePrintf("Invalid ICC Profile: %s.\n", msg); \ |
| 170 return false; \ |
| 171 } \ |
| 172 } while (0) |
| 173 |
| 174 #define return_null(msg) \ |
| 175 do { \ |
| 176 SkDebugf("ICC Profile: %s\n", (msg)); \ |
| 177 return nullptr; \ |
| 178 } while (0) |
| 179 |
| 180 static uint16_t read_big_endian_short(const uint8_t* ptr) { |
| 181 return ptr[0] << 8 | ptr[1]; |
| 182 } |
| 183 |
| 184 static uint32_t read_big_endian_int(const uint8_t* ptr) { |
| 185 return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; |
| 186 } |
| 187 |
| 188 // This is equal to the header size according to the ICC specification (128) |
| 189 // plus the size of the tag count (4). We include the tag count since we |
| 190 // always require it to be present anyway. |
| 191 static constexpr size_t kICCHeaderSize = 132; |
| 192 |
| 193 // Contains a signature (4), offset (4), and size (4). |
| 194 static constexpr size_t kICCTagTableEntrySize = 12; |
| 195 |
| 196 static constexpr uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' '
); |
| 197 static constexpr uint32_t kGray_ColorSpace = SkSetFourByteTag('G', 'R', 'A', 'Y'
); |
| 198 |
| 199 struct ICCProfileHeader { |
| 200 // TODO (msarett): |
| 201 // Can we ignore less of these fields? |
| 202 uint32_t fSize; |
| 203 uint32_t fCMMType_ignored; |
| 204 uint32_t fVersion; |
| 205 uint32_t fClassProfile; |
| 206 uint32_t fColorSpace; |
| 207 uint32_t fPCS; |
| 208 uint32_t fDateTime_ignored[3]; |
| 209 uint32_t fSignature; |
| 210 uint32_t fPlatformTarget_ignored; |
| 211 uint32_t fFlags_ignored; |
| 212 uint32_t fManufacturer_ignored; |
| 213 uint32_t fDeviceModel_ignored; |
| 214 uint32_t fDeviceAttributes_ignored[2]; |
| 215 uint32_t fRenderingIntent; |
| 216 uint32_t fIlluminantXYZ_ignored[3]; |
| 217 uint32_t fCreator_ignored; |
| 218 uint32_t fProfileId_ignored[4]; |
| 219 uint32_t fReserved_ignored[7]; |
| 220 uint32_t fTagCount; |
| 221 |
| 222 void init(const uint8_t* src, size_t len) { |
| 223 SkASSERT(kICCHeaderSize == sizeof(*this)); |
| 224 |
| 225 uint32_t* dst = (uint32_t*) this; |
| 226 for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) { |
| 227 dst[i] = read_big_endian_int(src); |
| 228 } |
| 229 } |
| 230 |
| 231 bool valid() const { |
| 232 // TODO (msarett): |
| 233 // For now it's nice to fail loudly on invalid inputs. But, can we |
| 234 // recover from some of these errors? |
| 235 |
| 236 return_if_false(fSize >= kICCHeaderSize, "Size is too small"); |
| 237 |
| 238 uint8_t majorVersion = fVersion >> 24; |
| 239 return_if_false(majorVersion <= 4, "Unsupported version"); |
| 240 |
| 241 const uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r'); |
| 242 const uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r'); |
| 243 const uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r'); |
| 244 // TODO (msarett): |
| 245 // Should we also support DeviceLink, ColorSpace, Abstract, or NamedColo
r? |
| 246 return_if_false(fClassProfile == kDisplay_Profile || |
| 247 fClassProfile == kInput_Profile || |
| 248 fClassProfile == kOutput_Profile, |
| 249 "Unsupported class profile"); |
| 250 |
| 251 // TODO (msarett): |
| 252 // There are many more color spaces that we could try to support. |
| 253 return_if_false(fColorSpace == kRGB_ColorSpace || fColorSpace == kGray_C
olorSpace, |
| 254 "Unsupported color space"); |
| 255 |
| 256 const uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' '); |
| 257 // TODO (msarett): |
| 258 // Can we support PCS LAB as well? |
| 259 return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space"); |
| 260 |
| 261 return_if_false(fSignature == SkSetFourByteTag('a', 'c', 's', 'p'), "Bad
signature"); |
| 262 |
| 263 // TODO (msarett): |
| 264 // Share this with sRGB? |
| 265 enum ICCIntents { |
| 266 kPerceptual = 0, |
| 267 kRelativeColorimetric = 1, |
| 268 kSaturation = 2, |
| 269 kAbsoluteColorimetric = 3, |
| 270 }; |
| 271 return_if_false(fRenderingIntent <= 3, "Bad rendering intent"); |
| 272 |
| 273 return_if_false(fTagCount <= 100, "Too many tags"); |
| 274 |
| 275 return true; |
| 276 } |
| 277 }; |
| 278 |
| 279 struct ICCTag { |
| 280 uint32_t fSignature; |
| 281 uint32_t fOffset; |
| 282 uint32_t fLength; |
| 283 |
| 284 const uint8_t* init(const uint8_t* src) { |
| 285 fSignature = read_big_endian_int(src); |
| 286 fOffset = read_big_endian_int(src + 4); |
| 287 fLength = read_big_endian_int(src + 8); |
| 288 return src + 12; |
| 289 } |
| 290 |
| 291 bool valid(size_t len) { |
| 292 return_if_false(fOffset + fLength <= len, "Tag too large for ICC profile
.\n"); |
| 293 return true; |
| 294 } |
| 295 |
| 296 const uint8_t* addr(const uint8_t* src) const { |
| 297 return src + fOffset; |
| 298 } |
| 299 |
| 300 static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature
) { |
| 301 for (int i = 0; i < count; ++i) { |
| 302 if (tags[i].fSignature == signature) { |
| 303 return &tags[i]; |
| 304 } |
| 305 } |
| 306 return nullptr; |
| 307 } |
| 308 }; |
| 309 |
| 310 // TODO (msarett): |
| 311 // Should we recognize more tags? |
| 312 constexpr uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z'); |
| 313 constexpr uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z'); |
| 314 constexpr uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z'); |
| 315 constexpr uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C'); |
| 316 constexpr uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C'); |
| 317 constexpr uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C'); |
| 318 |
| 319 bool load_xyz(float dst[3], const uint8_t* src, size_t len) { |
| 320 if (len < 20) { |
| 321 SkColorSpacePrintf("XYZ tag is too small (%d bytes)", len); |
| 322 return false; |
| 323 } |
| 324 |
| 325 dst[0] = SkFixedToFloat(read_big_endian_int(src + 8)); |
| 326 dst[1] = SkFixedToFloat(read_big_endian_int(src + 12)); |
| 327 dst[2] = SkFixedToFloat(read_big_endian_int(src + 16)); |
| 328 SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]); |
| 329 return true; |
| 330 } |
| 331 |
| 332 constexpr uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', 'v'); |
| 333 constexpr uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a'); |
| 334 |
| 335 static bool load_gamma(float* gamma, const uint8_t* src, size_t len) { |
| 336 if (len < 14) { |
| 337 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); |
| 338 return false; |
| 339 } |
| 340 |
| 341 uint32_t type = read_big_endian_int(src); |
| 342 switch (type) { |
| 343 case kTAG_CurveType: { |
| 344 uint32_t count = read_big_endian_int(src + 8); |
| 345 if (0 == count) { |
| 346 return false; |
| 347 } |
| 348 |
| 349 const uint16_t* table = (const uint16_t*) (src + 12); |
| 350 if (1 == count) { |
| 351 // Table entry is the exponent (bias 256). |
| 352 uint16_t value = read_big_endian_short((const uint8_t*) table); |
| 353 *gamma = value / 256.0f; |
| 354 SkColorSpacePrintf("gamma %d %g\n", value, *gamma); |
| 355 return true; |
| 356 } |
| 357 |
| 358 // Check length again if we have a table. |
| 359 if (len < 12 + 2 * count) { |
| 360 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); |
| 361 return false; |
| 362 } |
| 363 |
| 364 // Print the interpolation table. For now, we ignore this and guess
2.2f. |
| 365 for (uint32_t i = 0; i < count; i++) { |
| 366 SkColorSpacePrintf("curve[%d] %d\n", i, |
| 367 read_big_endian_short((const uint8_t*) &table[i])); |
| 368 } |
| 369 |
| 370 *gamma = 2.2f; |
| 371 return true; |
| 372 } break; |
| 373 case kTAG_ParaCurveType: { |
| 374 // Guess 2.2f. |
| 375 SkColorSpacePrintf("parametric curve\n"); |
| 376 *gamma = 2.2f; |
| 377 return true; |
| 378 } break; |
| 379 default: |
| 380 SkColorSpacePrintf("Unsupported gamma tag type %d\n", type); |
| 381 return false; |
| 382 } |
| 383 } |
| 384 |
| 385 SkColorSpace* SkColorSpace::NewICC(const void* base, size_t len) { |
| 386 const uint8_t* ptr = (const uint8_t*) base; |
| 387 |
| 388 if (len < kICCHeaderSize) { |
| 389 SkColorSpacePrintf("Data is not large enough to contain an ICC profile.\
n"); |
| 390 return nullptr; |
| 391 } |
| 392 |
| 393 // Read the ICC profile header and check to make sure that it is valid. |
| 394 ICCProfileHeader header; |
| 395 header.init(ptr, len); |
| 396 if (!header.valid()) { |
| 397 return nullptr; |
| 398 } |
| 399 |
| 400 // Adjust ptr and len before reading the tags. |
| 401 if (len < header.fSize) { |
| 402 SkColorSpacePrintf("ICC profile might be truncated.\n"); |
| 403 } else if (len > header.fSize) { |
| 404 SkColorSpacePrintf("Caller provided extra data beyonf the end of the ICC
profile.\n"); |
| 405 len = header.fSize; |
| 406 } |
| 407 ptr += kICCHeaderSize; |
| 408 len -= kICCHeaderSize; |
| 409 |
| 410 // Parse tag headers. |
| 411 uint32_t tagCount = header.fTagCount; |
| 412 SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount); |
| 413 if (len < kICCTagTableEntrySize * tagCount) { |
| 414 SkColorSpacePrintf("Not enough input data to read tag table entries.\n")
; |
| 415 } |
| 416 |
| 417 SkAutoTArray<ICCTag> tags(tagCount); |
| 418 for (uint32_t i = 0; i < tagCount; i++) { |
| 419 ptr = tags[i].init(ptr); |
| 420 SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24
) & 0xFF, |
| 421 (tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >> 8) &
0xFF, |
| 422 (tags[i].fSignature >> 0) & 0xFF, tags[i].fOffset, tags[i].fLen
gth); |
| 423 |
| 424 if (!tags[i].valid(kICCHeaderSize + len)) { |
| 425 return nullptr; |
| 426 } |
| 427 } |
| 428 |
| 429 // Load our XYZ and gamma matrices. |
| 430 SkFloat3x3 toXYZ; |
| 431 SkFloat3 gamma {{ 1.0f, 1.0f, 1.0f }}; |
| 432 switch (header.fColorSpace) { |
| 433 case kRGB_ColorSpace: { |
| 434 const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ); |
| 435 const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ); |
| 436 const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ); |
| 437 if (!r || !g || !b) { |
| 438 return_null("Need rgb tags for XYZ space"); |
| 439 } |
| 440 |
| 441 if (!load_xyz(&toXYZ.fMat[0], r->addr((const uint8_t*) base), r->fLe
ngth) || |
| 442 !load_xyz(&toXYZ.fMat[3], g->addr((const uint8_t*) base), g->fLe
ngth) || |
| 443 !load_xyz(&toXYZ.fMat[6], b->addr((const uint8_t*) base), b->fLe
ngth)) { |
| 444 return_null("Need valid rgb tags for XYZ space"); |
| 445 } |
| 446 |
| 447 r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC); |
| 448 g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC); |
| 449 b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC); |
| 450 if (r) { |
| 451 load_gamma(&gamma.fVec[0], r->addr((const uint8_t*) base), r->fL
ength); |
| 452 } |
| 453 if (g) { |
| 454 load_gamma(&gamma.fVec[1], g->addr((const uint8_t*) base), g->fL
ength); |
| 455 } |
| 456 if (b) { |
| 457 load_gamma(&gamma.fVec[2], b->addr((const uint8_t*) base), b->fL
ength); |
| 458 } |
| 459 return SkColorSpace::NewRGB(toXYZ, gamma); |
| 460 } |
| 461 default: |
| 462 break; |
| 463 } |
| 464 |
| 465 return_null("ICC profile contains unsupported colorspace.\n"); |
| 466 } |
| 467 |
| 468 ////////////////////////////////////////////////////////////////////////////////
/////////////////// |
| 469 |
161 SkColorSpace::Result SkColorSpace::Concat(const SkColorSpace* src, const SkColor
Space* dst, | 470 SkColorSpace::Result SkColorSpace::Concat(const SkColorSpace* src, const SkColor
Space* dst, |
162 SkFloat3x3* result) { | 471 SkFloat3x3* result) { |
163 if (!src || !dst || (src->named() == kDevice_Named) || (src->named() == dst-
>named())) { | 472 if (!src || !dst || (src->named() == kDevice_Named) || (src->named() == dst-
>named())) { |
164 if (result) { | 473 if (result) { |
165 *result = {{ 1, 0, 0, 0, 1, 0, 0, 0, 1 }}; | 474 *result = {{ 1, 0, 0, 0, 1, 0, 0, 0, 1 }}; |
166 } | 475 } |
167 return kIdentity_Result; | 476 return kIdentity_Result; |
168 } | 477 } |
169 if (result) { | 478 if (result) { |
170 *result = concat(src->fToXYZD50, invert(dst->fToXYZD50)); | 479 *result = concat(src->fToXYZD50, invert(dst->fToXYZD50)); |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
236 } | 545 } |
237 | 546 |
238 // D65 white point of Rec. 709 [8] are: | 547 // D65 white point of Rec. 709 [8] are: |
239 // | 548 // |
240 // D65 white-point in unit luminance XYZ = 0.9505, 1.0000, 1.0890 | 549 // D65 white-point in unit luminance XYZ = 0.9505, 1.0000, 1.0890 |
241 // | 550 // |
242 // R G B white | 551 // R G B white |
243 // x 0.640 0.300 0.150 0.3127 | 552 // x 0.640 0.300 0.150 0.3127 |
244 // y 0.330 0.600 0.060 0.3290 | 553 // y 0.330 0.600 0.060 0.3290 |
245 // z 0.030 0.100 0.790 0.3582 | 554 // z 0.030 0.100 0.790 0.3582 |
OLD | NEW |