Refactored SkColorSpace and added in a Lab PCS GM
authorraftias <raftias@google.com>
Tue, 18 Oct 2016 17:02:51 +0000 (10:02 -0700)
committerCommit bot <commit-bot@chromium.org>
Tue, 18 Oct 2016 17:02:52 +0000 (10:02 -0700)
The refactoring breaks off A2B0 tag support into a separate
subclass of SkColorSpace_Base, while keeping the current
(besides CLUT) functionality in a XYZTRC subclass.

ICC profile loading is now aware of this and creates the A2B0
subclass when SkColorSpace::NewICC() is called on a profile
in need of the A2B0 functionality.

The LabPCSDemo GM loads a .icc profile containing a LAB PCS and
then runs a Lab->XYZ conversion on an image using it so we can
display it and test out the A2B0 SkColorSpace functionality,
sans a/b/m-curves, as well as the Lab->XYZ conversion code.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2389983002

Review-Url: https://codereview.chromium.org/2389983002

26 files changed:
bench/ColorCodecBench.cpp
dm/DMSrcSink.cpp
gm/gamut.cpp
gm/labpcsdemo.cpp [new file with mode: 0644]
gyp/core.gypi
resources/icc_profiles/srgb_lab_pcs.icc [new file with mode: 0644]
samplecode/SampleApp.cpp
src/codec/SkJpegCodec.cpp
src/core/SkColorSpace.cpp
src/core/SkColorSpaceXform.cpp
src/core/SkColorSpaceXform_Base.h
src/core/SkColorSpace_A2B.cpp [new file with mode: 0644]
src/core/SkColorSpace_A2B.h [new file with mode: 0644]
src/core/SkColorSpace_Base.h
src/core/SkColorSpace_ICC.cpp
src/core/SkColorSpace_XYZ.cpp [new file with mode: 0644]
src/core/SkColorSpace_XYZ.h [new file with mode: 0644]
src/effects/gradients/SkGradientShader.cpp
src/gpu/GrColorSpaceXform.cpp
tests/CodecTest.cpp
tests/ColorSpaceTest.cpp
tests/ColorSpaceXformTest.cpp
tests/SurfaceTest.cpp
tests/TestConfigParsing.cpp
tools/flags/SkCommonFlagsConfig.cpp
tools/visualize_color_gamut.cpp

index 6aa46d609f5877bb5e624ff66c4b45bf395a26c3..fe3cdd24b5791c3a44075a0808f3ff6619fdb612 100644 (file)
@@ -9,7 +9,7 @@
 #include "Resources.h"
 #include "SkCodec.h"
 #include "SkCodecPriv.h"
-#include "SkColorSpace_Base.h"
+#include "SkColorSpace_XYZ.h"
 #include "SkColorSpaceXform.h"
 #include "SkCommandLineFlags.h"
 
@@ -170,7 +170,8 @@ void ColorCodecBench::onDelayedSetup() {
 
     if (FLAGS_half) {
         fDstInfo = fDstInfo.makeColorType(kRGBA_F16_SkColorType);
-        fDstSpace = as_CSB(fDstSpace)->makeLinearGamma();
+        SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(fDstSpace)->type());
+        fDstSpace = static_cast<SkColorSpace_XYZ*>(fDstSpace.get())->makeLinearGamma();
     }
 
     if (FLAGS_nonstd) {
index e777815baabce71bcf5ac20ad74e2721dd52eda2..8b62864c8f4a4018807309c5df439322929b3d60 100644 (file)
@@ -11,7 +11,7 @@
 #include "SkCodec.h"
 #include "SkCodecImageGenerator.h"
 #include "SkColorSpace.h"
-#include "SkColorSpace_Base.h"
+#include "SkColorSpace_XYZ.h"
 #include "SkColorSpaceXform.h"
 #include "SkCommonFlags.h"
 #include "SkData.h"
@@ -920,7 +920,9 @@ Error ColorCodecSrc::draw(SkCanvas* canvas) const {
         decodeInfo = decodeInfo.makeAlphaType(kPremul_SkAlphaType);
     }
     if (kRGBA_F16_SkColorType == fColorType) {
-        decodeInfo = decodeInfo.makeColorSpace(as_CSB(decodeInfo.colorSpace())->makeLinearGamma());
+        SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(decodeInfo.colorSpace())->type());
+        SkColorSpace_XYZ* csXYZ = static_cast<SkColorSpace_XYZ*>(decodeInfo.colorSpace());
+        decodeInfo = decodeInfo.makeColorSpace(csXYZ->makeLinearGamma());
     }
 
     SkImageInfo bitmapInfo = decodeInfo;
