Unify ICC support for gray jpegs and gray pngs

(1) Parse ICC gray profiles into RGB SkColorSpace objects.  This is
    easy - "gray transfer fn + white point" is subset of "RGB transfer fns
    + matrix".  This makes it easier/possible for the drawing code to reason
    about color spaces attached to kGray buffers.
(2) Allow gray images to be tagged with gray ICCs OR rgb ICCs.  ICC gray
    forces to designer to use "D50 gray".  It is not uncommon to see gray
    images with RGB profiles - and this actually allows the designer to
    choose more kinds of gray (ex: sRGB gray).
(3) Make SkJpegCodec support gray images with RGB ICCs.
(4) Make SkPngCodec support gray images with Gray ICCs.
(5) Delete gray from SkColorSpace_A2B - we no longer create these objects
    for gray profiles.

BUG=skia:

Change-Id: Id5eca803798330c54a19c4657def2e5976d1941e
Reviewed-on: https://skia-review.googlesource.com/6922
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Matt Sarett <msarett@google.com>
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
index 8c2f5dc..8295236 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -231,21 +231,20 @@
         sk_sp<SkColorSpace> colorSpace = nullptr;
         bool unsupportedICC = false;
         if (iccData) {
-            SkColorSpace_Base::InputColorFormat inputColorFormat =
-                    SkColorSpace_Base::InputColorFormat::kRGB;
+            SkColorSpace_Base::ICCTypeFlag iccType = SkColorSpace_Base::kRGB_ICCTypeFlag;
             switch (decoderMgr->dinfo()->jpeg_color_space) {
                 case JCS_CMYK:
                 case JCS_YCCK:
-                    inputColorFormat = SkColorSpace_Base::InputColorFormat::kCMYK;
+                    iccType = SkColorSpace_Base::kCMYK_ICCTypeFlag;
                     break;
                 case JCS_GRAYSCALE:
-                    inputColorFormat = SkColorSpace_Base::InputColorFormat::kGray;
+                    // Note the "or equals".  We will accept gray or rgb profiles for gray images.
+                    iccType |= SkColorSpace_Base::kGray_ICCTypeFlag;
                     break;
                 default:
                     break;
             }
-            colorSpace = SkColorSpace_Base::MakeICC(iccData->data(), iccData->size(),
-                                                    inputColorFormat);
+            colorSpace = SkColorSpace_Base::MakeICC(iccData->data(), iccData->size(), iccType);
             if (!colorSpace) {
                 SkCodecPrintf("Could not create SkColorSpace from ICC data.\n");
                 unsupportedICC = true;
diff --git a/src/codec/SkPngCodec.cpp b/src/codec/SkPngCodec.cpp
index c928287..9f5c8e6 100644
--- a/src/codec/SkPngCodec.cpp
+++ b/src/codec/SkPngCodec.cpp
@@ -323,7 +323,8 @@
 // Returns a colorSpace object that represents any color space information in
 // the encoded data.  If the encoded data contains an invalid/unsupported color space,
 // this will return NULL. If there is no color space information, it will guess sRGB
-sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr) {
+sk_sp<SkColorSpace> read_color_space(png_structp png_ptr, png_infop info_ptr,
+                                     SkColorSpace_Base::ICCTypeFlag iccType) {
 
 #if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6)
 
@@ -340,7 +341,7 @@
     int compression;
     if (PNG_INFO_iCCP == png_get_iCCP(png_ptr, info_ptr, &name, &compression, &profile,
             &length)) {
-        return SkColorSpace::MakeICC(profile, length);
+        return SkColorSpace_Base::MakeICC(profile, length, iccType);
     }
 
     // Second, check for sRGB.
@@ -996,7 +997,11 @@
 #endif
     if (fOutCodec) {
         SkASSERT(nullptr == *fOutCodec);
-        sk_sp<SkColorSpace> colorSpace = read_color_space(fPng_ptr, fInfo_ptr);
+        SkColorSpace_Base::ICCTypeFlag iccType = SkColorSpace_Base::kRGB_ICCTypeFlag;
+        if (SkEncodedInfo::kGray_Color == color || SkEncodedInfo::kGrayAlpha_Color == color) {
+            iccType |= SkColorSpace_Base::kGray_ICCTypeFlag;
+        }
+        sk_sp<SkColorSpace> colorSpace = read_color_space(fPng_ptr, fInfo_ptr, iccType);
         const bool unsupportedICC = !colorSpace;
         if (!colorSpace) {
             // Treat unsupported/invalid color spaces as sRGB.
diff --git a/src/core/SkColorSpaceXform_A2B.cpp b/src/core/SkColorSpaceXform_A2B.cpp
index 0f284e4..6e49e84 100644
--- a/src/core/SkColorSpaceXform_A2B.cpp
+++ b/src/core/SkColorSpaceXform_A2B.cpp
@@ -17,10 +17,6 @@
 #include "SkSRGB.h"
 #include "SkTypes.h"
 
-#include <algorithm>
-
-#define AI SK_ALWAYS_INLINE
-
 bool SkColorSpaceXform_A2B::onApply(ColorFormat dstFormat, void* dst, ColorFormat srcFormat,
                                     const void* src, int count, SkAlphaType alphaType) const {
     SkRasterPipeline pipeline;
@@ -147,22 +143,20 @@
         "None", "Named", "Value", "Table", "Param"
     };
 #endif
-    int currentChannels = -1;
-    switch (srcSpace->inputColorFormat()) {
-        case SkColorSpace_Base::InputColorFormat::kRGB:
+    int currentChannels;
+    switch (srcSpace->iccType()) {
+        case SkColorSpace_Base::kRGB_ICCTypeFlag:
             currentChannels = 3;
             break;
-        case SkColorSpace_Base::InputColorFormat::kCMYK:
+        case SkColorSpace_Base::kCMYK_ICCTypeFlag:
             currentChannels = 4;
             // CMYK images from JPEGs (the only format that supports it) are actually
             // inverted CMYK, so we need to invert every channel.
             // TransferFn is y = -x + 1 for x < 1.f, otherwise 0x + 0, ie y = 1 - x for x in [0,1]
             this->addTransferFns({1.f, 0.f, 0.f, -1.f, 1.f, 0.f, 1.f}, 4);
             break;
-        case SkColorSpace_Base::InputColorFormat::kGray:
-            currentChannels = 1;
-            break;
         default:
+            currentChannels = 0;
             SkASSERT(false);
     }
     // add in all input color space -> PCS xforms
@@ -239,20 +233,6 @@
         }
     }
 
-    // take care of monochrome ICC profiles (but not A2B with gray input color space!)
-    if (1 == currentChannels) {
-        // Gray color spaces must multiply their channel by the PCS whitepoint to convert to
-        // the PCS however, PCSLAB profiles must be n-component LUT-based ones, which
-        // need to have 3 (to match PCS) output channels, not 1
-        SkASSERT(SkColorSpace_Base::InputColorFormat::kGray == srcSpace->inputColorFormat());
-        SkASSERT(SkColorSpace_A2B::PCS::kXYZ == srcSpace->pcs());
-        constexpr float PCSXYZWhitePoint[3] = {0.9642f, 1.f, 0.8249f};
-        fMatrices.push_front(std::vector<float>(12, 0.f));
-        std::copy_n(PCSXYZWhitePoint, 3, fMatrices.front().begin());
-        fElementsPipeline.append(SkRasterPipeline::matrix_3x4, fMatrices.front().data());
-        currentChannels = 3;
-    }
-
     // Lab PCS -> XYZ PCS
     if (SkColorSpace_A2B::PCS::kLAB == srcSpace->pcs()) {
         SkCSXformPrintf("Lab -> XYZ element added\n");
diff --git a/src/core/SkColorSpace_A2B.cpp b/src/core/SkColorSpace_A2B.cpp
index 1fef71a..7e097ff 100644
--- a/src/core/SkColorSpace_A2B.cpp
+++ b/src/core/SkColorSpace_A2B.cpp
@@ -7,10 +7,12 @@
 
 #include "SkColorSpace_A2B.h"
 
-SkColorSpace_A2B::SkColorSpace_A2B(InputColorFormat inputColorFormat, std::vector<Element> elements, 
+SkColorSpace_A2B::SkColorSpace_A2B(ICCTypeFlag iccType, std::vector<Element> elements,
                                    PCS pcs, sk_sp<SkData> profileData)
     : INHERITED(std::move(profileData))
-    , fInputColorFormat(inputColorFormat)
+    , fICCType(iccType)
     , fElements(std::move(elements))
     , fPCS(pcs)
-{}
+{
+    SkASSERT(kRGB_ICCTypeFlag == iccType || kCMYK_ICCTypeFlag == iccType);
+}
diff --git a/src/core/SkColorSpace_A2B.h b/src/core/SkColorSpace_A2B.h
index 084c593..3321945 100644
--- a/src/core/SkColorSpace_A2B.h
+++ b/src/core/SkColorSpace_A2B.h
@@ -159,13 +159,13 @@
 
     PCS pcs() const { return fPCS; }
 
-    InputColorFormat inputColorFormat() const { return fInputColorFormat; }
+    ICCTypeFlag iccType() const { return fICCType; }
 
-    SkColorSpace_A2B(InputColorFormat inputColorFormat, std::vector<Element> elements, PCS pcs,
+    SkColorSpace_A2B(ICCTypeFlag iccType, std::vector<Element> elements, PCS pcs,
                      sk_sp<SkData> profileData);
 
 private:
-    InputColorFormat     fInputColorFormat;
+    ICCTypeFlag          fICCType;
     std::vector<Element> fElements;
     PCS                  fPCS;
 
diff --git a/src/core/SkColorSpace_Base.h b/src/core/SkColorSpace_Base.h
index c2e0dc5..88905b3 100644
--- a/src/core/SkColorSpace_Base.h
+++ b/src/core/SkColorSpace_Base.h
@@ -185,14 +185,12 @@
 
     virtual Type type() const = 0;
 
-    enum class InputColorFormat {
-        kRGB,
-        kCMYK,
-        kGray
-    };
+    typedef uint8_t ICCTypeFlag;
+    static constexpr ICCTypeFlag kRGB_ICCTypeFlag  = 1 << 0;
+    static constexpr ICCTypeFlag kCMYK_ICCTypeFlag = 1 << 1;
+    static constexpr ICCTypeFlag kGray_ICCTypeFlag = 1 << 2;
 
-    static sk_sp<SkColorSpace> MakeICC(const void* input, size_t len,
-                                       InputColorFormat inputColorFormat);
+    static sk_sp<SkColorSpace> MakeICC(const void* input, size_t len, ICCTypeFlag type);
 
     static sk_sp<SkColorSpace> MakeRGB(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50);
 
diff --git a/src/core/SkColorSpace_ICC.cpp b/src/core/SkColorSpace_ICC.cpp
index ff4b901..9c2082a 100644
--- a/src/core/SkColorSpace_ICC.cpp
+++ b/src/core/SkColorSpace_ICC.cpp
@@ -42,6 +42,8 @@
     return (int32_t) read_big_endian_u32(ptr);
 }
 
+static constexpr float kWhitePointD50[] = { 0.96420f, 1.00000f, 0.82491f, };
+
 struct ICCProfileHeader {
     uint32_t fSize;
 
@@ -156,10 +158,11 @@
             SkColorSpacePrintf("Warning, bad rendering intent.\n");
         }
 
-        return_if_false(color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[0]), 0.96420f) &&
-                        color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[1]), 1.00000f) &&
-                        color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[2]), 0.82491f),
-                        "Illuminant must be D50");
+        return_if_false(
+                color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[0]), kWhitePointD50[0]) &&
+                color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[1]), kWhitePointD50[1]) &&
+                color_space_almost_equal(SkFixedToFloat(fIlluminantXYZ[2]), kWhitePointD50[2]),
+                "Illuminant must be D50");
 
         return_if_false(fTagCount <= 100, "Too many tags");
 
