aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--include/core/SkColorSpace.h14
-rw-r--r--src/core/SkImageFilter.cpp7
-rw-r--r--src/effects/SkAlphaThresholdFilter.cpp3
-rw-r--r--src/effects/SkArithmeticImageFilter.cpp6
-rw-r--r--src/effects/SkDisplacementMapEffect.cpp4
-rw-r--r--src/effects/SkMagnifierImageFilter.cpp3
-rw-r--r--src/effects/SkXfermodeImageFilter.cpp6
-rw-r--r--src/gpu/GrColorSpaceInfo.cpp3
-rw-r--r--src/gpu/GrColorSpaceXform.cpp124
-rw-r--r--src/gpu/GrColorSpaceXform.h46
-rw-r--r--src/gpu/GrTestUtils.cpp8
-rw-r--r--src/gpu/GrTextureAdjuster.cpp3
-rw-r--r--src/gpu/GrTextureMaker.cpp3
-rw-r--r--src/gpu/SkGpuDevice.cpp5
-rw-r--r--src/gpu/SkGpuDevice_drawTexture.cpp3
-rw-r--r--src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp4
-rw-r--r--src/gpu/glsl/GrGLSLColorSpaceXformHelper.h44
-rw-r--r--src/gpu/glsl/GrGLSLShaderBuilder.cpp93
-rw-r--r--src/shaders/SkColorShader.cpp6
-rw-r--r--src/shaders/SkImageShader.cpp5
-rw-r--r--src/shaders/gradients/SkGradientShader.cpp6
-rw-r--r--src/shaders/gradients/SkGradientShaderPriv.h5
22 files changed, 314 insertions, 87 deletions
diff --git a/include/core/SkColorSpace.h b/include/core/SkColorSpace.h
index 5b2d86b175..962253243d 100644
--- a/include/core/SkColorSpace.h
+++ b/include/core/SkColorSpace.h
@@ -54,6 +54,20 @@ struct SK_API SkColorSpaceTransferFn {
* this one.
*/
SkColorSpaceTransferFn invert() const;
+
+ /**
+ * Transform a single float by this transfer function.
+ * For negative inputs, returns sign(x) * f(abs(x)).
+ */
+ float operator()(float x) {
+ SkScalar s = SkScalarSignAsScalar(x);
+ x = sk_float_abs(x);
+ if (x >= fD) {
+ return s * (powf(fA * x + fB, fG) + fE);
+ } else {
+ return s * (fC * x + fF);
+ }
+ }
};
class SK_API SkColorSpace : public SkRefCnt {
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index cdea2d1ccc..bf34ae3180 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -368,8 +368,11 @@ sk_sp<SkSpecialImage> SkImageFilter::ImageToColorSpace(SkSpecialImage* src,
// object. If that produces something, then both are tagged, and the source is in a different
// gamut than the dest. There is some overhead to making the xform, but those are cached, and
// if we get one back, that means we're about to use it during the conversion anyway.
- sk_sp<GrColorSpaceXform> colorSpaceXform = GrColorSpaceXform::Make(src->getColorSpace(),
- outProps.colorSpace());
+ //
+ // TODO: Fix this check, to handle wider support of transfer functions, config mismatch, etc.
+ // For now, continue to just check if gamut is different, which may not be sufficient.
+ auto colorSpaceXform = GrColorSpaceXform::MakeGamutXform(src->getColorSpace(),
+ outProps.colorSpace());
if (!colorSpaceXform) {
// No xform needed, just return the original image
diff --git a/src/effects/SkAlphaThresholdFilter.cpp b/src/effects/SkAlphaThresholdFilter.cpp
index fa77c85bf9..e7dfa5f252 100644
--- a/src/effects/SkAlphaThresholdFilter.cpp
+++ b/src/effects/SkAlphaThresholdFilter.cpp
@@ -172,9 +172,10 @@ sk_sp<SkSpecialImage> SkAlphaThresholdFilterImpl::onFilterImage(SkSpecialImage*
}
const OutputProperties& outProps = ctx.outputProperties();
+ GrPixelConfig inputConfig = inputProxy->config();
auto textureFP = GrSimpleTextureEffect::Make(std::move(inputProxy), SkMatrix::I());
textureFP = GrColorSpaceXformEffect::Make(std::move(textureFP), input->getColorSpace(),
- outProps.colorSpace());
+ inputConfig, outProps.colorSpace());
if (!textureFP) {
return nullptr;
}
diff --git a/src/effects/SkArithmeticImageFilter.cpp b/src/effects/SkArithmeticImageFilter.cpp
index 0ef40a23d9..a4d6ffe7dd 100644
--- a/src/effects/SkArithmeticImageFilter.cpp
+++ b/src/effects/SkArithmeticImageFilter.cpp
@@ -306,12 +306,13 @@ sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::filterImageGPU(
if (backgroundProxy) {
SkMatrix backgroundMatrix = SkMatrix::MakeTrans(-SkIntToScalar(backgroundOffset.fX),
-SkIntToScalar(backgroundOffset.fY));
+ GrPixelConfig bgConfig = backgroundProxy->config();
bgFP = GrTextureDomainEffect::Make(
std::move(backgroundProxy), backgroundMatrix,
GrTextureDomain::MakeTexelDomain(background->subset()),
GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(),
- outputProperties.colorSpace());
+ bgConfig, outputProperties.colorSpace());
} else {
bgFP = GrConstColorProcessor::Make(GrColor4f::TransparentBlack(),
GrConstColorProcessor::kIgnore_InputMode);
@@ -320,12 +321,13 @@ sk_sp<SkSpecialImage> ArithmeticImageFilterImpl::filterImageGPU(
if (foregroundProxy) {
SkMatrix foregroundMatrix = SkMatrix::MakeTrans(-SkIntToScalar(foregroundOffset.fX),
-SkIntToScalar(foregroundOffset.fY));
+ GrPixelConfig fgConfig = foregroundProxy->config();
auto foregroundFP = GrTextureDomainEffect::Make(
std::move(foregroundProxy), foregroundMatrix,
GrTextureDomain::MakeTexelDomain(foreground->subset()),
GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP),
- foreground->getColorSpace(),
+ foreground->getColorSpace(), fgConfig,
outputProperties.colorSpace());
paint.addColorFragmentProcessor(std::move(foregroundFP));
diff --git a/src/effects/SkDisplacementMapEffect.cpp b/src/effects/SkDisplacementMapEffect.cpp
index 1cdd0ce231..09aaf7cc4b 100644
--- a/src/effects/SkDisplacementMapEffect.cpp
+++ b/src/effects/SkDisplacementMapEffect.cpp
@@ -286,6 +286,7 @@ sk_sp<SkSpecialImage> SkDisplacementMapEffect::onFilterImage(SkSpecialImage* sou
SkIntToScalar(colorOffset.fY - displOffset.fY));
SkColorSpace* colorSpace = ctx.outputProperties().colorSpace();
+ GrPixelConfig colorConfig = colorProxy->config();
std::unique_ptr<GrFragmentProcessor> fp =
GrDisplacementMapEffect::Make(fXChannelSelector,
fYChannelSelector,
@@ -294,7 +295,8 @@ sk_sp<SkSpecialImage> SkDisplacementMapEffect::onFilterImage(SkSpecialImage* sou
offsetMatrix,
std::move(colorProxy),
SkISize::Make(color->width(), color->height()));
- fp = GrColorSpaceXformEffect::Make(std::move(fp), color->getColorSpace(), colorSpace);
+ fp = GrColorSpaceXformEffect::Make(std::move(fp), color->getColorSpace(), colorConfig,
+ colorSpace);
GrPaint paint;
paint.addColorFragmentProcessor(std::move(fp));
diff --git a/src/effects/SkMagnifierImageFilter.cpp b/src/effects/SkMagnifierImageFilter.cpp
index bb0e7ed4fb..dd5714ba75 100644
--- a/src/effects/SkMagnifierImageFilter.cpp
+++ b/src/effects/SkMagnifierImageFilter.cpp
@@ -355,6 +355,7 @@ sk_sp<SkSpecialImage> SkMagnifierImageFilter::onFilterImage(SkSpecialImage* sour
offset->fY = bounds.top();
bounds.offset(-inputOffset);
+ GrPixelConfig inputConfig = inputProxy->config();
auto fp = GrMagnifierEffect::Make(std::move(inputProxy),
bounds,
fSrcRect,
@@ -363,7 +364,7 @@ sk_sp<SkSpecialImage> SkMagnifierImageFilter::onFilterImage(SkSpecialImage* sour
bounds.width() * invInset,
bounds.height() * invInset);
fp = GrColorSpaceXformEffect::Make(std::move(fp), input->getColorSpace(),
- ctx.outputProperties().colorSpace());
+ inputConfig, ctx.outputProperties().colorSpace());
if (!fp) {
return nullptr;
}
diff --git a/src/effects/SkXfermodeImageFilter.cpp b/src/effects/SkXfermodeImageFilter.cpp
index 1fb4cdef7b..111dc61a56 100644
--- a/src/effects/SkXfermodeImageFilter.cpp
+++ b/src/effects/SkXfermodeImageFilter.cpp
@@ -295,12 +295,13 @@ sk_sp<SkSpecialImage> SkXfermodeImageFilter_Base::filterImageGPU(
if (backgroundProxy) {
SkMatrix bgMatrix = SkMatrix::MakeTrans(-SkIntToScalar(backgroundOffset.fX),
-SkIntToScalar(backgroundOffset.fY));
+ GrPixelConfig bgConfig = backgroundProxy->config();
bgFP = GrTextureDomainEffect::Make(std::move(backgroundProxy), bgMatrix,
GrTextureDomain::MakeTexelDomain(background->subset()),
GrTextureDomain::kDecal_Mode,
GrSamplerState::Filter::kNearest);
bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(),
- outputProperties.colorSpace());
+ bgConfig, outputProperties.colorSpace());
} else {
bgFP = GrConstColorProcessor::Make(GrColor4f::TransparentBlack(),
GrConstColorProcessor::kIgnore_InputMode);
@@ -309,12 +310,13 @@ sk_sp<SkSpecialImage> SkXfermodeImageFilter_Base::filterImageGPU(
if (foregroundProxy) {
SkMatrix fgMatrix = SkMatrix::MakeTrans(-SkIntToScalar(foregroundOffset.fX),
-SkIntToScalar(foregroundOffset.fY));
+ GrPixelConfig fgConfig = foregroundProxy->config();
auto foregroundFP = GrTextureDomainEffect::Make(
std::move(foregroundProxy), fgMatrix,
GrTextureDomain::MakeTexelDomain(foreground->subset()),
GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP),
- foreground->getColorSpace(),
+ foreground->getColorSpace(), fgConfig,
outputProperties.colorSpace());
paint.addColorFragmentProcessor(std::move(foregroundFP));
diff --git a/src/gpu/GrColorSpaceInfo.cpp b/src/gpu/GrColorSpaceInfo.cpp
index cd0dc1634d..1a49e8622d 100644
--- a/src/gpu/GrColorSpaceInfo.cpp
+++ b/src/gpu/GrColorSpaceInfo.cpp
@@ -17,7 +17,8 @@ GrColorSpaceXform* GrColorSpaceInfo::colorSpaceXformFromSRGB() const {
if (!fInitializedColorSpaceXformFromSRGB) {
// sRGB sources are very common (SkColor, etc...), so we cache that gamut transformation
auto srgbColorSpace = SkColorSpace::MakeSRGB();
- fColorXformFromSRGB = GrColorSpaceXform::Make(srgbColorSpace.get(), fColorSpace.get());
+ fColorXformFromSRGB = GrColorSpaceXform::MakeGamutXform(srgbColorSpace.get(),
+ fColorSpace.get());
fInitializedColorSpaceXformFromSRGB = true;
}
// You can't be color-space aware in legacy mode
diff --git a/src/gpu/GrColorSpaceXform.cpp b/src/gpu/GrColorSpaceXform.cpp
index e43ccdc14d..f0dc279be3 100644
--- a/src/gpu/GrColorSpaceXform.cpp
+++ b/src/gpu/GrColorSpaceXform.cpp
@@ -7,6 +7,7 @@
#include "GrColorSpaceXform.h"
#include "SkColorSpace.h"
+#include "SkColorSpacePriv.h"
#include "SkColorSpace_Base.h"
#include "SkMatrix44.h"
#include "SkSpinlock.h"
@@ -56,56 +57,114 @@ private:
uint64_t fSequence;
};
-GrColorSpaceXform::GrColorSpaceXform(const SkMatrix44& srcToDst)
- : fSrcToDst(srcToDst) {}
+GrColorSpaceXform::GrColorSpaceXform(const SkColorSpaceTransferFn& srcTransferFn,
+ const SkMatrix44& gamutXform, uint32_t flags)
+ : fSrcTransferFn(srcTransferFn), fGamutXform(gamutXform), fFlags(flags) {}
static SkSpinlock gColorSpaceXformCacheSpinlock;
-sk_sp<GrColorSpaceXform> GrColorSpaceXform::Make(const SkColorSpace* src, const SkColorSpace* dst) {
- if (!src || !dst) {
- // Invalid
+sk_sp<GrColorSpaceXform> GrColorSpaceXform::Make(const SkColorSpace* src,
+ GrPixelConfig srcConfig,
+ const SkColorSpace* dst) {
+ if (!dst) {
+ // No transformation is performed in legacy mode
return nullptr;
}
- if (src == dst) {
- // Quick equality check - no conversion needed in this case
+ // Treat null sources as sRGB
+ if (!src) {
+ if (GrPixelConfigIsFloatingPoint(srcConfig)) {
+ src = SkColorSpace::MakeSRGBLinear().get();
+ } else {
+ src = SkColorSpace::MakeSRGB().get();
+ }
+ }
+
+ uint32_t flags = 0;
+ SkColorSpaceTransferFn srcTransferFn;
+
+ // kUnknown_GrPixelConfig is a sentinel that means we don't care about transfer functions,
+ // just the gamut xform.
+ if (kUnknown_GrPixelConfig != srcConfig) {
+ // Determine if src transfer function is needed, based on src config and color space
+ if (GrPixelConfigIsSRGB(srcConfig)) {
+ // Source texture is sRGB, will be converted to linear when we sample
+ if (src->gammaCloseToSRGB()) {
+ // Hardware linearize does the right thing
+ } else if (src->gammaIsLinear()) {
+ // Oops, need to undo the (extra) linearize
+ flags |= kApplyInverseSRGB_Flag;
+ } else if (src->isNumericalTransferFn(&srcTransferFn)) {
+ // Need to undo the (extra) linearize, then apply the correct transfer function
+ flags |= (kApplyInverseSRGB_Flag | kApplyTransferFn_Flag);
+ } else {
+ // We don't (yet) support more complex transfer functions
+ return nullptr;
+ }
+ } else {
+ // Source texture is some non-sRGB format, we consider it linearly encoded
+ if (src->gammaIsLinear()) {
+ // Linear sampling does the right thing
+ } else if (src->isNumericalTransferFn(&srcTransferFn)) {
+ // Need to manually apply some transfer function (including sRGB)
+ flags |= kApplyTransferFn_Flag;
+ } else {
+ // We don't (yet) support more complex transfer functions
+ return nullptr;
+ }
+ }
+ }
+ if (src == dst && (0 == flags)) {
+ // Quick equality check - no conversion (or transfer function) needed in this case
return nullptr;
}
const SkMatrix44* toXYZD50 = as_CSB(src)->toXYZD50();
const SkMatrix44* fromXYZD50 = as_CSB(dst)->fromXYZD50();
if (!toXYZD50 || !fromXYZD50) {
- // unsupported colour spaces -- cannot specify gamut as a matrix
+ // Unsupported colour spaces -- cannot specify gamut as a matrix
return nullptr;
}
+ // Determine if a gamut xform is needed
uint32_t srcHash = as_CSB(src)->toXYZD50Hash();
uint32_t dstHash = as_CSB(dst)->toXYZD50Hash();
- if (srcHash == dstHash) {
- // Identical gamut - no conversion needed in this case
+ if (srcHash != dstHash) {
+ flags |= kApplyGamutXform_Flag;
+ } else {
SkASSERT(*toXYZD50 == *as_CSB(dst)->toXYZD50() && "Hash collision");
+ }
+
+ if (0 == flags) {
+ // Identical gamut and no transfer function - no conversion needed in this case
return nullptr;
}
- auto deferredResult = [fromXYZD50, toXYZD50]() {
+ auto makeXform = [srcTransferFn, fromXYZD50, toXYZD50, flags]() {
SkMatrix44 srcToDst(SkMatrix44::kUninitialized_Constructor);
- srcToDst.setConcat(*fromXYZD50, *toXYZD50);
- return sk_make_sp<GrColorSpaceXform>(srcToDst);
+ if (SkToBool(flags & kApplyGamutXform_Flag)) {
+ srcToDst.setConcat(*fromXYZD50, *toXYZD50);
+ } else {
+ srcToDst.setIdentity();
+ }
+ return sk_make_sp<GrColorSpaceXform>(srcTransferFn, srcToDst, flags);
};
- if (gColorSpaceXformCacheSpinlock.tryAcquire()) {
+ // For now, we only cache pure gamut xforms (no transfer functions)
+ // TODO: Fold a hash of the transfer function into the cache key
+ if ((kApplyGamutXform_Flag == flags) && gColorSpaceXformCacheSpinlock.tryAcquire()) {
static GrColorSpaceXformCache* gCache;
if (nullptr == gCache) {
gCache = new GrColorSpaceXformCache();
}
uint64_t key = static_cast<uint64_t>(srcHash) << 32 | static_cast<uint64_t>(dstHash);
- sk_sp<GrColorSpaceXform> xform = gCache->findOrAdd(key, deferredResult);
+ sk_sp<GrColorSpaceXform> xform = gCache->findOrAdd(key, makeXform);
gColorSpaceXformCacheSpinlock.release();
return xform;
} else {
- // Rather than wait for the spin lock, just bypass the cache
- return deferredResult();
+ // If our xform has non-gamut components, or we can't get the spin lock, just build it
+ return makeXform();
}
}
@@ -114,16 +173,36 @@ bool GrColorSpaceXform::Equals(const GrColorSpaceXform* a, const GrColorSpaceXfo
return true;
}
- if (!a || !b) {
+ if (!a || !b || a->fFlags != b->fFlags) {
+ return false;
+ }
+
+ if (SkToBool(a->fFlags & kApplyTransferFn_Flag) &&
+ 0 != memcmp(&a->fSrcTransferFn, &b->fSrcTransferFn, sizeof(SkColorSpaceTransferFn))) {
+ return false;
+ }
+
+ if (SkToBool(a->fFlags && kApplyGamutXform_Flag) && a->fGamutXform != b->fGamutXform) {
return false;
}
- return a->fSrcToDst == b->fSrcToDst;
+ return true;
}
GrColor4f GrColorSpaceXform::unclampedXform(const GrColor4f& srcColor) {
- GrColor4f result;
- fSrcToDst.mapScalars(srcColor.fRGBA, result.fRGBA);
+ // This transform step should only happen with textures (not CPU xform of individual values)
+ SkASSERT(!SkToBool(fFlags & kApplyInverseSRGB_Flag));
+
+ GrColor4f result = srcColor;
+ if (fFlags & kApplyTransferFn_Flag) {
+ // Only transform RGB (not alpha)
+ for (int i = 0; i < 3; ++i) {
+ result.fRGBA[i] = fSrcTransferFn(result.fRGBA[i]);
+ }
+ }
+ if (fFlags & kApplyGamutXform_Flag) {
+ fGamutXform.mapScalars(result.fRGBA, result.fRGBA);
+ }
return result;
}
@@ -214,12 +293,13 @@ GrFragmentProcessor::OptimizationFlags GrColorSpaceXformEffect::OptFlags(
std::unique_ptr<GrFragmentProcessor> GrColorSpaceXformEffect::Make(
std::unique_ptr<GrFragmentProcessor> child,
const SkColorSpace* src,
+ GrPixelConfig srcConfig,
const SkColorSpace* dst) {
if (!child) {
return nullptr;
}
- auto colorXform = GrColorSpaceXform::Make(src, dst);
+ auto colorXform = GrColorSpaceXform::Make(src, srcConfig, dst);
if (colorXform) {
return std::unique_ptr<GrFragmentProcessor>(
new GrColorSpaceXformEffect(std::move(child), std::move(colorXform)));
diff --git a/src/gpu/GrColorSpaceXform.h b/src/gpu/GrColorSpaceXform.h
index 3f03e5652d..f63b1660f3 100644
--- a/src/gpu/GrColorSpaceXform.h
+++ b/src/gpu/GrColorSpaceXform.h
@@ -10,29 +10,42 @@
#include "GrColor.h"
#include "GrFragmentProcessor.h"
+#include "SkColorSpace.h"
#include "SkMatrix44.h"
#include "SkRefCnt.h"
-class SkColorSpace;
-
/**
- * Represents a color gamut transformation (as a 4x4 color matrix)
+ * Represents a color space transformation
*/
class GrColorSpaceXform : public SkRefCnt {
public:
- GrColorSpaceXform(const SkMatrix44& srcToDst);
+ GrColorSpaceXform(const SkColorSpaceTransferFn&, const SkMatrix44&, uint32_t);
+
+ static sk_sp<GrColorSpaceXform> Make(const SkColorSpace* src,
+ GrPixelConfig srcConfig,
+ const SkColorSpace* dst);
+ static sk_sp<GrColorSpaceXform> MakeGamutXform(const SkColorSpace* src,
+ const SkColorSpace* dst) {
+ auto result = Make(src, kUnknown_GrPixelConfig, dst);
+ SkASSERT(!result || 0 == (result->fFlags & ~kApplyGamutXform_Flag));
+ return result;
+ }
- static sk_sp<GrColorSpaceXform> Make(const SkColorSpace* src, const SkColorSpace* dst);
+ const SkColorSpaceTransferFn& transferFn() const { return fSrcTransferFn; }
+ const float* transferFnCoeffs() const {
+ static_assert(0 == offsetof(SkColorSpaceTransferFn, fG), "TransferFn layout");
+ return &fSrcTransferFn.fG;
+ }
- const SkMatrix44& srcToDst() const { return fSrcToDst; }
+ const SkMatrix44& gamutXform() const { return fGamutXform; }
/**
* GrGLSLFragmentProcessor::GenKey() must call this and include the returned value in its
* computed key.
*/
static uint32_t XformKey(const GrColorSpaceXform* xform) {
- // Code generation changes if there is an xform, but it otherwise constant
- return SkToBool(xform) ? 1 : 0;
+ // Code generation depends on which steps we apply (as encoded by fFlags)
+ return SkToBool(xform) ? xform->fFlags : 0;
}
static bool Equals(const GrColorSpaceXform* a, const GrColorSpaceXform* b);
@@ -41,7 +54,21 @@ public:
GrColor4f clampedXform(const GrColor4f& srcColor);
private:
- SkMatrix44 fSrcToDst;
+ friend class GrGLSLColorSpaceXformHelper;
+
+ enum Flags {
+ kApplyTransferFn_Flag = 0x1,
+ kApplyGamutXform_Flag = 0x2,
+
+ // Almost never used. This handles the case where the src data is sRGB pixel config,
+ // but the color space has a different transfer function. In that case, we first undo
+ // the HW sRGB -> Linear conversion, before applying any other steps.
+ kApplyInverseSRGB_Flag = 0x4,
+ };
+
+ SkColorSpaceTransferFn fSrcTransferFn;
+ SkMatrix44 fGamutXform;
+ uint32_t fFlags;
};
class GrColorSpaceXformEffect : public GrFragmentProcessor {
@@ -52,6 +79,7 @@ public:
*/
static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> child,
const SkColorSpace* src,
+ GrPixelConfig srcConfig,
const SkColorSpace* dst);
const char* name() const override { return "ColorSpaceXform"; }
diff --git a/src/gpu/GrTestUtils.cpp b/src/gpu/GrTestUtils.cpp
index da3cb97dfe..bd190a42ee 100644
--- a/src/gpu/GrTestUtils.cpp
+++ b/src/gpu/GrTestUtils.cpp
@@ -325,10 +325,10 @@ sk_sp<GrColorSpaceXform> TestColorXform(SkRandom* random) {
sk_sp<SkColorSpace> adobe = SkColorSpace_Base::MakeNamed(SkColorSpace_Base::kAdobeRGB_Named);
// No gamut change
gXforms[0] = nullptr;
- // To larger gamut
- gXforms[1] = GrColorSpaceXform::Make(srgb.get(), adobe.get());
- // To smaller gamut
- gXforms[2] = GrColorSpaceXform::Make(adobe.get(), srgb.get());
+ // To larger gamut (with automatic transfer function)
+ gXforms[1] = GrColorSpaceXform::Make(srgb.get(), kSRGBA_8888_GrPixelConfig, adobe.get());
+ // To smaller gamut (with manual transfer function)
+ gXforms[2] = GrColorSpaceXform::Make(adobe.get(), kRGBA_8888_GrPixelConfig, srgb.get());
}
return gXforms[random->nextULessThan(static_cast<uint32_t>(SK_ARRAY_COUNT(gXforms)))];
}
diff --git a/src/gpu/GrTextureAdjuster.cpp b/src/gpu/GrTextureAdjuster.cpp
index 1a9b228390..2ae95a929e 100644
--- a/src/gpu/GrTextureAdjuster.cpp
+++ b/src/gpu/GrTextureAdjuster.cpp
@@ -156,7 +156,8 @@ std::unique_ptr<GrFragmentProcessor> GrTextureAdjuster::createFragmentProcessor(
}
SkASSERT(kNoDomain_DomainMode == domainMode ||
(domain.fLeft <= domain.fRight && domain.fTop <= domain.fBottom));
+ GrPixelConfig config = proxy->config();
auto fp = CreateFragmentProcessorForDomainAndFilter(std::move(proxy), textureMatrix,
domainMode, domain, filterOrNullForBicubic);
- return GrColorSpaceXformEffect::Make(std::move(fp), fColorSpace, dstColorSpace);
+ return GrColorSpaceXformEffect::Make(std::move(fp), fColorSpace, config, dstColorSpace);
}
diff --git a/src/gpu/GrTextureMaker.cpp b/src/gpu/GrTextureMaker.cpp
index 822d2141cb..a60a745696 100644
--- a/src/gpu/GrTextureMaker.cpp
+++ b/src/gpu/GrTextureMaker.cpp
@@ -118,9 +118,10 @@ std::unique_ptr<GrFragmentProcessor> GrTextureMaker::createFragmentProcessor(
proxy.get(),
nullptr, fmForDetermineDomain, &domain);
SkASSERT(kTightCopy_DomainMode != domainMode);
+ GrPixelConfig config = proxy->config();
auto fp = CreateFragmentProcessorForDomainAndFilter(std::move(proxy), adjustedMatrix,
domainMode, domain, filterOrNullForBicubic);
- return GrColorSpaceXformEffect::Make(std::move(fp), texColorSpace.get(), dstColorSpace);
+ return GrColorSpaceXformEffect::Make(std::move(fp), texColorSpace.get(), config, dstColorSpace);
}
sk_sp<GrTextureProxy> GrTextureMaker::generateTextureProxyForParams(const CopyParams& copyParams,
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index df81d0494b..760e6064bb 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -992,6 +992,7 @@ void SkGpuDevice::drawBitmapTile(const SkBitmap& bitmap,
// Construct a GrPaint by setting the bitmap texture as the first effect and then configuring
// the rest from the SkPaint.
std::unique_ptr<GrFragmentProcessor> fp;
+ GrPixelConfig config = proxy->config();
if (needsTextureDomain && (SkCanvas::kStrict_SrcRectConstraint == constraint)) {
// Use a constrained texture domain to avoid color bleeding
@@ -1022,7 +1023,7 @@ void SkGpuDevice::drawBitmapTile(const SkBitmap& bitmap,
fp = GrSimpleTextureEffect::Make(std::move(proxy), texMatrix, samplerState);
}
- fp = GrColorSpaceXformEffect::Make(std::move(fp), bitmap.colorSpace(),
+ fp = GrColorSpaceXformEffect::Make(std::move(fp), bitmap.colorSpace(), config,
fRenderTargetContext->colorSpaceInfo().colorSpace());
GrPaint grPaint;
if (!SkPaintToGrPaintWithTexture(this->context(), fRenderTargetContext->colorSpaceInfo(), paint,
@@ -1088,7 +1089,7 @@ void SkGpuDevice::drawSpecial(SkSpecialImage* special1, int left, int top, const
tmpUnfiltered.setImageFilter(nullptr);
auto fp = GrSimpleTextureEffect::Make(std::move(proxy), SkMatrix::I());
- fp = GrColorSpaceXformEffect::Make(std::move(fp), result->getColorSpace(),
+ fp = GrColorSpaceXformEffect::Make(std::move(fp), result->getColorSpace(), config,
fRenderTargetContext->colorSpaceInfo().colorSpace());
if (GrPixelConfigIsAlphaOnly(config)) {
fp = GrFragmentProcessor::MakeInputPremulAndMulByOutput(std::move(fp));
diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp
index 8d0ed6eabd..9838f46ee2 100644
--- a/src/gpu/SkGpuDevice_drawTexture.cpp
+++ b/src/gpu/SkGpuDevice_drawTexture.cpp
@@ -114,7 +114,8 @@ static void draw_texture_affine(const SkPaint& paint, const SkMatrix& ctm, const
SkAssertResult(srcRect.intersect(SkRect::MakeIWH(proxy->width(), proxy->height())));
srcToDst.mapRect(&dstRect, srcRect);
}
- auto csxf = GrColorSpaceXform::Make(colorSpace, rtc->colorSpaceInfo().colorSpace());
+ auto csxf = GrColorSpaceXform::Make(colorSpace, proxy->config(),
+ rtc->colorSpaceInfo().colorSpace());
GrSamplerState::Filter filter;
switch (paint.getFilterQuality()) {
case kNone_SkFilterQuality:
diff --git a/src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp b/src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp
index 7bdb04ec59..cc48356e0d 100644
--- a/src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp
+++ b/src/gpu/effects/GrNonlinearColorSpaceXformEffect.cpp
@@ -229,11 +229,11 @@ std::unique_ptr<GrFragmentProcessor> GrNonlinearColorSpaceXformEffect::Make(
uint32_t ops = 0;
// We rely on GrColorSpaceXform to build the gamut xform matrix for us (to get caching)
- auto gamutXform = GrColorSpaceXform::Make(src, dst);
+ auto gamutXform = GrColorSpaceXform::MakeGamutXform(src, dst);
SkMatrix44 srcToDstMtx(SkMatrix44::kUninitialized_Constructor);
if (gamutXform) {
ops |= kGamutXform_Op;
- srcToDstMtx = gamutXform->srcToDst();
+ srcToDstMtx = gamutXform->gamutXform();
}
SkColorSpaceTransferFn srcTransferFn;
diff --git a/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h b/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h
index f9ac30b6aa..527de91c50 100644
--- a/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h
+++ b/src/gpu/glsl/GrGLSLColorSpaceXformHelper.h
@@ -18,28 +18,56 @@
*/
class GrGLSLColorSpaceXformHelper : public SkNoncopyable {
public:
- GrGLSLColorSpaceXformHelper() : fValid(false) {}
+ GrGLSLColorSpaceXformHelper() : fFlags(0) {}
void emitCode(GrGLSLUniformHandler* uniformHandler, const GrColorSpaceXform* colorSpaceXform,
uint32_t visibility = kFragment_GrShaderFlag) {
SkASSERT(uniformHandler);
if (colorSpaceXform) {
- fGamutXformVar = uniformHandler->addUniform(visibility, kHalf4x4_GrSLType,
- "ColorXform");
- fValid = true;
+ fFlags = colorSpaceXform->fFlags;
+ if (this->applyGamutXform()) {
+ fGamutXformVar = uniformHandler->addUniform(visibility,
+ kHalf4x4_GrSLType,
+ "ColorXform");
+ }
+ if (this->applyTransferFn()) {
+ fTransferFnVar = uniformHandler->addUniformArray(visibility,
+ kHalf_GrSLType,
+ "TransferFn",
+ kNumTransferFnCoeffs);
+ }
}
}
void setData(const GrGLSLProgramDataManager& pdman, const GrColorSpaceXform* colorSpaceXform) {
- pdman.setSkMatrix44(fGamutXformVar, colorSpaceXform->srcToDst());
+ if (this->applyGamutXform()) {
+ pdman.setSkMatrix44(fGamutXformVar, colorSpaceXform->gamutXform());
+ }
+ if (this->applyTransferFn()) {
+ pdman.set1fv(fTransferFnVar, kNumTransferFnCoeffs, colorSpaceXform->transferFnCoeffs());
+ }
}
- bool isValid() const { return fValid; }
- GrGLSLProgramDataManager::UniformHandle const gamutXformUniform() { return fGamutXformVar; }
+ bool isValid() const { return (0 != fFlags); }
+ bool applyInverseSRGB() const {
+ return SkToBool(fFlags & GrColorSpaceXform::kApplyInverseSRGB_Flag);
+ }
+ bool applyTransferFn() const {
+ return SkToBool(fFlags & GrColorSpaceXform::kApplyTransferFn_Flag);
+ }
+ bool applyGamutXform() const {
+ return SkToBool(fFlags & GrColorSpaceXform::kApplyGamutXform_Flag);
+ }
+
+ GrGLSLProgramDataManager::UniformHandle gamutXformUniform() const { return fGamutXformVar; }
+ GrGLSLProgramDataManager::UniformHandle transferFnUniform() const { return fTransferFnVar; }
private:
+ static const int kNumTransferFnCoeffs = 7;
+
GrGLSLProgramDataManager::UniformHandle fGamutXformVar;
- bool fValid;
+ GrGLSLProgramDataManager::UniformHandle fTransferFnVar;
+ uint32_t fFlags;
};
#endif
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.cpp b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
index f87194e3d3..750df63a79 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
@@ -118,29 +118,78 @@ void GrGLSLShaderBuilder::appendTextureLookupAndModulate(
void GrGLSLShaderBuilder::appendColorGamutXform(SkString* out,
const char* srcColor,
GrGLSLColorSpaceXformHelper* colorXformHelper) {
- // Our color is (r, g, b, a), but we want to multiply (r, g, b, 1) by our matrix, then
- // re-insert the original alpha. The supplied srcColor is likely to be of the form
- // "texture(...)", and we don't want to evaluate that twice, so wrap everything in a function.
- static const GrShaderVar gColorGamutXformArgs[] = {
- GrShaderVar("color", kHalf4_GrSLType),
- GrShaderVar("xform", kHalf4x4_GrSLType),
- };
- SkString functionBody;
- // Gamut xform, clamp to destination gamut. We only support/have premultiplied textures, so we
- // always just clamp to alpha.
- functionBody.append("\tcolor.rgb = clamp((xform * half4(color.rgb, 1.0)).rgb, 0.0, color.a);\n");
- functionBody.append("\treturn color;");
- SkString colorGamutXformFuncName;
- this->emitFunction(kHalf4_GrSLType,
- "colorGamutXform",
- SK_ARRAY_COUNT(gColorGamutXformArgs),
- gColorGamutXformArgs,
- functionBody.c_str(),
- &colorGamutXformFuncName);
-
GrGLSLUniformHandler* uniformHandler = fProgramBuilder->uniformHandler();
- out->appendf("%s(%s, %s)", colorGamutXformFuncName.c_str(), srcColor,
- uniformHandler->getUniformCStr(colorXformHelper->gamutXformUniform()));
+
+ // We define up to three helper functions, to keep things clearer. One does inverse sRGB,
+ // one does an arbitrary transfer function, and the last does gamut xform. Any combination of
+ // these may be present, although some configurations are much more likely.
+
+ SkString inverseSrgbFuncName;
+ if (colorXformHelper->applyInverseSRGB()) {
+ static const GrShaderVar gInverseSRGBArgs[] = { GrShaderVar("x", kHalf_GrSLType) };
+ SkString body;
+ body.append("return (x <= 0.0031308) ? (x * 12.92) : (1.055 * pow(x, 0.4166667) - 0.055);");
+ this->emitFunction(kHalf_GrSLType, "inverse_srgb", SK_ARRAY_COUNT(gInverseSRGBArgs),
+ gInverseSRGBArgs, body.c_str(), &inverseSrgbFuncName);
+
+ }
+
+ SkString transferFnFuncName;
+ if (colorXformHelper->applyTransferFn()) {
+ static const GrShaderVar gTransferFnArgs[] = { GrShaderVar("x", kHalf_GrSLType) };
+ const char* coeffs = uniformHandler->getUniformCStr(colorXformHelper->transferFnUniform());
+ SkString body;
+ // Temporaries to make evaluation line readable
+ body.appendf("half G = %s[0];", coeffs);
+ body.appendf("half A = %s[1];", coeffs);
+ body.appendf("half B = %s[2];", coeffs);
+ body.appendf("half C = %s[3];", coeffs);
+ body.appendf("half D = %s[4];", coeffs);
+ body.appendf("half E = %s[5];", coeffs);
+ body.appendf("half F = %s[6];", coeffs);
+ body.append("half s = sign(x);");
+ body.append("x = abs(x);");
+ body.appendf("return s * ((x < D) ? (C * x) + F : pow(A * x + B, G) + E);");
+ this->emitFunction(kHalf_GrSLType, "transfer_fn", SK_ARRAY_COUNT(gTransferFnArgs),
+ gTransferFnArgs, body.c_str(), &transferFnFuncName);
+ }
+
+ SkString gamutXformFuncName;
+ if (colorXformHelper->applyGamutXform()) {
+ // Our color is (r, g, b, a), but we want to multiply (r, g, b, 1) by our matrix, then
+ // re-insert the original alpha.
+ static const GrShaderVar gGamutXformArgs[] = { GrShaderVar("color", kHalf4_GrSLType) };
+ const char* xform = uniformHandler->getUniformCStr(colorXformHelper->gamutXformUniform());
+ SkString body;
+ body.appendf("color.rgb = clamp((%s * half4(color.rgb, 1.0)).rgb, 0.0, color.a);", xform);
+ body.append("return color;");
+ this->emitFunction(kHalf4_GrSLType, "gamut_xform", SK_ARRAY_COUNT(gGamutXformArgs),
+ gGamutXformArgs, body.c_str(), &gamutXformFuncName);
+ }
+
+ // Now define a wrapper function that applies all the intermediate steps
+ {
+ static const GrShaderVar gColorXformArgs[] = { GrShaderVar("color", kHalf4_GrSLType) };
+ SkString body;
+ if (colorXformHelper->applyInverseSRGB()) {
+ body.appendf("color.r = %s(color.r);", inverseSrgbFuncName.c_str());
+ body.appendf("color.g = %s(color.g);", inverseSrgbFuncName.c_str());
+ body.appendf("color.b = %s(color.b);", inverseSrgbFuncName.c_str());
+ }
+ if (colorXformHelper->applyTransferFn()) {
+ body.appendf("color.r = %s(color.r);", transferFnFuncName.c_str());
+ body.appendf("color.g = %s(color.g);", transferFnFuncName.c_str());
+ body.appendf("color.b = %s(color.b);", transferFnFuncName.c_str());
+ }
+ if (colorXformHelper->applyGamutXform()) {
+ body.appendf("color = %s(color);", gamutXformFuncName.c_str());
+ }
+ body.append("return color;");
+ SkString colorXformFuncName;
+ this->emitFunction(kHalf4_GrSLType, "color_xform", SK_ARRAY_COUNT(gColorXformArgs),
+ gColorXformArgs, body.c_str(), &colorXformFuncName);
+ out->appendf("%s(%s)", colorXformFuncName.c_str(), srcColor);
+ }
}
void GrGLSLShaderBuilder::appendColorGamutXform(const char* srcColor,
diff --git a/src/shaders/SkColorShader.cpp b/src/shaders/SkColorShader.cpp
index 4bc7d71194..861898737b 100644
--- a/src/shaders/SkColorShader.cpp
+++ b/src/shaders/SkColorShader.cpp
@@ -211,8 +211,10 @@ SkShader::GradientType SkColor4Shader::asAGradient(GradientInfo* info) const {
std::unique_ptr<GrFragmentProcessor> SkColor4Shader::asFragmentProcessor(
const AsFPArgs& args) const {
- sk_sp<GrColorSpaceXform> colorSpaceXform =
- GrColorSpaceXform::Make(fColorSpace.get(), args.fDstColorSpaceInfo->colorSpace());
+ // Construct an xform assuming float inputs. The color space can have a transfer function on
+ // it, which will be applied below.
+ auto colorSpaceXform = GrColorSpaceXform::Make(fColorSpace.get(), kRGBA_float_GrPixelConfig,
+ args.fDstColorSpaceInfo->colorSpace());
GrColor4f color = GrColor4f::FromSkColor4f(fColor4);
if (colorSpaceXform) {
color = colorSpaceXform->clampedXform(color);
diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp
index 753024dbad..46b20b1de3 100644
--- a/src/shaders/SkImageShader.cpp
+++ b/src/shaders/SkImageShader.cpp
@@ -218,7 +218,8 @@ std::unique_ptr<GrFragmentProcessor> SkImageShader::asFragmentProcessor(
return nullptr;
}
- bool isAlphaOnly = GrPixelConfigIsAlphaOnly(proxy->config());
+ GrPixelConfig config = proxy->config();
+ bool isAlphaOnly = GrPixelConfigIsAlphaOnly(config);
lmInverse.postScale(scaleAdjust[0], scaleAdjust[1]);
@@ -228,7 +229,7 @@ std::unique_ptr<GrFragmentProcessor> SkImageShader::asFragmentProcessor(
} else {
inner = GrSimpleTextureEffect::Make(std::move(proxy), lmInverse, samplerState);
}
- inner = GrColorSpaceXformEffect::Make(std::move(inner), texColorSpace.get(),
+ inner = GrColorSpaceXformEffect::Make(std::move(inner), texColorSpace.get(), config,
args.fDstColorSpaceInfo->colorSpace());
if (isAlphaOnly) {
return inner;
diff --git a/src/shaders/gradients/SkGradientShader.cpp b/src/shaders/gradients/SkGradientShader.cpp
index 7d31e50f84..279491b177 100644
--- a/src/shaders/gradients/SkGradientShader.cpp
+++ b/src/shaders/gradients/SkGradientShader.cpp
@@ -184,6 +184,7 @@ SkGradientShaderBase::SkGradientShaderBase(const Descriptor& desc, const SkMatri
fColorSpace = SkColorSpace::MakeSRGBLinear();
} else {
// The color space refers to the float colors, so it must be linear gamma
+ // TODO: GPU code no longer requires this (see GrGradientEffect). Remove this restriction?
SkASSERT(desc.fColorSpace->gammaIsLinear());
fColorSpace = desc.fColorSpace;
}
@@ -1260,8 +1261,11 @@ GrGradientEffect::GrGradientEffect(ClassID classID, const CreateArgs& args, bool
fPremulType = kAfterInterp_PremulType;
}
- // Convert input colors to GrColor4f, possibly premul, and apply color space xform
+ // Convert input colors to GrColor4f, possibly premul, and apply color space xform.
+ // The xform is constructed assuming floats as input, but the color space can have a
+ // transfer function on it, which will be applied below.
auto colorSpaceXform = GrColorSpaceXform::Make(shader.fColorSpace.get(),
+ kRGBA_float_GrPixelConfig,
args.fDstColorSpace);
SkASSERT(shader.fOrigColors && shader.fOrigColors4f);
fColors4f.setCount(shader.fColorCount);
diff --git a/src/shaders/gradients/SkGradientShaderPriv.h b/src/shaders/gradients/SkGradientShaderPriv.h
index 8fabc5c4fd..0d8e7bef9a 100644
--- a/src/shaders/gradients/SkGradientShaderPriv.h
+++ b/src/shaders/gradients/SkGradientShaderPriv.h
@@ -277,8 +277,13 @@ protected:
// xform is needed. With texture-based gradients, we leave the data in the source color
// space (to avoid clamping if we can't use F16)... Add an extra FP to do the xform.
if (kTexture_ColorType == gradientFP->getColorType()) {
+ // Our texture is always either F16 or sRGB, so the data is "linear" in the shader.
+ // Create our xform assuming float inputs, which will suppress any extra sRGB work.
+ // We do support having a transfer function on the color space of the stops, so
+ // this FP may include that transformation.
fp = GrColorSpaceXformEffect::Make(std::move(gradientFP),
args.fShader->fColorSpace.get(),
+ kRGBA_float_GrPixelConfig,
args.fDstColorSpace);
} else {
fp = std::move(gradientFP);