diff options
author | 2016-05-02 11:12:14 -0700 | |
---|---|---|
committer | 2016-05-02 11:12:14 -0700 | |
commit | ffc2aea3cb6981a5cc26f6c0f2ebf889ca5eb73f (patch) | |
tree | 9e66bb7012cea8b36482936760cb3d7f373351bf | |
parent | c578b06319f271ac46ac78e3b86f74fbceed360e (diff) |
Introduce SkGammas type to represent ICC gamma curves
BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1928123002
Committed: https://skia.googlesource.com/skia/+/7b2c6dd8c918209cb92e1338905d511c68da3eb2
Review-Url: https://codereview.chromium.org/1928123002
-rw-r--r-- | src/codec/SkPngCodec.cpp | 20 | ||||
-rw-r--r-- | src/core/SkColorSpace.cpp | 103 | ||||
-rw-r--r-- | src/core/SkColorSpace.h | 118 | ||||
-rw-r--r-- | tests/ColorSpaceTest.cpp | 24 |
4 files changed, 163 insertions, 102 deletions
diff --git a/src/codec/SkPngCodec.cpp b/src/codec/SkPngCodec.cpp index 1ad7f8006e..b34c80c6ed 100644 --- a/src/codec/SkPngCodec.cpp +++ b/src/codec/SkPngCodec.cpp @@ -210,7 +210,7 @@ sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr) { png_fixed_point XYZ[9]; SkFloat3x3 toXYZD50; png_fixed_point gamma; - SkFloat3 gammas; + SkColorSpace::SkGammas gammas; if (png_get_cHRM_XYZ_fixed(png_ptr, info_ptr, &XYZ[0], &XYZ[1], &XYZ[2], &XYZ[3], &XYZ[4], &XYZ[5], &XYZ[6], &XYZ[7], &XYZ[8])) { @@ -225,16 +225,17 @@ sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr) { } if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) { - gammas.fVec[0] = gammas.fVec[1] = gammas.fVec[2] = - png_inverted_fixed_point_to_float(gamma); + float value = png_inverted_fixed_point_to_float(gamma); + gammas = SkColorSpace::SkGammas(value, value, value); + } else { - // If the image does not specify gamma, let's choose linear. Should we default - // to sRGB? Most images are intended to be sRGB (gamma = 2.2f). - gammas.fVec[0] = gammas.fVec[1] = gammas.fVec[2] = 1.0f; + // Default to sRGB (gamma = 2.2f) if the image has color space information, + // but does not specify gamma. + gammas = SkColorSpace::SkGammas(2.2f, 2.2f, 2.2f); } - return SkColorSpace::NewRGB(toXYZD50, gammas); + return SkColorSpace::NewRGB(toXYZD50, std::move(gammas)); } // Last, check for gamma. @@ -247,9 +248,10 @@ sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr) { toXYZD50.fMat[0] = toXYZD50.fMat[4] = toXYZD50.fMat[8] = 1.0f; // Set the gammas. - gammas.fVec[0] = gammas.fVec[1] = gammas.fVec[2] = png_inverted_fixed_point_to_float(gamma); + float value = png_inverted_fixed_point_to_float(gamma); + gammas = SkColorSpace::SkGammas(value, value, value); - return SkColorSpace::NewRGB(toXYZD50, gammas); + return SkColorSpace::NewRGB(toXYZD50, std::move(gammas)); } #endif // LIBPNG >= 1.6 diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp index 43c424f1dc..54495de940 100644 --- a/src/core/SkColorSpace.cpp +++ b/src/core/SkColorSpace.cpp @@ -23,29 +23,28 @@ void SkFloat3x3::dump() const { static int32_t gUniqueColorSpaceID; -SkColorSpace::SkColorSpace(const SkFloat3& gamma, const SkFloat3x3& toXYZD50, Named named) - : fGamma(gamma) +SkColorSpace::SkColorSpace(SkGammas gammas, const SkFloat3x3& toXYZD50, Named named) + : fGammas(std::move(gammas)) , fToXYZD50(toXYZD50) , fToXYZOffset({{ 0.0f, 0.0f, 0.0f }}) , fUniqueID(sk_atomic_inc(&gUniqueColorSpaceID)) , fNamed(named) {} -SkColorSpace::SkColorSpace(SkColorLookUpTable colorLUT, const SkFloat3& gamma, +SkColorSpace::SkColorSpace(SkColorLookUpTable colorLUT, SkGammas gammas, const SkFloat3x3& toXYZD50, const SkFloat3& toXYZOffset) : fColorLUT(std::move(colorLUT)) - , fGamma(gamma) + , fGammas(std::move(gammas)) , fToXYZD50(toXYZD50) , fToXYZOffset(toXYZOffset) , fUniqueID(sk_atomic_inc(&gUniqueColorSpaceID)) , fNamed(kUnknown_Named) {} -sk_sp<SkColorSpace> SkColorSpace::NewRGB(const SkFloat3x3& toXYZD50, const SkFloat3& gamma) { - return sk_sp<SkColorSpace>(new SkColorSpace(gamma, toXYZD50, kUnknown_Named)); +sk_sp<SkColorSpace> SkColorSpace::NewRGB(const SkFloat3x3& toXYZD50, SkGammas gammas) { + return sk_sp<SkColorSpace>(new SkColorSpace(std::move(gammas), toXYZD50, kUnknown_Named)); } -const SkFloat3 gSRGB_gamma {{ 2.2f, 2.2f, 2.2f }}; const SkFloat3x3 gSRGB_toXYZD50 {{ 0.4358f, 0.2224f, 0.0139f, // * R 0.3853f, 0.7170f, 0.0971f, // * G @@ -55,7 +54,8 @@ const SkFloat3x3 gSRGB_toXYZD50 {{ sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) { switch (named) { case kSRGB_Named: - return sk_sp<SkColorSpace>(new SkColorSpace(gSRGB_gamma, gSRGB_toXYZD50, kSRGB_Named)); + return sk_sp<SkColorSpace>(new SkColorSpace(SkGammas(2.2f, 2.2f, 2.2f), gSRGB_toXYZD50, + kSRGB_Named)); default: break; } @@ -264,9 +264,8 @@ 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'); -// 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) { +bool SkColorSpace::LoadGammas(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): @@ -289,7 +288,7 @@ static bool load_gammas(float* gammas, uint32_t numGammas, const uint8_t* src, s // 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; + gammas[i].fValue = 1.0f; break; } else if (len < 12 + 2 * count) { SkColorSpacePrintf("gamma tag is too small (%d bytes)", len); @@ -298,27 +297,31 @@ static bool load_gammas(float* gammas, uint32_t numGammas, const uint8_t* src, s const uint16_t* table = (const uint16_t*) (src + 12); if (1 == count) { - // Table entry is the exponent (bias 256). + // The table entry is the gamma (with a bias of 256). uint16_t value = read_big_endian_short((const uint8_t*) table); - gammas[i] = value / 256.0f; + gammas[i].fValue = value / 256.0f; SkColorSpacePrintf("gamma %d %g\n", value, *gamma); break; } - // Print the interpolation table. For now, we ignore this and guess 2.2f. + // Fill in the interpolation table. + // FIXME (msarett): + // We should recognize commonly occurring tables and just set gamma to 2.2f. + gammas[i].fTableSize = count; + gammas[i].fTable = std::unique_ptr<float[]>(new float[count]); 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].fTable[j] = + (read_big_endian_short((const uint8_t*) &table[j])) / 65535.0f; } - - gammas[i] = 2.2f; break; } case kTAG_ParaCurveType: // Guess 2.2f. + // FIXME (msarett): Handle parametric curves. SkColorSpacePrintf("parametric curve\n"); - gammas[i] = 2.2f; + gammas[i].fValue = 2.2f; + // Determine the size of the parametric curve tag. switch(read_big_endian_short(src + 8)) { case 0: tagBytes = 12 + 4; @@ -358,28 +361,19 @@ static bool load_gammas(float* gammas, uint32_t numGammas, const uint8_t* src, s } } - // 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; - } - } - - return false; + return true; } 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) { +bool SkColorSpace::LoadColorLUT(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); + SkASSERT(inputChannels <= SkColorLookUpTable::kMaxChannels && 3 == outputChannels); colorLUT->fInputChannels = inputChannels; colorLUT->fOutputChannels = outputChannels; uint32_t numEntries = 1; @@ -441,8 +435,8 @@ bool load_matrix(SkFloat3x3* toXYZ, SkFloat3* toXYZOffset, const uint8_t* src, s return true; } -bool load_a2b0(SkColorLookUpTable* colorLUT, SkFloat3* gamma, SkFloat3x3* toXYZ, - SkFloat3* toXYZOffset, const uint8_t* src, size_t len) { +bool SkColorSpace::LoadA2B0(SkColorLookUpTable* colorLUT, SkGammas* gammas, 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; @@ -460,7 +454,7 @@ bool load_a2b0(SkColorLookUpTable* colorLUT, SkFloat3* gamma, SkFloat3x3* toXYZ, uint8_t inputChannels = src[8]; uint8_t outputChannels = src[9]; if (0 == inputChannels || inputChannels > SkColorLookUpTable::kMaxChannels || - 0 < outputChannels || outputChannels > SkColorLookUpTable::kMaxChannels) { + 3 != outputChannels) { // 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"); @@ -483,16 +477,16 @@ bool load_a2b0(SkColorLookUpTable* colorLUT, SkFloat3* gamma, SkFloat3x3* toXYZ, 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)) { + if (!SkColorSpace::LoadColorLUT(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)) - { + if (!SkColorSpace::LoadGammas(&gammas->fRed, outputChannels, src + offsetToMCurves, + len - offsetToMCurves)) { SkColorSpacePrintf("Failed to read M curves from A to B tag.\n"); } } @@ -567,42 +561,39 @@ sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* base, size_t len) { // 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 }}; + SkGammas gammas; 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)) - { + if (!r || !SkColorSpace::LoadGammas(&gammas.fRed, 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)) - { + if (!g || !SkColorSpace::LoadGammas(&gammas.fGreen, 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)) - { + if (!b || !SkColorSpace::LoadGammas(&gammas.fBlue, 1, + b->addr((const uint8_t*) base), b->fLength)) { SkColorSpacePrintf("Failed to read B gamma tag.\n"); } - return SkColorSpace::NewRGB(toXYZ, gamma); + return SkColorSpace::NewRGB(toXYZ, std::move(gammas)); } // Recognize color profile specified by A2B0 tag. const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0); if (a2b0) { SkColorLookUpTable colorLUT; - SkFloat3 gamma; + SkGammas gammas; SkFloat3x3 toXYZ; SkFloat3 toXYZOffset; - if (!load_a2b0(&colorLUT, &gamma, &toXYZ, &toXYZOffset, - a2b0->addr((const uint8_t*) base), a2b0->fLength)) { + if (!SkColorSpace::LoadA2B0(&colorLUT, &gammas, &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)); + return sk_sp<SkColorSpace>(new SkColorSpace(std::move(colorLUT), std::move(gammas), + toXYZ, toXYZOffset)); } } diff --git a/src/core/SkColorSpace.h b/src/core/SkColorSpace.h index 2b3b15fe95..a1ccf13fe0 100644 --- a/src/core/SkColorSpace.h +++ b/src/core/SkColorSpace.h @@ -35,57 +35,121 @@ struct SkFloat3x3 { void dump() const; }; -struct SkColorLookUpTable { - static const uint8_t kMaxChannels = 16; - - uint8_t fInputChannels; - uint8_t fOutputChannels; - uint8_t fGridPoints[kMaxChannels]; - std::unique_ptr<float[]> fTable; - - SkColorLookUpTable() { - memset(this, 0, sizeof(struct SkColorLookUpTable)); - } - - SkColorLookUpTable(SkColorLookUpTable&& that) - : fInputChannels(that.fInputChannels) - , fOutputChannels(that.fOutputChannels) - , fTable(std::move(that.fTable)) - { - memcpy(fGridPoints, that.fGridPoints, kMaxChannels); - } -}; - class SkColorSpace : public SkRefCnt { +private: + struct SkGammaCurve { + bool isValue() const { + bool result = (0.0f != fValue); + SkASSERT(!result || (0 == fTableSize)); + return result; + } + + bool isTable() const { + bool result = (0 != fTableSize); + SkASSERT(!result || (0.0f == fValue)); + SkASSERT(!result || fTable); + return result; + } + + bool isParametric() const { return false; } + + // We have three different ways to represent gamma. + // (1) A single value: + float fValue; + + // (2) A lookup table: + uint32_t fTableSize; + std::unique_ptr<float[]> fTable; + + // (3) Parameters for a curve: + // FIXME (msarett): Handle parametric curves. + + SkGammaCurve() { + memset(this, 0, sizeof(struct SkGammaCurve)); + } + + SkGammaCurve(float value) + : fValue(value) + , fTableSize(0) + , fTable(nullptr) + {} + }; + + struct SkColorLookUpTable { + static const uint8_t kMaxChannels = 16; + + uint8_t fInputChannels; + uint8_t fOutputChannels; + uint8_t fGridPoints[kMaxChannels]; + std::unique_ptr<float[]> fTable; + + SkColorLookUpTable() { + memset(this, 0, sizeof(struct SkColorLookUpTable)); + } + }; + public: enum Named { kUnknown_Named, kSRGB_Named, }; + struct SkGammas { + public: + SkGammas(float red, float green, float blue) + : fRed(red) + , fGreen(green) + , fBlue(blue) + {} + + SkGammas() {} + + SkDEBUGCODE(float red() const { return fRed.fValue; }) + SkDEBUGCODE(float green() const { return fGreen.fValue; }) + SkDEBUGCODE(float blue() const { return fBlue.fValue; }) + + private: + SkGammaCurve fRed; + SkGammaCurve fGreen; + SkGammaCurve fBlue; + + friend class SkColorSpace; + }; + /** * Return a colorspace instance, given a 3x3 transform from linear_RGB to D50_XYZ * and the src-gamma, return a ColorSpace */ - static sk_sp<SkColorSpace> NewRGB(const SkFloat3x3& toXYZD50, const SkFloat3& gamma); + static sk_sp<SkColorSpace> NewRGB(const SkFloat3x3& toXYZD50, SkGammas gammas); static sk_sp<SkColorSpace> NewNamed(Named); static sk_sp<SkColorSpace> NewICC(const void*, size_t); - SkFloat3 gamma() const { return fGamma; } + const SkGammas& gammas() const { return fGammas; } SkFloat3x3 xyz() const { return fToXYZD50; } SkFloat3 xyzOffset() const { return fToXYZOffset; } Named named() const { return fNamed; } uint32_t uniqueID() const { return fUniqueID; } private: - SkColorSpace(const SkFloat3& gamma, const SkFloat3x3& toXYZ, Named); - SkColorSpace(SkColorLookUpTable colorLUT, const SkFloat3& gamma, const SkFloat3x3& toXYZ, - const SkFloat3& toXYZOffset); + static bool LoadGammas(SkGammaCurve* gammas, uint32_t num, const uint8_t* src, size_t len); + + + static bool LoadColorLUT(SkColorLookUpTable* colorLUT, uint32_t inputChannels, + uint32_t outputChannels, const uint8_t* src, size_t len); + + + static bool LoadA2B0(SkColorLookUpTable* colorLUT, SkGammas* gammas, SkFloat3x3* toXYZ, + SkFloat3* toXYZOffset, const uint8_t* src, size_t len); + + SkColorSpace(SkGammas gammas, const SkFloat3x3& toXYZ, Named); + + SkColorSpace(SkColorLookUpTable colorLUT, SkGammas gammas, + const SkFloat3x3& toXYZ, const SkFloat3& toXYZOffset); const SkColorLookUpTable fColorLUT; - const SkFloat3 fGamma; + const SkGammas fGammas; const SkFloat3x3 fToXYZD50; const SkFloat3 fToXYZOffset; diff --git a/tests/ColorSpaceTest.cpp b/tests/ColorSpaceTest.cpp index 313eed86f1..e5ecbc8346 100644 --- a/tests/ColorSpaceTest.cpp +++ b/tests/ColorSpaceTest.cpp @@ -35,12 +35,14 @@ DEF_TEST(ColorSpaceParsePngICCProfile, r) { SkColorSpace* colorSpace = codec->getColorSpace(); REPORTER_ASSERT(r, nullptr != colorSpace); - // No need to use almost equal here. The color profile that we have extracted - // actually has a table of gammas. And our current implementation guesses 2.2f. - SkFloat3 gammas = colorSpace->gamma(); - REPORTER_ASSERT(r, 2.2f == gammas.fVec[0]); - REPORTER_ASSERT(r, 2.2f == gammas.fVec[1]); - REPORTER_ASSERT(r, 2.2f == gammas.fVec[2]); + // The color profile that we have extracted has represents gamma with a lookup table. + // So we expect the gamma value to be zero. +#ifdef SK_DEBUG + const SkColorSpace::SkGammas& gammas = colorSpace->gammas(); + REPORTER_ASSERT(r, 0.0f == gammas.red()); + REPORTER_ASSERT(r, 0.0f == gammas.green()); + REPORTER_ASSERT(r, 0.0f == gammas.blue()); +#endif // These nine values were extracted from the color profile in isolation (before // we embedded it in the png). Here we check that we still extract the same values. @@ -75,10 +77,12 @@ DEF_TEST(ColorSpaceParseJpegICCProfile, r) { // It's important to use almost equal here. This profile sets gamma as // 563 / 256, which actually comes out to about 2.19922. - SkFloat3 gammas = colorSpace->gamma(); - REPORTER_ASSERT(r, almost_equal(2.2f, gammas.fVec[0])); - REPORTER_ASSERT(r, almost_equal(2.2f, gammas.fVec[1])); - REPORTER_ASSERT(r, almost_equal(2.2f, gammas.fVec[2])); +#ifdef SK_DEBUG + const SkColorSpace::SkGammas& gammas = colorSpace->gammas(); + REPORTER_ASSERT(r, almost_equal(2.2f, gammas.red())); + REPORTER_ASSERT(r, almost_equal(2.2f, gammas.green())); + REPORTER_ASSERT(r, almost_equal(2.2f, gammas.blue())); +#endif // These nine values were extracted from the color profile. Until we know any // better, we'll assume these are the right values and test that we continue |