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();
     }