@@ -1167,23 +1170,21 @@
     return true;
 }
 
-static inline int icf_channels(SkColorSpace_Base::InputColorFormat inputColorFormat) {
-    switch (inputColorFormat) {
-        case SkColorSpace_Base::InputColorFormat::kRGB:
+static inline int icf_channels(SkColorSpace_Base::ICCTypeFlag iccType) {
+    switch (iccType) {
+        case SkColorSpace_Base::kRGB_ICCTypeFlag:
             return 3;
-        case SkColorSpace_Base::InputColorFormat::kCMYK:
+        case SkColorSpace_Base::kCMYK_ICCTypeFlag:
             return 4;
-        case SkColorSpace_Base::InputColorFormat::kGray:
-            return 1;
         default:
             SkASSERT(false);
-            return -1;
+            return 0;
     }
 }
 
 static bool load_a2b0(std::vector<SkColorSpace_A2B::Element>* elements, const uint8_t* src,
                       size_t len, SkColorSpace_A2B::PCS pcs,
-                      SkColorSpace_Base::InputColorFormat inputColorFormat) {
+                      SkColorSpace_Base::ICCTypeFlag iccType) {
     const uint32_t type = read_big_endian_u32(src);
     switch (type) {
         case kTAG_AtoBType:
@@ -1224,10 +1225,10 @@
     SkASSERT(SkColorSpace_A2B::PCS::kLAB == pcs || SkColorSpace_A2B::PCS::kXYZ == pcs);
     static constexpr int kPCSChannels = 3; // must be PCSLAB or PCSXYZ
     if (elements->empty()) {
-        return kPCSChannels == icf_channels(inputColorFormat);
+        return kPCSChannels == icf_channels(iccType);
     }
     // now let's verify that the input/output channels of each A2B element actually match up
-    if (icf_channels(inputColorFormat) != elements->front().inputChannels()) {
+    if (icf_channels(iccType) != elements->front().inputChannels()) {
         SkColorSpacePrintf("Input channel count does not match first A2B element's input count");
         return false;
     }
@@ -1432,28 +1433,32 @@
             parse_gamma(&data, &params, &tagBytes, grayTRC->addr(base), grayTRC->fLength);
     handle_invalid_gamma(&type, &data);
 
-    std::vector<SkColorSpace_A2B::Element> elements;
+    SkMatrix44 toXYZD50(SkMatrix44::kIdentity_Constructor);
+    toXYZD50.setFloat(0, 0, kWhitePointD50[0]);
+    toXYZD50.setFloat(1, 1, kWhitePointD50[1]);
+    toXYZD50.setFloat(2, 2, kWhitePointD50[2]);
     if (SkGammas::Type::kNamed_Type == type) {
-        elements.push_back(SkColorSpace_A2B::Element(data.fNamed, 1));
-    } else {
-        size_t allocSize = sizeof(SkGammas);
-        if (!safe_add(allocSize, gamma_alloc_size(type, data), &allocSize)) {
-            return_null("SkGammas struct is too large to allocate");
-        }
-        void* memory = sk_malloc_throw(allocSize);
-        sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(1));
-        load_gammas(memory, 0, type, &data, params, grayTRC->addr(base));
-        gammas->fType[0] = type;
-        gammas->fData[0] = data;
-        elements.push_back(SkColorSpace_A2B::Element(std::move(gammas)));
+        return SkColorSpace_Base::MakeRGB(data.fNamed, toXYZD50);
     }
-    return sk_sp<SkColorSpace>(new SkColorSpace_A2B(SkColorSpace_Base::InputColorFormat::kGray,
-                                                    std::move(elements),
-                                                    SkColorSpace_A2B::PCS::kXYZ,
-                                                    std::move(profileData)));
+
+    size_t allocSize = sizeof(SkGammas);
+    if (!safe_add(allocSize, gamma_alloc_size(type, data), &allocSize)) {
+        return_null("SkGammas struct is too large to allocate");
+    }
+    void* memory = sk_malloc_throw(allocSize);
+    sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(3));
+    load_gammas(memory, 0, type, &data, params, grayTRC->addr(base));
+    for (int i = 0; i < 3; ++i) {
+        gammas->fType[i] = type;
+        gammas->fData[i] = data;
+    }
+
+    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed,
+                                                    std::move(gammas),
+                                                    toXYZD50, std::move(profileData)));
 }
 
