Reland "Fill intermediate device image with clamped backdrop content"
This reverts commit35ac48c715
. Reason for revert: guarded needs_subset Original change's description: > Revert "Fill intermediate device image with clamped backdrop content" > > This reverts commit25f5e1f838
. > > Reason for revert: needs_subset is unused for no-gpu+release builds > > Original change's description: > > Fill intermediate device image with clamped backdrop content > > > > Adds asShader() to SkSpecialImage that accepts a local matrix and a > > tilemode. It automatically handles the subsetting of the image data > > (either by making a new bitmap view, or using GrTexturEffect), as well > > as modifying the local matrix to account for the subset's offset within > > the image data. > > > > By adding asShader(), SkCanvas can fill a device using drawPaint() to > > apply the image's tilemode to the entire contents, ensuring that any > > device bounds clipping from snapping of backdrop filters doesn't leave > > the default transparent pixels around for later filtering. > > > > Additionally, making SkSpecialImage more like a shader will make it > > easier down the road to continue refactoring the image filter pipeline > > to be more backend agnostic and allow operations to be performed by > > filling devices with shaders, instead of relying on FPs directly. > > > > Lastly, once SkImage::makeShader() can support subsets on the raster > > backend, then SkSpecialImage may not be required at all in order to > > implement the image filtering pipeline... > > > > Bug: b/197774543, skia:12784 > > Change-Id: I21dcb22b56b19ff58d246b3c0517bb8a265649bc > > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/491445 > > Reviewed-by: Brian Salomon <bsalomon@google.com> > > Commit-Queue: Michael Ludwig <michaelludwig@google.com> > > Bug: b/197774543, skia:12784 > Change-Id: Ia9adedaab9378d2679353ab94c7a9589c0ff9e02 > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/491978 > Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com> > Commit-Queue: Michael Ludwig <michaelludwig@google.com> Bug: b/197774543, skia:12784 Change-Id: I6034e4ddbe4f6da476d058a78f85f4e2bb6742fa Reviewed-on: https://skia-review.googlesource.com/c/skia/+/492116 Reviewed-by: Brian Salomon <bsalomon@google.com> Auto-Submit: Michael Ludwig <michaelludwig@google.com> Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
parent
5bb80e1ee0
commit
54d9447b6a
@ -924,12 +924,15 @@ void SkCanvas::internalDrawDeviceWithFilter(SkBaseDevice* src,
|
||||
intermediateDevice->setOrigin(SkM44(srcToIntermediate),
|
||||
requiredInput.left(), requiredInput.top());
|
||||
|
||||
SkMatrix offsetLocalToDevice = intermediateDevice->localToDevice();
|
||||
offsetLocalToDevice.preTranslate(srcSubset.left(), srcSubset.top());
|
||||
// We draw with non-AA bilinear since we cover the destination but definitely don't have
|
||||
// a pixel-aligned transform.
|
||||
intermediateDevice->drawSpecial(srcImage.get(), offsetLocalToDevice,
|
||||
SkSamplingOptions{SkFilterMode::kLinear}, {});
|
||||
// We use drawPaint to fill the entire device with the src input + clamp tiling, which
|
||||
// extends the backdrop's edge pixels to the parts of 'requiredInput' that map offscreen
|
||||
// Without this, the intermediateDevice would contain transparent pixels that may then
|
||||
// infect blurs and other filters with large kernels.
|
||||
SkPaint imageFill;
|
||||
imageFill.setShader(srcImage->asShader(SkTileMode::kClamp,
|
||||
SkSamplingOptions{SkFilterMode::kLinear},
|
||||
SkMatrix::Translate(srcSubset.topLeft())));
|
||||
intermediateDevice->drawPaint(imageFill);
|
||||
filterInput = intermediateDevice->snapSpecial();
|
||||
|
||||
// TODO: Like the non-intermediate case, we need to apply the image origin.
|
||||
|
@ -10,6 +10,8 @@
|
||||
#include "include/core/SkBitmap.h"
|
||||
#include "include/core/SkCanvas.h"
|
||||
#include "include/core/SkImage.h"
|
||||
#include "include/core/SkMatrix.h"
|
||||
#include "include/core/SkTileMode.h"
|
||||
#include "src/core/SkSpecialSurface.h"
|
||||
#include "src/core/SkSurfacePriv.h"
|
||||
#include "src/image/SkImage_Base.h"
|
||||
@ -22,6 +24,7 @@
|
||||
#include "src/gpu/GrRecordingContextPriv.h"
|
||||
#include "src/gpu/GrTextureProxy.h"
|
||||
#include "src/image/SkImage_Gpu.h"
|
||||
#include "src/shaders/SkImageShader.h"
|
||||
#endif
|
||||
|
||||
// Currently the raster imagefilters can only handle certain imageinfos. Call this to know if
|
||||
@ -64,6 +67,10 @@ public:
|
||||
// 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<SkShader> onAsShader(SkTileMode,
|
||||
const SkSamplingOptions&,
|
||||
const SkMatrix&) const = 0;
|
||||
|
||||
virtual sk_sp<SkSurface> onMakeTightSurface(
|
||||
SkColorType colorType, const SkColorSpace* colorSpace,
|
||||
const SkISize& size, SkAlphaType at) const = 0;
|
||||
@ -138,6 +145,21 @@ sk_sp<SkImage> SkSpecialImage::asImage(const SkIRect* subset) const {
|
||||
}
|
||||
}
|
||||
|
||||
sk_sp<SkShader> SkSpecialImage::asShader(SkTileMode tileMode,
|
||||
const SkSamplingOptions& sampling,
|
||||
const SkMatrix& lm) const {
|
||||
return as_SIB(this)->onAsShader(tileMode, sampling, lm);
|
||||
}
|
||||
|
||||
sk_sp<SkShader> SkSpecialImage::asShader(const SkSamplingOptions& sampling) const {
|
||||
return this->asShader(sampling, SkMatrix::I());
|
||||
}
|
||||
|
||||
sk_sp<SkShader> SkSpecialImage::asShader(const SkSamplingOptions& sampling,
|
||||
const SkMatrix& lm) const {
|
||||
return this->asShader(SkTileMode::kClamp, sampling, lm);
|
||||
}
|
||||
|
||||
#if defined(SK_DEBUG) || SK_SUPPORT_GPU
|
||||
static bool rect_fits(const SkIRect& rect, int width, int height) {
|
||||
if (0 == width && 0 == height) {
|
||||
@ -252,7 +274,19 @@ public:
|
||||
return fBitmap.asImage();
|
||||
}
|
||||
|
||||
sk_sp<SkSurface> onMakeTightSurface(SkColorType colorType, const SkColorSpace* colorSpace,
|
||||
sk_sp<SkShader> onAsShader(SkTileMode tileMode,
|
||||
const SkSamplingOptions& sampling,
|
||||
const SkMatrix& lm) const override {
|
||||
// TODO(skbug.com/12784): SkImage::makeShader() doesn't support a subset yet, but SkBitmap
|
||||
// supports subset views so create the shader from the subset bitmap instead of fBitmap.
|
||||
SkBitmap subsetBM;
|
||||
if (!this->getROPixels(&subsetBM)) {
|
||||
return nullptr;
|
||||
}
|
||||
return subsetBM.asImage()->makeShader(tileMode, tileMode, sampling, lm);
|
||||
}
|
||||
|
||||
sk_sp<SkSurface> onMakeTightSurface(SkColorType colorType, const SkColorSpace* colorSpace,
|
||||
const SkISize& size, SkAlphaType at) const override {
|
||||
// Ignore the requested color type, the raster backend currently only supports N32
|
||||
colorType = kN32_SkColorType; // TODO: find ways to allow f16
|
||||
@ -438,6 +472,23 @@ public:
|
||||
return wrap_proxy_in_image(fContext, fView, this->colorType(), fAlphaType, fColorSpace);
|
||||
}
|
||||
|
||||
sk_sp<SkShader> onAsShader(SkTileMode tileMode,
|
||||
const SkSamplingOptions& sampling,
|
||||
const SkMatrix& lm) const override {
|
||||
// The special image's logical (0,0) is at its subset's topLeft() so we need to account for
|
||||
// that in the local matrix used when sampling.
|
||||
SkMatrix subsetOrigin = SkMatrix::Translate(-this->subset().topLeft());
|
||||
subsetOrigin.postConcat(lm);
|
||||
// However, we don't need to modify the subset itself since that is defined with respect to
|
||||
// the base image, and the local matrix is applied before any tiling/clamping.
|
||||
const SkRect subset = SkRect::Make(this->subset());
|
||||
|
||||
// asImage() w/o a subset makes no copy; create the SkImageShader directly to remember the
|
||||
// subset used to access the image.
|
||||
return SkImageShader::MakeSubset(
|
||||
this->asImage(), subset, tileMode, tileMode, sampling, &subsetOrigin);
|
||||
}
|
||||
|
||||
sk_sp<SkSurface> onMakeTightSurface(SkColorType colorType, const SkColorSpace* colorSpace,
|
||||
const SkISize& size, SkAlphaType at) const override {
|
||||
// TODO (michaelludwig): Why does this ignore colorType but onMakeSurface doesn't ignore it?
|
||||
|
@ -25,10 +25,13 @@ class SkBitmap;
|
||||
class SkCanvas;
|
||||
class SkImage;
|
||||
struct SkImageInfo;
|
||||
class SkMatrix;
|
||||
class SkPaint;
|
||||
class SkPixmap;
|
||||
class SkShader;
|
||||
class SkSpecialSurface;
|
||||
class SkSurface;
|
||||
enum class SkTileMode;
|
||||
|
||||
enum {
|
||||
kNeedNewImageUniqueID_SpecialImage = 0
|
||||
@ -126,8 +129,20 @@ public:
|
||||
* When the 'subset' parameter is specified the returned image will be tight even if that
|
||||
* entails a copy! The 'subset' is relative to this special image's content rect.
|
||||
*/
|
||||
// TODO: The only version that uses the subset is the tile image filter, and that doesn't need
|
||||
// to if it can be rewritten to use asShader() and SkTileModes. Similarly, the only use case of
|
||||
// asImage() w/o a subset is SkImage::makeFiltered() and that could/should return an SkShader so
|
||||
// that users don't need to worry about correctly applying the subset, etc.
|
||||
sk_sp<SkImage> asImage(const SkIRect* subset = nullptr) const;
|
||||
|
||||
/**
|
||||
* Create an SkShader that samples the contents of this special image, applying tile mode for
|
||||
* any sample that falls outside its internal subset.
|
||||
*/
|
||||
sk_sp<SkShader> asShader(SkTileMode, const SkSamplingOptions&, const SkMatrix&) const;
|
||||
sk_sp<SkShader> asShader(const SkSamplingOptions& sampling) const;
|
||||
sk_sp<SkShader> asShader(const SkSamplingOptions& sampling, const SkMatrix& lm) const;
|
||||
|
||||
/**
|
||||
* If the SpecialImage is backed by a gpu texture, return true.
|
||||
*/
|
||||
|
@ -176,11 +176,9 @@ sk_sp<SkSpecialImage> SkRuntimeImageFilter::onFilterImage(const Context& ctx,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SkMatrix localM = inverse *
|
||||
SkMatrix::Translate(inputOffset) *
|
||||
SkMatrix::Translate(-input->subset().topLeft());
|
||||
SkMatrix localM = inverse * SkMatrix::Translate(inputOffset);
|
||||
sk_sp<SkShader> inputShader =
|
||||
input->asImage()->makeShader(SkSamplingOptions(SkFilterMode::kLinear), &localM);
|
||||
input->asShader(SkSamplingOptions(SkFilterMode::kLinear), localM);
|
||||
SkASSERT(inputShader);
|
||||
inputShaders.push_back(std::move(inputShader));
|
||||
}
|
||||
|
@ -67,7 +67,15 @@ static SkTileMode optimize(SkTileMode tm, int dimension) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// TODO: currently this only *always* used in asFragmentProcessor(), which is excluded on no-gpu
|
||||
// builds. No-gpu builds only use needs_subset() in asserts, so release+no-gpu doesn't use it, which
|
||||
// can cause builds to fail if unused warnings are treated as errors.
|
||||
SK_MAYBE_UNUSED static bool needs_subset(SkImage* img, const SkRect& subset) {
|
||||
return subset != SkRect::Make(img->dimensions());
|
||||
}
|
||||
|
||||
SkImageShader::SkImageShader(sk_sp<SkImage> img,
|
||||
const SkRect& subset,
|
||||
SkTileMode tmx, SkTileMode tmy,
|
||||
const SkSamplingOptions& sampling,
|
||||
const SkMatrix* localMatrix,
|
||||
@ -78,6 +86,7 @@ SkImageShader::SkImageShader(sk_sp<SkImage> img,
|
||||
, fSampling(sampling)
|
||||
, fTileModeX(optimize(tmx, fImage->width()))
|
||||
, fTileModeY(optimize(tmy, fImage->height()))
|
||||
, fSubset(subset)
|
||||
, fRaw(raw)
|
||||
, fClampAsIfUnpremul(clampAsIfUnpremul) {
|
||||
// These options should never appear together:
|
||||
@ -132,6 +141,9 @@ sk_sp<SkFlattenable> SkImageShader::CreateProc(SkReadBuffer& buffer) {
|
||||
bool raw = buffer.isVersionLT(SkPicturePriv::Version::kRawImageShaders) ? false
|
||||
: buffer.readBool();
|
||||
|
||||
// TODO(skbug.com/12784): Subset is not serialized yet; it's only used by special images so it
|
||||
// will never be written to an SKP.
|
||||
|
||||
return raw ? SkImageShader::MakeRaw(std::move(img), tmx, tmy, sampling, &localMatrix)
|
||||
: SkImageShader::Make(std::move(img), tmx, tmy, sampling, &localMatrix);
|
||||
}
|
||||
@ -146,6 +158,10 @@ void SkImageShader::flatten(SkWriteBuffer& buffer) const {
|
||||
buffer.writeImage(fImage.get());
|
||||
SkASSERT(fClampAsIfUnpremul == false);
|
||||
|
||||
// TODO(skbug.com/12784): Subset is not serialized yet; it's only used by special images so it
|
||||
// will never be written to an SKP.
|
||||
SkASSERT(!needs_subset(fImage.get(), fSubset));
|
||||
|
||||
buffer.writeBool(fRaw);
|
||||
}
|
||||
|
||||
@ -189,6 +205,7 @@ static bool legacy_shader_can_handle(const SkMatrix& inv) {
|
||||
|
||||
SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec,
|
||||
SkArenaAlloc* alloc) const {
|
||||
SkASSERT(!needs_subset(fImage.get(), fSubset)); // TODO(skbug.com/12784)
|
||||
if (fImage->alphaType() == kUnpremul_SkAlphaType) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -265,19 +282,8 @@ sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image,
|
||||
const SkSamplingOptions& options,
|
||||
const SkMatrix* localMatrix,
|
||||
bool clampAsIfUnpremul) {
|
||||
auto is_unit = [](float x) {
|
||||
return x >= 0 && x <= 1;
|
||||
};
|
||||
if (options.useCubic) {
|
||||
if (!is_unit(options.cubic.B) || !is_unit(options.cubic.C)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
if (!image) {
|
||||
return sk_make_sp<SkEmptyShader>();
|
||||
}
|
||||
return sk_sp<SkShader>{new SkImageShader(
|
||||
image, tmx, tmy, options, localMatrix, /*raw=*/false, clampAsIfUnpremul)};
|
||||
SkRect subset = image ? SkRect::Make(image->dimensions()) : SkRect::MakeEmpty();
|
||||
return MakeSubset(std::move(image), subset, tmx, tmy, options, localMatrix, clampAsIfUnpremul);
|
||||
}
|
||||
|
||||
sk_sp<SkShader> SkImageShader::MakeRaw(sk_sp<SkImage> image,
|
||||
@ -291,7 +297,36 @@ sk_sp<SkShader> SkImageShader::MakeRaw(sk_sp<SkImage> image,
|
||||
return sk_make_sp<SkEmptyShader>();
|
||||
}
|
||||
return sk_sp<SkShader>{new SkImageShader(
|
||||
image, tmx, tmy, options, localMatrix, /*raw=*/true, /*clampAsIfUnpremul=*/false)};
|
||||
image, SkRect::Make(image->dimensions()), tmx, tmy, options, localMatrix,
|
||||
/*raw=*/true, /*clampAsIfUnpremul=*/false)};
|
||||
}
|
||||
|
||||
sk_sp<SkShader> SkImageShader::MakeSubset(sk_sp<SkImage> image,
|
||||
const SkRect& subset,
|
||||
SkTileMode tmx, SkTileMode tmy,
|
||||
const SkSamplingOptions& options,
|
||||
const SkMatrix* localMatrix,
|
||||
bool clampAsIfUnpremul) {
|
||||
auto is_unit = [](float x) {
|
||||
return x >= 0 && x <= 1;
|
||||
};
|
||||
if (options.useCubic) {
|
||||
if (!is_unit(options.cubic.B) || !is_unit(options.cubic.C)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
if (!image || subset.isEmpty()) {
|
||||
return sk_make_sp<SkEmptyShader>();
|
||||
}
|
||||
|
||||
// Validate subset and check if we can drop it
|
||||
if (!SkRect::Make(image->bounds()).contains(subset)) {
|
||||
return nullptr;
|
||||
}
|
||||
// TODO(skbug.com/12784): GPU-only for now since it's only supported in onAsFragmentProcessor()
|
||||
SkASSERT(!needs_subset(image.get(), subset) || image->isTextureBacked());
|
||||
return sk_sp<SkShader>{new SkImageShader(
|
||||
image, subset, tmx, tmy, options, localMatrix, /*raw=*/false, clampAsIfUnpremul)};
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -310,10 +345,12 @@ std::unique_ptr<GrFragmentProcessor> SkImageShader::asFragmentProcessor(
|
||||
}
|
||||
|
||||
SkTileMode tileModes[2] = {fTileModeX, fTileModeY};
|
||||
const SkRect* subset = needs_subset(fImage.get(), fSubset) ? &fSubset : nullptr;
|
||||
auto fp = as_IB(fImage.get())->asFragmentProcessor(args.fContext,
|
||||
fSampling,
|
||||
tileModes,
|
||||
lmInverse);
|
||||
lmInverse,
|
||||
subset);
|
||||
if (!fp) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -407,6 +444,7 @@ static SkMatrix tweak_inv_matrix(SkFilterMode filter, SkMatrix matrix) {
|
||||
}
|
||||
|
||||
bool SkImageShader::doStages(const SkStageRec& rec, TransformShader* updater) const {
|
||||
SkASSERT(!needs_subset(fImage.get(), fSubset)); // TODO(skbug.com/12784)
|
||||
// We only support certain sampling options in stages so far
|
||||
auto sampling = fSampling;
|
||||
if (sampling.useCubic) {
|
||||
@ -705,7 +743,7 @@ skvm::Color SkImageShader::makeProgram(
|
||||
skvm::Builder* p, skvm::Coord device, skvm::Coord origLocal, skvm::Color paint,
|
||||
const SkMatrixProvider& matrices, const SkMatrix* localM, const SkColorInfo& dst,
|
||||
skvm::Uniforms* uniforms, const TransformShader* coordShader, SkArenaAlloc* alloc) const {
|
||||
|
||||
SkASSERT(!needs_subset(fImage.get(), fSubset)); // TODO(skbug.com/12784)
|
||||
SkMatrix baseInv;
|
||||
if (!this->computeTotalInverse(matrices.localToDevice(), localM, &baseInv)) {
|
||||
return {};
|
||||
|
@ -28,6 +28,16 @@ public:
|
||||
const SkSamplingOptions&,
|
||||
const SkMatrix* localMatrix);
|
||||
|
||||
// TODO(skbug.com/12784): Requires SkImage to be texture backed, and created SkShader can only
|
||||
// be used on GPU-backed surfaces.
|
||||
static sk_sp<SkShader> MakeSubset(sk_sp<SkImage>,
|
||||
const SkRect& subset,
|
||||
SkTileMode tmx,
|
||||
SkTileMode tmy,
|
||||
const SkSamplingOptions&,
|
||||
const SkMatrix* localMatrix,
|
||||
bool clampAsIfUnpremul = false);
|
||||
|
||||
bool isOpaque() const override;
|
||||
|
||||
#if SK_SUPPORT_GPU
|
||||
@ -40,6 +50,7 @@ private:
|
||||
SK_FLATTENABLE_HOOKS(SkImageShader)
|
||||
|
||||
SkImageShader(sk_sp<SkImage>,
|
||||
const SkRect& subset,
|
||||
SkTileMode tmx,
|
||||
SkTileMode tmy,
|
||||
const SkSamplingOptions&,
|
||||
@ -74,6 +85,11 @@ private:
|
||||
const SkSamplingOptions fSampling;
|
||||
const SkTileMode fTileModeX;
|
||||
const SkTileMode fTileModeY;
|
||||
|
||||
// TODO(skbug.com/12784): This is only supported for GPU images currently.
|
||||
// If subset == (0,0,w,h) of the image, then no subset is applied. Subset will not be empty.
|
||||
const SkRect fSubset;
|
||||
|
||||
const bool fRaw;
|
||||
const bool fClampAsIfUnpremul;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user