@@ -939,7 +941,15 @@ Error ColorCodecSrc::draw(SkCanvas* canvas) const {
     size_t rowBytes = bitmap.rowBytes();
     SkCodec::Result r = codec->getPixels(decodeInfo, bitmap.getPixels(), rowBytes);
     if (SkCodec::kSuccess != r && SkCodec::kIncompleteInput != r) {
-        return SkStringPrintf("Couldn't getPixels %s. Error code %d", fPath.c_str(), r);
+        if (kRGBA_F16_SkColorType == decodeInfo.colorType()) {
+            // FIXME (raftias):
+            // Get the codecs to not fail when there is no color xform,
+            // which currently happens in F16 mode.
+            return Error::Nonfatal(SkStringPrintf("Couldn't getPixels %s in F16. Error code %d",
+                                                  fPath.c_str()));
+        } else {
+            return SkStringPrintf("Couldn't getPixels %s. Error code %d", fPath.c_str(), r);
+        }
     }
 
     switch (fMode) {
index 707da29f64bdfa009ff80020b4910ae3b129b7ef..3c64915561df393e379bf4b9f76d86d64f2ef4b0 100644 (file)
@@ -127,20 +127,25 @@ static void draw_gamut_grid(SkCanvas* canvas, SkTArray<SkAutoTDelete<CellRendere
 
     // Use the original canvas' color type, but account for gamma requirements
     SkImageInfo origInfo = canvas->imageInfo();
-    auto srgbCS = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
-    auto wideCS = SkColorSpace::NewRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                       wideGamutRGB_toXYZD50);
+    sk_sp<SkColorSpace> srgbCS;
+    sk_sp<SkColorSpace> wideCS;
     switch (origInfo.colorType()) {
         case kRGBA_8888_SkColorType:
         case kBGRA_8888_SkColorType:
+            srgbCS = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+            wideCS = SkColorSpace::NewRGB(SkColorSpace::kSRGB_RenderTargetGamma,
+                                          wideGamutRGB_toXYZD50);
             break;
         case kRGBA_F16_SkColorType:
-            srgbCS = as_CSB(srgbCS.get())->makeLinearGamma();
-            wideCS = as_CSB(wideCS.get())->makeLinearGamma();
+            srgbCS = SkColorSpace::NewNamed(SkColorSpace::kSRGBLinear_Named);
+            wideCS = SkColorSpace::NewRGB(SkColorSpace::kLinear_RenderTargetGamma,
+                                          wideGamutRGB_toXYZD50);
             break;
         default:
             return;
     }
+    SkASSERT(srgbCS);
+    SkASSERT(wideCS);
 
     // Make our two working surfaces (one sRGB, one Adobe)
     SkImageInfo srgbGamutInfo = SkImageInfo::Make(gRectSize, gRectSize, origInfo.colorType(),
diff --git a/gm/labpcsdemo.cpp b/gm/labpcsdemo.cpp
new file mode 100644 (file)
index 0000000..4bd9ed8
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+ * 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 <cmath>
+#include "gm.h"
+#include "Resources.h"
+#include "SkCodec.h"
+#include "SkColorSpace_Base.h"
+#include "SkColorSpace_A2B.h"
+#include "SkColorSpacePriv.h"
+#include "SkData.h"
+#include "SkFloatingPoint.h"
+#include "SkImageInfo.h"
+#include "SkScalar.h"
+#include "SkSRGB.h"
+#include "SkStream.h"
+#include "SkSurface.h"
+#include "SkTypes.h"
+
+static inline void interp_3d_clut(float dst[3], float src[3], const SkColorLookUpTable* colorLUT) {
+    // Call the src components x, y, and z.
+    uint8_t maxX = colorLUT->fGridPoints[0] - 1;
+    uint8_t maxY = colorLUT->fGridPoints[1] - 1;
+    uint8_t maxZ = colorLUT->fGridPoints[2] - 1;
+
+    // An approximate index into each of the three dimensions of the table.
+    float x = src[0] * maxX;
+    float y = src[1] * maxY;
+    float z = src[2] * maxZ;
+
+    // This gives us the low index for our interpolation.
+    int ix = sk_float_floor2int(x);
+    int iy = sk_float_floor2int(y);
+    int iz = sk_float_floor2int(z);
+
+    // Make sure the low index is not also the max index.
+    ix = (maxX == ix) ? ix - 1 : ix;
+    iy = (maxY == iy) ? iy - 1 : iy;
+    iz = (maxZ == iz) ? iz - 1 : iz;
+
+    // Weighting factors for the interpolation.
+    float diffX = x - ix;
+    float diffY = y - iy;
+    float diffZ = z - iz;
+
+    // Constants to help us navigate the 3D table.
+    // Ex: Assume x = a, y = b, z = c.
+    //     table[a * n001 + b * n010 + c * n100] logically equals table[a][b][c].
+    const int n000 = 0;
+    const int n001 = 3 * colorLUT->fGridPoints[1] * colorLUT->fGridPoints[2];
+    const int n010 = 3 * colorLUT->fGridPoints[2];
+    const int n011 = n001 + n010;
+    const int n100 = 3;
+    const int n101 = n100 + n001;
+    const int n110 = n100 + n010;
+    const int n111 = n110 + n001;
+
+    // Base ptr into the table.
+    const float* ptr = &(colorLUT->table()[ix*n001 + iy*n010 + iz*n100]);
+
+    // The code below performs a tetrahedral interpolation for each of the three
+    // dst components.  Once the tetrahedron containing the interpolation point is
+    // identified, the interpolation is a weighted sum of grid values at the
+    // vertices of the tetrahedron.  The claim is that tetrahedral interpolation
+    // provides a more accurate color conversion.
+    // blogs.mathworks.com/steve/2006/11/24/tetrahedral-interpolation-for-colorspace-conversion/
+    //
+    // I have one test image, and visually I can't tell the difference between
+    // tetrahedral and trilinear interpolation.  In terms of computation, the
+    // tetrahedral code requires more branches but less computation.  The
+    // SampleICC library provides an option for the client to choose either
+    // tetrahedral or trilinear.
+    for (int i = 0; i < 3; i++) {
+        if (diffZ < diffY) {
+            if (diffZ < diffX) {
+                dst[i] = (ptr[n000] + diffZ * (ptr[n110] - ptr[n010]) +
+                                      diffY * (ptr[n010] - ptr[n000]) +
+                                      diffX * (ptr[n111] - ptr[n110]));
+            } else if (diffY < diffX) {
+                dst[i] = (ptr[n000] + diffZ * (ptr[n111] - ptr[n011]) +
+                                      diffY * (ptr[n011] - ptr[n001]) +
+                                      diffX * (ptr[n001] - ptr[n000]));
+            } else {
+                dst[i] = (ptr[n000] + diffZ * (ptr[n111] - ptr[n011]) +
+                                      diffY * (ptr[n010] - ptr[n000]) +
+                                      diffX * (ptr[n011] - ptr[n010]));
+            }
+        } else {
+            if (diffZ < diffX) {
+                dst[i] = (ptr[n000] + diffZ * (ptr[n101] - ptr[n001]) +
+                                      diffY * (ptr[n111] - ptr[n101]) +
+                                      diffX * (ptr[n001] - ptr[n000]));
+            } else if (diffY < diffX) {
+                dst[i] = (ptr[n000] + diffZ * (ptr[n100] - ptr[n000]) +
+                                      diffY * (ptr[n111] - ptr[n101]) +
+                                      diffX * (ptr[n101] - ptr[n100]));
+            } else {
+                dst[i] = (ptr[n000] + diffZ * (ptr[n100] - ptr[n000]) +
+                                      diffY * (ptr[n110] - ptr[n100]) +
+                                      diffX * (ptr[n111] - ptr[n110]));
+            }
+        }
+
+        // Increment the table ptr in order to handle the next component.
+        // Note that this is the how table is designed: all of nXXX
+        // variables are multiples of 3 because there are 3 output
+        // components.
+        ptr++;
+    }
+}
+
+
+/**
+ *  This tests decoding from a Lab source image and displays on the left
+ *  the image as raw RGB values, and on the right a Lab PCS.
+ *  It currently does NOT apply a/b/m-curves, as in the .icc profile
+ *  We are testing it on these are all identity transforms.
+ */
+class LabPCSDemoGM : public skiagm::GM {
+public:
+    LabPCSDemoGM()
+        : fWidth(1080)
+        , fHeight(480)
+        {}
+
+protected:
+
+
+    SkString onShortName() override {
+        return SkString("labpcsdemo");
+    }
+
+    SkISize onISize() override {
+        return SkISize::Make(fWidth, fHeight);
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        canvas->drawColor(SK_ColorGREEN);
+        const char* filename = "brickwork-texture.jpg";
+        renderImage(canvas, filename, 0, false);
+        renderImage(canvas, filename, 1, true);
+    }
+
+    void renderImage(SkCanvas* canvas, const char* filename, int col, bool convertLabToXYZ) {
+        SkBitmap bitmap;
+        SkStream* stream(GetResourceAsStream(filename));
+        if (stream == nullptr) {
+            return;
+        }
+        std::unique_ptr<SkCodec> codec(SkCodec::NewFromStream(stream));
+        
+
+        // srgb_lab_pcs.icc is an elaborate way to specify sRGB but uses
+        // Lab as the PCS, so we can take any arbitrary image that should
+        // be sRGB and this should show a reasonable image
+        const SkString iccFilename(GetResourcePath("icc_profiles/srgb_lab_pcs.icc"));
+        sk_sp<SkData> iccData = SkData::MakeFromFileName(iccFilename.c_str());
+        if (iccData == nullptr) {
+            return;
+        }
+        sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewICC(iccData->bytes(), iccData->size());
+
+        const int imageWidth = codec->getInfo().width();
+        const int imageHeight = codec->getInfo().height();
+        // Using nullptr as the color space instructs the codec to decode in legacy mode,
+        // meaning that we will get the raw encoded bytes without any color correction.
+        SkImageInfo imageInfo = SkImageInfo::Make(imageWidth, imageHeight, kN32_SkColorType,  
+                                                  kOpaque_SkAlphaType, nullptr);
+        bitmap.allocPixels(imageInfo);
+        codec->getPixels(imageInfo, bitmap.getPixels(), bitmap.rowBytes());
+        if (convertLabToXYZ) {
+            SkASSERT(SkColorSpace_Base::Type::kA2B == as_CSB(colorSpace)->type());
+            SkColorSpace_A2B& cs = *static_cast<SkColorSpace_A2B*>(colorSpace.get());
+            bool printConversions = false;
+            SkASSERT(cs.colorLUT());
+            // We're skipping evaluating the TRCs and the matrix here since they aren't
+            // in the ICC profile initially used here.
+            SkASSERT(kLinear_SkGammaNamed == cs.aCurveNamed());
+            SkASSERT(kLinear_SkGammaNamed == cs.mCurveNamed());
+            SkASSERT(kLinear_SkGammaNamed == cs.bCurveNamed());
+            SkASSERT(cs.matrix().isIdentity());
+            for (int y = 0; y < imageHeight; ++y) {
+                for (int x = 0; x < imageWidth; ++x) {
+                    uint32_t& p = *bitmap.getAddr32(x, y);
+                    const int r = SkColorGetR(p);
+                    const int g = SkColorGetG(p);
+                    const int b = SkColorGetB(p);
+                    if (printConversions) {
+                        SkColorSpacePrintf("\nraw = (%d, %d, %d)\t", r, g, b);
+                    }
+
+                    float lab[4] = { r * (1.f/255.f), g * (1.f/255.f), b * (1.f/255.f), 1.f };
+                    
+                    interp_3d_clut(lab, lab, cs.colorLUT());
+                    
+                    // Lab has ranges [0,100] for L and [-128,127] for a and b
+                    // but the ICC profile loader stores as [0,1]. The ICC
+                    // specifies an offset of -128 to convert.
+                    // note: formula could be adjusted to remove this conversion,
+                    //       but for now let's keep it like this for clarity until
+                    //       an optimized version is added.
+                    lab[0] *= 100.f;
+                    lab[1] = 255.f * lab[1] - 128.f;
+                    lab[2] = 255.f * lab[2] - 128.f;
+                    if (printConversions) {
+                        SkColorSpacePrintf("Lab = < %f, %f, %f >\n", lab[0], lab[1], lab[2]);
+                    }
+
+                    // convert from Lab to XYZ
+                    float Y = (lab[0] + 16.f) * (1.f/116.f);
+                    float X = lab[1] * (1.f/500.f) + Y;
+                    float Z = Y - (lab[2] * (1.f/200.f));
+                    float cubed;
+                    cubed = X*X*X;
+                    if (cubed > 0.008856f)
+                        X = cubed;
+                    else
+                        X = (X - (16.f/116.f)) * (1.f/7.787f);
+                    cubed = Y*Y*Y;
+                    if (cubed > 0.008856f)
+                        Y = cubed;
+                    else
+                        Y = (Y - (16.f/116.f)) * (1.f/7.787f);
+                    cubed = Z*Z*Z;
+                    if (cubed > 0.008856f)
+                        Z = cubed;
+                    else
+                        Z = (Z - (16.f/116.f)) * (1.f/7.787f);
+
+                    // adjust to D50 illuminant
+                    X *= 0.96422f;
+                    Y *= 1.00000f;
+                    Z *= 0.82521f;
+
+                    if (printConversions) {
+                        SkColorSpacePrintf("XYZ = (%4f, %4f, %4f)\t", X, Y, Z);
+                    }
+
+                    // convert XYZ -> linear sRGB
+                    Sk4f lRGB( 3.1338561f*X - 1.6168667f*Y - 0.4906146f*Z,
+                              -0.9787684f*X + 1.9161415f*Y + 0.0334540f*Z,
+                               0.0719453f*X - 0.2289914f*Y + 1.4052427f*Z,
+                              1.f);
+                    // and apply sRGB gamma
+                    Sk4i sRGB = sk_linear_to_srgb(lRGB);
+                    if (printConversions) {
+                        SkColorSpacePrintf("sRGB = (%d, %d, %d)\n", sRGB[0], sRGB[1], sRGB[2]);
+                    }
+                    p = SkColorSetRGB(sRGB[0], sRGB[1], sRGB[2]);
+                }
+            }
+        }
+        const int freeWidth = fWidth - 2*imageWidth;
+        const int freeHeight = fHeight - imageHeight;
+        canvas->drawBitmap(bitmap,
+                           static_cast<SkScalar>((col+1) * (freeWidth / 3) + col*imageWidth),
+                           static_cast<SkScalar>(freeHeight / 2));
+        ++col;
+    }
+
+private:
+    const int fWidth;
+    const int fHeight;
+
+    typedef skiagm::GM INHERITED;
+};
+
+DEF_GM( return new LabPCSDemoGM; )
index a43516488338bc222592a65c2164268abffd901e..0488436b97edbd1e07cb2acfd350ae46c853922a 100644 (file)
         '<(skia_src_path)/core/SkColorShader.cpp',
         '<(skia_src_path)/core/SkColorShader.h',
         '<(skia_src_path)/core/SkColorSpace.cpp',
+        '<(skia_src_path)/core/SkColorSpace_A2B.cpp',
+        '<(skia_src_path)/core/SkColorSpace_A2B.h',
+        '<(skia_src_path)/core/SkColorSpace_XYZ.cpp',
+        '<(skia_src_path)/core/SkColorSpace_XYZ.h',
         '<(skia_src_path)/core/SkColorSpace_ICC.cpp',
         '<(skia_src_path)/core/SkColorSpaceXform.cpp',
         '<(skia_src_path)/core/SkColorTable.cpp',
diff --git a/resources/icc_profiles/srgb_lab_pcs.icc b/resources/icc_profiles/srgb_lab_pcs.icc
new file mode 100644 (file)
index 0000000..cfbd03e
Binary files /dev/null and b/resources/icc_profiles/srgb_lab_pcs.icc differ
index 4b4621698fdbd08cf62d9e4c8340827679db580e..2194895e0d2c3dc77e65e8b83f69aaecc5e25f51 100644 (file)
@@ -12,7 +12,7 @@
 #include "SampleCode.h"
 #include "SkAnimTimer.h"
 #include "SkCanvas.h"
-#include "SkColorSpace_Base.h"
+#include "SkColorSpace_XYZ.h"
 #include "SkCommandLineFlags.h"
 #include "SkData.h"
 #include "SkDocument.h"
@@ -1676,7 +1676,9 @@ bool SampleWindow::onEvent(const SkEvent& evt) {
         }
         if (kRGBA_F16_SkColorType == gConfig[selected].fColorType) {
             SkASSERT(colorSpace);
-            colorSpace = as_CSB(colorSpace)->makeLinearGamma();
+            SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(colorSpace)->type());
+            SkColorSpace_XYZ* csXYZ = static_cast<SkColorSpace_XYZ*>(colorSpace.get());
+            colorSpace = csXYZ->makeLinearGamma();
         }
         this->setDeviceColorType(gConfig[selected].fColorType, colorSpace);
         return true;
index 92ab2bc1823bbc025bed055e24fbf1cf2618d83b..6863855d3b966ba5868d6d927eb17cb0b9897af5 100644 (file)
@@ -412,7 +412,9 @@ bool SkJpegCodec::setOutputColorSpace(const SkImageInfo& dstInfo) {
             fDecoderMgr->dinfo()->out_color_space = JCS_GRAYSCALE;
             return true;
         case kRGBA_F16_SkColorType:
-            SkASSERT(fColorXform);
+            if (!fColorXform) {
+                return false;
+            }
             if (!dstInfo.colorSpace()->gammaIsLinear()) {
                 return false;
             }
@@ -635,7 +637,6 @@ void SkJpegCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options&
 void SkJpegCodec::initializeColorXform(const SkImageInfo& dstInfo) {
     if (needs_color_xform(dstInfo, this->getInfo())) {
         fColorXform = SkColorSpaceXform::New(this->getInfo().colorSpace(), dstInfo.colorSpace());
-        SkASSERT(fColorXform);
     }
 }
 
index b956c7fe3e342255641a64f9530538c0a68e9c0c..e90254c969eee6746b5b5d8d95b89f68047edb0d 100644 (file)
@@ -7,8 +7,8 @@
 
 #include "SkColorSpace.h"
 #include "SkColorSpace_Base.h"
+#include "SkColorSpace_XYZ.h"
 #include "SkColorSpacePriv.h"
-#include "SkColorSpaceXform_Base.h"
 #include "SkOnce.h"
 #include "SkPoint3.h"
 
@@ -83,23 +83,8 @@ bool SkColorSpacePrimaries::toXYZD50(SkMatrix44* toXYZ_D50) const {
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-SkColorSpace_Base::SkColorSpace_Base(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50)
-    : fGammaNamed(gammaNamed)
-    , fGammas(nullptr)
-    , fProfileData(nullptr)
-    , fToXYZD50(toXYZD50)
-    , fFromXYZD50(SkMatrix44::kUninitialized_Constructor)
-{}
-
-SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkColorLookUpTable> colorLUT, SkGammaNamed gammaNamed,
-                                     sk_sp<SkGammas> gammas, const SkMatrix44& toXYZD50,
-                                     sk_sp<SkData> profileData)
-    : fColorLUT(std::move(colorLUT))
-    , fGammaNamed(gammaNamed)
-    , fGammas(std::move(gammas))
-    , fProfileData(std::move(profileData))
-    , fToXYZD50(toXYZD50)
-    , fFromXYZD50(SkMatrix44::kUninitialized_Constructor)
+SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkData> profileData)
+    : fProfileData(std::move(profileData))
 {}
 
 static constexpr float gSRGB_toXYZD50[] {
@@ -163,8 +148,8 @@ sk_sp<SkColorSpace> SkColorSpace::NewRGB(const float values[3], const SkMatrix44
         gammas->fRedData.fValue = values[0];
         gammas->fGreenData.fValue = values[1];
         gammas->fBlueData.fValue = values[2];
-        return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, kNonStandard_SkGammaNamed, gammas,
-                                                         toXYZD50, nullptr));
+        return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed,
+                                                        gammas, toXYZD50, nullptr));
     }
 
     return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50);
@@ -194,7 +179,7 @@ sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(SkGammaNamed gammaNamed, const SkM
             break;
     }
 
-    return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammaNamed, toXYZD50));
+    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed, toXYZD50));
 }
 
 sk_sp<SkColorSpace> SkColorSpace::NewRGB(RenderTargetGamma gamma, const SkMatrix44& toXYZD50) {
@@ -235,8 +220,8 @@ sk_sp<SkColorSpace> SkColorSpace::NewRGB(const SkColorSpaceTransferFn& coeffs,
     gammas->fRedData = data;
     gammas->fGreenData = data;
     gammas->fBlueData = data;
-    return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, kNonStandard_SkGammaNamed,
-                                                     std::move(gammas), toXYZD50, nullptr));
+    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed,
+                                                    std::move(gammas), toXYZD50, nullptr));
 }
 
 static SkColorSpace* gAdobeRGB;
@@ -256,7 +241,7 @@ sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
 
                 // Force the mutable type mask to be computed.  This avoids races.
                 (void)srgbToxyzD50.getType();
-                gSRGB = new SkColorSpace_Base(kSRGB_SkGammaNamed, srgbToxyzD50);
+                gSRGB = new SkColorSpace_XYZ(kSRGB_SkGammaNamed, srgbToxyzD50);
             });
             return sk_ref_sp<SkColorSpace>(gSRGB);
         }
@@ -267,7 +252,7 @@ sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
 
                 // Force the mutable type mask to be computed.  This avoids races.
                 (void)adobergbToxyzD50.getType();
-                gAdobeRGB = new SkColorSpace_Base(k2Dot2Curve_SkGammaNamed, adobergbToxyzD50);
+                gAdobeRGB = new SkColorSpace_XYZ(k2Dot2Curve_SkGammaNamed, adobergbToxyzD50);
             });
             return sk_ref_sp<SkColorSpace>(gAdobeRGB);
         }
@@ -278,7 +263,7 @@ sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
 
                 // Force the mutable type mask to be computed.  This avoids races.
                 (void)srgbToxyzD50.getType();
-                gSRGBLinear = new SkColorSpace_Base(kLinear_SkGammaNamed, srgbToxyzD50);
+                gSRGBLinear = new SkColorSpace_XYZ(kLinear_SkGammaNamed, srgbToxyzD50);
             });
             return sk_ref_sp<SkColorSpace>(gSRGBLinear);
         }
@@ -288,53 +273,14 @@ sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) {
     return nullptr;
 }
 
