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