-static sk_sp<SkColorSpace> make_a2b(SkColorSpace_Base::InputColorFormat inputColorFormat,
+static sk_sp<SkColorSpace> make_a2b(SkColorSpace_Base::ICCTypeFlag iccType,
                                     const ICCProfileHeader& header, ICCTag* tags, int tagCount,
                                     const uint8_t* base, sk_sp<SkData> profileData) {
     const ICCTag* a2b0 = ICCTag::Find(tags, tagCount, kTAG_A2B0);
@@ -1462,8 +1467,8 @@
                                         ? SkColorSpace_A2B::PCS::kXYZ
                                         : SkColorSpace_A2B::PCS::kLAB;
         std::vector<SkColorSpace_A2B::Element> elements;
-        if (load_a2b0(&elements, a2b0->addr(base), a2b0->fLength, pcs, inputColorFormat)) {
-            return sk_sp<SkColorSpace>(new SkColorSpace_A2B(inputColorFormat, std::move(elements),
+        if (load_a2b0(&elements, a2b0->addr(base), a2b0->fLength, pcs, iccType)) {
+            return sk_sp<SkColorSpace>(new SkColorSpace_A2B(iccType, std::move(elements),
                                                             pcs, std::move(profileData)));
         }
     }
@@ -1472,11 +1477,11 @@
 }
 
 sk_sp<SkColorSpace> SkColorSpace::MakeICC(const void* input, size_t len) {
-    return SkColorSpace_Base::MakeICC(input, len, SkColorSpace_Base::InputColorFormat::kRGB);
+    return SkColorSpace_Base::MakeICC(input, len, SkColorSpace_Base::kRGB_ICCTypeFlag);
 }
 
 sk_sp<SkColorSpace> SkColorSpace_Base::MakeICC(const void* input, size_t len,
-                                               InputColorFormat inputColorFormat) {
+                                               ICCTypeFlag desiredType) {
     if (!input || len < kICCHeaderSize) {
         return_null("Data is null or not large enough to contain an ICC profile");
     }
@@ -1526,7 +1531,7 @@
 
     switch (header.fInputColorSpace) {
         case kRGB_ColorSpace: {
-            if (InputColorFormat::kRGB != inputColorFormat) {
+            if (!(kRGB_ICCTypeFlag & desiredType)) {
                 return_null("Provided input color format (RGB) does not match profile.");
             }
 
@@ -1536,30 +1541,26 @@
                 return colorSpace;
             }
 
+            desiredType = kRGB_ICCTypeFlag;
             break;
         }
         case kGray_ColorSpace: {
-            if (InputColorFormat::kGray != inputColorFormat) {
+            if (!(kGray_ICCTypeFlag & desiredType)) {
                 return_null("Provided input color format (Gray) does not match profile.");
             }
 
-            sk_sp<SkColorSpace> colorSpace =
-                    make_gray(header, tags.get(), tagCount, base, profileData);
-            if (colorSpace) {
-                return colorSpace;
-            }
-
-            break;
+            return make_gray(header, tags.get(), tagCount, base, profileData);
         }
         case kCMYK_ColorSpace:
-            if (InputColorFormat::kCMYK != inputColorFormat) {
+            if (!(kCMYK_ICCTypeFlag & desiredType)) {
                 return_null("Provided input color format (CMYK) does not match profile.");
             }
 
+            desiredType = kCMYK_ICCTypeFlag;
             break;
         default:
             return_null("ICC profile contains unsupported colorspace");
     }
 
-    return make_a2b(inputColorFormat, header, tags.get(), tagCount, base, profileData);
+    return make_a2b(desiredType, header, tags.get(), tagCount, base, profileData);
 }
diff --git a/tests/ColorSpaceXformTest.cpp b/tests/ColorSpaceXformTest.cpp
index 413d74e..03a9263 100644
--- a/tests/ColorSpaceXformTest.cpp
+++ b/tests/ColorSpaceXformTest.cpp
@@ -50,7 +50,7 @@
         srcElements.push_back(SkColorSpace_A2B::Element(arbitraryMatrix));
         auto srcSpace =
                 ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
-                                                    SkColorSpace_Base::InputColorFormat::kRGB,
+                                                    SkColorSpace_Base::kRGB_ICCTypeFlag,
                                                     std::move(srcElements));
         sk_sp<SkColorSpace> dstSpace(new SkColorSpace_XYZ(gammaNamed, gammas, arbitraryMatrix,
                                                           nullptr));
@@ -60,9 +60,9 @@
     }
 
     static sk_sp<SkColorSpace> CreateA2BSpace(SkColorSpace_A2B::PCS pcs,
-                                              SkColorSpace_Base::InputColorFormat inputColorFormat,
+                                              SkColorSpace_Base::ICCTypeFlag iccType,
                                               std::vector<SkColorSpace_A2B::Element> elements) {
-        return sk_sp<SkColorSpace>(new SkColorSpace_A2B(inputColorFormat, std::move(elements),
+        return sk_sp<SkColorSpace>(new SkColorSpace_A2B(iccType, std::move(elements),
                                                         pcs, nullptr));
     }
 };
@@ -302,7 +302,7 @@
     std::vector<SkColorSpace_A2B::Element> srcElements;
     srcElements.push_back(SkColorSpace_A2B::Element(std::move(colorLUT)));
     auto srcSpace = ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
-                                                        SkColorSpace_Base::InputColorFormat::kRGB,
+                                                        SkColorSpace_Base::kRGB_ICCTypeFlag,
                                                         std::move(srcElements));
     // dst space is entirely identity
     auto dstSpace = SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, SkMatrix44::I());