Revert "Remove old ICC parser, A2B SkColorSpace, SkGammas, etc..."

This reverts commit 8ae7c90fafa2b2405d78d99a8524f07936858ff4.

Reason for revert: Chrome roll

Original change's description:
> Remove old ICC parser, A2B SkColorSpace, SkGammas, etc...
> 
> Docs-Preview: https://skia.org/?cl=148807
> Change-Id: I2d77f6543e390c4948d57242a518af77443f0165
> Reviewed-on: https://skia-review.googlesource.com/148807
> Commit-Queue: Mike Klein <mtklein@google.com>
> Reviewed-by: Mike Klein <mtklein@google.com>

TBR=mtklein@google.com,brianosman@google.com,reed@google.com

Change-Id: I5821591b22e395327ec0cd29ec18569bf7f61859
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://skia-review.googlesource.com/150142
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/gn/core.gni b/gn/core.gni
index 254ca94..6696887 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -75,8 +75,11 @@
   "$_src/core/SkColorMatrixFilterRowMajor255.cpp",
   "$_src/core/SkColorMatrixFilterRowMajor255.h",
   "$_src/core/SkColorSpace.cpp",
+  "$_src/core/SkColorSpace_A2B.cpp",
+  "$_src/core/SkColorSpace_A2B.h",
   "$_src/core/SkColorSpace_XYZ.cpp",
   "$_src/core/SkColorSpace_XYZ.h",
+  "$_src/core/SkColorSpace_ICC.cpp",
   "$_src/core/SkColorSpaceXform.cpp",
   "$_src/core/SkColorSpaceXformCanvas.cpp",
   "$_src/core/SkColorSpaceXformSteps.cpp",
diff --git a/include/core/SkColorSpace.h b/include/core/SkColorSpace.h
index 7ad2f10..eaafc25 100644
--- a/include/core/SkColorSpace.h
+++ b/include/core/SkColorSpace.h
@@ -127,6 +127,11 @@
     static sk_sp<SkColorSpace> MakeRGB(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50);
 
     /**
+     *  Create an SkColorSpace from an ICC profile.
+     */
+    static sk_sp<SkColorSpace> MakeICC(const void*, size_t);
+
+    /**
      *  Create an SkColorSpace from a parsed (skcms) ICC profile.
      */
     static sk_sp<SkColorSpace> Make(const skcms_ICCProfile&);
@@ -136,6 +141,16 @@
      */
     void toProfile(skcms_ICCProfile*) const;
 
+    /**
+     *  Types of colorspaces.
+     */
+    enum Type {
+        kRGB_Type,
+        kCMYK_Type,
+        kGray_Type,
+    };
+    Type type() const;
+
     SkGammaNamed gammaNamed() const;
 
     /**
@@ -250,6 +265,9 @@
     virtual bool onGammaCloseToSRGB() const = 0;
     virtual bool onGammaIsLinear() const = 0;
     virtual bool onIsNumericalTransferFn(SkColorSpaceTransferFn* coeffs) const = 0;
+    virtual bool onIsCMYK() const { return false; }
+
+    virtual const SkData* onProfileData() const { return nullptr; }
 
     using INHERITED = SkRefCnt;
 };
diff --git a/site/user/sample/color.md b/site/user/sample/color.md
index eb9df66..5870b03 100644
--- a/site/user/sample/color.md
+++ b/site/user/sample/color.md
@@ -80,6 +80,9 @@
 	
 	// Choose a common gamut and a common transfer function
 	sk_sp<SkColorSpace> MakeRGB(RenderTargetGamma, Gamut);
+	
+	// Create a color space from an ICC profile
+	sk_sp<SkColorSpace> MakeICC();
 
 Starting with **sources** (the things that you draw), there are a number of ways to make sure
 that they are tagged with a color space.
diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
index 81747e9..239f98a 100644
--- a/src/core/SkColorSpace.cpp
+++ b/src/core/SkColorSpace.cpp
@@ -9,7 +9,6 @@
 #include "SkColorSpace_XYZ.h"
 #include "SkColorSpacePriv.h"
 #include "SkPoint3.h"
-#include "SkTemplates.h"
 #include <new>
 
 bool SkColorSpacePrimaries::toXYZD50(SkMatrix44* toXYZ_D50) const {
@@ -128,7 +127,7 @@
             break;
     }
 
-    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed, nullptr, toXYZD50));
+    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed, toXYZD50));
 }
 
 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(RenderTargetGamma gamma, const SkMatrix44& toXYZD50) {
@@ -160,7 +159,18 @@
         return SkColorSpace::MakeRGB(kLinear_SkGammaNamed, toXYZD50);
     }
 
-    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed, &coeffs, toXYZD50));
+    void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn));
+    sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(3));
+    SkColorSpaceTransferFn* fn = SkTAddOffset<SkColorSpaceTransferFn>(memory, sizeof(SkGammas));
+    *fn = coeffs;
+    SkGammas::Data data;
+    data.fParamOffset = 0;
+    for (int channel = 0; channel < 3; ++channel) {
+        gammas->fType[channel] = SkGammas::Type::kParam_Type;
+        gammas->fData[channel] = data;
+    }
+    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed,
+                                                    std::move(gammas), toXYZD50, nullptr));
 }
 
 sk_sp<SkColorSpace> SkColorSpace::MakeRGB(RenderTargetGamma gamma, Gamut gamut) {
@@ -179,7 +189,7 @@
     SkMatrix44 m44(SkMatrix44::kUninitialized_Constructor);
     m44.set3x3RowMajorf(to_xyz);
     (void)m44.getType();  // Force typemask to be computed to avoid races.
-    return new SkColorSpace_XYZ(gamma, nullptr, m44);
+    return new SkColorSpace_XYZ(gamma, m44);
 }
 
 SkColorSpace* sk_srgb_singleton() {
@@ -201,6 +211,14 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+SkColorSpace::Type SkColorSpace::type() const {
+    const SkMatrix44* m = this->toXYZD50();
+    if (m) {
+        return m->isScale() ? kGray_Type : kRGB_Type;
+    }
+    return this->onIsCMYK() ? kCMYK_Type : kRGB_Type;
+}
+
 SkGammaNamed SkColorSpace::gammaNamed() const {
     return this->onGammaNamed();
 }
@@ -271,7 +289,6 @@
      *  If kICC_Flag is set, we will write an ICC profile after the header.
      *  The ICC profile will be written as a uint32 size, followed immediately
      *  by the data (padded to 4 bytes).
-     *  DEPRECATED / UNUSED
      */
     static constexpr uint8_t kICC_Flag        = 1 << 1;
 
