Allow image filters to require a translation-only layer transform

Will be used for first version of runtime image filters.

Bug: skia:12074
Change-Id: I2d7fc3af77d5df8182fd3f3d8da888e20ee05b34
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/416778
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Brian Osman 2021-06-08 15:17:11 -04:00 committed by Skia Commit-Bot
parent 4f4b5212d5
commit 03f1d12b64
11 changed files with 60 additions and 42 deletions

View File

@ -315,21 +315,21 @@ skif::FilterResult<For::kOutput> SkImageFilter_Base::onFilterImage(const skif::C
return skif::FilterResult<For::kOutput>(std::move(image), skif::LayerSpace<SkIPoint>(origin));
}
bool SkImageFilter_Base::canHandleComplexCTM() const {
SkImageFilter_Base::MatrixCapability SkImageFilter_Base::getCTMCapability() const {
MatrixCapability result = this->onGetCTMCapability();
// CropRects need to apply in the source coordinate system, but are not aware of complex CTMs
// when performing clipping. For a simple fix, any filter with a crop rect set cannot support
// complex CTMs until that's updated.
if (this->cropRectIsSet() || !this->onCanHandleComplexCTM()) {
return false;
// more than scale+translate CTMs until that's updated.
if (this->cropRectIsSet()) {
result = std::min(result, MatrixCapability::kScaleTranslate);
}
const int count = this->countInputs();
for (int i = 0; i < count; ++i) {
const SkImageFilter_Base* input = as_IFB(this->getInput(i));
if (input && !input->canHandleComplexCTM()) {
return false;
if (const SkImageFilter_Base* input = as_IFB(this->getInput(i))) {
result = std::min(result, input->getCTMCapability());
}
}
return true;
return result;
}
void SkImageFilter_Base::CropRect::applyTo(const SkIRect& imageBounds, const SkMatrix& ctm,

View File

@ -30,9 +30,16 @@ Mapping Mapping::DecomposeCTM(const SkMatrix& ctm, const SkImageFilter* filter,
const skif::ParameterSpace<SkPoint>& representativePoint) {
SkMatrix remainder, layer;
SkSize scale;
if (!filter || ctm.isScaleTranslate() || as_IFB(filter)->canHandleComplexCTM()) {
// It doesn't matter what type of matrix ctm is, we can have layer space be equivalent to
// device space.
using MatrixCapability = SkImageFilter_Base::MatrixCapability;
MatrixCapability capability =
filter ? as_IFB(filter)->getCTMCapability() : MatrixCapability::kComplex;
if (capability == MatrixCapability::kTranslate) {
// Apply the entire CTM post-filtering
remainder = ctm;
layer = SkMatrix::I();
} else if (ctm.isScaleTranslate() || capability == MatrixCapability::kComplex) {
// Either layer space can be anything (kComplex) - or - it can be scale+translate, and the
// ctm is. In both cases, the layer space can be equivalent to device space.
remainder = SkMatrix::I();
layer = ctm;
} else if (ctm.decomposeScale(&scale, &remainder)) {

View File

@ -648,11 +648,11 @@ public:
// layer space where the image filters are evaluated, as well as the remaining transformation
// from the layer space to the final device space. The layer space defined by the returned
// Mapping may be the same as the root device space, or be an intermediate space that is
// supported by the image filter DAG (depending on what it returns from canHandleComplexCTM()).
// If a node returns false from canHandleComplexCTM(), the layer matrix of the mapping will be
// at most a scale + translate, and the remaining matrix will be appropriately set to transform
// the layer space to the final device space (applied by the SkCanvas when filtering is
// finished).
// supported by the image filter DAG (depending on what it returns from getCTMCapability()).
// If a node returns something other than kComplex from getCTMCapability(), the layer matrix of
// the mapping will respect that return value, and the remaining matrix will be appropriately
// set to transform the layer space to the final device space (applied by the SkCanvas when
// filtering is finished).
const Mapping& mapping() const { return fMapping; }
// DEPRECATED: Use mapping() and its coordinate-space types instead
const SkMatrix& ctm() const { return fMapping.layerMatrix(); }

View File

@ -108,11 +108,16 @@ public:
}
/**
* ImageFilters can natively handle scaling and translate components in the CTM. Only some of
* them can handle affine (or more complex) matrices. This call returns true iff the filter
* and all of its (non-null) inputs can handle these more complex matrices.
* Most ImageFilters can natively handle scaling and translate components in the CTM. Only
* some of them can handle affine (or more complex) matrices. Some may only handle translation.
* This call returns the maximum "kind" of CTM for a filter and all of its (non-null) inputs.
*/
bool canHandleComplexCTM() const;
enum class MatrixCapability {
kTranslate,
kScaleTranslate,
kComplex,
};
MatrixCapability getCTMCapability() const;
uint32_t uniqueID() const { return fUniqueID; }
@ -350,11 +355,14 @@ private:
virtual bool onIsColorFilterNode(SkColorFilter** /*filterPtr*/) const { return false; }
/**
* Return true if this filter can map from its parameter space to a layer space described by an
* arbitrary transformation matrix. If this returns false, the filter only needs to worry about
* mapping from parameter to layer using a scale+translate matrix.
* Return the most complex matrix type this filter can support (mapping from its parameter
* space to a layer space). If this returns anything less than kComplex, the filter only needs
* to worry about mapping from parameter to layer using a matrix that is constrained in that
* way (eg, scale+translate).
*/
virtual bool onCanHandleComplexCTM() const { return false; }
virtual MatrixCapability onGetCTMCapability() const {
return MatrixCapability::kScaleTranslate;
}
/**
* Return true if this filter would transform transparent black pixels to a color other than

View File

@ -18,7 +18,9 @@ sk_sp<SkImageFilter> SkLocalMatrixImageFilter::Make(const SkMatrix& localM,
if (localM.isIdentity()) {
return input;
}
if (!as_IFB(input)->canHandleComplexCTM() && !localM.isScaleTranslate()) {
MatrixCapability inputCapability = as_IFB(input)->getCTMCapability();
if ((inputCapability == MatrixCapability::kTranslate && !localM.isTranslate()) ||
(inputCapability == MatrixCapability::kScaleTranslate && !localM.isScaleTranslate())) {
// Nothing we can do at this point
return nullptr;
}

View File

@ -27,7 +27,7 @@ protected:
SkIRect onFilterBounds(const SkIRect& src, const SkMatrix& ctm,
MapDirection, const SkIRect* inputRect) const override;
bool onCanHandleComplexCTM() const override { return true; }
MatrixCapability onGetCTMCapability() const override { return MatrixCapability::kComplex; }
private:
SK_FLATTENABLE_HOOKS(SkLocalMatrixImageFilter)

View File

@ -28,7 +28,7 @@ protected:
void flatten(SkWriteBuffer&) const override;
sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint* offset) const override;
bool onIsColorFilterNode(SkColorFilter**) const override;
bool onCanHandleComplexCTM() const override { return true; }
MatrixCapability onGetCTMCapability() const override { return MatrixCapability::kComplex; }
bool affectsTransparentBlack() const override;
private:

View File

@ -27,7 +27,7 @@ protected:
sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint* offset) const override;
SkIRect onFilterBounds(const SkIRect&, const SkMatrix& ctm,
MapDirection, const SkIRect* inputRect) const override;
bool onCanHandleComplexCTM() const override { return true; }
MatrixCapability onGetCTMCapability() const override { return MatrixCapability::kComplex; }
private:
friend void ::SkRegisterComposeImageFilterFlattenable();

View File

@ -38,7 +38,7 @@ protected:
SkIRect onFilterNodeBounds(const SkIRect&, const SkMatrix& ctm,
MapDirection, const SkIRect* inputRect) const override;
bool onCanHandleComplexCTM() const override { return true; }
MatrixCapability onGetCTMCapability() const override { return MatrixCapability::kComplex; }
private:
friend void ::SkRegisterImageImageFilterFlattenable();

View File

@ -26,7 +26,7 @@ public:
protected:
sk_sp<SkSpecialImage> onFilterImage(const Context&, SkIPoint* offset) const override;
bool onCanHandleComplexCTM() const override { return true; }
MatrixCapability onGetCTMCapability() const override { return MatrixCapability::kComplex; }
private:
friend void ::SkRegisterMergeImageFilterFlattenable();

View File

@ -1786,26 +1786,27 @@ DEF_TEST(ImageFilterComplexCTM, reporter) {
sk_sp<SkColorFilter> cf = SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcATop);
sk_sp<SkImageFilter> cfif = SkImageFilters::ColorFilter(cf, nullptr); // can handle
sk_sp<SkImageFilter> blif = SkImageFilters::Blur(3, 3, nullptr); // cannot handle
using MatrixCapability = SkImageFilter_Base::MatrixCapability;
struct {
sk_sp<SkImageFilter> fFilter;
bool fExpectCanHandle;
MatrixCapability fExpectCapability;
} recs[] = {
{ cfif, true },
{ SkImageFilters::ColorFilter(cf, cfif), true },
{ SkImageFilters::Merge(cfif, cfif), true },
{ SkImageFilters::Compose(cfif, cfif), true },
{ cfif, MatrixCapability::kComplex },
{ SkImageFilters::ColorFilter(cf, cfif), MatrixCapability::kComplex },
{ SkImageFilters::Merge(cfif, cfif), MatrixCapability::kComplex },
{ SkImageFilters::Compose(cfif, cfif), MatrixCapability::kComplex },
{ blif, false },
{ SkImageFilters::Blur(3, 3, cfif), false },
{ SkImageFilters::ColorFilter(cf, blif), false },
{ SkImageFilters::Merge(cfif, blif), false },
{ SkImageFilters::Compose(blif, cfif), false },
{ blif, MatrixCapability::kScaleTranslate },
{ SkImageFilters::Blur(3, 3, cfif), MatrixCapability::kScaleTranslate },
{ SkImageFilters::ColorFilter(cf, blif), MatrixCapability::kScaleTranslate },
{ SkImageFilters::Merge(cfif, blif), MatrixCapability::kScaleTranslate },
{ SkImageFilters::Compose(blif, cfif), MatrixCapability::kScaleTranslate },
};
for (const auto& rec : recs) {
const bool canHandle = as_IFB(rec.fFilter)->canHandleComplexCTM();
REPORTER_ASSERT(reporter, canHandle == rec.fExpectCanHandle);
const MatrixCapability capability = as_IFB(rec.fFilter)->getCTMCapability();
REPORTER_ASSERT(reporter, capability == rec.fExpectCapability);
}
}