aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar raftias <raftias@google.com>2016-12-01 13:44:07 -0500
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2016-12-01 19:42:32 +0000
commit5476128f0a88217414f05e6a7ee518cdb411d026 (patch)
tree6b65531042d9af58a82ba8305a928edec17e0f29
parentb6d4e139b522821933d356ef7d2472816d25340a (diff)
Added CMYK support for ICC profiles.
Changed ICC parsing/SkGammas/SkColorLookUpTable to handle non-3-channel inputs. Parsed CMYK A2B ICC profiles. Integrated this with SkJpegCodec (the only file that supports CMYK) and SkColorSpaceXform_A2B to allow parsing and color xforming of ICC CMYK images. CQ_INCLUDE_TRYBOTS=skia.primary:Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-SKNX_NO_SIMD Change-Id: I11e3d17180244281be3eb43fd608609925a7f71e Reviewed-on: https://skia-review.googlesource.com/5444 Reviewed-by: Matt Sarett <msarett@google.com> Commit-Queue: Matt Sarett <msarett@google.com>
-rw-r--r--dm/DMSrcSink.cpp6
-rw-r--r--gm/labpcsdemo.cpp192
-rw-r--r--gn/gm.gni1
-rw-r--r--src/codec/SkJpegCodec.cpp19
-rw-r--r--src/core/SkColorLookUpTable.cpp51
-rw-r--r--src/core/SkColorLookUpTable.h36
-rw-r--r--src/core/SkColorSpace.cpp13
-rw-r--r--src/core/SkColorSpaceXform_A2B.cpp81
-rw-r--r--src/core/SkColorSpaceXform_A2B.h12
-rw-r--r--src/core/SkColorSpace_A2B.cpp7
-rw-r--r--src/core/SkColorSpace_A2B.h55
-rw-r--r--src/core/SkColorSpace_Base.h61
-rw-r--r--src/core/SkColorSpace_ICC.cpp604
-rw-r--r--src/core/SkColorSpace_XYZ.cpp5
-rw-r--r--src/core/SkRasterPipeline.h3
-rw-r--r--src/opts/SkRasterPipeline_opts.h23
-rw-r--r--tests/ColorSpaceXformTest.cpp101
17 files changed, 652 insertions, 618 deletions
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 0a54702d08..31a3bc7059 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -1018,11 +1018,7 @@ 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) {
- // FIXME (raftias):
- // This should be a fatal error. We need to add support for
- // A2B images in SkColorSpaceXform.
- return Error::Nonfatal(SkStringPrintf("Couldn't getPixels %s. Error code %d",
- fPath.c_str(), r));
+ return SkStringPrintf("Couldn't getPixels %s. Error code %d", fPath.c_str(), r);
}
switch (fMode) {
diff --git a/gm/labpcsdemo.cpp b/gm/labpcsdemo.cpp
deleted file mode 100644
index 26e48a8890..0000000000
--- a/gm/labpcsdemo.cpp
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * 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"
-
-/**
- * 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::MakeICC(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());
- const SkColorLookUpTable* colorLUT = nullptr;
- bool printConversions = false;
- // We're skipping evaluating the TRCs and the matrix here since they aren't
- // in the ICC profile initially used here.
- for (int e = 0; e < cs.count(); ++e) {
- switch (cs.element(e).type()) {
- case SkColorSpace_A2B::Element::Type::kGammaNamed:
- SkASSERT(kLinear_SkGammaNamed == cs.element(e).gammaNamed());
- break;
- case SkColorSpace_A2B::Element::Type::kGammas:
- SkASSERT(false);
- break;
- case SkColorSpace_A2B::Element::Type::kCLUT:
- colorLUT = &cs.element(e).colorLUT();
- break;
- case SkColorSpace_A2B::Element::Type::kMatrix:
- SkASSERT(cs.element(e).matrix().isIdentity());
- break;
- }
- }
- SkASSERT(colorLUT);
- 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 };
-
- colorLUT->interp3D(lab, lab);
-
- // 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/gn/gm.gni b/gn/gm.gni
index 66bc3aa1ae..41a75ff268 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -172,7 +172,6 @@ gm_sources = [
"$_gm/imagetoyuvplanes.cpp",
"$_gm/internal_links.cpp",
"$_gm/inversepaths.cpp",
- "$_gm/labpcsdemo.cpp",
"$_gm/largeglyphblur.cpp",
"$_gm/lattice.cpp",
"$_gm/lcdblendmodes.cpp",
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
index 69b80273df..7a264d5d99 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -11,6 +11,7 @@
#include "SkJpegDecoderMgr.h"
#include "SkCodecPriv.h"
#include "SkColorPriv.h"
+#include "SkColorSpace_Base.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkTypes.h"
@@ -229,7 +230,18 @@ bool SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut,
sk_sp<SkData> iccData = get_icc_profile(decoderMgr->dinfo());
sk_sp<SkColorSpace> colorSpace = nullptr;
if (iccData) {
- colorSpace = SkColorSpace::MakeICC(iccData->data(), iccData->size());
+ SkColorSpace_Base::InputColorFormat inputColorFormat =
+ SkColorSpace_Base::InputColorFormat::kRGB;
+ switch (decoderMgr->dinfo()->jpeg_color_space) {
+ case JCS_CMYK:
+ case JCS_YCCK:
+ inputColorFormat = SkColorSpace_Base::InputColorFormat::kCMYK;
+ break;
+ default:
+ break;
+ }
+ colorSpace = SkColorSpace_Base::MakeICC(iccData->data(), iccData->size(),
+ inputColorFormat);
if (!colorSpace) {
SkCodecPrintf("Could not create SkColorSpace from ICC data.\n");
}
@@ -368,9 +380,6 @@ bool SkJpegCodec::setOutputColorSpace(const SkImageInfo& dstInfo) {
// we must do it ourselves.
J_COLOR_SPACE encodedColorType = fDecoderMgr->dinfo()->jpeg_color_space;
bool isCMYK = (JCS_CMYK == encodedColorType || JCS_YCCK == encodedColorType);
- if (isCMYK && this->colorXform()) {
- return false;
- }
// Check for valid color types and set the output color space
switch (dstInfo.colorType()) {
@@ -569,7 +578,7 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo,
SkASSERT(1 == dinfo->rec_outbuf_height);
J_COLOR_SPACE colorSpace = dinfo->out_color_space;
- if (JCS_CMYK == colorSpace) {
+ if (JCS_CMYK == colorSpace && nullptr == this->colorXform()) {
this->initializeSwizzler(dstInfo, options);
}
diff --git a/src/core/SkColorLookUpTable.cpp b/src/core/SkColorLookUpTable.cpp
index eb832b3214..8a550182f7 100644
--- a/src/core/SkColorLookUpTable.cpp
+++ b/src/core/SkColorLookUpTable.cpp
@@ -8,7 +8,23 @@
#include "SkColorLookUpTable.h"
#include "SkFloatingPoint.h"
-void SkColorLookUpTable::interp3D(float dst[3], float src[3]) const {
+void SkColorLookUpTable::interp(float* dst, const float* src) const {
+ if (fInputChannels == 3) {
+ interp3D(dst, src);
+ } else {
+ SkASSERT(dst != src);
+ // index gets initialized as the algorithm proceeds by interpDimension.
+ // It's just there to store the choice of low/high so far.
+ int index[kMaxColorChannels];
+ for (uint8_t outputDimension = 0; outputDimension < kOutputChannels; ++outputDimension) {
+ dst[outputDimension] = interpDimension(src, fInputChannels - 1, outputDimension,
+ index);
+ }
+ }
+}
+
+void SkColorLookUpTable::interp3D(float* dst, const float* src) const {
+ SkASSERT(3 == kOutputChannels);
// Call the src components x, y, and z.
const uint8_t maxX = fGridPoints[0] - 1;
const uint8_t maxY = fGridPoints[1] - 1;
@@ -111,3 +127,36 @@ void SkColorLookUpTable::interp3D(float dst[3], float src[3]) const {
ptr++;
}
}
+
+float SkColorLookUpTable::interpDimension(const float* src, int inputDimension,
+ int outputDimension,
+ int index[kMaxColorChannels]) const {
+ // Base case. We've already decided whether to use the low or high point for each dimension
+ // which is stored inside of index[] where index[i] gives the point in the CLUT to use for
+ // input dimension i.
+ if (inputDimension < 0) {
+ // compute index into CLUT and look up the colour
+ int outputIndex = outputDimension;
+ int indexMultiplier = kOutputChannels;
+ for (int i = fInputChannels - 1; i >= 0; --i) {
+ outputIndex += index[i] * indexMultiplier;
+ indexMultiplier *= fGridPoints[i];
+ }
+ return table()[outputIndex];
+ }
+ // for each dimension (input channel), try both the low and high point for it
+ // and then do the same recursively for the later dimensions.
+ // Finally, we need to LERP the results. ie LERP X then LERP Y then LERP Z.
+ const float x = src[inputDimension] * (fGridPoints[inputDimension] - 1);
+ // try the low point for this dimension
+ index[inputDimension] = sk_float_floor2int(x);
+ const float diff = x - index[inputDimension];
+ // and recursively LERP all sub-dimensions with the current dimension fixed to the low point
+ const float lo = interpDimension(src, inputDimension - 1, outputDimension, index);
+ // now try the high point for this dimension
+ index[inputDimension] = sk_float_ceil2int(x);
+ // and recursively LERP all sub-dimensions with the current dimension fixed to the high point
+ const float hi = interpDimension(src, inputDimension - 1, outputDimension, index);
+ // then LERP the results based on the current dimension
+ return (1 - diff) * lo + diff * hi;
+}
diff --git a/src/core/SkColorLookUpTable.h b/src/core/SkColorLookUpTable.h
index 020a953700..e563b2d4a2 100644
--- a/src/core/SkColorLookUpTable.h
+++ b/src/core/SkColorLookUpTable.h
@@ -11,25 +11,49 @@
#include "SkRefCnt.h"
#include "SkTemplates.h"
+static constexpr uint8_t kMaxColorChannels = 4;
+
class SkColorLookUpTable : public SkRefCnt {
public:
static constexpr uint8_t kOutputChannels = 3;
- SkColorLookUpTable(uint8_t inputChannels, const uint8_t gridPoints[3]) {
- SkASSERT(3 == inputChannels);
- memcpy(fGridPoints, gridPoints, 3 * sizeof(uint8_t));
+ SkColorLookUpTable(uint8_t inputChannels, const uint8_t gridPoints[kMaxColorChannels])
+ : fInputChannels(inputChannels) {
+ SkASSERT(inputChannels >= 1 && inputChannels <= kMaxColorChannels);
+ memcpy(fGridPoints, gridPoints, fInputChannels * sizeof(uint8_t));
}
- void interp3D(float dst[3], float src[3]) const;
+ /**
+ * If fInputChannels == kOutputChannels == 3, performs tetrahedral interpolation, otherwise
+ * performs multilinear interpolation (ie LERP for n =1, bilinear for n=2, trilinear for n=3)
+ * with fInputChannels input dimensions and kOutputChannels output dimensions.
+ * |dst| can be |src| only when fInputChannels == kOutputChannels == 3
+ * |dst| is the destination pixel, must have at least kOutputChannels elements.
+ * |src| is the source pixel, must have at least fInputChannels elements.
+ */
+ void interp(float* dst, const float* src) const;
+
+ int inputChannels() const { return fInputChannels; }
+
+ int outputChannels() const { return kOutputChannels; }
private:
const float* table() const {
return SkTAddOffset<const float>(this, sizeof(SkColorLookUpTable));
}
- uint8_t fGridPoints[3];
+ /**
+ * Performs tetrahedral interpolation with 3 input and 3 output dimensions.
+ * |dst| can be |src|
+ */
+ void interp3D(float* dst, const float* src) const;
+
+ // recursively LERPs one dimension at a time. Used by interp() for the general case
+ float interpDimension(const float* src, int inputDimension, int outputDimension,
+ int index[kMaxColorChannels]) const;
- friend class SkColorSpaceXform_A2B;
+ uint8_t fInputChannels;
+ uint8_t fGridPoints[kMaxColorChannels];
public:
// Objects of this type are created in a custom fashion using sk_malloc_throw
diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
index c09387bbad..e9db37a810 100644
--- a/src/core/SkColorSpace.cpp
+++ b/src/core/SkColorSpace.cpp
@@ -178,18 +178,15 @@ sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const SkColorSpaceTransferFn& coeffs,
}
void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn));
- sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas());
+ sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(3));
SkColorSpaceTransferFn* fn = SkTAddOffset<SkColorSpaceTransferFn>(memory, sizeof(SkGammas));
*fn = coeffs;
- gammas->fRedType = SkGammas::Type::kParam_Type;
- gammas->fGreenType = SkGammas::Type::kParam_Type;
- gammas->fBlueType = SkGammas::Type::kParam_Type;
-
SkGammas::Data data;
data.fParamOffset = 0;
- gammas->fRedData = data;
- gammas->fGreenData = data;
- gammas->fBlueData = data;
+ for (int channel = 0; channel < 3; ++channel) {
+ gammas->fType[channel] = SkGammas::Type::kParam_Type;
+ gammas->fData[channel] = data;
+ }
return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(kNonStandard_SkGammaNamed,
std::move(gammas), toXYZD50, nullptr));
}
diff --git a/src/core/SkColorSpaceXform_A2B.cpp b/src/core/SkColorSpaceXform_A2B.cpp
index f2f694349f..b0495cfef2 100644
--- a/src/core/SkColorSpaceXform_A2B.cpp
+++ b/src/core/SkColorSpaceXform_A2B.cpp
@@ -169,16 +169,33 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
"None", "Named", "Value", "Table", "Param"
};
#endif
+ int currentChannels = -1;
+ switch (srcSpace->inputColorFormat()) {
+ case SkColorSpace_Base::InputColorFormat::kRGB:
+ currentChannels = 3;
+ break;
+ case SkColorSpace_Base::InputColorFormat::kCMYK:
+ currentChannels = 4;
+ // CMYK images from JPEGs (the only format that supports it) are actually
+ // inverted CMYK, so we need to invert every channel.
+ // TransferFn is y = -x + 1 for x < 1.f, otherwise 0x + 0, ie y = 1 - x for x in [0,1]
+ this->addTransferFns({1.f, 0.f, 0.f, 0.f, 1.f, -1.f, 1.f}, 4);
+ break;
+ default:
+ SkASSERT(false);
+ }
// add in all input color space -> PCS xforms
for (int i = 0; i < srcSpace->count(); ++i) {
const SkColorSpace_A2B::Element& e = srcSpace->element(i);
+ SkASSERT(e.inputChannels() == currentChannels);
+ currentChannels = e.outputChannels();
switch (e.type()) {
case SkColorSpace_A2B::Element::Type::kGammaNamed:
if (kLinear_SkGammaNamed != e.gammaNamed()) {
SkCSXformPrintf("Gamma stage added: %s\n",
debugGammaNamed[(int)e.gammaNamed()]);
SkColorSpaceTransferFn fn = gammanamed_to_parametric(e.gammaNamed());
- this->addTransferFn(fn, kRGB_Channels);
+ this->addTransferFns(fn, currentChannels);
fElementsPipeline.append(SkRasterPipeline::clamp_0);
fElementsPipeline.append(SkRasterPipeline::clamp_1);
@@ -187,23 +204,23 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
case SkColorSpace_A2B::Element::Type::kGammas: {
const SkGammas& gammas = e.gammas();
SkCSXformPrintf("Gamma stage added:");
- for (int channel = 0; channel < 3; ++channel) {
+ for (int channel = 0; channel < gammas.channels(); ++channel) {
SkCSXformPrintf(" %s", debugGammas[(int)gammas.type(channel)]);
}
SkCSXformPrintf("\n");
bool gammaNeedsRef = false;
- for (int channel = 0; channel < 3; ++channel) {
+ for (int channel = 0; channel < gammas.channels(); ++channel) {
if (SkGammas::Type::kTable_Type == gammas.type(channel)) {
SkTableTransferFn table = {
gammas.table(channel),
gammas.data(channel).fTable.fSize,
};
- this->addTableFn(table, static_cast<Channels>(channel));
+ this->addTableFn(table, channel);
gammaNeedsRef = true;
} else {
SkColorSpaceTransferFn fn = gamma_to_parametric(gammas, channel);
- this->addTransferFn(fn, static_cast<Channels>(channel));
+ this->addTransferFn(fn, channel);
}
}
if (gammaNeedsRef) {
@@ -215,8 +232,8 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
break;
}
case SkColorSpace_A2B::Element::Type::kCLUT:
- SkCSXformPrintf("CLUT stage added [%d][%d][%d]\n", e.colorLUT().fGridPoints[0],
- e.colorLUT().fGridPoints[1], e.colorLUT().fGridPoints[2]);
+ SkCSXformPrintf("CLUT (%d -> %d) stage added\n", e.colorLUT().inputChannels(),
+ e.colorLUT().outputChannels());
fCLUTs.push_back(sk_ref_sp(&e.colorLUT()));
fElementsPipeline.append(SkRasterPipeline::color_lookup_table,
fCLUTs.back().get());
@@ -230,6 +247,8 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
}
}
+ SkASSERT(3 == currentChannels);
+
// Lab PCS -> XYZ PCS
if (SkColorSpace_A2B::PCS::kLAB == srcSpace->pcs()) {
SkCSXformPrintf("Lab -> XYZ element added\n");
@@ -245,7 +264,7 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
if (!fLinearDstGamma) {
SkColorSpaceTransferFn fn =
invert_parametric(gammanamed_to_parametric(dstSpace->gammaNamed()));
- this->addTransferFn(fn, kRGB_Channels);
+ this->addTransferFns(fn, 3);
fElementsPipeline.append(SkRasterPipeline::clamp_0);
fElementsPipeline.append(SkRasterPipeline::clamp_1);
}
@@ -261,10 +280,10 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
};
fTableStorage.push_front(std::move(storage));
- this->addTableFn(table, static_cast<Channels>(channel));
+ this->addTableFn(table, channel);
} else {
SkColorSpaceTransferFn fn = invert_parametric(gamma_to_parametric(gammas, channel));
- this->addTransferFn(fn, static_cast<Channels>(channel));
+ this->addTransferFn(fn, channel);
}
}
@@ -273,45 +292,47 @@ SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
}
}
-void SkColorSpaceXform_A2B::addTransferFn(const SkColorSpaceTransferFn& fn, Channels channels) {
+void SkColorSpaceXform_A2B::addTransferFns(const SkColorSpaceTransferFn& fn, int channelCount) {
+ for (int i = 0; i < channelCount; ++i) {
+ this->addTransferFn(fn, i);
+ }
+}
+
+void SkColorSpaceXform_A2B::addTransferFn(const SkColorSpaceTransferFn& fn, int channelIndex) {
fTransferFns.push_front(fn);
- switch (channels) {
- case kRGB_Channels:
- fElementsPipeline.append(SkRasterPipeline::parametric_r, &fTransferFns.front());
- fElementsPipeline.append(SkRasterPipeline::parametric_g, &fTransferFns.front());
- fElementsPipeline.append(SkRasterPipeline::parametric_b, &fTransferFns.front());
- break;
- case kR_Channels:
+ switch (channelIndex) {
+ case 0:
fElementsPipeline.append(SkRasterPipeline::parametric_r, &fTransferFns.front());
break;
- case kG_Channels:
+ case 1:
fElementsPipeline.append(SkRasterPipeline::parametric_g, &fTransferFns.front());
break;
- case kB_Channels:
+ case 2:
fElementsPipeline.append(SkRasterPipeline::parametric_b, &fTransferFns.front());
break;
+ case 3:
+ fElementsPipeline.append(SkRasterPipeline::parametric_a, &fTransferFns.front());
+ break;
default:
SkASSERT(false);
}
}
-void SkColorSpaceXform_A2B::addTableFn(const SkTableTransferFn& fn, Channels channels) {
+void SkColorSpaceXform_A2B::addTableFn(const SkTableTransferFn& fn, int channelIndex) {
fTableTransferFns.push_front(fn);
- switch (channels) {
- case kRGB_Channels:
- fElementsPipeline.append(SkRasterPipeline::table_r, &fTableTransferFns.front());
- fElementsPipeline.append(SkRasterPipeline::table_g, &fTableTransferFns.front());
- fElementsPipeline.append(SkRasterPipeline::table_b, &fTableTransferFns.front());
- break;
- case kR_Channels:
+ switch (channelIndex) {
+ case 0:
fElementsPipeline.append(SkRasterPipeline::table_r, &fTableTransferFns.front());
break;
- case kG_Channels:
+ case 1:
fElementsPipeline.append(SkRasterPipeline::table_g, &fTableTransferFns.front());
break;
- case kB_Channels:
+ case 2:
fElementsPipeline.append(SkRasterPipeline::table_b, &fTableTransferFns.front());
break;
+ case 3:
+ fElementsPipeline.append(SkRasterPipeline::table_a, &fTableTransferFns.front());
+ break;
default:
SkASSERT(false);
}
diff --git a/src/core/SkColorSpaceXform_A2B.h b/src/core/SkColorSpaceXform_A2B.h
index 6beda285e1..9376491fa1 100644
--- a/src/core/SkColorSpaceXform_A2B.h
+++ b/src/core/SkColorSpaceXform_A2B.h
@@ -32,17 +32,11 @@ public:
private:
SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace, SkColorSpace_XYZ* dstSpace);
- enum Channels {
- kRGB_Channels = -1,
- kR_Channels = 0,
- kG_Channels = 1,
- kB_Channels = 2
- };
+ void addTransferFns(const SkColorSpaceTransferFn& fn, int channelCount);
+ void addTransferFn(const SkColorSpaceTransferFn& fn, int channelIndex);
-
- void addTransferFn(const SkColorSpaceTransferFn& fn, Channels channels);
- void addTableFn(const SkTableTransferFn& table, Channels channels);
+ void addTableFn(const SkTableTransferFn& table, int channelIndex);
void addMatrix(const SkMatrix44& matrix);
diff --git a/src/core/SkColorSpace_A2B.cpp b/src/core/SkColorSpace_A2B.cpp
index 9fed82379a..1fef71a617 100644
--- a/src/core/SkColorSpace_A2B.cpp
+++ b/src/core/SkColorSpace_A2B.cpp
@@ -7,9 +7,10 @@
#include "SkColorSpace_A2B.h"
-SkColorSpace_A2B::SkColorSpace_A2B(PCS pcs, sk_sp<SkData> profileData,
- std::vector<Element> elements)
+SkColorSpace_A2B::SkColorSpace_A2B(InputColorFormat inputColorFormat, std::vector<Element> elements,
+ PCS pcs, sk_sp<SkData> profileData)
: INHERITED(std::move(profileData))
- , fPCS(pcs)
+ , fInputColorFormat(inputColorFormat)
, fElements(std::move(elements))
+ , fPCS(pcs)
{}
diff --git a/src/core/SkColorSpace_A2B.h b/src/core/SkColorSpace_A2B.h
index 2fb7a83cab..b42de77137 100644
--- a/src/core/SkColorSpace_A2B.h
+++ b/src/core/SkColorSpace_A2B.h
@@ -16,14 +16,15 @@
// 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.
+// matrix operations, as well as have multiple TRC functions. The CLUT also
+// allows conversion between non-3-channel input color spaces ie CMYK(4) to
+// a workable PCS (ie XYZ).
//
-// Currently AtoBType A2B0 tag types are supported. There are also lut8Type,
-// lut16Type and MPET (multi-processing-elements) A2B0 tags which allow you to
-// combine these 3 primitives (TRC, CLUT, matrix) in any order/quantitiy,
-// but support for that is not implemented.
+// AtoBType, lut8Type and lut16Type A2B0 tag types are supported. There are
+// also MPET (multi-processing-elements) A2B0 tags in the standard which allow
+// you to combine these 3 primitives (TRC, CLUT, matrix) in any order/quantity.
+// MPET tags are currently unsupported by the MakeICC parser, could be supported
+// here by the nature of the design.
class SkColorSpace_A2B : public SkColorSpace_Base {
public:
const SkMatrix44* toXYZD50() const override {
@@ -45,12 +46,12 @@ public:
// 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;
@@ -71,29 +72,37 @@ public:
class Element {
public:
- explicit Element(SkGammaNamed gammaNamed)
+ Element(SkGammaNamed gammaNamed, int channelCount)
: fType(Type::kGammaNamed)
, fGammaNamed(gammaNamed)
, fMatrix(SkMatrix44::kUninitialized_Constructor)
+ , fInputChannels(channelCount)
+ , fOutputChannels(channelCount)
{}
explicit Element(sk_sp<SkGammas> gammas)
: fType(Type::kGammas)
, fGammas(std::move(gammas))
- , fMatrix(SkMatrix44::kUninitialized_Constructor)
+ , fMatrix(SkMatrix44::kUninitialized_Constructor)
+ , fInputChannels(fGammas->channels())
+ , fOutputChannels(fGammas->channels())
{}
explicit Element(sk_sp<SkColorLookUpTable> colorLUT)
: fType(Type::kCLUT)
, fCLUT(std::move(colorLUT))
, fMatrix(SkMatrix44::kUninitialized_Constructor)
+ , fInputChannels(fCLUT->inputChannels())
+ , fOutputChannels(fCLUT->outputChannels())
{}
explicit Element(const SkMatrix44& matrix)
: fType(Type::kMatrix)
, fMatrix(matrix)
+ , fInputChannels(3)
+ , fOutputChannels(3)
{}
-
+
enum class Type {
kGammaNamed,
kGammas,
@@ -123,15 +132,21 @@ public:
return fMatrix;
}
+ int inputChannels() const { return fInputChannels; }
+
+ int outputChannels() const { return fOutputChannels; }
+
private:
Type fType;
SkGammaNamed fGammaNamed;
sk_sp<SkGammas> fGammas;
sk_sp<SkColorLookUpTable> fCLUT;
SkMatrix44 fMatrix;
+ int fInputChannels;
+ int fOutputChannels;
};
- const Element& element(size_t i) const { return fElements[i]; }
-
+ const Element& element(int i) const { return fElements[i]; }
+
int count() const { return (int)fElements.size(); }
// the intermediate profile connection space that this color space
@@ -140,16 +155,20 @@ public:
kLAB, // CIELAB
kXYZ // CIEXYZ
};
-
+
PCS pcs() const { return fPCS; }
+ InputColorFormat inputColorFormat() const { return fInputColorFormat; }
+
private:
- SkColorSpace_A2B(PCS pcs, sk_sp<SkData> profileData, std::vector<Element> elements);
+ SkColorSpace_A2B(InputColorFormat inputColorFormat, std::vector<Element> elements, PCS pcs,
+ sk_sp<SkData> profileData);
- PCS fPCS;
+ InputColorFormat fInputColorFormat;
std::vector<Element> fElements;
+ PCS fPCS;
- friend class SkColorSpace;
+ friend class SkColorSpace_Base;
friend class ColorSpaceXformTest;
typedef SkColorSpace_Base INHERITED;
};
diff --git a/src/core/SkColorSpace_Base.h b/src/core/SkColorSpace_Base.h
index 480febd7ff..947f725036 100644
--- a/src/core/SkColorSpace_Base.h
+++ b/src/core/SkColorSpace_Base.h
@@ -83,17 +83,8 @@ struct SkGammas : SkRefCnt {
}
const Data& data(int i) const {
- switch (i) {
- case 0:
- return fRedData;
- case 1:
- return fGreenData;
- case 2:
- return fBlueData;
- default:
- SkASSERT(false);
- return fRedData;
- }
+ SkASSERT(i >= 0 && i < fChannels);
+ return fData[i];
}
const float* table(int i) const {
@@ -107,32 +98,24 @@ struct SkGammas : SkRefCnt {
}
Type type(int i) const {
- switch (i) {
- case 0:
- return fRedType;
- case 1:
- return fGreenType;
- case 2:
- return fBlueType;
- default:
- SkASSERT(false);
- return fRedType;
- }
+ SkASSERT(i >= 0 && i < fChannels);
+ return fType[i];
}
+
+ uint8_t channels() const { return fChannels; }
- SkGammas()
- : fRedType(Type::kNone_Type)
- , fGreenType(Type::kNone_Type)
- , fBlueType(Type::kNone_Type)
- {}
+ SkGammas(uint8_t channels)
+ : fChannels(channels) {
+ SkASSERT(channels <= kMaxColorChannels);
+ for (uint8_t i = 0; i < kMaxColorChannels; ++i) {
+ fType[i] = Type::kNone_Type;
+ }
+ }
// These fields should only be modified when initializing the struct.
- Data fRedData;
- Data fGreenData;
- Data fBlueData;
- Type fRedType;
- Type fGreenType;
- Type fBlueType;
+ uint8_t fChannels;
+ Data fData[kMaxColorChannels];
+ Type fType[kMaxColorChannels];
// Objects of this type are sometimes created in a custom fashion using
// sk_malloc_throw and therefore must be sk_freed. We overload new to
@@ -188,9 +171,17 @@ public:
kXYZ,
kA2B
};
-
+
virtual Type type() const = 0;
-
+
+ enum class InputColorFormat {
+ kRGB,
+ kCMYK
+ };
+
+ static sk_sp<SkColorSpace> MakeICC(const void* input, size_t len,
+ InputColorFormat inputColorFormat);
+
protected:
SkColorSpace_Base(sk_sp<SkData> profileData);
diff --git a/src/core/SkColorSpace_ICC.cpp b/src/core/SkColorSpace_ICC.cpp
index 5fe066ac11..ea28c90612 100644
--- a/src/core/SkColorSpace_ICC.cpp
+++ b/src/core/SkColorSpace_ICC.cpp
@@ -49,6 +49,7 @@ static constexpr size_t kICCHeaderSize = 132;
static constexpr size_t kICCTagTableEntrySize = 12;
static constexpr uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' ');
+static constexpr uint32_t kCMYK_ColorSpace = SkSetFourByteTag('C', 'M', 'Y', 'K');
static constexpr uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r');
static constexpr uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r');
static constexpr uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r');
@@ -127,9 +128,19 @@ struct ICCProfileHeader {
fProfileClass == kColorSpace_Profile,
"Unsupported profile");
- // TODO (msarett):
- // All the profiles we've tested so far use RGB as the input color space.
- return_if_false(fInputColorSpace == kRGB_ColorSpace, "Unsupported color space");
+ switch (fInputColorSpace) {
+ case kRGB_ColorSpace:
+ SkColorSpacePrintf("RGB Input Color Space");
+ break;
+ case kCMYK_ColorSpace:
+ SkColorSpacePrintf("CMYK Input Color Space\n");
+ break;
+ default:
+ SkColorSpacePrintf("Unsupported Input Color Space: %c%c%c%c\n",
+ (fInputColorSpace>>24)&0xFF, (fInputColorSpace>>16)&0xFF,
+ (fInputColorSpace>> 8)&0xFF, (fInputColorSpace>> 0)&0xFF);
+ return false;
+ }
switch (fPCS) {
case kXYZ_PCSSpace:
@@ -140,7 +151,9 @@ struct ICCProfileHeader {
break;
default:
// ICC currently (V4.3) only specifices XYZ and Lab PCS spaces
- SkColorSpacePrintf("Unsupported PCS space\n");
+ SkColorSpacePrintf("Unsupported PCS space: %c%c%c%c\n",
+ (fPCS>>24)&0xFF, (fPCS>>16)&0xFF,
+ (fPCS>> 8)&0xFF, (fPCS>> 0)&0xFF);
return false;
}
@@ -616,7 +629,6 @@ static bool load_color_lut(sk_sp<SkColorLookUpTable>* colorLUT, uint32_t inputCh
return false;
}
- SkASSERT(3 == inputChannels);
uint32_t numEntries = SkColorLookUpTable::kOutputChannels;
for (uint32_t i = 0; i < inputChannels; i++) {
if (0 == gridPoints[i]) {
@@ -735,14 +747,12 @@ static bool load_matrix(SkMatrix44* matrix, const uint8_t* src, size_t len, bool
}
static inline SkGammaNamed is_named(const sk_sp<SkGammas>& gammas) {
- if (gammas->isNamed(0) && gammas->isNamed(1) && gammas->isNamed(2) &&
- gammas->fRedData.fNamed == gammas->fGreenData.fNamed &&
- gammas->fRedData.fNamed == gammas->fBlueData.fNamed)
- {
- return gammas->fRedData.fNamed;
+ for (uint8_t i = 0; i < gammas->channels(); ++i) {
+ if (!gammas->isNamed(i) || gammas->data(i).fNamed != gammas->data(0).fNamed) {
+ return kNonStandard_SkGammaNamed;
+ }
}
-
- return kNonStandard_SkGammaNamed;
+ return gammas->data(0).fNamed;
}
/**
@@ -752,93 +762,87 @@ static inline SkGammaNamed is_named(const sk_sp<SkGammas>& gammas) {
* 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
+ * @param gammaNamed Out-variable. The named gamma curve.
+ * @param gammas Out-variable. The stored gamma curve information. Can be null if
+ * gammaNamed is a named curve
+ * @param inputChannels The number of gamma input channels
+ * @param rTagPtr Pointer to start of the gamma tag.
+ * @param taglen The size in bytes of the tag
*
- * @return false on failure, true on success
+ * @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;
+ uint8_t inputChannels, const uint8_t* tagSrc, size_t tagLen) {
+ SkGammas::Data data[kMaxColorChannels];
+ SkColorSpaceTransferFn params[kMaxColorChannels];
+ SkGammas::Type type[kMaxColorChannels];
+ const uint8_t* tagPtr[kMaxColorChannels];
+
+ tagPtr[0] = tagSrc;
*gammaNamed = kNonStandard_SkGammaNamed;
// On an invalid first gamma, tagBytes remains set as zero. This causes the two
// subsequent to be treated as identical (which is what we want).
size_t tagBytes = 0;
- SkGammas::Type rType = parse_gamma(&rData, &rParams, &tagBytes, rTagPtr, tagLen);
- handle_invalid_gamma(&rType, &rData);
+ type[0] = parse_gamma(&data[0], &params[0], &tagBytes, tagPtr[0], tagLen);
+ handle_invalid_gamma(&type[0], &data[0]);
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;
+ bool allChannelsSame = false;
+ if (inputChannels * alignedTagBytes <= tagLen) {
+ allChannelsSame = true;
+ for (uint8_t i = 1; i < inputChannels; ++i) {
+ if (0 != memcmp(tagSrc, tagSrc + i * alignedTagBytes, tagBytes)) {
+ allChannelsSame = false;
+ break;
+ }
+ }
+ }
+ if (allChannelsSame) {
+ if (SkGammas::Type::kNamed_Type == type[0]) {
+ *gammaNamed = data[0].fNamed;
} else {
size_t allocSize = sizeof(SkGammas);
- return_if_false(safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize),
+ return_if_false(safe_add(allocSize, gamma_alloc_size(type[0], data[0]), &allocSize),
"SkGammas struct is too large to allocate");
void* memory = sk_malloc_throw(allocSize);
- *gammas = sk_sp<SkGammas>(new (memory) SkGammas());
- load_gammas(memory, 0, rType, &rData, rParams, rTagPtr);
-
- (*gammas)->fRedType = rType;
- (*gammas)->fGreenType = rType;
- (*gammas)->fBlueType = rType;
+ *gammas = sk_sp<SkGammas>(new (memory) SkGammas(inputChannels));
+ load_gammas(memory, 0, type[0], &data[0], params[0], tagPtr[0]);
- (*gammas)->fRedData = rData;
- (*gammas)->fGreenData = rData;
- (*gammas)->fBlueData = rData;
+ for (uint8_t channel = 0; channel < inputChannels; ++channel) {
+ (*gammas)->fType[channel] = type[0];
+ (*gammas)->fData[channel] = data[0];
+ }
}
} 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);
+ for (uint8_t channel = 1; channel < inputChannels; ++channel) {
+ tagPtr[channel] = tagPtr[channel - 1] + alignedTagBytes;
+ tagLen = tagLen > alignedTagBytes ? tagLen - alignedTagBytes : 0;
+ tagBytes = 0;
+ type[channel] = parse_gamma(&data[channel], &params[channel], &tagBytes,
+ tagPtr[channel], tagLen);
+ handle_invalid_gamma(&type[channel], &data[channel]);
+ alignedTagBytes = SkAlign4(tagBytes);
+ }
size_t allocSize = sizeof(SkGammas);
- 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");
+ for (uint8_t channel = 0; channel < inputChannels; ++channel) {
+ return_if_false(safe_add(allocSize, gamma_alloc_size(type[channel], data[channel]),
+ &allocSize),
+ "SkGammas struct is too large to allocate");
+ }
void* memory = sk_malloc_throw(allocSize);
- *gammas = sk_sp<SkGammas>(new (memory) SkGammas());
+ *gammas = sk_sp<SkGammas>(new (memory) SkGammas(inputChannels));
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);
+ for (uint8_t channel = 0; channel < inputChannels; ++channel) {
+ (*gammas)->fType[channel] = type[channel];
+ offset += load_gammas(memory,offset, type[channel], &data[channel], params[channel],
+ tagPtr[channel]);
+ (*gammas)->fData[channel] = data[channel];
- (*gammas)->fBlueType = bType;
- load_gammas(memory, offset, bType, &bData, bParams, bTagPtr);
-
- (*gammas)->fRedData = rData;
- (*gammas)->fGreenData = gData;
- (*gammas)->fBlueData = bData;
+ }
}
if (kNonStandard_SkGammaNamed == *gammaNamed) {
@@ -889,7 +893,7 @@ static bool load_lut_gammas(sk_sp<SkGammas>* gammas, size_t numTables, size_t en
"SkGammas struct is too large to allocate");
void* memory = sk_malloc_throw(allocSize);
- *gammas = sk_sp<SkGammas>(new (memory) SkGammas());
+ *gammas = sk_sp<SkGammas>(new (memory) SkGammas(numTables));
for (size_t tableIndex = 0; tableIndex < numTablesToUse; ++tableIndex) {
const uint8_t* ptr = src + readBytesPerChannel * tableIndex;
@@ -906,24 +910,18 @@ static bool load_lut_gammas(sk_sp<SkGammas>* gammas, size_t numTables, size_t en
}
}
- (*gammas)->fRedType = SkGammas::Type::kTable_Type;
- (*gammas)->fGreenType = SkGammas::Type::kTable_Type;
- (*gammas)->fBlueType = SkGammas::Type::kTable_Type;
+ SkASSERT(1 == numTablesToUse|| numTables == numTablesToUse);
- if (1 == numTablesToUse) {
- (*gammas)->fRedData.fTable.fOffset = 0;
- (*gammas)->fGreenData.fTable.fOffset = 0;
- (*gammas)->fBlueData.fTable.fOffset = 0;
- } else {
- (*gammas)->fRedData.fTable.fOffset = 0;
- (*gammas)->fGreenData.fTable.fOffset = writeBytesPerChannel;
- (*gammas)->fBlueData.fTable.fOffset = writeBytesPerChannel * 2;
+ size_t tableOffset = 0;
+ for (size_t tableIndex = 0; tableIndex < numTables; ++tableIndex) {
+ (*gammas)->fType[tableIndex] = SkGammas::Type::kTable_Type;
+ (*gammas)->fData[tableIndex].fTable.fOffset = tableOffset;
+ (*gammas)->fData[tableIndex].fTable.fSize = entriesPerTable;
+ if (numTablesToUse > 1) {
+ tableOffset += writeBytesPerChannel;
+ }
}
- (*gammas)->fRedData.fTable.fSize = entriesPerTable;
- (*gammas)->fGreenData.fTable.fSize = entriesPerTable;
- (*gammas)->fBlueData.fTable.fSize = entriesPerTable;
-
return true;
}
@@ -934,15 +932,23 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con
// must be zero.
const uint8_t inputChannels = src[8];
const uint8_t outputChannels = src[9];
- if (3 != inputChannels || SkColorLookUpTable::kOutputChannels != outputChannels) {
- // We only handle (supposedly) RGB inputs and RGB outputs. The numbers of input
- // channels and output channels both must be 3.
- // TODO (msarett):
- // Support different numbers of input channels. Ex: CMYK (4).
- SkColorSpacePrintf("Input and output channels must equal 3 in A to B tag.\n");
+ if (SkColorLookUpTable::kOutputChannels != outputChannels) {
+ // We only handle RGB outputs. The number of output channels must be 3.
+ SkColorSpacePrintf("Output channels (%d) must equal 3 in A to B tag.\n", outputChannels);
+ return false;
+ }
+ if (inputChannels == 0 || inputChannels > 4) {
+ // And we only support 4 input channels.
+ // ICC says up to 16 but our decode can only handle 4.
+ // It could easily be extended to support up to 8, but we only allow CMYK/RGB
+ // input color spaces which are 3 and 4 so let's restrict it to 4 instead of 8.
+ // We can always change this check when we support bigger input spaces.
+ SkColorSpacePrintf("Input channels (%d) must be between 1 and 4 in A to B tag.\n",
+ inputChannels);
return false;
}
+
// It is important that these are loaded in the order of application, as the
// order you construct an A2B color space's elements is the order it is applied
@@ -952,13 +958,14 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con
const size_t tagLen = len - offsetToACurves;
SkGammaNamed gammaNamed;
sk_sp<SkGammas> gammas;
- if (!parse_and_load_gamma(&gammaNamed, &gammas, src + offsetToACurves, tagLen)) {
+ if (!parse_and_load_gamma(&gammaNamed, &gammas, inputChannels, src + offsetToACurves,
+ tagLen)) {
return false;
}
if (gammas) {
elements->push_back(SkColorSpace_A2B::Element(std::move(gammas)));
- } else {
- elements->push_back(SkColorSpace_A2B::Element(gammaNamed));
+ } else if (kLinear_SkGammaNamed != gammaNamed) {
+ elements->push_back(SkColorSpace_A2B::Element(gammaNamed, inputChannels));
}
}
@@ -975,8 +982,8 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con
return false;
}
- SkASSERT(3 == inputChannels);
- uint8_t gridPoints[3];
+ SkASSERT(inputChannels <= kMaxColorChannels);
+ uint8_t gridPoints[kMaxColorChannels];
for (uint32_t i = 0; i < inputChannels; ++i) {
gridPoints[i] = clutSrc[i];
}
@@ -996,13 +1003,14 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con
const size_t tagLen = len - offsetToMCurves;
SkGammaNamed gammaNamed;
sk_sp<SkGammas> gammas;
- if (!parse_and_load_gamma(&gammaNamed, &gammas, src + offsetToMCurves, tagLen)) {
+ if (!parse_and_load_gamma(&gammaNamed, &gammas, outputChannels, src + offsetToMCurves,
+ tagLen)) {
return false;
}
if (gammas) {
elements->push_back(SkColorSpace_A2B::Element(std::move(gammas)));
- } else {
- elements->push_back(SkColorSpace_A2B::Element(gammaNamed));
+ } else if (kLinear_SkGammaNamed != gammaNamed) {
+ elements->push_back(SkColorSpace_A2B::Element(gammaNamed, outputChannels));
}
}
@@ -1011,7 +1019,7 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con
SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor);
if (!load_matrix(&matrix, src + offsetToMatrix, len - offsetToMatrix, true, pcs)) {
SkColorSpacePrintf("Failed to read matrix from A to B tag.\n");
- } else {
+ } else if (!matrix.isIdentity()) {
elements->push_back(SkColorSpace_A2B::Element(matrix));
}
}
@@ -1021,13 +1029,14 @@ bool load_a2b0_a_to_b_type(std::vector<SkColorSpace_A2B::Element>* elements, con
const size_t tagLen = len - offsetToBCurves;
SkGammaNamed gammaNamed;
sk_sp<SkGammas> gammas;
- if (!parse_and_load_gamma(&gammaNamed, &gammas, src + offsetToBCurves, tagLen)) {
+ if (!parse_and_load_gamma(&gammaNamed, &gammas, outputChannels, src + offsetToBCurves,
+ tagLen)) {
return false;
}
if (gammas) {
elements->push_back(SkColorSpace_A2B::Element(std::move(gammas)));
- } else {
- elements->push_back(SkColorSpace_A2B::Element(gammaNamed));
+ } else if (kLinear_SkGammaNamed != gammaNamed) {
+ elements->push_back(SkColorSpace_A2B::Element(gammaNamed, outputChannels));
}
}
@@ -1052,12 +1061,19 @@ bool load_a2b0_lutn_type(std::vector<SkColorSpace_A2B::Element>* elements, const
// The four bytes (4-7) that we skipped are reserved and must be zero.
const uint8_t inputChannels = src[8];
const uint8_t outputChannels = src[9];
- if (3 != inputChannels || SkColorLookUpTable::kOutputChannels != outputChannels) {
- // We only handle (supposedly) RGB inputs and RGB outputs. The numbers of input
- // channels and output channels both must be 3.
- // TODO (msarett):
- // Support different numbers of input channels. Ex: CMYK (4).
- SkColorSpacePrintf("Input and output channels must equal 3 in A to B tag.\n");
+ if (SkColorLookUpTable::kOutputChannels != outputChannels) {
+ // We only handle RGB outputs. The number of output channels must be 3.
+ SkColorSpacePrintf("Output channels (%d) must equal 3 in A to B tag.\n", outputChannels);
+ return false;
+ }
+ if (inputChannels == 0 || inputChannels > 4) {
+ // And we only support 4 input channels.
+ // ICC says up to 16 but our decode can only handle 4.
+ // It could easily be extended to support up to 8, but we only allow CMYK/RGB
+ // input color spaces which are 3 and 4 so let's restrict it to 4 instead of 8.
+ // We can always change this check when we support bigger input spaces.
+ SkColorSpacePrintf("Input channels (%d) must be between 1 and 4 in A to B tag.\n",
+ inputChannels);
return false;
}
@@ -1066,7 +1082,13 @@ bool load_a2b0_lutn_type(std::vector<SkColorSpace_A2B::Element>* elements, const
SkMatrix44 matrix(SkMatrix44::kUninitialized_Constructor);
load_matrix(&matrix, &src[12], len - 12, false, pcs);
- elements->push_back(SkColorSpace_A2B::Element(matrix));
+ if (!matrix.isIdentity()) {
+ // ICC specs (10.8/10.9) say lut8/16Type profiles must have identity matrices
+ // if the input color space is not PCSXYZ, and we do not support PCSXYZ input color spaces
+ // so we should never encounter a non-identity matrix here.
+ SkColorSpacePrintf("non-Identity matrix found in non-XYZ input color space lut profile");
+ return false;
+ }
size_t dataOffset = 48;
// # of input table entries
@@ -1111,12 +1133,21 @@ bool load_a2b0_lutn_type(std::vector<SkColorSpace_A2B::Element>* elements, const
return false;
}
SkASSERT(inputGammas);
- elements->push_back(SkColorSpace_A2B::Element(std::move(inputGammas)));
+ const SkGammaNamed inputGammaNamed = is_named(inputGammas);
+ if (kLinear_SkGammaNamed != inputGammaNamed) {
+ if (kNonStandard_SkGammaNamed != inputGammaNamed) {
+ elements->push_back(SkColorSpace_A2B::Element(inputGammaNamed, inputChannels));
+ } else {
+ elements->push_back(SkColorSpace_A2B::Element(std::move(inputGammas)));
+ }
+ }
const size_t clutOffset = inputOffset + precision*inTableEntries*inputChannels;
return_if_false(len >= clutOffset, "A2B0 lutnType tag too small for CLUT");
sk_sp<SkColorLookUpTable> colorLUT;
- const uint8_t gridPoints[3] = {clutGridPoints, clutGridPoints, clutGridPoints};
+ const uint8_t gridPoints[kMaxColorChannels] = {
+ clutGridPoints, clutGridPoints, clutGridPoints, clutGridPoints
+ };
if (!load_color_lut(&colorLUT, inputChannels, precision, gridPoints, src + clutOffset,
len - clutOffset)) {
SkColorSpacePrintf("Failed to read color LUT from lutnType tag.\n");
@@ -1138,13 +1169,33 @@ bool load_a2b0_lutn_type(std::vector<SkColorSpace_A2B::Element>* elements, const
return false;
}
SkASSERT(outputGammas);
- elements->push_back(SkColorSpace_A2B::Element(std::move(outputGammas)));
+ const SkGammaNamed outputGammaNamed = is_named(outputGammas);
+ if (kLinear_SkGammaNamed != outputGammaNamed) {
+ if (kNonStandard_SkGammaNamed != outputGammaNamed) {
+ elements->push_back(SkColorSpace_A2B::Element(outputGammaNamed, outputChannels));
+ } else {
+ elements->push_back(SkColorSpace_A2B::Element(std::move(outputGammas)));
+ }
+ }
return true;
}
+static inline int icf_channels(SkColorSpace_Base::InputColorFormat inputColorFormat) {
+ switch (inputColorFormat) {
+ case SkColorSpace_Base::InputColorFormat::kRGB:
+ return 3;
+ case SkColorSpace_Base::InputColorFormat::kCMYK:
+ return 4;
+ default:
+ SkASSERT(false);
+ return -1;
+ }
+}
+
static bool load_a2b0(std::vector<SkColorSpace_A2B::Element>* elements, const uint8_t* src,
- size_t len, SkColorSpace_A2B::PCS pcs) {
+ size_t len, SkColorSpace_A2B::PCS pcs,
+ SkColorSpace_Base::InputColorFormat inputColorFormat) {
const uint32_t type = read_big_endian_u32(src);
switch (type) {
case kTAG_AtoBType:
@@ -1153,26 +1204,54 @@ static bool load_a2b0(std::vector<SkColorSpace_A2B::Element>* elements, const ui
return false;
}
SkColorSpacePrintf("A2B0 tag is of type lutAtoBType\n");
- return load_a2b0_a_to_b_type(elements, src, len, pcs);
+ if (!load_a2b0_a_to_b_type(elements, src, len, pcs)) {
+ return false;
+ }
+ break;
case kTAG_lut8Type:
if (len < 48) {
SkColorSpacePrintf("lut8 tag is too small (%d bytes).", len);
return false;
}
SkColorSpacePrintf("A2B0 tag of type lut8Type\n");
- return load_a2b0_lutn_type(elements, src, len, pcs);
+ if (!load_a2b0_lutn_type(elements, src, len, pcs)) {
+ return false;
+ }
+ break;
case kTAG_lut16Type:
if (len < 52) {
SkColorSpacePrintf("lut16 tag is too small (%d bytes).", len);
return false;
}
SkColorSpacePrintf("A2B0 tag of type lut16Type\n");
- return load_a2b0_lutn_type(elements, src, len, pcs);
+ if (!load_a2b0_lutn_type(elements, src, len, pcs)) {
+ return false;
+ }
+ break;
default:
SkColorSpacePrintf("Unsupported A to B tag type: %c%c%c%c\n", (type>>24)&0xFF,
(type>>16)&0xFF, (type>>8)&0xFF, type&0xFF);
+ return false;
}
- return false;
+ // now let's verify that the input/output channels of each A2B element actually match up
+ SkASSERT(!elements->empty());
+ if (icf_channels(inputColorFormat) != elements->front().inputChannels()) {
+ SkColorSpacePrintf("Input channel count does not match first A2B element's input count");
+ return false;
+ }
+ for (size_t i = 1; i < elements->size(); ++i) {
+ if ((*elements)[i - 1].outputChannels() != (*elements)[i].inputChannels()) {
+ SkColorSpacePrintf("A2B elements don't agree in input/output channel counts");
+ return false;
+ }
+ }
+ SkASSERT(SkColorSpace_A2B::PCS::kLAB == pcs || SkColorSpace_A2B::PCS::kXYZ == pcs);
+ static constexpr int kPCSChannels = 3; // must be PCSLAB or PCSXYZ
+ if (kPCSChannels != elements->back().outputChannels()) {
+ SkColorSpacePrintf("PCS channel count doesn't match last A2B element's output count");
+ return false;
+ }
+ return true;
}
static bool tag_equals(const ICCTag* a, const ICCTag* b, const uint8_t* base) {
@@ -1210,6 +1289,11 @@ static inline bool is_close_to_d50(const SkMatrix44& matrix) {
}
sk_sp<SkColorSpace> SkColorSpace::MakeICC(const void* input, size_t len) {
+ return SkColorSpace_Base::MakeICC(input, len, SkColorSpace_Base::InputColorFormat::kRGB);
+}
+
+sk_sp<SkColorSpace> SkColorSpace_Base::MakeICC(const void* input, size_t len,
+ InputColorFormat inputColorFormat) {
if (!input || len < kICCHeaderSize) {
return_null("Data is null or not large enough to contain an ICC profile");
}
@@ -1228,6 +1312,22 @@ sk_sp<SkColorSpace> SkColorSpace::MakeICC(const void* input, size_t len) {
return nullptr;
}
+ switch (inputColorFormat) {
+ case InputColorFormat::kRGB:
+ if (header.fInputColorSpace != kRGB_ColorSpace) {
+ return_null("Provided input color format (RGB) does not match profile.\n");
+ }
+ break;
+ case InputColorFormat::kCMYK:
+ if (header.fInputColorSpace != kCMYK_ColorSpace) {
+ return_null("Provided input color format (CMYK) does not match profile.\n");
+ return nullptr;
+ }
+ break;
+ default:
+ return_null("Provided input color format not supported");
+ }
+
// Adjust ptr and len before reading the tags.
if (len < header.fSize) {
SkColorSpacePrintf("ICC profile might be truncated.\n");
@@ -1257,102 +1357,84 @@ sk_sp<SkColorSpace> SkColorSpace::MakeICC(const void* input, size_t len) {
}
}
- switch (header.fInputColorSpace) {
- case kRGB_ColorSpace: {
- // Recognize color profile specified by A2B0 tag.
- // this must be done before XYZ profile checking, as a profile can have both
- // in which case we should use the A2B case to be accurate
- // (XYZ is there as a fallback / quick preview)
- const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0);
- if (a2b0) {
- const SkColorSpace_A2B::PCS pcs = kXYZ_PCSSpace == header.fPCS
- ? SkColorSpace_A2B::PCS::kXYZ
- : SkColorSpace_A2B::PCS::kLAB;
- std::vector<SkColorSpace_A2B::Element> elements;
- if (load_a2b0(&elements, a2b0->addr(base), a2b0->fLength, pcs)) {
- return sk_sp<SkColorSpace>(new SkColorSpace_A2B(pcs, std::move(data),
- std::move(elements)));
- }
- SkColorSpacePrintf("Ignoring malformed A2B0 tag.\n");
- }
+ // Recognize color profile specified by A2B0 tag.
+ // this must be done before XYZ profile checking, as a profile can have both
+ // in which case we should use the A2B case to be accurate
+ // (XYZ is there as a fallback / quick preview)
+ const ICCTag* a2b0 = ICCTag::Find(tags.get(), tagCount, kTAG_A2B0);
+ if (a2b0) {
+ const SkColorSpace_A2B::PCS pcs = kXYZ_PCSSpace == header.fPCS
+ ? SkColorSpace_A2B::PCS::kXYZ
+ : SkColorSpace_A2B::PCS::kLAB;
+ std::vector<SkColorSpace_A2B::Element> elements;
+ if (load_a2b0(&elements, a2b0->addr(base), a2b0->fLength, pcs, inputColorFormat)) {
+ return sk_sp<SkColorSpace>(new SkColorSpace_A2B(inputColorFormat, std::move(elements),
+ pcs, std::move(data)));
+ }
+ SkColorSpacePrintf("Ignoring malformed A2B0 tag.\n");
+ }
- // Recognize the rXYZ, gXYZ, and bXYZ tags.
- const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ);
- const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ);
- const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ);
- // 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 (r && g && b && kXYZ_PCSSpace == header.fPCS) {
- float toXYZ[9];
- if (!load_xyz(&toXYZ[0], r->addr(base), r->fLength) ||
- !load_xyz(&toXYZ[3], g->addr(base), g->fLength) ||
- !load_xyz(&toXYZ[6], b->addr(base), b->fLength))
- {
- return_null("Need valid rgb tags for XYZ space");
- }
- SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
- mat.set3x3(toXYZ[0], toXYZ[1], toXYZ[2],
- toXYZ[3], toXYZ[4], toXYZ[5],
- toXYZ[6], toXYZ[7], toXYZ[8]);
- if (!is_close_to_d50(mat)) {
- // QCMS treats these profiles as "bogus". I'm not sure if that's
- // correct, but we certainly do not handle non-D50 matrices
- // correctly. So I'll disable this for now.
- SkColorSpacePrintf("Matrix is not close to D50");
- return nullptr;
- }
+ if (kRGB_ColorSpace == header.fInputColorSpace) {
+ // Recognize the rXYZ, gXYZ, and bXYZ tags.
+ const ICCTag* r = ICCTag::Find(tags.get(), tagCount, kTAG_rXYZ);
+ const ICCTag* g = ICCTag::Find(tags.get(), tagCount, kTAG_gXYZ);
+ const ICCTag* b = ICCTag::Find(tags.get(), tagCount, kTAG_bXYZ);
+ // 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 (r && g && b && kXYZ_PCSSpace == header.fPCS) {
+ float toXYZ[9];
+ if (!load_xyz(&toXYZ[0], r->addr(base), r->fLength) ||
+ !load_xyz(&toXYZ[3], g->addr(base), g->fLength) ||
+ !load_xyz(&toXYZ[6], b->addr(base), b->fLength))
+ {
+ return_null("Need valid rgb tags for XYZ space");
+ }
+ SkMatrix44 mat(SkMatrix44::kUninitialized_Constructor);
+ mat.set3x3(toXYZ[0], toXYZ[1], toXYZ[2],
+ toXYZ[3], toXYZ[4], toXYZ[5],
+ toXYZ[6], toXYZ[7], toXYZ[8]);
+ if (!is_close_to_d50(mat)) {
+ // QCMS treats these profiles as "bogus". I'm not sure if that's
+ // correct, but we certainly do not handle non-D50 matrices
+ // correctly. So I'll disable this for now.
+ SkColorSpacePrintf("Matrix is not close to D50");
+ return nullptr;
+ }
- r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC);
- g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC);
- b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC);
+ r = ICCTag::Find(tags.get(), tagCount, kTAG_rTRC);
+ g = ICCTag::Find(tags.get(), tagCount, kTAG_gTRC);
+ b = ICCTag::Find(tags.get(), tagCount, kTAG_bTRC);
- // If some, but not all, of the gamma tags are missing, assume that all
- // gammas are meant to be the same. This behavior is an arbitrary guess,
- // but it simplifies the code below.
- if ((!r || !g || !b) && (r || g || b)) {
- if (!r) {
- r = g ? g : b;
- }
+ // If some, but not all, of the gamma tags are missing, assume that all
+ // gammas are meant to be the same. This behavior is an arbitrary guess,
+ // but it simplifies the code below.
+ if ((!r || !g || !b) && (r || g || b)) {
+ if (!r) {
+ r = g ? g : b;
+ }
- if (!g) {
- g = r ? r : b;
- }
+ if (!g) {
+ g = r ? r : b;
+ }
- if (!b) {
- b = r ? r : g;
- }
+ if (!b) {
+ b = r ? r : g;
}
+ }
- SkGammaNamed gammaNamed = kNonStandard_SkGammaNamed;
- sk_sp<SkGammas> gammas = nullptr;
- size_t tagBytes;
- if (r && g && b) {
- if (tag_equals(r, g, base) && tag_equals(g, b, base)) {
- SkGammas::Data data;
- SkColorSpaceTransferFn params;
- SkGammas::Type type =
- parse_gamma(&data, &params, &tagBytes, r->addr(base), r->fLength);
- handle_invalid_gamma(&type, &data);
-
- if (SkGammas::Type::kNamed_Type == type) {
- gammaNamed = data.fNamed;
- } else {
- size_t allocSize = sizeof(SkGammas);
- if (!safe_add(allocSize, gamma_alloc_size(type, data), &allocSize)) {
- return_null("SkGammas struct is too large to allocate");
- }
- void* memory = sk_malloc_throw(allocSize);
- gammas = sk_sp<SkGammas>(new (memory) SkGammas());
- load_gammas(memory, 0, type, &data, params, r->addr(base));
-
- gammas->fRedType = type;
- gammas->fGreenType = type;
- gammas->fBlueType = type;
-
- gammas->fRedData = data;
- gammas->fGreenData = data;
- gammas->fBlueData = data;
- }
+ SkGammaNamed gammaNamed = kNonStandard_SkGammaNamed;
+ sk_sp<SkGammas> gammas = nullptr;
+ size_t tagBytes;
+ if (r && g && b) {
+ if (tag_equals(r, g, base) && tag_equals(g, b, base)) {
+ SkGammas::Data data;
+ SkColorSpaceTransferFn params;
+ SkGammas::Type type =
+ parse_gamma(&data, &params, &tagBytes, r->addr(base), r->fLength);
+ handle_invalid_gamma(&type, &data);
+
+ if (SkGammas::Type::kNamed_Type == type) {
+ gammaNamed = data.fNamed;
} else {
SkGammas::Data rData;
SkColorSpaceTransferFn rParams;
@@ -1373,53 +1455,81 @@ sk_sp<SkColorSpace> SkColorSpace::MakeICC(const void* input, size_t len) {
handle_invalid_gamma(&bType, &bData);
size_t allocSize = sizeof(SkGammas);
- if (!safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize) ||
- !safe_add(allocSize, gamma_alloc_size(gType, gData), &allocSize) ||
- !safe_add(allocSize, gamma_alloc_size(bType, bData), &allocSize))
- {
+ if (!safe_add(allocSize, gamma_alloc_size(type, data), &allocSize)) {
return_null("SkGammas struct is too large to allocate");
}
void* memory = sk_malloc_throw(allocSize);
- gammas = sk_sp<SkGammas>(new (memory) SkGammas());
+ gammas = sk_sp<SkGammas>(new (memory) SkGammas(3));
+ load_gammas(memory, 0, type, &data, params, r->addr(base));
- uint32_t offset = 0;
- gammas->fRedType = rType;
- offset += load_gammas(memory, offset, rType, &rData, rParams,
- r->addr(base));
+ for (int i = 0; i < 3; ++i) {
+ gammas->fType[i] = type;
+ gammas->fData[i] = data;
+ }
+ }
+ } else {
+ SkGammas::Data rData;
+ SkColorSpaceTransferFn rParams;
+ SkGammas::Type rType =
+ parse_gamma(&rData, &rParams, &tagBytes, r->addr(base), r->fLength);
+ handle_invalid_gamma(&rType, &rData);
+
+ SkGammas::Data gData;
+ SkColorSpaceTransferFn gParams;
+ SkGammas::Type gType =
+ parse_gamma(&gData, &gParams, &tagBytes, g->addr(base), g->fLength);
+ handle_invalid_gamma(&gType, &gData);
+
+ SkGammas::Data bData;
+ SkColorSpaceTransferFn bParams;
+ SkGammas::Type bType =
+ parse_gamma(&bData, &bParams, &tagBytes, b->addr(base), b->fLength);
+ handle_invalid_gamma(&bType, &bData);
+
+ size_t allocSize = sizeof(SkGammas);
+ if (!safe_add(allocSize, gamma_alloc_size(rType, rData), &allocSize) ||
+ !safe_add(allocSize, gamma_alloc_size(gType, gData), &allocSize) ||
+ !safe_add(allocSize, gamma_alloc_size(bType, bData), &allocSize)) {
+ return_null("SkGammas struct is too large to allocate");
+ }
+ void* memory = sk_malloc_throw(allocSize);
+ gammas = sk_sp<SkGammas>(new (memory) SkGammas(3));
- gammas->fGreenType = gType;
- offset += load_gammas(memory, offset, gType, &gData, gParams,
- g->addr(base));
+ uint32_t offset = 0;
+ gammas->fType[0] = rType;
+ offset += load_gammas(memory, offset, rType, &rData, rParams,
+ r->addr(base));
- gammas->fBlueType = bType;
- load_gammas(memory, offset, bType, &bData, bParams, b->addr(base));
+ gammas->fType[1] = gType;
+ offset += load_gammas(memory, offset, gType, &gData, gParams,
+ g->addr(base));
- gammas->fRedData = rData;
- gammas->fGreenData = gData;
- gammas->fBlueData = bData;
- }
- } else {
- // Guess sRGB if the profile is missing transfer functions.
- gammaNamed = kSRGB_SkGammaNamed;
- }
+ gammas->fType[2] = bType;
+ load_gammas(memory, offset, bType, &bData, bParams, b->addr(base));
- if (kNonStandard_SkGammaNamed == gammaNamed) {
- // It's possible that we'll initially detect non-matching gammas, only for
- // them to evaluate to the same named gamma curve.
- gammaNamed = is_named(gammas);
+ gammas->fData[0] = rData;
+ gammas->fData[1] = gData;
+ gammas->fData[2] = bData;
}
+ } else {
+ // Guess sRGB if the profile is missing transfer functions.
+ gammaNamed = kSRGB_SkGammaNamed;
+ }
- if (kNonStandard_SkGammaNamed == gammaNamed) {
- return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed,
- std::move(gammas),
- mat, std::move(data)));
- }
+ if (kNonStandard_SkGammaNamed == gammaNamed) {
+ // It's possible that we'll initially detect non-matching gammas, only for
+ // them to evaluate to the same named gamma curve.
+ gammaNamed = is_named(gammas);
+ }
- return SkColorSpace_Base::MakeRGB(gammaNamed, mat);
+ if (kNonStandard_SkGammaNamed == gammaNamed) {
+ return sk_sp<SkColorSpace>(new SkColorSpace_XYZ(gammaNamed,
+ std::move(gammas),
+ mat, std::move(data)));
}
+
+ return SkColorSpace_Base::MakeRGB(gammaNamed, mat);
}
- default:
- break;
}
return_null("ICC profile contains unsupported colorspace");
diff --git a/src/core/SkColorSpace_XYZ.cpp b/src/core/SkColorSpace_XYZ.cpp
index f99702d19b..4b90cae6a4 100644
--- a/src/core/SkColorSpace_XYZ.cpp
+++ b/src/core/SkColorSpace_XYZ.cpp
@@ -31,8 +31,9 @@ SkColorSpace_XYZ::SkColorSpace_XYZ(SkGammaNamed gammaNamed, sk_sp<SkGammas> gamm
, fGammas(std::move(gammas))
, fToXYZD50(toXYZD50)
, fToXYZD50Hash(SkGoodHash()(toXYZD50))
- , fFromXYZD50(SkMatrix44::kUninitialized_Constructor)
-{}
+ , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) {
+ SkASSERT(!fGammas || 3 == fGammas->channels());
+}
const SkMatrix44* SkColorSpace_XYZ::fromXYZD50() const {
fFromXYZOnce([this] {
diff --git a/src/core/SkRasterPipeline.h b/src/core/SkRasterPipeline.h
index 9e8f8f7906..7338c4d001 100644
--- a/src/core/SkRasterPipeline.h
+++ b/src/core/SkRasterPipeline.h
@@ -76,7 +76,8 @@
M(matrix_2x3) M(matrix_3x4) M(matrix_4x5) \
M(matrix_perspective) \
M(parametric_r) M(parametric_g) M(parametric_b) \
- M(table_r) M(table_g) M(table_b) \
+ M(parametric_a) \
+ M(table_r) M(table_g) M(table_b) M(table_a) \
M(color_lookup_table) M(lab_to_xyz) \
M(clamp_x) M(mirror_x) M(repeat_x) \
M(clamp_y) M(mirror_y) M(repeat_y) \
diff --git a/src/opts/SkRasterPipeline_opts.h b/src/opts/SkRasterPipeline_opts.h
index ee8a9bf029..66668bd6d9 100644
--- a/src/opts/SkRasterPipeline_opts.h
+++ b/src/opts/SkRasterPipeline_opts.h
@@ -632,6 +632,7 @@ SI SkNf parametric(const SkNf& v, const SkColorSpaceTransferFn& p) {
STAGE(parametric_r) { r = parametric(r, *(const SkColorSpaceTransferFn*)ctx); }
STAGE(parametric_g) { g = parametric(g, *(const SkColorSpaceTransferFn*)ctx); }
STAGE(parametric_b) { b = parametric(b, *(const SkColorSpaceTransferFn*)ctx); }
+STAGE(parametric_a) { a = parametric(a, *(const SkColorSpaceTransferFn*)ctx); }
SI SkNf table(const SkNf& v, const SkTableTransferFn& table) {
float result[N];
@@ -643,23 +644,29 @@ SI SkNf table(const SkNf& v, const SkTableTransferFn& table) {
STAGE(table_r) { r = table(r, *(const SkTableTransferFn*)ctx); }
STAGE(table_g) { g = table(g, *(const SkTableTransferFn*)ctx); }
STAGE(table_b) { b = table(b, *(const SkTableTransferFn*)ctx); }
+STAGE(table_a) { a = table(a, *(const SkTableTransferFn*)ctx); }
STAGE(color_lookup_table) {
const SkColorLookUpTable* colorLUT = (const SkColorLookUpTable*)ctx;
- float rgb[3];
+ SkASSERT(3 == colorLUT->inputChannels() || 4 == colorLUT->inputChannels());
+ SkASSERT(3 == colorLUT->outputChannels());
float result[3][N];
for (int i = 0; i < N; ++i) {
- rgb[0] = r[i];
- rgb[1] = g[i];
- rgb[2] = b[i];
- colorLUT->interp3D(rgb, rgb);
- result[0][i] = rgb[0];
- result[1][i] = rgb[1];
- result[2][i] = rgb[2];
+ const float in[4] = { r[i], g[i], b[i], a[i] };
+ float out[3];
+ colorLUT->interp(out, in);
+ for (int j = 0; j < colorLUT->outputChannels(); ++j) {
+ result[j][i] = out[j];
+ }
}
r = SkNf::Load(result[0]);
g = SkNf::Load(result[1]);
b = SkNf::Load(result[2]);
+ if (4 == colorLUT->inputChannels()) {
+ // we must set the pixel to opaque, as the alpha channel was used
+ // as input before this.
+ a = 1.f;
+ }
}
STAGE(lab_to_xyz) {
diff --git a/tests/ColorSpaceXformTest.cpp b/tests/ColorSpaceXformTest.cpp
index 0e67fe497d..d2bd2a3358 100644
--- a/tests/ColorSpaceXformTest.cpp
+++ b/tests/ColorSpaceXformTest.cpp
@@ -16,6 +16,8 @@
#include "SkColorSpaceXform_Base.h"
#include "Test.h"
+static constexpr int kChannels = 3;
+
class ColorSpaceXformTest {
public:
static std::unique_ptr<SkColorSpaceXform> CreateIdentityXform(const sk_sp<SkGammas>& gammas) {
@@ -40,13 +42,16 @@ public:
SkMatrix44 arbitraryMatrix{SkMatrix44::kUninitialized_Constructor};
arbitraryMatrix.setRowMajorf(values);
if (kNonStandard_SkGammaNamed == gammaNamed) {
+ SkASSERT(gammas);
srcElements.push_back(SkColorSpace_A2B::Element(gammas));
} else {
- srcElements.push_back(SkColorSpace_A2B::Element(gammaNamed));
+ srcElements.push_back(SkColorSpace_A2B::Element(gammaNamed, kChannels));
}
srcElements.push_back(SkColorSpace_A2B::Element(arbitraryMatrix));
- auto srcSpace = ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
- std::move(srcElements));
+ auto srcSpace =
+ ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
+ SkColorSpace_Base::InputColorFormat::kRGB,
+ std::move(srcElements));
sk_sp<SkColorSpace> dstSpace(new SkColorSpace_XYZ(gammaNamed, gammas, arbitraryMatrix,
nullptr));
@@ -55,13 +60,15 @@ public:
}
static sk_sp<SkColorSpace> CreateA2BSpace(SkColorSpace_A2B::PCS pcs,
+ SkColorSpace_Base::InputColorFormat inputColorFormat,
std::vector<SkColorSpace_A2B::Element> elements) {
- return sk_sp<SkColorSpace>(new SkColorSpace_A2B(pcs, nullptr, std::move(elements)));
+ return sk_sp<SkColorSpace>(new SkColorSpace_A2B(inputColorFormat, std::move(elements),
+ pcs, nullptr));
}
};
static bool almost_equal(int x, int y) {
- return SkTAbs(x - y) <= 1 ;
+ return SkTAbs(x - y) <= 1;
}
static void test_identity_xform(skiatest::Reporter* r, const sk_sp<SkGammas>& gammas,
@@ -101,7 +108,7 @@ static void test_identity_xform(skiatest::Reporter* r, const sk_sp<SkGammas>& ga
}
static void test_identity_xform_A2B(skiatest::Reporter* r, SkGammaNamed gammaNamed,
- const sk_sp<SkGammas>& gammas, bool repeat) {
+ const sk_sp<SkGammas>& gammas) {
// Arbitrary set of 10 pixels
constexpr int width = 10;
constexpr uint32_t srcPixels[width] = {
@@ -128,24 +135,19 @@ static void test_identity_xform_A2B(skiatest::Reporter* r, SkGammaNamed gammaNam
REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF),
SkGetPackedA32(dstPixels[i])));
}
-
- if (repeat) {
- // We should cache part of the transform after the run. So it is interesting
- // to make sure it still runs correctly the second time.
- test_identity_xform_A2B(r, gammaNamed, gammas, false);
- }
}
DEF_TEST(ColorSpaceXform_TableGamma, r) {
// Lookup-table based gamma curves
constexpr size_t tableSize = 10;
void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize);
- sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas());
- gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kTable_Type;
- gammas->fRedData.fTable.fSize = gammas->fGreenData.fTable.fSize =
- gammas->fBlueData.fTable.fSize = tableSize;
- gammas->fRedData.fTable.fOffset = gammas->fGreenData.fTable.fOffset =
- gammas->fBlueData.fTable.fOffset = 0;
+ sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels));
+ for (int i = 0; i < kChannels; ++i) {
+ gammas->fType[i] = SkGammas::Type::kTable_Type;
+ gammas->fData[i].fTable.fSize = tableSize;
+ gammas->fData[i].fTable.fOffset = 0;
+ }
+
float* table = SkTAddOffset<float>(memory, sizeof(SkGammas));
table[0] = 0.00f;
@@ -159,16 +161,18 @@ DEF_TEST(ColorSpaceXform_TableGamma, r) {
table[8] = 0.75f;
table[9] = 1.00f;
test_identity_xform(r, gammas, true);
- test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true);
+ test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
}
DEF_TEST(ColorSpaceXform_ParametricGamma, r) {
// Parametric gamma curves
void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn));
- sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas());
- gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kParam_Type;
- gammas->fRedData.fParamOffset = gammas->fGreenData.fParamOffset =
- gammas->fBlueData.fParamOffset = 0;
+ sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels));
+ for (int i = 0; i < kChannels; ++i) {
+ gammas->fType[i] = SkGammas::Type::kParam_Type;
+ gammas->fData[i].fParamOffset = 0;
+ }
+
SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn>
(memory, sizeof(SkGammas));
@@ -186,36 +190,38 @@ DEF_TEST(ColorSpaceXform_ParametricGamma, r) {
params->fC = 0.0f;
params->fG = 2.4f;
test_identity_xform(r, gammas, true);
- test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true);
+ test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
}
DEF_TEST(ColorSpaceXform_ExponentialGamma, r) {
// Exponential gamma curves
- sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas());
- gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kValue_Type;
- gammas->fRedData.fValue = gammas->fGreenData.fValue = gammas->fBlueData.fValue = 1.4f;
+ sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas(kChannels));
+ for (int i = 0; i < kChannels; ++i) {
+ gammas->fType[i] = SkGammas::Type::kValue_Type;
+ gammas->fData[i].fValue = 1.4f;
+ }
test_identity_xform(r, gammas, true);
- test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true);
+ test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
}
DEF_TEST(ColorSpaceXform_NamedGamma, r) {
- sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas());
- gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kNamed_Type;
- gammas->fRedData.fNamed = kSRGB_SkGammaNamed;
- gammas->fGreenData.fNamed = k2Dot2Curve_SkGammaNamed;
- gammas->fBlueData.fNamed = kLinear_SkGammaNamed;
+ sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas(kChannels));
+ gammas->fType[0] = gammas->fType[1] = gammas->fType[2] = SkGammas::Type::kNamed_Type;
+ gammas->fData[0].fNamed = kSRGB_SkGammaNamed;
+ gammas->fData[1].fNamed = k2Dot2Curve_SkGammaNamed;
+ gammas->fData[2].fNamed = kLinear_SkGammaNamed;
test_identity_xform(r, gammas, true);
- test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true);
- test_identity_xform_A2B(r, kSRGB_SkGammaNamed, nullptr, true);
- test_identity_xform_A2B(r, k2Dot2Curve_SkGammaNamed, nullptr, true);
- test_identity_xform_A2B(r, kLinear_SkGammaNamed, nullptr, true);
+ test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
+ test_identity_xform_A2B(r, kSRGB_SkGammaNamed, nullptr);
+ test_identity_xform_A2B(r, k2Dot2Curve_SkGammaNamed, nullptr);
+ test_identity_xform_A2B(r, kLinear_SkGammaNamed, nullptr);
}
DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) {
constexpr size_t tableSize = 10;
void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize +
sizeof(SkColorSpaceTransferFn));
- sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas());
+ sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels));
float* table = SkTAddOffset<float>(memory, sizeof(SkGammas));
table[0] = 0.00f;
@@ -239,18 +245,18 @@ DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) {
params->fF = 0.0f;
params->fG = 2.4f;
- gammas->fRedType = SkGammas::Type::kValue_Type;
- gammas->fRedData.fValue = 1.2f;
+ gammas->fType[0] = SkGammas::Type::kValue_Type;
+ gammas->fData[0].fValue = 1.2f;
- gammas->fGreenType = SkGammas::Type::kTable_Type;
- gammas->fGreenData.fTable.fSize = tableSize;
- gammas->fGreenData.fTable.fOffset = 0;
+ gammas->fType[1] = SkGammas::Type::kTable_Type;
+ gammas->fData[1].fTable.fSize = tableSize;
+ gammas->fData[1].fTable.fOffset = 0;
- gammas->fBlueType = SkGammas::Type::kParam_Type;
- gammas->fBlueData.fParamOffset = sizeof(float) * tableSize;
+ gammas->fType[2] = SkGammas::Type::kParam_Type;
+ gammas->fData[2].fParamOffset = sizeof(float) * tableSize;
test_identity_xform(r, gammas, true);
- test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, true);
+ test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas);
}
DEF_TEST(ColorSpaceXform_A2BCLUT, r) {
@@ -258,7 +264,7 @@ DEF_TEST(ColorSpaceXform_A2BCLUT, r) {
constexpr int gp = 4; // # grid points
constexpr int numEntries = gp*gp*gp*3;
- uint8_t gridPoints[3] = {gp, gp, gp};
+ const uint8_t gridPoints[3] = {gp, gp, gp};
void* memory = sk_malloc_throw(sizeof(SkColorLookUpTable) + sizeof(float) * numEntries);
sk_sp<SkColorLookUpTable> colorLUT(new (memory) SkColorLookUpTable(inputChannels, gridPoints));
// make a CLUT that rotates R, G, and B ie R->G, G->B, B->R
@@ -296,6 +302,7 @@ DEF_TEST(ColorSpaceXform_A2BCLUT, r) {
std::vector<SkColorSpace_A2B::Element> srcElements;
srcElements.push_back(SkColorSpace_A2B::Element(std::move(colorLUT)));
auto srcSpace = ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ,
+ SkColorSpace_Base::InputColorFormat::kRGB,
std::move(srcElements));
// dst space is entirely identity
auto dstSpace = SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, SkMatrix44::I());