| 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 "SkColorSpace.h" | 8 #include "SkColorSpace.h" |
| 9 #include "SkColorSpace_Base.h" | 9 #include "SkColorSpace_Base.h" |
| 10 #include "SkEndian.h" | 10 #include "SkColorSpacePriv.h" |
| 11 #include "SkOnce.h" | 11 #include "SkOnce.h" |
| 12 #include "SkReadBuffer.h" | |
| 13 #include "SkWriteBuffer.h" | |
| 14 | |
| 15 #define SkColorSpacePrintf(...) | |
| 16 | |
| 17 static bool color_space_almost_equal(float a, float b) { | |
| 18 return SkTAbs(a - b) < 0.01f; | |
| 19 } | |
| 20 | |
| 21 ////////////////////////////////////////////////////////////////////////////////
////////////////// | |
| 22 | 12 |
| 23 SkColorSpace::SkColorSpace(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Na
med named) | 13 SkColorSpace::SkColorSpace(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Na
med named) |
| 24 : fGammaNamed(gammaNamed) | 14 : fGammaNamed(gammaNamed) |
| 25 , fToXYZD50(toXYZD50) | 15 , fToXYZD50(toXYZD50) |
| 26 , fNamed(named) | 16 , fNamed(named) |
| 27 {} | 17 {} |
| 28 | 18 |
| 29 SkColorSpace_Base::SkColorSpace_Base(GammaNamed gammaNamed, const SkMatrix44& to
XYZD50, Named named) | 19 SkColorSpace_Base::SkColorSpace_Base(GammaNamed gammaNamed, const SkMatrix44& to
XYZD50, Named named) |
| 30 : INHERITED(gammaNamed, toXYZD50, named) | 20 : INHERITED(gammaNamed, toXYZD50, named) |
| 31 , fGammas(nullptr) | 21 , fGammas(nullptr) |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 70 color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) && | 60 color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) && |
| 71 color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) && | 61 color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) && |
| 72 color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) && | 62 color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) && |
| 73 color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) && | 63 color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) && |
| 74 color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) && | 64 color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) && |
| 75 color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) && | 65 color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) && |
| 76 color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) && | 66 color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) && |
| 77 color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f); | 67 color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f); |
| 78 } | 68 } |
| 79 | 69 |
| 80 static void set_gamma_value(SkGammaCurve* gamma, float value) { | |
| 81 if (color_space_almost_equal(2.2f, value)) { | |
| 82 gamma->fNamed = SkColorSpace::k2Dot2Curve_GammaNamed; | |
| 83 } else if (color_space_almost_equal(1.0f, value)) { | |
| 84 gamma->fNamed = SkColorSpace::kLinear_GammaNamed; | |
| 85 } else if (color_space_almost_equal(0.0f, value)) { | |
| 86 SkColorSpacePrintf("Treating invalid zero gamma as linear."); | |
| 87 gamma->fNamed = SkColorSpace::kLinear_GammaNamed; | |
| 88 } else { | |
| 89 gamma->fValue = value; | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(float values[3], const SkMatrix44&
toXYZD50) { | 70 sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(float values[3], const SkMatrix44&
toXYZD50) { |
| 94 SkGammaCurve curves[3]; | 71 SkGammaCurve curves[3]; |
| 95 set_gamma_value(&curves[0], values[0]); | 72 set_gamma_value(&curves[0], values[0]); |
| 96 set_gamma_value(&curves[1], values[1]); | 73 set_gamma_value(&curves[1], values[1]); |
| 97 set_gamma_value(&curves[2], values[2]); | 74 set_gamma_value(&curves[2], values[2]); |
| 98 | 75 |
| 99 GammaNamed gammaNamed = SkGammas::Named(curves); | 76 GammaNamed gammaNamed = SkGammas::Named(curves); |
| 100 if (kNonStandard_GammaNamed == gammaNamed) { | 77 if (kNonStandard_GammaNamed == gammaNamed) { |
| 101 sk_sp<SkGammas> gammas(new SkGammas(std::move(curves[0]), std::move(curv
es[1]), | 78 sk_sp<SkGammas> gammas(new SkGammas(std::move(curves[0]), std::move(curv
es[1]), |
| 102 std::move(curves[2]))); | 79 std::move(curves[2]))); |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 157 return sk_ref_sp(adobeRGB); | 134 return sk_ref_sp(adobeRGB); |
| 158 } | 135 } |
| 159 default: | 136 default: |
| 160 break; | 137 break; |
| 161 } | 138 } |
| 162 return nullptr; | 139 return nullptr; |
| 163 } | 140 } |
| 164 | 141 |
| 165 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | 142 ////////////////////////////////////////////////////////////////////////////////
/////////////////// |
| 166 | 143 |
| 167 #include "SkFixed.h" | |
| 168 #include "SkTemplates.h" | |
| 169 | |
| 170 #define return_if_false(pred, msg) \ | |
| 171 do { \ | |
| 172 if (!(pred)) { \ | |
| 173 SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \ | |
| 174 return false; \ | |
| 175 } \ | |
| 176 } while (0) | |
| 177 | |
| 178 #define return_null(msg) \ | |
| 179 do { \ | |
| 180 SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \ | |
| 181 return nullptr; \ | |
| 182 } while (0) | |
| 183 | |
| 184 static uint16_t read_big_endian_short(const uint8_t* ptr) { | |
| 185 return ptr[0] << 8 | ptr[1]; | |
| 186 } | |
| 187 | |
| 188 static uint32_t read_big_endian_uint(const uint8_t* ptr) { | |
| 189 return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; | |
| 190 } | |
| 191 | |
| 192 static int32_t read_big_endian_int(const uint8_t* ptr) { | |
| 193 return (int32_t) read_big_endian_uint(ptr); | |
| 194 } | |
| 195 | |
| 196 // This is equal to the header size according to the ICC specification (128) | |
| 197 // plus the size of the tag count (4). We include the tag count since we | |
| 198 // always require it to be present anyway. | |
| 199 static constexpr size_t kICCHeaderSize = 132; | |
| 200 | |
| 201 // Contains a signature (4), offset (4), and size (4). | |
| 202 static constexpr size_t kICCTagTableEntrySize = 12; | |
| 203 | |
| 204 static constexpr uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' '
); | |
| 205 static constexpr uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r'
); | |
| 206 static constexpr uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r'
); | |
| 207 static constexpr uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r'
); | |
| 208 static constexpr uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' '
); | |
| 209 static constexpr uint32_t kACSP_Signature = SkSetFourByteTag('a', 'c', 's', 'p'
); | |
| 210 | |
| 211 struct ICCProfileHeader { | |
| 212 uint32_t fSize; | |
| 213 | |
| 214 // No reason to care about the preferred color management module (ex: Adobe,
Apple, etc.). | |
| 215 // We're always going to use this one. | |
| 216 uint32_t fCMMType_ignored; | |
| 217 | |
| 218 uint32_t fVersion; | |
| 219 uint32_t fProfileClass; | |
| 220 uint32_t fInputColorSpace; | |
| 221 uint32_t fPCS; | |
| 222 uint32_t fDateTime_ignored[3]; | |
| 223 uint32_t fSignature; | |
| 224 | |
| 225 // Indicates the platform that this profile was created for (ex: Apple, Micr
osoft). This | |
| 226 // doesn't really matter to us. | |
| 227 uint32_t fPlatformTarget_ignored; | |
| 228 | |
| 229 // Flags can indicate: | |
| 230 // (1) Whether this profile was embedded in a file. This flag is consistent
ly wrong. | |
| 231 // Ex: The profile came from a file but indicates that it did not. | |
| 232 // (2) Whether we are allowed to use the profile independently of the color
data. If set, | |
| 233 // this may allow us to use the embedded profile for testing separate fr
om the original | |
| 234 // image. | |
| 235 uint32_t fFlags_ignored; | |
| 236 | |
| 237 // We support many output devices. It doesn't make sense to think about the
attributes of | |
| 238 // the device in the context of the image profile. | |
| 239 uint32_t fDeviceManufacturer_ignored; | |
| 240 uint32_t fDeviceModel_ignored; | |
| 241 uint32_t fDeviceAttributes_ignored[2]; | |
| 242 | |
| 243 uint32_t fRenderingIntent; | |
| 244 int32_t fIlluminantXYZ[3]; | |
| 245 | |
| 246 // We don't care who created the profile. | |
| 247 uint32_t fCreator_ignored; | |
| 248 | |
| 249 // This is an MD5 checksum. Could be useful for checking if profiles are eq
ual. | |
| 250 uint32_t fProfileId_ignored[4]; | |
| 251 | |
| 252 // Reserved for future use. | |
| 253 uint32_t fReserved_ignored[7]; | |
| 254 | |
| 255 uint32_t fTagCount; | |
| 256 | |
| 257 void init(const uint8_t* src, size_t len) { | |
| 258 SkASSERT(kICCHeaderSize == sizeof(*this)); | |
| 259 | |
| 260 uint32_t* dst = (uint32_t*) this; | |
| 261 for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) { | |
| 262 dst[i] = read_big_endian_uint(src); | |
| 263 } | |
| 264 } | |
| 265 | |
| 266 bool valid() const { | |
| 267 return_if_false(fSize >= kICCHeaderSize, "Size is too small"); | |
| 268 | |
| 269 uint8_t majorVersion = fVersion >> 24; | |
| 270 return_if_false(majorVersion <= 4, "Unsupported version"); | |
| 271 | |
| 272 // These are the three basic classes of profiles that we might expect to
see embedded | |
| 273 // in images. Four additional classes exist, but they generally are use
d as a convenient | |
| 274 // way for CMMs to store calculated transforms. | |
| 275 return_if_false(fProfileClass == kDisplay_Profile || | |
| 276 fProfileClass == kInput_Profile || | |
| 277 fProfileClass == kOutput_Profile, | |
| 278 "Unsupported profile"); | |
| 279 | |
| 280 // TODO (msarett): | |
| 281 // All the profiles we've tested so far use RGB as the input color space
. | |
| 282 return_if_false(fInputColorSpace == kRGB_ColorSpace, "Unsupported color
space"); | |
| 283 | |
| 284 // TODO (msarett): | |
| 285 // All the profiles we've tested so far use XYZ as the profile connectio
n space. | |
| 286 return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space"); | |
| 287 | |
| 288 return_if_false(fSignature == kACSP_Signature, "Bad signature"); | |
| 289 | |
| 290 // TODO (msarett): | |
| 291 // Should we treat different rendering intents differently? | |
| 292 // Valid rendering intents include kPerceptual (0), kRelative (1), | |
| 293 // kSaturation (2), and kAbsolute (3). | |
| 294 return_if_false(fRenderingIntent <= 3, "Bad rendering intent"); | |
| 295 | |
| 296 return_if_false(color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[0
]), 0.96420f) && | |
| 297 color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[1
]), 1.00000f) && | |
| 298 color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[2
]), 0.82491f), | |
| 299 "Illuminant must be D50"); | |
| 300 | |
| 301 return_if_false(fTagCount <= 100, "Too many tags"); | |
| 302 | |
| 303 return true; | |
| 304 } | |
| 305 }; | |
| 306 | |
| 307 template <class T> | |
| 308 static bool safe_add(T arg1, T arg2, size_t* result) { | |
| 309 SkASSERT(arg1 >= 0); | |
| 310 SkASSERT(arg2 >= 0); | |
| 311 if (arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) { | |
| 312 T sum = arg1 + arg2; | |
| 313 if (sum <= std::numeric_limits<size_t>::max()) { | |
| 314 *result = static_cast<size_t>(sum); | |
| 315 return true; | |
| 316 } | |
| 317 } | |
| 318 return false; | |
| 319 } | |
| 320 | |
| 321 static bool safe_mul(uint32_t arg1, uint32_t arg2, uint32_t* result) { | |
| 322 uint64_t product64 = (uint64_t) arg1 * (uint64_t) arg2; | |
| 323 uint32_t product32 = (uint32_t) product64; | |
| 324 if (product32 != product64) { | |
| 325 return false; | |
| 326 } | |
| 327 | |
| 328 *result = product32; | |
| 329 return true; | |
| 330 } | |
| 331 | |
| 332 struct ICCTag { | |
| 333 uint32_t fSignature; | |
| 334 uint32_t fOffset; | |
| 335 uint32_t fLength; | |
| 336 | |
| 337 const uint8_t* init(const uint8_t* src) { | |
| 338 fSignature = read_big_endian_uint(src); | |
| 339 fOffset = read_big_endian_uint(src + 4); | |
| 340 fLength = read_big_endian_uint(src + 8); | |
| 341 return src + 12; | |
| 342 } | |
| 343 | |
| 344 bool valid(size_t len) { | |
| 345 size_t tagEnd; | |
| 346 return_if_false(safe_add(fOffset, fLength, &tagEnd), | |
| 347 "Tag too large, overflows integer addition"); | |
| 348 return_if_false(tagEnd <= len, "Tag too large for ICC profile"); | |
| 349 return true; | |
| 350 } | |
| 351 | |
| 352 const uint8_t* addr(const uint8_t* src) const { | |
| 353 return src + fOffset; | |
| 354 } | |
| 355 | |
| 356 static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature
) { | |
| 357 for (int i = 0; i < count; ++i) { | |
| 358 if (tags[i].fSignature == signature) { | |
| 359 return &tags[i]; | |
| 360 } | |
| 361 } | |
| 362 return nullptr; | |
| 363 } | |
| 364 }; | |
| 365 | |
| 366 static constexpr uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z'); | |
| 367 static constexpr uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z'); | |
| 368 static constexpr uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z'); | |
| 369 static constexpr uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C'); | |
| 370 static constexpr uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C'); | |
| 371 static constexpr uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C'); | |
| 372 static constexpr uint32_t kTAG_A2B0 = SkSetFourByteTag('A', '2', 'B', '0'); | |
| 373 | |
| 374 static bool load_xyz(float dst[3], const uint8_t* src, size_t len) { | |
| 375 if (len < 20) { | |
| 376 SkColorSpacePrintf("XYZ tag is too small (%d bytes)", len); | |
| 377 return false; | |
| 378 } | |
| 379 | |
| 380 dst[0] = SkFixedToFloat(read_big_endian_int(src + 8)); | |
| 381 dst[1] = SkFixedToFloat(read_big_endian_int(src + 12)); | |
| 382 dst[2] = SkFixedToFloat(read_big_endian_int(src + 16)); | |
| 383 SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]); | |
| 384 return true; | |
| 385 } | |
| 386 | |
| 387 static constexpr uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', '
v'); | |
| 388 static constexpr uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', '
a'); | |
| 389 | |
| 390 static bool load_gammas(SkGammaCurve* gammas, uint32_t numGammas, const uint8_t*
src, size_t len) { | |
| 391 for (uint32_t i = 0; i < numGammas; i++) { | |
| 392 if (len < 12) { | |
| 393 // FIXME (msarett): | |
| 394 // We could potentially return false here after correctly parsing *s
ome* of the | |
| 395 // gammas correctly. Should we somehow try to indicate a partial su
ccess? | |
| 396 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); | |
| 397 return false; | |
| 398 } | |
| 399 | |
| 400 // We need to count the number of bytes in the tag, so we are able to mo
ve to the | |
| 401 // next tag on the next loop iteration. | |
| 402 size_t tagBytes; | |
| 403 | |
| 404 uint32_t type = read_big_endian_uint(src); | |
| 405 switch (type) { | |
| 406 case kTAG_CurveType: { | |
| 407 uint32_t count = read_big_endian_uint(src + 8); | |
| 408 | |
| 409 // tagBytes = 12 + 2 * count | |
| 410 // We need to do safe addition here to avoid integer overflow. | |
| 411 if (!safe_add(count, count, &tagBytes) || | |
| 412 !safe_add((size_t) 12, tagBytes, &tagBytes)) | |
| 413 { | |
| 414 SkColorSpacePrintf("Invalid gamma count"); | |
| 415 return false; | |
| 416 } | |
| 417 | |
| 418 if (0 == count) { | |
| 419 // Some tags require a gamma curve, but the author doesn't a
ctually want | |
| 420 // to transform the data. In this case, it is common to see
a curve with | |
| 421 // a count of 0. | |
| 422 gammas[i].fNamed = SkColorSpace::kLinear_GammaNamed; | |
| 423 break; | |
| 424 } else if (len < tagBytes) { | |
| 425 SkColorSpacePrintf("gamma tag is too small (%d bytes)", len)
; | |
| 426 return false; | |
| 427 } | |
| 428 | |
| 429 const uint16_t* table = (const uint16_t*) (src + 12); | |
| 430 if (1 == count) { | |
| 431 // The table entry is the gamma (with a bias of 256). | |
| 432 float value = (read_big_endian_short((const uint8_t*) table)
) / 256.0f; | |
| 433 set_gamma_value(&gammas[i], value); | |
| 434 SkColorSpacePrintf("gamma %g\n", value); | |
| 435 break; | |
| 436 } | |
| 437 | |
| 438 // Check for frequently occurring sRGB curves. | |
| 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 // an sRGB curve to 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 o
ver and | |
| 444 // over again, but relatively few variations. | |
| 445 if (1024 == count) { | |
| 446 // The magic values were chosen because they match a very co
mmon sRGB | |
| 447 // gamma table and the less common Canon sRGB gamma table (w
hich 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*) &tabl
e[257]) && | |
| 451 14116 == read_big_endian_short((const uint8_t*) &tab
le[513]) && | |
| 452 34318 == read_big_endian_short((const uint8_t*) &tab
le[768]) && | |
| 453 65535 == read_big_endian_short((const uint8_t*) &tab
le[1023])) { | |
| 454 gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed; | |
| 455 break; | |
| 456 } | |
| 457 } else if (26 == count) { | |
| 458 // The magic values were chosen because they match a very co
mmon 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*) &tabl
e[6]) && | |
| 462 12824 == read_big_endian_short((const uint8_t*) &tab
le[12]) && | |
| 463 31237 == read_big_endian_short((const uint8_t*) &tab
le[18]) && | |
| 464 65535 == read_big_endian_short((const uint8_t*) &tab
le[25])) { | |
| 465 gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed; | |
| 466 break; | |
| 467 } | |
| 468 } else if (4096 == count) { | |
| 469 // The magic values were chosen because they match Nikon, Ep
son, and | |
| 470 // LCMS sRGB gamma tables (all of which use different roundi
ng 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*) &tabl
e[1025]) && | |
| 474 14079 == read_big_endian_short((const uint8_t*) &tab
le[2051]) && | |
| 475 65535 == read_big_endian_short((const uint8_t*) &tab
le[4095])) { | |
| 476 gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed; | |
| 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 set_gamma_value(&gammas[i], 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 t
he 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 b
ytes)", 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 b
ytes)", 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 b
ytes)", 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 b
ytes)", 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" b
ut 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 represent
ation 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].fNamed = SkColorSpace::kSRGB_GammaNamed; | |
| 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 gam
ma 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 fu
nction 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].isNamed() || gammas[i].isValue() || gammas[i].isTable
() || | |
| 649 gammas[i].isParametric()); | |
| 650 | |
| 651 // Adjust src and len if there is another gamma curve to load. | |
| 652 if (i != numGammas - 1) { | |
| 653 // Each curve is padded to 4-byte alignment. | |
| 654 tagBytes = SkAlign4(tagBytes); | |
| 655 if (len < tagBytes) { | |
| 656 return false; | |
| 657 } | |
| 658 | |
| 659 src += tagBytes; | |
| 660 len -= tagBytes; | |
| 661 } | |
| 662 } | |
| 663 | |
| 664 return true; | |
| 665 } | |
| 666 | |
| 667 static constexpr uint32_t kTAG_AtoBType = SkSetFourByteTag('m', 'A', 'B', ' '); | |
| 668 | |
| 669 bool load_color_lut(SkColorLookUpTable* colorLUT, uint32_t inputChannels, uint32
_t outputChannels, | |
| 670 const uint8_t* src, size_t len) { | |
| 671 // 16 bytes reserved for grid points, 2 for precision, 2 for padding. | |
| 672 // The color LUT data follows after this header. | |
| 673 static constexpr uint32_t kColorLUTHeaderSize = 20; | |
| 674 if (len < kColorLUTHeaderSize) { | |
| 675 SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len); | |
| 676 return false; | |
| 677 } | |
| 678 size_t dataLen = len - kColorLUTHeaderSize; | |
| 679 | |
| 680 SkASSERT(3 == inputChannels && 3 == outputChannels); | |
| 681 colorLUT->fInputChannels = inputChannels; | |
| 682 colorLUT->fOutputChannels = outputChannels; | |
| 683 uint32_t numEntries = 1; | |
| 684 for (uint32_t i = 0; i < inputChannels; i++) { | |
| 685 colorLUT->fGridPoints[i] = src[i]; | |
| 686 if (0 == src[i]) { | |
| 687 SkColorSpacePrintf("Each input channel must have at least one grid p
oint."); | |
| 688 return false; | |
| 689 } | |
| 690 | |
| 691 if (!safe_mul(numEntries, src[i], &numEntries)) { | |
| 692 SkColorSpacePrintf("Too many entries in Color LUT."); | |
| 693 return false; | |
| 694 } | |
| 695 } | |
| 696 | |
| 697 if (!safe_mul(numEntries, outputChannels, &numEntries)) { | |
| 698 SkColorSpacePrintf("Too many entries in Color LUT."); | |
| 699 return false; | |
| 700 } | |
| 701 | |
| 702 // Space is provided for a maximum of the 16 input channels. Now we determi
ne the precision | |
| 703 // of the table values. | |
| 704 uint8_t precision = src[16]; | |
| 705 switch (precision) { | |
| 706 case 1: // 8-bit data | |
| 707 case 2: // 16-bit data | |
| 708 break; | |
| 709 default: | |
| 710 SkColorSpacePrintf("Color LUT precision must be 8-bit or 16-bit.\n")
; | |
| 711 return false; | |
| 712 } | |
| 713 | |
| 714 uint32_t clutBytes; | |
| 715 if (!safe_mul(numEntries, precision, &clutBytes)) { | |
| 716 SkColorSpacePrintf("Too many entries in Color LUT."); | |
| 717 return false; | |
| 718 } | |
| 719 | |
| 720 if (dataLen < clutBytes) { | |
| 721 SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len); | |
| 722 return false; | |
| 723 } | |
| 724 | |
| 725 // Movable struct colorLUT has ownership of fTable. | |
| 726 colorLUT->fTable = std::unique_ptr<float[]>(new float[numEntries]); | |
| 727 const uint8_t* ptr = src + kColorLUTHeaderSize; | |
| 728 for (uint32_t i = 0; i < numEntries; i++, ptr += precision) { | |
| 729 if (1 == precision) { | |
| 730 colorLUT->fTable[i] = ((float) ptr[i]) / 255.0f; | |
| 731 } else { | |
| 732 colorLUT->fTable[i] = ((float) read_big_endian_short(ptr)) / 65535.0
f; | |
| 733 } | |
| 734 } | |
| 735 | |
| 736 return true; | |
| 737 } | |
| 738 | |
| 739 bool load_matrix(SkMatrix44* toXYZ, const uint8_t* src, size_t len) { | |
| 740 if (len < 48) { | |
| 741 SkColorSpacePrintf("Matrix tag is too small (%d bytes).", len); | |
| 742 return false; | |
| 743 } | |
| 744 | |
| 745 // For this matrix to behave like our "to XYZ D50" matrices, it needs to be
scaled. | |
| 746 constexpr float scale = 65535.0 / 32768.0; | |
| 747 float array[16]; | |
| 748 array[ 0] = scale * SkFixedToFloat(read_big_endian_int(src)); | |
| 749 array[ 1] = scale * SkFixedToFloat(read_big_endian_int(src + 4)); | |
| 750 array[ 2] = scale * SkFixedToFloat(read_big_endian_int(src + 8)); | |
| 751 array[ 3] = scale * SkFixedToFloat(read_big_endian_int(src + 36)); // transl
ate R | |
| 752 array[ 4] = scale * SkFixedToFloat(read_big_endian_int(src + 12)); | |
| 753 array[ 5] = scale * SkFixedToFloat(read_big_endian_int(src + 16)); | |
| 754 array[ 6] = scale * SkFixedToFloat(read_big_endian_int(src + 20)); | |
| 755 array[ 7] = scale * SkFixedToFloat(read_big_endian_int(src + 40)); // transl
ate G | |
| 756 array[ 8] = scale * SkFixedToFloat(read_big_endian_int(src + 24)); | |
| 757 array[ 9] = scale * SkFixedToFloat(read_big_endian_int(src + 28)); | |
| 758 array[10] = scale * SkFixedToFloat(read_big_endian_int(src + 32)); | |
| 759 array[11] = scale * SkFixedToFloat(read_big_endian_int(src + 44)); // transl
ate B | |
| 760 array[12] = 0.0f; | |
| 761 array[13] = 0.0f; | |
| 762 array[14] = 0.0f; | |
| 763 array[15] = 1.0f; | |
| 764 toXYZ->setColMajorf(array); | |
| 765 return true; | |
| 766 } | |
| 767 | |
| 768 bool load_a2b0(SkColorLookUpTable* colorLUT, SkGammaCurve* gammas, SkMatrix44* t
oXYZ, | |
| 769 const uint8_t* src, size_t len) { | |
| 770 if (len < 32) { | |
| 771 SkColorSpacePrintf("A to B tag is too small (%d bytes).", len); | |
| 772 return false; | |
| 773 } | |
| 774 | |
| 775 uint32_t type = read_big_endian_uint(src); | |
| 776 if (kTAG_AtoBType != type) { | |
| 777 // FIXME (msarett): Need to support lut8Type and lut16Type. | |
| 778 SkColorSpacePrintf("Unsupported A to B tag type.\n"); | |
| 779 return false; | |
| 780 } | |
| 781 | |
| 782 // Read the number of channels. The four bytes that we skipped are reserved
and | |
| 783 // must be zero. | |
| 784 uint8_t inputChannels = src[8]; | |
| 785 uint8_t outputChannels = src[9]; | |
| 786 if (3 != inputChannels || 3 != outputChannels) { | |
| 787 // We only handle (supposedly) RGB inputs and RGB outputs. The numbers
of input | |
| 788 // channels and output channels both must be 3. | |
| 789 SkColorSpacePrintf("Input and output channels must equal 3 in A to B tag
.\n"); | |
| 790 return false; | |
| 791 } | |
| 792 | |
| 793 // Read the offsets of each element in the A to B tag. With the exception o
f A curves and | |
| 794 // B curves (which we do not yet support), we will handle these elements in
the order in | |
| 795 // which they should be applied (rather than the order in which they occur i
n the tag). | |
| 796 // If the offset is non-zero it indicates that the element is present. | |
| 797 uint32_t offsetToACurves = read_big_endian_int(src + 28); | |
| 798 uint32_t offsetToBCurves = read_big_endian_int(src + 12); | |
| 799 if ((0 != offsetToACurves) || (0 != offsetToBCurves)) { | |
| 800 // FIXME (msarett): Handle A and B curves. | |
| 801 // Note that the A curve is technically required in order to have a colo
r LUT. | |
| 802 // However, all the A curves I have seen so far have are just placeholde
rs that | |
| 803 // don't actually transform the data. | |
| 804 SkColorSpacePrintf("Ignoring A and/or B curve. Output may be wrong.\n")
; | |
| 805 } | |
| 806 | |
| 807 uint32_t offsetToColorLUT = read_big_endian_int(src + 24); | |
| 808 if (0 != offsetToColorLUT && offsetToColorLUT < len) { | |
| 809 if (!load_color_lut(colorLUT, inputChannels, outputChannels, src + offse
tToColorLUT, | |
| 810 len - offsetToColorLUT)) { | |
| 811 SkColorSpacePrintf("Failed to read color LUT from A to B tag.\n"); | |
| 812 } | |
| 813 } | |
| 814 | |
| 815 uint32_t offsetToMCurves = read_big_endian_int(src + 20); | |
| 816 if (0 != offsetToMCurves && offsetToMCurves < len) { | |
| 817 if (!load_gammas(gammas, outputChannels, src + offsetToMCurves, len - of
fsetToMCurves)) { | |
| 818 SkColorSpacePrintf("Failed to read M curves from A to B tag. Using
linear gamma.\n"); | |
| 819 gammas[0].fNamed = SkColorSpace::kLinear_GammaNamed; | |
| 820 gammas[1].fNamed = SkColorSpace::kLinear_GammaNamed; | |
| 821 gammas[2].fNamed = SkColorSpace::kLinear_GammaNamed; | |
| 822 } | |
| 823 } | |
| 824 | |
| 825 uint32_t offsetToMatrix = read_big_endian_int(src + 16); | |
| 826 if (0 != offsetToMatrix && offsetToMatrix < len) { | |
| 827 if (!load_matrix(toXYZ, src + offsetToMatrix, len - offsetToMatrix)) { | |
| 828 SkColorSpacePrintf("Failed to read matrix from A to B tag.\n"); | |
| 829 toXYZ->setIdentity(); | |
| 830 } | |
| 831 } | |
| 832 | |
| 833 return true; | |
| 834 } | |
| 835 | |
| 836 sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* input, size_t len) { | |
| 837 if (!input || len < kICCHeaderSize) { | |
| 838 return_null("Data is null or not large enough to contain an ICC profile"
); | |
| 839 } | |
| 840 | |
| 841 // Create our own copy of the input. | |
| 842 void* memory = sk_malloc_throw(len); | |
| 843 memcpy(memory, input, len); | |
| 844 sk_sp<SkData> data = SkData::MakeFromMalloc(memory, len); | |
| 845 const void* base = data->data(); | |
| 846 const uint8_t* ptr = (const uint8_t*) base; | |
| 847 | |
| 848 // Read the ICC profile header and check to make sure that it is valid. | |
| 849 ICCProfileHeader header; | |
| 850 header.init(ptr, len); | |
| 851 if (!header.valid()) { | |
| 852 return nullptr; | |
| 853 } | |
| 854 | |
| 855 // Adjust ptr and len before reading the tags. | |
| 856 if (len < header.fSize) { | |
| 857 SkColorSpacePrintf("ICC profile might be truncated.\n"); | |
| 858 } else if (len > header.fSize) { | |
| 859 SkColorSpacePrintf("Caller provided extra data beyond the end of the ICC
profile.\n"); | |
| 860 len = header.fSize; | |
| 861 } | |
| 862 ptr += kICCHeaderSize; | |
| 863 len -= kICCHeaderSize; | |
| 864 | |
| 865 // Parse tag headers. | |
| 866 uint32_t tagCount = header.fTagCount; | |
| 867 SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount); | |
| 868 if (len < kICCTagTableEntrySize * tagCount) { | |
| 869 return_null("Not enough input data to read tag table entries"); | |
| 870 } | |
| 871 | |
| 872 SkAutoTArray<ICCTag> tags(tagCount); | |
| 873 for (uint32_t i = 0; i < tagCount; i++) { | |
| 874 ptr = tags[i].init(ptr); | |
| 875 SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24
) & 0xFF, | |
| 876 (tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >> 8) &
0xFF, | |
| 877 (tags[i].fSignature >> 0) & 0xFF, tags[i].fOffset, tags[i].fLen
gth); | |
| 878 | |
| 879 if (!tags[i].valid(kICCHeaderSize + len)) { | |
| 880 return_null("Tag is too large to fit in ICC profile"); | |
| 881 } | |
| 882 } | |
| 883 | |
| 884 switch (header.fInputColorSpace) { | |
| 885 case kRGB_ColorSpace: { | |
| 886 // Recognize the rXYZ, gXYZ, and bXYZ tags. | |
| 887 const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ); | |
| 888 const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ); | |
| 889 const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ); | |
| 890 if (r && g && b) { | |
| 891 float toXYZ[9]; | |
| 892 if (!load_xyz(&toXYZ[0], r->addr((const uint8_t*) base), r->fLen
gth) || | |
| 893 !load_xyz(&toXYZ[3], g->addr((const uint8_t*) base), g->fLen
gth) || | |
| 894 !load_xyz(&toXYZ[6], b->addr((const uint8_t*) base), b->fLen
gth)) | |
| 895 { | |
| 896 return_null("Need valid rgb tags for XYZ space"); | |
| 897 } | |
| 898 SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor); | |
| 899 mat.set3x3RowMajorf(toXYZ); | |
| 900 | |
| 901 // It is not uncommon to see missing or empty gamma tags. This
indicates | |
| 902 // that we should use unit gamma. | |
| 903 SkGammaCurve curves[3]; | |
| 904 r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC); | |
| 905 g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC); | |
| 906 b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC); | |
| 907 if (!r || !load_gammas(&curves[0], 1, r->addr((const uint8_t*) b
ase), r->fLength)) | |
| 908 { | |
| 909 SkColorSpacePrintf("Failed to read R gamma tag.\n"); | |
| 910 curves[0].fNamed = SkColorSpace::kLinear_GammaNamed; | |
| 911 } | |
| 912 if (!g || !load_gammas(&curves[1], 1, g->addr((const uint8_t*) b
ase), g->fLength)) | |
| 913 { | |
| 914 SkColorSpacePrintf("Failed to read G gamma tag.\n"); | |
| 915 curves[1].fNamed = SkColorSpace::kLinear_GammaNamed; | |
| 916 } | |
| 917 if (!b || !load_gammas(&curves[2], 1, b->addr((const uint8_t*) b
ase), b->fLength)) | |
| 918 { | |
| 919 SkColorSpacePrintf("Failed to read B gamma tag.\n"); | |
| 920 curves[2].fNamed = SkColorSpace::kLinear_GammaNamed; | |
| 921 } | |
| 922 | |
| 923 GammaNamed gammaNamed = SkGammas::Named(curves); | |
| 924 if (kNonStandard_GammaNamed == gammaNamed) { | |
| 925 sk_sp<SkGammas> gammas = sk_make_sp<SkGammas>(std::move(curv
es[0]), | |
| 926 std::move(curv
es[1]), | |
| 927 std::move(curv
es[2])); | |
| 928 return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, st
d::move(gammas), | |
| 929 mat, std::m
ove(data))); | |
| 930 } else { | |
| 931 return SkColorSpace_Base::NewRGB(gammaNamed, mat); | |
| 932 } | |
| 933 } | |
| 934 | |
| 935 // Recognize color profile specified by A2B0 tag. | |
| 936 const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0); | |
| 937 if (a2b0) { | |
| 938 sk_sp<SkColorLookUpTable> colorLUT = sk_make_sp<SkColorLookUpTab
le>(); | |
| 939 SkGammaCurve curves[3]; | |
| 940 SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); | |
| 941 if (!load_a2b0(colorLUT.get(), curves, &toXYZ, a2b0->addr((const
uint8_t*) base), | |
| 942 a2b0->fLength)) { | |
| 943 return_null("Failed to parse A2B0 tag"); | |
| 944 } | |
| 945 | |
| 946 GammaNamed gammaNamed = SkGammas::Named(curves); | |
| 947 colorLUT = colorLUT->fTable ? colorLUT : nullptr; | |
| 948 if (colorLUT || kNonStandard_GammaNamed == gammaNamed) { | |
| 949 sk_sp<SkGammas> gammas = sk_make_sp<SkGammas>(std::move(curv
es[0]), | |
| 950 std::move(curv
es[1]), | |
| 951 std::move(curv
es[2])); | |
| 952 | |
| 953 return sk_sp<SkColorSpace>(new SkColorSpace_Base(std::move(c
olorLUT), | |
| 954 std::move(g
ammas), toXYZ, | |
| 955 std::move(d
ata))); | |
| 956 } else { | |
| 957 return SkColorSpace_Base::NewRGB(gammaNamed, toXYZ); | |
| 958 } | |
| 959 } | |
| 960 } | |
| 961 default: | |
| 962 break; | |
| 963 } | |
| 964 | |
| 965 return_null("ICC profile contains unsupported colorspace"); | |
| 966 } | |
| 967 | |
| 968 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | |
| 969 | |
| 970 // We will write a profile with the minimum nine required tags. | |
| 971 static constexpr uint32_t kICCNumEntries = 9; | |
| 972 | |
| 973 static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c'); | |
| 974 static constexpr uint32_t kTAG_desc_Bytes = 12; | |
| 975 static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize + kICCNumEntries*kIC
CTagTableEntrySize; | |
| 976 | |
| 977 static constexpr uint32_t kTAG_XYZ_Bytes = 20; | |
| 978 static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes; | |
| 979 static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes; | |
| 980 static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes; | |
| 981 | |
| 982 static constexpr uint32_t kTAG_TRC_Bytes = 14; | |
| 983 static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes; | |
| 984 static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset + SkAlign4(kTAG_TR
C_Bytes); | |
| 985 static constexpr uint32_t kTAG_bTRC_Offset = kTAG_gTRC_Offset + SkAlign4(kTAG_TR
C_Bytes); | |
| 986 | |
| 987 static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't'); | |
| 988 static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + SkAlign4(kTAG_TR
C_Bytes); | |
| 989 | |
| 990 static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't'); | |
| 991 static constexpr uint32_t kTAG_cprt_Bytes = 12; | |
| 992 static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes; | |
| 993 | |
| 994 static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes; | |
| 995 | |
| 996 static constexpr uint32_t gICCHeader[kICCHeaderSize / 4] { | |
| 997 SkEndian_SwapBE32(kICCProfileSize), // Size of the profile | |
| 998 0, // Preferred CMM type (ignored) | |
| 999 SkEndian_SwapBE32(0x02100000), // Version 2.1 | |
| 1000 SkEndian_SwapBE32(kDisplay_Profile), // Display device profile | |
| 1001 SkEndian_SwapBE32(kRGB_ColorSpace), // RGB input color space | |
| 1002 SkEndian_SwapBE32(kXYZ_PCSSpace), // XYZ profile connection space | |
| 1003 0, 0, 0, // Date and time (ignored) | |
| 1004 SkEndian_SwapBE32(kACSP_Signature), // Profile signature | |
| 1005 0, // Platform target (ignored) | |
| 1006 0x00000000, // Flags: not embedded, can be used ind
ependently | |
| 1007 0, // Device manufacturer (ignored) | |
| 1008 0, // Device model (ignored) | |
| 1009 0, 0, // Device attributes (ignored) | |
| 1010 SkEndian_SwapBE32(1), // Relative colorimetric rendering inte
nt | |
| 1011 SkEndian_SwapBE32(0x0000f6d6), // D50 standard illuminant (X) | |
| 1012 SkEndian_SwapBE32(0x00010000), // D50 standard illuminant (Y) | |
| 1013 SkEndian_SwapBE32(0x0000d32d), // D50 standard illuminant (Z) | |
| 1014 0, // Profile creator (ignored) | |
| 1015 0, 0, 0, 0, // Profile id checksum (ignored) | |
| 1016 0, 0, 0, 0, 0, 0, 0, // Reserved (ignored) | |
| 1017 SkEndian_SwapBE32(kICCNumEntries), // Number of tags | |
| 1018 }; | |
| 1019 | |
| 1020 static constexpr uint32_t gICCTagTable[3 * kICCNumEntries] { | |
| 1021 // Profile description | |
| 1022 SkEndian_SwapBE32(kTAG_desc), | |
| 1023 SkEndian_SwapBE32(kTAG_desc_Offset), | |
| 1024 SkEndian_SwapBE32(kTAG_desc_Bytes), | |
| 1025 | |
| 1026 // rXYZ | |
| 1027 SkEndian_SwapBE32(kTAG_rXYZ), | |
| 1028 SkEndian_SwapBE32(kTAG_rXYZ_Offset), | |
| 1029 SkEndian_SwapBE32(kTAG_XYZ_Bytes), | |
| 1030 | |
| 1031 // gXYZ | |
| 1032 SkEndian_SwapBE32(kTAG_gXYZ), | |
| 1033 SkEndian_SwapBE32(kTAG_gXYZ_Offset), | |
| 1034 SkEndian_SwapBE32(kTAG_XYZ_Bytes), | |
| 1035 | |
| 1036 // bXYZ | |
| 1037 SkEndian_SwapBE32(kTAG_bXYZ), | |
| 1038 SkEndian_SwapBE32(kTAG_bXYZ_Offset), | |
| 1039 SkEndian_SwapBE32(kTAG_XYZ_Bytes), | |
| 1040 | |
| 1041 // rTRC | |
| 1042 SkEndian_SwapBE32(kTAG_rTRC), | |
| 1043 SkEndian_SwapBE32(kTAG_rTRC_Offset), | |
| 1044 SkEndian_SwapBE32(kTAG_TRC_Bytes), | |
| 1045 | |
| 1046 // gTRC | |
| 1047 SkEndian_SwapBE32(kTAG_gTRC), | |
| 1048 SkEndian_SwapBE32(kTAG_gTRC_Offset), | |
| 1049 SkEndian_SwapBE32(kTAG_TRC_Bytes), | |
| 1050 | |
| 1051 // bTRC | |
| 1052 SkEndian_SwapBE32(kTAG_bTRC), | |
| 1053 SkEndian_SwapBE32(kTAG_bTRC_Offset), | |
| 1054 SkEndian_SwapBE32(kTAG_TRC_Bytes), | |
| 1055 | |
| 1056 // White point | |
| 1057 SkEndian_SwapBE32(kTAG_wtpt), | |
| 1058 SkEndian_SwapBE32(kTAG_wtpt_Offset), | |
| 1059 SkEndian_SwapBE32(kTAG_XYZ_Bytes), | |
| 1060 | |
| 1061 // Copyright | |
| 1062 SkEndian_SwapBE32(kTAG_cprt), | |
| 1063 SkEndian_SwapBE32(kTAG_cprt_Offset), | |
| 1064 SkEndian_SwapBE32(kTAG_cprt_Bytes), | |
| 1065 }; | |
| 1066 | |
| 1067 static constexpr uint32_t kTAG_TextType = SkSetFourByteTag('m', 'l', 'u', 'c'); | |
| 1068 static constexpr uint32_t gEmptyTextTag[3] { | |
| 1069 SkEndian_SwapBE32(kTAG_TextType), // Type signature | |
| 1070 0, // Reserved | |
| 1071 0, // Zero records | |
| 1072 }; | |
| 1073 | |
| 1074 static void write_xyz_tag(uint32_t* ptr, const SkMatrix44& toXYZ, int row) { | |
| 1075 ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace); | |
| 1076 ptr[1] = 0; | |
| 1077 ptr[2] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 0))); | |
| 1078 ptr[3] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 1))); | |
| 1079 ptr[4] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 2))); | |
| 1080 } | |
| 1081 | |
| 1082 static void write_trc_tag(uint32_t* ptr, float value) { | |
| 1083 ptr[0] = SkEndian_SwapBE32(kTAG_CurveType); | |
| 1084 ptr[1] = 0; | |
| 1085 | |
| 1086 // Gamma will be specified with a single value. | |
| 1087 ptr[2] = SkEndian_SwapBE32(1); | |
| 1088 | |
| 1089 // Convert gamma to 16-bit fixed point. | |
| 1090 uint16_t* ptr16 = (uint16_t*) (ptr + 3); | |
| 1091 ptr16[0] = SkEndian_SwapBE16((uint16_t) (value * 256.0f)); | |
| 1092 | |
| 1093 // Pad tag with zero. | |
| 1094 ptr16[1] = 0; | |
| 1095 } | |
| 1096 | |
| 1097 static float get_gamma_value(const SkGammaCurve* curve) { | |
| 1098 switch (curve->fNamed) { | |
| 1099 case SkColorSpace::kSRGB_GammaNamed: | |
| 1100 // FIXME (msarett): | |
| 1101 // kSRGB cannot be represented by a value. Here we fall through to
2.2f, | |
| 1102 // which is a close guess. To be more accurate, we need to represen
t sRGB | |
| 1103 // gamma with a parametric curve. | |
| 1104 case SkColorSpace::k2Dot2Curve_GammaNamed: | |
| 1105 return 2.2f; | |
| 1106 case SkColorSpace::kLinear_GammaNamed: | |
| 1107 return 1.0f; | |
| 1108 default: | |
| 1109 SkASSERT(curve->isValue()); | |
| 1110 return curve->fValue; | |
| 1111 } | |
| 1112 } | |
| 1113 | |
| 1114 sk_sp<SkData> SkColorSpace_Base::writeToICC() const { | |
| 1115 // Return if this object was created from a profile, or if we have already s
erialized | |
| 1116 // the profile. | |
| 1117 if (fProfileData) { | |
| 1118 return fProfileData; | |
| 1119 } | |
| 1120 | |
| 1121 // The client may create an SkColorSpace using an SkMatrix44, but currently
we only | |
| 1122 // support writing profiles with 3x3 matrices. | |
| 1123 // TODO (msarett): Fix this! | |
| 1124 if (0.0f != fToXYZD50.getFloat(3, 0) || 0.0f != fToXYZD50.getFloat(3, 1) || | |
| 1125 0.0f != fToXYZD50.getFloat(3, 2) || 0.0f != fToXYZD50.getFloat(0, 3) || | |
| 1126 0.0f != fToXYZD50.getFloat(1, 3) || 0.0f != fToXYZD50.getFloat(2, 3)) | |
| 1127 { | |
| 1128 return nullptr; | |
| 1129 } | |
| 1130 | |
| 1131 SkAutoMalloc profile(kICCProfileSize); | |
| 1132 uint8_t* ptr = (uint8_t*) profile.get(); | |
| 1133 | |
| 1134 // Write profile header | |
| 1135 memcpy(ptr, gICCHeader, sizeof(gICCHeader)); | |
| 1136 ptr += sizeof(gICCHeader); | |
| 1137 | |
| 1138 // Write tag table | |
| 1139 memcpy(ptr, gICCTagTable, sizeof(gICCTagTable)); | |
| 1140 ptr += sizeof(gICCTagTable); | |
| 1141 | |
| 1142 // Write profile description tag | |
| 1143 memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag)); | |
| 1144 ptr += sizeof(gEmptyTextTag); | |
| 1145 | |
| 1146 // Write XYZ tags | |
| 1147 write_xyz_tag((uint32_t*) ptr, fToXYZD50, 0); | |
| 1148 ptr += kTAG_XYZ_Bytes; | |
| 1149 write_xyz_tag((uint32_t*) ptr, fToXYZD50, 1); | |
| 1150 ptr += kTAG_XYZ_Bytes; | |
| 1151 write_xyz_tag((uint32_t*) ptr, fToXYZD50, 2); | |
| 1152 ptr += kTAG_XYZ_Bytes; | |
| 1153 | |
| 1154 // Write TRC tags | |
| 1155 GammaNamed gammaNamed = this->gammaNamed(); | |
| 1156 if (kNonStandard_GammaNamed == gammaNamed) { | |
| 1157 write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->f
Red)); | |
| 1158 ptr += SkAlign4(kTAG_TRC_Bytes); | |
| 1159 write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->f
Green)); | |
| 1160 ptr += SkAlign4(kTAG_TRC_Bytes); | |
| 1161 write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->f
Blue)); | |
| 1162 ptr += SkAlign4(kTAG_TRC_Bytes); | |
| 1163 } else { | |
| 1164 switch (gammaNamed) { | |
| 1165 case SkColorSpace::kSRGB_GammaNamed: | |
| 1166 // FIXME (msarett): | |
| 1167 // kSRGB cannot be represented by a value. Here we fall through
to 2.2f, | |
| 1168 // which is a close guess. To be more accurate, we need to repr
esent sRGB | |
| 1169 // gamma with a parametric curve. | |
| 1170 case SkColorSpace::k2Dot2Curve_GammaNamed: | |
| 1171 write_trc_tag((uint32_t*) ptr, 2.2f); | |
| 1172 ptr += SkAlign4(kTAG_TRC_Bytes); | |
| 1173 write_trc_tag((uint32_t*) ptr, 2.2f); | |
| 1174 ptr += SkAlign4(kTAG_TRC_Bytes); | |
| 1175 write_trc_tag((uint32_t*) ptr, 2.2f); | |
| 1176 ptr += SkAlign4(kTAG_TRC_Bytes); | |
| 1177 break; | |
| 1178 case SkColorSpace::kLinear_GammaNamed: | |
| 1179 write_trc_tag((uint32_t*) ptr, 1.0f); | |
| 1180 ptr += SkAlign4(kTAG_TRC_Bytes); | |
| 1181 write_trc_tag((uint32_t*) ptr, 1.0f); | |
| 1182 ptr += SkAlign4(kTAG_TRC_Bytes); | |
| 1183 write_trc_tag((uint32_t*) ptr, 1.0f); | |
| 1184 ptr += SkAlign4(kTAG_TRC_Bytes); | |
| 1185 break; | |
| 1186 default: | |
| 1187 SkASSERT(false); | |
| 1188 break; | |
| 1189 } | |
| 1190 } | |
| 1191 | |
| 1192 // Write white point tag | |
| 1193 uint32_t* ptr32 = (uint32_t*) ptr; | |
| 1194 ptr32[0] = SkEndian_SwapBE32(kXYZ_PCSSpace); | |
| 1195 ptr32[1] = 0; | |
| 1196 // TODO (msarett): These values correspond to the D65 white point. This may
not always be | |
| 1197 // correct. | |
| 1198 ptr32[2] = SkEndian_SwapBE32(0x0000f351); | |
| 1199 ptr32[3] = SkEndian_SwapBE32(0x00010000); | |
| 1200 ptr32[4] = SkEndian_SwapBE32(0x000116cc); | |
| 1201 ptr += kTAG_XYZ_Bytes; | |
| 1202 | |
| 1203 // Write copyright tag | |
| 1204 memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag)); | |
| 1205 | |
| 1206 // TODO (msarett): Should we try to hold onto the data so we can return imme
diately if | |
| 1207 // the client calls again? | |
| 1208 return SkData::MakeFromMalloc(profile.release(), kICCProfileSize); | |
| 1209 } | |
| 1210 | |
| 1211 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | |
| 1212 | |
| 1213 enum Version { | 144 enum Version { |
| 1214 k0_Version, // Initial version, header + flags for matrix and profile | 145 k0_Version, // Initial version, header + flags for matrix and profile |
| 1215 }; | 146 }; |
| 1216 | 147 |
| 1217 struct ColorSpaceHeader { | 148 struct ColorSpaceHeader { |
| 1218 /** | 149 /** |
| 1219 * If kMatrix_Flag is set, we will write 12 floats after the header. | 150 * If kMatrix_Flag is set, we will write 12 floats after the header. |
| 1220 * Should not be set at the same time as the kICC_Flag. | 151 * Should not be set at the same time as the kICC_Flag. |
| 1221 */ | 152 */ |
| 1222 static constexpr uint8_t kMatrix_Flag = 1 << 0; | 153 static constexpr uint8_t kMatrix_Flag = 1 << 0; |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1350 | 281 |
| 1351 uint32_t profileSize = *((uint32_t*) data); | 282 uint32_t profileSize = *((uint32_t*) data); |
| 1352 data = SkTAddOffset<const void>(data, sizeof(uint32_t)); | 283 data = SkTAddOffset<const void>(data, sizeof(uint32_t)); |
| 1353 length -= sizeof(uint32_t); | 284 length -= sizeof(uint32_t); |
| 1354 if (length < profileSize) { | 285 if (length < profileSize) { |
| 1355 return nullptr; | 286 return nullptr; |
| 1356 } | 287 } |
| 1357 | 288 |
| 1358 return NewICC(data, profileSize); | 289 return NewICC(data, profileSize); |
| 1359 } | 290 } |
| OLD | NEW |