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/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);