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 | |
Hal Canary | 95e3c05 | 2017-01-11 12:44:43 -0500 | [diff] [blame] | 8 | #include "SkAutoMalloc.h" |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 9 | #include "SkColorSpace.h" |
Hal Canary | 95e3c05 | 2017-01-11 12:44:43 -0500 | [diff] [blame] | 10 | #include "SkColorSpacePriv.h" |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 11 | #include "SkColorSpace_A2B.h" |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 12 | #include "SkColorSpace_Base.h" |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 13 | #include "SkColorSpace_XYZ.h" |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 14 | #include "SkEndian.h" |
| 15 | #include "SkFixed.h" |
Matt Sarett | 8e04286 | 2016-12-19 09:43:30 -0500 | [diff] [blame] | 16 | #include "SkICCPriv.h" |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 17 | #include "SkTemplates.h" |
| 18 | |
| 19 | #define return_if_false(pred, msg) \ |
| 20 | do { \ |
| 21 | if (!(pred)) { \ |
| 22 | SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \ |
| 23 | return false; \ |
| 24 | } \ |
| 25 | } while (0) |
| 26 | |
| 27 | #define return_null(msg) \ |
| 28 | do { \ |
| 29 | SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \ |
| 30 | return nullptr; \ |
| 31 | } while (0) |
| 32 | |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 33 | static uint16_t read_big_endian_u16(const uint8_t* ptr) { |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 34 | return ptr[0] << 8 | ptr[1]; |
| 35 | } |
| 36 | |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 37 | static uint32_t read_big_endian_u32(const uint8_t* ptr) { |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 38 | return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; |
| 39 | } |
| 40 | |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 41 | static int32_t read_big_endian_i32(const uint8_t* ptr) { |
| 42 | return (int32_t) read_big_endian_u32(ptr); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 43 | } |
| 44 | |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 45 | static constexpr float kWhitePointD50[] = { 0.96420f, 1.00000f, 0.82491f, }; |
| 46 | |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 47 | struct ICCProfileHeader { |
| 48 | uint32_t fSize; |
| 49 | |
| 50 | // No reason to care about the preferred color management module (ex: Adobe, Apple, etc.). |
| 51 | // We're always going to use this one. |
| 52 | uint32_t fCMMType_ignored; |
| 53 | |
| 54 | uint32_t fVersion; |
| 55 | uint32_t fProfileClass; |
| 56 | uint32_t fInputColorSpace; |
| 57 | uint32_t fPCS; |
| 58 | uint32_t fDateTime_ignored[3]; |
| 59 | uint32_t fSignature; |
| 60 | |
| 61 | // Indicates the platform that this profile was created for (ex: Apple, Microsoft). This |
| 62 | // doesn't really matter to us. |
| 63 | uint32_t fPlatformTarget_ignored; |
| 64 | |
| 65 | // Flags can indicate: |
| 66 | // (1) Whether this profile was embedded in a file. This flag is consistently wrong. |
| 67 | // Ex: The profile came from a file but indicates that it did not. |
| 68 | // (2) Whether we are allowed to use the profile independently of the color data. If set, |
| 69 | // this may allow us to use the embedded profile for testing separate from the original |
| 70 | // image. |
| 71 | uint32_t fFlags_ignored; |
| 72 | |
| 73 | // We support many output devices. It doesn't make sense to think about the attributes of |
| 74 | // the device in the context of the image profile. |
| 75 | uint32_t fDeviceManufacturer_ignored; |
| 76 | uint32_t fDeviceModel_ignored; |
| 77 | uint32_t fDeviceAttributes_ignored[2]; |
| 78 | |
| 79 | uint32_t fRenderingIntent; |
| 80 | int32_t fIlluminantXYZ[3]; |
| 81 | |
| 82 | // We don't care who created the profile. |
| 83 | uint32_t fCreator_ignored; |
| 84 | |
| 85 | // This is an MD5 checksum. Could be useful for checking if profiles are equal. |
| 86 | uint32_t fProfileId_ignored[4]; |
| 87 | |
| 88 | // Reserved for future use. |
| 89 | uint32_t fReserved_ignored[7]; |
| 90 | |
| 91 | uint32_t fTagCount; |
| 92 | |
| 93 | void init(const uint8_t* src, size_t len) { |
| 94 | SkASSERT(kICCHeaderSize == sizeof(*this)); |
| 95 | |
| 96 | uint32_t* dst = (uint32_t*) this; |
| 97 | for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) { |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 98 | dst[i] = read_big_endian_u32(src); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 99 | } |
| 100 | } |
| 101 | |
| 102 | bool valid() const { |
| 103 | return_if_false(fSize >= kICCHeaderSize, "Size is too small"); |
| 104 | |
| 105 | uint8_t majorVersion = fVersion >> 24; |
| 106 | return_if_false(majorVersion <= 4, "Unsupported version"); |
| 107 | |
msarett | 18895db | 2016-07-15 10:47:19 -0700 | [diff] [blame] | 108 | // These are the four basic classes of profiles that we might expect to see embedded |
| 109 | // in images. Additional classes exist, but they generally are used as a convenient |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 110 | // way for CMMs to store calculated transforms. |
| 111 | return_if_false(fProfileClass == kDisplay_Profile || |
| 112 | fProfileClass == kInput_Profile || |
msarett | 18895db | 2016-07-15 10:47:19 -0700 | [diff] [blame] | 113 | fProfileClass == kOutput_Profile || |
| 114 | fProfileClass == kColorSpace_Profile, |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 115 | "Unsupported profile"); |
| 116 | |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 117 | switch (fInputColorSpace) { |
| 118 | case kRGB_ColorSpace: |
| 119 | SkColorSpacePrintf("RGB Input Color Space"); |
| 120 | break; |
| 121 | case kCMYK_ColorSpace: |
| 122 | SkColorSpacePrintf("CMYK Input Color Space\n"); |
| 123 | break; |
raftias | 91db12d | 2016-12-02 11:56:59 -0500 | [diff] [blame] | 124 | case kGray_ColorSpace: |
| 125 | SkColorSpacePrintf("Gray Input Color Space\n"); |
| 126 | break; |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 127 | default: |
| 128 | SkColorSpacePrintf("Unsupported Input Color Space: %c%c%c%c\n", |
| 129 | (fInputColorSpace>>24)&0xFF, (fInputColorSpace>>16)&0xFF, |
| 130 | (fInputColorSpace>> 8)&0xFF, (fInputColorSpace>> 0)&0xFF); |
| 131 | return false; |
| 132 | } |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 133 | |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 134 | switch (fPCS) { |
| 135 | case kXYZ_PCSSpace: |
| 136 | SkColorSpacePrintf("XYZ PCS\n"); |
| 137 | break; |
| 138 | case kLAB_PCSSpace: |
| 139 | SkColorSpacePrintf("Lab PCS\n"); |
| 140 | break; |
| 141 | default: |
| 142 | // ICC currently (V4.3) only specifices XYZ and Lab PCS spaces |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 143 | SkColorSpacePrintf("Unsupported PCS space: %c%c%c%c\n", |
| 144 | (fPCS>>24)&0xFF, (fPCS>>16)&0xFF, |
| 145 | (fPCS>> 8)&0xFF, (fPCS>> 0)&0xFF); |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 146 | return false; |
| 147 | } |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 148 | |
| 149 | return_if_false(fSignature == kACSP_Signature, "Bad signature"); |
| 150 | |
| 151 | // TODO (msarett): |
| 152 | // Should we treat different rendering intents differently? |
| 153 | // Valid rendering intents include kPerceptual (0), kRelative (1), |
| 154 | // kSaturation (2), and kAbsolute (3). |
msarett | 3bf7509 | 2016-08-24 06:22:41 -0700 | [diff] [blame] | 155 | if (fRenderingIntent > 3) { |
msarett | 18895db | 2016-07-15 10:47:19 -0700 | [diff] [blame] | 156 | // Warn rather than fail here. Occasionally, we see perfectly |
| 157 | // normal profiles with wacky rendering intents. |
| 158 | SkColorSpacePrintf("Warning, bad rendering intent.\n"); |
| 159 | } |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 160 | |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 161 | return_if_false( |
| 162 | color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[0]), kWhitePointD50[0]) && |
| 163 | color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[1]), kWhitePointD50[1]) && |
| 164 | color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[2]), kWhitePointD50[2]), |
| 165 | "Illuminant must be D50"); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 166 | |
| 167 | return_if_false(fTagCount <= 100, "Too many tags"); |
| 168 | |
| 169 | return true; |
| 170 | } |
| 171 | }; |
| 172 | |
| 173 | template <class T> |
| 174 | static bool safe_add(T arg1, T arg2, size_t* result) { |
| 175 | SkASSERT(arg1 >= 0); |
| 176 | SkASSERT(arg2 >= 0); |
| 177 | if (arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) { |
| 178 | T sum = arg1 + arg2; |
| 179 | if (sum <= std::numeric_limits<size_t>::max()) { |
| 180 | *result = static_cast<size_t>(sum); |
| 181 | return true; |
| 182 | } |
| 183 | } |
| 184 | return false; |
| 185 | } |
| 186 | |
| 187 | static bool safe_mul(uint32_t arg1, uint32_t arg2, uint32_t* result) { |
| 188 | uint64_t product64 = (uint64_t) arg1 * (uint64_t) arg2; |
| 189 | uint32_t product32 = (uint32_t) product64; |
| 190 | if (product32 != product64) { |
| 191 | return false; |
| 192 | } |
| 193 | |
| 194 | *result = product32; |
| 195 | return true; |
| 196 | } |
| 197 | |
| 198 | struct ICCTag { |
| 199 | uint32_t fSignature; |
| 200 | uint32_t fOffset; |
| 201 | uint32_t fLength; |
| 202 | |
| 203 | const uint8_t* init(const uint8_t* src) { |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 204 | fSignature = read_big_endian_u32(src); |
| 205 | fOffset = read_big_endian_u32(src + 4); |
| 206 | fLength = read_big_endian_u32(src + 8); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 207 | return src + 12; |
| 208 | } |
| 209 | |
| 210 | bool valid(size_t len) { |
| 211 | size_t tagEnd; |
| 212 | return_if_false(safe_add(fOffset, fLength, &tagEnd), |
| 213 | "Tag too large, overflows integer addition"); |
| 214 | return_if_false(tagEnd <= len, "Tag too large for ICC profile"); |
| 215 | return true; |
| 216 | } |
| 217 | |
| 218 | const uint8_t* addr(const uint8_t* src) const { |
| 219 | return src + fOffset; |
| 220 | } |
| 221 | |
| 222 | static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature) { |
| 223 | for (int i = 0; i < count; ++i) { |
| 224 | if (tags[i].fSignature == signature) { |
| 225 | return &tags[i]; |
| 226 | } |
| 227 | } |
| 228 | return nullptr; |
| 229 | } |
| 230 | }; |
| 231 | |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 232 | static bool load_xyz(float dst[3], const uint8_t* src, size_t len) { |
| 233 | if (len < 20) { |
| 234 | SkColorSpacePrintf("XYZ tag is too small (%d bytes)", len); |
| 235 | return false; |
| 236 | } |
| 237 | |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 238 | dst[0] = SkFixedToFloat(read_big_endian_i32(src + 8)); |
| 239 | dst[1] = SkFixedToFloat(read_big_endian_i32(src + 12)); |
| 240 | dst[2] = SkFixedToFloat(read_big_endian_i32(src + 16)); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 241 | SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]); |
| 242 | return true; |
| 243 | } |
| 244 | |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 245 | static SkGammas::Type set_gamma_value(SkGammas::Data* data, float value) { |
| 246 | if (color_space_almost_equal(2.2f, value)) { |
msarett | 600c737 | 2016-09-07 12:03:53 -0700 | [diff] [blame] | 247 | data->fNamed = k2Dot2Curve_SkGammaNamed; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 248 | return SkGammas::Type::kNamed_Type; |
msarett | 959ccc1 | 2016-07-20 15:10:02 -0700 | [diff] [blame] | 249 | } |
msarett | 456bf30 | 2016-07-20 16:14:16 -0700 | [diff] [blame] | 250 | |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 251 | if (color_space_almost_equal(1.0f, value)) { |
msarett | 600c737 | 2016-09-07 12:03:53 -0700 | [diff] [blame] | 252 | data->fNamed = kLinear_SkGammaNamed; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 253 | return SkGammas::Type::kNamed_Type; |
| 254 | } |
| 255 | |
| 256 | if (color_space_almost_equal(0.0f, value)) { |
| 257 | return SkGammas::Type::kNone_Type; |
| 258 | } |
| 259 | |
| 260 | data->fValue = value; |
| 261 | return SkGammas::Type::kValue_Type; |
| 262 | } |
| 263 | |
| 264 | static float read_big_endian_16_dot_16(const uint8_t buf[4]) { |
| 265 | // It just so happens that SkFixed is also 16.16! |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 266 | return SkFixedToFloat(read_big_endian_i32(buf)); |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 267 | } |
| 268 | |
| 269 | /** |
| 270 | * @param outData Set to the appropriate value on success. If we have table or |
| 271 | * parametric gamma, it is the responsibility of the caller to set |
| 272 | * fOffset. |
| 273 | * @param outParams If this is a parametric gamma, this is set to the appropriate |
| 274 | * parameters on success. |
| 275 | * @param outTagBytes Will be set to the length of the tag on success. |
| 276 | * @src Pointer to tag data. |
| 277 | * @len Length of tag data in bytes. |
| 278 | * |
| 279 | * @return kNone_Type on failure, otherwise the type of the gamma tag. |
| 280 | */ |
Matt Sarett | df44fc5 | 2016-10-11 16:57:50 -0400 | [diff] [blame] | 281 | static SkGammas::Type parse_gamma(SkGammas::Data* outData, SkColorSpaceTransferFn* outParams, |
| 282 | size_t* outTagBytes, const uint8_t* src, size_t len) { |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 283 | if (len < 12) { |
| 284 | SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); |
| 285 | return SkGammas::Type::kNone_Type; |
| 286 | } |
| 287 | |
| 288 | // In the case of consecutive gamma tags, we need to count the number of bytes in the |
| 289 | // tag, so that we can move on to the next tag. |
| 290 | size_t tagBytes; |
| 291 | |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 292 | uint32_t type = read_big_endian_u32(src); |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 293 | // Bytes 4-7 are reserved and should be set to zero. |
| 294 | switch (type) { |
| 295 | case kTAG_CurveType: { |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 296 | uint32_t count = read_big_endian_u32(src + 8); |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 297 | |
| 298 | // tagBytes = 12 + 2 * count |
| 299 | // We need to do safe addition here to avoid integer overflow. |
| 300 | if (!safe_add(count, count, &tagBytes) || |
| 301 | !safe_add((size_t) 12, tagBytes, &tagBytes)) |
| 302 | { |
| 303 | SkColorSpacePrintf("Invalid gamma count"); |
| 304 | return SkGammas::Type::kNone_Type; |
| 305 | } |
| 306 | |
| 307 | if (len < tagBytes) { |
| 308 | SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); |
| 309 | return SkGammas::Type::kNone_Type; |
| 310 | } |
| 311 | *outTagBytes = tagBytes; |
| 312 | |
| 313 | if (0 == count) { |
| 314 | // Some tags require a gamma curve, but the author doesn't actually want |
| 315 | // to transform the data. In this case, it is common to see a curve with |
| 316 | // a count of 0. |
msarett | 600c737 | 2016-09-07 12:03:53 -0700 | [diff] [blame] | 317 | outData->fNamed = kLinear_SkGammaNamed; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 318 | return SkGammas::Type::kNamed_Type; |
| 319 | } |
| 320 | |
| 321 | const uint16_t* table = (const uint16_t*) (src + 12); |
| 322 | if (1 == count) { |
| 323 | // The table entry is the gamma (with a bias of 256). |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 324 | float value = (read_big_endian_u16((const uint8_t*) table)) / 256.0f; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 325 | SkColorSpacePrintf("gamma %g\n", value); |
| 326 | |
| 327 | return set_gamma_value(outData, value); |
| 328 | } |
| 329 | |
Matt Sarett | e31d8a1 | 2017-05-17 15:21:59 -0400 | [diff] [blame] | 330 | // This optimization is especially important for A2B profiles, where we do |
| 331 | // not resize tables or interpolate lookups. |
| 332 | if (2 == count) { |
| 333 | if (0 == read_big_endian_u16((const uint8_t*) &table[0]) && |
| 334 | 65535 == read_big_endian_u16((const uint8_t*) &table[1])) { |
| 335 | outData->fNamed = kLinear_SkGammaNamed; |
| 336 | return SkGammas::Type::kNamed_Type; |
| 337 | } |
| 338 | } |
| 339 | |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 340 | // Check for frequently occurring sRGB curves. |
| 341 | // We do this by sampling a few values and see if they match our expectation. |
| 342 | // A more robust solution would be to compare each value in this curve against |
| 343 | // an sRGB curve to see if we remain below an error threshold. At this time, |
| 344 | // we haven't seen any images in the wild that make this kind of |
| 345 | // calculation necessary. We encounter identical gamma curves over and |
| 346 | // over again, but relatively few variations. |
| 347 | if (1024 == count) { |
| 348 | // The magic values were chosen because they match both the very common |
| 349 | // HP sRGB gamma table and the less common Canon sRGB gamma table (which use |
| 350 | // different rounding rules). |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 351 | if (0 == read_big_endian_u16((const uint8_t*) &table[0]) && |
| 352 | 3366 == read_big_endian_u16((const uint8_t*) &table[257]) && |
| 353 | 14116 == read_big_endian_u16((const uint8_t*) &table[513]) && |
| 354 | 34318 == read_big_endian_u16((const uint8_t*) &table[768]) && |
| 355 | 65535 == read_big_endian_u16((const uint8_t*) &table[1023])) { |
msarett | 600c737 | 2016-09-07 12:03:53 -0700 | [diff] [blame] | 356 | outData->fNamed = kSRGB_SkGammaNamed; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 357 | return SkGammas::Type::kNamed_Type; |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | if (26 == count) { |
Matt Sarett | b3d1c2e | 2016-10-27 12:44:18 -0400 | [diff] [blame] | 362 | // The magic values match a clever "minimum size" approach to representing sRGB. |
| 363 | // code.facebook.com/posts/411525055626587/under-the-hood-improving-facebook-photos |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 364 | if (0 == read_big_endian_u16((const uint8_t*) &table[0]) && |
| 365 | 3062 == read_big_endian_u16((const uint8_t*) &table[6]) && |
| 366 | 12824 == read_big_endian_u16((const uint8_t*) &table[12]) && |
| 367 | 31237 == read_big_endian_u16((const uint8_t*) &table[18]) && |
| 368 | 65535 == read_big_endian_u16((const uint8_t*) &table[25])) { |
msarett | 600c737 | 2016-09-07 12:03:53 -0700 | [diff] [blame] | 369 | outData->fNamed = kSRGB_SkGammaNamed; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 370 | return SkGammas::Type::kNamed_Type; |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | if (4096 == count) { |
| 375 | // The magic values were chosen because they match Nikon, Epson, and |
Matt Sarett | b3d1c2e | 2016-10-27 12:44:18 -0400 | [diff] [blame] | 376 | // lcms2 sRGB gamma tables (all of which use different rounding rules). |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 377 | if (0 == read_big_endian_u16((const uint8_t*) &table[0]) && |
| 378 | 950 == read_big_endian_u16((const uint8_t*) &table[515]) && |
| 379 | 3342 == read_big_endian_u16((const uint8_t*) &table[1025]) && |
| 380 | 14079 == read_big_endian_u16((const uint8_t*) &table[2051]) && |
| 381 | 65535 == read_big_endian_u16((const uint8_t*) &table[4095])) { |
msarett | 600c737 | 2016-09-07 12:03:53 -0700 | [diff] [blame] | 382 | outData->fNamed = kSRGB_SkGammaNamed; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 383 | return SkGammas::Type::kNamed_Type; |
| 384 | } |
| 385 | } |
| 386 | |
msarett | e54f4b7 | 2016-09-06 14:41:42 -0700 | [diff] [blame] | 387 | // Otherwise, we will represent gamma with a table. |
| 388 | outData->fTable.fSize = count; |
| 389 | return SkGammas::Type::kTable_Type; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 390 | } |
| 391 | case kTAG_ParaCurveType: { |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 392 | // Determine the format of the parametric curve tag. |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 393 | uint16_t format = read_big_endian_u16(src + 8); |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 394 | if (format > kGABCDEF_ParaCurveType) { |
| 395 | SkColorSpacePrintf("Unsupported gamma tag type %d\n", type); |
| 396 | return SkGammas::Type::kNone_Type; |
| 397 | } |
| 398 | |
| 399 | if (kExponential_ParaCurveType == format) { |
| 400 | tagBytes = 12 + 4; |
| 401 | if (len < tagBytes) { |
| 402 | SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); |
| 403 | return SkGammas::Type::kNone_Type; |
| 404 | } |
| 405 | |
| 406 | // Y = X^g |
| 407 | float g = read_big_endian_16_dot_16(src + 12); |
| 408 | |
| 409 | *outTagBytes = tagBytes; |
| 410 | return set_gamma_value(outData, g); |
| 411 | } |
| 412 | |
| 413 | // Here's where the real parametric gammas start. There are many |
| 414 | // permutations of the same equations. |
| 415 | // |
Matt Sarett | 2410717 | 2016-12-19 14:33:35 -0500 | [diff] [blame] | 416 | // Y = (aX + b)^g + e for X >= d |
| 417 | // Y = cX + f otherwise |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 418 | // |
| 419 | // We will fill in with zeros as necessary to always match the above form. |
| 420 | if (len < 24) { |
| 421 | SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); |
| 422 | return SkGammas::Type::kNone_Type; |
| 423 | } |
| 424 | float g = read_big_endian_16_dot_16(src + 12); |
| 425 | float a = read_big_endian_16_dot_16(src + 16); |
| 426 | float b = read_big_endian_16_dot_16(src + 20); |
| 427 | float c = 0.0f, d = 0.0f, e = 0.0f, f = 0.0f; |
| 428 | switch(format) { |
| 429 | case kGAB_ParaCurveType: |
| 430 | tagBytes = 12 + 12; |
| 431 | |
| 432 | // Y = (aX + b)^g for X >= -b/a |
| 433 | // Y = 0 otherwise |
| 434 | d = -b / a; |
| 435 | break; |
| 436 | case kGABC_ParaCurveType: |
| 437 | tagBytes = 12 + 16; |
| 438 | if (len < tagBytes) { |
| 439 | SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); |
| 440 | return SkGammas::Type::kNone_Type; |
| 441 | } |
| 442 | |
Matt Sarett | 2410717 | 2016-12-19 14:33:35 -0500 | [diff] [blame] | 443 | // Y = (aX + b)^g + e for X >= -b/a |
| 444 | // Y = e otherwise |
| 445 | e = read_big_endian_16_dot_16(src + 24); |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 446 | d = -b / a; |
Matt Sarett | 2410717 | 2016-12-19 14:33:35 -0500 | [diff] [blame] | 447 | f = e; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 448 | break; |
| 449 | case kGABDE_ParaCurveType: |
| 450 | tagBytes = 12 + 20; |
| 451 | if (len < tagBytes) { |
| 452 | SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); |
| 453 | return SkGammas::Type::kNone_Type; |
| 454 | } |
| 455 | |
| 456 | // Y = (aX + b)^g for X >= d |
Matt Sarett | 2410717 | 2016-12-19 14:33:35 -0500 | [diff] [blame] | 457 | // Y = cX otherwise |
| 458 | c = read_big_endian_16_dot_16(src + 24); |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 459 | d = read_big_endian_16_dot_16(src + 28); |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 460 | break; |
| 461 | case kGABCDEF_ParaCurveType: |
| 462 | tagBytes = 12 + 28; |
| 463 | if (len < tagBytes) { |
| 464 | SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); |
| 465 | return SkGammas::Type::kNone_Type; |
| 466 | } |
| 467 | |
Matt Sarett | 2410717 | 2016-12-19 14:33:35 -0500 | [diff] [blame] | 468 | // Y = (aX + b)^g + e for X >= d |
| 469 | // Y = cX + f otherwise |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 470 | c = read_big_endian_16_dot_16(src + 24); |
| 471 | d = read_big_endian_16_dot_16(src + 28); |
| 472 | e = read_big_endian_16_dot_16(src + 32); |
| 473 | f = read_big_endian_16_dot_16(src + 36); |
| 474 | break; |
| 475 | default: |
| 476 | SkASSERT(false); |
| 477 | return SkGammas::Type::kNone_Type; |
| 478 | } |
| 479 | |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 480 | outParams->fG = g; |
| 481 | outParams->fA = a; |
| 482 | outParams->fB = b; |
| 483 | outParams->fC = c; |
| 484 | outParams->fD = d; |
| 485 | outParams->fE = e; |
| 486 | outParams->fF = f; |
Matt Sarett | df44fc5 | 2016-10-11 16:57:50 -0400 | [diff] [blame] | 487 | |
| 488 | if (!is_valid_transfer_fn(*outParams)) { |
| 489 | return SkGammas::Type::kNone_Type; |
| 490 | } |
| 491 | |
| 492 | if (is_almost_srgb(*outParams)) { |
| 493 | outData->fNamed = kSRGB_SkGammaNamed; |
| 494 | return SkGammas::Type::kNamed_Type; |
| 495 | } |
| 496 | |
| 497 | if (is_almost_2dot2(*outParams)) { |
| 498 | outData->fNamed = k2Dot2Curve_SkGammaNamed; |
| 499 | return SkGammas::Type::kNamed_Type; |
| 500 | } |
| 501 | |
| 502 | *outTagBytes = tagBytes; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 503 | return SkGammas::Type::kParam_Type; |
| 504 | } |
| 505 | default: |
| 506 | SkColorSpacePrintf("Unsupported gamma tag type %d\n", type); |
| 507 | return SkGammas::Type::kNone_Type; |
| 508 | } |
| 509 | } |
| 510 | |
| 511 | /** |
| 512 | * Returns the additional size in bytes needed to store the gamma tag. |
| 513 | */ |
| 514 | static size_t gamma_alloc_size(SkGammas::Type type, const SkGammas::Data& data) { |
| 515 | switch (type) { |
| 516 | case SkGammas::Type::kNamed_Type: |
| 517 | case SkGammas::Type::kValue_Type: |
| 518 | return 0; |
| 519 | case SkGammas::Type::kTable_Type: |
| 520 | return sizeof(float) * data.fTable.fSize; |
| 521 | case SkGammas::Type::kParam_Type: |
Matt Sarett | df44fc5 | 2016-10-11 16:57:50 -0400 | [diff] [blame] | 522 | return sizeof(SkColorSpaceTransferFn); |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 523 | default: |
| 524 | SkASSERT(false); |
| 525 | return 0; |
| 526 | } |
| 527 | } |
| 528 | |
| 529 | /** |
| 530 | * Sets invalid gamma to the default value. |
| 531 | */ |
| 532 | static void handle_invalid_gamma(SkGammas::Type* type, SkGammas::Data* data) { |
| 533 | if (SkGammas::Type::kNone_Type == *type) { |
| 534 | *type = SkGammas::Type::kNamed_Type; |
msarett | 712bb1e | 2016-09-05 10:33:12 -0700 | [diff] [blame] | 535 | |
| 536 | // Guess sRGB in the case of a malformed transfer function. |
msarett | 600c737 | 2016-09-07 12:03:53 -0700 | [diff] [blame] | 537 | data->fNamed = kSRGB_SkGammaNamed; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 538 | } |
| 539 | } |
| 540 | |
| 541 | /** |
| 542 | * Finish loading the gammas, now that we have allocated memory for the SkGammas struct. |
| 543 | * |
| 544 | * There's nothing to do for the simple cases, but for table gammas we need to actually |
| 545 | * read the table into heap memory. And for parametric gammas, we need to copy over the |
| 546 | * parameter values. |
| 547 | * |
| 548 | * @param memory Pointer to start of the SkGammas memory block |
| 549 | * @param offset Bytes of memory (after the SkGammas struct) that are already in use. |
| 550 | * @param data In-out variable. Will fill in the offset to the table or parameters |
| 551 | * if necessary. |
| 552 | * @param params Parameters for gamma curve. Only initialized/used when we have a |
| 553 | * parametric gamma. |
| 554 | * @param src Pointer to start of the gamma tag. |
| 555 | * |
| 556 | * @return Additional bytes of memory that are being used by this gamma curve. |
| 557 | */ |
| 558 | static size_t load_gammas(void* memory, size_t offset, SkGammas::Type type, |
Matt Sarett | df44fc5 | 2016-10-11 16:57:50 -0400 | [diff] [blame] | 559 | SkGammas::Data* data, const SkColorSpaceTransferFn& params, |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 560 | const uint8_t* src) { |
| 561 | void* storage = SkTAddOffset<void>(memory, offset + sizeof(SkGammas)); |
| 562 | |
| 563 | switch (type) { |
| 564 | case SkGammas::Type::kNamed_Type: |
| 565 | case SkGammas::Type::kValue_Type: |
| 566 | // Nothing to do here. |
| 567 | return 0; |
| 568 | case SkGammas::Type::kTable_Type: { |
| 569 | data->fTable.fOffset = offset; |
| 570 | |
| 571 | float* outTable = (float*) storage; |
| 572 | const uint16_t* inTable = (const uint16_t*) (src + 12); |
| 573 | for (int i = 0; i < data->fTable.fSize; i++) { |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 574 | outTable[i] = (read_big_endian_u16((const uint8_t*) &inTable[i])) / 65535.0f; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 575 | } |
| 576 | |
| 577 | return sizeof(float) * data->fTable.fSize; |
| 578 | } |
| 579 | case SkGammas::Type::kParam_Type: |
| 580 | data->fTable.fOffset = offset; |
Matt Sarett | df44fc5 | 2016-10-11 16:57:50 -0400 | [diff] [blame] | 581 | memcpy(storage, ¶ms, sizeof(SkColorSpaceTransferFn)); |
| 582 | return sizeof(SkColorSpaceTransferFn); |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 583 | default: |
| 584 | SkASSERT(false); |
| 585 | return 0; |
| 586 | } |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 587 | } |
| 588 | |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 589 | static constexpr uint32_t kTAG_AtoBType = SkSetFourByteTag('m', 'A', 'B', ' '); |
| 590 | static constexpr uint32_t kTAG_lut8Type = SkSetFourByteTag('m', 'f', 't', '1'); |
| 591 | static constexpr uint32_t kTAG_lut16Type = SkSetFourByteTag('m', 'f', 't', '2'); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 592 | |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 593 | static bool load_color_lut(sk_sp<SkColorLookUpTable>* colorLUT, uint32_t inputChannels, |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 594 | size_t precision, const uint8_t gridPoints[3], const uint8_t* src, |
| 595 | size_t len) { |
| 596 | switch (precision) { |
| 597 | case 1: // 8-bit data |
| 598 | case 2: // 16-bit data |
| 599 | break; |
| 600 | default: |
| 601 | SkColorSpacePrintf("Color LUT precision must be 8-bit or 16-bit. Found: %d-bit\n", |
| 602 | 8*precision); |
| 603 | return false; |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 604 | } |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 605 | |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 606 | uint32_t numEntries = SkColorLookUpTable::kOutputChannels; |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 607 | for (uint32_t i = 0; i < inputChannels; i++) { |
Matt Sarett | 9d687df | 2017-05-05 10:38:01 -0400 | [diff] [blame] | 608 | if (1 >= gridPoints[i]) { |
| 609 | SkColorSpacePrintf("Each input channel must have at least two grid points."); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 610 | return false; |
| 611 | } |
| 612 | |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 613 | if (!safe_mul(numEntries, gridPoints[i], &numEntries)) { |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 614 | SkColorSpacePrintf("Too many entries in Color LUT."); |
| 615 | return false; |
| 616 | } |
| 617 | } |
| 618 | |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 619 | uint32_t clutBytes; |
| 620 | if (!safe_mul(numEntries, precision, &clutBytes)) { |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 621 | SkColorSpacePrintf("Too many entries in Color LUT.\n"); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 622 | return false; |
| 623 | } |
| 624 | |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 625 | if (len < clutBytes) { |
| 626 | SkColorSpacePrintf("Color LUT tag is too small (%d / %d bytes).\n", len, clutBytes); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 627 | return false; |
| 628 | } |
| 629 | |
| 630 | // Movable struct colorLUT has ownership of fTable. |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 631 | void* memory = sk_malloc_throw(sizeof(SkColorLookUpTable) + sizeof(float) * numEntries); |
| 632 | *colorLUT = sk_sp<SkColorLookUpTable>(new (memory) SkColorLookUpTable(inputChannels, |
| 633 | gridPoints)); |
| 634 | |
| 635 | float* table = SkTAddOffset<float>(memory, sizeof(SkColorLookUpTable)); |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 636 | const uint8_t* ptr = src; |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 637 | for (uint32_t i = 0; i < numEntries; i++, ptr += precision) { |
| 638 | if (1 == precision) { |
raftias | 80739b8 | 2016-10-20 10:38:58 -0700 | [diff] [blame] | 639 | table[i] = ((float) *ptr) / 255.0f; |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 640 | } else { |
msarett | 959d45b | 2016-07-21 13:19:03 -0700 | [diff] [blame] | 641 | table[i] = ((float) read_big_endian_u16(ptr)) / 65535.0f; |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 642 | } |
| 643 | } |
| 644 | |
| 645 | return true; |
| 646 | } |
| 647 | |
raftias | 14d2899 | 2016-11-21 12:59:36 -0500 | [diff] [blame] | 648 | /** |
| 649 | * Reads a matrix out of an A2B tag of an ICC profile. |
| 650 | * If |translate| is true, it will load a 3x4 matrix out that corresponds to a XYZ |
| 651 | * transform as well as a translation, and if |translate| is false it only loads a |
| 652 | * 3x3 matrix with no translation |
| 653 | * |
| 654 | * @param matrix The matrix to store the result in |
| 655 | * @param src Data to load the matrix out of. |
| 656 | * @param len The length of |src|. |
| 657 | * Must have 48 bytes if |translate| is set and 36 bytes otherwise. |
| 658 | * @param translate Whether to read the translation column or not |
| 659 | * @param pcs The profile connection space of the profile this matrix is for |
| 660 | * |
| 661 | * @return false on failure, true on success |
| 662 | */ |
| 663 | static bool load_matrix(SkMatrix44* matrix, const uint8_t* src, size_t len, bool translate, |
| 664 | SkColorSpace_A2B::PCS pcs) { |
| 665 | const size_t minLen = translate ? 48 : 36; |
| 666 | if (len < minLen) { |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 667 | SkColorSpacePrintf("Matrix tag is too small (%d bytes).", len); |
| 668 | return false; |
| 669 | } |
| 670 | |
raftias | 14d2899 | 2016-11-21 12:59:36 -0500 | [diff] [blame] | 671 | float encodingFactor; |
| 672 | switch (pcs) { |
| 673 | case SkColorSpace_A2B::PCS::kLAB: |
| 674 | encodingFactor = 1.f; |
| 675 | break; |
| 676 | case SkColorSpace_A2B::PCS::kXYZ: |
| 677 | encodingFactor = 65535 / 32768.f; |
| 678 | break; |
| 679 | default: |
| 680 | encodingFactor = 1.f; |
| 681 | SkASSERT(false); |
| 682 | break; |
| 683 | } |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 684 | float array[16]; |
raftias | 14d2899 | 2016-11-21 12:59:36 -0500 | [diff] [blame] | 685 | array[ 0] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src)); |
| 686 | array[ 1] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 4)); |
| 687 | array[ 2] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 8)); |
| 688 | |
| 689 | array[ 4] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 12)); |
| 690 | array[ 5] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 16)); |
| 691 | array[ 6] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 20)); |
| 692 | |
| 693 | array[ 8] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 24)); |
| 694 | array[ 9] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 28)); |
| 695 | array[10] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 32)); |
| 696 | |
| 697 | if (translate) { |
| 698 | array[ 3] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 36)); // translate R |
| 699 | array[ 7] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 40)); // translate G |
| 700 | array[11] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 44)); // translate B |
| 701 | } else { |
| 702 | array[ 3] = 0.0f; |
| 703 | array[ 7] = 0.0f; |
| 704 | array[11] = 0.0f; |
| 705 | } |
| 706 | |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 707 | array[12] = 0.0f; |
| 708 | array[13] = 0.0f; |
| 709 | array[14] = 0.0f; |
| 710 | array[15] = 1.0f; |
raftias | 14d2899 | 2016-11-21 12:59:36 -0500 | [diff] [blame] | 711 | matrix->setRowMajorf(array); |
| 712 | SkColorSpacePrintf("A2B0 matrix loaded:\n"); |
| 713 | for (int r = 0; r < 4; ++r) { |
| 714 | SkColorSpacePrintf("|"); |
| 715 | for (int c = 0; c < 4; ++c) { |
| 716 | SkColorSpacePrintf(" %f ", matrix->get(r, c)); |
| 717 | } |
| 718 | SkColorSpacePrintf("|\n"); |
| 719 | } |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 720 | return true; |
| 721 | } |
| 722 | |
msarett | 600c737 | 2016-09-07 12:03:53 -0700 | [diff] [blame] | 723 | static inline SkGammaNamed is_named(const sk_sp<SkGammas>& gammas) { |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 724 | for (uint8_t i = 0; i < gammas->channels(); ++i) { |
| 725 | if (!gammas->isNamed(i) || gammas->data(i).fNamed != gammas->data(0).fNamed) { |
| 726 | return kNonStandard_SkGammaNamed; |
| 727 | } |
msarett | 0f0c4f0 | 2016-08-22 09:44:35 -0700 | [diff] [blame] | 728 | } |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 729 | return gammas->data(0).fNamed; |
msarett | 0f0c4f0 | 2016-08-22 09:44:35 -0700 | [diff] [blame] | 730 | } |
| 731 | |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 732 | /** |
| 733 | * Parse and load an entire stored curve. Handles invalid gammas as well. |
| 734 | * |
| 735 | * There's nothing to do for the simple cases, but for table gammas we need to actually |
| 736 | * read the table into heap memory. And for parametric gammas, we need to copy over the |
| 737 | * parameter values. |
| 738 | * |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 739 | * @param gammaNamed Out-variable. The named gamma curve. |
| 740 | * @param gammas Out-variable. The stored gamma curve information. Can be null if |
| 741 | * gammaNamed is a named curve |
| 742 | * @param inputChannels The number of gamma input channels |
| 743 | * @param rTagPtr Pointer to start of the gamma tag. |
| 744 | * @param taglen The size in bytes of the tag |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 745 | * |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 746 | * @return false on failure, true on success |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 747 | */ |
| 748 | static bool parse_and_load_gamma(SkGammaNamed* gammaNamed, sk_sp<SkGammas>* gammas, |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 749 | uint8_t inputChannels, const uint8_t* tagSrc, size_t tagLen) { |
| 750 | SkGammas::Data data[kMaxColorChannels]; |
| 751 | SkColorSpaceTransferFn params[kMaxColorChannels]; |
| 752 | SkGammas::Type type[kMaxColorChannels]; |
| 753 | const uint8_t* tagPtr[kMaxColorChannels]; |
| 754 | |
| 755 | tagPtr[0] = tagSrc; |
msarett | 0f0c4f0 | 2016-08-22 09:44:35 -0700 | [diff] [blame] | 756 | |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 757 | *gammaNamed = kNonStandard_SkGammaNamed; |
| 758 | |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 759 | // On an invalid first gamma, tagBytes remains set as zero. This causes the two |
| 760 | // subsequent to be treated as identical (which is what we want). |
| 761 | size_t tagBytes = 0; |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 762 | type[0] = parse_gamma(&data[0], ¶ms[0], &tagBytes, tagPtr[0], tagLen); |
| 763 | handle_invalid_gamma(&type[0], &data[0]); |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 764 | size_t alignedTagBytes = SkAlign4(tagBytes); |
| 765 | |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 766 | bool allChannelsSame = false; |
| 767 | if (inputChannels * alignedTagBytes <= tagLen) { |
| 768 | allChannelsSame = true; |
| 769 | for (uint8_t i = 1; i < inputChannels; ++i) { |
| 770 | if (0 != memcmp(tagSrc, tagSrc + i * alignedTagBytes, tagBytes)) { |
| 771 | allChannelsSame = false; |
| 772 | break; |
| 773 | } |
| 774 | } |
| 775 | } |
| 776 | if (allChannelsSame) { |
| 777 | if (SkGammas::Type::kNamed_Type == type[0]) { |
| 778 | *gammaNamed = data[0].fNamed; |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 779 | } else { |
| 780 | size_t allocSize = sizeof(SkGammas); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 781 | return_if_false(safe_add(allocSize, gamma_alloc_size(type[0], data[0]), &allocSize), |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 782 | "SkGammas struct is too large to allocate"); |
| 783 | void* memory = sk_malloc_throw(allocSize); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 784 | *gammas = sk_sp<SkGammas>(new (memory) SkGammas(inputChannels)); |
| 785 | load_gammas(memory, 0, type[0], &data[0], params[0], tagPtr[0]); |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 786 | |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 787 | for (uint8_t channel = 0; channel < inputChannels; ++channel) { |
| 788 | (*gammas)->fType[channel] = type[0]; |
| 789 | (*gammas)->fData[channel] = data[0]; |
| 790 | } |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 791 | } |
| 792 | } else { |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 793 | for (uint8_t channel = 1; channel < inputChannels; ++channel) { |
| 794 | tagPtr[channel] = tagPtr[channel - 1] + alignedTagBytes; |
| 795 | tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0; |
| 796 | tagBytes = 0; |
| 797 | type[channel] = parse_gamma(&data[channel], ¶ms[channel], &tagBytes, |
| 798 | tagPtr[channel], tagLen); |
| 799 | handle_invalid_gamma(&type[channel], &data[channel]); |
| 800 | alignedTagBytes = SkAlign4(tagBytes); |
| 801 | } |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 802 | |
| 803 | size_t allocSize = sizeof(SkGammas); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 804 | for (uint8_t channel = 0; channel < inputChannels; ++channel) { |
| 805 | return_if_false(safe_add(allocSize, gamma_alloc_size(type[channel], data[channel]), |
| 806 | &allocSize), |
| 807 | "SkGammas struct is too large to allocate"); |
| 808 | } |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 809 | void* memory = sk_malloc_throw(allocSize); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 810 | *gammas = sk_sp<SkGammas>(new (memory) SkGammas(inputChannels)); |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 811 | |
| 812 | uint32_t offset = 0; |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 813 | for (uint8_t channel = 0; channel < inputChannels; ++channel) { |
| 814 | (*gammas)->fType[channel] = type[channel]; |
| 815 | offset += load_gammas(memory,offset, type[channel], &data[channel], params[channel], |
| 816 | tagPtr[channel]); |
| 817 | (*gammas)->fData[channel] = data[channel]; |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 818 | |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 819 | } |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 820 | } |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 821 | |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 822 | if (kNonStandard_SkGammaNamed == *gammaNamed) { |
| 823 | *gammaNamed = is_named(*gammas); |
| 824 | if (kNonStandard_SkGammaNamed != *gammaNamed) { |
| 825 | // No need to keep the gammas struct, the enum is enough. |
| 826 | *gammas = nullptr; |
| 827 | } |
| 828 | } |
| 829 | return true; |
| 830 | } |
| 831 | |
raftias | e8ea07a | 2016-12-05 14:21:45 -0500 | [diff] [blame] | 832 | static bool is_lut_gamma_linear(const uint8_t* src, size_t count, size_t precision) { |
| 833 | // check for linear gamma (this is very common in lut gammas, as they aren't optional) |
| 834 | const float normalizeX = 1.f / (count - 1); |
| 835 | for (uint32_t x = 0; x < count; ++x) { |
| 836 | const float y = precision == 1 ? (src[x] / 255.f) |
| 837 | : (read_big_endian_u16(src + 2*x) / 65535.f); |
| 838 | if (!color_space_almost_equal(x * normalizeX, y)) { |
| 839 | return false; |
| 840 | } |
| 841 | } |
| 842 | return true; |
| 843 | } |
| 844 | |
| 845 | static bool load_lut_gammas(sk_sp<SkGammas>* gammas, SkGammaNamed* gammaNamed, size_t numTables, |
| 846 | size_t entriesPerTable, size_t precision, const uint8_t* src, |
| 847 | size_t len) { |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 848 | if (precision != 1 && precision != 2) { |
| 849 | SkColorSpacePrintf("Invalid gamma table precision %d\n", precision); |
| 850 | return false; |
| 851 | } |
| 852 | uint32_t totalEntries; |
| 853 | return_if_false(safe_mul(entriesPerTable, numTables, &totalEntries), |
| 854 | "Too many entries in gamma table."); |
| 855 | uint32_t readBytes; |
| 856 | return_if_false(safe_mul(precision, totalEntries, &readBytes), |
| 857 | "SkGammas struct is too large to read"); |
| 858 | if (len < readBytes) { |
| 859 | SkColorSpacePrintf("Gamma table is too small. Provided: %d. Required: %d\n", |
| 860 | len, readBytes); |
| 861 | return false; |
| 862 | } |
| 863 | |
| 864 | uint32_t writeBytesPerChannel; |
| 865 | return_if_false(safe_mul(sizeof(float), entriesPerTable, &writeBytesPerChannel), |
| 866 | "SkGammas struct is too large to allocate"); |
| 867 | const size_t readBytesPerChannel = precision * entriesPerTable; |
| 868 | size_t numTablesToUse = 1; |
| 869 | for (size_t tableIndex = 1; tableIndex < numTables; ++tableIndex) { |
| 870 | if (0 != memcmp(src, src + readBytesPerChannel * tableIndex, readBytesPerChannel)) { |
| 871 | numTablesToUse = numTables; |
| 872 | break; |
| 873 | } |
| 874 | } |
| 875 | |
raftias | e8ea07a | 2016-12-05 14:21:45 -0500 | [diff] [blame] | 876 | if (1 == numTablesToUse) { |
| 877 | if (is_lut_gamma_linear(src, entriesPerTable, precision)) { |
| 878 | *gammaNamed = kLinear_SkGammaNamed; |
| 879 | return true; |
| 880 | } |
| 881 | } |
| 882 | *gammaNamed = kNonStandard_SkGammaNamed; |
| 883 | |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 884 | uint32_t writetableBytes; |
| 885 | return_if_false(safe_mul(numTablesToUse, writeBytesPerChannel, &writetableBytes), |
| 886 | "SkGammas struct is too large to allocate"); |
| 887 | size_t allocSize = sizeof(SkGammas); |
| 888 | return_if_false(safe_add(allocSize, (size_t)writetableBytes, &allocSize), |
| 889 | "SkGammas struct is too large to allocate"); |
| 890 | |
| 891 | void* memory = sk_malloc_throw(allocSize); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 892 | *gammas = sk_sp<SkGammas>(new (memory) SkGammas(numTables)); |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 893 | |
| 894 | for (size_t tableIndex = 0; tableIndex < numTablesToUse; ++tableIndex) { |
| 895 | const uint8_t* ptr = src + readBytesPerChannel * tableIndex; |
| 896 | const size_t offset = sizeof(SkGammas) + tableIndex * writeBytesPerChannel; |
| 897 | float* table = SkTAddOffset<float>(memory, offset); |
| 898 | if (1 == precision) { |
| 899 | for (uint32_t i = 0; i < entriesPerTable; ++i, ptr += 1) { |
| 900 | table[i] = ((float) *ptr) / 255.0f; |
| 901 | } |
| 902 | } else if (2 == precision) { |
| 903 | for (uint32_t i = 0; i < entriesPerTable; ++i, ptr += 2) { |
| 904 | table[i] = ((float) read_big_endian_u16(ptr)) / 65535.0f; |
| 905 | } |
| 906 | } |
| 907 | } |
| 908 | |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 909 | SkASSERT(1 == numTablesToUse|| numTables == numTablesToUse); |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 910 | |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 911 | size_t tableOffset = 0; |
| 912 | for (size_t tableIndex = 0; tableIndex < numTables; ++tableIndex) { |
| 913 | (*gammas)->fType[tableIndex] = SkGammas::Type::kTable_Type; |
| 914 | (*gammas)->fData[tableIndex].fTable.fOffset = tableOffset; |
| 915 | (*gammas)->fData[tableIndex].fTable.fSize = entriesPerTable; |
| 916 | if (numTablesToUse > 1) { |
| 917 | tableOffset += writeBytesPerChannel; |
| 918 | } |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 919 | } |
| 920 | |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 921 | return true; |
| 922 | } |
| 923 | |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 924 | bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, const uint8_t* src, |
raftias | 14d2899 | 2016-11-21 12:59:36 -0500 | [diff] [blame] | 925 | size_t len, SkColorSpace_A2B::PCS pcs) { |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 926 | SkASSERT(len >= 32); |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 927 | // Read the number of channels. The four bytes (4-7) that we skipped are reserved and |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 928 | // must be zero. |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 929 | const uint8_t inputChannels = src[8]; |
| 930 | const uint8_t outputChannels = src[9]; |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 931 | if (SkColorLookUpTable::kOutputChannels != outputChannels) { |
| 932 | // We only handle RGB outputs. The number of output channels must be 3. |
| 933 | SkColorSpacePrintf("Output channels (%d) must equal 3 in A to B tag.\n", outputChannels); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 934 | return false; |
| 935 | } |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 936 | if (inputChannels == 0 || inputChannels > 4) { |
| 937 | // And we only support 4 input channels. |
| 938 | // ICC says up to 16 but our decode can only handle 4. |
| 939 | // It could easily be extended to support up to 8, but we only allow CMYK/RGB |
| 940 | // input color spaces which are 3 and 4 so let's restrict it to 4 instead of 8. |
| 941 | // We can always change this check when we support bigger input spaces. |
| 942 | SkColorSpacePrintf("Input channels (%d) must be between 1 and 4 in A to B tag.\n", |
| 943 | inputChannels); |
| 944 | return false; |
| 945 | } |
| 946 | |
raftias | 14d2899 | 2016-11-21 12:59:36 -0500 | [diff] [blame] | 947 | |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 948 | // It is important that these are loaded in the order of application, as the |
| 949 | // order you construct an A2B color space's elements is the order it is applied |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 950 | |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 951 | // If the offset is non-zero it indicates that the element is present. |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 952 | const uint32_t offsetToACurves = read_big_endian_i32(src + 28); |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 953 | if (0 != offsetToACurves && offsetToACurves < len) { |
| 954 | const size_t tagLen = len - offsetToACurves; |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 955 | SkGammaNamed gammaNamed; |
| 956 | sk_sp<SkGammas> gammas; |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 957 | if (!parse_and_load_gamma(&gammaNamed, &gammas, inputChannels, src + offsetToACurves, |
| 958 | tagLen)) { |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 959 | return false; |
| 960 | } |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 961 | if (gammas) { |
| 962 | elements->push_back(SkColorSpace_A2B::Element(std::move(gammas))); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 963 | } else if (kLinear_SkGammaNamed != gammaNamed) { |
| 964 | elements->push_back(SkColorSpace_A2B::Element(gammaNamed, inputChannels)); |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 965 | } |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 966 | } |
| 967 | |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 968 | const uint32_t offsetToColorLUT = read_big_endian_i32(src + 24); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 969 | if (0 != offsetToColorLUT && offsetToColorLUT < len) { |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 970 | sk_sp<SkColorLookUpTable> colorLUT; |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 971 | const uint8_t* clutSrc = src + offsetToColorLUT; |
| 972 | const size_t clutLen = len - offsetToColorLUT; |
| 973 | // 16 bytes reserved for grid points, 1 for precision, 3 for padding. |
| 974 | // The color LUT data follows after this header. |
| 975 | static constexpr uint32_t kColorLUTHeaderSize = 20; |
| 976 | if (clutLen < kColorLUTHeaderSize) { |
| 977 | SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", clutLen); |
| 978 | return false; |
| 979 | } |
| 980 | |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 981 | SkASSERT(inputChannels <= kMaxColorChannels); |
| 982 | uint8_t gridPoints[kMaxColorChannels]; |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 983 | for (uint32_t i = 0; i < inputChannels; ++i) { |
| 984 | gridPoints[i] = clutSrc[i]; |
| 985 | } |
| 986 | // Space is provided for a maximum of 16 input channels. |
| 987 | // Now we determine the precision of the table values. |
| 988 | const uint8_t precision = clutSrc[16]; |
| 989 | if (!load_color_lut(&colorLUT, inputChannels, precision, gridPoints, |
| 990 | clutSrc + kColorLUTHeaderSize, clutLen - kColorLUTHeaderSize)) { |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 991 | SkColorSpacePrintf("Failed to read color LUT from A to B tag.\n"); |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 992 | return false; |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 993 | } |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 994 | elements->push_back(SkColorSpace_A2B::Element(std::move(colorLUT))); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 995 | } |
| 996 | |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 997 | const uint32_t offsetToMCurves = read_big_endian_i32(src + 20); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 998 | if (0 != offsetToMCurves && offsetToMCurves < len) { |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 999 | const size_t tagLen = len - offsetToMCurves; |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1000 | SkGammaNamed gammaNamed; |
| 1001 | sk_sp<SkGammas> gammas; |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1002 | if (!parse_and_load_gamma(&gammaNamed, &gammas, outputChannels, src + offsetToMCurves, |
| 1003 | tagLen)) { |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 1004 | return false; |
msarett | 0f0c4f0 | 2016-08-22 09:44:35 -0700 | [diff] [blame] | 1005 | } |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1006 | if (gammas) { |
| 1007 | elements->push_back(SkColorSpace_A2B::Element(std::move(gammas))); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1008 | } else if (kLinear_SkGammaNamed != gammaNamed) { |
| 1009 | elements->push_back(SkColorSpace_A2B::Element(gammaNamed, outputChannels)); |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1010 | } |
msarett | 0f0c4f0 | 2016-08-22 09:44:35 -0700 | [diff] [blame] | 1011 | } |
| 1012 | |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1013 | const uint32_t offsetToMatrix = read_big_endian_i32(src + 16); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 1014 | if (0 != offsetToMatrix && offsetToMatrix < len) { |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1015 | SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor); |
raftias | 14d2899 | 2016-11-21 12:59:36 -0500 | [diff] [blame] | 1016 | if (!load_matrix(&matrix, src + offsetToMatrix, len - offsetToMatrix, true, pcs)) { |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 1017 | SkColorSpacePrintf("Failed to read matrix from A to B tag.\n"); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1018 | } else if (!matrix.isIdentity()) { |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1019 | elements->push_back(SkColorSpace_A2B::Element(matrix)); |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 1020 | } |
| 1021 | } |
raftias | 14d2899 | 2016-11-21 12:59:36 -0500 | [diff] [blame] | 1022 | |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1023 | const uint32_t offsetToBCurves = read_big_endian_i32(src + 12); |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 1024 | if (0 != offsetToBCurves && offsetToBCurves < len) { |
| 1025 | const size_t tagLen = len - offsetToBCurves; |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1026 | SkGammaNamed gammaNamed; |
| 1027 | sk_sp<SkGammas> gammas; |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1028 | if (!parse_and_load_gamma(&gammaNamed, &gammas, outputChannels, src + offsetToBCurves, |
| 1029 | tagLen)) { |
raftias | 9488833 | 2016-10-18 10:02:51 -0700 | [diff] [blame] | 1030 | return false; |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 1031 | } |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1032 | if (gammas) { |
| 1033 | elements->push_back(SkColorSpace_A2B::Element(std::move(gammas))); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1034 | } else if (kLinear_SkGammaNamed != gammaNamed) { |
| 1035 | elements->push_back(SkColorSpace_A2B::Element(gammaNamed, outputChannels)); |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1036 | } |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 1037 | } |
| 1038 | |
| 1039 | return true; |
| 1040 | } |
| 1041 | |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 1042 | bool load_a2b0_lutn_type(std::vector<SkColorSpace_A2B::Element>* elements, const uint8_t* src, |
| 1043 | size_t len, SkColorSpace_A2B::PCS pcs) { |
| 1044 | const uint32_t type = read_big_endian_u32(src); |
| 1045 | switch (type) { |
| 1046 | case kTAG_lut8Type: |
| 1047 | SkASSERT(len >= 48); |
| 1048 | break; |
| 1049 | case kTAG_lut16Type: |
| 1050 | SkASSERT(len >= 52); |
| 1051 | break; |
| 1052 | default: |
| 1053 | SkASSERT(false); |
| 1054 | return false; |
| 1055 | } |
| 1056 | // Read the number of channels. |
| 1057 | // The four bytes (4-7) that we skipped are reserved and must be zero. |
| 1058 | const uint8_t inputChannels = src[8]; |
| 1059 | const uint8_t outputChannels = src[9]; |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1060 | if (SkColorLookUpTable::kOutputChannels != outputChannels) { |
| 1061 | // We only handle RGB outputs. The number of output channels must be 3. |
| 1062 | SkColorSpacePrintf("Output channels (%d) must equal 3 in A to B tag.\n", outputChannels); |
| 1063 | return false; |
| 1064 | } |
| 1065 | if (inputChannels == 0 || inputChannels > 4) { |
| 1066 | // And we only support 4 input channels. |
| 1067 | // ICC says up to 16 but our decode can only handle 4. |
| 1068 | // It could easily be extended to support up to 8, but we only allow CMYK/RGB |
| 1069 | // input color spaces which are 3 and 4 so let's restrict it to 4 instead of 8. |
| 1070 | // We can always change this check when we support bigger input spaces. |
| 1071 | SkColorSpacePrintf("Input channels (%d) must be between 1 and 4 in A to B tag.\n", |
| 1072 | inputChannels); |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 1073 | return false; |
| 1074 | } |
| 1075 | |
| 1076 | const uint8_t clutGridPoints = src[10]; |
| 1077 | // 11th byte reserved for padding (required to be zero) |
| 1078 | |
| 1079 | SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor); |
| 1080 | load_matrix(&matrix, &src[12], len - 12, false, pcs); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1081 | if (!matrix.isIdentity()) { |
| 1082 | // ICC specs (10.8/10.9) say lut8/16Type profiles must have identity matrices |
| 1083 | // if the input color space is not PCSXYZ, and we do not support PCSXYZ input color spaces |
| 1084 | // so we should never encounter a non-identity matrix here. |
raftias | fc6d9e9 | 2016-12-08 11:56:34 -0500 | [diff] [blame] | 1085 | // However, 2 test images from the ICC website have RGB input spaces and non-identity |
| 1086 | // matrices so we're not going to fail here, despite being against the spec. |
| 1087 | SkColorSpacePrintf("Warning: non-Identity matrix found in non-XYZ input color space" |
| 1088 | "lut profile"); |
| 1089 | elements->push_back(SkColorSpace_A2B::Element(matrix)); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1090 | } |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 1091 | |
| 1092 | size_t dataOffset = 48; |
| 1093 | // # of input table entries |
| 1094 | size_t inTableEntries = 256; |
| 1095 | // # of output table entries |
| 1096 | size_t outTableEntries = 256; |
| 1097 | size_t precision = 1; |
| 1098 | if (kTAG_lut16Type == type) { |
| 1099 | dataOffset = 52; |
| 1100 | inTableEntries = read_big_endian_u16(src + 48); |
| 1101 | outTableEntries = read_big_endian_u16(src + 50); |
| 1102 | precision = 2; |
raftias | 8daef3e | 2016-11-22 13:21:22 -0500 | [diff] [blame] | 1103 | |
| 1104 | constexpr size_t kMaxLut16GammaEntries = 4096; |
| 1105 | if (inTableEntries < 2) { |
| 1106 | SkColorSpacePrintf("Too few (%d) input gamma table entries. Must have at least 2.\n", |
| 1107 | inTableEntries); |
| 1108 | return false; |
| 1109 | } else if (inTableEntries > kMaxLut16GammaEntries) { |
| 1110 | SkColorSpacePrintf("Too many (%d) input gamma table entries. Must have at most %d.\n", |
| 1111 | inTableEntries, kMaxLut16GammaEntries); |
| 1112 | return false; |
| 1113 | } |
| 1114 | |
| 1115 | if (outTableEntries < 2) { |
| 1116 | SkColorSpacePrintf("Too few (%d) output gamma table entries. Must have at least 2.\n", |
| 1117 | outTableEntries); |
| 1118 | return false; |
| 1119 | } else if (outTableEntries > kMaxLut16GammaEntries) { |
| 1120 | SkColorSpacePrintf("Too many (%d) output gamma table entries. Must have at most %d.\n", |
| 1121 | outTableEntries, kMaxLut16GammaEntries); |
| 1122 | return false; |
| 1123 | } |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 1124 | } |
| 1125 | |
| 1126 | const size_t inputOffset = dataOffset; |
| 1127 | return_if_false(len >= inputOffset, "A2B0 lutnType tag too small for input gamma table"); |
| 1128 | sk_sp<SkGammas> inputGammas; |
raftias | e8ea07a | 2016-12-05 14:21:45 -0500 | [diff] [blame] | 1129 | SkGammaNamed inputGammaNamed; |
| 1130 | if (!load_lut_gammas(&inputGammas, &inputGammaNamed, inputChannels, inTableEntries, precision, |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 1131 | src + inputOffset, len - inputOffset)) { |
| 1132 | SkColorSpacePrintf("Failed to read input gammas from lutnType tag.\n"); |
| 1133 | return false; |
| 1134 | } |
raftias | e8ea07a | 2016-12-05 14:21:45 -0500 | [diff] [blame] | 1135 | SkASSERT(inputGammas || inputGammaNamed != kNonStandard_SkGammaNamed); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1136 | if (kLinear_SkGammaNamed != inputGammaNamed) { |
| 1137 | if (kNonStandard_SkGammaNamed != inputGammaNamed) { |
| 1138 | elements->push_back(SkColorSpace_A2B::Element(inputGammaNamed, inputChannels)); |
| 1139 | } else { |
| 1140 | elements->push_back(SkColorSpace_A2B::Element(std::move(inputGammas))); |
| 1141 | } |
| 1142 | } |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 1143 | |
| 1144 | const size_t clutOffset = inputOffset + precision*inTableEntries*inputChannels; |
| 1145 | return_if_false(len >= clutOffset, "A2B0 lutnType tag too small for CLUT"); |
| 1146 | sk_sp<SkColorLookUpTable> colorLUT; |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1147 | const uint8_t gridPoints[kMaxColorChannels] = { |
| 1148 | clutGridPoints, clutGridPoints, clutGridPoints, clutGridPoints |
| 1149 | }; |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 1150 | if (!load_color_lut(&colorLUT, inputChannels, precision, gridPoints, src + clutOffset, |
| 1151 | len - clutOffset)) { |
| 1152 | SkColorSpacePrintf("Failed to read color LUT from lutnType tag.\n"); |
| 1153 | return false; |
| 1154 | } |
| 1155 | SkASSERT(colorLUT); |
| 1156 | elements->push_back(SkColorSpace_A2B::Element(std::move(colorLUT))); |
| 1157 | |
| 1158 | size_t clutSize = precision * outputChannels; |
| 1159 | for (int i = 0; i < inputChannels; ++i) { |
| 1160 | clutSize *= clutGridPoints; |
| 1161 | } |
| 1162 | const size_t outputOffset = clutOffset + clutSize; |
| 1163 | return_if_false(len >= outputOffset, "A2B0 lutnType tag too small for output gamma table"); |
| 1164 | sk_sp<SkGammas> outputGammas; |
raftias | e8ea07a | 2016-12-05 14:21:45 -0500 | [diff] [blame] | 1165 | SkGammaNamed outputGammaNamed; |
| 1166 | if (!load_lut_gammas(&outputGammas, &outputGammaNamed, outputChannels, outTableEntries, |
| 1167 | precision, src + outputOffset, len - outputOffset)) { |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 1168 | SkColorSpacePrintf("Failed to read output gammas from lutnType tag.\n"); |
| 1169 | return false; |
| 1170 | } |
raftias | e8ea07a | 2016-12-05 14:21:45 -0500 | [diff] [blame] | 1171 | SkASSERT(outputGammas || outputGammaNamed != kNonStandard_SkGammaNamed); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1172 | if (kLinear_SkGammaNamed != outputGammaNamed) { |
| 1173 | if (kNonStandard_SkGammaNamed != outputGammaNamed) { |
| 1174 | elements->push_back(SkColorSpace_A2B::Element(outputGammaNamed, outputChannels)); |
| 1175 | } else { |
| 1176 | elements->push_back(SkColorSpace_A2B::Element(std::move(outputGammas))); |
| 1177 | } |
| 1178 | } |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 1179 | |
| 1180 | return true; |
| 1181 | } |
| 1182 | |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1183 | static inline int icf_channels(SkColorSpace_Base::ICCTypeFlag iccType) { |
| 1184 | switch (iccType) { |
| 1185 | case SkColorSpace_Base::kRGB_ICCTypeFlag: |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1186 | return 3; |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1187 | case SkColorSpace_Base::kCMYK_ICCTypeFlag: |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1188 | return 4; |
| 1189 | default: |
| 1190 | SkASSERT(false); |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1191 | return 0; |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1192 | } |
| 1193 | } |
| 1194 | |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1195 | static bool load_a2b0(std::vector<SkColorSpace_A2B::Element>* elements, const uint8_t* src, |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1196 | size_t len, SkColorSpace_A2B::PCS pcs, |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1197 | SkColorSpace_Base::ICCTypeFlag iccType) { |
Matt Sarett | 6e83479 | 2017-04-18 11:08:29 -0400 | [diff] [blame] | 1198 | if (len < 4) { |
| 1199 | return false; |
| 1200 | } |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1201 | const uint32_t type = read_big_endian_u32(src); |
Matt Sarett | 6e83479 | 2017-04-18 11:08:29 -0400 | [diff] [blame] | 1202 | |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1203 | switch (type) { |
| 1204 | case kTAG_AtoBType: |
| 1205 | if (len < 32) { |
| 1206 | SkColorSpacePrintf("A to B tag is too small (%d bytes).", len); |
| 1207 | return false; |
| 1208 | } |
| 1209 | SkColorSpacePrintf("A2B0 tag is of type lutAtoBType\n"); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1210 | if (!load_a2b0_a_to_b_type(elements, src, len, pcs)) { |
| 1211 | return false; |
| 1212 | } |
| 1213 | break; |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 1214 | case kTAG_lut8Type: |
| 1215 | if (len < 48) { |
| 1216 | SkColorSpacePrintf("lut8 tag is too small (%d bytes).", len); |
| 1217 | return false; |
| 1218 | } |
| 1219 | SkColorSpacePrintf("A2B0 tag of type lut8Type\n"); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1220 | if (!load_a2b0_lutn_type(elements, src, len, pcs)) { |
| 1221 | return false; |
| 1222 | } |
| 1223 | break; |
raftias | 71de072 | 2016-11-21 14:22:53 -0500 | [diff] [blame] | 1224 | case kTAG_lut16Type: |
| 1225 | if (len < 52) { |
| 1226 | SkColorSpacePrintf("lut16 tag is too small (%d bytes).", len); |
| 1227 | return false; |
| 1228 | } |
| 1229 | SkColorSpacePrintf("A2B0 tag of type lut16Type\n"); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1230 | if (!load_a2b0_lutn_type(elements, src, len, pcs)) { |
| 1231 | return false; |
| 1232 | } |
| 1233 | break; |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1234 | default: |
| 1235 | SkColorSpacePrintf("Unsupported A to B tag type: %c%c%c%c\n", (type>>24)&0xFF, |
| 1236 | (type>>16)&0xFF, (type>>8)&0xFF, type&0xFF); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1237 | return false; |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1238 | } |
raftias | 2bb3592 | 2016-12-01 16:40:51 -0500 | [diff] [blame] | 1239 | SkASSERT(SkColorSpace_A2B::PCS::kLAB == pcs || SkColorSpace_A2B::PCS::kXYZ == pcs); |
| 1240 | static constexpr int kPCSChannels = 3; // must be PCSLAB or PCSXYZ |
| 1241 | if (elements->empty()) { |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1242 | return kPCSChannels == icf_channels(iccType); |
raftias | 2bb3592 | 2016-12-01 16:40:51 -0500 | [diff] [blame] | 1243 | } |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1244 | // now let's verify that the input/output channels of each A2B element actually match up |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1245 | if (icf_channels(iccType) != elements->front().inputChannels()) { |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1246 | SkColorSpacePrintf("Input channel count does not match first A2B element's input count"); |
| 1247 | return false; |
| 1248 | } |
| 1249 | for (size_t i = 1; i < elements->size(); ++i) { |
| 1250 | if ((*elements)[i - 1].outputChannels() != (*elements)[i].inputChannels()) { |
| 1251 | SkColorSpacePrintf("A2B elements don't agree in input/output channel counts"); |
| 1252 | return false; |
| 1253 | } |
| 1254 | } |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1255 | if (kPCSChannels != elements->back().outputChannels()) { |
| 1256 | SkColorSpacePrintf("PCS channel count doesn't match last A2B element's output count"); |
| 1257 | return false; |
| 1258 | } |
| 1259 | return true; |
raftias | 026f223 | 2016-10-24 09:52:26 -0700 | [diff] [blame] | 1260 | } |
| 1261 | |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 1262 | static bool tag_equals(const ICCTag* a, const ICCTag* b, const uint8_t* base) { |
| 1263 | if (!a || !b) { |
| 1264 | return a == b; |
| 1265 | } |
| 1266 | |
| 1267 | if (a->fLength != b->fLength) { |
| 1268 | return false; |
| 1269 | } |
| 1270 | |
| 1271 | if (a->fOffset == b->fOffset) { |
| 1272 | return true; |
| 1273 | } |
| 1274 | |
| 1275 | return !memcmp(a->addr(base), b->addr(base), a->fLength); |
| 1276 | } |
| 1277 | |
Matt Sarett | 0186661 | 2016-10-31 13:41:57 -0400 | [diff] [blame] | 1278 | static inline bool is_close_to_d50(const SkMatrix44& matrix) { |
| 1279 | // rX + gX + bX |
| 1280 | float X = matrix.getFloat(0, 0) + matrix.getFloat(0, 1) + matrix.getFloat(0, 2); |
| 1281 | |
| 1282 | // rY + gY + bY |
| 1283 | float Y = matrix.getFloat(1, 0) + matrix.getFloat(1, 1) + matrix.getFloat(1, 2); |
| 1284 | |
| 1285 | // rZ + gZ + bZ |
| 1286 | float Z = matrix.getFloat(2, 0) + matrix.getFloat(2, 1) + matrix.getFloat(2, 2); |
| 1287 | |
| 1288 | static const float kD50_WhitePoint[3] = { 0.96420f, 1.00000f, 0.82491f }; |
| 1289 | |
| 1290 | // This is a bit more lenient than QCMS and Adobe. Is there a reason to be stricter here? |
| 1291 | return (SkTAbs(X - kD50_WhitePoint[0]) <= 0.04f) && |
| 1292 | (SkTAbs(Y - kD50_WhitePoint[1]) <= 0.04f) && |
| 1293 | (SkTAbs(Z - kD50_WhitePoint[2]) <= 0.04f); |
| 1294 | } |
| 1295 | |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1296 | static sk_sp<SkColorSpace> make_xyz(const ICCProfileHeader& header, ICCTag* tags, int tagCount, |
| 1297 | const uint8_t* base, sk_sp<SkData> profileData) { |
| 1298 | if (kLAB_PCSSpace == header.fPCS) { |
| 1299 | return nullptr; |
| 1300 | } |
| 1301 | |
| 1302 | // Recognize the rXYZ, gXYZ, and bXYZ tags. |
| 1303 | const ICCTag* r = ICCTag::Find(tags, tagCount, kTAG_rXYZ); |
| 1304 | const ICCTag* g = ICCTag::Find(tags, tagCount, kTAG_gXYZ); |
| 1305 | const ICCTag* b = ICCTag::Find(tags, tagCount, kTAG_bXYZ); |
| 1306 | if (!r || !g || !b) { |
| 1307 | return nullptr; |
| 1308 | } |
| 1309 | |
| 1310 | float toXYZ[9]; |
| 1311 | if (!load_xyz(&toXYZ[0], r->addr(base), r->fLength) || |
| 1312 | !load_xyz(&toXYZ[3], g->addr(base), g->fLength) || |
| 1313 | !load_xyz(&toXYZ[6], b->addr(base), b->fLength)) |
| 1314 | { |
| 1315 | return_null("Need valid rgb tags for XYZ space"); |
| 1316 | } |
| 1317 | SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor); |
| 1318 | mat.set3x3(toXYZ[0], toXYZ[1], toXYZ[2], |
| 1319 | toXYZ[3], toXYZ[4], toXYZ[5], |
| 1320 | toXYZ[6], toXYZ[7], toXYZ[8]); |
| 1321 | if (!is_close_to_d50(mat)) { |
| 1322 | return_null("XYZ matrix is not D50"); |
| 1323 | } |
| 1324 | |
| 1325 | // If some, but not all, of the gamma tags are missing, assume that all |
| 1326 | // gammas are meant to be the same. |
| 1327 | r = ICCTag::Find(tags, tagCount, kTAG_rTRC); |
| 1328 | g = ICCTag::Find(tags, tagCount, kTAG_gTRC); |
| 1329 | b = ICCTag::Find(tags, tagCount, kTAG_bTRC); |
| 1330 | if ((!r || !g || !b)) { |
| 1331 | if (!r) { |
| 1332 | r = g ? g : b; |
| 1333 | } |
| 1334 | if (!g) { |
| 1335 | g = r ? r : b; |
| 1336 | } |
| 1337 | if (!b) { |
| 1338 | b = r ? r : g; |
| 1339 | } |
| 1340 | } |
| 1341 | |
| 1342 | SkGammaNamed gammaNamed = kNonStandard_SkGammaNamed; |
| 1343 | sk_sp<SkGammas> gammas = nullptr; |
| 1344 | size_t tagBytes; |
| 1345 | if (r && g && b) { |
| 1346 | if (tag_equals(r, g, base) && tag_equals(g, b, base)) { |
| 1347 | SkGammas::Data data; |
| 1348 | SkColorSpaceTransferFn params; |
| 1349 | SkGammas::Type type = |
| 1350 | parse_gamma(&data, ¶ms, &tagBytes, r->addr(base), r->fLength); |
| 1351 | handle_invalid_gamma(&type, &data); |
| 1352 | |
| 1353 | if (SkGammas::Type::kNamed_Type == type) { |
| 1354 | gammaNamed = data.fNamed; |
| 1355 | } else { |
| 1356 | size_t allocSize = sizeof(SkGammas); |
| 1357 | if (!safe_add(allocSize, gamma_alloc_size(type, data), &allocSize)) { |
| 1358 | return_null("SkGammas struct is too large to allocate"); |
| 1359 | } |
| 1360 | void* memory = sk_malloc_throw(allocSize); |
| 1361 | gammas = sk_sp<SkGammas>(new (memory) SkGammas(3)); |
| 1362 | load_gammas(memory, 0, type, &data, params, r->addr(base)); |
| 1363 | |
| 1364 | for (int i = 0; i < 3; ++i) { |
| 1365 | gammas->fType[i] = type; |
| 1366 | gammas->fData[i] = data; |
| 1367 | } |
| 1368 | } |
| 1369 | } else { |
| 1370 | SkGammas::Data rData; |
| 1371 | SkColorSpaceTransferFn rParams; |
| 1372 | SkGammas::Type rType = |
| 1373 | parse_gamma(&rData, &rParams, &tagBytes, r->addr(base), r->fLength); |
| 1374 | handle_invalid_gamma(&rType, &rData); |
| 1375 | |
| 1376 | SkGammas::Data gData; |
| 1377 | SkColorSpaceTransferFn gParams; |
| 1378 | SkGammas::Type gType = |
| 1379 | parse_gamma(&gData, &gParams, &tagBytes, g->addr(base), g->fLength); |
| 1380 | handle_invalid_gamma(&gType, &gData); |
| 1381 | |
| 1382 | SkGammas::Data bData; |
| 1383 | SkColorSpaceTransferFn bParams; |
| 1384 | SkGammas::Type bType = |
| 1385 | parse_gamma(&bData, &bParams, &tagBytes, b->addr(base), b->fLength); |
| 1386 | handle_invalid_gamma(&bType, &bData); |
| 1387 | |
| 1388 | size_t allocSize = sizeof(SkGammas); |
| 1389 | if (!safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize) || |
| 1390 | !safe_add(allocSize, gamma_alloc_size(gType, gData), &allocSize) || |
| 1391 | !safe_add(allocSize, gamma_alloc_size(bType, bData), &allocSize)) { |
| 1392 | return_null("SkGammas struct is too large to allocate"); |
| 1393 | } |
| 1394 | void* memory = sk_malloc_throw(allocSize); |
| 1395 | gammas = sk_sp<SkGammas>(new (memory) SkGammas(3)); |
| 1396 | |
| 1397 | uint32_t offset = 0; |
| 1398 | gammas->fType[0] = rType; |
| 1399 | offset += load_gammas(memory, offset, rType, &rData, rParams, |
| 1400 | r->addr(base)); |
| 1401 | |
| 1402 | gammas->fType[1] = gType; |
| 1403 | offset += load_gammas(memory, offset, gType, &gData, gParams, |
| 1404 | g->addr(base)); |
| 1405 | |
| 1406 | gammas->fType[2] = bType; |
| 1407 | load_gammas(memory, offset, bType, &bData, bParams, b->addr(base)); |
| 1408 | |
| 1409 | gammas->fData[0] = rData; |
| 1410 | gammas->fData[1] = gData; |
| 1411 | gammas->fData[2] = bData; |
| 1412 | } |
| 1413 | } else { |
| 1414 | // Guess sRGB if the profile is missing transfer functions. |
| 1415 | gammaNamed = kSRGB_SkGammaNamed; |
| 1416 | } |
| 1417 | |
| 1418 | if (kNonStandard_SkGammaNamed == gammaNamed) { |
| 1419 | // It's possible that we'll initially detect non-matching gammas, only for |
| 1420 | // them to evaluate to the same named gamma curve. |
| 1421 | gammaNamed = is_named(gammas); |
| 1422 | } |
| 1423 | |
| 1424 | if (kNonStandard_SkGammaNamed == gammaNamed) { |
| 1425 | return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed, |
| 1426 | std::move(gammas), |
Matt Sarett | f388093 | 2017-03-24 10:06:03 -0400 | [diff] [blame] | 1427 | mat, std::move(profileData))); |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1428 | } |
| 1429 | |
Matt Sarett | f388093 | 2017-03-24 10:06:03 -0400 | [diff] [blame] | 1430 | return SkColorSpace_Base::MakeRGB(gammaNamed, mat); |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1431 | } |
| 1432 | |
| 1433 | static sk_sp<SkColorSpace> make_gray(const ICCProfileHeader& header, ICCTag* tags, int tagCount, |
| 1434 | const uint8_t* base, sk_sp<SkData> profileData) { |
| 1435 | if (kLAB_PCSSpace == header.fPCS) { |
| 1436 | return nullptr; |
| 1437 | } |
| 1438 | |
| 1439 | const ICCTag* grayTRC = ICCTag::Find(tags, tagCount, kTAG_kTRC); |
| 1440 | if (!grayTRC) { |
| 1441 | return_null("grayTRC tag required for monochrome profiles."); |
| 1442 | } |
| 1443 | SkGammas::Data data; |
| 1444 | SkColorSpaceTransferFn params; |
| 1445 | size_t tagBytes; |
| 1446 | SkGammas::Type type = |
| 1447 | parse_gamma(&data, ¶ms, &tagBytes, grayTRC->addr(base), grayTRC->fLength); |
| 1448 | handle_invalid_gamma(&type, &data); |
| 1449 | |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1450 | SkMatrix44 toXYZD50(SkMatrix44::kIdentity_Constructor); |
| 1451 | toXYZD50.setFloat(0, 0, kWhitePointD50[0]); |
| 1452 | toXYZD50.setFloat(1, 1, kWhitePointD50[1]); |
| 1453 | toXYZD50.setFloat(2, 2, kWhitePointD50[2]); |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1454 | if (SkGammas::Type::kNamed_Type == type) { |
Matt Sarett | f388093 | 2017-03-24 10:06:03 -0400 | [diff] [blame] | 1455 | return SkColorSpace_Base::MakeRGB(data.fNamed, toXYZD50); |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1456 | } |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1457 | |
| 1458 | size_t allocSize = sizeof(SkGammas); |
| 1459 | if (!safe_add(allocSize, gamma_alloc_size(type, data), &allocSize)) { |
| 1460 | return_null("SkGammas struct is too large to allocate"); |
| 1461 | } |
| 1462 | void* memory = sk_malloc_throw(allocSize); |
| 1463 | sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(3)); |
| 1464 | load_gammas(memory, 0, type, &data, params, grayTRC->addr(base)); |
| 1465 | for (int i = 0; i < 3; ++i) { |
| 1466 | gammas->fType[i] = type; |
| 1467 | gammas->fData[i] = data; |
| 1468 | } |
| 1469 | |
| 1470 | return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed, |
| 1471 | std::move(gammas), |
Matt Sarett | f388093 | 2017-03-24 10:06:03 -0400 | [diff] [blame] | 1472 | toXYZD50, std::move(profileData))); |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1473 | } |
| 1474 | |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1475 | static sk_sp<SkColorSpace> make_a2b(SkColorSpace_Base::ICCTypeFlag iccType, |
Matt Sarett | 8e04286 | 2016-12-19 09:43:30 -0500 | [diff] [blame] | 1476 | const ICCProfileHeader& header, ICCTag* tags, int tagCount, |
| 1477 | const uint8_t* base, sk_sp<SkData> profileData) { |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1478 | const ICCTag* a2b0 = ICCTag::Find(tags, tagCount, kTAG_A2B0); |
| 1479 | if (a2b0) { |
| 1480 | const SkColorSpace_A2B::PCS pcs = kXYZ_PCSSpace == header.fPCS |
| 1481 | ? SkColorSpace_A2B::PCS::kXYZ |
| 1482 | : SkColorSpace_A2B::PCS::kLAB; |
| 1483 | std::vector<SkColorSpace_A2B::Element> elements; |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1484 | if (load_a2b0(&elements, a2b0->addr(base), a2b0->fLength, pcs, iccType)) { |
| 1485 | return sk_sp<SkColorSpace>(new SkColorSpace_A2B(iccType, std::move(elements), |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1486 | pcs, std::move(profileData))); |
| 1487 | } |
| 1488 | } |
| 1489 | |
| 1490 | return nullptr; |
| 1491 | } |
| 1492 | |
Brian Osman | 526972e | 2016-10-24 09:24:02 -0400 | [diff] [blame] | 1493 | sk_sp<SkColorSpace> SkColorSpace::MakeICC(const void* input, size_t len) { |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1494 | return SkColorSpace_Base::MakeICC(input, len, SkColorSpace_Base::kRGB_ICCTypeFlag); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1495 | } |
| 1496 | |
| 1497 | sk_sp<SkColorSpace> SkColorSpace_Base::MakeICC(const void* input, size_t len, |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1498 | ICCTypeFlag desiredType) { |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 1499 | if (!input || len < kICCHeaderSize) { |
| 1500 | return_null("Data is null or not large enough to contain an ICC profile"); |
| 1501 | } |
| 1502 | |
| 1503 | // Create our own copy of the input. |
| 1504 | void* memory = sk_malloc_throw(len); |
| 1505 | memcpy(memory, input, len); |
raftias | 91db12d | 2016-12-02 11:56:59 -0500 | [diff] [blame] | 1506 | sk_sp<SkData> profileData = SkData::MakeFromMalloc(memory, len); |
| 1507 | const uint8_t* base = profileData->bytes(); |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 1508 | const uint8_t* ptr = base; |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 1509 | |
| 1510 | // Read the ICC profile header and check to make sure that it is valid. |
| 1511 | ICCProfileHeader header; |
| 1512 | header.init(ptr, len); |
| 1513 | if (!header.valid()) { |
| 1514 | return nullptr; |
| 1515 | } |
| 1516 | |
| 1517 | // Adjust ptr and len before reading the tags. |
| 1518 | if (len < header.fSize) { |
| 1519 | SkColorSpacePrintf("ICC profile might be truncated.\n"); |
| 1520 | } else if (len > header.fSize) { |
| 1521 | SkColorSpacePrintf("Caller provided extra data beyond the end of the ICC profile.\n"); |
| 1522 | len = header.fSize; |
| 1523 | } |
| 1524 | ptr += kICCHeaderSize; |
| 1525 | len -= kICCHeaderSize; |
| 1526 | |
| 1527 | // Parse tag headers. |
| 1528 | uint32_t tagCount = header.fTagCount; |
| 1529 | SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount); |
| 1530 | if (len < kICCTagTableEntrySize * tagCount) { |
| 1531 | return_null("Not enough input data to read tag table entries"); |
| 1532 | } |
| 1533 | |
| 1534 | SkAutoTArray<ICCTag> tags(tagCount); |
| 1535 | for (uint32_t i = 0; i < tagCount; i++) { |
| 1536 | ptr = tags[i].init(ptr); |
| 1537 | SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24) & 0xFF, |
| 1538 | (tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >> 8) & 0xFF, |
| 1539 | (tags[i].fSignature >> 0) & 0xFF, tags[i].fOffset, tags[i].fLength); |
| 1540 | |
| 1541 | if (!tags[i].valid(kICCHeaderSize + len)) { |
| 1542 | return_null("Tag is too large to fit in ICC profile"); |
| 1543 | } |
| 1544 | } |
| 1545 | |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1546 | switch (header.fInputColorSpace) { |
| 1547 | case kRGB_ColorSpace: { |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1548 | if (!(kRGB_ICCTypeFlag & desiredType)) { |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1549 | return_null("Provided input color format (RGB) does not match profile."); |
| 1550 | } |
| 1551 | |
| 1552 | sk_sp<SkColorSpace> colorSpace = |
| 1553 | make_xyz(header, tags.get(), tagCount, base, profileData); |
| 1554 | if (colorSpace) { |
| 1555 | return colorSpace; |
| 1556 | } |
| 1557 | |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1558 | desiredType = kRGB_ICCTypeFlag; |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1559 | break; |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1560 | } |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1561 | case kGray_ColorSpace: { |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1562 | if (!(kGray_ICCTypeFlag & desiredType)) { |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1563 | return_null("Provided input color format (Gray) does not match profile."); |
| 1564 | } |
| 1565 | |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1566 | return make_gray(header, tags.get(), tagCount, base, profileData); |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1567 | } |
| 1568 | case kCMYK_ColorSpace: |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1569 | if (!(kCMYK_ICCTypeFlag & desiredType)) { |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1570 | return_null("Provided input color format (CMYK) does not match profile."); |
| 1571 | } |
| 1572 | |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1573 | desiredType = kCMYK_ICCTypeFlag; |
Matt Sarett | 595599f | 2016-12-15 13:05:53 -0500 | [diff] [blame] | 1574 | break; |
| 1575 | default: |
| 1576 | return_null("ICC profile contains unsupported colorspace"); |
raftias | 5476128 | 2016-12-01 13:44:07 -0500 | [diff] [blame] | 1577 | } |
| 1578 | |
Matt Sarett | 523116d | 2017-01-12 18:36:38 -0500 | [diff] [blame] | 1579 | return make_a2b(desiredType, header, tags.get(), tagCount, base, profileData); |
msarett | 085cad4 | 2016-06-28 07:16:40 -0700 | [diff] [blame] | 1580 | } |