@@ -307,61 +324,87 @@
 };
 
 size_t SkColorSpace::writeToMemory(void* memory) const {
-    // If we have a named profile, only write the enum.
-    const SkGammaNamed gammaNamed = this->gammaNamed();
-    if (this == sk_srgb_singleton()) {
-        if (memory) {
-            *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(
-                    k0_Version, kSRGB_NamedColorSpace, gammaNamed, 0);
+    // Start by trying the serialization fast path.  If we haven't saved ICC profile data,
+    // we must have a profile that we can serialize easily.
+    if (!this->onProfileData()) {
+        // Profile data is mandatory for A2B0 color spaces, so we must be XYZ.
+        SkASSERT(this->toXYZD50());
+        // If we have a named profile, only write the enum.
+        const SkGammaNamed gammaNamed = this->gammaNamed();
+        if (this == sk_srgb_singleton()) {
+            if (memory) {
+                *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(
+                        k0_Version, kSRGB_NamedColorSpace, gammaNamed, 0);
+            }
+            return sizeof(ColorSpaceHeader);
+        } else if (this == sk_srgb_linear_singleton()) {
+            if (memory) {
+                *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(
+                        k0_Version, kSRGBLinear_NamedColorSpace, gammaNamed, 0);
+            }
+            return sizeof(ColorSpaceHeader);
         }
-        return sizeof(ColorSpaceHeader);
-    } else if (this == sk_srgb_linear_singleton()) {
-        if (memory) {
-            *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(
-                    k0_Version, kSRGBLinear_NamedColorSpace, gammaNamed, 0);
+
+        // If we have a named gamma, write the enum and the matrix.
+        switch (gammaNamed) {
+            case kSRGB_SkGammaNamed:
+            case k2Dot2Curve_SkGammaNamed:
+            case kLinear_SkGammaNamed: {
+                if (memory) {
+                    *((ColorSpaceHeader*) memory) =
+                            ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed,
+                                                   ColorSpaceHeader::kMatrix_Flag);
+                    memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
+                    this->toXYZD50()->as3x4RowMajorf((float*) memory);
+                }
+                return sizeof(ColorSpaceHeader) + 12 * sizeof(float);
+            }
+            default: {
+                SkColorSpaceTransferFn transferFn;
+                SkAssertResult(this->isNumericalTransferFn(&transferFn));
+
+                if (memory) {
+                    *((ColorSpaceHeader*) memory) =
+                            ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed,
+                                                   ColorSpaceHeader::kTransferFn_Flag);
+                    memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
+
+                    *(((float*) memory) + 0) = transferFn.fA;
+                    *(((float*) memory) + 1) = transferFn.fB;
+                    *(((float*) memory) + 2) = transferFn.fC;
+                    *(((float*) memory) + 3) = transferFn.fD;
+                    *(((float*) memory) + 4) = transferFn.fE;
+                    *(((float*) memory) + 5) = transferFn.fF;
+                    *(((float*) memory) + 6) = transferFn.fG;
+                    memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
+
+                    this->toXYZD50()->as3x4RowMajorf((float*) memory);
+                }
+
+                return sizeof(ColorSpaceHeader) + 19 * sizeof(float);
+            }
         }
-        return sizeof(ColorSpaceHeader);
     }
 
-    // If we have a named gamma, write the enum and the matrix.
-    switch (gammaNamed) {
-        case kSRGB_SkGammaNamed:
-        case k2Dot2Curve_SkGammaNamed:
-        case kLinear_SkGammaNamed: {
-            if (memory) {
-                *((ColorSpaceHeader*) memory) =
-                        ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed,
-                                                ColorSpaceHeader::kMatrix_Flag);
-                memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
-                this->toXYZD50()->as3x4RowMajorf((float*) memory);
-            }
-            return sizeof(ColorSpaceHeader) + 12 * sizeof(float);
-        }
-        default: {
-            SkColorSpaceTransferFn transferFn;
-            SkAssertResult(this->isNumericalTransferFn(&transferFn));
-
-            if (memory) {
-                *((ColorSpaceHeader*) memory) =
-                        ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed,
-                                                ColorSpaceHeader::kTransferFn_Flag);
-                memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
-
-                *(((float*) memory) + 0) = transferFn.fA;
-                *(((float*) memory) + 1) = transferFn.fB;
-                *(((float*) memory) + 2) = transferFn.fC;
-                *(((float*) memory) + 3) = transferFn.fD;
-                *(((float*) memory) + 4) = transferFn.fE;
-                *(((float*) memory) + 5) = transferFn.fF;
-                *(((float*) memory) + 6) = transferFn.fG;
-                memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
-
-                this->toXYZD50()->as3x4RowMajorf((float*) memory);
-            }
-
-            return sizeof(ColorSpaceHeader) + 19 * sizeof(float);
-        }
+    // Otherwise, serialize the ICC data.
+    size_t profileSize = this->onProfileData()->size();
+    if (SkAlign4(profileSize) != (uint32_t) SkAlign4(profileSize)) {
+        return 0;
     }
+
+    if (memory) {
+        *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(k0_Version, 0,
+                                                               kNonStandard_SkGammaNamed,
+                                                               ColorSpaceHeader::kICC_Flag);
+        memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
+
+        *((uint32_t*) memory) = (uint32_t) SkAlign4(profileSize);
+        memory = SkTAddOffset<void>(memory, sizeof(uint32_t));
+
+        memcpy(memory, this->onProfileData()->data(), profileSize);
+        memset(SkTAddOffset<void>(memory, profileSize), 0, SkAlign4(profileSize) - profileSize);
+    }
+    return sizeof(ColorSpaceHeader) + sizeof(uint32_t) + SkAlign4(profileSize);
 }
 
 sk_sp<SkData> SkColorSpace::serialize() const {
@@ -412,8 +455,18 @@
 
     switch (header.fFlags) {
         case ColorSpaceHeader::kICC_Flag: {
-            // Deprecated and unsupported code path
-            return nullptr;
+            if (length < sizeof(uint32_t)) {
+                return nullptr;
+            }
+
+            uint32_t profileSize = *((uint32_t*) data);
+            data = SkTAddOffset<const void>(data, sizeof(uint32_t));
+            length -= sizeof(uint32_t);
+            if (length < profileSize) {
+                return nullptr;
+            }
+
+            return MakeICC(data, profileSize);
         }
         case ColorSpaceHeader::kTransferFn_Flag: {
             if (length < 19 * sizeof(float)) {
@@ -448,6 +501,18 @@
         return false;
     }
 
+    const SkData* srcData = src->onProfileData();
+    const SkData* dstData = dst->onProfileData();
+    if (srcData || dstData) {
+        if (srcData && dstData) {
+            return srcData->size() == dstData->size() &&
+                   0 == memcmp(srcData->data(), dstData->data(), srcData->size());
+        }
+
+        return false;
+    }
+
+    // Profiles are mandatory for A2B0 color spaces, so these must be XYZ
     if (src->gammaNamed() != dst->gammaNamed()) {
         return false;
     }
@@ -463,7 +528,6 @@
             return false;
         default:
             // It is unlikely that we will reach this case.
-            // TODO: Simplify this case now that color spaces have one representation.
             sk_sp<SkData> serializedSrcData = src->serialize();
             sk_sp<SkData> serializedDstData = dst->serialize();
             return serializedSrcData->size() == serializedDstData->size() &&
diff --git a/src/core/SkColorSpacePriv.h b/src/core/SkColorSpacePriv.h
index 898a61e..7df6c97 100644
--- a/src/core/SkColorSpacePriv.h
+++ b/src/core/SkColorSpacePriv.h
@@ -188,6 +188,43 @@
     return linearExp || linearFn;
 }
 
+static inline bool is_just_gamma(const SkColorSpaceTransferFn& coeffs) {
+    return transfer_fn_almost_equal(coeffs.fA, 1.0f)
+        && transfer_fn_almost_equal(coeffs.fB, 0.0f)
+        && transfer_fn_almost_equal(coeffs.fC, 0.0f)
+        && transfer_fn_almost_equal(coeffs.fD, 0.0f)
+        && transfer_fn_almost_equal(coeffs.fE, 0.0f)
+        && transfer_fn_almost_equal(coeffs.fF, 0.0f);
+}
+
+
+static inline void value_to_parametric(SkColorSpaceTransferFn* coeffs, float exponent) {
+    coeffs->fA = 1.0f;
+    coeffs->fB = 0.0f;
+    coeffs->fC = 0.0f;
+    coeffs->fD = 0.0f;
+    coeffs->fE = 0.0f;
+    coeffs->fF = 0.0f;
+    coeffs->fG = exponent;
+}
+
+static inline bool named_to_parametric(SkColorSpaceTransferFn* coeffs,
+                                       SkGammaNamed gammaNamed) {
+    switch (gammaNamed) {
+        case kSRGB_SkGammaNamed:
+            *coeffs = gSRGB_TransferFn;
+            return true;
+        case k2Dot2Curve_SkGammaNamed:
+            *coeffs = g2Dot2_TransferFn;
+            return true;
+        case kLinear_SkGammaNamed:
+            *coeffs = gLinear_TransferFn;
+            return true;
+        default:
+            return false;
+    }
+}
+
 // Return raw pointers to commonly used SkColorSpaces.
 // No need to ref/unref these, but if you do, do it in pairs.
 SkColorSpace* sk_srgb_singleton();
diff --git a/src/core/SkColorSpaceXform.cpp b/src/core/SkColorSpaceXform.cpp
index 8596abf..61a37ad 100644
--- a/src/core/SkColorSpaceXform.cpp
+++ b/src/core/SkColorSpaceXform.cpp
@@ -77,19 +77,23 @@
 }
 
 void SkColorSpace::toProfile(skcms_ICCProfile* profile) const {
-    SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
-    SkColorSpaceTransferFn tf;
-    SkAssertResult(this->toXYZD50(&toXYZ) && this->isNumericalTransferFn(&tf));
+    if (auto blob = this->onProfileData()) {
+        SkAssertResult(skcms_Parse(blob->data(), blob->size(), profile));
+    } else {
+        SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
+        SkColorSpaceTransferFn tf;
+        SkAssertResult(this->toXYZD50(&toXYZ) && this->isNumericalTransferFn(&tf));
 
-    skcms_Matrix3x3 m = { {
-        { toXYZ.get(0, 0), toXYZ.get(0, 1), toXYZ.get(0, 2) },
-        { toXYZ.get(1, 0), toXYZ.get(1, 1), toXYZ.get(1, 2) },
-        { toXYZ.get(2, 0), toXYZ.get(2, 1), toXYZ.get(2, 2) },
-    } };
+        skcms_Matrix3x3 m = { {
+            { toXYZ.get(0, 0), toXYZ.get(0, 1), toXYZ.get(0, 2) },
+            { toXYZ.get(1, 0), toXYZ.get(1, 1), toXYZ.get(1, 2) },
+            { toXYZ.get(2, 0), toXYZ.get(2, 1), toXYZ.get(2, 2) },
+        } };
 
-    skcms_Init(profile);
-    skcms_SetTransferFunction(profile, (const skcms_TransferFunction*)&tf);
-    skcms_SetXYZD50(profile, &m);
+        skcms_Init(profile);
+        skcms_SetTransferFunction(profile, (const skcms_TransferFunction*)&tf);
+        skcms_SetXYZD50(profile, &m);
+    }
 }
 
 std::unique_ptr<SkColorSpaceXform> SkMakeColorSpaceXform(SkColorSpace* src, SkColorSpace* dst) {
diff --git a/src/core/SkColorSpace_A2B.cpp b/src/core/SkColorSpace_A2B.cpp
new file mode 100644
index 0000000..295f28f
--- /dev/null
+++ b/src/core/SkColorSpace_A2B.cpp
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkColorSpace_A2B.h"
+
+SkColorSpace_A2B::SkColorSpace_A2B(SkColorSpace::Type iccType, std::vector<Element> elements,
+                                   PCS pcs, sk_sp<SkData> profileData)
+    : fProfileData(std::move(profileData))
+    , fICCType(iccType)
+    , fElements(std::move(elements))
+    , fPCS(pcs)
+{
+    SkASSERT(SkColorSpace::kRGB_Type == iccType || SkColorSpace::kCMYK_Type == iccType);
+}
diff --git a/src/core/SkColorSpace_A2B.h b/src/core/SkColorSpace_A2B.h
new file mode 100644
index 0000000..48e8a70
--- /dev/null
+++ b/src/core/SkColorSpace_A2B.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkColorSpace_A2B_DEFINED
+#define SkColorSpace_A2B_DEFINED
+
+#include "SkColorLookUpTable.h"
+#include "SkColorSpace.h"
+#include "SkGammas.h"
+#include <vector>
+
+// An alternative SkColorSpace that represents all the color space data that
+// is stored in an A2B0 ICC tag. This allows us to use alternative profile
+// connection spaces (CIELAB instead of just CIEXYZ), use color-lookup-tables
+// to do color space transformations not representable as TRC functions or
+// matrix operations, as well as have multiple TRC functions. The CLUT also
+// allows conversion between non-3-channel input color spaces ie CMYK(4) to
+// a workable PCS (ie XYZ).
+//
+// AtoBType, lut8Type and lut16Type A2B0 tag types are supported. There are
+// also MPET (multi-processing-elements) A2B0 tags in the standard which allow
+// you to combine these 3 primitives (TRC, CLUT, matrix) in any order/quantity.
+// MPET tags are currently unsupported by the MakeICC parser, could be supported
+// here by the nature of the design.
+class SkColorSpace_A2B : public SkColorSpace {
+public:
+    const SkMatrix44* onToXYZD50() const override {
+        // the matrix specified in A2B0 profiles is not necessarily
+        // a to-XYZ matrix, as to-Lab is supported as well so returning
+        // that could be misleading. Additionally, B-curves are applied
+        // after the matrix is, but a toXYZD50 matrix is the last thing
+        // applied in order to get into the (XYZ) profile connection space.
+        return nullptr;
+    }
+
+    uint32_t onToXYZD50Hash() const override {
+        // See onToXYZD50()'s comment.
+        return 0;
+    }
+
+    const SkMatrix44* onFromXYZD50() const override {
+        // See onToXYZD50()'s comment. Also, A2B0 profiles are not supported
+        // as destination color spaces, so an inverse matrix is never wanted.
+        return nullptr;
+    }
+
+    // There is no single gamma curve in an A2B0 profile
+    SkGammaNamed onGammaNamed() const override { return kNonStandard_SkGammaNamed; }
+    bool onGammaCloseToSRGB() const override { return false; }
+    bool onGammaIsLinear() const override { return false; }
+    bool onIsNumericalTransferFn(SkColorSpaceTransferFn* coeffs) const override { return false; }
+
+    bool onIsCMYK() const override { return SkColorSpace::kCMYK_Type == fICCType; }
+
+    const SkData* onProfileData() const override { return fProfileData.get(); }
+
+    sk_sp<SkColorSpace> makeLinearGamma() const override {
+        // TODO: Analyze the extrema of our projection into XYZ and use suitable primaries?
+        // For now, just fall back to a default, because we don't have a good answer.
+        return SkColorSpace::MakeSRGBLinear();
+    }
+
+    sk_sp<SkColorSpace> makeSRGBGamma() const override {
+        // See comment in makeLinearGamma
+        return SkColorSpace::MakeSRGB();
+    }
+
+    class Element {
+    public:
+        Element(SkGammaNamed gammaNamed, int channelCount)
+            : fType(Type::kGammaNamed)
+            , fGammaNamed(gammaNamed)
+            , fMatrix(SkMatrix44::kUninitialized_Constructor)
+            , fInputChannels(channelCount)
+            , fOutputChannels(channelCount) {
+            SkASSERT(gammaNamed != kNonStandard_SkGammaNamed);
+        }
+
+        explicit Element(sk_sp<SkGammas> gammas)
+            : fType(Type::kGammas)
+            , fGammas(std::move(gammas))
+            , fMatrix(SkMatrix44::kUninitialized_Constructor)
+            , fInputChannels(fGammas->channels())
+            , fOutputChannels(fGammas->channels()) {
+            for (int i = 0; i < fGammas->channels(); ++i) {
+                if (SkGammas::Type::kTable_Type == fGammas->type(i)) {
+                    SkASSERT(fGammas->data(i).fTable.fSize >= 2);
+                }
+            }
+        }
+
+        explicit Element(sk_sp<SkColorLookUpTable> colorLUT)
+            : fType(Type::kCLUT)
+            , fCLUT(std::move(colorLUT))
+            , fMatrix(SkMatrix44::kUninitialized_Constructor)
+            , fInputChannels(fCLUT->inputChannels())
+            , fOutputChannels(fCLUT->outputChannels())
+        {}
+
+        explicit Element(const SkMatrix44& matrix)
+            : fType(Type::kMatrix)
+            , fMatrix(matrix)
+            , fInputChannels(3)
+            , fOutputChannels(3)
+        {}
+
+        enum class Type {
+            kGammaNamed,
+            kGammas,
+            kCLUT,
+            kMatrix
+        };
+
+        Type type() const { return fType; }
+
+        SkGammaNamed gammaNamed() const {
+            SkASSERT(Type::kGammaNamed == fType);
+            return fGammaNamed;
+        }
+
+        const SkGammas& gammas() const {
+            SkASSERT(Type::kGammas == fType);
+            return *fGammas;
+        }
+
+        const SkColorLookUpTable& colorLUT() const {
+            SkASSERT(Type::kCLUT == fType);
+            return *fCLUT;
+        }
+
+        const SkMatrix44& matrix() const {
+            SkASSERT(Type::kMatrix == fType);
+            return fMatrix;
+        }
+
+        int inputChannels() const { return fInputChannels; }
+
+        int outputChannels() const { return fOutputChannels; }
+
+    private:
+        Type                      fType;
+        SkGammaNamed              fGammaNamed;
+        sk_sp<SkGammas>           fGammas;
+        sk_sp<SkColorLookUpTable> fCLUT;
+        SkMatrix44                fMatrix;
+        int                       fInputChannels;
+        int                       fOutputChannels;
+    };
+    const Element& element(int i) const { return fElements[i]; }
+
+    int count() const { return (int)fElements.size(); }
+
+    // the intermediate profile connection space that this color space
+    // represents the transformation to
+    enum class PCS : uint8_t {
+        kLAB, // CIELAB
+        kXYZ  // CIEXYZ
+    };
+
+    PCS pcs() const { return fPCS; }
+
+    SkColorSpace::Type iccType() const { return fICCType; }
+
+    SkColorSpace_A2B(SkColorSpace::Type iccType, std::vector<Element> elements, PCS pcs,
+                     sk_sp<SkData> profileData);
+
+private:
+    sk_sp<SkData>        fProfileData;
+
+    SkColorSpace::Type   fICCType;
+    std::vector<Element> fElements;
+    PCS                  fPCS;
+
+    friend class ColorSpaceXformTest;
+};
+
+#endif
diff --git a/src/core/SkColorSpace_ICC.cpp b/src/core/SkColorSpace_ICC.cpp
new file mode 100644
index 0000000..3cc82ed
--- /dev/null
+++ b/src/core/SkColorSpace_ICC.cpp
@@ -0,0 +1,1556 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkAutoMalloc.h"
+#include "SkColorSpace.h"
+#include "SkColorSpacePriv.h"
+#include "SkColorSpace_A2B.h"
+#include "SkColorSpace_XYZ.h"
+#include "SkEndian.h"
+#include "SkFixed.h"
+#include "SkICCPriv.h"
+#include "SkTemplates.h"
+#include "../../third_party/skcms/skcms.h"
+
+#define return_if_false(pred, msg)                                   \
+    do {                                                             \
+        if (!(pred)) {                                               \
+            SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg)); \
+            return false;                                            \
+        }                                                            \
+    } while (0)
+
+#define return_null(msg)                                             \
+    do {                                                             \
+        SkColorSpacePrintf("Invalid ICC Profile: %s.\n", (msg));     \
+        return nullptr;                                              \
+    } while (0)
+
+static uint16_t read_big_endian_u16(const uint8_t* ptr) {
+    return ptr[0] << 8 | ptr[1];
+}
+
+static uint32_t read_big_endian_u32(const uint8_t* ptr) {
+    return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
+}
+
+static int32_t read_big_endian_i32(const uint8_t* ptr) {
+    return (int32_t) read_big_endian_u32(ptr);
+}
+
+static constexpr float kWhitePointD50[] = { 0.96420f, 1.00000f, 0.82491f, };
+
+struct ICCProfileHeader {
+    uint32_t fSize;
+
+    // No reason to care about the preferred color management module (ex: Adobe, Apple, etc.).
+    // We're always going to use this one.
+    uint32_t fCMMType_ignored;
+
+    uint32_t fVersion;
+    uint32_t fProfileClass;
+    uint32_t fInputColorSpace;
+    uint32_t fPCS;
+    uint32_t fDateTime_ignored[3];
+    uint32_t fSignature;
+
+    // Indicates the platform that this profile was created for (ex: Apple, Microsoft).  This
+    // doesn't really matter to us.
+    uint32_t fPlatformTarget_ignored;
+
+    // Flags can indicate:
+    // (1) Whether this profile was embedded in a file.  This flag is consistently wrong.
+    //     Ex: The profile came from a file but indicates that it did not.
+    // (2) Whether we are allowed to use the profile independently of the color data.  If set,
+    //     this may allow us to use the embedded profile for testing separate from the original
+    //     image.
+    uint32_t fFlags_ignored;
+
+    // We support many output devices.  It doesn't make sense to think about the attributes of
+    // the device in the context of the image profile.
+    uint32_t fDeviceManufacturer_ignored;
+    uint32_t fDeviceModel_ignored;
+    uint32_t fDeviceAttributes_ignored[2];
+
+    uint32_t fRenderingIntent;
+    int32_t  fIlluminantXYZ[3];
+
+    // We don't care who created the profile.
+    uint32_t fCreator_ignored;
+
+    // This is an MD5 checksum.  Could be useful for checking if profiles are equal.
+    uint32_t fProfileId_ignored[4];
+
+    // Reserved for future use.
+    uint32_t fReserved_ignored[7];
+
+    uint32_t fTagCount;
+
+    void init(const uint8_t* src, size_t len) {
+        SkASSERT(kICCHeaderSize == sizeof(*this));
+
+        uint32_t* dst = (uint32_t*) this;
+        for (uint32_t i = 0; i < kICCHeaderSize / 4; i++, src+=4) {
+            dst[i] = read_big_endian_u32(src);
+        }
+    }
+
+    bool valid() const {
+        return_if_false(fSize >= kICCHeaderSize, "Size is too small");
+
+        uint8_t majorVersion = fVersion >> 24;
+        return_if_false(majorVersion <= 4, "Unsupported version");
+
+        // These are the four basic classes of profiles that we might expect to see embedded
+        // in images.  Additional classes exist, but they generally are used as a convenient
+        // way for CMMs to store calculated transforms.
+        return_if_false(fProfileClass == kDisplay_Profile ||
+                        fProfileClass == kInput_Profile ||
+                        fProfileClass == kOutput_Profile ||
+                        fProfileClass == kColorSpace_Profile,
+                        "Unsupported profile");
+
+        switch (fInputColorSpace) {
+            case kRGB_ColorSpace:
+                SkColorSpacePrintf("RGB Input Color Space");
+                break;
+            case kCMYK_ColorSpace:
+                SkColorSpacePrintf("CMYK Input Color Space\n");
+                break;
+            case kGray_ColorSpace:
+                SkColorSpacePrintf("Gray Input Color Space\n");
+                break;
+            default:
+                SkColorSpacePrintf("Unsupported Input Color Space: %c%c%c%c\n",
+                                   (fInputColorSpace>>24)&0xFF, (fInputColorSpace>>16)&0xFF,
+                                   (fInputColorSpace>> 8)&0xFF, (fInputColorSpace>> 0)&0xFF);
+                return false;
+        }
+
+        switch (fPCS) {
+            case kXYZ_PCSSpace:
+                SkColorSpacePrintf("XYZ PCS\n");
+                break;
+            case kLAB_PCSSpace:
+                SkColorSpacePrintf("Lab PCS\n");
+                break;
+            default:
+                // ICC currently (V4.3) only specifices XYZ and Lab PCS spaces
+                SkColorSpacePrintf("Unsupported PCS space: %c%c%c%c\n",
+                                   (fPCS>>24)&0xFF, (fPCS>>16)&0xFF,
+                                   (fPCS>> 8)&0xFF, (fPCS>> 0)&0xFF);
+                return false;
+        }
+
+        return_if_false(fSignature == kACSP_Signature, "Bad signature");
+
+        // TODO (msarett):
+        // Should we treat different rendering intents differently?
+        // Valid rendering intents include kPerceptual (0), kRelative (1),
+        // kSaturation (2), and kAbsolute (3).
+        if (fRenderingIntent > 3) {
+            // Warn rather than fail here.  Occasionally, we see perfectly
+            // normal profiles with wacky rendering intents.
+            SkColorSpacePrintf("Warning, bad rendering intent.\n");
+        }
+
+        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");
+
+        return true;
+    }
+};
+
+template <class T>
+static bool safe_add(T arg1, T arg2, size_t* result) {
+    SkASSERT(arg1 >= 0);
+    SkASSERT(arg2 >= 0);
+    if (arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) {
+        T sum = arg1 + arg2;
+        if (sum <= std::numeric_limits<size_t>::max()) {
+            *result = static_cast<size_t>(sum);
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool safe_mul(uint32_t arg1, uint32_t arg2, uint32_t* result) {
+    uint64_t product64 = (uint64_t) arg1 * (uint64_t) arg2;
+    uint32_t product32 = (uint32_t) product64;
+    if (product32 != product64) {
+        return false;
+    }
+
+    *result = product32;
+    return true;
+}
+
+struct ICCTag {
+    uint32_t fSignature;
+    uint32_t fOffset;
+    uint32_t fLength;
+
+    const uint8_t* init(const uint8_t* src) {
+        fSignature = read_big_endian_u32(src);
+        fOffset = read_big_endian_u32(src + 4);
+        fLength = read_big_endian_u32(src + 8);
+        return src + 12;
+    }
+
+    bool valid(size_t len) {
+        size_t tagEnd;
+        return_if_false(safe_add(fOffset, fLength, &tagEnd),
+                        "Tag too large, overflows integer addition");
+        return_if_false(tagEnd <= len, "Tag too large for ICC profile");
+        return true;
+    }
+
+    const uint8_t* addr(const uint8_t* src) const {
+        return src + fOffset;
+    }
+
+    static const ICCTag* Find(const ICCTag tags[], int count, uint32_t signature) {
+        for (int i = 0; i < count; ++i) {
+            if (tags[i].fSignature == signature) {
+                return &tags[i];
+            }
+        }
+        return nullptr;
+    }
+};
+
+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;
+    }
+
+    dst[0] = SkFixedToFloat(read_big_endian_i32(src + 8));
+    dst[1] = SkFixedToFloat(read_big_endian_i32(src + 12));
+    dst[2] = SkFixedToFloat(read_big_endian_i32(src + 16));
+    SkColorSpacePrintf("XYZ %g %g %g\n", dst[0], dst[1], dst[2]);
+    return true;
+}
+
+static SkGammas::Type set_gamma_value(SkGammas::Data* data, float value) {
+    if (color_space_almost_equal(2.2f, value)) {
+        data->fNamed = k2Dot2Curve_SkGammaNamed;
+        return SkGammas::Type::kNamed_Type;
+    }
+
+    if (color_space_almost_equal(1.0f, value)) {
+        data->fNamed = kLinear_SkGammaNamed;
+        return SkGammas::Type::kNamed_Type;
+    }
+
+    if (color_space_almost_equal(0.0f, value)) {
+        return SkGammas::Type::kNone_Type;
+    }
+
+    data->fValue = value;
+    return SkGammas::Type::kValue_Type;
+}
+
+static float read_big_endian_16_dot_16(const uint8_t buf[4]) {
+    // It just so happens that SkFixed is also 16.16!
+    return SkFixedToFloat(read_big_endian_i32(buf));
+}
+
+/**
+ *  @param outData     Set to the appropriate value on success.  If we have table or
+ *                     parametric gamma, it is the responsibility of the caller to set
+ *                     fOffset.
+ *  @param outParams   If this is a parametric gamma, this is set to the appropriate
+ *                     parameters on success.
+ *  @param outTagBytes Will be set to the length of the tag on success.
+ *  @src               Pointer to tag data.
+ *  @len               Length of tag data in bytes.
+ *
+ *  @return            kNone_Type on failure, otherwise the type of the gamma tag.
+ */
+static SkGammas::Type parse_gamma(SkGammas::Data* outData, SkColorSpaceTransferFn* outParams,
+                                  size_t* outTagBytes, const uint8_t* src, size_t len) {
+    if (len < 12) {
+        SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+        return SkGammas::Type::kNone_Type;
+    }
+
+    // In the case of consecutive gamma tags, we need to count the number of bytes in the
+    // tag, so that we can move on to the next tag.
+    size_t tagBytes;
+
+    uint32_t type = read_big_endian_u32(src);
+    // Bytes 4-7 are reserved and should be set to zero.
+    switch (type) {
+        case kTAG_CurveType: {
+            uint32_t count = read_big_endian_u32(src + 8);
+
+            // tagBytes = 12 + 2 * count
+            // We need to do safe addition here to avoid integer overflow.
+            if (!safe_add(count, count, &tagBytes) ||
+                !safe_add((size_t) 12, tagBytes, &tagBytes))
+            {
+                SkColorSpacePrintf("Invalid gamma count");
+                return SkGammas::Type::kNone_Type;
+            }
+
+            if (len < tagBytes) {
+                SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+                return SkGammas::Type::kNone_Type;
+            }
+            *outTagBytes = tagBytes;
+
+            if (0 == count) {
+                // 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.
+                outData->fNamed = kLinear_SkGammaNamed;
+                return SkGammas::Type::kNamed_Type;
+            }
+
+            const uint16_t* table = (const uint16_t*) (src + 12);
+            if (1 == count) {
+                // The table entry is the gamma (with a bias of 256).
+                float value = (read_big_endian_u16((const uint8_t*) table)) / 256.0f;
+                SkColorSpacePrintf("gamma %g\n", value);
+
+                return set_gamma_value(outData, value);
+            }
+
+            // This optimization is especially important for A2B profiles, where we do
+            // not resize tables or interpolate lookups.
+            if (2 == count) {
+                if (0 == read_big_endian_u16((const uint8_t*) &table[0]) &&
+                        65535 == read_big_endian_u16((const uint8_t*) &table[1])) {
+                    outData->fNamed = kLinear_SkGammaNamed;
+                    return SkGammas::Type::kNamed_Type;
+                }
+            }
+
+            // 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
+            // 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.
+            if (1024 == count) {
+                // The magic values were chosen because they match both the very common
+                // HP sRGB gamma table and the less common Canon sRGB gamma table (which use
+                // different rounding rules).
+                if (0 == read_big_endian_u16((const uint8_t*) &table[0]) &&
+                        3366 == read_big_endian_u16((const uint8_t*) &table[257]) &&
+                        14116 == read_big_endian_u16((const uint8_t*) &table[513]) &&
+                        34318 == read_big_endian_u16((const uint8_t*) &table[768]) &&
+                        65535 == read_big_endian_u16((const uint8_t*) &table[1023])) {
+                    outData->fNamed = kSRGB_SkGammaNamed;
+                    return SkGammas::Type::kNamed_Type;
+                }
+            }
+
+            if (26 == count) {
+                // The magic values match a clever "minimum size" approach to representing sRGB.
+                // code.facebook.com/posts/411525055626587/under-the-hood-improving-facebook-photos
+                if (0 == read_big_endian_u16((const uint8_t*) &table[0]) &&
+                        3062 == read_big_endian_u16((const uint8_t*) &table[6]) &&
+                        12824 == read_big_endian_u16((const uint8_t*) &table[12]) &&
+                        31237 == read_big_endian_u16((const uint8_t*) &table[18]) &&
+                        65535 == read_big_endian_u16((const uint8_t*) &table[25])) {
+                    outData->fNamed = kSRGB_SkGammaNamed;
+                    return SkGammas::Type::kNamed_Type;
+                }
+            }
+
+            if (4096 == count) {
+                // The magic values were chosen because they match Nikon, Epson, and
+                // lcms2 sRGB gamma tables (all of which use different rounding rules).
+                if (0 == read_big_endian_u16((const uint8_t*) &table[0]) &&
+                        950 == read_big_endian_u16((const uint8_t*) &table[515]) &&
+                        3342 == read_big_endian_u16((const uint8_t*) &table[1025]) &&
+                        14079 == read_big_endian_u16((const uint8_t*) &table[2051]) &&
+                        65535 == read_big_endian_u16((const uint8_t*) &table[4095])) {
+                    outData->fNamed = kSRGB_SkGammaNamed;
+                    return SkGammas::Type::kNamed_Type;
+                }
+            }
+
+            // Otherwise, we will represent gamma with a table.
+            outData->fTable.fSize = count;
+            return SkGammas::Type::kTable_Type;
+        }
+        case kTAG_ParaCurveType: {
+            // Determine the format of the parametric curve tag.
+            uint16_t format = read_big_endian_u16(src + 8);
+            if (format > kGABCDEF_ParaCurveType) {
+                SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
+                return SkGammas::Type::kNone_Type;
+            }
+
+            if (kExponential_ParaCurveType == format) {
+                tagBytes = 12 + 4;
+                if (len < tagBytes) {
+                    SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+                    return SkGammas::Type::kNone_Type;
+                }
+
+                // Y = X^g
+                float g = read_big_endian_16_dot_16(src + 12);
+
+                *outTagBytes = tagBytes;
+                return set_gamma_value(outData, g);
+            }
+
+            // Here's where the real parametric gammas start.  There are many
+            // permutations of the same equations.
+            //
+            // Y = (aX + b)^g + e  for X >= d
+            // Y = cX + f          otherwise
+            //
+            // We will fill in with zeros as necessary to always match the above form.
+            if (len < 24) {
+                SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+                return SkGammas::Type::kNone_Type;
+            }
+            float g = read_big_endian_16_dot_16(src + 12);
+            float a = read_big_endian_16_dot_16(src + 16);
+            float b = read_big_endian_16_dot_16(src + 20);
+            float c = 0.0f, d = 0.0f, e = 0.0f, f = 0.0f;
+            switch(format) {
+                case kGAB_ParaCurveType:
+                    tagBytes = 12 + 12;
+
+                    // Y = (aX + b)^g  for X >= -b/a
+                    // Y = 0           otherwise
+                    d = -b / a;
+                    break;
+                case kGABC_ParaCurveType:
+                    tagBytes = 12 + 16;
+                    if (len < tagBytes) {
+                        SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+                        return SkGammas::Type::kNone_Type;
+                    }
+
+                    // Y = (aX + b)^g + e  for X >= -b/a
+                    // Y = e               otherwise
+                    e = read_big_endian_16_dot_16(src + 24);
+                    d = -b / a;
+                    f = e;
+                    break;
+                case kGABDE_ParaCurveType:
+                    tagBytes = 12 + 20;
+                    if (len < tagBytes) {
+                        SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+                        return SkGammas::Type::kNone_Type;
+                    }
+
+                    // Y = (aX + b)^g  for X >= d
+                    // Y = cX          otherwise
+                    c = read_big_endian_16_dot_16(src + 24);
+                    d = read_big_endian_16_dot_16(src + 28);
+                    break;
+                case kGABCDEF_ParaCurveType:
+                    tagBytes = 12 + 28;
+                    if (len < tagBytes) {
+                        SkColorSpacePrintf("gamma tag is too small (%d bytes)", len);
+                        return SkGammas::Type::kNone_Type;
+                    }
+
+                    // Y = (aX + b)^g + e  for X >= d
+                    // Y = cX + f          otherwise
+                    c = read_big_endian_16_dot_16(src + 24);
+                    d = read_big_endian_16_dot_16(src + 28);
+                    e = read_big_endian_16_dot_16(src + 32);
+                    f = read_big_endian_16_dot_16(src + 36);
+                    break;
+                default:
+                    SkASSERT(false);
+                    return SkGammas::Type::kNone_Type;
+            }
+
+            outParams->fG = g;
+            outParams->fA = a;
+            outParams->fB = b;
+            outParams->fC = c;
+            outParams->fD = d;
+            outParams->fE = e;
+            outParams->fF = f;
+
+            if (!is_valid_transfer_fn(*outParams)) {
+                return SkGammas::Type::kNone_Type;
+            }
+
+            if (is_almost_srgb(*outParams)) {
+                outData->fNamed = kSRGB_SkGammaNamed;
+                return SkGammas::Type::kNamed_Type;
+            }
+
+            if (is_almost_2dot2(*outParams)) {
+                outData->fNamed = k2Dot2Curve_SkGammaNamed;
+                return SkGammas::Type::kNamed_Type;
+            }
+
+            *outTagBytes = tagBytes;
+            return SkGammas::Type::kParam_Type;
+        }
+        default:
+            SkColorSpacePrintf("Unsupported gamma tag type %d\n", type);
+            return SkGammas::Type::kNone_Type;
+    }
+}
+
+/**
+ *  Returns the additional size in bytes needed to store the gamma tag.
+ */
+static size_t gamma_alloc_size(SkGammas::Type type, const SkGammas::Data& data) {
+    switch (type) {
+        case SkGammas::Type::kNamed_Type:
+        case SkGammas::Type::kValue_Type:
+            return 0;
+        case SkGammas::Type::kTable_Type:
+            return sizeof(float) * data.fTable.fSize;
+        case SkGammas::Type::kParam_Type:
+            return sizeof(SkColorSpaceTransferFn);
+        default:
+            SkASSERT(false);
+            return 0;
+    }
+}
+
+/**
+ *  Sets invalid gamma to the default value.
+ */
+static void handle_invalid_gamma(SkGammas::Type* type, SkGammas::Data* data) {
+    if (SkGammas::Type::kNone_Type == *type) {
+        *type = SkGammas::Type::kNamed_Type;
+
+        // Guess sRGB in the case of a malformed transfer function.
+        data->fNamed = kSRGB_SkGammaNamed;
+    }
+}
+
+/**
+ *  Finish loading the gammas, now that we have allocated memory for the SkGammas struct.
+ *
+ *  There's nothing to do for the simple cases, but for table gammas we need to actually
+ *  read the table into heap memory.  And for parametric gammas, we need to copy over the
+ *  parameter values.
+ *
+ *  @param memory Pointer to start of the SkGammas memory block
+ *  @param offset Bytes of memory (after the SkGammas struct) that are already in use.
+ *  @param data   In-out variable.  Will fill in the offset to the table or parameters
+ *                if necessary.
+ *  @param params Parameters for gamma curve.  Only initialized/used when we have a
+ *                parametric gamma.
+ *  @param src    Pointer to start of the gamma tag.
+ *
+ *  @return       Additional bytes of memory that are being used by this gamma curve.
+ */
+static size_t load_gammas(void* memory, size_t offset, SkGammas::Type type,
+                        SkGammas::Data* data, const SkColorSpaceTransferFn& params,
+                        const uint8_t* src) {
+    void* storage = SkTAddOffset<void>(memory, offset + sizeof(SkGammas));
+
+    switch (type) {
+        case SkGammas::Type::kNamed_Type:
+        case SkGammas::Type::kValue_Type:
+            // Nothing to do here.
+            return 0;
+        case SkGammas::Type::kTable_Type: {
+            data->fTable.fOffset = offset;
+
+            float* outTable = (float*) storage;
+            const uint16_t* inTable = (const uint16_t*) (src + 12);
+            for (int i = 0; i < data->fTable.fSize; i++) {
+                outTable[i] = (read_big_endian_u16((const uint8_t*) &inTable[i])) / 65535.0f;
+            }
+
+            return sizeof(float) * data->fTable.fSize;
+        }
+        case SkGammas::Type::kParam_Type:
+            data->fTable.fOffset = offset;
+            memcpy(storage, &params, sizeof(SkColorSpaceTransferFn));
+            return sizeof(SkColorSpaceTransferFn);
+        default:
+            SkASSERT(false);
+            return 0;
+    }
+}
+
+static constexpr uint32_t kTAG_AtoBType  = SkSetFourByteTag('m', 'A', 'B', ' ');
+static constexpr uint32_t kTAG_lut8Type  = SkSetFourByteTag('m', 'f', 't', '1');
+static constexpr uint32_t kTAG_lut16Type = SkSetFourByteTag('m', 'f', 't', '2');
+
+static bool load_color_lut(sk_sp<SkColorLookUpTable>* colorLUT, uint32_t inputChannels,
+                           size_t precision, const uint8_t gridPoints[3], const uint8_t* src,
+                           size_t len) {
+    switch (precision) {
+        case 1: //  8-bit data
+        case 2: // 16-bit data
+            break;
+        default:
+            SkColorSpacePrintf("Color LUT precision must be 8-bit or 16-bit. Found: %d-bit\n",
+                               8*precision);
+            return false;
+    }
+
+    uint32_t numEntries = SkColorLookUpTable::kOutputChannels;
+    for (uint32_t i = 0; i < inputChannels; i++) {
+        if (1 >= gridPoints[i]) {
+            SkColorSpacePrintf("Each input channel must have at least two grid points.");
+            return false;
+        }
+
+        if (!safe_mul(numEntries, gridPoints[i], &numEntries)) {
+            SkColorSpacePrintf("Too many entries in Color LUT.");
+            return false;
+        }
+    }
+
+    uint32_t clutBytes;
+    if (!safe_mul(numEntries, precision, &clutBytes)) {
+        SkColorSpacePrintf("Too many entries in Color LUT.\n");
+        return false;
+    }
+
+    if (len < clutBytes) {
+        SkColorSpacePrintf("Color LUT tag is too small (%d / %d bytes).\n", len, clutBytes);
+        return false;
+    }
+
+    // Movable struct colorLUT has ownership of fTable.
+    void* memory = sk_malloc_throw(sizeof(SkColorLookUpTable) + sizeof(float) * numEntries);
+    *colorLUT = sk_sp<SkColorLookUpTable>(new (memory) SkColorLookUpTable(inputChannels,
+                                                                          gridPoints));
+
+    float* table = SkTAddOffset<float>(memory, sizeof(SkColorLookUpTable));
+    const uint8_t* ptr = src;
+    for (uint32_t i = 0; i < numEntries; i++, ptr += precision) {
+        if (1 == precision) {
+            table[i] = ((float) *ptr) / 255.0f;
+        } else {
+            table[i] = ((float) read_big_endian_u16(ptr)) / 65535.0f;
+        }
+    }
+
+    return true;
+}
+
+/**
+ *  Reads a matrix out of an A2B tag of an ICC profile.
+ *  If |translate| is true, it will load a 3x4 matrix out that corresponds to a XYZ
+ *  transform as well as a translation, and if |translate| is false it only loads a
+ *  3x3 matrix with no translation
+ *
+ *  @param matrix    The matrix to store the result in
+ *  @param src       Data to load the matrix out of.
+ *  @param len       The length of |src|.
+ *                   Must have 48 bytes if |translate| is set and 36 bytes otherwise.
+ *  @param translate Whether to read the translation column or not
+ *  @param pcs       The profile connection space of the profile this matrix is for
+ *
+ *  @return          false on failure, true on success
+ */
+static bool load_matrix(SkMatrix44* matrix, const uint8_t* src, size_t len, bool translate,
+                        SkColorSpace_A2B::PCS pcs) {
+    const size_t minLen = translate ? 48 : 36;
+    if (len < minLen) {
+        SkColorSpacePrintf("Matrix tag is too small (%d bytes).", len);
+        return false;
+    }
+
+    float encodingFactor;
+    switch (pcs) {
+        case SkColorSpace_A2B::PCS::kLAB:
+            encodingFactor = 1.f;
+            break;
+        case SkColorSpace_A2B::PCS::kXYZ:
+            encodingFactor = 65535 / 32768.f;
+            break;
+        default:
+            encodingFactor = 1.f;
+            SkASSERT(false);
+            break;
+    }
+    float array[16];
+    array[ 0] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src));
+    array[ 1] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 4));
+    array[ 2] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 8));
+
+    array[ 4] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 12));
+    array[ 5] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 16));
+    array[ 6] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 20));
+
+    array[ 8] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 24));
+    array[ 9] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 28));
+    array[10] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 32));
+
+    if (translate) {
+        array[ 3] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 36)); // translate R
+        array[ 7] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 40)); // translate G
+        array[11] = encodingFactor * SkFixedToFloat(read_big_endian_i32(src + 44)); // translate B
+    } else {
+        array[ 3] = 0.0f;
+        array[ 7] = 0.0f;
+        array[11] = 0.0f;
+    }
+
+    array[12] = 0.0f;
+    array[13] = 0.0f;
+    array[14] = 0.0f;
+    array[15] = 1.0f;
+    matrix->setRowMajorf(array);
+    SkColorSpacePrintf("A2B0 matrix loaded:\n");
+    for (int r = 0; r < 4; ++r) {
+        SkColorSpacePrintf("|");
+        for (int c = 0; c < 4; ++c) {
+            SkColorSpacePrintf(" %f ", matrix->get(r, c));
+        }
+        SkColorSpacePrintf("|\n");
+    }
+    return true;
+}
+
+static inline SkGammaNamed is_named(const sk_sp<SkGammas>& gammas) {
+    for (uint8_t i = 0; i < gammas->channels(); ++i) {
+        if (!gammas->isNamed(i) || gammas->data(i).fNamed != gammas->data(0).fNamed) {
+            return kNonStandard_SkGammaNamed;
+        }
+    }
+    return gammas->data(0).fNamed;
+}
+
+/**
+ *  Parse and load an entire stored curve. Handles invalid gammas as well.
+ *
+ *  There's nothing to do for the simple cases, but for table gammas we need to actually
+ *  read the table into heap memory.  And for parametric gammas, we need to copy over the
+ *  parameter values.
+ *
+ *  @param gammaNamed    Out-variable. The named gamma curve.
+ *  @param gammas        Out-variable. The stored gamma curve information. Can be null if
+ *                       gammaNamed is a named curve
+ *  @param inputChannels The number of gamma input channels
+ *  @param rTagPtr       Pointer to start of the gamma tag.
+ *  @param taglen        The size in bytes of the tag
+ *
+ *  @return              false on failure, true on success
+ */
+static bool parse_and_load_gamma(SkGammaNamed* gammaNamed, sk_sp<SkGammas>* gammas,
+                                 uint8_t inputChannels, const uint8_t* tagSrc, size_t tagLen) {
+    SkGammas::Data data[kMaxColorChannels];
+    SkColorSpaceTransferFn params[kMaxColorChannels];
+    SkGammas::Type type[kMaxColorChannels];
+    const uint8_t* tagPtr[kMaxColorChannels];
+
+    tagPtr[0] = tagSrc;
+
+    *gammaNamed = kNonStandard_SkGammaNamed;
+
+    // On an invalid first gamma, tagBytes remains set as zero.  This causes the two
+    // subsequent to be treated as identical (which is what we want).
+    size_t tagBytes = 0;
+    type[0] = parse_gamma(&data[0], &params[0], &tagBytes, tagPtr[0], tagLen);
+    handle_invalid_gamma(&type[0], &data[0]);
+    size_t alignedTagBytes = SkAlign4(tagBytes);
+
+    bool allChannelsSame = false;
+    if (inputChannels * alignedTagBytes <= tagLen) {
+        allChannelsSame = true;
+        for (uint8_t i = 1; i < inputChannels; ++i) {
+            if (0 != memcmp(tagSrc, tagSrc + i * alignedTagBytes, tagBytes)) {
+                allChannelsSame = false;
+                break;
+            }
+        }
+    }
+    if (allChannelsSame) {
+        if (SkGammas::Type::kNamed_Type == type[0]) {
+            *gammaNamed = data[0].fNamed;
+        } else {
+            size_t allocSize = sizeof(SkGammas);
+            return_if_false(safe_add(allocSize, gamma_alloc_size(type[0], data[0]), &allocSize),
+                            "SkGammas struct is too large to allocate");
+            void* memory = sk_malloc_throw(allocSize);
+            *gammas = sk_sp<SkGammas>(new (memory) SkGammas(inputChannels));
+            load_gammas(memory, 0, type[0], &data[0], params[0], tagPtr[0]);
+
+            for (uint8_t channel = 0; channel < inputChannels; ++channel) {
+                (*gammas)->fType[channel] = type[0];
+                (*gammas)->fData[channel] = data[0];
+            }
+        }
+    } else {
+        for (uint8_t channel = 1; channel < inputChannels; ++channel) {
+            tagPtr[channel] = tagPtr[channel - 1] + alignedTagBytes;
+            tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0;
+            tagBytes = 0;
+            type[channel] = parse_gamma(&data[channel], &params[channel], &tagBytes,
+                                        tagPtr[channel], tagLen);
+            handle_invalid_gamma(&type[channel], &data[channel]);
+            alignedTagBytes = SkAlign4(tagBytes);
+        }
+
+        size_t allocSize = sizeof(SkGammas);
+        for (uint8_t channel = 0; channel < inputChannels; ++channel) {
+            return_if_false(safe_add(allocSize, gamma_alloc_size(type[channel], data[channel]),
+                                     &allocSize),
+                            "SkGammas struct is too large to allocate");
+        }
+        void* memory = sk_malloc_throw(allocSize);
+        *gammas = sk_sp<SkGammas>(new (memory) SkGammas(inputChannels));
+
+        uint32_t offset = 0;
+        for (uint8_t channel = 0; channel < inputChannels; ++channel) {
+            (*gammas)->fType[channel] = type[channel];
+            offset += load_gammas(memory,offset, type[channel], &data[channel], params[channel],
+                                  tagPtr[channel]);
+            (*gammas)->fData[channel] = data[channel];
+
+        }
+    }
+
+    if (kNonStandard_SkGammaNamed == *gammaNamed) {
+        *gammaNamed = is_named(*gammas);
+        if (kNonStandard_SkGammaNamed != *gammaNamed) {
+            // No need to keep the gammas struct, the enum is enough.
+            *gammas = nullptr;
+        }
+    }
+    return true;
+}
+
+static bool is_lut_gamma_linear(const uint8_t* src, size_t count, size_t precision) {
+    // check for linear gamma (this is very common in lut gammas, as they aren't optional)
+    const float normalizeX = 1.f / (count - 1);
+    for (uint32_t x = 0; x < count; ++x) {
+        const float y = precision == 1 ? (src[x] / 255.f)
+                                       : (read_big_endian_u16(src + 2*x) / 65535.f);
+        if (!color_space_almost_equal(x * normalizeX, y)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static bool load_lut_gammas(sk_sp<SkGammas>* gammas, SkGammaNamed* gammaNamed, size_t numTables,
+                            size_t entriesPerTable, size_t precision, const uint8_t* src,
+                            size_t len) {
+    if (precision != 1 && precision != 2) {
+        SkColorSpacePrintf("Invalid gamma table precision %d\n", precision);
+        return false;
+    }
+    uint32_t totalEntries;
+    return_if_false(safe_mul(entriesPerTable, numTables, &totalEntries),
+                    "Too many entries in gamma table.");
+    uint32_t readBytes;
+    return_if_false(safe_mul(precision, totalEntries, &readBytes),
+                    "SkGammas struct is too large to read");
+    if (len < readBytes) {
+        SkColorSpacePrintf("Gamma table is too small. Provided: %d. Required: %d\n",
+                           len, readBytes);
+        return false;
+    }
+
+    uint32_t writeBytesPerChannel;
+    return_if_false(safe_mul(sizeof(float), entriesPerTable, &writeBytesPerChannel),
+                    "SkGammas struct is too large to allocate");
+    const size_t readBytesPerChannel = precision * entriesPerTable;
+    size_t numTablesToUse = 1;
+    for (size_t tableIndex = 1; tableIndex < numTables; ++tableIndex) {
+        if (0 != memcmp(src, src + readBytesPerChannel * tableIndex, readBytesPerChannel)) {
+            numTablesToUse = numTables;
+            break;
+        }
+    }
+
+    if (1 == numTablesToUse) {
+        if (is_lut_gamma_linear(src, entriesPerTable, precision)) {
+            *gammaNamed = kLinear_SkGammaNamed;
+            return true;
+        }
+    }
+    *gammaNamed = kNonStandard_SkGammaNamed;
+
+    uint32_t writetableBytes;
+    return_if_false(safe_mul(numTablesToUse, writeBytesPerChannel, &writetableBytes),
+                    "SkGammas struct is too large to allocate");
+    size_t allocSize = sizeof(SkGammas);
+    return_if_false(safe_add(allocSize, (size_t)writetableBytes, &allocSize),
+                    "SkGammas struct is too large to allocate");
+
+    void* memory = sk_malloc_throw(allocSize);
+    *gammas = sk_sp<SkGammas>(new (memory) SkGammas(numTables));
+
+    for (size_t tableIndex = 0; tableIndex < numTablesToUse; ++tableIndex) {
+        const uint8_t* ptr = src + readBytesPerChannel * tableIndex;
+        const size_t offset = sizeof(SkGammas) + tableIndex * writeBytesPerChannel;
+        float* table = SkTAddOffset<float>(memory, offset);
+        if (1 == precision) {
+            for (uint32_t i = 0; i < entriesPerTable; ++i, ptr += 1) {
+                table[i] = ((float) *ptr) / 255.0f;
+            }
+        } else if (2 == precision) {
+            for (uint32_t i = 0; i < entriesPerTable; ++i, ptr += 2) {
+                table[i] = ((float) read_big_endian_u16(ptr)) / 65535.0f;
+            }
+        }
+    }
+
+    SkASSERT(1 == numTablesToUse|| numTables == numTablesToUse);
+
+    size_t tableOffset = 0;
+    for (size_t tableIndex = 0; tableIndex < numTables; ++tableIndex) {
+        (*gammas)->fType[tableIndex]                = SkGammas::Type::kTable_Type;
+        (*gammas)->fData[tableIndex].fTable.fOffset = tableOffset;
+        (*gammas)->fData[tableIndex].fTable.fSize   = entriesPerTable;
+        if (numTablesToUse > 1) {
+            tableOffset += writeBytesPerChannel;
+        }
+    }
+
+    return true;
+}
+
+bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, const uint8_t* src,
+                           size_t len, SkColorSpace_A2B::PCS pcs) {
+    SkASSERT(len >= 32);
+    // Read the number of channels.  The four bytes (4-7) that we skipped are reserved and
+    // must be zero.
+    const uint8_t inputChannels = src[8];
+    const uint8_t outputChannels = src[9];
+    if (SkColorLookUpTable::kOutputChannels != outputChannels) {
+        // We only handle RGB outputs. The number of output channels must be 3.
+        SkColorSpacePrintf("Output channels (%d) must equal 3 in A to B tag.\n", outputChannels);
+        return false;
+    }
+    if (inputChannels == 0 || inputChannels > 4) {
+        // And we only support 4 input channels.
+        // ICC says up to 16 but our decode can only handle 4.
+        // It could easily be extended to support up to 8, but we only allow CMYK/RGB
+        // input color spaces which are 3 and 4 so let's restrict it to 4 instead of 8.
+        // We can always change this check when we support bigger input spaces.
+        SkColorSpacePrintf("Input channels (%d) must be between 1 and 4 in A to B tag.\n",
+                           inputChannels);
+        return false;
+    }
+
+
+    // It is important that these are loaded in the order of application, as the
+    // order you construct an A2B color space's elements is the order it is applied
+
+    // If the offset is non-zero it indicates that the element is present.
+    const uint32_t offsetToACurves = read_big_endian_i32(src + 28);
+    if (0 != offsetToACurves && offsetToACurves < len) {
+        const size_t tagLen = len - offsetToACurves;
+        SkGammaNamed gammaNamed;
+        sk_sp<SkGammas> gammas;
+        if (!parse_and_load_gamma(&gammaNamed, &gammas, inputChannels, src + offsetToACurves,
+                                  tagLen)) {
+            return false;
+        }
+        if (gammas) {
+            elements->push_back(SkColorSpace_A2B::Element(std::move(gammas)));
+        } else if (kLinear_SkGammaNamed != gammaNamed) {
+            elements->push_back(SkColorSpace_A2B::Element(gammaNamed, inputChannels));
+        }
+    }
+
+    const uint32_t offsetToColorLUT = read_big_endian_i32(src + 24);
+    if (0 != offsetToColorLUT && offsetToColorLUT < len) {
+        sk_sp<SkColorLookUpTable> colorLUT;
+        const uint8_t* clutSrc = src + offsetToColorLUT;
+        const size_t clutLen = len - offsetToColorLUT;
+        // 16 bytes reserved for grid points, 1 for precision, 3 for padding.
+        // The color LUT data follows after this header.
+        static constexpr uint32_t kColorLUTHeaderSize = 20;
+        if (clutLen < kColorLUTHeaderSize) {
+            SkColorSpacePrintf("Color LUT tag is too small (%d bytes).", clutLen);
+            return false;
+        }
+
+        SkASSERT(inputChannels <= kMaxColorChannels);
+        uint8_t gridPoints[kMaxColorChannels];
+        for (uint32_t i = 0; i < inputChannels; ++i) {
+            gridPoints[i] = clutSrc[i];
+        }
+        // Space is provided for a maximum of 16 input channels.
+        // Now we determine the precision of the table values.
+        const uint8_t precision = clutSrc[16];
+        if (!load_color_lut(&colorLUT, inputChannels, precision, gridPoints,
+                            clutSrc + kColorLUTHeaderSize, clutLen - kColorLUTHeaderSize)) {
+            SkColorSpacePrintf("Failed to read color LUT from A to B tag.\n");
+            return false;
+        }
+        elements->push_back(SkColorSpace_A2B::Element(std::move(colorLUT)));
+    }
+
+    const uint32_t offsetToMCurves = read_big_endian_i32(src + 20);
+    if (0 != offsetToMCurves && offsetToMCurves < len) {
+        const size_t tagLen = len - offsetToMCurves;
+        SkGammaNamed gammaNamed;
+        sk_sp<SkGammas> gammas;
+        if (!parse_and_load_gamma(&gammaNamed, &gammas, outputChannels, src + offsetToMCurves,
+                                  tagLen)) {
+            return false;
+        }
+        if (gammas) {
+            elements->push_back(SkColorSpace_A2B::Element(std::move(gammas)));
+        } else if (kLinear_SkGammaNamed != gammaNamed) {
+            elements->push_back(SkColorSpace_A2B::Element(gammaNamed, outputChannels));
+        }
+    }
+
+    const uint32_t offsetToMatrix = read_big_endian_i32(src + 16);
+    if (0 != offsetToMatrix && offsetToMatrix < len) {
+        SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor);
+        if (!load_matrix(&matrix, src + offsetToMatrix, len - offsetToMatrix, true, pcs)) {
+            SkColorSpacePrintf("Failed to read matrix from A to B tag.\n");
+        } else if (!matrix.isIdentity()) {
+            elements->push_back(SkColorSpace_A2B::Element(matrix));
+        }
+    }
+
+    const uint32_t offsetToBCurves = read_big_endian_i32(src + 12);
+    if (0 != offsetToBCurves && offsetToBCurves < len) {
+        const size_t tagLen = len - offsetToBCurves;
+        SkGammaNamed gammaNamed;
+        sk_sp<SkGammas> gammas;
+        if (!parse_and_load_gamma(&gammaNamed, &gammas, outputChannels, src + offsetToBCurves,
+                                  tagLen)) {
+            return false;
+        }
+        if (gammas) {
+            elements->push_back(SkColorSpace_A2B::Element(std::move(gammas)));
+        } else if (kLinear_SkGammaNamed != gammaNamed) {
+            elements->push_back(SkColorSpace_A2B::Element(gammaNamed, outputChannels));
+        }
+    }
+
+    return true;
+}
+
+bool load_a2b0_lutn_type(std::vector<SkColorSpace_A2B::Element>* elements, const uint8_t* src,
+                         size_t len, SkColorSpace_A2B::PCS pcs) {
+    const uint32_t type = read_big_endian_u32(src);
+    switch (type) {
+        case kTAG_lut8Type:
+            SkASSERT(len >= 48);
+            break;
+        case kTAG_lut16Type:
+            SkASSERT(len >= 52);
+            break;
+        default:
+            SkASSERT(false);
+            return false;
+    }
+    // Read the number of channels.
+    // The four bytes (4-7) that we skipped are reserved and must be zero.
+    const uint8_t inputChannels = src[8];
+    const uint8_t outputChannels = src[9];
+    if (SkColorLookUpTable::kOutputChannels != outputChannels) {
+        // We only handle RGB outputs. The number of output channels must be 3.
+        SkColorSpacePrintf("Output channels (%d) must equal 3 in A to B tag.\n", outputChannels);
+        return false;
+    }
+    if (inputChannels == 0 || inputChannels > 4) {
+        // And we only support 4 input channels.
+        // ICC says up to 16 but our decode can only handle 4.
+        // It could easily be extended to support up to 8, but we only allow CMYK/RGB
+        // input color spaces which are 3 and 4 so let's restrict it to 4 instead of 8.
+        // We can always change this check when we support bigger input spaces.
+        SkColorSpacePrintf("Input channels (%d) must be between 1 and 4 in A to B tag.\n",
+                           inputChannels);
+        return false;
+    }
+
+    const uint8_t clutGridPoints = src[10];
+    // 11th byte reserved for padding (required to be zero)
+
+    SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor);
+    load_matrix(&matrix, &src[12], len - 12, false, pcs);
+    if (!matrix.isIdentity()) {
+        // ICC specs (10.8/10.9) say lut8/16Type profiles must have identity matrices
+        // if the input color space is not PCSXYZ, and we do not support PCSXYZ input color spaces
+        // so we should never encounter a non-identity matrix here.
+        // However, 2 test images from the ICC website have RGB input spaces and non-identity
+        // matrices so we're not going to fail here, despite being against the spec.
+        SkColorSpacePrintf("Warning: non-Identity matrix found in non-XYZ input color space"
+                           "lut profile");
+        elements->push_back(SkColorSpace_A2B::Element(matrix));
+    }
+
+    size_t dataOffset      = 48;
+    // # of input table entries
+    size_t inTableEntries  = 256;
+    // # of output table entries
+    size_t outTableEntries = 256;
+    size_t precision       = 1;
+    if (kTAG_lut16Type == type) {
+        dataOffset      = 52;
+        inTableEntries  = read_big_endian_u16(src + 48);
+        outTableEntries = read_big_endian_u16(src + 50);
+        precision       = 2;
+
+        constexpr size_t kMaxLut16GammaEntries = 4096;
+        if (inTableEntries < 2) {
+            SkColorSpacePrintf("Too few (%d) input gamma table entries. Must have at least 2.\n",
+                               inTableEntries);
+            return false;
+        } else if (inTableEntries > kMaxLut16GammaEntries) {
+            SkColorSpacePrintf("Too many (%d) input gamma table entries. Must have at most %d.\n",
+                               inTableEntries, kMaxLut16GammaEntries);
+            return false;
+        }
+
+        if (outTableEntries < 2) {
+            SkColorSpacePrintf("Too few (%d) output gamma table entries. Must have at least 2.\n",
+                               outTableEntries);
+            return false;
+        } else if (outTableEntries > kMaxLut16GammaEntries) {
+            SkColorSpacePrintf("Too many (%d) output gamma table entries. Must have at most %d.\n",
+                               outTableEntries, kMaxLut16GammaEntries);
+            return false;
+        }
+    }
+
+    const size_t inputOffset = dataOffset;
+    return_if_false(len >= inputOffset, "A2B0 lutnType tag too small for input gamma table");
+    sk_sp<SkGammas> inputGammas;
+    SkGammaNamed inputGammaNamed;
+    if (!load_lut_gammas(&inputGammas, &inputGammaNamed, inputChannels, inTableEntries, precision,
+                         src + inputOffset, len - inputOffset)) {
+        SkColorSpacePrintf("Failed to read input gammas from lutnType tag.\n");
+        return false;
+    }
+    SkASSERT(inputGammas || inputGammaNamed != kNonStandard_SkGammaNamed);
+    if (kLinear_SkGammaNamed != inputGammaNamed) {
+        if (kNonStandard_SkGammaNamed != inputGammaNamed) {
+            elements->push_back(SkColorSpace_A2B::Element(inputGammaNamed, inputChannels));
+        } else {
+            elements->push_back(SkColorSpace_A2B::Element(std::move(inputGammas)));
+        }
+    }
+
+    const size_t clutOffset = inputOffset + precision*inTableEntries*inputChannels;
+    return_if_false(len >= clutOffset, "A2B0 lutnType tag too small for CLUT");
+    sk_sp<SkColorLookUpTable> colorLUT;
+    const uint8_t gridPoints[kMaxColorChannels] = {
+        clutGridPoints, clutGridPoints, clutGridPoints, clutGridPoints
+    };
+    if (!load_color_lut(&colorLUT, inputChannels, precision, gridPoints, src + clutOffset,
+                        len - clutOffset)) {
+        SkColorSpacePrintf("Failed to read color LUT from lutnType tag.\n");
+        return false;
+    }
+    SkASSERT(colorLUT);
+    elements->push_back(SkColorSpace_A2B::Element(std::move(colorLUT)));
+
+    size_t clutSize = precision * outputChannels;
+    for (int i = 0; i < inputChannels; ++i) {
+        clutSize *= clutGridPoints;
+    }
+    const size_t outputOffset = clutOffset + clutSize;
+    return_if_false(len >= outputOffset, "A2B0 lutnType tag too small for output gamma table");
+    sk_sp<SkGammas> outputGammas;
+    SkGammaNamed outputGammaNamed;
+    if (!load_lut_gammas(&outputGammas, &outputGammaNamed, outputChannels, outTableEntries,
+                         precision, src + outputOffset, len - outputOffset)) {
+        SkColorSpacePrintf("Failed to read output gammas from lutnType tag.\n");
+        return false;
+    }
+    SkASSERT(outputGammas || outputGammaNamed != kNonStandard_SkGammaNamed);
+    if (kLinear_SkGammaNamed != outputGammaNamed) {
+        if (kNonStandard_SkGammaNamed != outputGammaNamed) {
+            elements->push_back(SkColorSpace_A2B::Element(outputGammaNamed, outputChannels));
+        } else {
+            elements->push_back(SkColorSpace_A2B::Element(std::move(outputGammas)));
+        }
+    }
+
+    return true;
+}
+
+static inline int icf_channels(SkColorSpace::Type iccType) {
+    switch (iccType) {
+        case SkColorSpace::kRGB_Type:
+            return 3;
+        case SkColorSpace::kCMYK_Type:
+            return 4;
+        default:
+            SkASSERT(false);
+            return 0;
+    }
+}
+
+static bool load_a2b0(std::vector<SkColorSpace_A2B::Element>* elements, const uint8_t* src,
+                      size_t len, SkColorSpace_A2B::PCS pcs,
+                      SkColorSpace::Type iccType) {
+    if (len < 4) {
+        return false;
+    }
+    const uint32_t type = read_big_endian_u32(src);
+
+    switch (type) {
+        case kTAG_AtoBType:
+            if (len < 32) {
+                SkColorSpacePrintf("A to B tag is too small (%d bytes).", len);
+                return false;
+            }
+            SkColorSpacePrintf("A2B0 tag is of type lutAtoBType\n");
+            if (!load_a2b0_a_to_b_type(elements, src, len, pcs)) {
+                return false;
+            }
+            break;
+        case kTAG_lut8Type:
+            if (len < 48) {
+                SkColorSpacePrintf("lut8 tag is too small (%d bytes).", len);
+                return false;
+            }
+            SkColorSpacePrintf("A2B0 tag of type lut8Type\n");
+            if (!load_a2b0_lutn_type(elements, src, len, pcs)) {
+                return false;
+            }
+            break;
+        case kTAG_lut16Type:
+            if (len < 52) {
+                SkColorSpacePrintf("lut16 tag is too small (%d bytes).", len);
+                return false;
+            }
+            SkColorSpacePrintf("A2B0 tag of type lut16Type\n");
+            if (!load_a2b0_lutn_type(elements, src, len, pcs)) {
+                return false;
+            }
+            break;
+        default:
+            SkColorSpacePrintf("Unsupported A to B tag type: %c%c%c%c\n", (type>>24)&0xFF,
+                               (type>>16)&0xFF, (type>>8)&0xFF, type&0xFF);
+            return false;
+    }
+    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(iccType);
+    }
+    // now let's verify that the input/output channels of each A2B element actually match up
+    if (icf_channels(iccType) != elements->front().inputChannels()) {
+        SkColorSpacePrintf("Input channel count does not match first A2B element's input count");
+        return false;
+    }
+    for (size_t i = 1; i < elements->size(); ++i) {
+        if ((*elements)[i - 1].outputChannels() != (*elements)[i].inputChannels()) {
+            SkColorSpacePrintf("A2B elements don't agree in input/output channel counts");
+            return false;
+        }
+    }
+    if (kPCSChannels != elements->back().outputChannels()) {
+        SkColorSpacePrintf("PCS channel count doesn't match last A2B element's output count");
+        return false;
+    }
+    return true;
+}
+
+static bool tag_equals(const ICCTag* a, const ICCTag* b, const uint8_t* base) {
+    if (!a || !b) {
+        return a == b;
+    }
+
+    if (a->fLength != b->fLength) {
+        return false;
+    }
+
+    if (a->fOffset == b->fOffset) {
+        return true;
+    }
+
+    return !memcmp(a->addr(base), b->addr(base), a->fLength);
+}
+
+static inline bool is_close_to_d50(const SkMatrix44& matrix) {
+    // rX + gX + bX
+    float X = matrix.getFloat(0, 0) + matrix.getFloat(0, 1) + matrix.getFloat(0, 2);
+
+    // rY + gY + bY
+    float Y = matrix.getFloat(1, 0) + matrix.getFloat(1, 1) + matrix.getFloat(1, 2);
+
+    // rZ + gZ + bZ
+    float Z = matrix.getFloat(2, 0) + matrix.getFloat(2, 1) + matrix.getFloat(2, 2);
+
+    static const float kD50_WhitePoint[3] = { 0.96420f, 1.00000f, 0.82491f };
+
+    // This is a bit more lenient than QCMS and Adobe.  Is there a reason to be stricter here?
+    return (SkTAbs(X - kD50_WhitePoint[0]) <= 0.04f) &&
+           (SkTAbs(Y - kD50_WhitePoint[1]) <= 0.04f) &&
+           (SkTAbs(Z - kD50_WhitePoint[2]) <= 0.04f);
+}
+
+static sk_sp<SkColorSpace> make_xyz(const ICCProfileHeader& header, ICCTag* tags, int tagCount,
+                                    const uint8_t* base, sk_sp<SkData> profileData) {
+    if (kLAB_PCSSpace == header.fPCS) {
+        return nullptr;
+    }
+
+    // Recognize the rXYZ, gXYZ, and bXYZ tags.
+    const ICCTag* r = ICCTag::Find(tags, tagCount, kTAG_rXYZ);
+    const ICCTag* g = ICCTag::Find(tags, tagCount, kTAG_gXYZ);
+    const ICCTag* b = ICCTag::Find(tags, tagCount, kTAG_bXYZ);
+    if (!r || !g || !b) {
+        return nullptr;
+    }
+
+    float toXYZ[9];
+    if (!load_xyz(&toXYZ[0], r->addr(base), r->fLength) ||
+        !load_xyz(&toXYZ[3], g->addr(base), g->fLength) ||
+        !load_xyz(&toXYZ[6], b->addr(base), b->fLength))
+    {
+        return_null("Need valid rgb tags for XYZ space");
+    }
+    SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
+    mat.set3x3(toXYZ[0], toXYZ[1], toXYZ[2],
+               toXYZ[3], toXYZ[4], toXYZ[5],
+               toXYZ[6], toXYZ[7], toXYZ[8]);
+    if (!is_close_to_d50(mat)) {
+        return_null("XYZ matrix is not D50");
+    }
+
+    // If some, but not all, of the gamma tags are missing, assume that all
+    // gammas are meant to be the same.
+    r = ICCTag::Find(tags, tagCount, kTAG_rTRC);
+    g = ICCTag::Find(tags, tagCount, kTAG_gTRC);
+    b = ICCTag::Find(tags, tagCount, kTAG_bTRC);
+    if (!r || !g || !b) {
+        return_null("Need valid TRC tags for XYZ space");
+    }
+
+    SkGammaNamed gammaNamed = kNonStandard_SkGammaNamed;
+    sk_sp<SkGammas> gammas = nullptr;
+    size_t tagBytes;
+    if (tag_equals(r, g, base) && tag_equals(g, b, base)) {
+        SkGammas::Data data;
+        SkColorSpaceTransferFn params;
+        SkGammas::Type type =
+                parse_gamma(&data, &params, &tagBytes, r->addr(base), r->fLength);
+        handle_invalid_gamma(&type, &data);
+
+        if (SkGammas::Type::kNamed_Type == type) {
+            gammaNamed = data.fNamed;
+        } 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);
+            gammas = sk_sp<SkGammas>(new (memory) SkGammas(3));
+            load_gammas(memory, 0, type, &data, params, r->addr(base));
+
+            for (int i = 0; i < 3; ++i) {
+                gammas->fType[i] = type;
+                gammas->fData[i] = data;
+            }
+        }
+    } else {
+        SkGammas::Data rData;
+        SkColorSpaceTransferFn rParams;
+        SkGammas::Type rType =
+                parse_gamma(&rData, &rParams, &tagBytes, r->addr(base), r->fLength);
+        handle_invalid_gamma(&rType, &rData);
+
+        SkGammas::Data gData;
+        SkColorSpaceTransferFn gParams;
+        SkGammas::Type gType =
+                parse_gamma(&gData, &gParams, &tagBytes, g->addr(base), g->fLength);
+        handle_invalid_gamma(&gType, &gData);
+
+        SkGammas::Data bData;
+        SkColorSpaceTransferFn bParams;
+        SkGammas::Type bType =
+                parse_gamma(&bData, &bParams, &tagBytes, b->addr(base), b->fLength);
+        handle_invalid_gamma(&bType, &bData);
+
+        size_t allocSize = sizeof(SkGammas);
+        if (!safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize) ||
+            !safe_add(allocSize, gamma_alloc_size(gType, gData), &allocSize) ||
+            !safe_add(allocSize, gamma_alloc_size(bType, bData), &allocSize)) {
+            return_null("SkGammas struct is too large to allocate");
+        }
+        void* memory = sk_malloc_throw(allocSize);
+        gammas = sk_sp<SkGammas>(new (memory) SkGammas(3));
+
+        uint32_t offset = 0;
+        gammas->fType[0] = rType;
+        offset += load_gammas(memory, offset, rType, &rData, rParams,
+                                r->addr(base));
+
+        gammas->fType[1] = gType;
+        offset += load_gammas(memory, offset, gType, &gData, gParams,
+                                g->addr(base));
+
+        gammas->fType[2] = bType;
+        load_gammas(memory, offset, bType, &bData, bParams, b->addr(base));
+
+        gammas->fData[0] = rData;
+        gammas->fData[1] = gData;
+        gammas->fData[2] = bData;
+    }
+
+
+    if (kNonStandard_SkGammaNamed == gammaNamed) {
+        // It's possible that we'll initially detect non-matching gammas, only for
+        // them to evaluate to the same named gamma curve.
+        gammaNamed = is_named(gammas);
+    }
+
+    if (kNonStandard_SkGammaNamed == gammaNamed) {
+        return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed,
+                                                        std::move(gammas),
+                                                        mat, std::move(profileData)));
+    }
+
+    return SkColorSpace::MakeRGB(gammaNamed, mat);
+}
+
+static sk_sp<SkColorSpace> make_gray(const ICCProfileHeader& header, ICCTag* tags, int tagCount,
+                                     const uint8_t* base, sk_sp<SkData> profileData) {
+    if (kLAB_PCSSpace == header.fPCS) {
+        return nullptr;
+    }
+
+    const ICCTag* grayTRC = ICCTag::Find(tags, tagCount, kTAG_kTRC);
+    if (!grayTRC) {
+        return_null("grayTRC tag required for monochrome profiles.");
+    }
+    SkGammas::Data data;
+    SkColorSpaceTransferFn params;
+    size_t tagBytes;
+    SkGammas::Type type =
+            parse_gamma(&data, &params, &tagBytes, grayTRC->addr(base), grayTRC->fLength);
+    handle_invalid_gamma(&type, &data);
+
+    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) {
+        return SkColorSpace::MakeRGB(data.fNamed, toXYZD50);
+    }
+
+    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::Type 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);
+    if (a2b0) {
+        const SkColorSpace_A2B::PCS pcs = kXYZ_PCSSpace == header.fPCS
+                                        ? SkColorSpace_A2B::PCS::kXYZ
+                                        : SkColorSpace_A2B::PCS::kLAB;
+        std::vector<SkColorSpace_A2B::Element> 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)));
+        }
+    }
+
+    return nullptr;
+}
+
+sk_sp<SkColorSpace> SkColorSpace::MakeICC(const void* input, size_t len) {
+    if (!input || len < kICCHeaderSize) {
+        return_null("Data is null or not large enough to contain an ICC profile");
+    }
+
+    // Make sure we're at least as strict as skcms_Parse().
+    skcms_ICCProfile p;
+    if (!skcms_Parse(input, len, &p)) {
+        return nullptr;
+    }
+
+    // Create our own copy of the input.
+    void* memory = sk_malloc_throw(len);
+    memcpy(memory, input, len);
+    sk_sp<SkData> profileData = SkData::MakeFromMalloc(memory, len);
+    const uint8_t* base = profileData->bytes();
+    const uint8_t* ptr = base;
+
+    // Read the ICC profile header and check to make sure that it is valid.
+    ICCProfileHeader header;
+    header.init(ptr, len);
+    if (!header.valid()) {
+        return nullptr;
+    }
+
+    // Adjust ptr and len before reading the tags.
+    if (len < header.fSize) {
+        SkColorSpacePrintf("ICC profile might be truncated.\n");
+    } else if (len > header.fSize) {
+        SkColorSpacePrintf("Caller provided extra data beyond the end of the ICC profile.\n");
+        len = header.fSize;
+    }
+    ptr += kICCHeaderSize;
+    len -= kICCHeaderSize;
+
+    // Parse tag headers.
+    uint32_t tagCount = header.fTagCount;
+    SkColorSpacePrintf("ICC profile contains %d tags.\n", tagCount);
+    if (len < kICCTagTableEntrySize * tagCount) {
+        return_null("Not enough input data to read tag table entries");
+    }
+
+    SkAutoTArray<ICCTag> tags(tagCount);
+    for (uint32_t i = 0; i < tagCount; i++) {
+        ptr = tags[i].init(ptr);
+        SkColorSpacePrintf("[%d] %c%c%c%c %d %d\n", i, (tags[i].fSignature >> 24) & 0xFF,
+                (tags[i].fSignature >> 16) & 0xFF, (tags[i].fSignature >>  8) & 0xFF,
+                (tags[i].fSignature >>  0) & 0xFF, tags[i].fOffset, tags[i].fLength);
+
+        if (!tags[i].valid(kICCHeaderSize + len)) {
+            return_null("Tag is too large to fit in ICC profile");
+        }
+    }
+
+    Type a2b_type = kRGB_Type;
+    switch (header.fInputColorSpace) {
+        case kRGB_ColorSpace: {
+            sk_sp<SkColorSpace> colorSpace =
+                    make_xyz(header, tags.get(), tagCount, base, profileData);
+            if (colorSpace) {
+                return colorSpace;
+            }
+            break;
+        }
+        case kGray_ColorSpace: {
+            return make_gray(header, tags.get(), tagCount, base, profileData);
+        }
+        case kCMYK_ColorSpace:
+            a2b_type = kCMYK_Type;
+            break;
+        default:
+            return_null("ICC profile contains unsupported colorspace");
+    }
+
+    return make_a2b(a2b_type, header, tags.get(), tagCount, base, profileData);
+}
diff --git a/src/core/SkColorSpace_XYZ.cpp b/src/core/SkColorSpace_XYZ.cpp
index cbb55cc..8a38f52 100644
--- a/src/core/SkColorSpace_XYZ.cpp
+++ b/src/core/SkColorSpace_XYZ.cpp
@@ -10,16 +10,30 @@
 #include "SkColorSpaceXformPriv.h"
 #include "SkOpts.h"
 
-SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed,
-                                   const SkColorSpaceTransferFn* transferFn,
-                                   const SkMatrix44& toXYZD50)
-        : fGammaNamed(gammaNamed)
-        , fToXYZD50(toXYZD50)
-        , fToXYZD50Hash(SkOpts::hash_fn(toXYZD50.values(), 16 * sizeof(SkMScalar), 0))
-        , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) {
-    SkASSERT(fGammaNamed != kNonStandard_SkGammaNamed || transferFn);
-    if (transferFn) {
-        fTransferFn = *transferFn;
+SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50)
+    : fProfileData(nullptr)
+    , fGammaNamed(gammaNamed)
+    , fGammas(nullptr)
+    , fToXYZD50(toXYZD50)
+    , fToXYZD50Hash(SkOpts::hash_fn(toXYZD50.values(), 16 * sizeof(SkMScalar), 0))
+    , fFromXYZD50(SkMatrix44::kUninitialized_Constructor)
+{}
+
+SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, sk_sp<SkGammas> gammas,
+                                   const SkMatrix44& toXYZD50, sk_sp<SkData> profileData)
+    : fProfileData(std::move(profileData))
+    , fGammaNamed(gammaNamed)
+    , fGammas(std::move(gammas))
+    , fToXYZD50(toXYZD50)
+    , fToXYZD50Hash(SkOpts::hash_fn(toXYZD50.values(), 16 * sizeof(SkMScalar), 0))
+    , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) {
+    SkASSERT(!fGammas || 3 == fGammas->channels());
+    if (fGammas) {
+        for (int i = 0; i < fGammas->channels(); ++i) {
+            if (SkGammas::Type::kTable_Type == fGammas->type(i)) {
+                SkASSERT(fGammas->data(i).fTable.fSize >= 2);
+            }
+        }
     }
 }
 
