Use SkTileMode for blur and matrix convolution filters

Bug: skia:9280
Change-Id: I6d40b83093d8a8e9e84bbe44d917d02dfde85877
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/230124
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Michael Ludwig 2019-07-26 11:26:51 -04:00 committed by Skia Commit-Bot
parent f9ad5ba8f5
commit 607c057de9
4 changed files with 165 additions and 48 deletions

View File

@ -10,9 +10,12 @@
#include "include/core/SkImageFilter.h"
enum class SkTileMode;
class SK_API SkBlurImageFilter {
public:
/*! \enum TileMode */
/*! \enum TileMode
* DEPRECATED: Use SkTileMode instead. */
enum TileMode {
kClamp_TileMode = 0, /*!< Clamp to the image's edge pixels. */
/*!< This re-weights the filter so samples outside have no effect */
@ -28,6 +31,10 @@ public:
sk_sp<SkImageFilter> input,
const SkImageFilter::CropRect* cropRect = nullptr,
TileMode tileMode = TileMode::kClampToBlack_TileMode);
// EXPERIMENTAL: kMirror is not yet supported
static sk_sp<SkImageFilter> Make(SkScalar sigmaX, SkScalar sigmaY, SkTileMode tileMode,
sk_sp<SkImageFilter> input,
const SkImageFilter::CropRect* cropRect = nullptr);
};
#endif

View File

