/* * Copyright 2016 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkAutoMalloc.h" #include "SkColorSpace.h" #include "SkColorSpacePriv.h" #include "SkColorSpace_A2B.h" #include "SkColorSpace_XYZ.h" #include "SkEndian.h" #include "SkFixed.h" #include "SkICCPriv.h" #include "SkTemplates.h" #include "../../third_party/skcms/skcms.h" #define return_if_false(pred, msg) \ do { \ if (!(pred)) { \ SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \ return false; \ } \ } while (0) #define return_null(msg) \ do { \ SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \ return nullptr; \ } while (0) static uint16_t read_big_endian_u16(const uint8_t* ptr) { return ptr[0] << 8 | ptr[1]; } static uint32_t read_big_endian_u32(const uint8_t* ptr) { return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3]; } static int32_t read_big_endian_i32(const uint8_t* ptr) { return (int32_t) read_big_endian_u32(ptr); } static constexpr float kWhitePointD50[] = { 0.96420f, 1.00000f, 0.82491f, }; struct ICCProfileHeader { uint32_t fSize; // No reason to care about the preferred color management module (ex: Adobe, Apple, etc.). // We're always going to use this one. uint32_t fCMMType_ignored; uint32_t fVersion; uint32_t fProfileClass; uint32_t fInputColorSpace; uint32_t fPCS; uint32_t fDateTime_ignored[3]; uint32_t fSignature; // Indicates the platform that this profile was created for (ex: Apple, Microsoft). This // doesn't really matter to us. uint32_t fPlatformTarget_ignored; // Flags can indicate: // (1) Whether this profile was embedded in a file. This flag is consistently wrong. // Ex: The profile came from a file but indicates that it did not. // (2) Whether we are allowed to use the profile independently of the color data. If set, // this may allow us to use the embedded profile for testing separate from the original // image. uint32_t fFlags_ignored; // We support many output devices. It doesn't make sense to think about the attributes of // the device in the context of the image profile. uint32_t fDeviceManufacturer_ignored; uint32_t fDeviceModel_ignored; uint32_t fDeviceAttributes_ignored[2]; uint32_t fRenderingIntent; int32_t fIlluminantXYZ[3]; // We don't care who created the profile. uint32_t fCreator_ignored; // This is an MD5 checksum. Could be useful for checking if profiles are equal. uint32_t fProfileId_ignored[4]; // Reserved for future use. uint32_t fReserved_ignored[7]; uint32_t fTagCount; void init(const uint8_t* src, size_t len) { SkASSERT(kICCHeaderSize == sizeof(*this)); uint32_t* dst = (uint32_t*) this; for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) { dst[i] = read_big_endian_u32(src); } } bool valid() const { return_if_false(fSize >= kICCHeaderSize, "Size is too small"); uint8_t majorVersion = fVersion >> 24; return_if_false(majorVersion <= 4, "Unsupported version"); // These are the four basic classes of profiles that we might expect to see embedded // in images. Additional classes exist, but they generally are used as a convenient // way for CMMs to store calculated transforms. return_if_false(fProfileClass == kDisplay_Profile || fProfileClass == kInput_Profile || fProfileClass == kOutput_Profile || fProfileClass == kColorSpace_Profile, "Unsupported profile"); switch (fInputColorSpace) { case kRGB_ColorSpace: SkColorSpacePrintf("RGB Input Color Space"); break; case kCMYK_ColorSpace: SkColorSpacePrintf("CMYK Input Color Space\n"); break; case kGray_ColorSpace: SkColorSpacePrintf("Gray Input Color Space\n"); break; default: SkColorSpacePrintf("Unsupported Input Color Space: %c%c%c%c\n", (fInputColorSpace>>24)&0xFF, (fInputColorSpace>>16)&0xFF, (fInputColorSpace>> 8)&0xFF, (fInputColorSpace>> 0)&0xFF); return false; } switch (fPCS) { case kXYZ_PCSSpace: SkColorSpacePrintf("XYZ PCS\n"); break; case kLAB_PCSSpace: SkColorSpacePrintf("Lab PCS\n"); break; default: // ICC currently (V4.3) only specifices XYZ and Lab PCS spaces SkColorSpacePrintf("Unsupported PCS space: %c%c%c%c\n", (fPCS>>24)&0xFF, (fPCS>>16)&0xFF, (fPCS>> 8)&0xFF, (fPCS>> 0)&0xFF); return false; } return_if_false(fSignature == kACSP_Signature, "Bad signature"); // TODO (msarett): // Should we treat different rendering intents differently? // Valid rendering intents include kPerceptual (0), kRelative (1), // kSaturation (2), and kAbsolute (3). if (fRenderingIntent > 3) { // Warn rather than fail here. Occasionally, we see perfectly // normal profiles with wacky rendering intents. SkColorSpacePrintf("Warning, bad rendering intent.\n"); } return_if_false( color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[0]), kWhitePointD50[0]) && color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[1]), kWhitePointD50[1]) && color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[2]), kWhitePointD50[2]), "Illuminant must be D50"); return_if_false(fTagCount <= 100, "Too many tags"); return true; } }; template static bool safe_add(T arg1, T arg2, size_t* result) { SkASSERT(arg1 >= 0); SkASSERT(arg2 >= 0); if (arg1 >= 0 && arg2 <= std::numeric_limits::max() - arg1) { T sum = arg1 + arg2; if (sum <= std::numeric_limits::max()) { *result = static_cast(sum); return true; } } return false; } static bool safe_mul(uint32_t arg1, uint32_t arg2, uint32_t* result) { uint64_t product64 = (uint64_t) arg1 * (uint64_t) arg2; uint32_t product32 = (uint32_t) product64; if (product32 != product64) { return false; } *result = product32; return true; } struct ICCTag { uint32_t fSignature; uint32_t fOffset; uint32_t fLength; const uint8_t* init(const uint8_t* src) { fSignature = read_big_endian_u32(src); fOffset = read_big_endian_u32(src + 4); fLength = read_big_endian_u32(src + 8); return src + 12; } bool valid(size_t len) { size_t tagEnd; return_if_false(safe_add(fOffset, fLength, &tagEnd), "Tag too large, overflows integer addition"); return_if_false(tagEnd <= len, "Tag too large for ICC profile"); return true; } const uint8_t* addr(const uint8_t* src) const { return src + fOffset; } static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature) { for (int i = 0; i < count; ++i) { if (tags[i].fSignature == signature) { return &tags[i]; } } return nullptr; } }; static bool load_xyz(float dst[3], const uint8_t* src, size_t len) { if (len < 20) { SkColorSpacePrintf("XYZ tag is too small (%d bytes)", len); return false; } dst[0] = SkFixedToFloat(read_big_endian_i32(src + 8)); dst[1] = SkFixedToFloat(read_big_endian_i32(src + 12)); dst[2] = SkFixedToFloat(read_big_endian_i32(src + 16)); SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]); return true; } static SkGammas::Type set_gamma_value(SkGammas::Data* data, float value) { if (color_space_almost_equal(2.2f, value)) { data->fNamed = k2Dot2Curve_SkGammaNamed; return SkGammas::Type::kNamed_Type; } if (color_space_almost_equal(1.0f, value)) { data->fNamed = kLinear_SkGammaNamed; return SkGammas::Type::kNamed_Type; } if (color_space_almost_equal(0.0f, value)) { return SkGammas::Type::kNone_Type; } data->fValue = value; return SkGammas::Type::kValue_Type; } static float read_big_endian_16_dot_16(const uint8_t buf[4]) { // It just so happens that SkFixed is also 16.16! return SkFixedToFloat(read_big_endian_i32(buf)); } /** * @param outData Set to the appropriate value on success. If we have table or * parametric gamma, it is the responsibility of the caller to set * fOffset. * @param outParams If this is a parametric gamma, this is set to the appropriate * parameters on success. * @param outTagBytes Will be set to the length of the tag on success. * @src Pointer to tag data. * @len Length of tag data in bytes. * * @return kNone_Type on failure, otherwise the type of the gamma tag. */ static SkGammas::Type parse_gamma(SkGammas::Data* outData, SkColorSpaceTransferFn* outParams, size_t* outTagBytes, const uint8_t* src, size_t len) { if (len < 12) { SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); return SkGammas::Type::kNone_Type; } // In the case of consecutive gamma tags, we need to count the number of bytes in the // tag, so that we can move on to the next tag. size_t tagBytes; uint32_t type = read_big_endian_u32(src); // Bytes 4-7 are reserved and should be set to zero. switch (type) { case kTAG_CurveType: { uint32_t count = read_big_endian_u32(src + 8); // tagBytes = 12 + 2 * count // We need to do safe addition here to avoid integer overflow. if (!safe_add(count, count, &tagBytes) || !safe_add((size_t) 12, tagBytes, &tagBytes)) { SkColorSpacePrintf("Invalid gamma count"); return SkGammas::Type::kNone_Type; } if (len < tagBytes) { SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); return SkGammas::Type::kNone_Type; } *outTagBytes = tagBytes; if (0 == count) { // Some tags require a gamma curve, but the author doesn't actually want // to transform the data. In this case, it is common to see a curve with // a count of 0. outData->fNamed = kLinear_SkGammaNamed; return SkGammas::Type::kNamed_Type; } const uint16_t* table = (const uint16_t*) (src + 12); if (1 == count) { // The table entry is the gamma (with a bias of 256). float value = (read_big_endian_u16((const uint8_t*) table)) / 256.0f; SkColorSpacePrintf("gamma %g\n", value); return set_gamma_value(outData, value); } // This optimization is especially important for A2B profiles, where we do // not resize tables or interpolate lookups. if (2 == count) { if (0 == read_big_endian_u16((const uint8_t*) &table[0]) && 65535 == read_big_endian_u16((const uint8_t*) &table[1])) { outData->fNamed = kLinear_SkGammaNamed; return SkGammas::Type::kNamed_Type; } } // Check for frequently occurring sRGB curves. // We do this by sampling a few values and see if they match our expectation. // A more robust solution would be to compare each value in this curve against // an sRGB curve to see if we remain below an error threshold. At this time, // we haven't seen any images in the wild that make this kind of // calculation necessary. We encounter identical gamma curves over and // over again, but relatively few variations. if (1024 == count) { // The magic values were chosen because they match both the very common // HP sRGB gamma table and the less common Canon sRGB gamma table (which use // different rounding rules). if (0 == read_big_endian_u16((const uint8_t*) &table[0]) && 3366 == read_big_endian_u16((const uint8_t*) &table[257]) && 14116 == read_big_endian_u16((const uint8_t*) &table[513]) && 34318 == read_big_endian_u16((const uint8_t*) &table[768]) && 65535 == read_big_endian_u16((const uint8_t*) &table[1023])) { outData->fNamed = kSRGB_SkGammaNamed; return SkGammas::Type::kNamed_Type; } } if (26 == count) { // The magic values match a clever "minimum size" approach to representing sRGB. // code.facebook.com/posts/411525055626587/under-the-hood-improving-facebook-photos if (0 == read_big_endian_u16((const uint8_t*) &table[0]) && 3062 == read_big_endian_u16((const uint8_t*) &table[6]) && 12824 == read_big_endian_u16((const uint8_t*) &table[12]) && 31237 == read_big_endian_u16((const uint8_t*) &table[18]) && 65535 == read_big_endian_u16((const uint8_t*) &table[25])) { outData->fNamed = kSRGB_SkGammaNamed; return SkGammas::Type::kNamed_Type; } } if (4096 == count) { // The magic values were chosen because they match Nikon, Epson, and // lcms2 sRGB gamma tables (all of which use different rounding rules). if (0 == read_big_endian_u16((const uint8_t*) &table[0]) && 950 == read_big_endian_u16((const uint8_t*) &table[515]) && 3342 == read_big_endian_u16((const uint8_t*) &table[1025]) && 14079 == read_big_endian_u16((const uint8_t*) &table[2051]) && 65535 == read_big_endian_u16((const uint8_t*) &table[4095])) { outData->fNamed = kSRGB_SkGammaNamed; return SkGammas::Type::kNamed_Type; } } // Otherwise, we will represent gamma with a table. outData->fTable.fSize = count; return SkGammas::Type::kTable_Type; } case kTAG_ParaCurveType: { // Determine the format of the parametric curve tag. uint16_t format = read_big_endian_u16(src + 8); if (format > kGABCDEF_ParaCurveType) { SkColorSpacePrintf("Unsupported gamma tag type %d\n", type); return SkGammas::Type::kNone_Type; } if (kExponential_ParaCurveType == format) { tagBytes = 12 + 4; if (len < tagBytes) { SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); return SkGammas::Type::kNone_Type; } // Y = X^g float g = read_big_endian_16_dot_16(src + 12); *outTagBytes = tagBytes; return set_gamma_value(outData, g); } // Here's where the real parametric gammas start. There are many // permutations of the same equations. // // Y = (aX + b)^g + e for X >= d // Y = cX + f otherwise // // We will fill in with zeros as necessary to always match the above form. if (len < 24) { SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); return SkGammas::Type::kNone_Type; } float g = read_big_endian_16_dot_16(src + 12); float a = read_big_endian_16_dot_16(src + 16); float b = read_big_endian_16_dot_16(src + 20); float c = 0.0f, d = 0.0f, e = 0.0f, f = 0.0f; switch(format) { case kGAB_ParaCurveType: tagBytes = 12 + 12; // Y = (aX + b)^g for X >= -b/a // Y = 0 otherwise d = -b / a; break; case kGABC_ParaCurveType: tagBytes = 12 + 16; if (len < tagBytes) { SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); return SkGammas::Type::kNone_Type; } // Y = (aX + b)^g + e for X >= -b/a // Y = e otherwise e = read_big_endian_16_dot_16(src + 24); d = -b / a; f = e; break; case kGABDE_ParaCurveType: tagBytes = 12 + 20; if (len < tagBytes) { SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); return SkGammas::Type::kNone_Type; } // Y = (aX + b)^g for X >= d // Y = cX otherwise c = read_big_endian_16_dot_16(src + 24); d = read_big_endian_16_dot_16(src + 28); break; case kGABCDEF_ParaCurveType: tagBytes = 12 + 28; if (len < tagBytes) { SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); return SkGammas::Type::kNone_Type; } // Y = (aX + b)^g + e for X >= d // Y = cX + f otherwise c = read_big_endian_16_dot_16(src + 24); d = read_big_endian_16_dot_16(src + 28); e = read_big_endian_16_dot_16(src + 32); f = read_big_endian_16_dot_16(src + 36); break; default: SkASSERT(false); return SkGammas::Type::kNone_Type; } outParams->fG = g; outParams->fA = a; outParams->fB = b; outParams->fC = c; outParams->fD = d; outParams->fE = e; outParams->fF = f; if (!is_valid_transfer_fn(*outParams)) { return SkGammas::Type::kNone_Type; } if (is_almost_srgb(*outParams)) { outData->fNamed = kSRGB_SkGammaNamed; return SkGammas::Type::kNamed_Type; } if (is_almost_2dot2(*outParams)) { outData->fNamed = k2Dot2Curve_SkGammaNamed; return SkGammas::Type::kNamed_Type; } *outTagBytes = tagBytes; return SkGammas::Type::kParam_Type; } default: SkColorSpacePrintf("Unsupported gamma tag type %d\n", type); return SkGammas::Type::kNone_Type; } } /** * Returns the additional size in bytes needed to store the gamma tag. */ static size_t gamma_alloc_size(SkGammas::Type type, const SkGammas::Data& data) { switch (type) { case SkGammas::Type::kNamed_Type: case SkGammas::Type::kValue_Type: return 0; case SkGammas::Type::kTable_Type: return sizeof(float) * data.fTable.fSize; case SkGammas::Type::kParam_Type: return sizeof(SkColorSpaceTransferFn); default: SkASSERT(false); return 0; } } /** * Sets invalid gamma to the default value. */ static void handle_invalid_gamma(SkGammas::Type* type, SkGammas::Data* data) { if (SkGammas::Type::kNone_Type == *type) { *type = SkGammas::Type::kNamed_Type; // Guess sRGB in the case of a malformed transfer function. data->fNamed = kSRGB_SkGammaNamed; } } /** * Finish loading the gammas, now that we have allocated memory for the SkGammas struct. * * There's nothing to do for the simple cases, but for table gammas we need to actually * read the table into heap memory. And for parametric gammas, we need to copy over the * parameter values. * * @param memory Pointer to start of the SkGammas memory block * @param offset Bytes of memory (after the SkGammas struct) that are already in use. * @param data In-out variable. Will fill in the offset to the table or parameters * if necessary. * @param params Parameters for gamma curve. Only initialized/used when we have a * parametric gamma. * @param src Pointer to start of the gamma tag. * * @return Additional bytes of memory that are being used by this gamma curve. */ static size_t load_gammas(void* memory, size_t offset, SkGammas::Type type, SkGammas::Data* data, const SkColorSpaceTransferFn& params, const uint8_t* src) { void* storage = SkTAddOffset(memory, offset + sizeof(SkGammas)); switch (type) { case SkGammas::Type::kNamed_Type: case SkGammas::Type::kValue_Type: // Nothing to do here. return 0; case SkGammas::Type::kTable_Type: { data->fTable.fOffset = offset; float* outTable = (float*) storage; const uint16_t* inTable = (const uint16_t*) (src + 12); for (int i = 0; i < data->fTable.fSize; i++) { outTable[i] = (read_big_endian_u16((const uint8_t*) &inTable[i])) / 65535.0f; } return sizeof(float) * data->fTable.fSize; } case SkGammas::Type::kParam_Type: data->fTable.fOffset = offset; memcpy(storage, ¶ms, sizeof(SkColorSpaceTransferFn)); return sizeof(SkColorSpaceTransferFn); default: SkASSERT(false); return 0; } } static constexpr uint32_t kTAG_AtoBType = SkSetFourByteTag('m', 'A', 'B', ' '); static constexpr uint32_t kTAG_lut8Type = SkSetFourByteTag('m', 'f', 't', '1'); static constexpr uint32_t kTAG_lut16Type = SkSetFourByteTag('m', 'f', 't', '2'); static bool load_color_lut(sk_sp* colorLUT, uint32_t inputChannels, size_t precision, const uint8_t gridPoints[3], const uint8_t* src, size_t len) { switch (precision) { case 1: // 8-bit data case 2: // 16-bit data break; default: SkColorSpacePrintf("Color LUT precision must be 8-bit or 16-bit. Found: %d-bit\n", 8*precision); return false; } uint32_t numEntries = SkColorLookUpTable::kOutputChannels; for (uint32_t i = 0; i < inputChannels; i++) { if (1 >= gridPoints[i]) { SkColorSpacePrintf("Each input channel must have at least two grid points."); return false; } if (!safe_mul(numEntries, gridPoints[i], &numEntries)) { SkColorSpacePrintf("Too many entries in Color LUT."); return false; } } uint32_t clutBytes; if (!safe_mul(numEntries, precision, &clutBytes)) { SkColorSpacePrintf("Too many entries in Color LUT.\n"); return false; } if (len < clutBytes) { SkColorSpacePrintf("Color LUT tag is too small (%d / %d bytes).\n", len, clutBytes); return false; } // Movable struct colorLUT has ownership of fTable. void* memory = sk_malloc_throw(sizeof(SkColorLookUpTable) + sizeof(float) * numEntries); *colorLUT = sk_sp(new (memory) SkColorLookUpTable(inputChannels, gridPoints)); float* table = SkTAddOffset(memory, sizeof(SkColorLookUpTable)); const uint8_t* ptr = src; for (uint32_t i = 0; i < numEntries; i++, ptr += precision) { if (1 == precision) { table[i] = ((float) *ptr) / 255.0f; } else { table[i] = ((float) read_big_endian_u16(ptr)) / 65535.0f; } } return true; } /** * Reads a matrix out of an A2B tag of an ICC profile. * If |translate| is true, it will load a 3x4 matrix out that corresponds to a XYZ * transform as well as a translation, and if |translate| is false it only loads a * 3x3 matrix with no translation * * @param matrix The matrix to store the result in * @param src Data to load the matrix out of. * @param len The length of |src|. * Must have 48 bytes if |translate| is set and 36 bytes otherwise. * @param translate Whether to read the translation column or not * @param pcs The profile connection space of the profile this matrix is for * * @return false on failure, true on success */ static bool load_matrix(SkMatrix44* matrix, const uint8_t* src, size_t len, bool translate, SkColorSpace_A2B::PCS pcs) { const size_t minLen = translate ? 48 : 36; if (len < minLen) { SkColorSpacePrintf("Matrix tag is too small (%d bytes).", len); return false; } float encodingFactor; switch (pcs) { case SkColorSpace_A2B::PCS::kLAB: encodingFactor = 1.f; break; case SkColorSpace_A2B::PCS::kXYZ: encodingFactor = 65535 / 32768.f; break; default: encodingFactor = 1.f; SkASSERT(false); break; } float array[16]; array[ 0] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src)); array[ 1] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 4)); array[ 2] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 8)); array[ 4] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 12)); array[ 5] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 16)); array[ 6] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 20)); array[ 8] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 24)); array[ 9] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 28)); array[10] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 32)); if (translate) { array[ 3] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 36)); // translate R array[ 7] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 40)); // translate G array[11] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 44)); // translate B } else { array[ 3] = 0.0f; array[ 7] = 0.0f; array[11] = 0.0f; } array[12] = 0.0f; array[13] = 0.0f; array[14] = 0.0f; array[15] = 1.0f; matrix->setRowMajorf(array); SkColorSpacePrintf("A2B0 matrix loaded:\n"); for (int r = 0; r < 4; ++r) { SkColorSpacePrintf("|"); for (int c = 0; c < 4; ++c) { SkColorSpacePrintf(" %f ", matrix->get(r, c)); } SkColorSpacePrintf("|\n"); } return true; } static inline SkGammaNamed is_named(const sk_sp& gammas) { for (uint8_t i = 0; i < gammas->channels(); ++i) { if (!gammas->isNamed(i) || gammas->data(i).fNamed != gammas->data(0).fNamed) { return kNonStandard_SkGammaNamed; } } return gammas->data(0).fNamed; } /** * Parse and load an entire stored curve. Handles invalid gammas as well. * * There's nothing to do for the simple cases, but for table gammas we need to actually * read the table into heap memory. And for parametric gammas, we need to copy over the * parameter values. * * @param gammaNamed Out-variable. The named gamma curve. * @param gammas Out-variable. The stored gamma curve information. Can be null if * gammaNamed is a named curve * @param inputChannels The number of gamma input channels * @param rTagPtr Pointer to start of the gamma tag. * @param taglen The size in bytes of the tag * * @return false on failure, true on success */ static bool parse_and_load_gamma(SkGammaNamed* gammaNamed, sk_sp* gammas, uint8_t inputChannels, const uint8_t* tagSrc, size_t tagLen) { SkGammas::Data data[kMaxColorChannels]; SkColorSpaceTransferFn params[kMaxColorChannels]; SkGammas::Type type[kMaxColorChannels]; const uint8_t* tagPtr[kMaxColorChannels]; tagPtr[0] = tagSrc; *gammaNamed = kNonStandard_SkGammaNamed; // On an invalid first gamma, tagBytes remains set as zero. This causes the two // subsequent to be treated as identical (which is what we want). size_t tagBytes = 0; type[0] = parse_gamma(&data[0], ¶ms[0], &tagBytes, tagPtr[0], tagLen); handle_invalid_gamma(&type[0], &data[0]); size_t alignedTagBytes = SkAlign4(tagBytes); bool allChannelsSame = false; if (inputChannels * alignedTagBytes <= tagLen) { allChannelsSame = true; for (uint8_t i = 1; i < inputChannels; ++i) { if (0 != memcmp(tagSrc, tagSrc + i * alignedTagBytes, tagBytes)) { allChannelsSame = false; break; } } } if (allChannelsSame) { if (SkGammas::Type::kNamed_Type == type[0]) { *gammaNamed = data[0].fNamed; } else { size_t allocSize = sizeof(SkGammas); return_if_false(safe_add(allocSize, gamma_alloc_size(type[0], data[0]), &allocSize), "SkGammas struct is too large to allocate"); void* memory = sk_malloc_throw(allocSize); *gammas = sk_sp(new (memory) SkGammas(inputChannels)); load_gammas(memory, 0, type[0], &data[0], params[0], tagPtr[0]); for (uint8_t channel = 0; channel < inputChannels; ++channel) { (*gammas)->fType[channel] = type[0]; (*gammas)->fData[channel] = data[0]; } } } else { for (uint8_t channel = 1; channel < inputChannels; ++channel) { tagPtr[channel] = tagPtr[channel - 1] + alignedTagBytes; tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0; tagBytes = 0; type[channel] = parse_gamma(&data[channel], ¶ms[channel], &tagBytes, tagPtr[channel], tagLen); handle_invalid_gamma(&type[channel], &data[channel]); alignedTagBytes = SkAlign4(tagBytes); } size_t allocSize = sizeof(SkGammas); for (uint8_t channel = 0; channel < inputChannels; ++channel) { return_if_false(safe_add(allocSize, gamma_alloc_size(type[channel], data[channel]), &allocSize), "SkGammas struct is too large to allocate"); } void* memory = sk_malloc_throw(allocSize); *gammas = sk_sp(new (memory) SkGammas(inputChannels)); uint32_t offset = 0; for (uint8_t channel = 0; channel < inputChannels; ++channel) { (*gammas)->fType[channel] = type[channel]; offset += load_gammas(memory,offset, type[channel], &data[channel], params[channel], tagPtr[channel]); (*gammas)->fData[channel] = data[channel]; } } if (kNonStandard_SkGammaNamed == *gammaNamed) { *gammaNamed = is_named(*gammas); if (kNonStandard_SkGammaNamed != *gammaNamed) { // No need to keep the gammas struct, the enum is enough. *gammas = nullptr; } } return true; } static bool is_lut_gamma_linear(const uint8_t* src, size_t count, size_t precision) { // check for linear gamma (this is very common in lut gammas, as they aren't optional) const float normalizeX = 1.f / (count - 1); for (uint32_t x = 0; x < count; ++x) { const float y = precision == 1 ? (src[x] / 255.f) : (read_big_endian_u16(src + 2*x) / 65535.f); if (!color_space_almost_equal(x * normalizeX, y)) { return false; } } return true; } static bool load_lut_gammas(sk_sp* gammas, SkGammaNamed* gammaNamed, size_t numTables, size_t entriesPerTable, size_t precision, const uint8_t* src, size_t len) { if (precision != 1 && precision != 2) { SkColorSpacePrintf("Invalid gamma table precision %d\n", precision); return false; } uint32_t totalEntries; return_if_false(safe_mul(entriesPerTable, numTables, &totalEntries), "Too many entries in gamma table."); uint32_t readBytes; return_if_false(safe_mul(precision, totalEntries, &readBytes), "SkGammas struct is too large to read"); if (len < readBytes) { SkColorSpacePrintf("Gamma table is too small. Provided: %d. Required: %d\n", len, readBytes); return false; } uint32_t writeBytesPerChannel; return_if_false(safe_mul(sizeof(float), entriesPerTable, &writeBytesPerChannel), "SkGammas struct is too large to allocate"); const size_t readBytesPerChannel = precision * entriesPerTable; size_t numTablesToUse = 1; for (size_t tableIndex = 1; tableIndex < numTables; ++tableIndex) { if (0 != memcmp(src, src + readBytesPerChannel * tableIndex, readBytesPerChannel)) { numTablesToUse = numTables; break; } } if (1 == numTablesToUse) { if (is_lut_gamma_linear(src, entriesPerTable, precision)) { *gammaNamed = kLinear_SkGammaNamed; return true; } } *gammaNamed = kNonStandard_SkGammaNamed; uint32_t writetableBytes; return_if_false(safe_mul(numTablesToUse, writeBytesPerChannel, &writetableBytes), "SkGammas struct is too large to allocate"); size_t allocSize = sizeof(SkGammas); return_if_false(safe_add(allocSize, (size_t)writetableBytes, &allocSize), "SkGammas struct is too large to allocate"); void* memory = sk_malloc_throw(allocSize); *gammas = sk_sp(new (memory) SkGammas(numTables)); for (size_t tableIndex = 0; tableIndex < numTablesToUse; ++tableIndex) { const uint8_t* ptr = src + readBytesPerChannel * tableIndex; const size_t offset = sizeof(SkGammas) + tableIndex * writeBytesPerChannel; float* table = SkTAddOffset(memory, offset); if (1 == precision) { for (uint32_t i = 0; i < entriesPerTable; ++i, ptr += 1) { table[i] = ((float) *ptr) / 255.0f; } } else if (2 == precision) { for (uint32_t i = 0; i < entriesPerTable; ++i, ptr += 2) { table[i] = ((float) read_big_endian_u16(ptr)) / 65535.0f; } } } SkASSERT(1 == numTablesToUse|| numTables == numTablesToUse); size_t tableOffset = 0; for (size_t tableIndex = 0; tableIndex < numTables; ++tableIndex) { (*gammas)->fType[tableIndex] = SkGammas::Type::kTable_Type; (*gammas)->fData[tableIndex].fTable.fOffset = tableOffset; (*gammas)->fData[tableIndex].fTable.fSize = entriesPerTable; if (numTablesToUse > 1) { tableOffset += writeBytesPerChannel; } } return true; } bool load_a2b0_a_to_b_type(std::vector* elements, const uint8_t* src, size_t len, SkColorSpace_A2B::PCS pcs) { SkASSERT(len >= 32); // Read the number of channels. The four bytes (4-7) that we skipped are reserved and // must be zero. const uint8_t inputChannels = src[8]; const uint8_t outputChannels = src[9]; if (SkColorLookUpTable::kOutputChannels != outputChannels) { // We only handle RGB outputs. The number of output channels must be 3. SkColorSpacePrintf("Output channels (%d) must equal 3 in A to B tag.\n", outputChannels); return false; } if (inputChannels == 0 || inputChannels > 4) { // And we only support 4 input channels. // ICC says up to 16 but our decode can only handle 4. // It could easily be extended to support up to 8, but we only allow CMYK/RGB // input color spaces which are 3 and 4 so let's restrict it to 4 instead of 8. // We can always change this check when we support bigger input spaces. SkColorSpacePrintf("Input channels (%d) must be between 1 and 4 in A to B tag.\n", inputChannels); return false; } // It is important that these are loaded in the order of application, as the // order you construct an A2B color space's elements is the order it is applied // If the offset is non-zero it indicates that the element is present. const uint32_t offsetToACurves = read_big_endian_i32(src + 28); if (0 != offsetToACurves && offsetToACurves < len) { const size_t tagLen = len - offsetToACurves; SkGammaNamed gammaNamed; sk_sp gammas; if (!parse_and_load_gamma(&gammaNamed, &gammas, inputChannels, src + offsetToACurves, tagLen)) { return false; } if (gammas) { elements->push_back(SkColorSpace_A2B::Element(std::move(gammas))); } else if (kLinear_SkGammaNamed != gammaNamed) { elements->push_back(SkColorSpace_A2B::Element(gammaNamed, inputChannels)); } } const uint32_t offsetToColorLUT = read_big_endian_i32(src + 24); if (0 != offsetToColorLUT && offsetToColorLUT < len) { sk_sp colorLUT; const uint8_t* clutSrc = src + offsetToColorLUT; const size_t clutLen = len - offsetToColorLUT; // 16 bytes reserved for grid points, 1 for precision, 3 for padding. // The color LUT data follows after this header. static constexpr uint32_t kColorLUTHeaderSize = 20; if (clutLen < kColorLUTHeaderSize) { SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", clutLen); return false; } SkASSERT(inputChannels <= kMaxColorChannels); uint8_t gridPoints[kMaxColorChannels]; for (uint32_t i = 0; i < inputChannels; ++i) { gridPoints[i] = clutSrc[i]; } // Space is provided for a maximum of 16 input channels. // Now we determine the precision of the table values. const uint8_t precision = clutSrc[16]; if (!load_color_lut(&colorLUT, inputChannels, precision, gridPoints, clutSrc + kColorLUTHeaderSize, clutLen - kColorLUTHeaderSize)) { SkColorSpacePrintf("Failed to read color LUT from A to B tag.\n"); return false; } elements->push_back(SkColorSpace_A2B::Element(std::move(colorLUT))); } const uint32_t offsetToMCurves = read_big_endian_i32(src + 20); if (0 != offsetToMCurves && offsetToMCurves < len) { const size_t tagLen = len - offsetToMCurves; SkGammaNamed gammaNamed; sk_sp gammas; if (!parse_and_load_gamma(&gammaNamed, &gammas, outputChannels, src + offsetToMCurves, tagLen)) { return false; } if (gammas) { elements->push_back(SkColorSpace_A2B::Element(std::move(gammas))); } else if (kLinear_SkGammaNamed != gammaNamed) { elements->push_back(SkColorSpace_A2B::Element(gammaNamed, outputChannels)); } } const uint32_t offsetToMatrix = read_big_endian_i32(src + 16); if (0 != offsetToMatrix && offsetToMatrix < len) { SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor); if (!load_matrix(&matrix, src + offsetToMatrix, len - offsetToMatrix, true, pcs)) { SkColorSpacePrintf("Failed to read matrix from A to B tag.\n"); } else if (!matrix.isIdentity()) { elements->push_back(SkColorSpace_A2B::Element(matrix)); } } const uint32_t offsetToBCurves = read_big_endian_i32(src + 12); if (0 != offsetToBCurves && offsetToBCurves < len) { const size_t tagLen = len - offsetToBCurves; SkGammaNamed gammaNamed; sk_sp gammas; if (!parse_and_load_gamma(&gammaNamed, &gammas, outputChannels, src + offsetToBCurves, tagLen)) { return false; } if (gammas) { elements->push_back(SkColorSpace_A2B::Element(std::move(gammas))); } else if (kLinear_SkGammaNamed != gammaNamed) { elements->push_back(SkColorSpace_A2B::Element(gammaNamed, outputChannels)); } } return true; } bool load_a2b0_lutn_type(std::vector* elements, const uint8_t* src, size_t len, SkColorSpace_A2B::PCS pcs) { const uint32_t type = read_big_endian_u32(src); switch (type) { case kTAG_lut8Type: SkASSERT(len >= 48); break; case kTAG_lut16Type: SkASSERT(len >= 52); break; default: SkASSERT(false); return false; } // Read the number of channels. // The four bytes (4-7) that we skipped are reserved and must be zero. const uint8_t inputChannels = src[8]; const uint8_t outputChannels = src[9]; if (SkColorLookUpTable::kOutputChannels != outputChannels) { // We only handle RGB outputs. The number of output channels must be 3. SkColorSpacePrintf("Output channels (%d) must equal 3 in A to B tag.\n", outputChannels); return false; } if (inputChannels == 0 || inputChannels > 4) { // And we only support 4 input channels. // ICC says up to 16 but our decode can only handle 4. // It could easily be extended to support up to 8, but we only allow CMYK/RGB // input color spaces which are 3 and 4 so let's restrict it to 4 instead of 8. // We can always change this check when we support bigger input spaces. SkColorSpacePrintf("Input channels (%d) must be between 1 and 4 in A to B tag.\n", inputChannels); return false; } const uint8_t clutGridPoints = src[10]; // 11th byte reserved for padding (required to be zero) SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor); load_matrix(&matrix, &src[12], len - 12, false, pcs); if (!matrix.isIdentity()) { // ICC specs (10.8/10.9) say lut8/16Type profiles must have identity matrices // if the input color space is not PCSXYZ, and we do not support PCSXYZ input color spaces // so we should never encounter a non-identity matrix here. // However, 2 test images from the ICC website have RGB input spaces and non-identity // matrices so we're not going to fail here, despite being against the spec. SkColorSpacePrintf("Warning: non-Identity matrix found in non-XYZ input color space" "lut profile"); elements->push_back(SkColorSpace_A2B::Element(matrix)); } size_t dataOffset = 48; // # of input table entries size_t inTableEntries = 256; // # of output table entries size_t outTableEntries = 256; size_t precision = 1; if (kTAG_lut16Type == type) { dataOffset = 52; inTableEntries = read_big_endian_u16(src + 48); outTableEntries = read_big_endian_u16(src + 50); precision = 2; constexpr size_t kMaxLut16GammaEntries = 4096; if (inTableEntries < 2) { SkColorSpacePrintf("Too few (%d) input gamma table entries. Must have at least 2.\n", inTableEntries); return false; } else if (inTableEntries > kMaxLut16GammaEntries) { SkColorSpacePrintf("Too many (%d) input gamma table entries. Must have at most %d.\n", inTableEntries, kMaxLut16GammaEntries); return false; } if (outTableEntries < 2) { SkColorSpacePrintf("Too few (%d) output gamma table entries. Must have at least 2.\n", outTableEntries); return false; } else if (outTableEntries > kMaxLut16GammaEntries) { SkColorSpacePrintf("Too many (%d) output gamma table entries. Must have at most %d.\n", outTableEntries, kMaxLut16GammaEntries); return false; } } const size_t inputOffset = dataOffset; return_if_false(len >= inputOffset, "A2B0 lutnType tag too small for input gamma table"); sk_sp inputGammas; SkGammaNamed inputGammaNamed; if (!load_lut_gammas(&inputGammas, &inputGammaNamed, inputChannels, inTableEntries, precision, src + inputOffset, len - inputOffset)) { SkColorSpacePrintf("Failed to read input gammas from lutnType tag.\n"); return false; } SkASSERT(inputGammas || inputGammaNamed != kNonStandard_SkGammaNamed); if (kLinear_SkGammaNamed != inputGammaNamed) { if (kNonStandard_SkGammaNamed != inputGammaNamed) { elements->push_back(SkColorSpace_A2B::Element(inputGammaNamed, inputChannels)); } else { elements->push_back(SkColorSpace_A2B::Element(std::move(inputGammas))); } } const size_t clutOffset = inputOffset + precision*inTableEntries*inputChannels; return_if_false(len >= clutOffset, "A2B0 lutnType tag too small for CLUT"); sk_sp colorLUT; const uint8_t gridPoints[kMaxColorChannels] = { clutGridPoints, clutGridPoints, clutGridPoints, clutGridPoints }; if (!load_color_lut(&colorLUT, inputChannels, precision, gridPoints, src + clutOffset, len - clutOffset)) { SkColorSpacePrintf("Failed to read color LUT from lutnType tag.\n"); return false; } SkASSERT(colorLUT); elements->push_back(SkColorSpace_A2B::Element(std::move(colorLUT))); size_t clutSize = precision * outputChannels; for (int i = 0; i < inputChannels; ++i) { clutSize *= clutGridPoints; } const size_t outputOffset = clutOffset + clutSize; return_if_false(len >= outputOffset, "A2B0 lutnType tag too small for output gamma table"); sk_sp outputGammas; SkGammaNamed outputGammaNamed; if (!load_lut_gammas(&outputGammas, &outputGammaNamed, outputChannels, outTableEntries, precision, src + outputOffset, len - outputOffset)) { SkColorSpacePrintf("Failed to read output gammas from lutnType tag.\n"); return false; } SkASSERT(outputGammas || outputGammaNamed != kNonStandard_SkGammaNamed); if (kLinear_SkGammaNamed != outputGammaNamed) { if (kNonStandard_SkGammaNamed != outputGammaNamed) { elements->push_back(SkColorSpace_A2B::Element(outputGammaNamed, outputChannels)); } else { elements->push_back(SkColorSpace_A2B::Element(std::move(outputGammas))); } } return true; } static inline int icf_channels(SkColorSpace::Type iccType) { switch (iccType) { case SkColorSpace::kRGB_Type: return 3; case SkColorSpace::kCMYK_Type: return 4; default: SkASSERT(false); return 0; } } static bool load_a2b0(std::vector* elements, const uint8_t* src, size_t len, SkColorSpace_A2B::PCS pcs, SkColorSpace::Type iccType) { if (len < 4) { return false; } const uint32_t type = read_big_endian_u32(src); switch (type) { case kTAG_AtoBType: if (len < 32) { SkColorSpacePrintf("A to B tag is too small (%d bytes).", len); return false; } SkColorSpacePrintf("A2B0 tag is of type lutAtoBType\n"); if (!load_a2b0_a_to_b_type(elements, src, len, pcs)) { return false; } break; case kTAG_lut8Type: if (len < 48) { SkColorSpacePrintf("lut8 tag is too small (%d bytes).", len); return false; } SkColorSpacePrintf("A2B0 tag of type lut8Type\n"); if (!load_a2b0_lutn_type(elements, src, len, pcs)) { return false; } break; case kTAG_lut16Type: if (len < 52) { SkColorSpacePrintf("lut16 tag is too small (%d bytes).", len); return false; } SkColorSpacePrintf("A2B0 tag of type lut16Type\n"); if (!load_a2b0_lutn_type(elements, src, len, pcs)) { return false; } break; default: SkColorSpacePrintf("Unsupported A to B tag type: %c%c%c%c\n", (type>>24)&0xFF, (type>>16)&0xFF, (type>>8)&0xFF, type&0xFF); return false; } SkASSERT(SkColorSpace_A2B::PCS::kLAB == pcs || SkColorSpace_A2B::PCS::kXYZ == pcs); static constexpr int kPCSChannels = 3; // must be PCSLAB or PCSXYZ if (elements->empty()) { return kPCSChannels == icf_channels(iccType); } // now let's verify that the input/output channels of each A2B element actually match up if (icf_channels(iccType) != elements->front().inputChannels()) { SkColorSpacePrintf("Input channel count does not match first A2B element's input count"); return false; } for (size_t i = 1; i < elements->size(); ++i) { if ((*elements)[i - 1].outputChannels() != (*elements)[i].inputChannels()) { SkColorSpacePrintf("A2B elements don't agree in input/output channel counts"); return false; } } if (kPCSChannels != elements->back().outputChannels()) { SkColorSpacePrintf("PCS channel count doesn't match last A2B element's output count"); return false; } return true; } static bool tag_equals(const ICCTag* a, const ICCTag* b, const uint8_t* base) { if (!a || !b) { return a == b; } if (a->fLength != b->fLength) { return false; } if (a->fOffset == b->fOffset) { return true; } return !memcmp(a->addr(base), b->addr(base), a->fLength); } static inline bool is_close_to_d50(const SkMatrix44& matrix) { // rX + gX + bX float X = matrix.getFloat(0, 0) + matrix.getFloat(0, 1) + matrix.getFloat(0, 2); // rY + gY + bY float Y = matrix.getFloat(1, 0) + matrix.getFloat(1, 1) + matrix.getFloat(1, 2); // rZ + gZ + bZ float Z = matrix.getFloat(2, 0) + matrix.getFloat(2, 1) + matrix.getFloat(2, 2); static const float kD50_WhitePoint[3] = { 0.96420f, 1.00000f, 0.82491f }; // This is a bit more lenient than QCMS and Adobe. Is there a reason to be stricter here? return (SkTAbs(X - kD50_WhitePoint[0]) <= 0.04f) && (SkTAbs(Y - kD50_WhitePoint[1]) <= 0.04f) && (SkTAbs(Z - kD50_WhitePoint[2]) <= 0.04f); } static sk_sp make_xyz(const ICCProfileHeader& header, ICCTag* tags, int tagCount, const uint8_t* base, sk_sp profileData) { if (kLAB_PCSSpace == header.fPCS) { return nullptr; } // Recognize the rXYZ, gXYZ, and bXYZ tags. const ICCTag* r = ICCTag::Find(tags, tagCount, kTAG_rXYZ); const ICCTag* g = ICCTag::Find(tags, tagCount, kTAG_gXYZ); const ICCTag* b = ICCTag::Find(tags, tagCount, kTAG_bXYZ); if (!r || !g || !b) { return nullptr; } float toXYZ[9]; if (!load_xyz(&toXYZ[0], r->addr(base), r->fLength) || !load_xyz(&toXYZ[3], g->addr(base), g->fLength) || !load_xyz(&toXYZ[6], b->addr(base), b->fLength)) { return_null("Need valid rgb tags for XYZ space"); } SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor); mat.set3x3(toXYZ[0], toXYZ[1], toXYZ[2], toXYZ[3], toXYZ[4], toXYZ[5], toXYZ[6], toXYZ[7], toXYZ[8]); if (!is_close_to_d50(mat)) { return_null("XYZ matrix is not D50"); } // If some, but not all, of the gamma tags are missing, assume that all // gammas are meant to be the same. r = ICCTag::Find(tags, tagCount, kTAG_rTRC); g = ICCTag::Find(tags, tagCount, kTAG_gTRC); b = ICCTag::Find(tags, tagCount, kTAG_bTRC); if (!r || !g || !b) { return_null("Need valid TRC tags for XYZ space"); } SkGammaNamed gammaNamed = kNonStandard_SkGammaNamed; sk_sp gammas = nullptr; size_t tagBytes; if (tag_equals(r, g, base) && tag_equals(g, b, base)) { SkGammas::Data data; SkColorSpaceTransferFn params; SkGammas::Type type = parse_gamma(&data, ¶ms, &tagBytes, r->addr(base), r->fLength); handle_invalid_gamma(&type, &data); if (SkGammas::Type::kNamed_Type == type) { gammaNamed = data.fNamed; } else { size_t allocSize = sizeof(SkGammas); if (!safe_add(allocSize, gamma_alloc_size(type, data), &allocSize)) { return_null("SkGammas struct is too large to allocate"); } void* memory = sk_malloc_throw(allocSize); gammas = sk_sp(new (memory) SkGammas(3)); load_gammas(memory, 0, type, &data, params, r->addr(base)); for (int i = 0; i < 3; ++i) { gammas->fType[i] = type; gammas->fData[i] = data; } } } else { SkGammas::Data rData; SkColorSpaceTransferFn rParams; SkGammas::Type rType = parse_gamma(&rData, &rParams, &tagBytes, r->addr(base), r->fLength); handle_invalid_gamma(&rType, &rData); SkGammas::Data gData; SkColorSpaceTransferFn gParams; SkGammas::Type gType = parse_gamma(&gData, &gParams, &tagBytes, g->addr(base), g->fLength); handle_invalid_gamma(&gType, &gData); SkGammas::Data bData; SkColorSpaceTransferFn bParams; SkGammas::Type bType = parse_gamma(&bData, &bParams, &tagBytes, b->addr(base), b->fLength); handle_invalid_gamma(&bType, &bData); size_t allocSize = sizeof(SkGammas); if (!safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize) || !safe_add(allocSize, gamma_alloc_size(gType, gData), &allocSize) || !safe_add(allocSize, gamma_alloc_size(bType, bData), &allocSize)) { return_null("SkGammas struct is too large to allocate"); } void* memory = sk_malloc_throw(allocSize); gammas = sk_sp(new (memory) SkGammas(3)); uint32_t offset = 0; gammas->fType[0] = rType; offset += load_gammas(memory, offset, rType, &rData, rParams, r->addr(base)); gammas->fType[1] = gType; offset += load_gammas(memory, offset, gType, &gData, gParams, g->addr(base)); gammas->fType[2] = bType; load_gammas(memory, offset, bType, &bData, bParams, b->addr(base)); gammas->fData[0] = rData; gammas->fData[1] = gData; gammas->fData[2] = bData; } if (kNonStandard_SkGammaNamed == gammaNamed) { // It's possible that we'll initially detect non-matching gammas, only for // them to evaluate to the same named gamma curve. gammaNamed = is_named(gammas); } if (kNonStandard_SkGammaNamed == gammaNamed) { return sk_sp(new SkColorSpace_XYZ(gammaNamed, std::move(gammas), mat, std::move(profileData))); } return SkColorSpace::MakeRGB(gammaNamed, mat); } static sk_sp make_gray(const ICCProfileHeader& header, ICCTag* tags, int tagCount, const uint8_t* base, sk_sp profileData) { if (kLAB_PCSSpace == header.fPCS) { return nullptr; } const ICCTag* grayTRC = ICCTag::Find(tags, tagCount, kTAG_kTRC); if (!grayTRC) { return_null("grayTRC tag required for monochrome profiles."); } SkGammas::Data data; SkColorSpaceTransferFn params; size_t tagBytes; SkGammas::Type type = parse_gamma(&data, ¶ms, &tagBytes, grayTRC->addr(base), grayTRC->fLength); handle_invalid_gamma(&type, &data); SkMatrix44 toXYZD50(SkMatrix44::kIdentity_Constructor); toXYZD50.setFloat(0, 0, kWhitePointD50[0]); toXYZD50.setFloat(1, 1, kWhitePointD50[1]); toXYZD50.setFloat(2, 2, kWhitePointD50[2]); if (SkGammas::Type::kNamed_Type == type) { return SkColorSpace::MakeRGB(data.fNamed, toXYZD50); } size_t allocSize = sizeof(SkGammas); if (!safe_add(allocSize, gamma_alloc_size(type, data), &allocSize)) { return_null("SkGammas struct is too large to allocate"); } void* memory = sk_malloc_throw(allocSize); sk_sp gammas = sk_sp(new (memory) SkGammas(3)); load_gammas(memory, 0, type, &data, params, grayTRC->addr(base)); for (int i = 0; i < 3; ++i) { gammas->fType[i] = type; gammas->fData[i] = data; } return sk_sp(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed, std::move(gammas), toXYZD50, std::move(profileData))); } static sk_sp make_a2b(SkColorSpace::Type iccType, const ICCProfileHeader& header, ICCTag* tags, int tagCount, const uint8_t* base, sk_sp profileData) { const ICCTag* a2b0 = ICCTag::Find(tags, tagCount, kTAG_A2B0); if (a2b0) { const SkColorSpace_A2B::PCS pcs = kXYZ_PCSSpace == header.fPCS ? SkColorSpace_A2B::PCS::kXYZ : SkColorSpace_A2B::PCS::kLAB; std::vector elements; if (load_a2b0(&elements, a2b0->addr(base), a2b0->fLength, pcs, iccType)) { return sk_sp(new SkColorSpace_A2B(iccType, std::move(elements), pcs, std::move(profileData))); } } return nullptr; } sk_sp SkColorSpace::MakeICC(const void* input, size_t len) { if (!input || len < kICCHeaderSize) { return_null("Data is null or not large enough to contain an ICC profile"); } // Make sure we're at least as strict as skcms_Parse(). skcms_ICCProfile p; if (!skcms_Parse(input, len, &p)) { return nullptr; } // Create our own copy of the input. void* memory = sk_malloc_throw(len); memcpy(memory, input, len); sk_sp profileData = SkData::MakeFromMalloc(memory, len); const uint8_t* base = profileData->bytes(); const uint8_t* ptr = base; // Read the ICC profile header and check to make sure that it is valid. ICCProfileHeader header; header.init(ptr, len); if (!header.valid()) { return nullptr; } // Adjust ptr and len before reading the tags. if (len < header.fSize) { SkColorSpacePrintf("ICC profile might be truncated.\n"); } else if (len > header.fSize) { SkColorSpacePrintf("Caller provided extra data beyond the end of the ICC profile.\n"); len = header.fSize; } ptr += kICCHeaderSize; len -= kICCHeaderSize; // Parse tag headers. uint32_t tagCount = header.fTagCount; SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount); if (len < kICCTagTableEntrySize * tagCount) { return_null("Not enough input data to read tag table entries"); } SkAutoTArray tags(tagCount); for (uint32_t i = 0; i < tagCount; i++) { ptr = tags[i].init(ptr); SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24) & 0xFF, (tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >> 8) & 0xFF, (tags[i].fSignature >> 0) & 0xFF, tags[i].fOffset, tags[i].fLength); if (!tags[i].valid(kICCHeaderSize + len)) { return_null("Tag is too large to fit in ICC profile"); } } Type a2b_type = kRGB_Type; switch (header.fInputColorSpace) { case kRGB_ColorSpace: { sk_sp colorSpace = make_xyz(header, tags.get(), tagCount, base, profileData); if (colorSpace) { return colorSpace; } break; } case kGray_ColorSpace: { return make_gray(header, tags.get(), tagCount, base, profileData); } case kCMYK_ColorSpace: a2b_type = kCMYK_Type; break; default: return_null("ICC profile contains unsupported colorspace"); } return make_a2b(a2b_type, header, tags.get(), tagCount, base, profileData); }