@@ -45,25 +59,26 @@
 }
 
 bool SkColorSpace_XYZ::onIsNumericalTransferFn(SkColorSpaceTransferFn* coeffs) const {
-    switch (fGammaNamed) {
-        case kSRGB_SkGammaNamed:
-            *coeffs = gSRGB_TransferFn;
-            break;
-        case k2Dot2Curve_SkGammaNamed:
-            *coeffs = g2Dot2_TransferFn;
-            break;
-        case kLinear_SkGammaNamed:
-            *coeffs = gLinear_TransferFn;
-            break;
-        case kNonStandard_SkGammaNamed:
-            *coeffs = fTransferFn;
-            break;
-        default:
-            SkDEBUGFAIL("Unknown named gamma");
-            return false;
+    if (named_to_parametric(coeffs, fGammaNamed)) {
+        return true;
     }
 
-    return true;
+    SkASSERT(fGammas);
+    if (!fGammas->allChannelsSame()) {
+        return false;
+    }
+
+    if (fGammas->isValue(0)) {
+        value_to_parametric(coeffs, fGammas->data(0).fValue);
+        return true;
+    }
+
+    if (fGammas->isParametric(0)) {
+        *coeffs = fGammas->params(0);
+        return true;
+    }
+
+    return false;
 }
 
 sk_sp<SkColorSpace> SkColorSpace_XYZ::makeLinearGamma() const {
@@ -85,5 +100,5 @@
     spin.set3x3(0, 1, 0, 0, 0, 1, 1, 0, 0);
     spin.postConcat(fToXYZD50);
     (void)spin.getType();  // Pre-cache spin matrix type to avoid races in future getType() calls.
-    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(fGammaNamed, &fTransferFn, spin));
+    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(fGammaNamed, fGammas, spin, fProfileData));
 }
