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