Fix subset behavior in makeWithFilter.

Most image filters were fixed with just the changes to SkImage::makeWithFilter
and the changes to SkSpecialImage's subset handling (particularly the
raster backend that could read from a bitmap view, or ganesh impls that relied
SkSpecialImage::draw).

The gpu implementation for alpha threshold, blurs, matrix convolutions,
and displacement maps have been updated to account for the special image's
offset when it reads from the backing texture.

Change-Id: I8778aa373e60e9268961305057b2bf6da2bdb3af
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/221121
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Michael Ludwig 2019-06-21 16:01:42 -04:00 committed by Skia Commit-Bot
parent 9b06f2168d
commit b4580351c7
14 changed files with 156 additions and 94 deletions

View File

@ -421,6 +421,10 @@ private:
typedef GM INHERITED;
};
// The different strategies should all look the same, with the exception of filters that affect
// transparent black (i.e. the lighting filter). In the save layer case, the filter affects the
// transparent pixels outside of the drawn subset, whereas the makeWithFilter is restricted. This
// works as intended.
DEF_GM( return new ImageMakeWithFilterGM(Strategy::kMakeWithFilter); )
DEF_GM( return new ImageMakeWithFilterGM(Strategy::kSaveLayer); )
// Test with crop rects on the image filters; should look identical to above if working correctly

View File

