diff options
author | raftias <raftias@google.com> | 2016-12-01 13:44:07 -0500 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2016-12-01 19:42:32 +0000 |
commit | 5476128f0a88217414f05e6a7ee518cdb411d026 (patch) | |
tree | 6b65531042d9af58a82ba8305a928edec17e0f29 | |
parent | b6d4e139b522821933d356ef7d2472816d25340a (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.cpp | 6 | ||||
-rw-r--r-- | gm/labpcsdemo.cpp | 192 | ||||
-rw-r--r-- | gn/gm.gni | 1 | ||||
-rw-r--r-- | src/codec/SkJpegCodec.cpp | 19 | ||||
-rw-r--r-- | src/core/SkColorLookUpTable.cpp | 51 | ||||
-rw-r--r-- | src/core/SkColorLookUpTable.h | 36 | ||||
-rw-r--r-- | src/core/SkColorSpace.cpp | 13 | ||||
-rw-r--r-- | src/core/SkColorSpaceXform_A2B.cpp | 81 | ||||
-rw-r--r-- | src/core/SkColorSpaceXform_A2B.h | 12 | ||||
-rw-r--r-- | src/core/SkColorSpace_A2B.cpp | 7 | ||||
-rw-r--r-- | src/core/SkColorSpace_A2B.h | 55 | ||||
-rw-r--r-- | src/core/SkColorSpace_Base.h | 61 | ||||
-rw-r--r-- | src/core/SkColorSpace_ICC.cpp | 604 | ||||
-rw-r--r-- | src/core/SkColorSpace_XYZ.cpp | 5 | ||||
-rw-r--r-- | src/core/SkRasterPipeline.h | 3 | ||||
-rw-r--r-- | src/opts/SkRasterPipeline_opts.h | 23 | ||||
-rw-r--r-- | tests/ColorSpaceXformTest.cpp | 101 |
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; ) @@ -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], ¶ms[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], ¶ms[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, ¶ms, &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, ¶ms, &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()); |