| Index: src/core/SkColorSpace.cpp
|
| diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
|
| index 794d8a04ff4dfa6fa1887582478b00e9736b262d..71fb0de2a03995f685819e7710e41ee3768a9b3d 100644
|
| --- a/src/core/SkColorSpace.cpp
|
| +++ b/src/core/SkColorSpace.cpp
|
| @@ -88,9 +88,10 @@ void SkFloat3x3::dump() const {
|
|
|
| static int32_t gUniqueColorSpaceID;
|
|
|
| -SkColorSpace::SkColorSpace(const SkFloat3x3& toXYZD50, const SkFloat3& gamma, Named named)
|
| - : fToXYZD50(toXYZD50)
|
| - , fGamma(gamma)
|
| +SkColorSpace::SkColorSpace(const SkFloat3& gamma, const SkFloat3x3& toXYZD50, Named named)
|
| + : fGamma(gamma)
|
| + , fToXYZD50(toXYZD50)
|
| + , fToXYZOffset({{ 0.0f, 0.0f, 0.0f }})
|
| , fUniqueID(sk_atomic_inc(&gUniqueColorSpaceID))
|
| , fNamed(named)
|
| {
|
| @@ -102,6 +103,16 @@ SkColorSpace::SkColorSpace(const SkFloat3x3& toXYZD50, const SkFloat3& gamma, Na
|
| }
|
| }
|
|
|
| +SkColorSpace::SkColorSpace(SkColorLookUpTable colorLUT, const SkFloat3& gamma,
|
| + const SkFloat3x3& toXYZD50, const SkFloat3& toXYZOffset)
|
| + : fColorLUT(std::move(colorLUT))
|
| + , fGamma(gamma)
|
| + , fToXYZD50(toXYZD50)
|
| + , fToXYZOffset(toXYZOffset)
|
| + , fUniqueID(sk_atomic_inc(&gUniqueColorSpaceID))
|
| + , fNamed(kUnknown_Named)
|
| +{}
|
| +
|
| sk_sp<SkColorSpace> SkColorSpace::NewRGB(const SkFloat3x3& toXYZD50, const SkFloat3& gamma) {
|
| for (int i = 0; i < 3; ++i) {
|
| if (!SkFloatIsFinite(gamma.fVec[i]) || gamma.fVec[i] < 0) {
|
| @@ -120,7 +131,7 @@ sk_sp<SkColorSpace> SkColorSpace::NewRGB(const SkFloat3x3& toXYZD50, const SkFlo
|
| return nullptr;
|
| }
|
|
|
| - return sk_sp<SkColorSpace>(new SkColorSpace(toXYZD50, gamma, kUnknown_Named));
|
| + return sk_sp<SkColorSpace>(new SkColorSpace(gamma, toXYZD50, kUnknown_Named));
|
| }
|
|
|
| void SkColorSpace::dump() const {
|
| @@ -147,10 +158,10 @@ const SkFloat3x3 gSRGB_toXYZD50 {{
|
| sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
|
| switch (named) {
|
| case kDevice_Named:
|
| - return sk_sp<SkColorSpace>(new SkColorSpace(gDevice_toXYZD50, gDevice_gamma,
|
| + return sk_sp<SkColorSpace>(new SkColorSpace(gDevice_gamma, gDevice_toXYZD50,
|
| kDevice_Named));
|
| case kSRGB_Named:
|
| - return sk_sp<SkColorSpace>(new SkColorSpace(gSRGB_toXYZD50, gSRGB_gamma, kSRGB_Named));
|
| + return sk_sp<SkColorSpace>(new SkColorSpace(gSRGB_gamma, gSRGB_toXYZD50, kSRGB_Named));
|
| default:
|
| break;
|
| }
|
| @@ -335,14 +346,13 @@ struct ICCTag {
|
| }
|
| };
|
|
|
| -// 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');
|
| +static const uint32_t kTAG_A2B0 = SkSetFourByteTag('A', '2', 'B', '0');
|
|
|
| bool load_xyz(float dst[3], const uint8_t* src, size_t len) {
|
| if (len < 20) {
|
| @@ -360,53 +370,247 @@ bool load_xyz(float dst[3], const uint8_t* src, size_t len) {
|
| 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_uint(src);
|
| - switch (type) {
|
| - case kTAG_CurveType: {
|
| - uint32_t count = read_big_endian_int(src + 8);
|
| - if (0 == count) {
|
| - return false;
|
| - }
|
| +// FIXME (msarett):
|
| +// We need to handle the possibility that the gamma curve does not correspond to 2.2f.
|
| +static bool load_gammas(float* 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;
|
| + }
|
|
|
| - 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;
|
| + // 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 + count * 2;
|
| + 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] = 1.0f;
|
| + break;
|
| + } else if (len < 12 + 2 * count) {
|
| + SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
|
| + 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);
|
| + gammas[i] = value / 256.0f;
|
| + SkColorSpacePrintf("gamma %d %g\n", value, *gamma);
|
| + break;
|
| + }
|
| +
|
| + // Print the interpolation table. For now, we ignore this and guess 2.2f.
|
| + for (uint32_t j = 0; j < count; j++) {
|
| + SkColorSpacePrintf("curve[%d] %d\n", j,
|
| + read_big_endian_short((const uint8_t*) &table[j]));
|
| + }
|
| +
|
| + gammas[i] = 2.2f;
|
| + break;
|
| }
|
| + case kTAG_ParaCurveType:
|
| + // Guess 2.2f.
|
| + SkColorSpacePrintf("parametric curve\n");
|
| + gammas[i] = 2.2f;
|
| +
|
| + switch(read_big_endian_short(src + 8)) {
|
| + case 0:
|
| + tagBytes = 12 + 4;
|
| + break;
|
| + case 1:
|
| + tagBytes = 12 + 12;
|
| + break;
|
| + case 2:
|
| + tagBytes = 12 + 16;
|
| + break;
|
| + case 3:
|
| + tagBytes = 12 + 20;
|
| + break;
|
| + case 4:
|
| + tagBytes = 12 + 28;
|
| + break;
|
| + default:
|
| + SkColorSpacePrintf("Invalid parametric curve type\n");
|
| + return false;
|
| + }
|
| + break;
|
| + default:
|
| + SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
|
| + return false;
|
| + }
|
|
|
| - // Check length again if we have a table.
|
| - if (len < 12 + 2 * count) {
|
| - SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
|
| + // Adjust src and len if there is another gamma curve to load.
|
| + if (0 != numGammas) {
|
| + // Each curve is padded to 4-byte alignment.
|
| + tagBytes = SkAlign4(tagBytes);
|
| + if (len < tagBytes) {
|
| 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]));
|
| - }
|
| + src += tagBytes;
|
| + len -= tagBytes;
|
| + }
|
| + }
|
|
|
| - *gamma = 2.2f;
|
| + // If all of the gammas we encounter are 1.0f, indicate that we failed to load gammas.
|
| + // There is no need to apply a gamma of 1.0f.
|
| + for (uint32_t i = 0; i < numGammas; i++) {
|
| + if (1.0f != gammas[i]) {
|
| return true;
|
| }
|
| - case kTAG_ParaCurveType:
|
| - // Guess 2.2f.
|
| - SkColorSpacePrintf("parametric curve\n");
|
| - *gamma = 2.2f;
|
| - return true;
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +static const 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) {
|
| + if (len < 20) {
|
| + SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", len);
|
| + return false;
|
| + }
|
| +
|
| + SkASSERT(inputChannels <= SkColorLookUpTable::kMaxChannels &&
|
| + outputChannels <= SkColorLookUpTable::kMaxChannels);
|
| + colorLUT->fInputChannels = inputChannels;
|
| + colorLUT->fOutputChannels = outputChannels;
|
| + uint32_t numEntries = 1;
|
| + for (uint32_t i = 0; i < inputChannels; i++) {
|
| + colorLUT->fGridPoints[i] = src[i];
|
| + numEntries *= src[i];
|
| + }
|
| + numEntries *= outputChannels;
|
| +
|
| + // 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("Unsupported gamma tag type %d\n", type);
|
| + SkColorSpacePrintf("Color LUT precision must be 8-bit or 16-bit.\n", len);
|
| return false;
|
| }
|
| +
|
| + if (len < 20 + numEntries * precision) {
|
| + 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 + 20;
|
| + 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(SkFloat3x3* toXYZ, SkFloat3* toXYZOffset, const uint8_t* src, size_t len) {
|
| + if (len < 48) {
|
| + SkColorSpacePrintf("Matrix tag is too small (%d bytes).", len);
|
| + return false;
|
| + }
|
| +
|
| + toXYZ->fMat[0] = SkFixedToFloat(read_big_endian_int(src));
|
| + toXYZ->fMat[3] = SkFixedToFloat(read_big_endian_int(src + 4));
|
| + toXYZ->fMat[6] = SkFixedToFloat(read_big_endian_int(src + 8));
|
| + toXYZ->fMat[1] = SkFixedToFloat(read_big_endian_int(src + 12));
|
| + toXYZ->fMat[4] = SkFixedToFloat(read_big_endian_int(src + 16));
|
| + toXYZ->fMat[7] = SkFixedToFloat(read_big_endian_int(src + 20));
|
| + toXYZ->fMat[2] = SkFixedToFloat(read_big_endian_int(src + 24));
|
| + toXYZ->fMat[5] = SkFixedToFloat(read_big_endian_int(src + 28));
|
| + toXYZ->fMat[8] = SkFixedToFloat(read_big_endian_int(src + 32));
|
| + toXYZOffset->fVec[0] = SkFixedToFloat(read_big_endian_int(src + 36));
|
| + toXYZOffset->fVec[1] = SkFixedToFloat(read_big_endian_int(src + 40));
|
| + toXYZOffset->fVec[2] = SkFixedToFloat(read_big_endian_int(src + 44));
|
| + return true;
|
| +}
|
| +
|
| +bool load_a2b0(SkColorLookUpTable* colorLUT, SkFloat3* gamma, SkFloat3x3* toXYZ,
|
| + SkFloat3* toXYZOffset, 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 (0 == inputChannels || inputChannels > SkColorLookUpTable::kMaxChannels ||
|
| + 0 < outputChannels || outputChannels > SkColorLookUpTable::kMaxChannels) {
|
| + // The color LUT assumes that there are at most 16 input channels. For RGB
|
| + // profiles, output channels should be 3.
|
| + SkColorSpacePrintf("Too many input or output channels 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(gamma->fVec, outputChannels, src + offsetToMCurves, len - offsetToMCurves))
|
| + {
|
| + SkColorSpacePrintf("Failed to read M curves from A to B tag.\n");
|
| + }
|
| + }
|
| +
|
| + uint32_t offsetToMatrix = read_big_endian_int(src + 16);
|
| + if (0 != offsetToMatrix && offsetToMatrix < len) {
|
| + if (!load_matrix(toXYZ, toXYZOffset, src + offsetToMatrix, len - offsetToMatrix)) {
|
| + SkColorSpacePrintf("Failed to read matrix from A to B tag.\n");
|
| + }
|
| + }
|
| +
|
| + return true;
|
| }
|
|
|
| sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* base, size_t len) {
|
| @@ -452,38 +656,61 @@ sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* base, size_t len) {
|
| }
|
| }
|
|
|
| - // Load our XYZ and gamma matrices.
|
| - SkFloat3x3 toXYZ;
|
| - SkFloat3 gamma {{ 1.0f, 1.0f, 1.0f }};
|
| 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) {
|
| - return_null("Need rgb tags for XYZ space");
|
| + if (r && g && b) {
|
| + SkFloat3x3 toXYZ;
|
| + 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");
|
| + }
|
| +
|
| + // It is not uncommon to see missing or empty gamma tags. This indicates
|
| + // that we should use unit gamma.
|
| + SkFloat3 gamma {{ 1.0f, 1.0f, 1.0f }};
|
| + 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(&gamma.fVec[0], 1, r->addr((const uint8_t*) base), r->fLength))
|
| + {
|
| + SkColorSpacePrintf("Failed to read R gamma tag.\n");
|
| + }
|
| + if (!g ||
|
| + !load_gammas(&gamma.fVec[1], 1, g->addr((const uint8_t*) base), g->fLength))
|
| + {
|
| + SkColorSpacePrintf("Failed to read G gamma tag.\n");
|
| + }
|
| + if (!b ||
|
| + !load_gammas(&gamma.fVec[2], 1, b->addr((const uint8_t*) base), b->fLength))
|
| + {
|
| + SkColorSpacePrintf("Failed to read B gamma tag.\n");
|
| + }
|
| + return SkColorSpace::NewRGB(toXYZ, gamma);
|
| }
|
|
|
| - 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");
|
| + // Recognize color profile specified by A2B0 tag.
|
| + const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0);
|
| + if (a2b0) {
|
| + SkColorLookUpTable colorLUT;
|
| + SkFloat3 gamma;
|
| + SkFloat3x3 toXYZ;
|
| + SkFloat3 toXYZOffset;
|
| + if (!load_a2b0(&colorLUT, &gamma, &toXYZ, &toXYZOffset,
|
| + a2b0->addr((const uint8_t*) base), a2b0->fLength)) {
|
| + return_null("Failed to parse A2B0 tag");
|
| + }
|
| +
|
| + return sk_sp<SkColorSpace>(new SkColorSpace(std::move(colorLUT), gamma, toXYZ,
|
| + toXYZOffset));
|
| }
|
|
|
| - 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;
|
|
|