ICC: SkICCGetColorProfileTag supports special cases
Special cases:
- "sRGB"
- "AdobeRGB"
- "DCI-P3"
- "Linear Transfer with sRGB Gamut"
- "2.2 Transfer with sRGB Gamut"
- "sRGB Transfer with DCI-P3 Gamut"
- "Linear Transfer with DCI-P3 Gamut"
- "sRGB Transfer with Rec-BT-2020 Gamut"
- "Linear Transfer with Rec-BT-2020 Gamut"
tools/colorspaceinfo now prints out the Tag.
Also: constants representing gSRGB_TransferFn, g2Dot2_TransferFn, and
gLinear_TransferFn, gDCIP3_TransferFn.
BUG=skia:6720
Change-Id: I92a3f9db9d744d3ec366e4e59afd759ba043c235
Reviewed-on: https://skia-review.googlesource.com/20225
Reviewed-by: Derek Sollenberger <djsollen@google.com>
Commit-Queue: Hal Canary <halcanary@google.com>
diff --git a/src/core/SkColorSpacePriv.h b/src/core/SkColorSpacePriv.h
index 4a63ddf..85c5afc 100644
--- a/src/core/SkColorSpacePriv.h
+++ b/src/core/SkColorSpacePriv.h
@@ -37,6 +37,20 @@
-0.00193139f, 0.0299794f, 0.797162f, // Rz, Gz, Bz
};
+static constexpr SkColorSpaceTransferFn gSRGB_TransferFn =
+ { 2.4f, 1.0f / 1.055f, 0.055f / 1.055f, 1.0f / 12.92f, 0.04045f, 0.0f, 0.0f };
+
+static constexpr SkColorSpaceTransferFn g2Dot2_TransferFn =
+ { 2.2f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+
+// gLinear_TransferFn.fD > 1.0f: Make sure that we use the linear segment of
+// the transfer function even when the x-value is 1.0f.
+static constexpr SkColorSpaceTransferFn gLinear_TransferFn =
+ { 0.0f, 0.0f, 0.0f, 1.0f, 1.0000001f, 0.0f, 0.0f };
+
+static constexpr SkColorSpaceTransferFn gDCIP3_TransferFn =
+ { 2.399994f, 0.947998047f, 0.0520019531f, 0.0769958496f, 0.0390014648f, 0.0f, 0.0f };
+
static inline void to_xyz_d50(SkMatrix44* toXYZD50, SkColorSpace::Gamut gamut) {
switch (gamut) {
case SkColorSpace::kSRGB_Gamut:
@@ -121,13 +135,13 @@
}
static inline bool is_almost_srgb(const SkColorSpaceTransferFn& coeffs) {
- return transfer_fn_almost_equal(1.0f / 1.055f, coeffs.fA) &&
- transfer_fn_almost_equal(0.055f / 1.055f, coeffs.fB) &&
- transfer_fn_almost_equal(1.0f / 12.92f, coeffs.fC) &&
- transfer_fn_almost_equal(0.04045f, coeffs.fD) &&
- transfer_fn_almost_equal(0.00000f, coeffs.fE) &&
- transfer_fn_almost_equal(0.00000f, coeffs.fF) &&
- transfer_fn_almost_equal(2.40000f, coeffs.fG);
+ return transfer_fn_almost_equal(gSRGB_TransferFn.fA, coeffs.fA) &&
+ transfer_fn_almost_equal(gSRGB_TransferFn.fB, coeffs.fB) &&
+ transfer_fn_almost_equal(gSRGB_TransferFn.fC, coeffs.fC) &&
+ transfer_fn_almost_equal(gSRGB_TransferFn.fD, coeffs.fD) &&
+ transfer_fn_almost_equal(gSRGB_TransferFn.fE, coeffs.fE) &&
+ transfer_fn_almost_equal(gSRGB_TransferFn.fF, coeffs.fF) &&
+ transfer_fn_almost_equal(gSRGB_TransferFn.fG, coeffs.fG);
}
static inline bool is_almost_2dot2(const SkColorSpaceTransferFn& coeffs) {
@@ -170,27 +184,13 @@
SkGammaNamed gammaNamed) {
switch (gammaNamed) {
case kSRGB_SkGammaNamed:
- coeffs->fA = 1.0f / 1.055f;
- coeffs->fB = 0.055f / 1.055f;
- coeffs->fC = 1.0f / 12.92f;
- coeffs->fD = 0.04045f;
- coeffs->fE = 0.0f;
- coeffs->fF = 0.0f;
- coeffs->fG = 2.4f;
+ *coeffs = gSRGB_TransferFn;
return true;
case k2Dot2Curve_SkGammaNamed:
- value_to_parametric(coeffs, 2.2f);
+ *coeffs = g2Dot2_TransferFn;
return true;
case kLinear_SkGammaNamed:
- coeffs->fA = 0.0f;
- coeffs->fB = 0.0f;
- coeffs->fC = 1.0f;
- // Make sure that we use the linear segment of the transfer function even
- // when the x-value is 1.0f.
- coeffs->fD = nextafterf(1.0f, 2.0f);
- coeffs->fE = 0.0f;
- coeffs->fF = 0.0f;
- coeffs->fG = 0.0f;
+ *coeffs = gLinear_TransferFn;
return true;
default:
return false;
diff --git a/src/core/SkColorSpaceXform.cpp b/src/core/SkColorSpaceXform.cpp
index ff177d4..5b1085f 100644
--- a/src/core/SkColorSpaceXform.cpp
+++ b/src/core/SkColorSpaceXform.cpp
@@ -230,9 +230,14 @@
if (gammas->isNamed(i)) {
switch (gammas->data(i).fNamed) {
case kSRGB_SkGammaNamed:
- (*fns.fBuildFromParam)(&gammaTableStorage[i * gammaTableSize], 2.4f,
- (1.0f / 1.055f), (0.055f / 1.055f),
- (1.0f / 12.92f), 0.04045f, 0.0f, 0.0f);
+ (*fns.fBuildFromParam)(&gammaTableStorage[i * gammaTableSize],
+ gSRGB_TransferFn.fG,
+ gSRGB_TransferFn.fA,
+ gSRGB_TransferFn.fB,
+ gSRGB_TransferFn.fC,
+ gSRGB_TransferFn.fD,
+ gSRGB_TransferFn.fE,
+ gSRGB_TransferFn.fF);
outGammaTables[i] = &gammaTableStorage[i * gammaTableSize];
break;
case k2Dot2Curve_SkGammaNamed:
diff --git a/src/core/SkICC.cpp b/src/core/SkICC.cpp
index 7eaf667..109c40a 100644
--- a/src/core/SkICC.cpp
+++ b/src/core/SkICC.cpp
@@ -6,15 +6,16 @@
*/
#include "SkAutoMalloc.h"
-#include "SkColorSpace_Base.h"
-#include "SkColorSpace_XYZ.h"
#include "SkColorSpacePriv.h"
#include "SkColorSpaceXformPriv.h"
+#include "SkColorSpace_Base.h"
+#include "SkColorSpace_XYZ.h"
#include "SkEndian.h"
#include "SkFixed.h"
#include "SkICC.h"
#include "SkICCPriv.h"
#include "SkMD5.h"
+#include "SkString.h"
#include "SkUtils.h"
SkICC::SkICC(sk_sp<SkColorSpace> colorSpace)
@@ -143,8 +144,12 @@
static constexpr char kDescriptionTagBodyPrefix[12] =
{ 'G', 'o', 'o', 'g', 'l', 'e', '/', 'S', 'k', 'i', 'a' , '/'};
-static constexpr size_t kDescriptionTagBodySize =
- (sizeof(kDescriptionTagBodyPrefix) + 2 * sizeof(SkMD5::Digest)) * 2;
+
+static constexpr size_t kICCDescriptionTagSize = 44;
+
+static_assert(kICCDescriptionTagSize ==
+ sizeof(kDescriptionTagBodyPrefix) + 2 * sizeof(SkMD5::Digest), "");
+static constexpr size_t kDescriptionTagBodySize = kICCDescriptionTagSize * 2; // ascii->utf16be
static_assert(SkIsAlign4(kDescriptionTagBodySize), "Description must be aligned to 4-bytes.");
static constexpr uint32_t kDescriptionTagHeader[7] {
@@ -309,18 +314,100 @@
1.0f == toXYZD50.get(3, 3);
}
-size_t SkICCWriteDescriptionTag(uint8_t* ptr,
- const SkColorSpaceTransferFn& fn,
- const SkMatrix44& toXYZD50) {
- if (ptr) {
- SkDEBUGCODE(const uint8_t* const ptrCheck = ptr);
- memcpy(ptr, kDescriptionTagHeader, sizeof(kDescriptionTagHeader));
- ptr += sizeof(kDescriptionTagHeader);
+static bool nearly_equal(float x, float y) {
+ // A note on why I chose this tolerance: transfer_fn_almost_equal() uses a
+ // tolerance of 0.001f, which doesn't seem to be enough to distinguish
+ // between similar transfer functions, for example: gamma2.2 and sRGB.
+ //
+ // If the tolerance is 0.0f, then this we can't distinguish between two
+ // different encodings of what is clearly the same colorspace. Some
+ // experimentation with example files lead to this number:
+ static constexpr float kTolerance = 1.0f / (1 << 11);
+ return ::fabsf(x - y) <= kTolerance;
+}
- for (unsigned i = 0; i < sizeof(kDescriptionTagBodyPrefix); ++i) {
- *ptr++ = 0;
- *ptr++ = kDescriptionTagBodyPrefix[i];
+static bool nearly_equal(const SkColorSpaceTransferFn& u,
+ const SkColorSpaceTransferFn& v) {
+ return nearly_equal(u.fG, v.fG)
+ && nearly_equal(u.fA, v.fA)
+ && nearly_equal(u.fB, v.fB)
+ && nearly_equal(u.fC, v.fC)
+ && nearly_equal(u.fD, v.fD)
+ && nearly_equal(u.fE, v.fE)
+ && nearly_equal(u.fF, v.fF);
+}
+
+static bool nearly_equal(const SkMatrix44& toXYZD50, const float standard[9]) {
+ return nearly_equal(toXYZD50.getFloat(0, 0), standard[0])
+ && nearly_equal(toXYZD50.getFloat(0, 1), standard[1])
+ && nearly_equal(toXYZD50.getFloat(0, 2), standard[2])
+ && nearly_equal(toXYZD50.getFloat(1, 0), standard[3])
+ && nearly_equal(toXYZD50.getFloat(1, 1), standard[4])
+ && nearly_equal(toXYZD50.getFloat(1, 2), standard[5])
+ && nearly_equal(toXYZD50.getFloat(2, 0), standard[6])
+ && nearly_equal(toXYZD50.getFloat(2, 1), standard[7])
+ && nearly_equal(toXYZD50.getFloat(2, 2), standard[8])
+ && nearly_equal(toXYZD50.getFloat(0, 3), 0.0f)
+ && nearly_equal(toXYZD50.getFloat(1, 3), 0.0f)
+ && nearly_equal(toXYZD50.getFloat(2, 3), 0.0f)
+ && nearly_equal(toXYZD50.getFloat(3, 0), 0.0f)
+ && nearly_equal(toXYZD50.getFloat(3, 1), 0.0f)
+ && nearly_equal(toXYZD50.getFloat(3, 2), 0.0f)
+ && nearly_equal(toXYZD50.getFloat(3, 3), 1.0f);
+}
+
+// Return nullptr if the color profile doen't have a special name.
+const char* get_color_profile_description(const SkColorSpaceTransferFn& fn,
+ const SkMatrix44& toXYZD50) {
+ bool srgb_xfer = nearly_equal(fn, gSRGB_TransferFn);
+ bool srgb_gamut = nearly_equal(toXYZD50, gSRGB_toXYZD50);
+ if (srgb_xfer && srgb_gamut) {
+ return "sRGB";
+ }
+ bool line_xfer = nearly_equal(fn, gLinear_TransferFn);
+ if (line_xfer && srgb_gamut) {
+ return "Linear Transfer with sRGB Gamut";
+ }
+ bool twoDotTwo = nearly_equal(fn, g2Dot2_TransferFn);
+ if (twoDotTwo && srgb_gamut) {
+ return "2.2 Transfer with sRGB Gamut";
+ }
+ if (twoDotTwo && nearly_equal(toXYZD50, gAdobeRGB_toXYZD50)) {
+ return "AdobeRGB";
+ }
+ bool dcip3_gamut = nearly_equal(toXYZD50, gDCIP3_toXYZD50);
+ if (srgb_xfer || line_xfer) {
+ if (srgb_xfer && dcip3_gamut) {
+ return "sRGB Transfer with DCI-P3 Gamut";
}
+ if (line_xfer && dcip3_gamut) {
+ return "Linear Transfer with DCI-P3 Gamut";
+ }
+ bool rec2020 = nearly_equal(toXYZD50, gRec2020_toXYZD50);
+ if (srgb_xfer && rec2020) {
+ return "sRGB Transfer with Rec-BT-2020 Gamut";
+ }
+ if (line_xfer && rec2020) {
+ return "Linear Transfer with Rec-BT-2020 Gamut";
+ }
+ }
+ if (dcip3_gamut && nearly_equal(fn, gDCIP3_TransferFn)) {
+ return "DCI-P3";
+ }
+ return nullptr;
+}
+
+static void get_color_profile_tag(char dst[kICCDescriptionTagSize],
+ const SkColorSpaceTransferFn& fn,
+ const SkMatrix44& toXYZD50) {
+ SkASSERT(dst);
+ if (const char* description = get_color_profile_description(fn, toXYZD50)) {
+ SkASSERT(strlen(description) < kICCDescriptionTagSize);
+ strncpy(dst, description, kICCDescriptionTagSize);
+ // "If the length of src is less than n, strncpy() writes additional
+ // null bytes to dest to ensure that a total of n bytes are written."
+ } else {
+ strncpy(dst, kDescriptionTagBodyPrefix, sizeof(kDescriptionTagBodyPrefix));
SkMD5 md5;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
@@ -332,17 +419,36 @@
md5.write(&fn, sizeof(fn));
SkMD5::Digest digest;
md5.finish(digest);
+ char* ptr = dst + sizeof(kDescriptionTagBodyPrefix);
for (unsigned i = 0; i < sizeof(SkMD5::Digest); ++i) {
- *ptr++ = 0;
- *ptr++ = SkHexadecimalDigits::gUpper[digest.data[i] >> 4];
- *ptr++ = 0;
- *ptr++ = SkHexadecimalDigits::gUpper[digest.data[i] & 0xF];
+ uint8_t byte = digest.data[i];
+ *ptr++ = SkHexadecimalDigits::gUpper[byte >> 4];
+ *ptr++ = SkHexadecimalDigits::gUpper[byte & 0xF];
}
- SkASSERT(ptr == ptrCheck + kDescriptionTagBodySize + sizeof(kDescriptionTagHeader));
+ SkASSERT(ptr == dst + kICCDescriptionTagSize);
}
- return kDescriptionTagBodySize + sizeof(kDescriptionTagHeader);
}
+SkString SkICCGetColorProfileTag(const SkColorSpaceTransferFn& fn,
+ const SkMatrix44& toXYZD50) {
+ char tag[kICCDescriptionTagSize];
+ get_color_profile_tag(tag, fn, toXYZD50);
+ size_t len = kICCDescriptionTagSize;
+ while (len > 0 && tag[len - 1] == '\0') {
+ --len; // tag is padded out with zeros
+ }
+ SkASSERT(len != 0);
+ return SkString(tag, len);
+}
+
+// returns pointer just beyond where we just wrote.
+static uint8_t* string_copy_ascii_to_utf16be(uint8_t* dst, const char* src, size_t count) {
+ while (count-- > 0) {
+ *dst++ = 0;
+ *dst++ = (uint8_t)(*src++);
+ }
+ return dst;
+}
sk_sp<SkData> SkICC::WriteToICC(const SkColorSpaceTransferFn& fn, const SkMatrix44& toXYZD50) {
if (!is_3x3(toXYZD50) || !is_valid_transfer_fn(fn)) {
@@ -361,7 +467,13 @@
ptr += sizeof(kICCTagTable);
// Write profile description tag
- ptr += SkICCWriteDescriptionTag(ptr, fn, toXYZD50);
+ memcpy(ptr, kDescriptionTagHeader, sizeof(kDescriptionTagHeader));
+ ptr += sizeof(kDescriptionTagHeader);
+ {
+ char colorProfileTag[kICCDescriptionTagSize];
+ get_color_profile_tag(colorProfileTag, fn, toXYZD50);
+ ptr = string_copy_ascii_to_utf16be(ptr, colorProfileTag, kICCDescriptionTagSize);
+ }
// Write XYZ tags
write_xyz_tag((uint32_t*) ptr, toXYZD50, 0);
diff --git a/src/core/SkICCPriv.h b/src/core/SkICCPriv.h
index 0b2f33b..a50d362 100644
--- a/src/core/SkICCPriv.h
+++ b/src/core/SkICCPriv.h
@@ -52,13 +52,10 @@
};
/*
- * Given fn and toXYZD50, generate a ICC decription tag that includes a hash of
- * the input. If ptr is not nullptr, write the tag there. Always returns
- * length of the tag.
- *
- * Exposed for unit testing.
+ * Given fn and toXYZD50, generate a decription tag that either includes a hash
+ * of the function and gamut or is a special name.
+ * Exposed for unit testing and tools.
*/
-size_t SkICCWriteDescriptionTag(uint8_t* ptr,
- const SkColorSpaceTransferFn& fn,
- const SkMatrix44& toXYZD50);
+SkString SkICCGetColorProfileTag(const SkColorSpaceTransferFn& fn,
+ const SkMatrix44& toXYZD50);
#endif // SkICCPriv_DEFINED