diff --git a/src/core/SkColorSpace_XYZ.h b/src/core/SkColorSpace_XYZ.h
index 97e3dbc..72a1d62 100644
--- a/src/core/SkColorSpace_XYZ.h
+++ b/src/core/SkColorSpace_XYZ.h
@@ -10,6 +10,7 @@
 
 #include "SkColorSpace.h"
 #include "SkData.h"
+#include "SkGammas.h"
 #include "SkOnce.h"
 
 class SkColorSpace_XYZ : public SkColorSpace {
@@ -23,19 +24,29 @@
     bool onGammaIsLinear() const override;
     bool onIsNumericalTransferFn(SkColorSpaceTransferFn* coeffs) const override;
 
+    const SkData* onProfileData() const override { return fProfileData.get(); }
+
     sk_sp<SkColorSpace> makeLinearGamma() const override;
     sk_sp<SkColorSpace> makeSRGBGamma() const override;
     sk_sp<SkColorSpace> makeColorSpin() const override;
 
     SkGammaNamed onGammaNamed() const override { return fGammaNamed; }
 
-    SkColorSpace_XYZ(SkGammaNamed gammaNamed, const SkColorSpaceTransferFn* transferFn,
-                     const SkMatrix44& toXYZ);
+    const SkGammas* gammas() const { return fGammas.get(); }
+
+    void toDstGammaTables(const uint8_t* tables[3], sk_sp<SkData>* storage, int numTables) const;
+
+    SkColorSpace_XYZ(SkGammaNamed gammaNamed, const SkMatrix44& toXYZ);
+
+    SkColorSpace_XYZ(SkGammaNamed gammaNamed, sk_sp<SkGammas> gammas,
+                     const SkMatrix44& toXYZ, sk_sp<SkData> profileData);
 
 private:
-    SkGammaNamed           fGammaNamed;
-    SkColorSpaceTransferFn fTransferFn;
-    SkMatrix44             fToXYZD50;
+    sk_sp<SkData>          fProfileData;
+
+    const SkGammaNamed     fGammaNamed;
+    sk_sp<SkGammas>        fGammas;
+    const SkMatrix44       fToXYZD50;
     uint32_t               fToXYZD50Hash;
 
     mutable SkMatrix44     fFromXYZD50;
diff --git a/src/core/SkGammas.h b/src/core/SkGammas.h
new file mode 100644
index 0000000..6cb504c
--- /dev/null
+++ b/src/core/SkGammas.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkGammas_DEFINED
+#define SkGammas_DEFINED
+
+#include "SkColorSpace.h"
+#include "SkData.h"
+#include "SkTemplates.h"
+
+struct SkGammas : SkRefCnt {
+
+    // There are four possible representations for gamma curves.  kNone_Type is used
+    // as a placeholder until the struct is initialized.  It is not a valid value.
+    enum class Type {
+        kNone_Type,
+        kNamed_Type,
+        kValue_Type,
+        kTable_Type,
+        kParam_Type,
+    };
+
+    // Contains information for a gamma table.
+    struct Table {
+        size_t fOffset;
+        int    fSize;
+
+        const float* table(const SkGammas* base) const {
+            return SkTAddOffset<const float>(base, sizeof(SkGammas) + fOffset);
+        }
+    };
+
+    // Contains the actual gamma curve information.  Should be interpreted
+    // based on the type of the gamma curve.
+    union Data {
+        Data() : fTable{0, 0} {}
+
+        SkGammaNamed fNamed;
+        float        fValue;
+        Table        fTable;
+        size_t       fParamOffset;
+
+        const SkColorSpaceTransferFn& params(const SkGammas* base) const {
+            return *SkTAddOffset<const SkColorSpaceTransferFn>(base,
+                                                               sizeof(SkGammas) + fParamOffset);
+        }
+    };
+
+    bool allChannelsSame() const {
+        // All channels are the same type?
+        Type type = this->type(0);
+        for (int i = 1; i < this->channels(); i++) {
+            if (type != this->type(i)) {
+                return false;
+            }
+        }
+
+        // All data the same?
+        auto& first = this->data(0);
+        for (int i = 1; i < this->channels(); i++) {
+            auto& data = this->data(i);
+            switch (type) {
+                case Type:: kNone_Type:                                                    break;
+                case Type::kNamed_Type: if (first.fNamed != data.fNamed) { return false; } break;
+                case Type::kValue_Type: if (first.fValue != data.fValue) { return false; } break;
+                case Type::kTable_Type:
+                    if (first.fTable.fOffset != data.fTable.fOffset) { return false; }
+                    if (first.fTable.fSize   != data.fTable.fSize  ) { return false; }
+                    break;
+                case Type::kParam_Type:
+                    if (0 != memcmp(&first.params(this), &data.params(this),
+                                    sizeof(SkColorSpaceTransferFn))) {
+                        return false;
+                    }
+                    break;
+            }
+        }
+        return true;
+    }
+
+    bool isNamed     (int i) const { return Type::kNamed_Type == this->type(i); }
+    bool isValue     (int i) const { return Type::kValue_Type == this->type(i); }
+    bool isTable     (int i) const { return Type::kTable_Type == this->type(i); }
+    bool isParametric(int i) const { return Type::kParam_Type == this->type(i); }
+
+    const Data& data(int i) const {
+        SkASSERT(i >= 0 && i < fChannels);
+        return fData[i];
+    }
+
+    const float* table(int i) const {
+        SkASSERT(this->isTable(i));
+        return this->data(i).fTable.table(this);
+    }
+
+    int tableSize(int i) const {
+        SkASSERT(this->isTable(i));
+        return this->data(i).fTable.fSize;
+    }
+
+    const SkColorSpaceTransferFn& params(int i) const {
+        SkASSERT(this->isParametric(i));
+        return this->data(i).params(this);
+    }
+
+    Type type(int i) const {
+        SkASSERT(i >= 0 && i < fChannels);
+        return fType[i];
+    }
+
+    int channels() const { return fChannels; }
+
+    SkGammas(int channels) : fChannels(channels) {
+        SkASSERT(channels <= (int)SK_ARRAY_COUNT(fType));
+        for (Type& t : fType) {
+            t = Type::kNone_Type;
+        }
+    }
+
+    // These fields should only be modified when initializing the struct.
+    int  fChannels;
+    Data fData[4];
+    Type fType[4];
+
+    // Objects of this type are sometimes created in a custom fashion using
+    // sk_malloc_throw and therefore must be sk_freed.  We overload new to
+    // also call sk_malloc_throw so that memory can be unconditionally released
+    // using sk_free in an overloaded delete. Overloading regular new means we
+    // must also overload placement new.
+    void* operator new(size_t size) { return sk_malloc_throw(size); }
+    void* operator new(size_t, void* p) { return p; }
+    void operator delete(void* p) { sk_free(p); }
+};
+
+#endif