-sk_sp<SkColorSpace> SkColorSpace_Base::makeLinearGamma() {
-    if (this->gammaIsLinear()) {
-        return sk_ref_sp(this);
-    }
-    return SkColorSpace_Base::NewRGB(kLinear_SkGammaNamed, as_CSB(this)->fToXYZD50);
-}
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 bool SkColorSpace::gammaCloseToSRGB() const {
-    return kSRGB_SkGammaNamed == as_CSB(this)->fGammaNamed ||
-           k2Dot2Curve_SkGammaNamed == as_CSB(this)->fGammaNamed;
+    return as_CSB(this)->onGammaCloseToSRGB();
 }
 
 bool SkColorSpace::gammaIsLinear() const {
-    return kLinear_SkGammaNamed == as_CSB(this)->fGammaNamed;
-}
-
-const SkMatrix44& SkColorSpace_Base::fromXYZD50() const {
-    fFromXYZOnce([this] {
-        if (!fToXYZD50.invert(&fFromXYZD50)) {
-            // If a client gives us a dst gamut with a transform that we can't invert, we will
-            // simply give them back a transform to sRGB gamut.
-            SkDEBUGFAIL("Non-invertible XYZ matrix, defaulting to sRGB");
-            SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
-            srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50);
-            srgbToxyzD50.invert(&fFromXYZD50);
-        }
-    });
-    return fFromXYZD50;
-}
-
-void SkColorSpace_Base::toDstGammaTables(const uint8_t* tables[3], sk_sp<SkData>* storage,
-                                         int numTables) const {
-    fToDstGammaOnce([this, numTables] {
-        const bool gammasAreMatching = numTables <= 1;
-        fDstStorage =
-                SkData::MakeUninitialized(numTables * SkColorSpaceXform_Base::kDstGammaTableSize);
-        SkColorSpaceXform_Base::BuildDstGammaTables(fToDstGammaTables,
-                                                    (uint8_t*) fDstStorage->writable_data(), this,
-                                                    gammasAreMatching);
-    });
-
-    *storage = fDstStorage;
-    tables[0] = fToDstGammaTables[0];
-    tables[1] = fToDstGammaTables[1];
-    tables[2] = fToDstGammaTables[2];
+    return as_CSB(this)->onGammaIsLinear();
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -403,51 +349,52 @@ size_t SkColorSpace::writeToMemory(void* memory) const {
     // 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 (!as_CSB(this)->fProfileData) {
+        // Profile data is mandatory for A2B0 color spaces.
+        SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(this)->type());
+        const SkColorSpace_XYZ* thisXYZ = static_cast<const SkColorSpace_XYZ*>(this);
         // If we have a named profile, only write the enum.
+        const SkGammaNamed gammaNamed = thisXYZ->gammaNamed();
         if (this == gSRGB) {
             if (memory) {
                 *((ColorSpaceHeader*) memory) =
-                        ColorSpaceHeader::Pack(k0_Version, kSRGB_Named,
-                                               as_CSB(this)->fGammaNamed, 0);
+                        ColorSpaceHeader::Pack(k0_Version, kSRGB_Named, gammaNamed, 0);
             }
             return sizeof(ColorSpaceHeader);
         } else if (this == gAdobeRGB) {
             if (memory) {
                 *((ColorSpaceHeader*) memory) =
-                        ColorSpaceHeader::Pack(k0_Version, kAdobeRGB_Named,
-                                               as_CSB(this)->fGammaNamed, 0);
+                        ColorSpaceHeader::Pack(k0_Version, kAdobeRGB_Named, gammaNamed, 0);
             }
             return sizeof(ColorSpaceHeader);
         } else if (this == gSRGBLinear) {
             if (memory) {
-                *((ColorSpaceHeader*)memory) =
-                        ColorSpaceHeader::Pack(k0_Version, kSRGBLinear_Named,
-                                               as_CSB(this)->fGammaNamed, 0);
+                *((ColorSpaceHeader*) memory) =
+                        ColorSpaceHeader::Pack(k0_Version, kSRGBLinear_Named, gammaNamed, 0);
             }
             return sizeof(ColorSpaceHeader);
         }
 
         // If we have a named gamma, write the enum and the matrix.
-        switch (as_CSB(this)->fGammaNamed) {
+        switch (gammaNamed) {
             case kSRGB_SkGammaNamed:
             case k2Dot2Curve_SkGammaNamed:
             case kLinear_SkGammaNamed: {
                 if (memory) {
                     *((ColorSpaceHeader*) memory) =
-                            ColorSpaceHeader::Pack(k0_Version, 0, as_CSB(this)->fGammaNamed,
+                            ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed,
                                                    ColorSpaceHeader::kMatrix_Flag);
                     memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
-                    as_CSB(this)->fToXYZD50.as3x4RowMajorf((float*) memory);
+                    thisXYZ->toXYZD50()->as3x4RowMajorf((float*) memory);
                 }
                 return sizeof(ColorSpaceHeader) + 12 * sizeof(float);
             }
             default:
-                const SkGammas* gammas = as_CSB(this)->gammas();
+                const SkGammas* gammas = thisXYZ->gammas();
                 SkASSERT(gammas);
                 if (gammas->isValue(0) && gammas->isValue(1) && gammas->isValue(2)) {
                     if (memory) {
                         *((ColorSpaceHeader*) memory) =
-                                ColorSpaceHeader::Pack(k0_Version, 0, as_CSB(this)->fGammaNamed,
+                                ColorSpaceHeader::Pack(k0_Version, 0, thisXYZ->fGammaNamed,
                                                        ColorSpaceHeader::kFloatGamma_Flag);
                         memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
 
@@ -456,7 +403,7 @@ size_t SkColorSpace::writeToMemory(void* memory) const {
                         *(((float*) memory) + 2) = gammas->fBlueData.fValue;
                         memory = SkTAddOffset<void>(memory, 3 * sizeof(float));
 
-                        as_CSB(this)->fToXYZD50.as3x4RowMajorf((float*) memory);
+                        thisXYZ->fToXYZD50.as3x4RowMajorf((float*) memory);
                     }
 
                     return sizeof(ColorSpaceHeader) + 15 * sizeof(float);
@@ -469,7 +416,7 @@ size_t SkColorSpace::writeToMemory(void* memory) const {
 
                     if (memory) {
                         *((ColorSpaceHeader*) memory) =
-                                ColorSpaceHeader::Pack(k0_Version, 0, as_CSB(this)->fGammaNamed,
+                                ColorSpaceHeader::Pack(k0_Version, 0, thisXYZ->fGammaNamed,
                                                        ColorSpaceHeader::kTransferFn_Flag);
                         memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
 
@@ -482,7 +429,7 @@ size_t SkColorSpace::writeToMemory(void* memory) const {
                         *(((float*) memory) + 6) = gammas->params(0).fG;
                         memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
 
-                        as_CSB(this)->fToXYZD50.as3x4RowMajorf((float*) memory);
+                        thisXYZ->fToXYZD50.as3x4RowMajorf((float*) memory);
                     }
 
                     return sizeof(ColorSpaceHeader) + 19 * sizeof(float);
@@ -624,23 +571,26 @@ bool SkColorSpace::Equals(const SkColorSpace* src, const SkColorSpace* dst) {
         return false;
     }
 
-    // It's important to check fProfileData before named gammas.  Some profiles may have named
-    // gammas, but also include other wacky features that cause us to save the data.
-    switch (as_CSB(src)->fGammaNamed) {
+    // profiles are mandatory for A2B0 color spaces
+    SkASSERT(as_CSB(src)->type() == SkColorSpace_Base::Type::kXYZ);
+    const SkColorSpace_XYZ* srcXYZ = static_cast<const SkColorSpace_XYZ*>(src);
+    const SkColorSpace_XYZ* dstXYZ = static_cast<const SkColorSpace_XYZ*>(dst);
+
+    switch (srcXYZ->gammaNamed()) {
         case kSRGB_SkGammaNamed:
         case k2Dot2Curve_SkGammaNamed:
         case kLinear_SkGammaNamed:
-            return (as_CSB(src)->fGammaNamed == as_CSB(dst)->fGammaNamed) &&
-                   (as_CSB(src)->fToXYZD50 == as_CSB(dst)->fToXYZD50);
+            return (srcXYZ->gammaNamed() == dstXYZ->gammaNamed()) &&
+                   (*srcXYZ->toXYZD50() == *dstXYZ->toXYZD50());
         default:
-            if (as_CSB(src)->fGammaNamed != as_CSB(dst)->fGammaNamed) {
+            if (srcXYZ->gammaNamed() != dstXYZ->gammaNamed()) {
                 return false;
             }
-
             // It is unlikely that we will reach this case.
-            sk_sp<SkData> srcData = src->serialize();
-            sk_sp<SkData> dstData = dst->serialize();
-            return srcData->size() == dstData->size() &&
-                   0 == memcmp(srcData->data(), dstData->data(), srcData->size());
+            sk_sp<SkData> serializedSrcData = src->serialize();
+            sk_sp<SkData> serializedDstData = dst->serialize();
+            return serializedSrcData->size() == serializedDstData->size() &&
+                   0 == memcmp(serializedSrcData->data(), serializedDstData->data(),
+                               serializedSrcData->size());
     }
 }
index 5e362464b41d5d9470a9795a2eea9e5e23601bf5..cacb38878cd1cf86c0489b3775b5abea945df16c 100644 (file)
@@ -6,7 +6,9 @@
  */
 
 #include "SkColorPriv.h"
+#include "SkColorSpace_A2B.h"
 #include "SkColorSpace_Base.h"
+#include "SkColorSpace_XYZ.h"
 #include "SkColorSpacePriv.h"
 #include "SkColorSpaceXform_Base.h"
 #include "SkHalf.h"
@@ -254,10 +256,10 @@ static const GammaFns<uint8_t> kFromLinear {
 // Build tables to transform src gamma to linear.
 template <typename T>
 static void build_gamma_tables(const T* outGammaTables[3], T* gammaTableStorage, int gammaTableSize,
-                               const SkColorSpace* space, const GammaFns<T>& fns,
+                               const SkColorSpace_XYZ* space, const GammaFns<T>& fns,
                                bool gammasAreMatching)
 {
-    switch (as_CSB(space)->gammaNamed()) {
+    switch (space->gammaNamed()) {
         case kSRGB_SkGammaNamed:
             outGammaTables[0] = outGammaTables[1] = outGammaTables[2] = fns.fSRGBTable;
             break;
@@ -268,7 +270,7 @@ static void build_gamma_tables(const T* outGammaTables[3], T* gammaTableStorage,
             outGammaTables[0] = outGammaTables[1] = outGammaTables[2] = nullptr;
             break;
         default: {
-            const SkGammas* gammas = as_CSB(space)->gammas();
+            const SkGammas* gammas = space->gammas();
             SkASSERT(gammas);
 
             auto build_table = [=](int i) {
@@ -326,7 +328,8 @@ static void build_gamma_tables(const T* outGammaTables[3], T* gammaTableStorage,
 }
 
 void SkColorSpaceXform_Base::BuildDstGammaTables(const uint8_t* dstGammaTables[3],
-                                                 uint8_t* dstStorage, const SkColorSpace* space,
+                                                 uint8_t* dstStorage,
+                                                 const SkColorSpace_XYZ* space,
                                                  bool gammasAreMatching) {
     build_gamma_tables(dstGammaTables, dstStorage, kDstGammaTableSize, space, kFromLinear,
                        gammasAreMatching);
@@ -355,13 +358,30 @@ std::unique_ptr<SkColorSpaceXform> SkColorSpaceXform::New(SkColorSpace* srcSpace
         return nullptr;
     }
 
+    if (SkColorSpace_Base::Type::kA2B == as_CSB(dstSpace)->type()) {
+        SkColorSpacePrintf("A2B destinations not supported\n");
+        return nullptr;
+    }
+
+    if (SkColorSpace_Base::Type::kA2B == as_CSB(srcSpace)->type()) {
+        // TODO (raftias): return an A2B-supporting SkColorSpaceXform here once the xform.
+        // is implemented. SkColorSpaceXform_Base only supports XYZ+TRC based SkColorSpaces
+        //SkColorSpace_A2B* src = static_cast<SkColorSpace_A2B*>(srcSpace);
+        //SkColorSpace_XYZ* dst = static_cast<SkColorSpace_XYZ*>(dstSpace);
+        //return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_A2B(src, dst));
+        SkColorSpacePrintf("A2B sources not supported (yet)\n");
+        return nullptr;
+    }
+    SkColorSpace_XYZ* srcSpaceXYZ = static_cast<SkColorSpace_XYZ*>(srcSpace);
+    SkColorSpace_XYZ* dstSpaceXYZ = static_cast<SkColorSpace_XYZ*>(dstSpace);
+
     ColorSpaceMatch csm = kNone_ColorSpaceMatch;
     SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor);
     if (SkColorSpace::Equals(srcSpace, dstSpace)) {
         srcToDst.setIdentity();
         csm = kFull_ColorSpaceMatch;
     } else {
-        srcToDst.setConcat(as_CSB(dstSpace)->fromXYZD50(), as_CSB(srcSpace)->toXYZD50());
+        srcToDst.setConcat(*dstSpaceXYZ->fromXYZD50(), *srcSpaceXYZ->toXYZD50());
 
         if (is_almost_identity(srcToDst)) {
             srcToDst.setIdentity();
@@ -371,109 +391,109 @@ std::unique_ptr<SkColorSpaceXform> SkColorSpaceXform::New(SkColorSpace* srcSpace
 
     switch (csm) {
         case kNone_ColorSpaceMatch:
-            switch (as_CSB(dstSpace)->gammaNamed()) {
+            switch (dstSpaceXYZ->gammaNamed()) {
                 case kSRGB_SkGammaNamed:
-                    if (srcSpace->gammaIsLinear()) {
+                    if (srcSpaceXYZ->gammaIsLinear()) {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kLinear_SrcGamma, kSRGB_DstGamma, kNone_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     } else {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kTable_SrcGamma, kSRGB_DstGamma, kNone_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     }
                 case k2Dot2Curve_SkGammaNamed:
-                    if (srcSpace->gammaIsLinear()) {
+                    if (srcSpaceXYZ->gammaIsLinear()) {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kLinear_SrcGamma, k2Dot2_DstGamma, kNone_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     } else {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kTable_SrcGamma, k2Dot2_DstGamma, kNone_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     }
                 case kLinear_SkGammaNamed:
-                    if (srcSpace->gammaIsLinear()) {
+                    if (srcSpaceXYZ->gammaIsLinear()) {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kLinear_SrcGamma, kLinear_DstGamma, kNone_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     } else {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kTable_SrcGamma, kLinear_DstGamma, kNone_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     }
                 default:
-                    if (srcSpace->gammaIsLinear()) {
+                    if (srcSpaceXYZ->gammaIsLinear()) {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kLinear_SrcGamma, kTable_DstGamma, kNone_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     } else {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kTable_SrcGamma, kTable_DstGamma, kNone_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     }
             }
         case kGamut_ColorSpaceMatch:
-            switch (as_CSB(dstSpace)->gammaNamed()) {
+            switch (dstSpaceXYZ->gammaNamed()) {
                 case kSRGB_SkGammaNamed:
-                    if (srcSpace->gammaIsLinear()) {
+                    if (srcSpaceXYZ->gammaIsLinear()) {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kLinear_SrcGamma, kSRGB_DstGamma, kGamut_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     } else {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kTable_SrcGamma, kSRGB_DstGamma, kGamut_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     }
                 case k2Dot2Curve_SkGammaNamed:
-                    if (srcSpace->gammaIsLinear()) {
+                    if (srcSpaceXYZ->gammaIsLinear()) {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kLinear_SrcGamma, k2Dot2_DstGamma, kGamut_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     } else {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kTable_SrcGamma, k2Dot2_DstGamma, kGamut_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     }
                 case kLinear_SkGammaNamed:
-                    if (srcSpace->gammaIsLinear()) {
+                    if (srcSpaceXYZ->gammaIsLinear()) {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kLinear_SrcGamma, kLinear_DstGamma, kGamut_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     } else {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kTable_SrcGamma, kLinear_DstGamma, kGamut_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     }
                 default:
-                    if (srcSpace->gammaIsLinear()) {
+                    if (srcSpaceXYZ->gammaIsLinear()) {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kLinear_SrcGamma, kTable_DstGamma, kGamut_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     } else {
                         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                                 <kTable_SrcGamma, kTable_DstGamma, kGamut_ColorSpaceMatch>
-                                (srcSpace, srcToDst, dstSpace));
+                                (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                     }
             }
         case kFull_ColorSpaceMatch:
-            switch (as_CSB(dstSpace)->gammaNamed()) {
+            switch (dstSpaceXYZ->gammaNamed()) {
                 case kSRGB_SkGammaNamed:
                     return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                             <kTable_SrcGamma, kSRGB_DstGamma, kFull_ColorSpaceMatch>
-                            (srcSpace, srcToDst, dstSpace));
+                            (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                 case k2Dot2Curve_SkGammaNamed:
                     return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                             <kTable_SrcGamma, k2Dot2_DstGamma, kFull_ColorSpaceMatch>
-                            (srcSpace, srcToDst, dstSpace));
+                            (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                 case kLinear_SkGammaNamed:
                     return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                             <kLinear_SrcGamma, kLinear_DstGamma, kFull_ColorSpaceMatch>
-                            (srcSpace, srcToDst, dstSpace));
+                            (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
                 default:
                     return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                             <kTable_SrcGamma, kTable_DstGamma, kFull_ColorSpaceMatch>
-                            (srcSpace, srcToDst, dstSpace));
+                            (srcSpaceXYZ, srcToDst, dstSpaceXYZ));
             }
         default:
             SkASSERT(false);
@@ -483,138 +503,6 @@ std::unique_ptr<SkColorSpaceXform> SkColorSpaceXform::New(SkColorSpace* srcSpace
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-static float byte_to_float(uint8_t byte) {
-    return ((float) byte) * (1.0f / 255.0f);
-}
-
-// Clamp to the 0-1 range.
-static float clamp_normalized_float(float v) {
-    if (v > 1.0f) {
-        return 1.0f;
-    } else if ((v < 0.0f) || (v != v)) {
-        return 0.0f;
-    } else {
-        return v;
-    }
-}
-
-static void interp_3d_clut(float dst[3], float src[3], const SkColorLookUpTable* colorLUT) {
-    // Call the src components x, y, and z.
-    uint8_t maxX = colorLUT->fGridPoints[0] - 1;
-    uint8_t maxY = colorLUT->fGridPoints[1] - 1;
-    uint8_t maxZ = colorLUT->fGridPoints[2] - 1;
-
-    // An approximate index into each of the three dimensions of the table.
-    float x = src[0] * maxX;
-    float y = src[1] * maxY;
-    float z = src[2] * maxZ;
-
-    // This gives us the low index for our interpolation.
-    int ix = sk_float_floor2int(x);
-    int iy = sk_float_floor2int(y);
-    int iz = sk_float_floor2int(z);
-
-    // Make sure the low index is not also the max index.
-    ix = (maxX == ix) ? ix - 1 : ix;
-    iy = (maxY == iy) ? iy - 1 : iy;
-    iz = (maxZ == iz) ? iz - 1 : iz;
-
-    // Weighting factors for the interpolation.
-    float diffX = x - ix;
-    float diffY = y - iy;
-    float diffZ = z - iz;
-
-    // Constants to help us navigate the 3D table.
-    // Ex: Assume x = a, y = b, z = c.
-    //     table[a * n001 + b * n010 + c * n100] logically equals table[a][b][c].
-    const int n000 = 0;
-    const int n001 = 3 * colorLUT->fGridPoints[1] * colorLUT->fGridPoints[2];
-    const int n010 = 3 * colorLUT->fGridPoints[2];
-    const int n011 = n001 + n010;
-    const int n100 = 3;
-    const int n101 = n100 + n001;
-    const int n110 = n100 + n010;
-    const int n111 = n110 + n001;
-
-    // Base ptr into the table.
-    const float* ptr = &(colorLUT->table()[ix*n001 + iy*n010 + iz*n100]);
-
-    // The code below performs a tetrahedral interpolation for each of the three
-    // dst components.  Once the tetrahedron containing the interpolation point is
-    // identified, the interpolation is a weighted sum of grid values at the
-    // vertices of the tetrahedron.  The claim is that tetrahedral interpolation
-    // provides a more accurate color conversion.
-    // blogs.mathworks.com/steve/2006/11/24/tetrahedral-interpolation-for-colorspace-conversion/
-    //
-    // I have one test image, and visually I can't tell the difference between
-    // tetrahedral and trilinear interpolation.  In terms of computation, the
-    // tetrahedral code requires more branches but less computation.  The
-    // SampleICC library provides an option for the client to choose either
-    // tetrahedral or trilinear.
-    for (int i = 0; i < 3; i++) {
-        if (diffZ < diffY) {
-            if (diffZ < diffX) {
-                dst[i] = (ptr[n000] + diffZ * (ptr[n110] - ptr[n010]) +
-                                      diffY * (ptr[n010] - ptr[n000]) +
-                                      diffX * (ptr[n111] - ptr[n110]));
-            } else if (diffY < diffX) {
-                dst[i] = (ptr[n000] + diffZ * (ptr[n111] - ptr[n011]) +
-                                      diffY * (ptr[n011] - ptr[n001]) +
-                                      diffX * (ptr[n001] - ptr[n000]));
-            } else {
-                dst[i] = (ptr[n000] + diffZ * (ptr[n111] - ptr[n011]) +
-                                      diffY * (ptr[n010] - ptr[n000]) +
-                                      diffX * (ptr[n011] - ptr[n010]));
-            }
-        } else {
-            if (diffZ < diffX) {
-                dst[i] = (ptr[n000] + diffZ * (ptr[n101] - ptr[n001]) +
-                                      diffY * (ptr[n111] - ptr[n101]) +
-                                      diffX * (ptr[n001] - ptr[n000]));
-            } else if (diffY < diffX) {
-                dst[i] = (ptr[n000] + diffZ * (ptr[n100] - ptr[n000]) +
-                                      diffY * (ptr[n111] - ptr[n101]) +
-                                      diffX * (ptr[n101] - ptr[n100]));
-            } else {
-                dst[i] = (ptr[n000] + diffZ * (ptr[n100] - ptr[n000]) +
-                                      diffY * (ptr[n110] - ptr[n100]) +
-                                      diffX * (ptr[n111] - ptr[n110]));
-            }
-        }
-
-        // Increment the table ptr in order to handle the next component.
-        // Note that this is the how table is designed: all of nXXX
-        // variables are multiples of 3 because there are 3 output
-        // components.
-        ptr++;
-    }
-}
-
-static void handle_color_lut(uint32_t* dst, const void* vsrc, int len,
-                             SkColorLookUpTable* colorLUT) {
-    const uint32_t* src = (const uint32_t*) vsrc;
-    while (len-- > 0) {
-        uint8_t r = (*src >>  0) & 0xFF,
-                g = (*src >>  8) & 0xFF,
-                b = (*src >> 16) & 0xFF;
-
-        float in[3];
-        float out[3];
-        in[0] = byte_to_float(r);
-        in[1] = byte_to_float(g);
-        in[2] = byte_to_float(b);
-        interp_3d_clut(out, in, colorLUT);
-
-        r = sk_float_round2int(255.0f * clamp_normalized_float(out[0]));
-        g = sk_float_round2int(255.0f * clamp_normalized_float(out[1]));
-        b = sk_float_round2int(255.0f * clamp_normalized_float(out[2]));
-        *dst = SkPackARGB_as_RGBA(0xFF, r, g, b);
-
-        src++;
-        dst++;
-    }
-}
-
 static inline void load_matrix(const float matrix[16],
                                Sk4f& rXgXbX, Sk4f& rYgYbY, Sk4f& rZgZbZ, Sk4f& rTgTbT) {
     rXgXbX = Sk4f::Load(matrix +  0);
@@ -1226,14 +1114,14 @@ static void color_xform_RGBA(void* dst, const void* vsrc, int len,
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-static inline int num_tables(SkColorSpace* space) {
-    switch (as_CSB(space)->gammaNamed()) {
+static inline int num_tables(SkColorSpace_XYZ* space) {
+    switch (space->gammaNamed()) {
         case kSRGB_SkGammaNamed:
         case k2Dot2Curve_SkGammaNamed:
         case kLinear_SkGammaNamed:
             return 0;
         default: {
-            const SkGammas* gammas = as_CSB(space)->gammas();
+            const SkGammas* gammas = space->gammas();
             SkASSERT(gammas);
 
             bool gammasAreMatching = (gammas->type(0) == gammas->type(1)) &&
@@ -1250,8 +1138,8 @@ static inline int num_tables(SkColorSpace* space) {
 
 template <SrcGamma kSrc, DstGamma kDst, ColorSpaceMatch kCSM>
 SkColorSpaceXform_XYZ<kSrc, kDst, kCSM>
-::SkColorSpaceXform_XYZ(SkColorSpace* srcSpace, const SkMatrix44& srcToDst, SkColorSpace* dstSpace)
-    : fColorLUT(sk_ref_sp((SkColorLookUpTable*) as_CSB(srcSpace)->colorLUT()))
+::SkColorSpaceXform_XYZ(SkColorSpace_XYZ* srcSpace, const SkMatrix44& srcToDst,
+                        SkColorSpace_XYZ* dstSpace)
 {
     srcToDst.asColMajorf(fSrcToDst);
 
@@ -1263,7 +1151,7 @@ SkColorSpaceXform_XYZ<kSrc, kDst, kCSM>
                        srcGammasAreMatching);
 
     const int numDstTables = num_tables(dstSpace);
-    as_CSB(dstSpace)->toDstGammaTables(fDstGammaTables, &fDstStorage, numDstTables);
+    dstSpace->toDstGammaTables(fDstGammaTables, &fDstStorage, numDstTables);
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1348,19 +1236,6 @@ bool SkColorSpaceXform_XYZ<kSrc, kDst, kCSM>
         }
     }
 
-#if defined(GOOGLE3)
-    // Stack frame size is limited in GOOGLE3.
-    SkAutoSMalloc<256 * sizeof(uint32_t)> storage;
-#else
-    SkAutoSMalloc<1024 * sizeof(uint32_t)> storage;
-#endif
-    if (fColorLUT) {
-        size_t storageBytes = len * sizeof(uint32_t);
-        storage.reset(storageBytes);
-        handle_color_lut((uint32_t*) storage.get(), src, len, fColorLUT.get());
-        src = (const uint32_t*) storage.get();
-    }
-
     switch (dstColorFormat) {
         case kRGBA_8888_ColorFormat:
             switch (kDst) {
@@ -1431,7 +1306,7 @@ bool SkColorSpaceXform::apply(ColorFormat dstColorFormat, void* dst, ColorFormat
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace* space) {
+std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace_XYZ* space) {
         return std::unique_ptr<SkColorSpaceXform>(new SkColorSpaceXform_XYZ
                 <kTable_SrcGamma, kTable_DstGamma, kNone_ColorSpaceMatch>
                 (space, SkMatrix::I(), space));
index c1b9785424eafca700156ac54d3aaa45153d061b..a6486776093dad0ef6de3ae5451a0136bd2a2e38 100644 (file)
@@ -13,6 +13,8 @@
 #include "SkColorSpaceXform.h"
 #include "SkResourceCache.h"
 
+class SkColorSpace_XYZ;
+
 class SkColorSpaceXform_Base : public SkColorSpaceXform {
 public:
     static constexpr int kDstGammaTableSize = 1024;
@@ -23,10 +25,10 @@ protected:
 
 private:
     static void BuildDstGammaTables(const uint8_t* outGammaTables[3], uint8_t* gammaTableStorage,
-                                    const SkColorSpace* space, bool gammasAreMatching);
+                                    const SkColorSpace_XYZ* space, bool gammasAreMatching);
 
     friend class SkColorSpaceXform;
-    friend class SkColorSpace_Base;
+    friend class SkColorSpace_XYZ;
 };
 
 enum SrcGamma {
@@ -54,10 +56,8 @@ protected:
                  int count, SkAlphaType alphaType) const override;
 
 private:
-    SkColorSpaceXform_XYZ(SkColorSpace* srcSpace, const SkMatrix44& srcToDst,
-                          SkColorSpace* dstSpace);
-
-    sk_sp<SkColorLookUpTable> fColorLUT;
+    SkColorSpaceXform_XYZ(SkColorSpace_XYZ* srcSpace, const SkMatrix44& srcToDst,
+                          SkColorSpace_XYZ* dstSpace);
 
     // Contain pointers into storage or pointers into precomputed tables.
     const float*              fSrcGammaTables[3];
@@ -68,10 +68,10 @@ private:
     float                     fSrcToDst[16];
 
     friend class SkColorSpaceXform;
-    friend std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace* space);
+    friend std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace_XYZ* space);
 };
 
 // For testing.  Bypasses opts for when src and dst color spaces are equal.
-std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace* space);
+std::unique_ptr<SkColorSpaceXform> SlowIdentityXform(SkColorSpace_XYZ* space);
 
 #endif
diff --git a/src/core/SkColorSpace_A2B.cpp b/src/core/SkColorSpace_A2B.cpp
new file mode 100644 (file)
index 0000000..f67da48
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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(SkGammaNamed aCurveNamed, sk_sp<SkGammas> aCurve,
+                                   sk_sp<SkColorLookUpTable> colorLUT,
+                                   SkGammaNamed mCurveNamed, sk_sp<SkGammas> mCurve,
+                                   const SkMatrix44& matrix,
+                                   SkGammaNamed bCurveNamed, sk_sp<SkGammas> bCurve,
+                                   PCS pcs, sk_sp<SkData> profileData)
+    : INHERITED(std::move(profileData))
+    , fACurveNamed(aCurveNamed)
+    , fACurve(std::move(aCurve))
+    , fColorLUT(std::move(colorLUT))
+    , fMCurveNamed(mCurveNamed)
+    , fMCurve(std::move(mCurve))
+    , fMatrix(matrix)
+    , fBCurveNamed(bCurveNamed)
+    , fBCurve(std::move(bCurve))
+    , fPCS(pcs)
+{}
diff --git a/src/core/SkColorSpace_A2B.h b/src/core/SkColorSpace_A2B.h
new file mode 100644 (file)
index 0000000..0fc952b
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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 "SkColorSpace_Base.h"
+
+// 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 has
+// the potential to allow conversion from input color spaces with a different
+// number of channels such as CMYK (4) or GRAY (1), but that is not supported yet.
+//
+// Evaluation is done: A-curve => CLUT => M-curve => Matrix => B-curve
+//
+// There are also multi-processing-elements in the A2B0 tag which allow you to
+// combine these 3 primitives (TRC, CLUT, matrix) in any order/quantitiy,
+// but support for that is not implemented.
+class SkColorSpace_A2B : public SkColorSpace_Base {
+public:
+    const SkMatrix44* toXYZD50() 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;
+    }
+
+    const SkMatrix44* fromXYZD50() const override {
+        // See toXYZD50()'s comment. Also, A2B0 profiles are not supported
+        // as destination color spaces, so an inverse matrix is never wanted.
+        return nullptr;
+    }
+    
+    bool onGammaCloseToSRGB() const override {
+        // There is no single gamma curve in an A2B0 profile
+        return false;
+    }
+    
+    bool onGammaIsLinear() const override {
+        // There is no single gamma curve in an A2B0 profile
+        return false;
+    }
+
+    SkGammaNamed aCurveNamed() const { return fACurveNamed; }
+
+    const SkGammas* aCurve() const { return fACurve.get(); }
+
+    const SkColorLookUpTable* colorLUT() const { return fColorLUT.get(); }
+
+    SkGammaNamed mCurveNamed() const { return fMCurveNamed; }
+    
+    const SkGammas* mCurve() const { return fMCurve.get(); }
+
+    const SkMatrix44& matrix() const { return fMatrix; }
+
+    SkGammaNamed bCurveNamed() const { return fBCurveNamed; }
+    
+    const SkGammas* bCurve() const { return fBCurve.get(); }
+
+    // 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; }
+
+    Type type() const override { return Type::kA2B; }
+
+private:
+    SkColorSpace_A2B(SkGammaNamed aCurveNamed, sk_sp<SkGammas> aCurve,
+                     sk_sp<SkColorLookUpTable> colorLUT,
+                     SkGammaNamed mCurveNamed, sk_sp<SkGammas> mCurve,
+                     const SkMatrix44& matrix,
+                     SkGammaNamed bCurveNamed, sk_sp<SkGammas> bCurve,
+                     PCS pcs, sk_sp<SkData> profileData);
+
+    const SkGammaNamed        fACurveNamed;
+    sk_sp<SkGammas>           fACurve;
+    sk_sp<SkColorLookUpTable> fColorLUT;
+    const SkGammaNamed        fMCurveNamed;
+    sk_sp<SkGammas>           fMCurve;
+    SkMatrix44                fMatrix;
+    const SkGammaNamed        fBCurveNamed;
+    sk_sp<SkGammas>           fBCurve;
+    PCS                       fPCS;
+    
+    friend class SkColorSpace;
+    typedef SkColorSpace_Base INHERITED;
+};
+
+#endif
index a188a11dd115255e07125dccb16b96c83d736175..d8470950c49f7b10f939113e4bb2705a27eb3951 100644 (file)
@@ -169,20 +169,32 @@ struct SkColorLookUpTable : public SkRefCnt {
 
 class SkColorSpace_Base : public SkColorSpace {
 public:
-    SkGammaNamed gammaNamed() const { return fGammaNamed; }
-    const SkGammas* gammas() const { return fGammas.get(); }
 
-    const SkColorLookUpTable* colorLUT() const { return fColorLUT.get(); }
-
-    const SkMatrix44& toXYZD50() const { return fToXYZD50; }
-    const SkMatrix44& fromXYZD50() const;
-    
-    void toDstGammaTables(const uint8_t* tables[3], sk_sp<SkData>* storage, int numTables) const;
+    /**
+     *  Describes color space gamut as a transformation to XYZ D50.
+     *  Returns nullptr if color gamut cannot be described in terms of XYZ D50.
+     */
+    virtual const SkMatrix44* toXYZD50() const = 0;
 
     /**
-     *  Create an SkColorSpace with the same gamut as this color space, but with linear gamma.
+     *  Describes color space gamut as a transformation from XYZ D50
+     *  Returns nullptr if color gamut cannot be described in terms of XYZ D50.
      */
-    sk_sp<SkColorSpace> makeLinearGamma();
+    virtual const SkMatrix44* fromXYZD50() const = 0;
+    
+    virtual bool onGammaCloseToSRGB() const = 0;
+    
+    virtual bool onGammaIsLinear() const = 0;
+    
+    enum class Type : uint8_t {
+        kXYZ,
+        kA2B
+    };
+    
+    virtual Type type() const = 0;
+    
+protected:
+    SkColorSpace_Base(sk_sp<SkData> profileData);
 
 private:
 
@@ -199,23 +211,10 @@ private:
 
     SkColorSpace_Base(SkGammaNamed gammaNamed, const SkMatrix44& toXYZ);
 
-    SkColorSpace_Base(sk_sp<SkColorLookUpTable> colorLUT, SkGammaNamed gammaNamed,
-                      sk_sp<SkGammas> gammas, const SkMatrix44& toXYZ, sk_sp<SkData> profileData);
-
-    sk_sp<SkColorLookUpTable>      fColorLUT;
-    const SkGammaNamed             fGammaNamed;
-    sk_sp<SkGammas>                fGammas;
-    sk_sp<SkData>                  fProfileData;
-
-    const SkMatrix44               fToXYZD50;
-    mutable SkMatrix44             fFromXYZD50;
-    mutable SkOnce                 fFromXYZOnce;
-
-    mutable sk_sp<SkData>          fDstStorage;
-    mutable const uint8_t*         fToDstGammaTables[3];
-    mutable SkOnce                 fToDstGammaOnce;
+    sk_sp<SkData> fProfileData;
 
     friend class SkColorSpace;
+    friend class SkColorSpace_XYZ;
     friend class ColorSpaceXformTest;
     friend class ColorSpaceTest;
     typedef SkColorSpace INHERITED;
index b33e6b5ba19bad70d7c32352fb403f1f71bdbf25..df665ecd690c0ebbd2a87d872865e27ba3608e72 100644 (file)
@@ -6,7 +6,9 @@
  */
 
 #include "SkColorSpace.h"
+#include "SkColorSpace_A2B.h"
 #include "SkColorSpace_Base.h"
+#include "SkColorSpace_XYZ.h"
 #include "SkColorSpacePriv.h"
 #include "SkEndian.h"
 #include "SkFixed.h"
@@ -52,6 +54,7 @@ static constexpr uint32_t kInput_Profile      = SkSetFourByteTag('s', 'c', 'n',
 static constexpr uint32_t kOutput_Profile     = SkSetFourByteTag('p', 'r', 't', 'r');
 static constexpr uint32_t kColorSpace_Profile = SkSetFourByteTag('s', 'p', 'a', 'c');
 static constexpr uint32_t kXYZ_PCSSpace       = SkSetFourByteTag('X', 'Y', 'Z', ' ');
+static constexpr uint32_t kLAB_PCSSpace       = SkSetFourByteTag('L', 'a', 'b', ' ');
 static constexpr uint32_t kACSP_Signature     = SkSetFourByteTag('a', 'c', 's', 'p');
 
 struct ICCProfileHeader {
@@ -130,7 +133,7 @@ struct ICCProfileHeader {
 
         // TODO (msarett):
         // All the profiles we've tested so far use XYZ as the profile connection space.
-        return_if_false(fPCS == kXYZ_PCSSpace, "Unsupported PCS space");
+        return_if_false(fPCS == kXYZ_PCSSpace || fPCS == kLAB_PCSSpace, "Unsupported PCS space");
 
         return_if_false(fSignature == kACSP_Signature, "Bad signature");
 
@@ -668,21 +671,19 @@ static bool load_matrix(SkMatrix44* toXYZ, const uint8_t* src, size_t len) {
         return false;
     }
 
-    // For this matrix to behave like our "to XYZ D50" matrices, it needs to be scaled.
-    constexpr float scale = 65535.0 / 32768.0;
     float array[16];
-    array[ 0] = scale * SkFixedToFloat(read_big_endian_i32(src));
-    array[ 1] = scale * SkFixedToFloat(read_big_endian_i32(src + 4));
-    array[ 2] = scale * SkFixedToFloat(read_big_endian_i32(src + 8));
-    array[ 3] = scale * SkFixedToFloat(read_big_endian_i32(src + 36)); // translate R
-    array[ 4] = scale * SkFixedToFloat(read_big_endian_i32(src + 12));
-    array[ 5] = scale * SkFixedToFloat(read_big_endian_i32(src + 16));
-    array[ 6] = scale * SkFixedToFloat(read_big_endian_i32(src + 20));
-    array[ 7] = scale * SkFixedToFloat(read_big_endian_i32(src + 40)); // translate G
-    array[ 8] = scale * SkFixedToFloat(read_big_endian_i32(src + 24));
-    array[ 9] = scale * SkFixedToFloat(read_big_endian_i32(src + 28));
-    array[10] = scale * SkFixedToFloat(read_big_endian_i32(src + 32));
-    array[11] = scale * SkFixedToFloat(read_big_endian_i32(src + 44)); // translate B
+    array[ 0] = SkFixedToFloat(read_big_endian_i32(src));
+    array[ 1] = SkFixedToFloat(read_big_endian_i32(src + 4));
+    array[ 2] = SkFixedToFloat(read_big_endian_i32(src + 8));
+    array[ 3] = SkFixedToFloat(read_big_endian_i32(src + 36)); // translate R
+    array[ 4] = SkFixedToFloat(read_big_endian_i32(src + 12));
+    array[ 5] = SkFixedToFloat(read_big_endian_i32(src + 16));
+    array[ 6] = SkFixedToFloat(read_big_endian_i32(src + 20));
+    array[ 7] = SkFixedToFloat(read_big_endian_i32(src + 40)); // translate G
+    array[ 8] = SkFixedToFloat(read_big_endian_i32(src + 24));
+    array[ 9] = SkFixedToFloat(read_big_endian_i32(src + 28));
+    array[10] = SkFixedToFloat(read_big_endian_i32(src + 32));
+    array[11] = SkFixedToFloat(read_big_endian_i32(src + 44)); // translate B
     array[12] = 0.0f;
     array[13] = 0.0f;
     array[14] = 0.0f;
@@ -702,9 +703,115 @@ static inline SkGammaNamed is_named(const sk_sp<SkGammas>& gammas) {
     return kNonStandard_SkGammaNamed;
 }
 
+/**
+ *  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 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,
+                                 const uint8_t* rTagPtr, size_t tagLen)
+{
+    SkGammas::Data rData;
+    SkColorSpaceTransferFn rParams;
+
+    // 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;
+    SkGammas::Type rType = parse_gamma(&rData, &rParams, &tagBytes, rTagPtr, tagLen);
+    handle_invalid_gamma(&rType, &rData);
+    size_t alignedTagBytes = SkAlign4(tagBytes);
+
+    if ((3 * alignedTagBytes <= tagLen) &&
+        !memcmp(rTagPtr, rTagPtr + 1 * alignedTagBytes, tagBytes) &&
+        !memcmp(rTagPtr, rTagPtr + 2 * alignedTagBytes, tagBytes))
+    {
+        if (SkGammas::Type::kNamed_Type == rType) {
+            *gammaNamed = rData.fNamed;
+        } else {
+            size_t allocSize = sizeof(SkGammas);
+            return_if_false(safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize),
+                            "SkGammas struct is too large to allocate");
+            void* memory = sk_malloc_throw(allocSize);
+            *gammas = sk_sp<SkGammas>(new (memory) SkGammas());
+            load_gammas(memory, 0, rType, &rData, rParams, rTagPtr);
+
+            (*gammas)->fRedType = rType;
+            (*gammas)->fGreenType = rType;
+            (*gammas)->fBlueType = rType;
+
+            (*gammas)->fRedData = rData;
+            (*gammas)->fGreenData = rData;
+            (*gammas)->fBlueData = rData;
+        }
+    } else {
+        const uint8_t* gTagPtr = rTagPtr + alignedTagBytes;
+        tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0;
+        SkGammas::Data gData;
+        SkColorSpaceTransferFn gParams;
+        tagBytes = 0;
+        SkGammas::Type gType = parse_gamma(&gData, &gParams, &tagBytes, gTagPtr,
+                                                   tagLen);
+        handle_invalid_gamma(&gType, &gData);
+
+        alignedTagBytes = SkAlign4(tagBytes);
+        const uint8_t* bTagPtr = gTagPtr + alignedTagBytes;
+        tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0;
+        SkGammas::Data bData;
+        SkColorSpaceTransferFn bParams;
+        SkGammas::Type bType = parse_gamma(&bData, &bParams, &tagBytes, bTagPtr,
+                                                   tagLen);
+        handle_invalid_gamma(&bType, &bData);
+
+        size_t allocSize = sizeof(SkGammas);
+        return_if_false(safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize),
+                        "SkGammas struct is too large to allocate");
+        return_if_false(safe_add(allocSize, gamma_alloc_size(gType, gData), &allocSize),
+                        "SkGammas struct is too large to allocate");
+        return_if_false(safe_add(allocSize, gamma_alloc_size(bType, bData), &allocSize),
+                        "SkGammas struct is too large to allocate");
+        void* memory = sk_malloc_throw(allocSize);
+        *gammas = sk_sp<SkGammas>(new (memory) SkGammas());
+
+        uint32_t offset = 0;
+        (*gammas)->fRedType = rType;
+        offset += load_gammas(memory, offset, rType, &rData, rParams, rTagPtr);
+
+        (*gammas)->fGreenType = gType;
+        offset += load_gammas(memory, offset, gType, &gData, gParams, gTagPtr);
+
+        (*gammas)->fBlueType = bType;
+        load_gammas(memory, offset, bType, &bData, bParams, bTagPtr);
+
+        (*gammas)->fRedData = rData;
+        (*gammas)->fGreenData = gData;
+        (*gammas)->fBlueData = bData;
+    }
+    
+    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 load_a2b0(sk_sp<SkColorLookUpTable>* colorLUT, SkGammaNamed* gammaNamed,
-                      sk_sp<SkGammas>* gammas, SkMatrix44* toXYZ, const uint8_t* src, size_t len) {
+static bool load_a2b0(sk_sp<SkColorLookUpTable>* colorLUT,
+                      SkGammaNamed* aCurveNamed, sk_sp<SkGammas>* aCurve,
+                      SkGammaNamed* mCurveNamed, sk_sp<SkGammas>* mCurve,
+                      SkGammaNamed* bCurveNamed, sk_sp<SkGammas>* bCurve,
+                      SkMatrix44* matrix, const uint8_t* src, size_t len) {
     if (len < 32) {
         SkColorSpacePrintf("A to B tag is too small (%d bytes).", len);
         return false;
@@ -730,18 +837,13 @@ static bool load_a2b0(sk_sp<SkColorLookUpTable>* colorLUT, SkGammaNamed* gammaNa
         return false;
     }
 
-    // Read the offsets of each element in the A to B tag.  With the exception of A curves and
-    // B curves (which we do not yet support), we will handle these elements in the order in
-    // which they should be applied (rather than the order in which they occur in the tag).
     // If the offset is non-zero it indicates that the element is present.
     uint32_t offsetToACurves = read_big_endian_i32(src + 28);
-    uint32_t offsetToBCurves = read_big_endian_i32(src + 12);
-    if ((0 != offsetToACurves) || (0 != offsetToBCurves)) {
-        // FIXME (msarett): Handle A and B curves.
-        // Note that the A curve is technically required in order to have a color LUT.
-        // However, all the A curves I have seen so far have are just placeholders that
-        // don't actually transform the data.
-        SkColorSpacePrintf("Ignoring A and/or B curve.  Output may be wrong.\n");
+    if (0 != offsetToACurves && offsetToACurves < len) {
+        const size_t tagLen = len - offsetToACurves;
+        if (!parse_and_load_gamma(aCurveNamed, aCurve, src + offsetToACurves, tagLen)) {
+            return false;
+        }
     }
 
     uint32_t offsetToColorLUT = read_big_endian_i32(src + 24);
@@ -749,107 +851,31 @@ static bool load_a2b0(sk_sp<SkColorLookUpTable>* colorLUT, SkGammaNamed* gammaNa
         if (!load_color_lut(colorLUT, inputChannels, src + offsetToColorLUT,
                             len - offsetToColorLUT)) {
             SkColorSpacePrintf("Failed to read color LUT from A to B tag.\n");
+            return false;
         }
     }
 
     uint32_t offsetToMCurves = read_big_endian_i32(src + 20);
     if (0 != offsetToMCurves && offsetToMCurves < len) {
-        const uint8_t* rTagPtr = src + offsetToMCurves;
-        size_t tagLen = len - offsetToMCurves;
-
-        SkGammas::Data rData;
-        SkColorSpaceTransferFn rParams;
-
-        // 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;
-        SkGammas::Type rType = parse_gamma(&rData, &rParams, &tagBytes, rTagPtr, tagLen);
-        handle_invalid_gamma(&rType, &rData);
-        size_t alignedTagBytes = SkAlign4(tagBytes);
-
-        if ((3 * alignedTagBytes <= tagLen) &&
-            !memcmp(rTagPtr, rTagPtr + 1 * alignedTagBytes, tagBytes) &&
-            !memcmp(rTagPtr, rTagPtr + 2 * alignedTagBytes, tagBytes))
-        {
-            if (SkGammas::Type::kNamed_Type == rType) {
-                *gammaNamed = rData.fNamed;
-            } else {
-                size_t allocSize = sizeof(SkGammas);
-                return_if_false(safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize),
-                                "SkGammas struct is too large to allocate");
-                void* memory = sk_malloc_throw(allocSize);
-                *gammas = sk_sp<SkGammas>(new (memory) SkGammas());
-                load_gammas(memory, 0, rType, &rData, rParams, rTagPtr);
-
-                (*gammas)->fRedType = rType;
-                (*gammas)->fGreenType = rType;
-                (*gammas)->fBlueType = rType;
-
-                (*gammas)->fRedData = rData;
-                (*gammas)->fGreenData = rData;
-                (*gammas)->fBlueData = rData;
-            }
-        } else {
-            const uint8_t* gTagPtr = rTagPtr + alignedTagBytes;
-            tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0;
-            SkGammas::Data gData;
-            SkColorSpaceTransferFn gParams;
-            tagBytes = 0;
-            SkGammas::Type gType = parse_gamma(&gData, &gParams, &tagBytes, gTagPtr,
-                                               tagLen);
-            handle_invalid_gamma(&gType, &gData);
-
-            alignedTagBytes = SkAlign4(tagBytes);
-            const uint8_t* bTagPtr = gTagPtr + alignedTagBytes;
-            tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0;
-            SkGammas::Data bData;
-            SkColorSpaceTransferFn bParams;
-            SkGammas::Type bType = parse_gamma(&bData, &bParams, &tagBytes, bTagPtr,
-                                                       tagLen);
-            handle_invalid_gamma(&bType, &bData);
-
-            size_t allocSize = sizeof(SkGammas);
-            return_if_false(safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize),
-                            "SkGammas struct is too large to allocate");
-            return_if_false(safe_add(allocSize, gamma_alloc_size(gType, gData), &allocSize),
-                            "SkGammas struct is too large to allocate");
-            return_if_false(safe_add(allocSize, gamma_alloc_size(bType, bData), &allocSize),
-                            "SkGammas struct is too large to allocate");
-            void* memory = sk_malloc_throw(allocSize);
-            *gammas = sk_sp<SkGammas>(new (memory) SkGammas());
-
-            uint32_t offset = 0;
-            (*gammas)->fRedType = rType;
-            offset += load_gammas(memory, offset, rType, &rData, rParams, rTagPtr);
-
-            (*gammas)->fGreenType = gType;
-            offset += load_gammas(memory, offset, gType, &gData, gParams, gTagPtr);
-
-            (*gammas)->fBlueType = bType;
-            load_gammas(memory, offset, bType, &bData, bParams, bTagPtr);
-
-            (*gammas)->fRedData = rData;
-            (*gammas)->fGreenData = gData;
-            (*gammas)->fBlueData = bData;
-        }
-    } else {
-        // Guess sRGB if the chunk is missing a transfer function.
-        *gammaNamed = kSRGB_SkGammaNamed;
-    }
-
-    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;
+        const size_t tagLen = len - offsetToMCurves;
+        if (!parse_and_load_gamma(mCurveNamed, mCurve, src + offsetToMCurves, tagLen)) {
+            return false;
         }
     }
 
     uint32_t offsetToMatrix = read_big_endian_i32(src + 16);
     if (0 != offsetToMatrix && offsetToMatrix < len) {
-        if (!load_matrix(toXYZ, src + offsetToMatrix, len - offsetToMatrix)) {
+        if (!load_matrix(matrix, src + offsetToMatrix, len - offsetToMatrix)) {
             SkColorSpacePrintf("Failed to read matrix from A to B tag.\n");
-            toXYZ->setIdentity();
+            matrix->setIdentity();
+        }
+    }
+    
+    uint32_t offsetToBCurves = read_big_endian_i32(src + 12);
+    if (0 != offsetToBCurves && offsetToBCurves < len) {
+        const size_t tagLen = len - offsetToBCurves;
+        if (!parse_and_load_gamma(bCurveNamed, bCurve, src + offsetToBCurves, tagLen)) {
+            return false;
         }
     }
 
@@ -927,6 +953,11 @@ sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* input, size_t len) {
             const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ);
             const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ);
             if (r && g && b) {
+                // Lab PCS means the profile is required to be an n-component LUT-based
+                // profile, so 3-component matrix-based profiles can only have an XYZ PCS
+                if (kXYZ_PCSSpace != header.fPCS) {
+                    return_null("Unsupported PCS space");
+                }
                 float toXYZ[9];
                 if (!load_xyz(&toXYZ[0], r->addr(base), r->fLength) ||
                     !load_xyz(&toXYZ[3], g->addr(base), g->fLength) ||
@@ -1044,11 +1075,12 @@ sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* input, size_t len) {
                     // 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_Base(nullptr, gammaNamed,
-                                                                         std::move(gammas), mat,
-                                                                         std::move(data)));
-                    }
+                }
+
+                if (kNonStandard_SkGammaNamed == gammaNamed) {
+                    return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed,
+                                                                    std::move(gammas),
+                                                                    mat, std::move(data)));
                 }
 
                 return SkColorSpace_Base::NewRGB(gammaNamed, mat);
@@ -1057,22 +1089,32 @@ sk_sp<SkColorSpace> SkColorSpace::NewICC(const void* input, size_t len) {
             // Recognize color profile specified by A2B0 tag.
             const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0);
             if (a2b0) {
-                SkGammaNamed gammaNamed = kNonStandard_SkGammaNamed;
-                sk_sp<SkGammas> gammas = nullptr;
+                // default to Linear transforms for when the curves are not
+                // in the profile (which is legal behavior for a profile)
+                SkGammaNamed aCurveNamed = kLinear_SkGammaNamed;
+                SkGammaNamed mCurveNamed = kLinear_SkGammaNamed;
+                SkGammaNamed bCurveNamed = kLinear_SkGammaNamed;
+                sk_sp<SkGammas> aCurve = nullptr;
+                sk_sp<SkGammas> mCurve = nullptr;
+                sk_sp<SkGammas> bCurve = nullptr;
                 sk_sp<SkColorLookUpTable> colorLUT = nullptr;
-                SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor);
-                if (!load_a2b0(&colorLUT, &gammaNamed, &gammas, &toXYZ, a2b0->addr(base),
-                               a2b0->fLength)) {
+                SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor);
+                if (!load_a2b0(&colorLUT, &aCurveNamed, &aCurve, &mCurveNamed, &mCurve,
+                               &bCurveNamed, &bCurve, &matrix, a2b0->addr(base), a2b0->fLength)) {
                     return_null("Failed to parse A2B0 tag");
                 }
 
-                if (colorLUT || kNonStandard_SkGammaNamed == gammaNamed) {
-                    return sk_sp<SkColorSpace>(new SkColorSpace_Base(std::move(colorLUT),
-                                                                     gammaNamed, std::move(gammas),
-                                                                     toXYZ, std::move(data)));
+                SkColorSpace_A2B::PCS pcs = SkColorSpace_A2B::PCS::kLAB;
+                if (header.fPCS == kXYZ_PCSSpace) {
+                    pcs = SkColorSpace_A2B::PCS::kXYZ;
                 }
 
-                return SkColorSpace_Base::NewRGB(gammaNamed, toXYZ);
+                return sk_sp<SkColorSpace>(new SkColorSpace_A2B(aCurveNamed, std::move(aCurve),
+                                                                std::move(colorLUT),
+                                                                mCurveNamed, std::move(mCurve),
+                                                                matrix,
+                                                                bCurveNamed, std::move(bCurve),
+                                                                pcs, std::move(data)));
             }
         }
         default:
@@ -1217,13 +1259,17 @@ sk_sp<SkData> SkColorSpace_Base::writeToICC() const {
     if (fProfileData) {
         return fProfileData;
     }
+    // Profile Data is be mandatory for A2B0 Color Spaces
+    SkASSERT(type() == Type::kXYZ);
 
     // The client may create an SkColorSpace using an SkMatrix44, but currently we only
     // support writing profiles with 3x3 matrices.
     // TODO (msarett): Fix this!
-    if (0.0f != fToXYZD50.getFloat(3, 0) || 0.0f != fToXYZD50.getFloat(3, 1) ||
-        0.0f != fToXYZD50.getFloat(3, 2) || 0.0f != fToXYZD50.getFloat(0, 3) ||
-        0.0f != fToXYZD50.getFloat(1, 3) || 0.0f != fToXYZD50.getFloat(2, 3))
+    const SkColorSpace_XYZ* thisXYZ = static_cast<const SkColorSpace_XYZ*>(this);
+    const SkMatrix44& toXYZD50 = *thisXYZ->toXYZD50();
+    if (0.0f != toXYZD50.getFloat(3, 0) || 0.0f != toXYZD50.getFloat(3, 1) ||
+        0.0f != toXYZD50.getFloat(3, 2) || 0.0f != toXYZD50.getFloat(0, 3) ||
+        0.0f != toXYZD50.getFloat(1, 3) || 0.0f != toXYZD50.getFloat(2, 3))
     {
         return nullptr;
     }
@@ -1244,15 +1290,15 @@ sk_sp<SkData> SkColorSpace_Base::writeToICC() const {
     ptr += sizeof(gEmptyTextTag);
 
     // Write XYZ tags
-    write_xyz_tag((uint32_t*) ptr, fToXYZD50, 0);
+    write_xyz_tag((uint32_t*) ptr, toXYZD50, 0);
     ptr += kTAG_XYZ_Bytes;
-    write_xyz_tag((uint32_t*) ptr, fToXYZD50, 1);
+    write_xyz_tag((uint32_t*) ptr, toXYZD50, 1);
     ptr += kTAG_XYZ_Bytes;
-    write_xyz_tag((uint32_t*) ptr, fToXYZD50, 2);
+    write_xyz_tag((uint32_t*) ptr, toXYZD50, 2);
     ptr += kTAG_XYZ_Bytes;
 
     // Write TRC tags
-    SkGammaNamed gammaNamed = this->gammaNamed();
+    SkGammaNamed gammaNamed = thisXYZ->gammaNamed();
     if (kNonStandard_SkGammaNamed == gammaNamed) {
         // FIXME (msarett):
         // Write the correct gamma representation rather than 2.2f.
diff --git a/src/core/SkColorSpace_XYZ.cpp b/src/core/SkColorSpace_XYZ.cpp
new file mode 100644 (file)
index 0000000..1f62245
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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_XYZ.h"
+#include "SkColorSpaceXform_Base.h"
+
+static constexpr float gSRGB_toXYZD50[] {
+    0.4358f, 0.3853f, 0.1430f,    // Rx, Gx, Bx
+    0.2224f, 0.7170f, 0.0606f,    // Ry, Gy, Gz
+    0.0139f, 0.0971f, 0.7139f,    // Rz, Gz, Bz
+};
+
+SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50)
+    : INHERITED(nullptr)
+    , fGammaNamed(gammaNamed)
+    , fGammas(nullptr)
+    , fToXYZD50(toXYZD50)
+    , fFromXYZD50(SkMatrix44::kUninitialized_Constructor)
+{}
+
+SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, sk_sp<SkGammas> gammas,
+                                   const SkMatrix44& toXYZD50, sk_sp<SkData> profileData)
+    : INHERITED(std::move(profileData))
+    , fGammaNamed(gammaNamed)
+    , fGammas(std::move(gammas))
+    , fToXYZD50(toXYZD50)
+    , fFromXYZD50(SkMatrix44::kUninitialized_Constructor)
+{}
+
+const SkMatrix44* SkColorSpace_XYZ::fromXYZD50() const {
+    fFromXYZOnce([this] {
+        if (!fToXYZD50.invert(&fFromXYZD50)) {
+            // If a client gives us a dst gamut with a transform that we can't invert, we will
+            // simply give them back a transform to sRGB gamut.
+            SkDEBUGFAIL("Non-invertible XYZ matrix, defaulting to sRGB");
+            SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor);
+            srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50);
+            srgbToxyzD50.invert(&fFromXYZD50);
+        }
+    });
+    return &fFromXYZD50;
+}
+
+bool SkColorSpace_XYZ::onGammaCloseToSRGB() const {
+    return kSRGB_SkGammaNamed == fGammaNamed || k2Dot2Curve_SkGammaNamed == fGammaNamed;
+}
+
+bool SkColorSpace_XYZ::onGammaIsLinear() const {
+    return kLinear_SkGammaNamed == fGammaNamed;
+}
+
+sk_sp<SkColorSpace> SkColorSpace_XYZ::makeLinearGamma() {
+    if (this->gammaIsLinear()) {
+        return sk_ref_sp(this);
+    }
+    return SkColorSpace_Base::NewRGB(kLinear_SkGammaNamed, fToXYZD50);
+}
+
+void SkColorSpace_XYZ::toDstGammaTables(const uint8_t* tables[3], sk_sp<SkData>* storage,
+                                         int numTables) const {
+    fToDstGammaOnce([this, numTables] {
+        const bool gammasAreMatching = numTables <= 1;
+        fDstStorage =
+                SkData::MakeUninitialized(numTables * SkColorSpaceXform_Base::kDstGammaTableSize);
+        SkColorSpaceXform_Base::BuildDstGammaTables(fToDstGammaTables,
+                                                    (uint8_t*) fDstStorage->writable_data(), this,
+                                                    gammasAreMatching);
+    });
+
+    *storage = fDstStorage;
+    tables[0] = fToDstGammaTables[0];
+    tables[1] = fToDstGammaTables[1];
+    tables[2] = fToDstGammaTables[2];
+}
diff --git a/src/core/SkColorSpace_XYZ.h b/src/core/SkColorSpace_XYZ.h
new file mode 100644 (file)
index 0000000..72ec3f1
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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_XYZ_DEFINED
+#define SkColorSpace_XYZ_DEFINED
+
+#include "SkColorSpace_Base.h"
+#include "SkData.h"
+#include "SkOnce.h"
+
+class SkColorSpace_XYZ : public SkColorSpace_Base {
+public:
+    const SkMatrix44* toXYZD50() const override { return &fToXYZD50; }
+    
+    const SkMatrix44* fromXYZD50() const override;
+
+    bool onGammaCloseToSRGB() const override;
+    
+    bool onGammaIsLinear() const override;
+    
+    Type type() const override { return Type::kXYZ; }
+    
+    sk_sp<SkColorSpace> makeLinearGamma();
+    
+    SkGammaNamed gammaNamed() const { return fGammaNamed; }
+    
+    const SkGammas* gammas() const { return fGammas.get(); }
+    
+    void toDstGammaTables(const uint8_t* tables[3], sk_sp<SkData>* storage, int numTables) const;
+
+private:
+    SkColorSpace_XYZ(SkGammaNamed gammaNamed, const SkMatrix44& toXYZ);
+
+    SkColorSpace_XYZ(SkGammaNamed gammaNamed, sk_sp<SkGammas> gammas,
+                     const SkMatrix44& toXYZ, sk_sp<SkData> profileData);
+
+    const SkGammaNamed     fGammaNamed;
+    sk_sp<SkGammas>        fGammas;
+    const SkMatrix44       fToXYZD50;
+    
+    mutable SkMatrix44     fFromXYZD50;
+    mutable SkOnce         fFromXYZOnce;
+    
+    mutable sk_sp<SkData>  fDstStorage;
+    mutable const uint8_t* fToDstGammaTables[3];
+    mutable SkOnce         fToDstGammaOnce;
+
+    friend class SkColorSpace;
+    friend class SkColorSpace_Base;
+    friend class ColorSpaceXformTest;
+    typedef SkColorSpace_Base INHERITED;
+};
+
+#endif
index 03f03b20b1f2a7fd1360eb901e9d3486caca8266..6c99b37ad7a59fe61abca48273e44e5d09fe4848 100644 (file)
@@ -6,7 +6,7 @@
  */
 
 #include "Sk4fLinearGradient.h"
-#include "SkColorSpace_Base.h"
+#include "SkColorSpace_XYZ.h"
 #include "SkGradientShaderPriv.h"
 #include "SkHalf.h"
 #include "SkLinearGradient.h"
@@ -1747,7 +1747,8 @@ GrGradientEffect::RandomGradientParams::RandomGradientParams(SkRandom* random) {
     if (fUseColors4f) {
         fColorSpace = GrTest::TestColorSpace(random);
         if (fColorSpace) {
-            fColorSpace = as_CSB(fColorSpace)->makeLinearGamma();
+            SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(fColorSpace)->type());
+            fColorSpace = static_cast<SkColorSpace_XYZ*>(fColorSpace.get())->makeLinearGamma();
         }
     }
 
index d2270fafdd4a2458b4f8a1721be0a4f41f5adf26..ef96087147c1656ae00586a36f5c2927f09c3be4 100644 (file)
@@ -49,8 +49,15 @@ sk_sp<GrColorSpaceXform> GrColorSpaceXform::Make(SkColorSpace* src, SkColorSpace
         return nullptr;
     }
 
+    
+    const SkMatrix44* toXYZD50   = as_CSB(src)->toXYZD50();
+    const SkMatrix44* fromXYZD50 = as_CSB(dst)->fromXYZD50();
+    if (!toXYZD50 || !fromXYZD50) {
+        // unsupported colour spaces -- cannot specify gamut as a matrix
+        return nullptr;
+    }
     SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor);
-    srcToDst.setConcat(as_CSB(dst)->fromXYZD50(), as_CSB(src)->toXYZD50());
+    srcToDst.setConcat(*fromXYZD50, *toXYZD50);
 
     if (matrix_is_almost_identity(srcToDst)) {
         return nullptr;
index a6058a9c35f75c5481ec5b49f0111aea0046ce00..45455b440577228e3a259de6401481f79c9af683 100644 (file)
@@ -10,7 +10,7 @@
 #include "SkBitmap.h"
 #include "SkCodec.h"
 #include "SkCodecImageGenerator.h"
-#include "SkColorSpace_Base.h"
+#include "SkColorSpace_XYZ.h"
 #include "SkData.h"
 #include "SkImageEncoder.h"
 #include "SkFrontBufferedStream.h"
@@ -1192,7 +1192,9 @@ static void test_conversion_possible(skiatest::Reporter* r, const char* path,
         REPORTER_ASSERT(r, SkCodec::kUnimplemented == result);
     }
 
-    infoF16 = infoF16.makeColorSpace(as_CSB(infoF16.colorSpace())->makeLinearGamma());
+    SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(infoF16.colorSpace())->type());
+    SkColorSpace_XYZ* csXYZ = static_cast<SkColorSpace_XYZ*>(infoF16.colorSpace());
+    infoF16 = infoF16.makeColorSpace(csXYZ->makeLinearGamma());
     result = codec->getPixels(infoF16, bm.getPixels(), bm.rowBytes());
     REPORTER_ASSERT(r, SkCodec::kSuccess == result);
     result = codec->startScanlineDecode(infoF16);
index aa0a33b40795b4e367448e661aabe2446cb82e3d..04d401012d5a393b0979eef5c25924813d8a5a05 100644 (file)
@@ -9,6 +9,7 @@
 #include "SkCodec.h"
 #include "SkColorSpace.h"
 #include "SkColorSpace_Base.h"
+#include "SkColorSpace_XYZ.h"
 #include "Test.h"
 
 #include "png.h"
@@ -22,9 +23,11 @@ static void test_space(skiatest::Reporter* r, SkColorSpace* space,
                        const SkGammaNamed expectedGamma) {
 
     REPORTER_ASSERT(r, nullptr != space);
-    REPORTER_ASSERT(r, expectedGamma == as_CSB(space)->gammaNamed());
+    SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(space)->type());
+    SkColorSpace_XYZ* csXYZ = static_cast<SkColorSpace_XYZ*>(space);
+    REPORTER_ASSERT(r, expectedGamma == csXYZ->gammaNamed());
 
-    const SkMatrix44& mat = as_CSB(space)->toXYZD50();
+    const SkMatrix44& mat = *csXYZ->toXYZD50();
     const float src[] = {
         1, 0, 0, 1,
         0, 1, 0, 1,
@@ -120,8 +123,9 @@ DEF_TEST(ColorSpaceSRGBLinearCompare, r) {
     sk_sp<SkColorSpace> namedColorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGBLinear_Named);
 
     // Create the linear sRGB color space via the sRGB color space's makeLinearGamma()
-    sk_sp<SkColorSpace> viaSrgbColorSpace =
-        as_CSB(SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named))->makeLinearGamma();
+    auto srgb = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+    auto srgbXYZ = static_cast<SkColorSpace_XYZ*>(srgb.get());
+    sk_sp<SkColorSpace> viaSrgbColorSpace = srgbXYZ->makeLinearGamma();
     REPORTER_ASSERT(r, namedColorSpace == viaSrgbColorSpace);
 
     // Create a linear sRGB color space by value
@@ -166,8 +170,12 @@ DEF_TEST(ColorSpaceWriteICC, r) {
     sk_sp<SkData> newMonitorData = ColorSpaceTest::WriteToICC(monitorSpace.get());
     sk_sp<SkColorSpace> newMonitorSpace = SkColorSpace::NewICC(newMonitorData->data(),
                                                                newMonitorData->size());
-    REPORTER_ASSERT(r, as_CSB(monitorSpace)->toXYZD50() == as_CSB(newMonitorSpace)->toXYZD50());
-    REPORTER_ASSERT(r, as_CSB(monitorSpace)->gammaNamed() == as_CSB(newMonitorSpace)->gammaNamed());
+    SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(monitorSpace)->type());
+    SkColorSpace_XYZ* monitorSpaceXYZ = static_cast<SkColorSpace_XYZ*>(monitorSpace.get());
+    SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(newMonitorSpace)->type());
+    SkColorSpace_XYZ* newMonitorSpaceXYZ = static_cast<SkColorSpace_XYZ*>(newMonitorSpace.get());
+    REPORTER_ASSERT(r, *monitorSpaceXYZ->toXYZD50() == *newMonitorSpaceXYZ->toXYZD50());
+    REPORTER_ASSERT(r, monitorSpaceXYZ->gammaNamed() == newMonitorSpaceXYZ->gammaNamed());
 }
 
 DEF_TEST(ColorSpace_Named, r) {
@@ -184,7 +192,9 @@ DEF_TEST(ColorSpace_Named, r) {
         auto cs = SkColorSpace::NewNamed(rec.fNamed);
         REPORTER_ASSERT(r, cs);
         if (cs) {
-            REPORTER_ASSERT(r, rec.fExpectedGamma == as_CSB(cs)->gammaNamed());
+            SkASSERT(SkColorSpace_Base::Type::kXYZ == as_CSB(cs)->type());
+            SkColorSpace_XYZ* csXYZ = static_cast<SkColorSpace_XYZ*>(cs.get());
+            REPORTER_ASSERT(r, rec.fExpectedGamma == csXYZ->gammaNamed());
         }
     }
 
index 6cf0dbef21bb5815978f39b6e1f61bc3f73290ea..477e61acdda9d4956afdb6eea2537bed421e18c9 100644 (file)
@@ -11,6 +11,7 @@
 #include "SkColorPriv.h"
 #include "SkColorSpace.h"
 #include "SkColorSpace_Base.h"
+#include "SkColorSpace_XYZ.h"
 #include "SkColorSpaceXform_Base.h"
 #include "Test.h"
 
@@ -18,11 +19,11 @@ class ColorSpaceXformTest {
 public:
     static std::unique_ptr<SkColorSpaceXform> CreateIdentityXform(const sk_sp<SkGammas>& gammas) {
         // Logically we can pass any matrix here.  For simplicty, pass I(), i.e. D50 XYZ gamut.
-        sk_sp<SkColorSpace> space(new SkColorSpace_Base(
-                nullptr, kNonStandard_SkGammaNamed, gammas, SkMatrix::I(), nullptr));
+        sk_sp<SkColorSpace> space(new SkColorSpace_XYZ(
+                kNonStandard_SkGammaNamed, gammas, SkMatrix::I(), nullptr));
 
         // Use special testing entry point, so we don't skip the xform, even though src == dst.
-        return SlowIdentityXform(space.get());
+        return SlowIdentityXform(static_cast<SkColorSpace_XYZ*>(space.get()));
     }
 };
 
@@ -175,25 +176,3 @@ DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) {
     test_identity_xform(r, gammas, true);
 }
 
-DEF_TEST(ColorSpaceXform_applyCLUTMemoryAccess, r) {
-    // buffers larger than 1024 (or 256 in GOOGLE3) will force ColorSpaceXform_Base::apply()
-    // to heap-allocate a buffer that is used for CLUT application, and this test is here to
-    // ensure that it no longer causes potential invalid memory accesses when this happens
-    const size_t len = 2048;
-    SkAutoTMalloc<uint32_t> src(len);
-    SkAutoTMalloc<uint32_t> dst(len);
-    for (uint32_t i = 0; i < len; ++i) {
-        src[i] = i;
-    }
-    // this ICC profile has a CLUT in it
-    const SkString filename(GetResourcePath("icc_profiles/upperRight.icc"));
-    sk_sp<SkData> iccData = SkData::MakeFromFileName(filename.c_str());
-    REPORTER_ASSERT_MESSAGE(r, iccData, "upperRight.icc profile required for test");
-    sk_sp<SkColorSpace> srcSpace = SkColorSpace::NewICC(iccData->bytes(), iccData->size());
-    sk_sp<SkColorSpace> dstSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
-    auto xform = SkColorSpaceXform::New(srcSpace.get(), dstSpace.get());
-    bool result = xform->apply(SkColorSpaceXform::kRGBA_8888_ColorFormat, dst.get(),
-                               SkColorSpaceXform::kRGBA_8888_ColorFormat, src.get(), len,
-                               kUnpremul_SkAlphaType);
-    REPORTER_ASSERT(r, result);
-}
index 241ddb1dbc6257356dc4f7c088c0f45cbf717804..180f2d985b6060e1bf9eee57e3ca6ac86447e5e7 100644 (file)
@@ -921,9 +921,10 @@ static void test_surface_creation_and_snapshot_with_color_space(
 
     auto srgbColorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
     auto adobeColorSpace = SkColorSpace::NewNamed(SkColorSpace::kAdobeRGB_Named);
-    SkMatrix44 srgbMatrix = as_CSB(srgbColorSpace)->toXYZD50();
+    const SkMatrix44* srgbMatrix = as_CSB(srgbColorSpace)->toXYZD50();
+    SkASSERT(srgbMatrix);
     const float oddGamma[] = { 2.4f, 2.4f, 2.4f };
-    auto oddColorSpace = SkColorSpace::NewRGB(oddGamma, srgbMatrix);
+    auto oddColorSpace = SkColorSpace::NewRGB(oddGamma, *srgbMatrix);
     auto linearColorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGBLinear_Named);
 
     const struct {
index 412be7362692cd1444a2eb671b4141d0078382c3..ec6d7219083027c4d8e54215c58956c06a13e109 100644 (file)
@@ -126,22 +126,28 @@ DEF_TEST(ParseConfigs_DefaultConfigs, reporter) {
     REPORTER_ASSERT(reporter, configs[25]->asConfigGpu()->getColorType() == kRGBA_F16_SkColorType);
     REPORTER_ASSERT(reporter, configs[25]->asConfigGpu()->getColorSpace());
     REPORTER_ASSERT(reporter, configs[25]->asConfigGpu()->getColorSpace()->gammaIsLinear());
-    REPORTER_ASSERT(reporter, as_CSB(configs[25]->asConfigGpu()->getColorSpace())->toXYZD50() ==
-                              as_CSB(srgbColorSpace)->toXYZD50());
+    const SkMatrix44* srgbXYZ = as_CSB(srgbColorSpace)->toXYZD50();
+    SkASSERT(srgbXYZ);
+    const SkMatrix44* config25XYZ =
+            as_CSB(configs[25]->asConfigGpu()->getColorSpace())->toXYZD50();
+    SkASSERT(config25XYZ);
+    REPORTER_ASSERT(reporter, *config25XYZ == *srgbXYZ);
     REPORTER_ASSERT(reporter, configs[26]->asConfigGpu()->getColorType() == kRGBA_8888_SkColorType);
     REPORTER_ASSERT(reporter, configs[26]->asConfigGpu()->getColorSpace() == srgbColorSpace.get());
     REPORTER_ASSERT(reporter, configs[41]->asConfigGpu()->getColorType() == kRGBA_F16_SkColorType);
     REPORTER_ASSERT(reporter, configs[41]->asConfigGpu()->getColorSpace());
     REPORTER_ASSERT(reporter, configs[41]->asConfigGpu()->getColorSpace()->gammaIsLinear());
-    REPORTER_ASSERT(reporter, as_CSB(configs[41]->asConfigGpu()->getColorSpace())->toXYZD50() !=
-                              as_CSB(srgbColorSpace)->toXYZD50());
+    const SkMatrix44* config41XYZ =
+            as_CSB(configs[41]->asConfigGpu()->getColorSpace())->toXYZD50();
+    SkASSERT(config41XYZ);
+    REPORTER_ASSERT(reporter, *config41XYZ != *srgbXYZ);
+    REPORTER_ASSERT(reporter, configs[33]->asConfigGpu()->getContextType() ==
+                              GrContextFactory::kGL_ContextType);
     REPORTER_ASSERT(reporter, configs[42]->asConfigGpu()->getColorType() == kRGBA_F16_SkColorType);
     REPORTER_ASSERT(reporter, configs[42]->asConfigGpu()->getColorSpace());
     REPORTER_ASSERT(reporter, configs[42]->asConfigGpu()->getColorSpace()->gammaIsLinear());
-    REPORTER_ASSERT(reporter, as_CSB(configs[42]->asConfigGpu()->getColorSpace())->toXYZD50() !=
-                    as_CSB(srgbColorSpace)->toXYZD50());
-    REPORTER_ASSERT(reporter, configs[33]->asConfigGpu()->getContextType() ==
-                              GrContextFactory::kGL_ContextType);
+    REPORTER_ASSERT(reporter, *as_CSB(configs[42]->asConfigGpu()->getColorSpace())->toXYZD50() !=
+                    *as_CSB(srgbColorSpace)->toXYZD50());
     REPORTER_ASSERT(reporter, configs[33]->asConfigGpu()->getUseInstanced());
     REPORTER_ASSERT(reporter, configs[34]->asConfigGpu()->getContextType() ==
                               GrContextFactory::kGL_ContextType);
index 107ab3ad15f224f455e4bb694ac4d5b7414f001b..9baada89e8ff2b07cd07b57622e9ff6891f7c0bc 100644 (file)
@@ -134,7 +134,6 @@ static const char configExtendedHelp[] =
     "\t    Options:\n"
     "\t\tsrgb\t\t\tsRGB gamut.\n"
     "\t\twide\t\t\tWide Gamut RGB.\n"
-    "\t\tnarrow\t\t\tNarrow Gamut RGB.\n"
     "\tdit\ttype: bool\tdefault: false.\n"
     "\t    Use device independent text.\n"
     "\tnvpr\ttype: bool\tdefault: false.\n"
@@ -284,7 +283,9 @@ static bool parse_option_gpu_color(const SkString& value,
     }
 
     // First, figure out color gamut that we'll work in (default to sRGB)
-    sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
+    const bool linearGamma = commands[0].equals("f16");
+    *outColorSpace = SkColorSpace::NewNamed(linearGamma ? SkColorSpace::kSRGBLinear_Named
+                                                        : SkColorSpace::kSRGB_Named);
     if (commands.count() == 2) {
         if (commands[1].equals("srgb")) {
             // sRGB gamut (which is our default)
@@ -297,8 +298,10 @@ static bool parse_option_gpu_color(const SkString& value,
             };
             SkMatrix44 wideGamutRGBMatrix(SkMatrix44::kUninitialized_Constructor);
             wideGamutRGBMatrix.set3x3RowMajorf(gWideGamutRGB_toXYZD50);
-            colorSpace = SkColorSpace::NewRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                              wideGamutRGBMatrix);
+            *outColorSpace = SkColorSpace::NewRGB(linearGamma
+                                                          ? SkColorSpace::kLinear_RenderTargetGamma
+                                                          : SkColorSpace::kSRGB_RenderTargetGamma,
+                                                  wideGamutRGBMatrix);
         } else if (commands[1].equals("narrow")) {
             // NarrowGamut RGB (an artifically smaller than sRGB gamut)
             SkColorSpacePrimaries primaries ={
@@ -309,8 +312,10 @@ static bool parse_option_gpu_color(const SkString& value,
             };
             SkMatrix44 narrowGamutRGBMatrix(SkMatrix44::kUninitialized_Constructor);
             primaries.toXYZD50(&narrowGamutRGBMatrix);
-            colorSpace = SkColorSpace::NewRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                              narrowGamutRGBMatrix);
+            *outColorSpace = SkColorSpace::NewRGB(linearGamma
+                                                          ? SkColorSpace::kLinear_RenderTargetGamma
+                                                          : SkColorSpace::kSRGB_RenderTargetGamma,
+                                                  narrowGamutRGBMatrix);
         } else {
             // Unknown color gamut
             return false;
@@ -320,12 +325,10 @@ static bool parse_option_gpu_color(const SkString& value,
     // Now pick a color type
     if (commands[0].equals("f16")) {
         *outColorType = kRGBA_F16_SkColorType;
-        *outColorSpace = as_CSB(colorSpace)->makeLinearGamma();
         return true;
     }
     if (commands[0].equals("srgb")) {
         *outColorType = kRGBA_8888_SkColorType;
-        *outColorSpace = colorSpace;
         return true;
     }
     return false;
index b45996bc8dc4a75827f3e45463c35d4316bf09c8..6d08ba213b5ec3062d490906d8102146c5ba2ebf 100644 (file)
@@ -141,13 +141,17 @@ int main(int argc, char** argv) {
     // Draw the sRGB gamut if requested.
     if (FLAGS_sRGB) {
         sk_sp<SkColorSpace> sRGBSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
-        draw_gamut(&canvas, as_CSB(sRGBSpace)->toXYZD50(), "sRGB", 0xFFFF9394, false);
+        const SkMatrix44* mat = as_CSB(sRGBSpace)->toXYZD50();
+        SkASSERT(mat);
+        draw_gamut(&canvas, *mat, "sRGB", 0xFFFF9394, false);
     }
 
     // Draw the Adobe RGB gamut if requested.
     if (FLAGS_adobeRGB) {
         sk_sp<SkColorSpace> adobeRGBSpace = SkColorSpace::NewNamed(SkColorSpace::kAdobeRGB_Named);
-        draw_gamut(&canvas, as_CSB(adobeRGBSpace)->toXYZD50(), "Adobe RGB", 0xFF31a9e1, false);
+        const SkMatrix44* mat = as_CSB(adobeRGBSpace)->toXYZD50();
+        SkASSERT(mat);
+        draw_gamut(&canvas, *mat, "Adobe RGB", 0xFF31a9e1, false);
     }
 
     // Draw gamut for the input image.
@@ -156,7 +160,9 @@ int main(int argc, char** argv) {
         SkDebugf("Image had no embedded color space information.  Defaulting to sRGB.\n");
         colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named);
     }
-    draw_gamut(&canvas, as_CSB(colorSpace)->toXYZD50(), input, 0xFF000000, true);
+    const SkMatrix44* mat = as_CSB(colorSpace)->toXYZD50();
+    SkASSERT(mat);
+    draw_gamut(&canvas, *mat, input, 0xFF000000, true);
 
     // Finally, encode the result to the output file.
     sk_sp<SkData> out(SkImageEncoder::EncodeData(gamut, SkImageEncoder::kPNG_Type, 100));