@ -884,6 +884,7 @@ sk_sp<GrTextureProxy> SkBlurMaskFilterImpl::filterMaskGPU(GrRecordingContext* co
sk_sp<GrRenderTargetContext> renderTargetContext(
SkGpuBlurUtils::GaussianBlur(context,
srcProxy,
SkIPoint::Make(0, 0),
nullptr,
clipRect,
SkIRect::EmptyIRect(),

View File

@ -152,8 +152,16 @@ static sk_sp<GrRenderTargetContext> convolve_gaussian_2d(GrRecordingContext* con
return renderTargetContext;
}
// NOTE: Both convolve_gaussian or decimate accept a proxyOffset. This is separate from the
// srcBounds and srcOffset, which are relative to the content rect of the image, whereas proxyOffset
// maps from the content rect to the proxy's coordinate space. Due to how the destination bounds are
// calculated, it is more convenient to have the proxy offset kept separate from the logical bounds
// (which do impact destination decisions). Both functions incorporate the proxy offset into the
// geometry they submit or before calling convolve_gaussian_1d.
static sk_sp<GrRenderTargetContext> convolve_gaussian(GrRecordingContext* context,
sk_sp<GrTextureProxy> proxy,
const SkIPoint& proxyOffset,
const SkIRect& srcRect,
const SkIPoint& srcOffset,
Direction direction,
@ -189,20 +197,22 @@ static sk_sp<GrRenderTargetContext> convolve_gaussian(GrRecordingContext* contex
int bounds[2] = { 0, 0 };
SkIRect dstRect = SkIRect::MakeWH(srcRect.width(), srcRect.height());
SkIPoint netOffset = srcOffset - proxyOffset;
if (GrTextureDomain::kIgnore_Mode == mode) {
*contentRect = dstRect;
convolve_gaussian_1d(dstRenderTargetContext.get(), clip, dstRect, srcOffset,
convolve_gaussian_1d(dstRenderTargetContext.get(), clip, dstRect, netOffset,
std::move(proxy), direction, radius, sigma,
GrTextureDomain::kIgnore_Mode, bounds);
return dstRenderTargetContext;
}
// These destination rects need to be adjusted by srcOffset, but should *not* be adjusted by
// the proxyOffset, which is why keeping them separate is convenient.
SkIRect midRect = *contentRect, leftRect, rightRect;
midRect.offset(srcOffset);
SkIRect topRect, bottomRect;
if (Direction::kX == direction) {
bounds[0] = contentRect->left();
bounds[1] = contentRect->right();
bounds[0] = contentRect->left() + proxyOffset.x();
bounds[1] = contentRect->right() + proxyOffset.x();
topRect = SkIRect::MakeLTRB(0, 0, dstRect.right(), midRect.top());
bottomRect = SkIRect::MakeLTRB(0, midRect.bottom(), dstRect.right(), dstRect.bottom());
midRect.inset(radius, 0);
@ -217,8 +227,8 @@ static sk_sp<GrRenderTargetContext> convolve_gaussian(GrRecordingContext* contex
contentRect->fRight = dstRect.fRight;
contentRect->fBottom = midRect.fBottom;
} else {
bounds[0] = contentRect->top();
bounds[1] = contentRect->bottom();
bounds[0] = contentRect->top() + proxyOffset.y();
bounds[1] = contentRect->bottom() + proxyOffset.y();
topRect = SkIRect::MakeLTRB(0, 0, midRect.left(), dstRect.bottom());
bottomRect = SkIRect::MakeLTRB(midRect.right(), 0, dstRect.right(), dstRect.bottom());
midRect.inset(0, radius);
@ -245,15 +255,15 @@ static sk_sp<GrRenderTargetContext> convolve_gaussian(GrRecordingContext* contex
if (midRect.isEmpty()) {
// Blur radius covers srcBounds; use bounds over entire draw
convolve_gaussian_1d(dstRenderTargetContext.get(), clip, dstRect, srcOffset,
convolve_gaussian_1d(dstRenderTargetContext.get(), clip, dstRect, netOffset,
std::move(proxy), direction, radius, sigma, mode, bounds);
} else {
// Draw right and left margins with bounds; middle without.
convolve_gaussian_1d(dstRenderTargetContext.get(), clip, leftRect, srcOffset,
convolve_gaussian_1d(dstRenderTargetContext.get(), clip, leftRect, netOffset,
proxy, direction, radius, sigma, mode, bounds);
convolve_gaussian_1d(dstRenderTargetContext.get(), clip, rightRect, srcOffset,
convolve_gaussian_1d(dstRenderTargetContext.get(), clip, rightRect, netOffset,
proxy, direction, radius, sigma, mode, bounds);
convolve_gaussian_1d(dstRenderTargetContext.get(), clip, midRect, srcOffset,
convolve_gaussian_1d(dstRenderTargetContext.get(), clip, midRect, netOffset,
std::move(proxy), direction, radius, sigma,
GrTextureDomain::kIgnore_Mode, bounds);
}
@ -263,6 +273,7 @@ static sk_sp<GrRenderTargetContext> convolve_gaussian(GrRecordingContext* contex
static sk_sp<GrTextureProxy> decimate(GrRecordingContext* context,
sk_sp<GrTextureProxy> src,
const SkIPoint& proxyOffset,
SkIPoint* srcOffset,
SkIRect* contentRect,
int scaleFactorX, int scaleFactorY,
@ -328,6 +339,7 @@ static sk_sp<GrTextureProxy> decimate(GrRecordingContext* context,
if (domain.fBottom < domain.fTop) {
domain.fTop = domain.fBottom = SkScalarAve(domain.fTop, domain.fBottom);
}
domain.offset(proxyOffset.x(), proxyOffset.y());
auto fp = GrTextureDomainEffect::Make(std::move(src),
SkMatrix::I(),
domain,
@ -344,10 +356,10 @@ static sk_sp<GrTextureProxy> decimate(GrRecordingContext* context,
}
paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
GrFixedClip clip(dstRect);
dstRenderTargetContext->fillRectToRect(clip, std::move(paint), GrAA::kNo,
dstRenderTargetContext->fillRectToRect(GrFixedClip::Disabled(), std::move(paint), GrAA::kNo,
SkMatrix::I(), SkRect::Make(dstRect),
SkRect::Make(srcRect));
SkRect::Make(srcRect.makeOffset(proxyOffset.x(),
proxyOffset.y())));
src = dstRenderTargetContext->asTextureProxyRef();
if (!src) {
@ -381,7 +393,9 @@ static sk_sp<GrTextureProxy> decimate(GrRecordingContext* context,
return dstRenderTargetContext->asTextureProxyRef();
}
// Expand the contents of 'srcRenderTargetContext' to fit in 'dstII'.
// Expand the contents of 'srcRenderTargetContext' to fit in 'dstII'. At this point, we are
// expanding an intermediate image, so there's no need to account for a proxy offset from the
// original input.
static sk_sp<GrRenderTargetContext> reexpand(GrRecordingContext* context,
sk_sp<GrRenderTargetContext> srcRenderTargetContext,
const SkIRect& localSrcBounds,
@ -460,6 +474,7 @@ namespace SkGpuBlurUtils {
sk_sp<GrRenderTargetContext> GaussianBlur(GrRecordingContext* context,
sk_sp<GrTextureProxy> srcProxy,
const SkIPoint& proxyOffset,
sk_sp<SkColorSpace> colorSpace,
const SkIRect& dstBounds,
const SkIRect& srcBounds,
@ -487,6 +502,8 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrRecordingContext* context,
SkASSERT(sigmaX || sigmaY);
SkIPoint srcOffset = SkIPoint::Make(-dstBounds.x(), -dstBounds.y());
SkIRect localSrcBounds = srcBounds;
SkIPoint localProxyOffset = proxyOffset;
// For really small blurs (certainly no wider than 5x5 on desktop gpus) it is faster to just
// launch a single non separable kernel vs two launches
@ -494,10 +511,11 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrRecordingContext* context,
(2 * radiusX + 1) * (2 * radiusY + 1) <= MAX_KERNEL_SIZE) {
// We shouldn't be scaling because this is a small size blur
SkASSERT((1 == scaleFactorX) && (1 == scaleFactorY));
return convolve_gaussian_2d(context, std::move(srcProxy), srcBounds, srcOffset,
radiusX, radiusY, sigmaX, sigmaY,
mode, finalDestII, fit);
// Apply the proxy offset to src bounds and offset directly
srcOffset -= proxyOffset;
localSrcBounds.offset(proxyOffset);
return convolve_gaussian_2d(context, std::move(srcProxy), localSrcBounds, srcOffset,
radiusX, radiusY, sigmaX, sigmaY, mode, finalDestII, fit);
}
// Only the last rendered renderTargetContext needs to match the supplied 'fit'
@ -508,12 +526,11 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrRecordingContext* context,
xFit = SkBackingFit::kApprox; // the y-pass will be last
}
SkIRect localSrcBounds = srcBounds;
if (scaleFactorX > 1 || scaleFactorY > 1) {
srcProxy = decimate(context, std::move(srcProxy), &srcOffset, &localSrcBounds,
scaleFactorX, scaleFactorY, sigmaX > 0.0f, sigmaY > 0.0f,
radiusX, radiusY, mode, finalDestII);
srcProxy = decimate(context, std::move(srcProxy), localProxyOffset, &srcOffset,
&localSrcBounds, scaleFactorX, scaleFactorY, sigmaX > 0.0f,
sigmaY > 0.0f, radiusX, radiusY, mode, finalDestII);
localProxyOffset.set(0, 0);
if (!srcProxy) {
return nullptr;
}
@ -524,9 +541,9 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrRecordingContext* context,
SkIRect srcRect = finalDestII.bounds();
scale_irect_roundout(&srcRect, 1.0f / scaleFactorX, 1.0f / scaleFactorY);
if (sigmaX > 0.0f) {
dstRenderTargetContext = convolve_gaussian(context, std::move(srcProxy), srcRect, srcOffset,
Direction::kX, radiusX, sigmaX, &localSrcBounds,
mode, finalDestII, xFit);
dstRenderTargetContext = convolve_gaussian(
context, std::move(srcProxy), localProxyOffset, srcRect, srcOffset, Direction::kX,
radiusX, sigmaX, &localSrcBounds, mode, finalDestII, xFit);
if (!dstRenderTargetContext) {
return nullptr;
}
@ -546,12 +563,13 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrRecordingContext* context,
srcRect.offsetTo(0, 0);
srcOffset.set(0, 0);
localProxyOffset.set(0, 0);
}
if (sigmaY > 0.0f) {
dstRenderTargetContext = convolve_gaussian(context, std::move(srcProxy), srcRect, srcOffset,
Direction::kY, radiusY, sigmaY, &localSrcBounds,
mode, finalDestII, yFit);
dstRenderTargetContext = convolve_gaussian(
context, std::move(srcProxy), localProxyOffset, srcRect, srcOffset, Direction::kY,
radiusY, sigmaY, &localSrcBounds, mode, finalDestII, yFit);
if (!dstRenderTargetContext) {
return nullptr;
}
@ -563,10 +581,12 @@ sk_sp<GrRenderTargetContext> GaussianBlur(GrRecordingContext* context,
srcRect.offsetTo(0, 0);
srcOffset.set(0, 0);
localProxyOffset.set(0, 0);
}
SkASSERT(dstRenderTargetContext);
SkASSERT(srcProxy.get() == dstRenderTargetContext->asTextureProxy());
SkASSERT(localProxyOffset.x() == 0 && localProxyOffset.y() == 0);
if (scaleFactorX > 1 || scaleFactorY > 1) {
dstRenderTargetContext = reexpand(context, std::move(dstRenderTargetContext),

View File

@ -21,14 +21,21 @@ namespace SkGpuBlurUtils {
/**
* Applies a 2D Gaussian blur to a given texture. The blurred result is returned
* as a renderTargetContext in case the caller wishes to future draw into the result.
*
* The 'proxyOffset' is kept separate form 'srcBounds' because they exist in different
* coordinate spaces. 'srcBounds' exists in the content space of the special image, and
* 'proxyOffset' maps from the content space to the proxy's space.
*
* Note: one of sigmaX and sigmaY should be non-zero!
* @param context The GPU context
* @param src The source to be blurred.
* @param proxyOffset The offset from the top-left corner to valid texels in 'src', which
* should come from the subset of the owning SkSpecialImage.
* @param colorSpace Color space of the source (used for the renderTargetContext result,
* too).
* @param dstBounds The destination bounds, relative to the source texture.
* @param srcBounds The source bounds, relative to the source texture. If non-null,
* no pixels will be sampled outside of this rectangle.
* @param srcBounds The source bounds, relative to the source texture's offset. No pixels
* will be sampled outside of this rectangle.
* @param sigmaX The blur's standard deviation in X.
* @param sigmaY The blur's standard deviation in Y.
* @param mode The mode to handle samples outside bounds.
@ -38,6 +45,7 @@ namespace SkGpuBlurUtils {
sk_sp<GrRenderTargetContext> GaussianBlur(
GrRecordingContext* context,
sk_sp<GrTextureProxy> src,
const SkIPoint& proxyOffset,
sk_sp<SkColorSpace> colorSpace,
const SkIRect& dstBounds,
const SkIRect& srcBounds,

View File

@ -53,12 +53,16 @@ public:
virtual sk_sp<GrTextureProxy> onAsTextureProxyRef(GrRecordingContext* context) const = 0;
#endif
// This subset is relative to the backing store's coordinate frame, it has already been mapped
// from the content rect by the non-virtual makeSubset().
virtual sk_sp<SkSpecialImage> onMakeSubset(const SkIRect& subset) const = 0;
virtual sk_sp<SkSpecialSurface> onMakeSurface(const SkImageFilter::OutputProperties& outProps,
const SkISize& size, SkAlphaType at,
const SkSurfaceProps* = nullptr) const = 0;
// This subset (when not null) is relative to the backing store's coordinate frame, it has
// already been mapped from the content rect by the non-virtual asImage().
virtual sk_sp<SkImage> onAsImage(const SkIRect* subset) const = 0;
virtual sk_sp<SkSurface> onMakeTightSurface(const SkImageFilter::OutputProperties& outProps,
@ -165,11 +169,17 @@ sk_sp<SkSurface> SkSpecialImage::makeTightSurface(const SkImageFilter::OutputPro
}
sk_sp<SkSpecialImage> SkSpecialImage::makeSubset(const SkIRect& subset) const {
return as_SIB(this)->onMakeSubset(subset);
SkIRect absolute = subset.makeOffset(this->subset().x(), this->subset().y());
return as_SIB(this)->onMakeSubset(absolute);
}
sk_sp<SkImage> SkSpecialImage::asImage(const SkIRect* subset) const {
return as_SIB(this)->onAsImage(subset);
if (subset) {
SkIRect absolute = subset->makeOffset(this->subset().x(), this->subset().y());
return as_SIB(this)->onAsImage(&absolute);
} else {
return as_SIB(this)->onAsImage(nullptr);
}
}
#if defined(SK_DEBUG) || SK_SUPPORT_GPU
@ -236,8 +246,7 @@ public:
}
bool onGetROPixels(SkBitmap* bm) const override {
*bm = fBitmap;
return true;
return fBitmap.extractSubset(bm, this->subset());
}
SkColorSpace* onGetColorSpace() const override {
@ -265,15 +274,8 @@ public:
}
sk_sp<SkSpecialImage> onMakeSubset(const SkIRect& subset) const override {
SkBitmap subsetBM;
if (!fBitmap.extractSubset(&subsetBM, subset)) {
return nullptr;
}
return SkSpecialImage::MakeFromRaster(SkIRect::MakeWH(subset.width(), subset.height()),
subsetBM,
&this->props());
// No need to extract subset, onGetROPixels handles that when needed
return SkSpecialImage::MakeFromRaster(subset, fBitmap, &this->props());
}
sk_sp<SkImage> onAsImage(const SkIRect* subset) const override {

View File

@ -65,7 +65,7 @@ public:
sk_sp<SkSpecialImage> makeTextureImage(GrRecordingContext*);
/**
* Draw this SpecialImage into the canvas.
* Draw this SpecialImage into the canvas, automatically taking into account the image's subset
*/
void draw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) const;
@ -107,7 +107,8 @@ public:
/**
* Extract a subset of this special image and return it as a special image.
* It may or may not point to the same backing memory.
* It may or may not point to the same backing memory. The input 'subset' is relative to the
* special image's content rect.
*/
sk_sp<SkSpecialImage> makeSubset(const SkIRect& subset) const;
@ -117,7 +118,7 @@ public:
* Note: when no 'subset' parameter is specified the the entire SkSpecialImage will be
* returned - including whatever extra padding may have resulted from a loose fit!
* When the 'subset' parameter is specified the returned image will be tight even if that
* entails a copy!
* entails a copy! The 'subset' is relative to this special image's content rect.
*/
sk_sp<SkImage> asImage(const SkIRect* subset = nullptr) const;
@ -133,17 +134,18 @@ public:
#if SK_SUPPORT_GPU
/**
* Regardless of the underlying backing store, return the contents as a GrTextureProxy.
* The active portion of the texture can be retrieved via 'subset'.
* Regardless of how the underlying backing data is stored, returns the contents as a
* GrTextureProxy. The returned proxy represents the entire backing image, so texture
* coordinates must be mapped from the content rect (e.g. relative to 'subset()') to the proxy's
* space (offset by subset().topLeft()).
*/
sk_sp<GrTextureProxy> asTextureProxyRef(GrRecordingContext*) const;
#endif
/**
* Regardless of the underlying backing store, return the contents as an SkBitmap
*
* The returned ImageInfo represents the backing memory. Use 'subset'
* to get the active portion's dimensions.
* Regardless of the underlying backing store, return the contents as an SkBitmap.
* The returned bitmap represents the subset accessed by this image, thus (0,0) refers to the
* top-left corner of 'subset'.
*/
bool getROPixels(SkBitmap*) const;

View File

@ -180,7 +180,9 @@ sk_sp<SkSpecialImage> SkAlphaThresholdFilterImpl::onFilterImage(SkSpecialImage*
}
const OutputProperties& outProps = ctx.outputProperties();
auto textureFP = GrSimpleTextureEffect::Make(std::move(inputProxy), SkMatrix::I());
auto textureFP = GrSimpleTextureEffect::Make(std::move(inputProxy),
SkMatrix::MakeTrans(input->subset().x(),
input->subset().y()));
textureFP = GrColorSpaceXformEffect::Make(std::move(textureFP), input->getColorSpace(),
input->alphaType(), outProps.colorSpace());
if (!textureFP) {

View File

@ -599,7 +599,6 @@ sk_sp<SkSpecialImage> SkBlurImageFilterImpl::onFilterImage(SkSpecialImage* sourc
// Ensure the input is in the destination's gamut. This saves us from having to do the
// xform during the filter itself.
input = ImageToColorSpace(input.get(), ctx.outputProperties());
result = this->gpuFilter(source, sigma, input, inputBounds, dstBounds, inputOffset,
ctx.outputProperties(), &resultOffset);
} else
@ -637,6 +636,7 @@ sk_sp<SkSpecialImage> SkBlurImageFilterImpl::gpuFilter(
sk_sp<GrRenderTargetContext> renderTargetContext(SkGpuBlurUtils::GaussianBlur(
context,
std::move(inputTexture),
input->subset().topLeft(),
outProps.colorSpace() ? sk_ref_sp(input->getColorSpace()) : nullptr,
dstBounds,
inputBounds,

View File

@ -156,11 +156,11 @@ public:
static std::unique_ptr<GrFragmentProcessor> Make(
SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
SkDisplacementMapEffect::ChannelSelectorType yChannelSelector, SkVector scale,
sk_sp<GrTextureProxy> displacement, const SkMatrix& offsetMatrix,
sk_sp<GrTextureProxy> color, const SkISize& colorDimensions) {
sk_sp<GrTextureProxy> displacement, const SkIRect& displSubset,
const SkMatrix& offsetMatrix, sk_sp<GrTextureProxy> color, const SkIRect& colorSubset) {
return std::unique_ptr<GrFragmentProcessor>(new GrDisplacementMapEffect(
xChannelSelector, yChannelSelector, scale, std::move(displacement), offsetMatrix,
std::move(color), colorDimensions));
xChannelSelector, yChannelSelector, scale, std::move(displacement), displSubset,
offsetMatrix, std::move(color), colorSubset));
}
~GrDisplacementMapEffect() override;
@ -189,9 +189,9 @@ private:
GrDisplacementMapEffect(SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
const SkVector& scale,
sk_sp<GrTextureProxy> displacement, const SkMatrix& offsetMatrix,
sk_sp<GrTextureProxy> color, const SkISize& colorDimensions);
const SkVector& scale, sk_sp<GrTextureProxy> displacement,
const SkIRect& displSubset, const SkMatrix& offsetMatrix,
sk_sp<GrTextureProxy> color, const SkIRect& colorSubset);
const TextureSampler& onTextureSampler(int i) const override {
return IthTextureSampler(i, fDisplacementSampler, fColorSampler);
@ -288,9 +288,10 @@ sk_sp<SkSpecialImage> SkDisplacementMapEffect::onFilterImage(SkSpecialImage* sou
fYChannelSelector,
scale,
std::move(displProxy),
displ->subset(),
offsetMatrix,
std::move(colorProxy),
SkISize::Make(color->width(), color->height()));
color->subset());
fp = GrColorSpaceXformEffect::Make(std::move(fp), color->getColorSpace(),
color->alphaType(), colorSpace);
@ -418,16 +419,20 @@ GrDisplacementMapEffect::GrDisplacementMapEffect(
SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
const SkVector& scale,
sk_sp<GrTextureProxy> displacement,
const SkIRect& displSubset,
const SkMatrix& offsetMatrix,
sk_sp<GrTextureProxy> color,
const SkISize& colorDimensions)
const SkIRect& colorSubset)
: INHERITED(kGrDisplacementMapEffect_ClassID,
GrFragmentProcessor::kNone_OptimizationFlags)
, fDisplacementTransform(offsetMatrix, displacement.get())
, fDisplacementTransform(
SkMatrix::Concat(SkMatrix::MakeTrans(displSubset.x(), displSubset.y()),
offsetMatrix),
displacement.get())
, fDisplacementSampler(displacement)
, fColorTransform(color.get())
, fColorTransform(SkMatrix::MakeTrans(colorSubset.x(), colorSubset.y()), color.get())
, fDomain(color.get(),
GrTextureDomain::MakeTexelDomain(SkIRect::MakeSize(colorDimensions),
GrTextureDomain::MakeTexelDomain(colorSubset,
GrTextureDomain::kDecal_Mode),
GrTextureDomain::kDecal_Mode, GrTextureDomain::kDecal_Mode)
, fColorSampler(color)
@ -491,9 +496,12 @@ std::unique_ptr<GrFragmentProcessor> GrDisplacementMapEffect::TestCreate(GrProce
SkISize colorDimensions;
colorDimensions.fWidth = d->fRandom->nextRangeU(0, colorProxy->width());
colorDimensions.fHeight = d->fRandom->nextRangeU(0, colorProxy->height());
SkIRect dispRect = SkIRect::MakeWH(dispProxy->width(), dispProxy->height());
return GrDisplacementMapEffect::Make(xChannelSelector, yChannelSelector, scale,
std::move(dispProxy), SkMatrix::I(),
std::move(colorProxy), colorDimensions);
std::move(dispProxy),
dispRect,
SkMatrix::I(),
std::move(colorProxy), SkIRect::MakeSize(colorDimensions));
}
#endif

View File

@ -353,6 +353,8 @@ sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialIma
offset->fY = dstBounds.top();
dstBounds.offset(-inputOffset);
srcBounds.offset(-inputOffset);
// Map srcBounds from input's logical image domain to that of the proxy
srcBounds.offset(input->subset().x(), input->subset().y());
auto fp = GrMatrixConvolutionEffect::Make(std::move(inputProxy),
srcBounds,
@ -372,7 +374,6 @@ sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialIma
#endif
SkBitmap inputBM;
if (!input->getROPixels(&inputBM)) {
return nullptr;
}

View File

@ -78,6 +78,7 @@ uniform half blurRadius;
sk_sp<GrRenderTargetContext> rtc2(
SkGpuBlurUtils::GaussianBlur(context,
std::move(srcProxy),
SkIPoint::Make(0, 0),
nullptr,
SkIRect::MakeWH(size.fWidth, size.fHeight),
SkIRect::EmptyIRect(),

View File

@ -79,6 +79,7 @@ public:
sk_sp<GrRenderTargetContext> rtc2(
SkGpuBlurUtils::GaussianBlur(context,
std::move(srcProxy),
SkIPoint::Make(0, 0),
nullptr,
SkIRect::MakeWH(size.fWidth, size.fHeight),
SkIRect::EmptyIRect(),

View File

@ -283,22 +283,40 @@ sk_sp<SkImage> SkImage::makeWithFilter(GrContext* grContext,
sk_sp<SkImageFilterCache> cache(
SkImageFilterCache::Create(SkImageFilterCache::kDefaultTransientSize));
SkImageFilter::OutputProperties outputProperties(fInfo.colorType(), fInfo.colorSpace());
SkImageFilter::Context context(SkMatrix::I(), clipBounds, cache.get(), outputProperties);
// The filters operate in the local space of the src image, where (0,0) corresponds to the
// subset's top left corner. But the clip bounds and any crop rects on the filters are in the
// original coordinate system, so configure the CTM to correct crop rects and explicitly adjust
// the clip bounds (since it is assumed to already be in image space).
SkImageFilter::Context context(SkMatrix::MakeTrans(-subset.x(), -subset.y()),
clipBounds.makeOffset(-subset.x(), -subset.y()),
cache.get(), outputProperties);
sk_sp<SkSpecialImage> result = filter->filterImage(srcSpecialImage.get(), context, offset);
if (!result) {
return nullptr;
}
*outSubset = SkIRect::MakeWH(result->width(), result->height());
if (!outSubset->intersect(clipBounds.makeOffset(-offset->x(), -offset->y()))) {
// The output image and offset are relative to the subset rectangle, so the offset needs to
// be shifted to put it in the correct spot with respect to the original coordinate system
offset->fX += subset.x();
offset->fY += subset.y();
// Final clip against the exact clipBounds (the clip provided in the context gets adjusted
// to account for pixel-moving filters so doesn't always exactly match when finished). The
// clipBounds are translated into the clippedDstRect coordinate space, including the
// result->subset() ensures that the result's image pixel origin does not affect results.
SkIRect dstRect = result->subset();
SkIRect clippedDstRect = dstRect;
if (!clippedDstRect.intersect(clipBounds.makeOffset(result->subset().x() - offset->x(),
result->subset().y() - offset->y()))) {
return nullptr;
}
offset->fX += outSubset->x();
offset->fY += outSubset->y();
// Note that here we're returning the special image's entire backing store, loose padding
// and all!
// Adjust the geometric offset if the top-left corner moved as well
offset->fX += (clippedDstRect.x() - dstRect.x());
offset->fY += (clippedDstRect.y() - dstRect.y());
*outSubset = clippedDstRect;
return result->asImage();
}

View File

@ -54,11 +54,10 @@ static SkBitmap create_bm() {
// Basic test of the SkSpecialImage public API (e.g., peekTexture, peekPixels & draw)
static void test_image(const sk_sp<SkSpecialImage>& img, skiatest::Reporter* reporter,
GrContext* context, bool isGPUBacked,
int offset, int size) {
GrContext* context, bool isGPUBacked) {
const SkIRect subset = img->subset();
REPORTER_ASSERT(reporter, offset == subset.left());
REPORTER_ASSERT(reporter, offset == subset.top());
REPORTER_ASSERT(reporter, kPad == subset.left());
REPORTER_ASSERT(reporter, kPad == subset.top());
REPORTER_ASSERT(reporter, kSmallerSize == subset.width());
REPORTER_ASSERT(reporter, kSmallerSize == subset.height());
@ -77,13 +76,8 @@ static void test_image(const sk_sp<SkSpecialImage>& img, skiatest::Reporter* rep
// Test getROPixels - this should always succeed regardless of backing store
SkBitmap bitmap;
REPORTER_ASSERT(reporter, img->getROPixels(&bitmap));
if (context) {
REPORTER_ASSERT(reporter, kSmallerSize == bitmap.width());
REPORTER_ASSERT(reporter, kSmallerSize == bitmap.height());
} else {
REPORTER_ASSERT(reporter, size == bitmap.width());
REPORTER_ASSERT(reporter, size == bitmap.height());
}
REPORTER_ASSERT(reporter, kSmallerSize == bitmap.width());
REPORTER_ASSERT(reporter, kSmallerSize == bitmap.height());
//--------------
// Test that draw restricts itself to the subset
@ -148,12 +142,12 @@ DEF_TEST(SpecialImage_Raster, reporter) {
{
sk_sp<SkSpecialImage> subSImg1(SkSpecialImage::MakeFromRaster(subset, bm));
test_image(subSImg1, reporter, nullptr, false, kPad, kFullSize);
test_image(subSImg1, reporter, nullptr, false);
}
{
sk_sp<SkSpecialImage> subSImg2(fullSImage->makeSubset(subset));
test_image(subSImg2, reporter, nullptr, false, 0, kSmallerSize);
test_image(subSImg2, reporter, nullptr, false);
}
}
@ -171,12 +165,12 @@ static void test_specialimage_image(skiatest::Reporter* reporter) {
{
sk_sp<SkSpecialImage> subSImg1(SkSpecialImage::MakeFromImage(nullptr, subset, fullImage));
test_image(subSImg1, reporter, nullptr, false, kPad, kFullSize);
test_image(subSImg1, reporter, nullptr, false);
}
{
sk_sp<SkSpecialImage> subSImg2(fullSImage->makeSubset(subset));
test_image(subSImg2, reporter, nullptr, false, 0, kSmallerSize);
test_image(subSImg2, reporter, nullptr, false);
}
}
@ -279,12 +273,12 @@ DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SpecialImage_Gpu, reporter, ctxInfo) {
context, subset,
kNeedNewImageUniqueID_SpecialImage,
std::move(proxy), nullptr));
test_image(subSImg1, reporter, context, true, kPad, kFullSize);
test_image(subSImg1, reporter, context, true);
}
{
sk_sp<SkSpecialImage> subSImg2(fullSImg->makeSubset(subset));
test_image(subSImg2, reporter, context, true, kPad, kFullSize);
test_image(subSImg2, reporter, context, true);
}
}