aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--bench/ColorCodecBench.cpp5
-rw-r--r--dm/DMSrcSink.cpp16
-rw-r--r--gm/gamut.cpp15
-rw-r--r--gm/labpcsdemo.cpp272
-rw-r--r--gyp/core.gypi4
-rw-r--r--resources/icc_profiles/srgb_lab_pcs.iccbin0 -> 60960 bytes
-rw-r--r--samplecode/SampleApp.cpp6
-rw-r--r--src/codec/SkJpegCodec.cpp5
-rw-r--r--src/core/SkColorSpace.cpp136
-rw-r--r--src/core/SkColorSpaceXform.cpp251
-rw-r--r--src/core/SkColorSpaceXform_Base.h16
-rw-r--r--src/core/SkColorSpace_A2B.cpp26
-rw-r--r--src/core/SkColorSpace_A2B.h102
-rw-r--r--src/core/SkColorSpace_Base.h49
-rw-r--r--src/core/SkColorSpace_ICC.cpp324
-rw-r--r--src/core/SkColorSpace_XYZ.cpp78
-rw-r--r--src/core/SkColorSpace_XYZ.h58
-rw-r--r--src/effects/gradients/SkGradientShader.cpp5
-rw-r--r--src/gpu/GrColorSpaceXform.cpp9
-rw-r--r--tests/CodecTest.cpp6
-rw-r--r--tests/ColorSpaceTest.cpp24
-rw-r--r--tests/ColorSpaceXformTest.cpp29
-rw-r--r--tests/SurfaceTest.cpp5
-rw-r--r--tests/TestConfigParsing.cpp22
-rw-r--r--tools/flags/SkCommonFlagsConfig.cpp19
-rw-r--r--tools/visualize_color_gamut.cpp12
26 files changed, 969 insertions, 525 deletions
diff --git a/bench/ColorCodecBench.cpp b/bench/ColorCodecBench.cpp
index 6aa46d609f..fe3cdd24b5 100644
--- a/bench/ColorCodecBench.cpp
+++ b/bench/ColorCodecBench.cpp
@@ -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) {
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index e777815baa..8b62864c8f 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -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) {
diff --git a/gm/gamut.cpp b/gm/gamut.cpp
index 707da29f64..3c64915561 100644
--- a/gm/gamut.cpp
+++ b/gm/gamut.cpp
@@ -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
index 0000000000..4bd9ed8140
--- /dev/null
+++ b/gm/labpcsdemo.cpp
@@ -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; )
diff --git a/gyp/core.gypi b/gyp/core.gypi
index a435164883..0488436b97 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -77,6 +77,10 @@
'<(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
index 0000000000..cfbd03e1f7
--- /dev/null
+++ b/resources/icc_profiles/srgb_lab_pcs.icc
Binary files differ
diff --git a/samplecode/SampleApp.cpp b/samplecode/SampleApp.cpp
index 4b4621698f..2194895e0d 100644
--- a/samplecode/SampleApp.cpp
+++ b/samplecode/SampleApp.cpp
@@ -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;
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
index 92ab2bc182..6863855d3b 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -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);
}
}
diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
index b956c7fe3e..e90254c969 100644
--- a/src/core/SkColorSpace.cpp
+++ b/src/core/SkColorSpace.cpp
@@ -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());
}
}
diff --git a/src/core/SkColorSpaceXform.cpp b/src/core/SkColorSpaceXform.cpp
index 5e362464b4..cacb38878c 100644
--- a/src/core/SkColorSpaceXform.cpp
+++ b/src/core/SkColorSpaceXform.cpp
@@ -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));
diff --git a/src/core/SkColorSpaceXform_Base.h b/src/core/SkColorSpaceXform_Base.h
index c1b9785424..a648677609 100644
--- a/src/core/SkColorSpaceXform_Base.h
+++ b/src/core/SkColorSpaceXform_Base.h
@@ -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
index 0000000000..f67da48259
--- /dev/null
+++ b/src/core/SkColorSpace_A2B.cpp
@@ -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
index 0000000000..0fc952bd6c
--- /dev/null
+++ b/src/core/SkColorSpace_A2B.h
@@ -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
diff --git a/src/core/SkColorSpace_Base.h b/src/core/SkColorSpace_Base.h
index a188a11dd1..d8470950c4 100644
--- a/src/core/SkColorSpace_Base.h
+++ b/src/core/SkColorSpace_Base.h
@@ -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;
diff --git a/src/core/SkColorSpace_ICC.cpp b/src/core/SkColorSpace_ICC.cpp
index b33e6b5ba1..df665ecd69 100644
--- a/src/core/SkColorSpace_ICC.cpp
+++ b/src/core/SkColorSpace_ICC.cpp
@@ -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
index 0000000000..1f62245d73
--- /dev/null
+++ b/src/core/SkColorSpace_XYZ.cpp
@@ -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
index 0000000000..72ec3f1b67
--- /dev/null
+++ b/src/core/SkColorSpace_XYZ.h
@@ -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
diff --git a/src/effects/gradients/SkGradientShader.cpp b/src/effects/gradients/SkGradientShader.cpp
index 03f03b20b1..6c99b37ad7 100644
--- a/src/effects/gradients/SkGradientShader.cpp
+++ b/src/effects/gradients/SkGradientShader.cpp
@@ -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();
}
}
diff --git a/src/gpu/GrColorSpaceXform.cpp b/src/gpu/GrColorSpaceXform.cpp
index d2270fafdd..ef96087147 100644
--- a/src/gpu/GrColorSpaceXform.cpp
+++ b/src/gpu/GrColorSpaceXform.cpp
@@ -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;
diff --git a/tests/CodecTest.cpp b/tests/CodecTest.cpp
index a6058a9c35..45455b4405 100644
--- a/tests/CodecTest.cpp
+++ b/tests/CodecTest.cpp
@@ -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);
diff --git a/tests/ColorSpaceTest.cpp b/tests/ColorSpaceTest.cpp
index aa0a33b407..04d401012d 100644
--- a/tests/ColorSpaceTest.cpp
+++ b/tests/ColorSpaceTest.cpp
@@ -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());
}
}
diff --git a/tests/ColorSpaceXformTest.cpp b/tests/ColorSpaceXformTest.cpp
index 6cf0dbef21..477e61acdd 100644
--- a/tests/ColorSpaceXformTest.cpp
+++ b/tests/ColorSpaceXformTest.cpp
@@ -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);
-}
diff --git a/tests/SurfaceTest.cpp b/tests/SurfaceTest.cpp
index 241ddb1dbc..180f2d985b 100644
--- a/tests/SurfaceTest.cpp
+++ b/tests/SurfaceTest.cpp
@@ -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 {
diff --git a/tests/TestConfigParsing.cpp b/tests/TestConfigParsing.cpp
index 412be73626..ec6d721908 100644
--- a/tests/TestConfigParsing.cpp
+++ b/tests/TestConfigParsing.cpp
@@ -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);
diff --git a/tools/flags/SkCommonFlagsConfig.cpp b/tools/flags/SkCommonFlagsConfig.cpp
index 107ab3ad15..9baada89e8 100644
--- a/tools/flags/SkCommonFlagsConfig.cpp
+++ b/tools/flags/SkCommonFlagsConfig.cpp
@@ -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;
diff --git a/tools/visualize_color_gamut.cpp b/tools/visualize_color_gamut.cpp
index b45996bc8d..6d08ba213b 100644
--- a/tools/visualize_color_gamut.cpp
+++ b/tools/visualize_color_gamut.cpp
@@ -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));