| Index: src/core/SkColorSpace.cpp
|
| diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
|
| index 6651abb3fca864086418a36be5bfb066e42dd3b0..b852d2dca2c179f09c5147d2fbfddb1e3c5f4d16 100644
|
| --- a/src/core/SkColorSpace.cpp
|
| +++ b/src/core/SkColorSpace.cpp
|
| @@ -158,6 +158,310 @@ SkColorSpace* SkColorSpace::NewNamed(Named named) {
|
|
|
| ///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
| +#include "SkFixed.h"
|
| +#include "SkTemplates.h"
|
| +
|
| +#define SkColorSpacePrintf(...)
|
| +
|
| +#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 { \
|
| + SkDebugf("Invalid ICC Profile: %s.\n", (msg)); \
|
| + return nullptr; \
|
| + } while (0)
|
| +
|
| +static uint16_t read_big_endian_short(const uint8_t* ptr) {
|
| + return ptr[0] << 8 | ptr[1];
|
| +}
|
| +
|
| +static uint32_t read_big_endian_int(const uint8_t* ptr) {
|
| + return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
|
| +}
|
| +
|
| +// This is equal to the header size according to the ICC specification (128)
|
| +// plus the size of the tag count (4). We include the tag count since we
|
| +// always require it to be present anyway.
|
| +static const size_t kICCHeaderSize = 132;
|
| +
|
| +// Contains a signature (4), offset (4), and size (4).
|
| +static const size_t kICCTagTableEntrySize = 12;
|
| +
|
| +static const uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' ');
|
| +static const uint32_t kGray_ColorSpace = SkSetFourByteTag('G', 'R', 'A', 'Y');
|
| +
|
| +struct ICCProfileHeader {
|
| + // TODO (msarett):
|
| + // Can we ignore less of these fields?
|
| + uint32_t fSize;
|
| + uint32_t fCMMType_ignored;
|
| + uint32_t fVersion;
|
| + uint32_t fClassProfile;
|
| + uint32_t fColorSpace;
|
| + uint32_t fPCS;
|
| + uint32_t fDateTime_ignored[3];
|
| + uint32_t fSignature;
|
| + uint32_t fPlatformTarget_ignored;
|
| + uint32_t fFlags_ignored;
|
| + uint32_t fManufacturer_ignored;
|
| + uint32_t fDeviceModel_ignored;
|
| + uint32_t fDeviceAttributes_ignored[2];
|
| + uint32_t fRenderingIntent;
|
| + uint32_t fIlluminantXYZ_ignored[3];
|
| + uint32_t fCreator_ignored;
|
| + uint32_t fProfileId_ignored[4];
|
| + 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_int(src);
|
| + }
|
| + }
|
| +
|
| + bool valid() const {
|
| + // TODO (msarett):
|
| + // For now it's nice to fail loudly on invalid inputs. But, can we
|
| + // recover from some of these errors?
|
| +
|
| + return_if_false(fSize >= kICCHeaderSize, "Size is too small");
|
| +
|
| + uint8_t majorVersion = fVersion >> 24;
|
| + return_if_false(majorVersion <= 4, "Unsupported version");
|
| +
|
| + const uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r');
|
| + const uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r');
|
| + const uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r');
|
| + // TODO (msarett):
|
| + // Should we also support DeviceLink, ColorSpace, Abstract, or NamedColor?
|
| + return_if_false(fClassProfile == kDisplay_Profile ||
|
| + fClassProfile == kInput_Profile ||
|
| + fClassProfile == kOutput_Profile,
|
| + "Unsupported class profile");
|
| +
|
| + // TODO (msarett):
|
| + // There are many more color spaces that we could try to support.
|
| + return_if_false(fColorSpace == kRGB_ColorSpace || fColorSpace == kGray_ColorSpace,
|
| + "Unsupported color space");
|
| +
|
| + const uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' ');
|
| + // TODO (msarett):
|
| + // Can we support PCS LAB as well?
|
| + return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space");
|
| +
|
| + return_if_false(fSignature == SkSetFourByteTag('a', 'c', 's', 'p'), "Bad signature");
|
| +
|
| + // TODO (msarett):
|
| + // Should we treat different rendering intents differently?
|
| + // Valid rendering intents include kPerceptual (0), kRelative (1),
|
| + // kSaturation (2), and kAbsolute (3).
|
| + return_if_false(fRenderingIntent <= 3, "Bad rendering intent");
|
| +
|
| + return_if_false(fTagCount <= 100, "Too many tags");
|
| +
|
| + 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_int(src);
|
| + fOffset = read_big_endian_int(src + 4);
|
| + fLength = read_big_endian_int(src + 8);
|
| + return src + 12;
|
| + }
|
| +
|
| + bool valid(size_t len) {
|
| + return_if_false(fOffset + fLength <= 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;
|
| + }
|
| +};
|
| +
|
| +// TODO (msarett):
|
| +// Should we recognize more tags?
|
| +static const uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z');
|
| +static const uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z');
|
| +static const uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z');
|
| +static const uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C');
|
| +static const uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C');
|
| +static const uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C');
|
| +
|
| +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_int(src + 8));
|
| + dst[1] = SkFixedToFloat(read_big_endian_int(src + 12));
|
| + dst[2] = SkFixedToFloat(read_big_endian_int(src + 16));
|
| + SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]);
|
| + return true;
|
| +}
|
| +
|
| +static const uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', 'v');
|
| +static const uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a');
|
| +
|
| +static bool load_gamma(float* gamma, const uint8_t* src, size_t len) {
|
| + if (len < 14) {
|
| + SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
|
| + return false;
|
| + }
|
| +
|
| + uint32_t type = read_big_endian_int(src);
|
| + switch (type) {
|
| + case kTAG_CurveType: {
|
| + uint32_t count = read_big_endian_int(src + 8);
|
| + if (0 == count) {
|
| + return false;
|
| + }
|
| +
|
| + const uint16_t* table = (const uint16_t*) (src + 12);
|
| + if (1 == count) {
|
| + // Table entry is the exponent (bias 256).
|
| + uint16_t value = read_big_endian_short((const uint8_t*) table);
|
| + *gamma = value / 256.0f;
|
| + SkColorSpacePrintf("gamma %d %g\n", value, *gamma);
|
| + return true;
|
| + }
|
| +
|
| + // Check length again if we have a table.
|
| + if (len < 12 + 2 * count) {
|
| + SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
|
| + return false;
|
| + }
|
| +
|
| + // Print the interpolation table. For now, we ignore this and guess 2.2f.
|
| + for (uint32_t i = 0; i < count; i++) {
|
| + SkColorSpacePrintf("curve[%d] %d\n", i,
|
| + read_big_endian_short((const uint8_t*) &table[i]));
|
| + }
|
| +
|
| + *gamma = 2.2f;
|
| + return true;
|
| + }
|
| + case kTAG_ParaCurveType:
|
| + // Guess 2.2f.
|
| + SkColorSpacePrintf("parametric curve\n");
|
| + *gamma = 2.2f;
|
| + return true;
|
| + default:
|
| + SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +SkColorSpace* SkColorSpace::NewICC(const void* base, size_t len) {
|
| + const uint8_t* ptr = (const uint8_t*) base;
|
| +
|
| + if (len < kICCHeaderSize) {
|
| + return_null("Data is not large enough to contain an ICC profile");
|
| + }
|
| +
|
| + // 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<ICCTag> 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");
|
| + }
|
| + }
|
| +
|
| + // Load our XYZ and gamma matrices.
|
| + SkFloat3x3 toXYZ;
|
| + SkFloat3 gamma {{ 1.0f, 1.0f, 1.0f }};
|
| + switch (header.fColorSpace) {
|
| + case kRGB_ColorSpace: {
|
| + const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ);
|
| + const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ);
|
| + const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ);
|
| + if (!r || !g || !b) {
|
| + return_null("Need rgb tags for XYZ space");
|
| + }
|
| +
|
| + if (!load_xyz(&toXYZ.fMat[0], r->addr((const uint8_t*) base), r->fLength) ||
|
| + !load_xyz(&toXYZ.fMat[3], g->addr((const uint8_t*) base), g->fLength) ||
|
| + !load_xyz(&toXYZ.fMat[6], b->addr((const uint8_t*) base), b->fLength))
|
| + {
|
| + return_null("Need valid rgb tags for XYZ space");
|
| + }
|
| +
|
| + r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC);
|
| + g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC);
|
| + b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC);
|
| + if (!r || !load_gamma(&gamma.fVec[0], r->addr((const uint8_t*) base), r->fLength)) {
|
| + SkColorSpacePrintf("Failed to read R gamma tag.\n");
|
| + }
|
| + if (!g || !load_gamma(&gamma.fVec[1], g->addr((const uint8_t*) base), g->fLength)) {
|
| + SkColorSpacePrintf("Failed to read G gamma tag.\n");
|
| + }
|
| + if (!b || !load_gamma(&gamma.fVec[2], b->addr((const uint8_t*) base), b->fLength)) {
|
| + SkColorSpacePrintf("Failed to read B gamma tag.\n");
|
| + }
|
| + return SkColorSpace::NewRGB(toXYZ, gamma);
|
| + }
|
| + default:
|
| + break;
|
| + }
|
| +
|
| + return_null("ICC profile contains unsupported colorspace");
|
| +}
|
| +
|
| +///////////////////////////////////////////////////////////////////////////////////////////////////
|
| +
|
| SkColorSpace::Result SkColorSpace::Concat(const SkColorSpace* src, const SkColorSpace* dst,
|
| SkFloat3x3* result) {
|
| if (!src || !dst || (src->named() == kDevice_Named) || (src->named() == dst->named())) {
|
|
|