@ -15,6 +15,7 @@
#include "include/core/SkSize.h"
class SkBitmap;
enum class SkTileMode;
/*! \class SkMatrixConvolutionImageFilter
Matrix convolution image filter. This filter applies an NxM image
@ -24,7 +25,8 @@ class SkBitmap;
class SK_API SkMatrixConvolutionImageFilter : public SkImageFilter {
public:
/*! \enum TileMode */
/*! \enum TileMode
* DEPRECATED: Use SkTileMode instead. */
enum TileMode {
kClamp_TileMode = 0, /*!< Clamp to the image's edge pixels. */
kRepeat_TileMode, /*!< Wrap around to the image's opposite edge. */
@ -37,6 +39,16 @@ public:
~SkMatrixConvolutionImageFilter() override;
static sk_sp<SkImageFilter> Make(const SkISize& kernelSize,
const SkScalar* kernel,
SkScalar gain,
SkScalar bias,
const SkIPoint& kernelOffset,
TileMode tileMode,
bool convolveAlpha,
sk_sp<SkImageFilter> input,
const CropRect* cropRect = nullptr);
/** Construct a matrix convolution image filter.
@param kernelSize The kernel size in pixels, in each dimension (N by M).
@param kernel The image processing kernel. Must contain N * M
@ -50,7 +62,7 @@ public:
over the image (e.g., a 3x3 kernel should have an
offset of {1, 1}).
@param tileMode How accesses outside the image are treated. (@see
TileMode).
TileMode). EXPERIMENTAL: kMirror not supported yet.
@param convolveAlpha If true, all channels are convolved. If false,
only the RGB channels are convolved, and
alpha is copied from the source image.
@ -63,7 +75,7 @@ public:
SkScalar gain,
SkScalar bias,
const SkIPoint& kernelOffset,
TileMode tileMode,
SkTileMode tileMode,
bool convolveAlpha,
sk_sp<SkImageFilter> input,
const CropRect* cropRect = nullptr);
@ -74,7 +86,7 @@ protected:
SkScalar gain,
SkScalar bias,
const SkIPoint& kernelOffset,
TileMode tileMode,
SkTileMode tileMode,
bool convolveAlpha,
sk_sp<SkImageFilter> input,
const CropRect* cropRect);
@ -89,13 +101,13 @@ protected:
private:
SK_FLATTENABLE_HOOKS(SkMatrixConvolutionImageFilter)
SkISize fKernelSize;
SkScalar* fKernel;
SkScalar fGain;
SkScalar fBias;
SkIPoint fKernelOffset;
TileMode fTileMode;
bool fConvolveAlpha;
SkISize fKernelSize;
SkScalar* fKernel;
SkScalar fGain;
SkScalar fBias;
SkIPoint fKernelOffset;
SkTileMode fTileMode;
bool fConvolveAlpha;
template <class PixelFetcher, bool convolveAlpha>
void filterPixels(const SkBitmap& src,

View File

@ -10,6 +10,7 @@
#include <algorithm>
#include "include/core/SkBitmap.h"
#include "include/core/SkTileMode.h"
#include "include/private/SkColorData.h"
#include "include/private/SkNx.h"
#include "include/private/SkTFitsIn.h"
@ -34,9 +35,9 @@ class SkBlurImageFilterImpl final : public SkImageFilter {
public:
SkBlurImageFilterImpl(SkScalar sigmaX,
SkScalar sigmaY,
SkTileMode tileMode,
sk_sp<SkImageFilter> input,
const CropRect* cropRect,
SkBlurImageFilter::TileMode tileMode);
const CropRect* cropRect);
SkRect computeFastBounds(const SkRect&) const override;
@ -60,8 +61,8 @@ private:
const OutputProperties& outProps, SkIPoint* offset) const;
#endif
SkSize fSigma;
SkBlurImageFilter::TileMode fTileMode;
SkSize fSigma;
SkTileMode fTileMode;
};
void SkImageFilter::RegisterFlattenables() { SK_REGISTER_FLATTENABLE(SkBlurImageFilterImpl); }
@ -72,11 +73,31 @@ sk_sp<SkImageFilter> SkBlurImageFilter::Make(SkScalar sigmaX, SkScalar sigmaY,
sk_sp<SkImageFilter> input,
const SkImageFilter::CropRect* cropRect,
TileMode tileMode) {
SkTileMode skTileMode;
switch(tileMode) {
case kClamp_TileMode:
skTileMode = SkTileMode::kClamp;
break;
case kRepeat_TileMode:
skTileMode = SkTileMode::kRepeat;
break;
case kClampToBlack_TileMode:
// Fall through
default:
skTileMode = SkTileMode::kDecal;
break;
}
return Make(sigmaX, sigmaY, skTileMode, std::move(input), cropRect);
}
sk_sp<SkImageFilter> SkBlurImageFilter::Make(SkScalar sigmaX, SkScalar sigmaY, SkTileMode tileMode,
sk_sp<SkImageFilter> input,
const SkImageFilter::CropRect* cropRect) {
if (sigmaX < SK_ScalarNearlyZero && sigmaY < SK_ScalarNearlyZero && !cropRect) {
return input;
}
return sk_sp<SkImageFilter>(
new SkBlurImageFilterImpl(sigmaX, sigmaY, input, cropRect, tileMode));
new SkBlurImageFilterImpl(sigmaX, sigmaY, tileMode, input, cropRect));
}
// This rather arbitrary-looking value results in a maximum box blur kernel size
@ -96,9 +117,9 @@ static SkVector map_sigma(const SkSize& localSigma, const SkMatrix& ctm) {
SkBlurImageFilterImpl::SkBlurImageFilterImpl(SkScalar sigmaX,
SkScalar sigmaY,
SkTileMode tileMode,
sk_sp<SkImageFilter> input,
const CropRect* cropRect,
SkBlurImageFilter::TileMode tileMode)
const CropRect* cropRect)
: INHERITED(&input, 1, cropRect), fSigma{sigmaX, sigmaY}, fTileMode(tileMode) {}
sk_sp<SkFlattenable> SkBlurImageFilterImpl::CreateProc(SkReadBuffer& buffer) {
@ -123,24 +144,46 @@ void SkBlurImageFilterImpl::flatten(SkWriteBuffer& buffer) const {
buffer.writeScalar(fSigma.fWidth);
buffer.writeScalar(fSigma.fHeight);
static_assert(SkBlurImageFilter::kLast_TileMode == 2, "flatten");
SkASSERT(fTileMode <= SkBlurImageFilter::kLast_TileMode);
// CreateProc only knows how to deserialize the old TileMode enum, so temporarily convert
// SkTileMode back to the old one, treating kMirror as kRepeat.
// TODO (michaelludwig) - Remove once CreateProc can deserialize SkTileMode directly, which will
// require a new SkPicture version number.
SkBlurImageFilter::TileMode backwardsCompatibleMode;
switch(fTileMode) {
case SkTileMode::kClamp:
backwardsCompatibleMode = SkBlurImageFilter::kClamp_TileMode;
break;
case SkTileMode::kMirror:
// Treat as repeat for now
case SkTileMode::kRepeat:
backwardsCompatibleMode = SkBlurImageFilter::kRepeat_TileMode;
break;
case SkTileMode::kDecal:
default:
backwardsCompatibleMode = SkBlurImageFilter::kClampToBlack_TileMode;
break;
}
buffer.writeInt(static_cast<int>(fTileMode));
static_assert(SkBlurImageFilter::kLast_TileMode == 2, "flatten");
SkASSERT(backwardsCompatibleMode <= SkBlurImageFilter::kLast_TileMode);
buffer.writeInt(static_cast<int>(backwardsCompatibleMode));
}
#if SK_SUPPORT_GPU
static GrTextureDomain::Mode to_texture_domain_mode(SkBlurImageFilter::TileMode tileMode) {
static GrTextureDomain::Mode to_texture_domain_mode(SkTileMode tileMode) {
switch (tileMode) {
case SkBlurImageFilter::TileMode::kClamp_TileMode:
return GrTextureDomain::kClamp_Mode;
case SkBlurImageFilter::TileMode::kClampToBlack_TileMode:
return GrTextureDomain::kDecal_Mode;
case SkBlurImageFilter::TileMode::kRepeat_TileMode:
return GrTextureDomain::kRepeat_Mode;
default:
SK_ABORT("Unsupported tile mode.");
return GrTextureDomain::kDecal_Mode;
case SkTileMode::kClamp:
return GrTextureDomain::kClamp_Mode;
case SkTileMode::kDecal:
return GrTextureDomain::kDecal_Mode;
case SkTileMode::kMirror:
// TODO (michaelludwig) - Support mirror mode, treat as repeat for now
case SkTileMode::kRepeat:
return GrTextureDomain::kRepeat_Mode;
default:
SK_ABORT("Unsupported tile mode.");
return GrTextureDomain::kDecal_Mode;
}
}
#endif

View File

@ -7,6 +7,7 @@
#include "include/core/SkBitmap.h"
#include "include/core/SkRect.h"
#include "include/core/SkTileMode.h"
#include "include/core/SkUnPreMultiply.h"
#include "include/effects/SkMatrixConvolutionImageFilter.h"
#include "include/private/SkColorData.h"
@ -30,7 +31,7 @@ SkMatrixConvolutionImageFilter::SkMatrixConvolutionImageFilter(const SkISize& ke
SkScalar gain,
SkScalar bias,
const SkIPoint& kernelOffset,
TileMode tileMode,
SkTileMode tileMode,
bool convolveAlpha,
sk_sp<SkImageFilter> input,
const CropRect* cropRect)
@ -58,6 +59,33 @@ sk_sp<SkImageFilter> SkMatrixConvolutionImageFilter::Make(const SkISize& kernelS
bool convolveAlpha,
sk_sp<SkImageFilter> input,
const CropRect* cropRect) {
SkTileMode skTileMode;
switch(tileMode) {
case kClamp_TileMode:
skTileMode = SkTileMode::kClamp;
break;
case kRepeat_TileMode:
skTileMode = SkTileMode::kRepeat;
break;
case kClampToBlack_TileMode:
// Fall through
default:
skTileMode = SkTileMode::kDecal;
break;
}
return Make(kernelSize, kernel, gain, bias, kernelOffset, skTileMode, convolveAlpha,
std::move(input), cropRect);
}
sk_sp<SkImageFilter> SkMatrixConvolutionImageFilter::Make(const SkISize& kernelSize,
const SkScalar* kernel,
SkScalar gain,
SkScalar bias,
const SkIPoint& kernelOffset,
SkTileMode tileMode,
bool convolveAlpha,
sk_sp<SkImageFilter> input,
const CropRect* cropRect) {
if (kernelSize.width() < 1 || kernelSize.height() < 1) {
return nullptr;
}
@ -121,7 +149,26 @@ void SkMatrixConvolutionImageFilter::flatten(SkWriteBuffer& buffer) const {
buffer.writeScalar(fBias);
buffer.writeInt(fKernelOffset.fX);
buffer.writeInt(fKernelOffset.fY);
buffer.writeInt((int) fTileMode);
// CreateProc only knows how to deserialize the old TileMode enum, so temporarily convert
// SkTileMode back to the old one, treating kMirror as kRepeat.
// TODO (michaelludwig) - Remove once CreateProc can deserialize SkTileMode directly, which will
// require a new SkPicture version number.
TileMode backwardsCompatibleMode;
switch(fTileMode) {
case SkTileMode::kClamp:
backwardsCompatibleMode = kClamp_TileMode;
break;
case SkTileMode::kMirror:
// Treat as repeat for now
case SkTileMode::kRepeat:
backwardsCompatibleMode = kRepeat_TileMode;
break;
case SkTileMode::kDecal:
default:
backwardsCompatibleMode = kClampToBlack_TileMode;
break;
}
buffer.writeInt((int) backwardsCompatibleMode);
buffer.writeBool(fConvolveAlpha);
}
@ -235,12 +282,15 @@ void SkMatrixConvolutionImageFilter::filterInteriorPixels(const SkBitmap& src,
const SkIRect& rect,
const SkIRect& bounds) const {
switch (fTileMode) {
case kRepeat_TileMode:
case SkTileMode::kMirror:
// TODO (michaelludwig) - Implement mirror tiling, treat as repeat for now.
case SkTileMode::kRepeat:
// In repeat mode, we still need to wrap the samples around the src
filterPixels<RepeatPixelFetcher>(src, result, offset, rect, bounds);
break;
case kClamp_TileMode:
case kClampToBlack_TileMode:
case SkTileMode::kClamp:
// Fall through
case SkTileMode::kDecal:
filterPixels<UncheckedPixelFetcher>(src, result, offset, rect, bounds);
break;
}
@ -252,13 +302,15 @@ void SkMatrixConvolutionImageFilter::filterBorderPixels(const SkBitmap& src,
const SkIRect& rect,
const SkIRect& srcBounds) const {
switch (fTileMode) {
case kClamp_TileMode:
case SkTileMode::kClamp:
filterPixels<ClampPixelFetcher>(src, result, offset, rect, srcBounds);
break;
case kRepeat_TileMode:
case SkTileMode::kMirror:
// TODO (michaelludwig) - Implement mirror tiling, treat as repeat for now.
case SkTileMode::kRepeat:
filterPixels<RepeatPixelFetcher>(src, result, offset, rect, srcBounds);
break;
case kClampToBlack_TileMode:
case SkTileMode::kDecal:
filterPixels<ClampToBlackPixelFetcher>(src, result, offset, rect, srcBounds);
break;
}
@ -289,13 +341,15 @@ static SkBitmap unpremultiply_bitmap(const SkBitmap& src) {
#if SK_SUPPORT_GPU
static GrTextureDomain::Mode convert_tilemodes(SkMatrixConvolutionImageFilter::TileMode tileMode) {
static GrTextureDomain::Mode convert_tilemodes(SkTileMode tileMode) {
switch (tileMode) {
case SkMatrixConvolutionImageFilter::kClamp_TileMode:
case SkTileMode::kClamp:
return GrTextureDomain::kClamp_Mode;
case SkMatrixConvolutionImageFilter::kRepeat_TileMode:
case SkTileMode::kMirror:
// TODO (michaelludwig) - Implement mirror tiling, treat as repeat for now.
case SkTileMode::kRepeat:
return GrTextureDomain::kRepeat_Mode;
case SkMatrixConvolutionImageFilter::kClampToBlack_TileMode:
case SkTileMode::kDecal:
return GrTextureDomain::kDecal_Mode;
default:
SkASSERT(false);
@ -325,7 +379,7 @@ sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialIma
SkIRect srcBounds = this->onFilterNodeBounds(dstBounds, ctx.ctm(), kReverse_MapDirection,
&originalSrcBounds);
if (kRepeat_TileMode == fTileMode) {
if (SkTileMode::kRepeat == fTileMode || SkTileMode::kMirror == fTileMode) {
srcBounds = DetermineRepeatedSrcBound(srcBounds, fKernelOffset,
fKernelSize, originalSrcBounds);
} else {
@ -407,7 +461,7 @@ sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialIma
srcBounds.offset(-inputOffset);
SkIRect interior;
if (kRepeat_TileMode == fTileMode) {
if (SkTileMode::kRepeat == fTileMode || SkTileMode::kMirror == fTileMode) {
// In repeat mode, the filterPixels calls will wrap around
// so we just need to render 'dstBounds'
interior = dstBounds;
@ -442,7 +496,8 @@ sk_sp<SkSpecialImage> SkMatrixConvolutionImageFilter::onFilterImage(SkSpecialIma
SkIRect SkMatrixConvolutionImageFilter::onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
MapDirection dir,
const SkIRect* inputRect) const {
if (kReverse_MapDirection == dir && kRepeat_TileMode == fTileMode && inputRect) {
if (kReverse_MapDirection == dir && inputRect &&
(SkTileMode::kRepeat == fTileMode || SkTileMode::kMirror == fTileMode)) {
SkASSERT(inputRect);
return DetermineRepeatedSrcBound(src, fKernelOffset, fKernelSize, *inputRect);
}
@ -467,5 +522,5 @@ bool SkMatrixConvolutionImageFilter::affectsTransparentBlack() const {
// For the other modes, because the kernel is applied in device-space, we have no idea what
// pixels it will affect in object-space.
return kRepeat_TileMode != fTileMode;
return SkTileMode::kRepeat != fTileMode && SkTileMode::kMirror != fTileMode;
}