Differentiate between sRGBGamma and 2Dot2Gamma
BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2067833003
Review-Url: https://codereview.chromium.org/2067833003
diff --git a/src/codec/SkPngCodec.cpp b/src/codec/SkPngCodec.cpp
index 43d1580..985d61a 100644
--- a/src/codec/SkPngCodec.cpp
+++ b/src/codec/SkPngCodec.cpp
@@ -8,7 +8,7 @@
#include "SkBitmap.h"
#include "SkCodecPriv.h"
#include "SkColorPriv.h"
-#include "SkColorSpace.h"
+#include "SkColorSpace_Base.h"
#include "SkColorTable.h"
#include "SkMath.h"
#include "SkOpts.h"
@@ -172,6 +172,12 @@
return 1.0f / png_fixed_point_to_float(x);
}
+static constexpr float gSRGB_toXYZD50[] {
+ 0.4358f, 0.2224f, 0.0139f, // * R
+ 0.3853f, 0.7170f, 0.0971f, // * G
+ 0.1430f, 0.0606f, 0.7139f, // * B
+};
+
// Returns a colorSpace object that represents any color space information in
// the encoded data. If the encoded data contains no color space, this will
// return NULL.
@@ -223,6 +229,8 @@
for (int i = 0; i < 9; i++) {
toXYZD50[i] = png_fixed_point_to_float(XYZ[i]);
}
+ SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
+ mat.set3x3ColMajorf(toXYZD50);
if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) {
float value = png_inverted_fixed_point_to_float(gamma);
@@ -230,40 +238,34 @@
gammas[1] = value;
gammas[2] = value;
- } else {
- // Default to sRGB (gamma = 2.2f) if the image has color space information,
- // but does not specify gamma.
- gammas[0] = 2.2f;
- gammas[1] = 2.2f;
- gammas[2] = 2.2f;
+ return SkColorSpace_Base::NewRGB(gammas, mat);
}
- SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
- mat.set3x3ColMajorf(toXYZD50);
- return SkColorSpace::NewRGB(gammas, mat);
+ // Default to sRGB gamma if the image has color space information,
+ // but does not specify gamma.
+ return SkColorSpace::NewRGB(SkColorSpace::kSRGB_GammaNamed, mat);
}
// Last, check for gamma.
if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) {
- // Guess a default value for cHRM? Or should we just give up?
- // Here we use the identity matrix as a default.
-
// Set the gammas.
float value = png_inverted_fixed_point_to_float(gamma);
gammas[0] = value;
gammas[1] = value;
gammas[2] = value;
- return SkColorSpace::NewRGB(gammas, SkMatrix44::I());
+ // Since there is no cHRM, we will guess sRGB gamut.
+ SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
+ mat.set3x3ColMajorf(gSRGB_toXYZD50);
+
+ return SkColorSpace_Base::NewRGB(gammas, mat);
}
#endif // LIBPNG >= 1.6
- // Finally, what should we do if there is no color space information in the PNG?
- // The specification says that this indicates "gamma is unknown" and that the
- // "color is device dependent". I'm assuming we can represent this with NULL.
- // But should we guess sRGB? Most images are sRGB, even if they don't specify.
+ // Report that there is no color space information in the PNG. SkPngCodec is currently
+ // implemented to guess sRGB in this case.
return nullptr;
}
diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
index 69fada2..ec8de72 100644
--- a/src/core/SkColorSpace.cpp
+++ b/src/core/SkColorSpace.cpp
@@ -10,6 +10,8 @@
#include "SkEndian.h"
#include "SkOnce.h"
+#define SkColorSpacePrintf(...)
+
static bool color_space_almost_equal(float a, float b) {
return SkTAbs(a - b) < 0.01f;
}
@@ -22,18 +24,10 @@
, fNamed(named)
{}
-SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkGammas> gammas, const SkMatrix44& toXYZD50,
- Named named, sk_sp<SkData> profileData)
- : INHERITED(kNonStandard_GammaNamed, toXYZD50, named)
- , fGammas(std::move(gammas))
- , fProfileData(std::move(profileData))
-{}
-
-SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkGammas> gammas, GammaNamed gammaNamed,
- const SkMatrix44& toXYZD50, Named named,
+SkColorSpace_Base::SkColorSpace_Base(GammaNamed gammaNamed, const SkMatrix44& toXYZD50, Named named,
sk_sp<SkData> profileData)
: INHERITED(gammaNamed, toXYZD50, named)
- , fGammas(std::move(gammas))
+ , fGammas(nullptr)
, fProfileData(std::move(profileData))
{}
@@ -82,52 +76,61 @@
color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f);
}
-static SkOnce g2Dot2CurveGammasOnce;
-static SkGammas* g2Dot2CurveGammas;
-static SkOnce gLinearGammasOnce;
-static SkGammas* gLinearGammas;
-
-sk_sp<SkColorSpace> SkColorSpace::NewRGB(const float gammaVals[3], const SkMatrix44& toXYZD50) {
- return SkColorSpace_Base::NewRGB(gammaVals, toXYZD50, nullptr);
+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(const float gammaVals[3], const SkMatrix44& toXYZD50,
+sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(float values[3], const SkMatrix44& toXYZD50) {
+ SkGammaCurve curves[3];
+ set_gamma_value(&curves[0], values[0]);
+ set_gamma_value(&curves[1], values[1]);
+ set_gamma_value(&curves[2], values[2]);
+
+ GammaNamed gammaNamed = SkGammas::Named(curves);
+ if (kNonStandard_GammaNamed == gammaNamed) {
+ sk_sp<SkGammas> gammas(new SkGammas(std::move(curves[0]), std::move(curves[1]),
+ std::move(curves[2])));
+ return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, gammas, toXYZD50, nullptr));
+ }
+
+ return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50, nullptr);
+}
+
+sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(GammaNamed gammaNamed, const SkMatrix44& toXYZD50,
sk_sp<SkData> profileData) {
- sk_sp<SkGammas> gammas = nullptr;
- GammaNamed gammaNamed = kNonStandard_GammaNamed;
-
- // Check if we really have sRGB or Adobe RGB
- if (color_space_almost_equal(2.2f, gammaVals[0]) &&
- color_space_almost_equal(2.2f, gammaVals[1]) &&
- color_space_almost_equal(2.2f, gammaVals[2]))
- {
- g2Dot2CurveGammasOnce([] {
- g2Dot2CurveGammas = new SkGammas(2.2f, 2.2f, 2.2f);
- });
- gammas = sk_ref_sp(g2Dot2CurveGammas);
- gammaNamed = k2Dot2Curve_GammaNamed;
-
- if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
- return SkColorSpace::NewNamed(kSRGB_Named);
- } else if (xyz_almost_equal(toXYZD50, gAdobeRGB_toXYZD50)) {
- return SkColorSpace::NewNamed(kAdobeRGB_Named);
- }
- } else if (color_space_almost_equal(1.0f, gammaVals[0]) &&
- color_space_almost_equal(1.0f, gammaVals[1]) &&
- color_space_almost_equal(1.0f, gammaVals[2]))
- {
- gLinearGammasOnce([] {
- gLinearGammas = new SkGammas(1.0f, 1.0f, 1.0f);
- });
- gammas = sk_ref_sp(gLinearGammas);
- gammaNamed = kLinear_GammaNamed;
+ switch (gammaNamed) {
+ case kSRGB_GammaNamed:
+ if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
+ return SkColorSpace::NewNamed(kSRGB_Named);
+ }
+ break;
+ case k2Dot2Curve_GammaNamed:
+ if (xyz_almost_equal(toXYZD50, gAdobeRGB_toXYZD50)) {
+ return SkColorSpace::NewNamed(kAdobeRGB_Named);
+ }
+ break;
+ case kNonStandard_GammaNamed:
+ // This is not allowed.
+ return nullptr;
+ default:
+ break;
}
- if (!gammas) {
- gammas = sk_sp<SkGammas>(new SkGammas(gammaVals[0], gammaVals[1], gammaVals[2]));
- }
- return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammas, gammaNamed, toXYZD50, kUnknown_Named,
- std::move(profileData)));
+ return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammaNamed, toXYZD50, kUnknown_Named,
+ profileData));
+}
+
+sk_sp<SkColorSpace> SkColorSpace::NewRGB(GammaNamed gammaNamed, const SkMatrix44& toXYZD50) {
+ return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50, nullptr);
}
sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
@@ -138,28 +141,18 @@
switch (named) {
case kSRGB_Named: {
- g2Dot2CurveGammasOnce([] {
- g2Dot2CurveGammas = new SkGammas(2.2f, 2.2f, 2.2f);
- });
-
sRGBOnce([] {
SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
srgbToxyzD50.set3x3ColMajorf(gSRGB_toXYZD50);
- sRGB = new SkColorSpace_Base(sk_ref_sp(g2Dot2CurveGammas), k2Dot2Curve_GammaNamed,
- srgbToxyzD50, kSRGB_Named, nullptr);
+ sRGB = new SkColorSpace_Base(kSRGB_GammaNamed, srgbToxyzD50, kSRGB_Named, nullptr);
});
return sk_ref_sp(sRGB);
}
case kAdobeRGB_Named: {
- g2Dot2CurveGammasOnce([] {
- g2Dot2CurveGammas = new SkGammas(2.2f, 2.2f, 2.2f);
- });
-
adobeRGBOnce([] {
SkMatrix44 adobergbToxyzD50(SkMatrix44::kUninitialized_Constructor);
adobergbToxyzD50.set3x3ColMajorf(gAdobeRGB_toXYZD50);
- adobeRGB = new SkColorSpace_Base(sk_ref_sp(g2Dot2CurveGammas),
- k2Dot2Curve_GammaNamed, adobergbToxyzD50,
+ adobeRGB = new SkColorSpace_Base(k2Dot2Curve_GammaNamed, adobergbToxyzD50,
kAdobeRGB_Named, nullptr);
});
return sk_ref_sp(adobeRGB);
@@ -175,8 +168,6 @@
#include "SkFixed.h"
#include "SkTemplates.h"
-#define SkColorSpacePrintf(...)
-
#define return_if_false(pred, msg) \
do { \
if (!(pred)) { \
@@ -370,7 +361,7 @@
static constexpr uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C');
static constexpr uint32_t kTAG_A2B0 = SkSetFourByteTag('A', '2', 'B', '0');
-bool load_xyz(float dst[3], const uint8_t* src, size_t len) {
+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;
@@ -386,7 +377,7 @@
static constexpr uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', 'v');
static constexpr uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a');
-bool load_gammas(SkGammaCurve* gammas, uint32_t numGammas, const uint8_t* src, size_t len) {
+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):
@@ -418,7 +409,7 @@
// 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].fValue = 1.0f;
+ gammas[i].fNamed = SkColorSpace::kLinear_GammaNamed;
break;
} else if (len < tagBytes) {
SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
@@ -428,20 +419,16 @@
const uint16_t* table = (const uint16_t*) (src + 12);
if (1 == count) {
// The table entry is the gamma (with a bias of 256).
- uint16_t value = read_big_endian_short((const uint8_t*) table);
- gammas[i].fValue = value / 256.0f;
- if (0.0f == gammas[i].fValue) {
- SkColorSpacePrintf("Cannot have zero gamma value");
- return false;
- }
- SkColorSpacePrintf("gamma %d %g\n", value, gammas[i].fValue);
+ 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 curves and use a fast approximation.
+ // 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
- // a 2.2f curve see if we remain below an error threshold. At this time,
+ // 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.
@@ -454,7 +441,7 @@
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].fValue = 2.2f;
+ gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
break;
}
} else if (26 == count) {
@@ -465,7 +452,7 @@
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].fValue = 2.2f;
+ gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
break;
}
} else if (4096 == count) {
@@ -476,7 +463,7 @@
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].fValue = 2.2f;
+ gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
break;
}
}
@@ -510,7 +497,7 @@
// Y = X^g
int32_t g = read_big_endian_int(src + 12);
- gammas[i].fValue = SkFixedToFloat(g);
+ set_gamma_value(&gammas[i], SkFixedToFloat(g));
} else {
// Here's where the real parametric gammas start. There are many
// permutations of the same equations.
@@ -607,7 +594,7 @@
color_space_almost_equal(0.0774f, e) &&
color_space_almost_equal(0.0000f, f) &&
color_space_almost_equal(2.4000f, g)) {
- gammas[i].fValue = 2.2f;
+ gammas[i].fNamed = SkColorSpace::kSRGB_GammaNamed;
} else {
// Fail on invalid gammas.
if (d <= 0.0f) {
@@ -648,7 +635,8 @@
}
// Ensure that we have successfully read a gamma representation.
- SkASSERT(gammas[i].isValue() || gammas[i].isTable() || gammas[i].isParametric());
+ 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) {
@@ -796,7 +784,10 @@
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.\n");
+ 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;
}
}
@@ -804,6 +795,7 @@
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();
}
}
@@ -872,6 +864,8 @@
{
return_null("Need valid rgb tags for XYZ space");
}
+ SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
+ mat.set3x3ColMajorf(toXYZ);
// It is not uncommon to see missing or empty gamma tags. This indicates
// that we should use unit gamma.
@@ -882,32 +876,28 @@
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;
}
- sk_sp<SkGammas> gammas(new SkGammas(std::move(curves[0]), std::move(curves[1]),
- std::move(curves[2])));
- SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
- mat.set3x3ColMajorf(toXYZ);
- if (gammas->isValues()) {
- // When we have values, take advantage of the NewFromRGB initializer.
- // This allows us to check for canonical sRGB and Adobe RGB.
- float gammaVals[3];
- gammaVals[0] = gammas->fRed.fValue;
- gammaVals[1] = gammas->fGreen.fValue;
- gammaVals[2] = gammas->fBlue.fValue;
- return SkColorSpace_Base::NewRGB(gammaVals, mat, std::move(data));
+ 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 sk_sp<SkColorSpace>(new SkColorSpace_Base(std::move(gammas), mat,
- kUnknown_Named,
- std::move(data)));
+ return SkColorSpace_Base::NewRGB(gammaNamed, mat, std::move(data));
}
}
@@ -922,24 +912,17 @@
return_null("Failed to parse A2B0 tag");
}
- sk_sp<SkGammas> gammas(new SkGammas(std::move(curves[0]), std::move(curves[1]),
- std::move(curves[2])));
- if (colorLUT->fTable) {
+ GammaNamed gammaNamed = SkGammas::Named(curves);
+ if (colorLUT->fTable || 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(colorLUT.release(),
std::move(gammas), toXYZ,
std::move(data)));
- } else if (gammas->isValues()) {
- // When we have values, take advantage of the NewFromRGB initializer.
- // This allows us to check for canonical sRGB and Adobe RGB.
- float gammaVals[3];
- gammaVals[0] = gammas->fRed.fValue;
- gammaVals[1] = gammas->fGreen.fValue;
- gammaVals[2] = gammas->fBlue.fValue;
- return SkColorSpace_Base::NewRGB(gammaVals, toXYZ, std::move(data));
} else {
- return sk_sp<SkColorSpace>(new SkColorSpace_Base(std::move(gammas), toXYZ,
- kUnknown_Named,
- std::move(data)));
+ return SkColorSpace_Base::NewRGB(gammaNamed, toXYZ, std::move(data));
}
}
}
@@ -1079,6 +1062,23 @@
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.
@@ -1120,15 +1120,42 @@
ptr += kTAG_XYZ_Bytes;
// Write TRC tags
- SkASSERT(as_CSB(this)->fGammas->fRed.isValue());
- write_trc_tag((uint32_t*) ptr, as_CSB(this)->fGammas->fRed.fValue);
- ptr += SkAlign4(kTAG_TRC_Bytes);
- SkASSERT(as_CSB(this)->fGammas->fGreen.isValue());
- write_trc_tag((uint32_t*) ptr, as_CSB(this)->fGammas->fGreen.fValue);
- ptr += SkAlign4(kTAG_TRC_Bytes);
- SkASSERT(as_CSB(this)->fGammas->fBlue.isValue());
- write_trc_tag((uint32_t*) ptr, as_CSB(this)->fGammas->fBlue.fValue);
- ptr += SkAlign4(kTAG_TRC_Bytes);
+ 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;
diff --git a/src/core/SkColorSpaceXform.cpp b/src/core/SkColorSpaceXform.cpp
index 0faff88..e142011 100644
--- a/src/core/SkColorSpaceXform.cpp
+++ b/src/core/SkColorSpaceXform.cpp
@@ -152,22 +152,27 @@
// be simpler and faster than our current approach.
float srcFloats[3];
for (int i = 0; i < 3; i++) {
- const SkGammaCurve& gamma = (*fSrcGammas)[i];
uint8_t byte = (*src >> (8 * i)) & 0xFF;
- if (gamma.isValue()) {
- srcFloats[i] = pow(byte_to_float(byte), gamma.fValue);
- } else if (gamma.isTable()) {
- srcFloats[i] = interp_lut(byte, gamma.fTable.get(), gamma.fTableSize);
- } else {
- SkASSERT(gamma.isParametric());
- float component = byte_to_float(byte);
- if (component < gamma.fD) {
- // Y = E * X + F
- srcFloats[i] = gamma.fE * component + gamma.fF;
+ if (fSrcGammas) {
+ const SkGammaCurve& gamma = (*fSrcGammas)[i];
+ if (gamma.isValue()) {
+ srcFloats[i] = powf(byte_to_float(byte), gamma.fValue);
+ } else if (gamma.isTable()) {
+ srcFloats[i] = interp_lut(byte, gamma.fTable.get(), gamma.fTableSize);
} else {
- // Y = (A * X + B)^G + C
- srcFloats[i] = pow(gamma.fA * component + gamma.fB, gamma.fG) + gamma.fC;
+ SkASSERT(gamma.isParametric());
+ float component = byte_to_float(byte);
+ if (component < gamma.fD) {
+ // Y = E * X + F
+ srcFloats[i] = gamma.fE * component + gamma.fF;
+ } else {
+ // Y = (A * X + B)^G + C
+ srcFloats[i] = powf(gamma.fA * component + gamma.fB, gamma.fG) + gamma.fC;
+ }
}
+ } else {
+ // FIXME: Handle named gammas.
+ srcFloats[i] = powf(byte_to_float(byte), 2.2f);
}
}
@@ -189,48 +194,54 @@
// QCMS builds a large float lookup table from the gamma info. Is this faster or
// better than our approach?
for (int i = 0; i < 3; i++) {
- const SkGammaCurve& gamma = (*fDstGammas)[i];
- if (gamma.isValue()) {
- dstFloats[i] = pow(dstFloats[i], 1.0f / gamma.fValue);
- } else if (gamma.isTable()) {
- // FIXME (msarett):
- // An inverse table lookup is particularly strange and non-optimal.
- dstFloats[i] = interp_lut_inv(dstFloats[i], gamma.fTable.get(), gamma.fTableSize);
- } else {
- SkASSERT(gamma.isParametric());
- // FIXME (msarett):
- // This is a placeholder implementation for inverting parametric gammas.
- // First, I need to verify if there are actually destination profiles that
- // require this functionality. Next, I need to explore other possibilities
- // for this implementation. The LUT based approach in QCMS would be a good
- // place to start.
-
- // We need to take the inverse of a piecewise function. Assume that
- // the gamma function is continuous, or this won't make much sense
- // anyway.
- // Plug in |fD| to the first equation to calculate the new piecewise
- // interval. Then simply use the inverse of the original functions.
- float interval = gamma.fE * gamma.fD + gamma.fF;
- if (dstFloats[i] < interval) {
- // X = (Y - F) / E
- if (0.0f == gamma.fE) {
- // The gamma curve for this segment is constant, so the inverse
- // is undefined.
- dstFloats[i] = 0.0f;
- } else {
- dstFloats[i] = (dstFloats[i] - gamma.fF) / gamma.fE;
- }
+ if (fDstGammas) {
+ const SkGammaCurve& gamma = (*fDstGammas)[i];
+ if (gamma.isValue()) {
+ dstFloats[i] = powf(dstFloats[i], 1.0f / gamma.fValue);
+ } else if (gamma.isTable()) {
+ // FIXME (msarett):
+ // An inverse table lookup is particularly strange and non-optimal.
+ dstFloats[i] = interp_lut_inv(dstFloats[i], gamma.fTable.get(),
+ gamma.fTableSize);
} else {
- // X = ((Y - C)^(1 / G) - B) / A
- if (0.0f == gamma.fA || 0.0f == gamma.fG) {
- // The gamma curve for this segment is constant, so the inverse
- // is undefined.
- dstFloats[i] = 0.0f;
+ SkASSERT(gamma.isParametric());
+ // FIXME (msarett):
+ // This is a placeholder implementation for inverting parametric gammas.
+ // First, I need to verify if there are actually destination profiles that
+ // require this functionality. Next, I need to explore other possibilities
+ // for this implementation. The LUT based approach in QCMS would be a good
+ // place to start.
+
+ // We need to take the inverse of a piecewise function. Assume that
+ // the gamma function is continuous, or this won't make much sense
+ // anyway.
+ // Plug in |fD| to the first equation to calculate the new piecewise
+ // interval. Then simply use the inverse of the original functions.
+ float interval = gamma.fE * gamma.fD + gamma.fF;
+ if (dstFloats[i] < interval) {
+ // X = (Y - F) / E
+ if (0.0f == gamma.fE) {
+ // The gamma curve for this segment is constant, so the inverse
+ // is undefined.
+ dstFloats[i] = 0.0f;
+ } else {
+ dstFloats[i] = (dstFloats[i] - gamma.fF) / gamma.fE;
+ }
} else {
- dstFloats[i] = (pow(dstFloats[i] - gamma.fC, 1.0f / gamma.fG) - gamma.fB)
- / gamma.fA;
+ // X = ((Y - C)^(1 / G) - B) / A
+ if (0.0f == gamma.fA || 0.0f == gamma.fG) {
+ // The gamma curve for this segment is constant, so the inverse
+ // is undefined.
+ dstFloats[i] = 0.0f;
+ } else {
+ dstFloats[i] = (powf(dstFloats[i] - gamma.fC, 1.0f / gamma.fG) -
+ gamma.fB) / gamma.fA;
+ }
}
}
+ } else {
+ // FIXME: Handle named gammas.
+ dstFloats[i] = powf(dstFloats[i], 1.0f / 2.2f);
}
}
diff --git a/src/core/SkColorSpace_Base.h b/src/core/SkColorSpace_Base.h
index ffab17a..fc4f665 100644
--- a/src/core/SkColorSpace_Base.h
+++ b/src/core/SkColorSpace_Base.h
@@ -13,8 +13,17 @@
#include "SkTemplates.h"
struct SkGammaCurve {
+ bool isNamed() const {
+ bool result = (SkColorSpace::kNonStandard_GammaNamed != fNamed);
+ SkASSERT(!result || (0.0f == fValue));
+ SkASSERT(!result || (0 == fTableSize));
+ SkASSERT(!result || (0.0f == fG && 0.0f == fE));
+ return result;
+ }
+
bool isValue() const {
bool result = (0.0f != fValue);
+ SkASSERT(!result || SkColorSpace::kNonStandard_GammaNamed == fNamed);
SkASSERT(!result || (0 == fTableSize));
SkASSERT(!result || (0.0f == fG && 0.0f == fE));
return result;
@@ -22,6 +31,7 @@
bool isTable() const {
bool result = (0 != fTableSize);
+ SkASSERT(!result || SkColorSpace::kNonStandard_GammaNamed == fNamed);
SkASSERT(!result || (0.0f == fValue));
SkASSERT(!result || (0.0f == fG && 0.0f == fE));
SkASSERT(!result || fTable);
@@ -30,20 +40,24 @@
bool isParametric() const {
bool result = (0.0f != fG || 0.0f != fE);
+ SkASSERT(!result || SkColorSpace::kNonStandard_GammaNamed == fNamed);
SkASSERT(!result || (0.0f == fValue));
SkASSERT(!result || (0 == fTableSize));
return result;
}
- // We have three different ways to represent gamma.
- // (1) A single value:
+ // We have four different ways to represent gamma.
+ // (1) A known, named type:
+ SkColorSpace::GammaNamed fNamed;
+
+ // (2) A single value:
float fValue;
- // (2) A lookup table:
+ // (3) A lookup table:
uint32_t fTableSize;
std::unique_ptr<float[]> fTable;
- // (3) Parameters for a curve:
+ // (4) Parameters for a curve:
// Y = (aX + b)^g + c for X >= d
// Y = eX + f otherwise
float fG;
@@ -54,12 +68,9 @@
float fE;
float fF;
- SkGammaCurve() {
- memset(this, 0, sizeof(struct SkGammaCurve));
- }
-
- SkGammaCurve(float value)
- : fValue(value)
+ SkGammaCurve()
+ : fNamed(SkColorSpace::kNonStandard_GammaNamed)
+ , fValue(0.0f)
, fTableSize(0)
, fTable(nullptr)
, fG(0.0f)
@@ -74,8 +85,29 @@
struct SkGammas : public SkRefCnt {
public:
- bool isValues() const {
- return fRed.isValue() && fGreen.isValue() && fBlue.isValue();
+ static SkColorSpace::GammaNamed Named(SkGammaCurve curves[3]) {
+ if (SkColorSpace::kLinear_GammaNamed == curves[0].fNamed &&
+ SkColorSpace::kLinear_GammaNamed == curves[1].fNamed &&
+ SkColorSpace::kLinear_GammaNamed == curves[2].fNamed)
+ {
+ return SkColorSpace::kLinear_GammaNamed;
+ }
+
+ if (SkColorSpace::kSRGB_GammaNamed == curves[0].fNamed &&
+ SkColorSpace::kSRGB_GammaNamed == curves[1].fNamed &&
+ SkColorSpace::kSRGB_GammaNamed == curves[2].fNamed)
+ {
+ return SkColorSpace::kSRGB_GammaNamed;
+ }
+
+ if (SkColorSpace::k2Dot2Curve_GammaNamed == curves[0].fNamed &&
+ SkColorSpace::k2Dot2Curve_GammaNamed == curves[1].fNamed &&
+ SkColorSpace::k2Dot2Curve_GammaNamed == curves[2].fNamed)
+ {
+ return SkColorSpace::k2Dot2Curve_GammaNamed;
+ }
+
+ return SkColorSpace::kNonStandard_GammaNamed;
}
const SkGammaCurve& operator[](int i) {
@@ -87,12 +119,6 @@
const SkGammaCurve fGreen;
const SkGammaCurve fBlue;
- SkGammas(float red, float green, float blue)
- : fRed(red)
- , fGreen(green)
- , fBlue(blue)
- {}
-
SkGammas(SkGammaCurve red, SkGammaCurve green, SkGammaCurve blue)
: fRed(std::move(red))
, fGreen(std::move(green))
@@ -120,6 +146,8 @@
class SkColorSpace_Base : public SkColorSpace {
public:
+ static sk_sp<SkColorSpace> NewRGB(float gammas[3], const SkMatrix44& toXYZD50);
+
const sk_sp<SkGammas>& gammas() const { return fGammas; }
SkColorLookUpTable* colorLUT() const { return fColorLUT.get(); }
@@ -131,17 +159,14 @@
private:
- static sk_sp<SkColorSpace> NewRGB(const float gammas[3], const SkMatrix44& toXYZD50,
+ static sk_sp<SkColorSpace> NewRGB(GammaNamed gammaNamed, const SkMatrix44& toXYZD50,
sk_sp<SkData> profileData);
- SkColorSpace_Base(sk_sp<SkGammas> gammas, const SkMatrix44& toXYZ, Named,
+ SkColorSpace_Base(GammaNamed gammaNamed, const SkMatrix44& toXYZ, Named named,
sk_sp<SkData> profileData);
- SkColorSpace_Base(sk_sp<SkGammas> gammas, GammaNamed gammaNamed, const SkMatrix44& toXYZ,
- Named, sk_sp<SkData> profileData);
-
- SkColorSpace_Base(SkColorLookUpTable* colorLUT, sk_sp<SkGammas> gammas,
- const SkMatrix44& toXYZ, sk_sp<SkData> profileData);
+ SkColorSpace_Base(SkColorLookUpTable* colorLUT, sk_sp<SkGammas> gammas, const SkMatrix44& toXYZ,
+ sk_sp<SkData> profileData);
SkAutoTDelete<SkColorLookUpTable> fColorLUT;
sk_sp<SkGammas> fGammas;
diff --git a/src/core/SkMipMap.cpp b/src/core/SkMipMap.cpp
index 69c8466..b9302cd 100644
--- a/src/core/SkMipMap.cpp
+++ b/src/core/SkMipMap.cpp
@@ -306,7 +306,7 @@
static bool treat_like_srgb(const SkImageInfo& info) {
if (info.colorSpace()) {
- return SkColorSpace::k2Dot2Curve_GammaNamed == info.colorSpace()->gammaNamed();
+ return info.colorSpace()->gammaCloseToSRGB();
} else {
return kSRGB_SkColorProfileType == info.profileType();
}