aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar msarett <msarett@google.com>2016-06-28 07:16:40 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2016-06-28 07:16:40 -0700
commit085cad4abcca4e10dcc6ba95347c378d4c47fb90 (patch)
tree47d287214316056532560bf3a57bc0030e220368
parent0a8f40cbe693274d8f63ac423e51be3e75ce5d35 (diff)
Move SkColorSpace ICC parsing/writing code to its own file
-rw-r--r--gyp/core.gypi1
-rw-r--r--src/core/SkColorSpace.cpp1071
-rw-r--r--src/core/SkColorSpacePriv.h25
-rw-r--r--src/core/SkColorSpace_ICC.cpp1054
4 files changed, 1081 insertions, 1070 deletions
diff --git a/gyp/core.gypi b/gyp/core.gypi
index 7c89960e66..91387a39d0 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -76,6 +76,7 @@
'<(skia_src_path)/core/SkColorShader.cpp',
'<(skia_src_path)/core/SkColorShader.h',
'<(skia_src_path)/core/SkColorSpace.cpp',
+ '<(skia_src_path)/core/SkColorSpace_ICC.cpp',
'<(skia_src_path)/core/SkColorSpaceXform.cpp',
'<(skia_src_path)/core/SkColorTable.cpp',
'<(skia_src_path)/core/SkComposeShader.cpp',
diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
index f988c7d323..894c4b99b7 100644
--- a/src/core/SkColorSpace.cpp
+++ b/src/core/SkColorSpace.cpp
@@ -7,18 +7,8 @@
#include "SkColorSpace.h"
#include "SkColorSpace_Base.h"
-#include "SkEndian.h"
+#include "SkColorSpacePriv.h"
#include "SkOnce.h"
-#include "SkReadBuffer.h"
-#include "SkWriteBuffer.h"
-
-#define SkColorSpacePrintf(...)
-
-static bool color_space_almost_equal(float a, float b) {
- return SkTAbs(a - b) < 0.01f;
-}
-
-//////////////////////////////////////////////////////////////////////////////////////////////////
SkColorSpace::SkColorSpace(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Named named)
: fGammaNamed(gammaNamed)
@@ -77,19 +67,6 @@ static bool xyz_almost_equal(const SkMatrix44& toXYZD50, const float* standard)
color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f);
}
-static void set_gamma_value(SkGammaCurve* gamma, float value) {
- if (color_space_almost_equal(2.2f, value)) {
- gamma->fNamed = SkColorSpace::k2Dot2Curve_GammaNamed;
- } else if (color_space_almost_equal(1.0f, value)) {
- gamma->fNamed = SkColorSpace::kLinear_GammaNamed;
- } else if (color_space_almost_equal(0.0f, value)) {
- SkColorSpacePrintf("Treating invalid zero gamma as linear.");
- gamma->fNamed = SkColorSpace::kLinear_GammaNamed;
- } else {
- gamma->fValue = value;
- }
-}
-
sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(float values[3], const SkMatrix44& toXYZD50) {
SkGammaCurve curves[3];
set_gamma_value(&curves[0], values[0]);
@@ -164,1052 +141,6 @@ sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
///////////////////////////////////////////////////////////////////////////////////////////////////
-#include "SkFixed.h"
-#include "SkTemplates.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_short(const uint8_t* ptr) {
- return ptr[0] << 8 | ptr[1];
-}
-
-static uint32_t read_big_endian_uint(const uint8_t* ptr) {
- return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
-}
-
-static int32_t read_big_endian_int(const uint8_t* ptr) {
- return (int32_t) read_big_endian_uint(ptr);
-}
-
-// 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 constexpr size_t kICCHeaderSize = 132;
-
-// Contains a signature (4), offset (4), and size (4).
-static constexpr size_t kICCTagTableEntrySize = 12;
-
-static constexpr uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' ');
-static constexpr uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r');
-static constexpr uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r');
-static constexpr uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r');
-static constexpr uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' ');
-static constexpr uint32_t kACSP_Signature = SkSetFourByteTag('a', 'c', 's', 'p');
-
-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_uint(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 three basic classes of profiles that we might expect to see embedded
- // in images. Four 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,
- "Unsupported profile");
-
- // TODO (msarett):
- // All the profiles we've tested so far use RGB as the input color space.
- return_if_false(fInputColorSpace == kRGB_ColorSpace, "Unsupported color space");
-
- // TODO (msarett):
- // All the profiles we've tested so far use XYZ as the profile connection space.
- return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space");
-
- 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).
- return_if_false(fRenderingIntent <= 3, "Bad rendering intent");
-
- return_if_false(color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[0]), 0.96420f) &&
- color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[1]), 1.00000f) &&
- color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[2]), 0.82491f),
- "Illuminant must be D50");
-
- return_if_false(fTagCount <= 100, "Too many tags");
-
- return true;
- }
-};
-
-template <class T>
-static bool safe_add(T arg1, T arg2, size_t* result) {
- SkASSERT(arg1 >= 0);
- SkASSERT(arg2 >= 0);
- if (arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) {
- T sum = arg1 + arg2;
- if (sum <= std::numeric_limits<size_t>::max()) {
- *result = static_cast<size_t>(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_uint(src);
- fOffset = read_big_endian_uint(src + 4);
- fLength = read_big_endian_uint(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 constexpr uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z');
-static constexpr uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z');
-static constexpr uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z');
-static constexpr uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C');
-static constexpr uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C');
-static constexpr uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C');
-static constexpr uint32_t kTAG_A2B0 = SkSetFourByteTag('A', '2', 'B', '0');
-
-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_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 constexpr uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', 'v');
-static constexpr uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a');
-
-static bool load_gammas(SkGammaCurve* gammas, uint32_t numGammas, const uint8_t* src, size_t len) {
- for (uint32_t i = 0; i < numGammas; i++) {
- if (len < 12) {
- // FIXME (msarett):
- // We could potentially return false here after correctly parsing *some* of the
- // gammas correctly. Should we somehow try to indicate a partial success?
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
- }
-
- // We need to count the number of bytes in the tag, so we are able to move to the
- // next tag on the next loop iteration.
- size_t tagBytes;
-
- uint32_t type = read_big_endian_uint(src);
- switch (type) {
- case kTAG_CurveType: {
- uint32_t count = read_big_endian_uint(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 false;
- }
-
- 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.
- gammas[i].fNamed = SkColorSpace::kLinear_GammaNamed;
- break;
- } else if (len < tagBytes) {
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
- }
-
- 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_short((const uint8_t*) table)) / 256.0f;
- set_gamma_value(&gammas[i], value);
- SkColorSpacePrintf("gamma %g\n", value);
- break;
- }
-
- // 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 a very common sRGB
- // gamma table and the less common Canon sRGB gamma table (which use
- // different rounding rules).
- if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
- 3366 == read_big_endian_short((const uint8_t*) &table[257]) &&
- 14116 == read_big_endian_short((const uint8_t*) &table[513]) &&
- 34318 == read_big_endian_short((const uint8_t*) &table[768]) &&
- 65535 == read_big_endian_short((const uint8_t*) &table[1023])) {
- gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
- break;
- }
- } else if (26 == count) {
- // The magic values were chosen because they match a very common sRGB
- // gamma table.
- if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
- 3062 == read_big_endian_short((const uint8_t*) &table[6]) &&
- 12824 == read_big_endian_short((const uint8_t*) &table[12]) &&
- 31237 == read_big_endian_short((const uint8_t*) &table[18]) &&
- 65535 == read_big_endian_short((const uint8_t*) &table[25])) {
- gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
- break;
- }
- } else if (4096 == count) {
- // The magic values were chosen because they match Nikon, Epson, and
- // LCMS sRGB gamma tables (all of which use different rounding rules).
- if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
- 950 == read_big_endian_short((const uint8_t*) &table[515]) &&
- 3342 == read_big_endian_short((const uint8_t*) &table[1025]) &&
- 14079 == read_big_endian_short((const uint8_t*) &table[2051]) &&
- 65535 == read_big_endian_short((const uint8_t*) &table[4095])) {
- gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
- break;
- }
- }
-
- // Otherwise, fill in the interpolation table.
- gammas[i].fTableSize = count;
- gammas[i].fTable = std::unique_ptr<float[]>(new float[count]);
- for (uint32_t j = 0; j < count; j++) {
- gammas[i].fTable[j] =
- (read_big_endian_short((const uint8_t*) &table[j])) / 65535.0f;
- }
- break;
- }
- case kTAG_ParaCurveType: {
- enum ParaCurveType {
- kExponential_ParaCurveType = 0,
- kGAB_ParaCurveType = 1,
- kGABC_ParaCurveType = 2,
- kGABDE_ParaCurveType = 3,
- kGABCDEF_ParaCurveType = 4,
- };
-
- // Determine the format of the parametric curve tag.
- uint16_t format = read_big_endian_short(src + 8);
- if (kExponential_ParaCurveType == format) {
- tagBytes = 12 + 4;
- if (len < tagBytes) {
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
- }
-
- // Y = X^g
- int32_t g = read_big_endian_int(src + 12);
- set_gamma_value(&gammas[i], SkFixedToFloat(g));
- } else {
- // Here's where the real parametric gammas start. There are many
- // permutations of the same equations.
- //
- // Y = (aX + b)^g + c for X >= d
- // Y = eX + f otherwise
- //
- // We will fill in with zeros as necessary to always match the above form.
- float g = 0.0f, a = 0.0f, b = 0.0f, c = 0.0f, d = 0.0f, e = 0.0f, f = 0.0f;
- switch(format) {
- case kGAB_ParaCurveType: {
- tagBytes = 12 + 12;
- if (len < tagBytes) {
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
- }
-
- // Y = (aX + b)^g for X >= -b/a
- // Y = 0 otherwise
- g = SkFixedToFloat(read_big_endian_int(src + 12));
- a = SkFixedToFloat(read_big_endian_int(src + 16));
- if (0.0f == a) {
- return false;
- }
-
- b = SkFixedToFloat(read_big_endian_int(src + 20));
- d = -b / a;
- break;
- }
- case kGABC_ParaCurveType:
- tagBytes = 12 + 16;
- if (len < tagBytes) {
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
- }
-
- // Y = (aX + b)^g + c for X >= -b/a
- // Y = c otherwise
- g = SkFixedToFloat(read_big_endian_int(src + 12));
- a = SkFixedToFloat(read_big_endian_int(src + 16));
- if (0.0f == a) {
- return false;
- }
-
- b = SkFixedToFloat(read_big_endian_int(src + 20));
- c = SkFixedToFloat(read_big_endian_int(src + 24));
- d = -b / a;
- f = c;
- break;
- case kGABDE_ParaCurveType:
- tagBytes = 12 + 20;
- if (len < tagBytes) {
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
- }
-
- // Y = (aX + b)^g for X >= d
- // Y = cX otherwise
- g = SkFixedToFloat(read_big_endian_int(src + 12));
- a = SkFixedToFloat(read_big_endian_int(src + 16));
- b = SkFixedToFloat(read_big_endian_int(src + 20));
- d = SkFixedToFloat(read_big_endian_int(src + 28));
- e = SkFixedToFloat(read_big_endian_int(src + 24));
- break;
- case kGABCDEF_ParaCurveType:
- tagBytes = 12 + 28;
- if (len < tagBytes) {
- SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
- return false;
- }
-
- // Y = (aX + b)^g + c for X >= d
- // Y = eX + f otherwise
- // NOTE: The ICC spec writes "cX" in place of "eX" but I think
- // it's a typo.
- g = SkFixedToFloat(read_big_endian_int(src + 12));
- a = SkFixedToFloat(read_big_endian_int(src + 16));
- b = SkFixedToFloat(read_big_endian_int(src + 20));
- c = SkFixedToFloat(read_big_endian_int(src + 24));
- d = SkFixedToFloat(read_big_endian_int(src + 28));
- e = SkFixedToFloat(read_big_endian_int(src + 32));
- f = SkFixedToFloat(read_big_endian_int(src + 36));
- break;
- default:
- SkColorSpacePrintf("Invalid parametric curve type\n");
- return false;
- }
-
- // Recognize and simplify a very common parametric representation of sRGB gamma.
- if (color_space_almost_equal(0.9479f, a) &&
- color_space_almost_equal(0.0521f, b) &&
- color_space_almost_equal(0.0000f, c) &&
- color_space_almost_equal(0.0405f, d) &&
- color_space_almost_equal(0.0774f, e) &&
- color_space_almost_equal(0.0000f, f) &&
- color_space_almost_equal(2.4000f, g)) {
- gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
- } else {
- // Fail on invalid gammas.
- if (d <= 0.0f) {
- // Y = (aX + b)^g + c for always
- if (0.0f == a || 0.0f == g) {
- SkColorSpacePrintf("A or G is zero, constant gamma function "
- "is nonsense");
- return false;
- }
- } else if (d >= 1.0f) {
- // Y = eX + f for always
- if (0.0f == e) {
- SkColorSpacePrintf("E is zero, constant gamma function is "
- "nonsense");
- return false;
- }
- } else if ((0.0f == a || 0.0f == g) && 0.0f == e) {
- SkColorSpacePrintf("A or G, and E are zero, constant gamma function "
- "is nonsense");
- return false;
- }
-
- gammas[i].fG = g;
- gammas[i].fA = a;
- gammas[i].fB = b;
- gammas[i].fC = c;
- gammas[i].fD = d;
- gammas[i].fE = e;
- gammas[i].fF = f;
- }
- }
-
- break;
- }
- default:
- SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
- return false;
- }
-
- // Ensure that we have successfully read a gamma representation.
- SkASSERT(gammas[i].isNamed() || gammas[i].isValue() || gammas[i].isTable() ||
- gammas[i].isParametric());
-
- // Adjust src and len if there is another gamma curve to load.
- if (i != numGammas - 1) {
- // Each curve is padded to 4-byte alignment.
- tagBytes = SkAlign4(tagBytes);
- if (len < tagBytes) {
- return false;
- }
-
- src += tagBytes;
- len -= tagBytes;
- }
- }
-
- return true;
-}
-
-static constexpr uint32_t kTAG_AtoBType = SkSetFourByteTag('m', 'A', 'B', ' ');
-
-bool load_color_lut(SkColorLookUpTable* colorLUT, uint32_t inputChannels, uint32_t outputChannels,
- const uint8_t* src, size_t len) {
- // 16 bytes reserved for grid points, 2 for precision, 2 for padding.
- // The color LUT data follows after this header.
- static constexpr uint32_t kColorLUTHeaderSize = 20;
- if (len < kColorLUTHeaderSize) {
- SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len);
- return false;
- }
- size_t dataLen = len - kColorLUTHeaderSize;
-
- SkASSERT(3 == inputChannels && 3 == outputChannels);
- colorLUT->fInputChannels = inputChannels;
- colorLUT->fOutputChannels = outputChannels;
- uint32_t numEntries = 1;
- for (uint32_t i = 0; i < inputChannels; i++) {
- colorLUT->fGridPoints[i] = src[i];
- if (0 == src[i]) {
- SkColorSpacePrintf("Each input channel must have at least one grid point.");
- return false;
- }
-
- if (!safe_mul(numEntries, src[i], &numEntries)) {
- SkColorSpacePrintf("Too many entries in Color LUT.");
- return false;
- }
- }
-
- if (!safe_mul(numEntries, outputChannels, &numEntries)) {
- SkColorSpacePrintf("Too many entries in Color LUT.");
- return false;
- }
-
- // Space is provided for a maximum of the 16 input channels. Now we determine the precision
- // of the table values.
- uint8_t precision = src[16];
- 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.\n");
- return false;
- }
-
- uint32_t clutBytes;
- if (!safe_mul(numEntries, precision, &clutBytes)) {
- SkColorSpacePrintf("Too many entries in Color LUT.");
- return false;
- }
-
- if (dataLen < clutBytes) {
- SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len);
- return false;
- }
-
- // Movable struct colorLUT has ownership of fTable.
- colorLUT->fTable = std::unique_ptr<float[]>(new float[numEntries]);
- const uint8_t* ptr = src + kColorLUTHeaderSize;
- for (uint32_t i = 0; i < numEntries; i++, ptr += precision) {
- if (1 == precision) {
- colorLUT->fTable[i] = ((float) ptr[i]) / 255.0f;
- } else {
- colorLUT->fTable[i] = ((float) read_big_endian_short(ptr)) / 65535.0f;
- }
- }
-
- return true;
-}
-
-bool load_matrix(SkMatrix44* toXYZ, const uint8_t* src, size_t len) {
- if (len < 48) {
- SkColorSpacePrintf("Matrix tag is too small (%d bytes).", len);
- return false;
- }
-
- // For this matrix to behave like our "to XYZ D50" matrices, it needs to be scaled.
- constexpr float scale = 65535.0 / 32768.0;
- float array[16];
- array[ 0] = scale * SkFixedToFloat(read_big_endian_int(src));
- array[ 1] = scale * SkFixedToFloat(read_big_endian_int(src + 4));
- array[ 2] = scale * SkFixedToFloat(read_big_endian_int(src + 8));
- array[ 3] = scale * SkFixedToFloat(read_big_endian_int(src + 36)); // translate R
- array[ 4] = scale * SkFixedToFloat(read_big_endian_int(src + 12));
- array[ 5] = scale * SkFixedToFloat(read_big_endian_int(src + 16));
- array[ 6] = scale * SkFixedToFloat(read_big_endian_int(src + 20));
- array[ 7] = scale * SkFixedToFloat(read_big_endian_int(src + 40)); // translate G
- array[ 8] = scale * SkFixedToFloat(read_big_endian_int(src + 24));
- array[ 9] = scale * SkFixedToFloat(read_big_endian_int(src + 28));
- array[10] = scale * SkFixedToFloat(read_big_endian_int(src + 32));
- array[11] = scale * SkFixedToFloat(read_big_endian_int(src + 44)); // translate B
- array[12] = 0.0f;
- array[13] = 0.0f;
- array[14] = 0.0f;
- array[15] = 1.0f;
- toXYZ->setColMajorf(array);
- return true;
-}
-
-bool load_a2b0(SkColorLookUpTable* colorLUT, SkGammaCurve* gammas, SkMatrix44* toXYZ,
- const uint8_t* src, size_t len) {
- if (len < 32) {
- SkColorSpacePrintf("A to B tag is too small (%d bytes).", len);
- return false;
- }
-
- uint32_t type = read_big_endian_uint(src);
- if (kTAG_AtoBType != type) {
- // FIXME (msarett): Need to support lut8Type and lut16Type.
- SkColorSpacePrintf("Unsupported A to B tag type.\n");
- return false;
- }
-
- // Read the number of channels. The four bytes that we skipped are reserved and
- // must be zero.
- uint8_t inputChannels = src[8];
- uint8_t outputChannels = src[9];
- if (3 != inputChannels || 3 != outputChannels) {
- // We only handle (supposedly) RGB inputs and RGB outputs. The numbers of input
- // channels and output channels both must be 3.
- SkColorSpacePrintf("Input and output channels must equal 3 in A to B tag.\n");
- return false;
- }
-
- // Read the offsets of each element in the A to B tag. With the exception of A curves and
- // B curves (which we do not yet support), we will handle these elements in the order in
- // which they should be applied (rather than the order in which they occur in the tag).
- // If the offset is non-zero it indicates that the element is present.
- uint32_t offsetToACurves = read_big_endian_int(src + 28);
- uint32_t offsetToBCurves = read_big_endian_int(src + 12);
- if ((0 != offsetToACurves) || (0 != offsetToBCurves)) {
- // FIXME (msarett): Handle A and B curves.
- // Note that the A curve is technically required in order to have a color LUT.
- // However, all the A curves I have seen so far have are just placeholders that
- // don't actually transform the data.
- SkColorSpacePrintf("Ignoring A and/or B curve. Output may be wrong.\n");
- }
-
- uint32_t offsetToColorLUT = read_big_endian_int(src + 24);
- if (0 != offsetToColorLUT && offsetToColorLUT < len) {
- if (!load_color_lut(colorLUT, inputChannels, outputChannels, src + offsetToColorLUT,
- len - offsetToColorLUT)) {
- SkColorSpacePrintf("Failed to read color LUT from A to B tag.\n");
- }
- }
-
- uint32_t offsetToMCurves = read_big_endian_int(src + 20);
- if (0 != offsetToMCurves && offsetToMCurves < len) {
- if (!load_gammas(gammas, outputChannels, src + offsetToMCurves, len - offsetToMCurves)) {
- SkColorSpacePrintf("Failed to read M curves from A to B tag. Using linear gamma.\n");
- gammas[0].fNamed = SkColorSpace::kLinear_GammaNamed;
- gammas[1].fNamed = SkColorSpace::kLinear_GammaNamed;
- gammas[2].fNamed = SkColorSpace::kLinear_GammaNamed;
- }
- }
-
- uint32_t offsetToMatrix = read_big_endian_int(src + 16);
- if (0 != offsetToMatrix && offsetToMatrix < len) {
- if (!load_matrix(toXYZ, src + offsetToMatrix, len - offsetToMatrix)) {
- SkColorSpacePrintf("Failed to read matrix from A to B tag.\n");
- toXYZ->setIdentity();
- }
- }
-
- return true;
-}
-
-sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* input, size_t len) {
- if (!input || len < kICCHeaderSize) {
- return_null("Data is null or not large enough to contain an ICC profile");
- }
-
- // Create our own copy of the input.
- void* memory = sk_malloc_throw(len);
- memcpy(memory, input, len);
- sk_sp<SkData> data = SkData::MakeFromMalloc(memory, len);
- const void* base = data->data();
- const uint8_t* ptr = (const uint8_t*) 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<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");
- }
- }
-
- switch (header.fInputColorSpace) {
- case kRGB_ColorSpace: {
- // Recognize the rXYZ, gXYZ, and bXYZ tags.
- 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) {
- float toXYZ[9];
- if (!load_xyz(&toXYZ[0], r->addr((const uint8_t*) base), r->fLength) ||
- !load_xyz(&toXYZ[3], g->addr((const uint8_t*) base), g->fLength) ||
- !load_xyz(&toXYZ[6], b->addr((const uint8_t*) base), b->fLength))
- {
- return_null("Need valid rgb tags for XYZ space");
- }
- SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
- mat.set3x3RowMajorf(toXYZ);
-
- // It is not uncommon to see missing or empty gamma tags. This indicates
- // that we should use unit gamma.
- SkGammaCurve curves[3];
- 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_gammas(&curves[0], 1, r->addr((const uint8_t*) base), r->fLength))
- {
- SkColorSpacePrintf("Failed to read R gamma tag.\n");
- curves[0].fNamed = SkColorSpace::kLinear_GammaNamed;
- }
- if (!g || !load_gammas(&curves[1], 1, g->addr((const uint8_t*) base), g->fLength))
- {
- SkColorSpacePrintf("Failed to read G gamma tag.\n");
- curves[1].fNamed = SkColorSpace::kLinear_GammaNamed;
- }
- if (!b || !load_gammas(&curves[2], 1, b->addr((const uint8_t*) base), b->fLength))
- {
- SkColorSpacePrintf("Failed to read B gamma tag.\n");
- curves[2].fNamed = SkColorSpace::kLinear_GammaNamed;
- }
-
- GammaNamed gammaNamed = SkGammas::Named(curves);
- if (kNonStandard_GammaNamed == gammaNamed) {
- sk_sp<SkGammas> gammas = sk_make_sp<SkGammas>(std::move(curves[0]),
- std::move(curves[1]),
- std::move(curves[2]));
- return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, std::move(gammas),
- mat, std::move(data)));
- } else {
- return SkColorSpace_Base::NewRGB(gammaNamed, mat);
- }
- }
-
- // Recognize color profile specified by A2B0 tag.
- const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0);
- if (a2b0) {
- sk_sp<SkColorLookUpTable> colorLUT = sk_make_sp<SkColorLookUpTable>();
- SkGammaCurve curves[3];
- SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
- if (!load_a2b0(colorLUT.get(), curves, &toXYZ, a2b0->addr((const uint8_t*) base),
- a2b0->fLength)) {
- return_null("Failed to parse A2B0 tag");
- }
-
- GammaNamed gammaNamed = SkGammas::Named(curves);
- colorLUT = colorLUT->fTable ? colorLUT : nullptr;
- if (colorLUT || kNonStandard_GammaNamed == gammaNamed) {
- sk_sp<SkGammas> gammas = sk_make_sp<SkGammas>(std::move(curves[0]),
- std::move(curves[1]),
- std::move(curves[2]));
-
- return sk_sp<SkColorSpace>(new SkColorSpace_Base(std::move(colorLUT),
- std::move(gammas), toXYZ,
- std::move(data)));
- } else {
- return SkColorSpace_Base::NewRGB(gammaNamed, toXYZ);
- }
- }
- }
- default:
- break;
- }
-
- return_null("ICC profile contains unsupported colorspace");
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-// We will write a profile with the minimum nine required tags.
-static constexpr uint32_t kICCNumEntries = 9;
-
-static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c');
-static constexpr uint32_t kTAG_desc_Bytes = 12;
-static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize + kICCNumEntries*kICCTagTableEntrySize;
-
-static constexpr uint32_t kTAG_XYZ_Bytes = 20;
-static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes;
-static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes;
-static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes;
-
-static constexpr uint32_t kTAG_TRC_Bytes = 14;
-static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes;
-static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset + SkAlign4(kTAG_TRC_Bytes);
-static constexpr uint32_t kTAG_bTRC_Offset = kTAG_gTRC_Offset + SkAlign4(kTAG_TRC_Bytes);
-
-static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't');
-static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + SkAlign4(kTAG_TRC_Bytes);
-
-static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't');
-static constexpr uint32_t kTAG_cprt_Bytes = 12;
-static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes;
-
-static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes;
-
-static constexpr uint32_t gICCHeader[kICCHeaderSize / 4] {
- SkEndian_SwapBE32(kICCProfileSize), // Size of the profile
- 0, // Preferred CMM type (ignored)
- SkEndian_SwapBE32(0x02100000), // Version 2.1
- SkEndian_SwapBE32(kDisplay_Profile), // Display device profile
- SkEndian_SwapBE32(kRGB_ColorSpace), // RGB input color space
- SkEndian_SwapBE32(kXYZ_PCSSpace), // XYZ profile connection space
- 0, 0, 0, // Date and time (ignored)
- SkEndian_SwapBE32(kACSP_Signature), // Profile signature
- 0, // Platform target (ignored)
- 0x00000000, // Flags: not embedded, can be used independently
- 0, // Device manufacturer (ignored)
- 0, // Device model (ignored)
- 0, 0, // Device attributes (ignored)
- SkEndian_SwapBE32(1), // Relative colorimetric rendering intent
- SkEndian_SwapBE32(0x0000f6d6), // D50 standard illuminant (X)
- SkEndian_SwapBE32(0x00010000), // D50 standard illuminant (Y)
- SkEndian_SwapBE32(0x0000d32d), // D50 standard illuminant (Z)
- 0, // Profile creator (ignored)
- 0, 0, 0, 0, // Profile id checksum (ignored)
- 0, 0, 0, 0, 0, 0, 0, // Reserved (ignored)
- SkEndian_SwapBE32(kICCNumEntries), // Number of tags
-};
-
-static constexpr uint32_t gICCTagTable[3 * kICCNumEntries] {
- // Profile description
- SkEndian_SwapBE32(kTAG_desc),
- SkEndian_SwapBE32(kTAG_desc_Offset),
- SkEndian_SwapBE32(kTAG_desc_Bytes),
-
- // rXYZ
- SkEndian_SwapBE32(kTAG_rXYZ),
- SkEndian_SwapBE32(kTAG_rXYZ_Offset),
- SkEndian_SwapBE32(kTAG_XYZ_Bytes),
-
- // gXYZ
- SkEndian_SwapBE32(kTAG_gXYZ),
- SkEndian_SwapBE32(kTAG_gXYZ_Offset),
- SkEndian_SwapBE32(kTAG_XYZ_Bytes),
-
- // bXYZ
- SkEndian_SwapBE32(kTAG_bXYZ),
- SkEndian_SwapBE32(kTAG_bXYZ_Offset),
- SkEndian_SwapBE32(kTAG_XYZ_Bytes),
-
- // rTRC
- SkEndian_SwapBE32(kTAG_rTRC),
- SkEndian_SwapBE32(kTAG_rTRC_Offset),
- SkEndian_SwapBE32(kTAG_TRC_Bytes),
-
- // gTRC
- SkEndian_SwapBE32(kTAG_gTRC),
- SkEndian_SwapBE32(kTAG_gTRC_Offset),
- SkEndian_SwapBE32(kTAG_TRC_Bytes),
-
- // bTRC
- SkEndian_SwapBE32(kTAG_bTRC),
- SkEndian_SwapBE32(kTAG_bTRC_Offset),
- SkEndian_SwapBE32(kTAG_TRC_Bytes),
-
- // White point
- SkEndian_SwapBE32(kTAG_wtpt),
- SkEndian_SwapBE32(kTAG_wtpt_Offset),
- SkEndian_SwapBE32(kTAG_XYZ_Bytes),
-
- // Copyright
- SkEndian_SwapBE32(kTAG_cprt),
- SkEndian_SwapBE32(kTAG_cprt_Offset),
- SkEndian_SwapBE32(kTAG_cprt_Bytes),
-};
-
-static constexpr uint32_t kTAG_TextType = SkSetFourByteTag('m', 'l', 'u', 'c');
-static constexpr uint32_t gEmptyTextTag[3] {
- SkEndian_SwapBE32(kTAG_TextType), // Type signature
- 0, // Reserved
- 0, // Zero records
-};
-
-static void write_xyz_tag(uint32_t* ptr, const SkMatrix44& toXYZ, int row) {
- ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
- ptr[1] = 0;
- ptr[2] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 0)));
- ptr[3] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 1)));
- ptr[4] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 2)));
-}
-
-static void write_trc_tag(uint32_t* ptr, float value) {
- ptr[0] = SkEndian_SwapBE32(kTAG_CurveType);
- ptr[1] = 0;
-
- // Gamma will be specified with a single value.
- ptr[2] = SkEndian_SwapBE32(1);
-
- // Convert gamma to 16-bit fixed point.
- uint16_t* ptr16 = (uint16_t*) (ptr + 3);
- ptr16[0] = SkEndian_SwapBE16((uint16_t) (value * 256.0f));
-
- // Pad tag with zero.
- ptr16[1] = 0;
-}
-
-static float get_gamma_value(const SkGammaCurve* curve) {
- switch (curve->fNamed) {
- case SkColorSpace::kSRGB_GammaNamed:
- // FIXME (msarett):
- // kSRGB cannot be represented by a value. Here we fall through to 2.2f,
- // which is a close guess. To be more accurate, we need to represent sRGB
- // gamma with a parametric curve.
- case SkColorSpace::k2Dot2Curve_GammaNamed:
- return 2.2f;
- case SkColorSpace::kLinear_GammaNamed:
- return 1.0f;
- default:
- SkASSERT(curve->isValue());
- return curve->fValue;
- }
-}
-
-sk_sp<SkData> SkColorSpace_Base::writeToICC() const {
- // Return if this object was created from a profile, or if we have already serialized
- // the profile.
- if (fProfileData) {
- return fProfileData;
- }
-
- // The client may create an SkColorSpace using an SkMatrix44, but currently we only
- // support writing profiles with 3x3 matrices.
- // TODO (msarett): Fix this!
- if (0.0f != fToXYZD50.getFloat(3, 0) || 0.0f != fToXYZD50.getFloat(3, 1) ||
- 0.0f != fToXYZD50.getFloat(3, 2) || 0.0f != fToXYZD50.getFloat(0, 3) ||
- 0.0f != fToXYZD50.getFloat(1, 3) || 0.0f != fToXYZD50.getFloat(2, 3))
- {
- return nullptr;
- }
-
- SkAutoMalloc profile(kICCProfileSize);
- uint8_t* ptr = (uint8_t*) profile.get();
-
- // Write profile header
- memcpy(ptr, gICCHeader, sizeof(gICCHeader));
- ptr += sizeof(gICCHeader);
-
- // Write tag table
- memcpy(ptr, gICCTagTable, sizeof(gICCTagTable));
- ptr += sizeof(gICCTagTable);
-
- // Write profile description tag
- memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag));
- ptr += sizeof(gEmptyTextTag);
-
- // Write XYZ tags
- write_xyz_tag((uint32_t*) ptr, fToXYZD50, 0);
- ptr += kTAG_XYZ_Bytes;
- write_xyz_tag((uint32_t*) ptr, fToXYZD50, 1);
- ptr += kTAG_XYZ_Bytes;
- write_xyz_tag((uint32_t*) ptr, fToXYZD50, 2);
- ptr += kTAG_XYZ_Bytes;
-
- // Write TRC tags
- GammaNamed gammaNamed = this->gammaNamed();
- if (kNonStandard_GammaNamed == gammaNamed) {
- write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->fRed));
- ptr += SkAlign4(kTAG_TRC_Bytes);
- write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->fGreen));
- ptr += SkAlign4(kTAG_TRC_Bytes);
- write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->fBlue));
- ptr += SkAlign4(kTAG_TRC_Bytes);
- } else {
- switch (gammaNamed) {
- case SkColorSpace::kSRGB_GammaNamed:
- // FIXME (msarett):
- // kSRGB cannot be represented by a value. Here we fall through to 2.2f,
- // which is a close guess. To be more accurate, we need to represent sRGB
- // gamma with a parametric curve.
- case SkColorSpace::k2Dot2Curve_GammaNamed:
- write_trc_tag((uint32_t*) ptr, 2.2f);
- ptr += SkAlign4(kTAG_TRC_Bytes);
- write_trc_tag((uint32_t*) ptr, 2.2f);
- ptr += SkAlign4(kTAG_TRC_Bytes);
- write_trc_tag((uint32_t*) ptr, 2.2f);
- ptr += SkAlign4(kTAG_TRC_Bytes);
- break;
- case SkColorSpace::kLinear_GammaNamed:
- write_trc_tag((uint32_t*) ptr, 1.0f);
- ptr += SkAlign4(kTAG_TRC_Bytes);
- write_trc_tag((uint32_t*) ptr, 1.0f);
- ptr += SkAlign4(kTAG_TRC_Bytes);
- write_trc_tag((uint32_t*) ptr, 1.0f);
- ptr += SkAlign4(kTAG_TRC_Bytes);
- break;
- default:
- SkASSERT(false);
- break;
- }
- }
-
- // Write white point tag
- uint32_t* ptr32 = (uint32_t*) ptr;
- ptr32[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
- ptr32[1] = 0;
- // TODO (msarett): These values correspond to the D65 white point. This may not always be
- // correct.
- ptr32[2] = SkEndian_SwapBE32(0x0000f351);
- ptr32[3] = SkEndian_SwapBE32(0x00010000);
- ptr32[4] = SkEndian_SwapBE32(0x000116cc);
- ptr += kTAG_XYZ_Bytes;
-
- // Write copyright tag
- memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag));
-
- // TODO (msarett): Should we try to hold onto the data so we can return immediately if
- // the client calls again?
- return SkData::MakeFromMalloc(profile.release(), kICCProfileSize);
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
enum Version {
k0_Version, // Initial version, header + flags for matrix and profile
};
diff --git a/src/core/SkColorSpacePriv.h b/src/core/SkColorSpacePriv.h
new file mode 100644
index 0000000000..7c7b9d0c21
--- /dev/null
+++ b/src/core/SkColorSpacePriv.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#define SkColorSpacePrintf(...)
+
+inline bool color_space_almost_equal(float a, float b) {
+ return SkTAbs(a - b) < 0.01f;
+}
+
+inline void set_gamma_value(SkGammaCurve* gamma, float value) {
+ if (color_space_almost_equal(2.2f, value)) {
+ gamma->fNamed = SkColorSpace::k2Dot2Curve_GammaNamed;
+ } else if (color_space_almost_equal(1.0f, value)) {
+ gamma->fNamed = SkColorSpace::kLinear_GammaNamed;
+ } else if (color_space_almost_equal(0.0f, value)) {
+ SkColorSpacePrintf("Treating invalid zero gamma as linear.");
+ gamma->fNamed = SkColorSpace::kLinear_GammaNamed;
+ } else {
+ gamma->fValue = value;
+ }
+}
diff --git a/src/core/SkColorSpace_ICC.cpp b/src/core/SkColorSpace_ICC.cpp
new file mode 100644
index 0000000000..e4be8f4e4e
--- /dev/null
+++ b/src/core/SkColorSpace_ICC.cpp
@@ -0,0 +1,1054 @@
+/*
+ * 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 "SkColorSpace.h"
+#include "SkColorSpace_Base.h"
+#include "SkColorSpacePriv.h"
+#include "SkEndian.h"
+#include "SkFixed.h"
+#include "SkTemplates.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_short(const uint8_t* ptr) {
+ return ptr[0] << 8 | ptr[1];
+}
+
+static uint32_t read_big_endian_uint(const uint8_t* ptr) {
+ return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
+}
+
+static int32_t read_big_endian_int(const uint8_t* ptr) {
+ return (int32_t) read_big_endian_uint(ptr);
+}
+
+// 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 constexpr size_t kICCHeaderSize = 132;
+
+// Contains a signature (4), offset (4), and size (4).
+static constexpr size_t kICCTagTableEntrySize = 12;
+
+static constexpr uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' ');
+static constexpr uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r');
+static constexpr uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r');
+static constexpr uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r');
+static constexpr uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' ');
+static constexpr uint32_t kACSP_Signature = SkSetFourByteTag('a', 'c', 's', 'p');
+
+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_uint(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 three basic classes of profiles that we might expect to see embedded
+ // in images. Four 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,
+ "Unsupported profile");
+
+ // TODO (msarett):
+ // All the profiles we've tested so far use RGB as the input color space.
+ return_if_false(fInputColorSpace == kRGB_ColorSpace, "Unsupported color space");
+
+ // TODO (msarett):
+ // All the profiles we've tested so far use XYZ as the profile connection space.
+ return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space");
+
+ 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).
+ return_if_false(fRenderingIntent <= 3, "Bad rendering intent");
+
+ return_if_false(color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[0]), 0.96420f) &&
+ color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[1]), 1.00000f) &&
+ color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[2]), 0.82491f),
+ "Illuminant must be D50");
+
+ return_if_false(fTagCount <= 100, "Too many tags");
+
+ return true;
+ }
+};
+
+template <class T>
+static bool safe_add(T arg1, T arg2, size_t* result) {
+ SkASSERT(arg1 >= 0);
+ SkASSERT(arg2 >= 0);
+ if (arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) {
+ T sum = arg1 + arg2;
+ if (sum <= std::numeric_limits<size_t>::max()) {
+ *result = static_cast<size_t>(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_uint(src);
+ fOffset = read_big_endian_uint(src + 4);
+ fLength = read_big_endian_uint(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 constexpr uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z');
+static constexpr uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z');
+static constexpr uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z');
+static constexpr uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C');
+static constexpr uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C');
+static constexpr uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C');
+static constexpr uint32_t kTAG_A2B0 = SkSetFourByteTag('A', '2', 'B', '0');
+
+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_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 constexpr uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', 'v');
+static constexpr uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a');
+
+static bool load_gammas(SkGammaCurve* gammas, uint32_t numGammas, const uint8_t* src, size_t len) {
+ for (uint32_t i = 0; i < numGammas; i++) {
+ if (len < 12) {
+ // FIXME (msarett):
+ // We could potentially return false here after correctly parsing *some* of the
+ // gammas correctly. Should we somehow try to indicate a partial success?
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return false;
+ }
+
+ // We need to count the number of bytes in the tag, so we are able to move to the
+ // next tag on the next loop iteration.
+ size_t tagBytes;
+
+ uint32_t type = read_big_endian_uint(src);
+ switch (type) {
+ case kTAG_CurveType: {
+ uint32_t count = read_big_endian_uint(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 false;
+ }
+
+ 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.
+ gammas[i].fNamed = SkColorSpace::kLinear_GammaNamed;
+ break;
+ } else if (len < tagBytes) {
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return false;
+ }
+
+ 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_short((const uint8_t*) table)) / 256.0f;
+ set_gamma_value(&gammas[i], value);
+ SkColorSpacePrintf("gamma %g\n", value);
+ break;
+ }
+
+ // 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 a very common sRGB
+ // gamma table and the less common Canon sRGB gamma table (which use
+ // different rounding rules).
+ if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
+ 3366 == read_big_endian_short((const uint8_t*) &table[257]) &&
+ 14116 == read_big_endian_short((const uint8_t*) &table[513]) &&
+ 34318 == read_big_endian_short((const uint8_t*) &table[768]) &&
+ 65535 == read_big_endian_short((const uint8_t*) &table[1023])) {
+ gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
+ break;
+ }
+ } else if (26 == count) {
+ // The magic values were chosen because they match a very common sRGB
+ // gamma table.
+ if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
+ 3062 == read_big_endian_short((const uint8_t*) &table[6]) &&
+ 12824 == read_big_endian_short((const uint8_t*) &table[12]) &&
+ 31237 == read_big_endian_short((const uint8_t*) &table[18]) &&
+ 65535 == read_big_endian_short((const uint8_t*) &table[25])) {
+ gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
+ break;
+ }
+ } else if (4096 == count) {
+ // The magic values were chosen because they match Nikon, Epson, and
+ // LCMS sRGB gamma tables (all of which use different rounding rules).
+ if (0 == read_big_endian_short((const uint8_t*) &table[0]) &&
+ 950 == read_big_endian_short((const uint8_t*) &table[515]) &&
+ 3342 == read_big_endian_short((const uint8_t*) &table[1025]) &&
+ 14079 == read_big_endian_short((const uint8_t*) &table[2051]) &&
+ 65535 == read_big_endian_short((const uint8_t*) &table[4095])) {
+ gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
+ break;
+ }
+ }
+
+ // Otherwise, fill in the interpolation table.
+ gammas[i].fTableSize = count;
+ gammas[i].fTable = std::unique_ptr<float[]>(new float[count]);
+ for (uint32_t j = 0; j < count; j++) {
+ gammas[i].fTable[j] =
+ (read_big_endian_short((const uint8_t*) &table[j])) / 65535.0f;
+ }
+ break;
+ }
+ case kTAG_ParaCurveType: {
+ enum ParaCurveType {
+ kExponential_ParaCurveType = 0,
+ kGAB_ParaCurveType = 1,
+ kGABC_ParaCurveType = 2,
+ kGABDE_ParaCurveType = 3,
+ kGABCDEF_ParaCurveType = 4,
+ };
+
+ // Determine the format of the parametric curve tag.
+ uint16_t format = read_big_endian_short(src + 8);
+ if (kExponential_ParaCurveType == format) {
+ tagBytes = 12 + 4;
+ if (len < tagBytes) {
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return false;
+ }
+
+ // Y = X^g
+ int32_t g = read_big_endian_int(src + 12);
+ set_gamma_value(&gammas[i], SkFixedToFloat(g));
+ } else {
+ // Here's where the real parametric gammas start. There are many
+ // permutations of the same equations.
+ //
+ // Y = (aX + b)^g + c for X >= d
+ // Y = eX + f otherwise
+ //
+ // We will fill in with zeros as necessary to always match the above form.
+ float g = 0.0f, a = 0.0f, b = 0.0f, c = 0.0f, d = 0.0f, e = 0.0f, f = 0.0f;
+ switch(format) {
+ case kGAB_ParaCurveType: {
+ tagBytes = 12 + 12;
+ if (len < tagBytes) {
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return false;
+ }
+
+ // Y = (aX + b)^g for X >= -b/a
+ // Y = 0 otherwise
+ g = SkFixedToFloat(read_big_endian_int(src + 12));
+ a = SkFixedToFloat(read_big_endian_int(src + 16));
+ if (0.0f == a) {
+ return false;
+ }
+
+ b = SkFixedToFloat(read_big_endian_int(src + 20));
+ d = -b / a;
+ break;
+ }
+ case kGABC_ParaCurveType:
+ tagBytes = 12 + 16;
+ if (len < tagBytes) {
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return false;
+ }
+
+ // Y = (aX + b)^g + c for X >= -b/a
+ // Y = c otherwise
+ g = SkFixedToFloat(read_big_endian_int(src + 12));
+ a = SkFixedToFloat(read_big_endian_int(src + 16));
+ if (0.0f == a) {
+ return false;
+ }
+
+ b = SkFixedToFloat(read_big_endian_int(src + 20));
+ c = SkFixedToFloat(read_big_endian_int(src + 24));
+ d = -b / a;
+ f = c;
+ break;
+ case kGABDE_ParaCurveType:
+ tagBytes = 12 + 20;
+ if (len < tagBytes) {
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return false;
+ }
+
+ // Y = (aX + b)^g for X >= d
+ // Y = cX otherwise
+ g = SkFixedToFloat(read_big_endian_int(src + 12));
+ a = SkFixedToFloat(read_big_endian_int(src + 16));
+ b = SkFixedToFloat(read_big_endian_int(src + 20));
+ d = SkFixedToFloat(read_big_endian_int(src + 28));
+ e = SkFixedToFloat(read_big_endian_int(src + 24));
+ break;
+ case kGABCDEF_ParaCurveType:
+ tagBytes = 12 + 28;
+ if (len < tagBytes) {
+ SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+ return false;
+ }
+
+ // Y = (aX + b)^g + c for X >= d
+ // Y = eX + f otherwise
+ // NOTE: The ICC spec writes "cX" in place of "eX" but I think
+ // it's a typo.
+ g = SkFixedToFloat(read_big_endian_int(src + 12));
+ a = SkFixedToFloat(read_big_endian_int(src + 16));
+ b = SkFixedToFloat(read_big_endian_int(src + 20));
+ c = SkFixedToFloat(read_big_endian_int(src + 24));
+ d = SkFixedToFloat(read_big_endian_int(src + 28));
+ e = SkFixedToFloat(read_big_endian_int(src + 32));
+ f = SkFixedToFloat(read_big_endian_int(src + 36));
+ break;
+ default:
+ SkColorSpacePrintf("Invalid parametric curve type\n");
+ return false;
+ }
+
+ // Recognize and simplify a very common parametric representation of sRGB gamma.
+ if (color_space_almost_equal(0.9479f, a) &&
+ color_space_almost_equal(0.0521f, b) &&
+ color_space_almost_equal(0.0000f, c) &&
+ color_space_almost_equal(0.0405f, d) &&
+ color_space_almost_equal(0.0774f, e) &&
+ color_space_almost_equal(0.0000f, f) &&
+ color_space_almost_equal(2.4000f, g)) {
+ gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
+ } else {
+ // Fail on invalid gammas.
+ if (d <= 0.0f) {
+ // Y = (aX + b)^g + c for always
+ if (0.0f == a || 0.0f == g) {
+ SkColorSpacePrintf("A or G is zero, constant gamma function "
+ "is nonsense");
+ return false;
+ }
+ } else if (d >= 1.0f) {
+ // Y = eX + f for always
+ if (0.0f == e) {
+ SkColorSpacePrintf("E is zero, constant gamma function is "
+ "nonsense");
+ return false;
+ }
+ } else if ((0.0f == a || 0.0f == g) && 0.0f == e) {
+ SkColorSpacePrintf("A or G, and E are zero, constant gamma function "
+ "is nonsense");
+ return false;
+ }
+
+ gammas[i].fG = g;
+ gammas[i].fA = a;
+ gammas[i].fB = b;
+ gammas[i].fC = c;
+ gammas[i].fD = d;
+ gammas[i].fE = e;
+ gammas[i].fF = f;
+ }
+ }
+
+ break;
+ }
+ default:
+ SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
+ return false;
+ }
+
+ // Ensure that we have successfully read a gamma representation.
+ SkASSERT(gammas[i].isNamed() || gammas[i].isValue() || gammas[i].isTable() ||
+ gammas[i].isParametric());
+
+ // Adjust src and len if there is another gamma curve to load.
+ if (i != numGammas - 1) {
+ // Each curve is padded to 4-byte alignment.
+ tagBytes = SkAlign4(tagBytes);
+ if (len < tagBytes) {
+ return false;
+ }
+
+ src += tagBytes;
+ len -= tagBytes;
+ }
+ }
+
+ return true;
+}
+
+static constexpr uint32_t kTAG_AtoBType = SkSetFourByteTag('m', 'A', 'B', ' ');
+
+bool load_color_lut(SkColorLookUpTable* colorLUT, uint32_t inputChannels, uint32_t outputChannels,
+ const uint8_t* src, size_t len) {
+ // 16 bytes reserved for grid points, 2 for precision, 2 for padding.
+ // The color LUT data follows after this header.
+ static constexpr uint32_t kColorLUTHeaderSize = 20;
+ if (len < kColorLUTHeaderSize) {
+ SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len);
+ return false;
+ }
+ size_t dataLen = len - kColorLUTHeaderSize;
+
+ SkASSERT(3 == inputChannels && 3 == outputChannels);
+ colorLUT->fInputChannels = inputChannels;
+ colorLUT->fOutputChannels = outputChannels;
+ uint32_t numEntries = 1;
+ for (uint32_t i = 0; i < inputChannels; i++) {
+ colorLUT->fGridPoints[i] = src[i];
+ if (0 == src[i]) {
+ SkColorSpacePrintf("Each input channel must have at least one grid point.");
+ return false;
+ }
+
+ if (!safe_mul(numEntries, src[i], &numEntries)) {
+ SkColorSpacePrintf("Too many entries in Color LUT.");
+ return false;
+ }
+ }
+
+ if (!safe_mul(numEntries, outputChannels, &numEntries)) {
+ SkColorSpacePrintf("Too many entries in Color LUT.");
+ return false;
+ }
+
+ // Space is provided for a maximum of the 16 input channels. Now we determine the precision
+ // of the table values.
+ uint8_t precision = src[16];
+ 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.\n");
+ return false;
+ }
+
+ uint32_t clutBytes;
+ if (!safe_mul(numEntries, precision, &clutBytes)) {
+ SkColorSpacePrintf("Too many entries in Color LUT.");
+ return false;
+ }
+
+ if (dataLen < clutBytes) {
+ SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len);
+ return false;
+ }
+
+ // Movable struct colorLUT has ownership of fTable.
+ colorLUT->fTable = std::unique_ptr<float[]>(new float[numEntries]);
+ const uint8_t* ptr = src + kColorLUTHeaderSize;
+ for (uint32_t i = 0; i < numEntries; i++, ptr += precision) {
+ if (1 == precision) {
+ colorLUT->fTable[i] = ((float) ptr[i]) / 255.0f;
+ } else {
+ colorLUT->fTable[i] = ((float) read_big_endian_short(ptr)) / 65535.0f;
+ }
+ }
+
+ return true;
+}
+
+bool load_matrix(SkMatrix44* toXYZ, const uint8_t* src, size_t len) {
+ if (len < 48) {
+ SkColorSpacePrintf("Matrix tag is too small (%d bytes).", len);
+ return false;
+ }
+
+ // For this matrix to behave like our "to XYZ D50" matrices, it needs to be scaled.
+ constexpr float scale = 65535.0 / 32768.0;
+ float array[16];
+ array[ 0] = scale * SkFixedToFloat(read_big_endian_int(src));
+ array[ 1] = scale * SkFixedToFloat(read_big_endian_int(src + 4));
+ array[ 2] = scale * SkFixedToFloat(read_big_endian_int(src + 8));
+ array[ 3] = scale * SkFixedToFloat(read_big_endian_int(src + 36)); // translate R
+ array[ 4] = scale * SkFixedToFloat(read_big_endian_int(src + 12));
+ array[ 5] = scale * SkFixedToFloat(read_big_endian_int(src + 16));
+ array[ 6] = scale * SkFixedToFloat(read_big_endian_int(src + 20));
+ array[ 7] = scale * SkFixedToFloat(read_big_endian_int(src + 40)); // translate G
+ array[ 8] = scale * SkFixedToFloat(read_big_endian_int(src + 24));
+ array[ 9] = scale * SkFixedToFloat(read_big_endian_int(src + 28));
+ array[10] = scale * SkFixedToFloat(read_big_endian_int(src + 32));
+ array[11] = scale * SkFixedToFloat(read_big_endian_int(src + 44)); // translate B
+ array[12] = 0.0f;
+ array[13] = 0.0f;
+ array[14] = 0.0f;
+ array[15] = 1.0f;
+ toXYZ->setColMajorf(array);
+ return true;
+}
+
+bool load_a2b0(SkColorLookUpTable* colorLUT, SkGammaCurve* gammas, SkMatrix44* toXYZ,
+ const uint8_t* src, size_t len) {
+ if (len < 32) {
+ SkColorSpacePrintf("A to B tag is too small (%d bytes).", len);
+ return false;
+ }
+
+ uint32_t type = read_big_endian_uint(src);
+ if (kTAG_AtoBType != type) {
+ // FIXME (msarett): Need to support lut8Type and lut16Type.
+ SkColorSpacePrintf("Unsupported A to B tag type.\n");
+ return false;
+ }
+
+ // Read the number of channels. The four bytes that we skipped are reserved and
+ // must be zero.
+ uint8_t inputChannels = src[8];
+ uint8_t outputChannels = src[9];
+ if (3 != inputChannels || 3 != outputChannels) {
+ // We only handle (supposedly) RGB inputs and RGB outputs. The numbers of input
+ // channels and output channels both must be 3.
+ SkColorSpacePrintf("Input and output channels must equal 3 in A to B tag.\n");
+ return false;
+ }
+
+ // Read the offsets of each element in the A to B tag. With the exception of A curves and
+ // B curves (which we do not yet support), we will handle these elements in the order in
+ // which they should be applied (rather than the order in which they occur in the tag).
+ // If the offset is non-zero it indicates that the element is present.
+ uint32_t offsetToACurves = read_big_endian_int(src + 28);
+ uint32_t offsetToBCurves = read_big_endian_int(src + 12);
+ if ((0 != offsetToACurves) || (0 != offsetToBCurves)) {
+ // FIXME (msarett): Handle A and B curves.
+ // Note that the A curve is technically required in order to have a color LUT.
+ // However, all the A curves I have seen so far have are just placeholders that
+ // don't actually transform the data.
+ SkColorSpacePrintf("Ignoring A and/or B curve. Output may be wrong.\n");
+ }
+
+ uint32_t offsetToColorLUT = read_big_endian_int(src + 24);
+ if (0 != offsetToColorLUT && offsetToColorLUT < len) {
+ if (!load_color_lut(colorLUT, inputChannels, outputChannels, src + offsetToColorLUT,
+ len - offsetToColorLUT)) {
+ SkColorSpacePrintf("Failed to read color LUT from A to B tag.\n");
+ }
+ }
+
+ uint32_t offsetToMCurves = read_big_endian_int(src + 20);
+ if (0 != offsetToMCurves && offsetToMCurves < len) {
+ if (!load_gammas(gammas, outputChannels, src + offsetToMCurves, len - offsetToMCurves)) {
+ SkColorSpacePrintf("Failed to read M curves from A to B tag. Using linear gamma.\n");
+ gammas[0].fNamed = SkColorSpace::kLinear_GammaNamed;
+ gammas[1].fNamed = SkColorSpace::kLinear_GammaNamed;
+ gammas[2].fNamed = SkColorSpace::kLinear_GammaNamed;
+ }
+ }
+
+ uint32_t offsetToMatrix = read_big_endian_int(src + 16);
+ if (0 != offsetToMatrix && offsetToMatrix < len) {
+ if (!load_matrix(toXYZ, src + offsetToMatrix, len - offsetToMatrix)) {
+ SkColorSpacePrintf("Failed to read matrix from A to B tag.\n");
+ toXYZ->setIdentity();
+ }
+ }
+
+ return true;
+}
+
+sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* input, size_t len) {
+ if (!input || len < kICCHeaderSize) {
+ return_null("Data is null or not large enough to contain an ICC profile");
+ }
+
+ // Create our own copy of the input.
+ void* memory = sk_malloc_throw(len);
+ memcpy(memory, input, len);
+ sk_sp<SkData> data = SkData::MakeFromMalloc(memory, len);
+ const void* base = data->data();
+ const uint8_t* ptr = (const uint8_t*) 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<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");
+ }
+ }
+
+ switch (header.fInputColorSpace) {
+ case kRGB_ColorSpace: {
+ // Recognize the rXYZ, gXYZ, and bXYZ tags.
+ 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) {
+ float toXYZ[9];
+ if (!load_xyz(&toXYZ[0], r->addr((const uint8_t*) base), r->fLength) ||
+ !load_xyz(&toXYZ[3], g->addr((const uint8_t*) base), g->fLength) ||
+ !load_xyz(&toXYZ[6], b->addr((const uint8_t*) base), b->fLength))
+ {
+ return_null("Need valid rgb tags for XYZ space");
+ }
+ SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
+ mat.set3x3RowMajorf(toXYZ);
+
+ // It is not uncommon to see missing or empty gamma tags. This indicates
+ // that we should use unit gamma.
+ SkGammaCurve curves[3];
+ 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_gammas(&curves[0], 1, r->addr((const uint8_t*) base), r->fLength))
+ {
+ SkColorSpacePrintf("Failed to read R gamma tag.\n");
+ curves[0].fNamed = SkColorSpace::kLinear_GammaNamed;
+ }
+ if (!g || !load_gammas(&curves[1], 1, g->addr((const uint8_t*) base), g->fLength))
+ {
+ SkColorSpacePrintf("Failed to read G gamma tag.\n");
+ curves[1].fNamed = SkColorSpace::kLinear_GammaNamed;
+ }
+ if (!b || !load_gammas(&curves[2], 1, b->addr((const uint8_t*) base), b->fLength))
+ {
+ SkColorSpacePrintf("Failed to read B gamma tag.\n");
+ curves[2].fNamed = SkColorSpace::kLinear_GammaNamed;
+ }
+
+ GammaNamed gammaNamed = SkGammas::Named(curves);
+ if (kNonStandard_GammaNamed == gammaNamed) {
+ sk_sp<SkGammas> gammas = sk_make_sp<SkGammas>(std::move(curves[0]),
+ std::move(curves[1]),
+ std::move(curves[2]));
+ return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, std::move(gammas),
+ mat, std::move(data)));
+ } else {
+ return SkColorSpace_Base::NewRGB(gammaNamed, mat);
+ }
+ }
+
+ // Recognize color profile specified by A2B0 tag.
+ const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0);
+ if (a2b0) {
+ sk_sp<SkColorLookUpTable> colorLUT = sk_make_sp<SkColorLookUpTable>();
+ SkGammaCurve curves[3];
+ SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
+ if (!load_a2b0(colorLUT.get(), curves, &toXYZ, a2b0->addr((const uint8_t*) base),
+ a2b0->fLength)) {
+ return_null("Failed to parse A2B0 tag");
+ }
+
+ GammaNamed gammaNamed = SkGammas::Named(curves);
+ colorLUT = colorLUT->fTable ? colorLUT : nullptr;
+ if (colorLUT || kNonStandard_GammaNamed == gammaNamed) {
+ sk_sp<SkGammas> gammas = sk_make_sp<SkGammas>(std::move(curves[0]),
+ std::move(curves[1]),
+ std::move(curves[2]));
+
+ return sk_sp<SkColorSpace>(new SkColorSpace_Base(std::move(colorLUT),
+ std::move(gammas), toXYZ,
+ std::move(data)));
+ } else {
+ return SkColorSpace_Base::NewRGB(gammaNamed, toXYZ);
+ }
+ }
+ }
+ default:
+ break;
+ }
+
+ return_null("ICC profile contains unsupported colorspace");
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+// We will write a profile with the minimum nine required tags.
+static constexpr uint32_t kICCNumEntries = 9;
+
+static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c');
+static constexpr uint32_t kTAG_desc_Bytes = 12;
+static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize + kICCNumEntries*kICCTagTableEntrySize;
+
+static constexpr uint32_t kTAG_XYZ_Bytes = 20;
+static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes;
+static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes;
+static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes;
+
+static constexpr uint32_t kTAG_TRC_Bytes = 14;
+static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes;
+static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset + SkAlign4(kTAG_TRC_Bytes);
+static constexpr uint32_t kTAG_bTRC_Offset = kTAG_gTRC_Offset + SkAlign4(kTAG_TRC_Bytes);
+
+static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't');
+static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + SkAlign4(kTAG_TRC_Bytes);
+
+static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't');
+static constexpr uint32_t kTAG_cprt_Bytes = 12;
+static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes;
+
+static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes;
+
+static constexpr uint32_t gICCHeader[kICCHeaderSize / 4] {
+ SkEndian_SwapBE32(kICCProfileSize), // Size of the profile
+ 0, // Preferred CMM type (ignored)
+ SkEndian_SwapBE32(0x02100000), // Version 2.1
+ SkEndian_SwapBE32(kDisplay_Profile), // Display device profile
+ SkEndian_SwapBE32(kRGB_ColorSpace), // RGB input color space
+ SkEndian_SwapBE32(kXYZ_PCSSpace), // XYZ profile connection space
+ 0, 0, 0, // Date and time (ignored)
+ SkEndian_SwapBE32(kACSP_Signature), // Profile signature
+ 0, // Platform target (ignored)
+ 0x00000000, // Flags: not embedded, can be used independently
+ 0, // Device manufacturer (ignored)
+ 0, // Device model (ignored)
+ 0, 0, // Device attributes (ignored)
+ SkEndian_SwapBE32(1), // Relative colorimetric rendering intent
+ SkEndian_SwapBE32(0x0000f6d6), // D50 standard illuminant (X)
+ SkEndian_SwapBE32(0x00010000), // D50 standard illuminant (Y)
+ SkEndian_SwapBE32(0x0000d32d), // D50 standard illuminant (Z)
+ 0, // Profile creator (ignored)
+ 0, 0, 0, 0, // Profile id checksum (ignored)
+ 0, 0, 0, 0, 0, 0, 0, // Reserved (ignored)
+ SkEndian_SwapBE32(kICCNumEntries), // Number of tags
+};
+
+static constexpr uint32_t gICCTagTable[3 * kICCNumEntries] {
+ // Profile description
+ SkEndian_SwapBE32(kTAG_desc),
+ SkEndian_SwapBE32(kTAG_desc_Offset),
+ SkEndian_SwapBE32(kTAG_desc_Bytes),
+
+ // rXYZ
+ SkEndian_SwapBE32(kTAG_rXYZ),
+ SkEndian_SwapBE32(kTAG_rXYZ_Offset),
+ SkEndian_SwapBE32(kTAG_XYZ_Bytes),
+
+ // gXYZ
+ SkEndian_SwapBE32(kTAG_gXYZ),
+ SkEndian_SwapBE32(kTAG_gXYZ_Offset),
+ SkEndian_SwapBE32(kTAG_XYZ_Bytes),
+
+ // bXYZ
+ SkEndian_SwapBE32(kTAG_bXYZ),
+ SkEndian_SwapBE32(kTAG_bXYZ_Offset),
+ SkEndian_SwapBE32(kTAG_XYZ_Bytes),
+
+ // rTRC
+ SkEndian_SwapBE32(kTAG_rTRC),
+ SkEndian_SwapBE32(kTAG_rTRC_Offset),
+ SkEndian_SwapBE32(kTAG_TRC_Bytes),
+
+ // gTRC
+ SkEndian_SwapBE32(kTAG_gTRC),
+ SkEndian_SwapBE32(kTAG_gTRC_Offset),
+ SkEndian_SwapBE32(kTAG_TRC_Bytes),
+
+ // bTRC
+ SkEndian_SwapBE32(kTAG_bTRC),
+ SkEndian_SwapBE32(kTAG_bTRC_Offset),
+ SkEndian_SwapBE32(kTAG_TRC_Bytes),
+
+ // White point
+ SkEndian_SwapBE32(kTAG_wtpt),
+ SkEndian_SwapBE32(kTAG_wtpt_Offset),
+ SkEndian_SwapBE32(kTAG_XYZ_Bytes),
+
+ // Copyright
+ SkEndian_SwapBE32(kTAG_cprt),
+ SkEndian_SwapBE32(kTAG_cprt_Offset),
+ SkEndian_SwapBE32(kTAG_cprt_Bytes),
+};
+
+static constexpr uint32_t kTAG_TextType = SkSetFourByteTag('m', 'l', 'u', 'c');
+static constexpr uint32_t gEmptyTextTag[3] {
+ SkEndian_SwapBE32(kTAG_TextType), // Type signature
+ 0, // Reserved
+ 0, // Zero records
+};
+
+static void write_xyz_tag(uint32_t* ptr, const SkMatrix44& toXYZ, int row) {
+ ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
+ ptr[1] = 0;
+ ptr[2] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 0)));
+ ptr[3] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 1)));
+ ptr[4] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(row, 2)));
+}
+
+static void write_trc_tag(uint32_t* ptr, float value) {
+ ptr[0] = SkEndian_SwapBE32(kTAG_CurveType);
+ ptr[1] = 0;
+
+ // Gamma will be specified with a single value.
+ ptr[2] = SkEndian_SwapBE32(1);
+
+ // Convert gamma to 16-bit fixed point.
+ uint16_t* ptr16 = (uint16_t*) (ptr + 3);
+ ptr16[0] = SkEndian_SwapBE16((uint16_t) (value * 256.0f));
+
+ // Pad tag with zero.
+ ptr16[1] = 0;
+}
+
+static float get_gamma_value(const SkGammaCurve* curve) {
+ switch (curve->fNamed) {
+ case SkColorSpace::kSRGB_GammaNamed:
+ // FIXME (msarett):
+ // kSRGB cannot be represented by a value. Here we fall through to 2.2f,
+ // which is a close guess. To be more accurate, we need to represent sRGB
+ // gamma with a parametric curve.
+ case SkColorSpace::k2Dot2Curve_GammaNamed:
+ return 2.2f;
+ case SkColorSpace::kLinear_GammaNamed:
+ return 1.0f;
+ default:
+ SkASSERT(curve->isValue());
+ return curve->fValue;
+ }
+}
+
+sk_sp<SkData> SkColorSpace_Base::writeToICC() const {
+ // Return if this object was created from a profile, or if we have already serialized
+ // the profile.
+ if (fProfileData) {
+ return fProfileData;
+ }
+
+ // The client may create an SkColorSpace using an SkMatrix44, but currently we only
+ // support writing profiles with 3x3 matrices.
+ // TODO (msarett): Fix this!
+ if (0.0f != fToXYZD50.getFloat(3, 0) || 0.0f != fToXYZD50.getFloat(3, 1) ||
+ 0.0f != fToXYZD50.getFloat(3, 2) || 0.0f != fToXYZD50.getFloat(0, 3) ||
+ 0.0f != fToXYZD50.getFloat(1, 3) || 0.0f != fToXYZD50.getFloat(2, 3))
+ {
+ return nullptr;
+ }
+
+ SkAutoMalloc profile(kICCProfileSize);
+ uint8_t* ptr = (uint8_t*) profile.get();
+
+ // Write profile header
+ memcpy(ptr, gICCHeader, sizeof(gICCHeader));
+ ptr += sizeof(gICCHeader);
+
+ // Write tag table
+ memcpy(ptr, gICCTagTable, sizeof(gICCTagTable));
+ ptr += sizeof(gICCTagTable);
+
+ // Write profile description tag
+ memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag));
+ ptr += sizeof(gEmptyTextTag);
+
+ // Write XYZ tags
+ write_xyz_tag((uint32_t*) ptr, fToXYZD50, 0);
+ ptr += kTAG_XYZ_Bytes;
+ write_xyz_tag((uint32_t*) ptr, fToXYZD50, 1);
+ ptr += kTAG_XYZ_Bytes;
+ write_xyz_tag((uint32_t*) ptr, fToXYZD50, 2);
+ ptr += kTAG_XYZ_Bytes;
+
+ // Write TRC tags
+ GammaNamed gammaNamed = this->gammaNamed();
+ if (kNonStandard_GammaNamed == gammaNamed) {
+ write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->fRed));
+ ptr += SkAlign4(kTAG_TRC_Bytes);
+ write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->fGreen));
+ ptr += SkAlign4(kTAG_TRC_Bytes);
+ write_trc_tag((uint32_t*) ptr, get_gamma_value(&as_CSB(this)->fGammas->fBlue));
+ ptr += SkAlign4(kTAG_TRC_Bytes);
+ } else {
+ switch (gammaNamed) {
+ case SkColorSpace::kSRGB_GammaNamed:
+ // FIXME (msarett):
+ // kSRGB cannot be represented by a value. Here we fall through to 2.2f,
+ // which is a close guess. To be more accurate, we need to represent sRGB
+ // gamma with a parametric curve.
+ case SkColorSpace::k2Dot2Curve_GammaNamed:
+ write_trc_tag((uint32_t*) ptr, 2.2f);
+ ptr += SkAlign4(kTAG_TRC_Bytes);
+ write_trc_tag((uint32_t*) ptr, 2.2f);
+ ptr += SkAlign4(kTAG_TRC_Bytes);
+ write_trc_tag((uint32_t*) ptr, 2.2f);
+ ptr += SkAlign4(kTAG_TRC_Bytes);
+ break;
+ case SkColorSpace::kLinear_GammaNamed:
+ write_trc_tag((uint32_t*) ptr, 1.0f);
+ ptr += SkAlign4(kTAG_TRC_Bytes);
+ write_trc_tag((uint32_t*) ptr, 1.0f);
+ ptr += SkAlign4(kTAG_TRC_Bytes);
+ write_trc_tag((uint32_t*) ptr, 1.0f);
+ ptr += SkAlign4(kTAG_TRC_Bytes);
+ break;
+ default:
+ SkASSERT(false);
+ break;
+ }
+ }
+
+ // Write white point tag
+ uint32_t* ptr32 = (uint32_t*) ptr;
+ ptr32[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
+ ptr32[1] = 0;
+ // TODO (msarett): These values correspond to the D65 white point. This may not always be
+ // correct.
+ ptr32[2] = SkEndian_SwapBE32(0x0000f351);
+ ptr32[3] = SkEndian_SwapBE32(0x00010000);
+ ptr32[4] = SkEndian_SwapBE32(0x000116cc);
+ ptr += kTAG_XYZ_Bytes;
+
+ // Write copyright tag
+ memcpy(ptr, gEmptyTextTag, sizeof(gEmptyTextTag));
+
+ // TODO (msarett): Should we try to hold onto the data so we can return immediately if
+ // the client calls again?
+ return SkData::MakeFromMalloc(profile.release(), kICCProfileSize);
+}