Update SkMatrixImageFilter to new API
Switches to use new coord-space tagging and separates definitions of
input vs. output bounds. For now, the onFilter logic still handles
rendering but a follow-up CL will fold transforms into FilterResult to
allow more general passthrough and combining the transform with the
draw on the next stage (or final layer restore).
Bug: skia:9282
Change-Id: I4060244283c039d28074c1070397ca10f49872d7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/570396
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/core/SkImageFilterTypes.cpp b/src/core/SkImageFilterTypes.cpp
index 18974df..59240fc 100644
--- a/src/core/SkImageFilterTypes.cpp
+++ b/src/core/SkImageFilterTypes.cpp
@@ -148,6 +148,19 @@
return SkSize::Make(v.fX, v.fY);
}
+template<>
+SkMatrix Mapping::map<SkMatrix>(const SkMatrix& m, const SkMatrix& matrix) {
+ // If 'matrix' maps from the C1 coord space to the C2 coord space, and 'm' is a transform that
+ // operates on, and outputs to, the C1 coord space, we want to return a new matrix that is
+ // equivalent to 'm' that operates on and outputs to C2. This is the same as mapping the input
+ // from C2 to C1 (matrix^-1), then transforming by 'm', and then mapping from C1 to C2 (matrix).
+ SkMatrix inv;
+ SkAssertResult(matrix.invert(&inv));
+ inv.postConcat(m);
+ inv.postConcat(matrix);
+ return inv;
+}
+
FilterResult FilterResult::resolveToBounds(const LayerSpace<SkIRect>& newBounds) const {
// NOTE(michaelludwig) - This implementation is based on the assumption that an image resolved
// to 'newBounds' will be decal tiled and that the current image is decal tiled. Because of this
diff --git a/src/core/SkImageFilterTypes.h b/src/core/SkImageFilterTypes.h
index 9fc0456..839fa73 100644
--- a/src/core/SkImageFilterTypes.h
+++ b/src/core/SkImageFilterTypes.h
@@ -10,6 +10,8 @@
#include "include/core/SkColorSpace.h"
#include "include/core/SkMatrix.h"
+#include "include/core/SkRect.h"
+#include "include/core/SkTypes.h"
#include "src/core/SkSpecialImage.h"
#include "src/core/SkSpecialSurface.h"
@@ -383,6 +385,7 @@
LayerSpace() = default;
explicit LayerSpace(const SkRect& geometry) : fData(geometry) {}
explicit LayerSpace(SkRect&& geometry) : fData(std::move(geometry)) {}
+ explicit LayerSpace(const LayerSpace<SkIRect>& rect) : fData(SkRect::Make(SkIRect(rect))) {}
explicit operator const SkRect&() const { return fData; }
static LayerSpace<SkRect> Empty() { return LayerSpace<SkRect>(SkRect::MakeEmpty()); }
@@ -418,6 +421,50 @@
SkRect fData;
};
+// A transformation that manipulates geometry in the layer-space coordinate system. Mathematically
+// there's little difference from these matrices compared to what's stored in a skif::Mapping, but
+// the intent differs. skif::Mapping's matrices map geometry from one coordinate space to another
+// while these transforms move geometry w/o changing the coordinate space semantics.
+// TODO(michaelludwig): Will be replaced with an SkM44 version when skif::Mapping works with SkM44.
+template<>
+class LayerSpace<SkMatrix> {
+public:
+ LayerSpace() = default;
+ explicit LayerSpace(const SkMatrix& m) : fData(m) {}
+ explicit LayerSpace(SkMatrix&& m) : fData(std::move(m)) {}
+ explicit operator const SkMatrix&() const { return fData; }
+
+ // Parrot a limited selection of the SkMatrix API while preserving coordinate space.
+ LayerSpace<SkRect> mapRect(const LayerSpace<SkRect>& r) {
+ return LayerSpace<SkRect>(fData.mapRect(SkRect(r)));
+ }
+
+ LayerSpace<SkPoint> mapPoint(const LayerSpace<SkPoint>& p) {
+ return LayerSpace<SkPoint>(fData.mapPoint(SkPoint(p)));
+ }
+
+ LayerSpace<Vector> mapVector(const LayerSpace<Vector>& v) {
+ return LayerSpace<Vector>(Vector(fData.mapVector(v.x(), v.y())));
+ }
+
+ LayerSpace<SkMatrix>& preConcat(const LayerSpace<SkMatrix>& m) {
+ fData = SkMatrix::Concat(fData, m.fData);
+ return *this;
+ }
+
+ LayerSpace<SkMatrix>& postConcat(const LayerSpace<SkMatrix>& m) {
+ fData = SkMatrix::Concat(m.fData, fData);
+ return *this;
+ }
+
+ bool invert(LayerSpace<SkMatrix>* inverse) {
+ return fData.invert(&inverse->fData);
+ }
+
+private:
+ SkMatrix fData;
+};
+
// Mapping is the primary definition of the shared layer space used when evaluating an image filter
// DAG. It encapsulates any needed decomposition of the total CTM into the parameter-to-layer matrix
// (that filters use to map their parameters to the layer space), and the layer-to-device matrix
@@ -533,6 +580,8 @@
: fImage(std::move(image))
, fOrigin{{0, 0}} {}
+ explicit operator bool() const { return SkToBool(fImage); }
+
const SkSpecialImage* image() const { return fImage.get(); }
sk_sp<SkSpecialImage> refImage() const { return fImage; }
diff --git a/src/effects/imagefilters/SkMatrixTransformImageFilter.cpp b/src/effects/imagefilters/SkMatrixTransformImageFilter.cpp
index 0efc089..4e5b3ba 100644
--- a/src/effects/imagefilters/SkMatrixTransformImageFilter.cpp
+++ b/src/effects/imagefilters/SkMatrixTransformImageFilter.cpp
@@ -5,12 +5,13 @@
* found in the LICENSE file.
*/
-#include "include/core/SkFlattenable.h"
#include "src/core/SkImageFilter_Base.h"
#include "include/core/SkCanvas.h"
+#include "include/core/SkFlattenable.h"
#include "include/core/SkRect.h"
#include "include/effects/SkImageFilters.h"
+#include "src/core/SkImageFilterTypes.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkSamplingPriv.h"
#include "src/core/SkSpecialImage.h"
@@ -29,25 +30,32 @@
, fTransform(transform)
, fSampling(sampling) {
// Pre-cache so future calls to fTransform.getType() are threadsafe.
- (void)fTransform.getType();
+ (void) static_cast<const SkMatrix&>(fTransform).getType();
}
SkRect computeFastBounds(const SkRect&) const override;
protected:
-
void flatten(SkWriteBuffer&) const override;
- sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint* offset) const override;
- SkIRect onFilterNodeBounds(const SkIRect& src, const SkMatrix& ctm,
- MapDirection, const SkIRect* inputRect) const override;
-
private:
friend void ::SkRegisterMatrixTransformImageFilterFlattenable();
SK_FLATTENABLE_HOOKS(SkMatrixTransformImageFilter)
- SkMatrix fTransform;
- SkSamplingOptions fSampling;
+ skif::FilterResult onFilterImage(const skif::Context& context) const override;
+
+ skif::LayerSpace<SkIRect> onGetInputLayerBounds(
+ const skif::Mapping& mapping,
+ const skif::LayerSpace<SkIRect>& desiredOutput,
+ const skif::LayerSpace<SkIRect>& contentBounds,
+ VisitChildren recurse) const override;
+
+ skif::LayerSpace<SkIRect> onGetOutputLayerBounds(
+ const skif::Mapping& mapping,
+ const skif::LayerSpace<SkIRect>& contentBounds) const override;
+
+ skif::ParameterSpace<SkMatrix> fTransform;
+ SkSamplingOptions fSampling;
};
} // namespace
@@ -83,40 +91,36 @@
void SkMatrixTransformImageFilter::flatten(SkWriteBuffer& buffer) const {
this->SkImageFilter_Base::flatten(buffer);
- buffer.writeMatrix(fTransform);
+ buffer.writeMatrix(SkMatrix(fTransform));
buffer.writeSampling(fSampling);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
-sk_sp<SkSpecialImage> SkMatrixTransformImageFilter::onFilterImage(const Context& ctx,
- SkIPoint* offset) const {
-
- SkIPoint inputOffset = SkIPoint::Make(0, 0);
- sk_sp<SkSpecialImage> input(this->filterInput(0, ctx, &inputOffset));
- if (!input) {
- return nullptr;
+skif::FilterResult SkMatrixTransformImageFilter::onFilterImage(const skif::Context& context) const {
+ skif::FilterResult childOutput = this->filterInput(0, context);
+ if (!childOutput) {
+ return {};
}
- SkMatrix matrix;
- if (!ctx.ctm().invert(&matrix)) {
- return nullptr;
+ skif::LayerSpace<SkRect> srcBoundsF{childOutput.layerBounds()};
+ skif::LayerSpace<SkMatrix> matrix = context.mapping().paramToLayer(fTransform);
+ skif::LayerSpace<SkIRect> dstBounds = matrix.mapRect(srcBoundsF).roundOut();
+
+ // TODO(michaelludwig): Generalize this logic for image filters that fall into the pattern of
+ // draw into canvas, or fill canvas with X. Ideally it would let us avoid building a full
+ // canvas and device for the very constrained operations that they actually perform.
+
+ // Unless a child filter optimized its result, srcBounds should be large enough that dstBounds
+ // will cover the desired output. However, we only need to produce the intersection between
+ // what's desired and what's defined.
+ if (!dstBounds.intersect(context.desiredOutput())) {
+ return {};
}
- matrix.postConcat(fTransform);
- matrix.postConcat(ctx.ctm());
- const SkIRect srcBounds = SkIRect::MakeXYWH(inputOffset.x(), inputOffset.y(),
- input->width(), input->height());
- const SkRect srcRect = SkRect::Make(srcBounds);
-
- SkRect dstRect;
- matrix.mapRect(&dstRect, srcRect);
- SkIRect dstBounds;
- dstRect.roundOut(&dstBounds);
-
- sk_sp<SkSpecialSurface> surf(ctx.makeSurface(dstBounds.size()));
+ sk_sp<SkSpecialSurface> surf(context.makeSurface(SkISize(dstBounds.size())));
if (!surf) {
- return nullptr;
+ return {};
}
SkCanvas* canvas = surf->getCanvas();
@@ -124,54 +128,55 @@
canvas->clear(0x0);
- canvas->translate(-SkIntToScalar(dstBounds.x()), -SkIntToScalar(dstBounds.y()));
- canvas->concat(matrix);
+ canvas->translate(-dstBounds.left(), -dstBounds.top());
+ canvas->concat(SkMatrix(matrix));
SkPaint paint;
paint.setAntiAlias(true);
paint.setBlendMode(SkBlendMode::kSrc);
- input->draw(canvas, srcRect.x(), srcRect.y(), fSampling, &paint);
+ childOutput.image()->draw(canvas, srcBoundsF.left(), srcBoundsF.top(), fSampling, &paint);
- offset->fX = dstBounds.fLeft;
- offset->fY = dstBounds.fTop;
- return surf->makeImageSnapshot();
+ return {surf->makeImageSnapshot(), dstBounds.topLeft()};
}
SkRect SkMatrixTransformImageFilter::computeFastBounds(const SkRect& src) const {
SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
- SkRect dst;
- fTransform.mapRect(&dst, bounds);
- return dst;
+ return static_cast<const SkMatrix&>(fTransform).mapRect(bounds);
}
-SkIRect SkMatrixTransformImageFilter::onFilterNodeBounds(const SkIRect& src,
- const SkMatrix& ctm,
- MapDirection dir,
- const SkIRect* inputRect) const {
- SkMatrix matrix;
- if (!ctm.invert(&matrix)) {
- return src;
+skif::LayerSpace<SkIRect> SkMatrixTransformImageFilter::onGetInputLayerBounds(
+ const skif::Mapping& mapping,
+ const skif::LayerSpace<SkIRect>& desiredOutput,
+ const skif::LayerSpace<SkIRect>& contentBounds,
+ VisitChildren recurse) const {
+ // The required input for this filter to cover 'desiredOutput' is the smallest rectangle such
+ // that after being transformed by the layer-space adjusted 'fTransform', it contains the output
+ skif::LayerSpace<SkMatrix> inverse;
+ if (!mapping.paramToLayer(fTransform).invert(&inverse)) {
+ return skif::LayerSpace<SkIRect>::Empty();
}
- if (kForward_MapDirection == dir) {
- matrix.postConcat(fTransform);
+ skif::LayerSpace<SkIRect> requiredInput =
+ inverse.mapRect(skif::LayerSpace<SkRect>(desiredOutput)).roundOut();
+
+ // Additionally if there is any filtering beyond nearest neighbor, we request an extra buffer of
+ // pixels so that the content is available to the bilerp/bicubic kernel.
+ if (fSampling != SkSamplingOptions()) {
+ requiredInput.outset(skif::LayerSpace<SkISize>({1, 1}));
+ }
+
+ if (recurse == VisitChildren::kNo) {
+ return requiredInput;
} else {
- SkMatrix transformInverse;
- if (!fTransform.invert(&transformInverse)) {
- return src;
- }
- matrix.postConcat(transformInverse);
+ // Our required input is the desired output for our child image filter.
+ return this->visitInputLayerBounds(mapping, requiredInput, contentBounds);
}
- matrix.postConcat(ctm);
- SkRect floatBounds;
- matrix.mapRect(&floatBounds, SkRect::Make(src));
- SkIRect result = floatBounds.roundOut();
+}
- if (kReverse_MapDirection == dir && SkSamplingOptions() != fSampling) {
- // When filtering we might need some pixels in the source that might be otherwise
- // clipped off.
- result.outset(1, 1);
- }
-
- return result;
+skif::LayerSpace<SkIRect> SkMatrixTransformImageFilter::onGetOutputLayerBounds(
+ const skif::Mapping& mapping,
+ const skif::LayerSpace<SkIRect>& contentBounds) const {
+ // The output of this filter is the transformed bounds of its child's output.
+ skif::LayerSpace<SkRect> childOutput{this->visitOutputLayerBounds(mapping, contentBounds)};
+ return mapping.paramToLayer(fTransform).mapRect(childOutput).roundOut();
}