| /* |
| * Copyright 2021 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/effects/imagefilters/SkCropImageFilter.h" |
| |
| #include "include/core/SkFlattenable.h" |
| #include "include/core/SkImageFilter.h" |
| #include "include/core/SkRect.h" |
| #include "src/core/SkImageFilterTypes.h" |
| #include "src/core/SkImageFilter_Base.h" |
| #include "src/core/SkReadBuffer.h" |
| #include "src/core/SkValidationUtils.h" |
| #include "src/core/SkWriteBuffer.h" |
| |
| #include <utility> |
| |
| namespace { |
| |
| class SkCropImageFilter final : public SkImageFilter_Base { |
| public: |
| SkCropImageFilter(const SkRect& cropRect, sk_sp<SkImageFilter> input) |
| : SkImageFilter_Base(&input, 1, /*cropRect=*/nullptr) |
| , fCropRect(cropRect) { |
| SkASSERT(cropRect.isFinite()); |
| SkASSERT(cropRect.isSorted()); |
| } |
| |
| SkRect computeFastBounds(const SkRect& bounds) const override; |
| |
| protected: |
| void flatten(SkWriteBuffer&) const override; |
| |
| private: |
| friend void ::SkRegisterCropImageFilterFlattenable(); |
| SK_FLATTENABLE_HOOKS(SkCropImageFilter) |
| |
| 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; |
| |
| // The crop rect is specified in floating point to allow cropping to partial local pixels, |
| // that could become whole pixels in the layer-space image if the canvas is scaled. |
| // For now it's always rounded to integer pixels as if it were non-AA. |
| skif::LayerSpace<SkIRect> cropRect(const skif::Mapping& mapping) const { |
| return mapping.paramToLayer(fCropRect).roundOut(); |
| } |
| |
| skif::ParameterSpace<SkRect> fCropRect; |
| }; |
| |
| } // end namespace |
| |
| sk_sp<SkImageFilter> SkMakeCropImageFilter(const SkRect& rect, sk_sp<SkImageFilter> input) { |
| if (!rect.isFinite()) { |
| return nullptr; |
| } |
| return sk_sp<SkImageFilter>(new SkCropImageFilter(rect, std::move(input))); |
| } |
| |
| void SkRegisterCropImageFilterFlattenable() { |
| SK_REGISTER_FLATTENABLE(SkCropImageFilter); |
| } |
| |
| sk_sp<SkFlattenable> SkCropImageFilter::CreateProc(SkReadBuffer& buffer) { |
| SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1); |
| SkRect cropRect = buffer.readRect(); |
| if (!buffer.isValid() || !buffer.validate(SkIsValidRect(cropRect))) { |
| return nullptr; |
| } |
| return SkMakeCropImageFilter(cropRect, common.getInput(0)); |
| } |
| |
| void SkCropImageFilter::flatten(SkWriteBuffer& buffer) const { |
| this->SkImageFilter_Base::flatten(buffer); |
| buffer.writeRect(SkRect(fCropRect)); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| skif::FilterResult SkCropImageFilter::onFilterImage(const skif::Context& context) const { |
| skif::LayerSpace<SkIRect> cropBounds = this->cropRect(context.mapping()); |
| if (cropBounds.isEmpty()) { |
| // Don't bother evaluating the input filter if the crop wouldn't show anything |
| return {}; |
| } |
| |
| skif::FilterResult childOutput = this->filterInput(0, context); |
| // While filterInput() adjusts the context passed to our child filter to account for the |
| // crop rect and desired output, 'childOutput' does not necessarily fit that exactly. Calling |
| // applyCrop() ensures this is true, optimally avoiding rendering a new image if possible. |
| return childOutput.applyCrop(context, cropBounds); |
| } |
| |
| // TODO(michaelludwig) - onGetInputLayerBounds() and onGetOutputLayerBounds() are tightly coupled |
| // to both each other's behavior and to onFilterImage(). If onFilterImage() had a concept of a |
| // dry-run (e.g. FilterResult had null images but tracked the bounds the images would be) then |
| // onGetInputLayerBounds() is the union of all requested inputs at the leaf nodes of the DAG, and |
| // onGetOutputLayerBounds() is the bounds of the dry-run result. This might have more overhead, but |
| // would reduce the complexity of implementations by quite a bit. |
| skif::LayerSpace<SkIRect> SkCropImageFilter::onGetInputLayerBounds( |
| const skif::Mapping& mapping, |
| const skif::LayerSpace<SkIRect>& desiredOutput, |
| const skif::LayerSpace<SkIRect>& contentBounds, |
| VisitChildren recurse) const { |
| // Assuming unbounded desired output, this filter only needs to process an image that's at most |
| // sized to our crop rect. |
| skif::LayerSpace<SkIRect> requiredInput = this->cropRect(mapping); |
| // But we can restrict the crop rect to just what's requested, since anything beyond that won't |
| // be rendered. |
| if (!requiredInput.intersect(desiredOutput)) { |
| // We wouldn't draw anything when filtering, so return empty bounds now to skip a layer. |
| return skif::LayerSpace<SkIRect>::Empty(); |
| } |
| |
| if (recurse == VisitChildren::kNo) { |
| return requiredInput; |
| } else { |
| // Our required input is the desired output for our child image filter. |
| return this->visitInputLayerBounds(mapping, requiredInput, contentBounds); |
| } |
| } |
| |
| skif::LayerSpace<SkIRect> SkCropImageFilter::onGetOutputLayerBounds( |
| const skif::Mapping& mapping, |
| const skif::LayerSpace<SkIRect>& contentBounds) const { |
| // Assuming unbounded child content, our output is a decal-tiled image sized to our crop rect. |
| skif::LayerSpace<SkIRect> output = this->cropRect(mapping); |
| // But the child output image is drawn into our output surface with its own decal tiling, which |
| // may allow the output dimensions to be reduced. |
| skif::LayerSpace<SkIRect> childOutput = this->visitOutputLayerBounds(mapping, contentBounds); |
| |
| if (output.intersect(childOutput)) { |
| return output; |
| } else { |
| // Nothing would be drawn into our crop rect, so nothing would be output. |
| return skif::LayerSpace<SkIRect>::Empty(); |
| } |
| } |
| |
| SkRect SkCropImageFilter::computeFastBounds(const SkRect& bounds) const { |
| // TODO(michaelludwig) - This is conceptually very similar to calling onGetOutputLayerBounds() |
| // with an identity skif::Mapping (hence why fCropRect can be used directly), but it also does |
| // not involve any rounding to pixels for both the content bounds or the output. |
| // FIXME(michaelludwig) - There is a limitation in the current system for "fast bounds", since |
| // there's no way for the crop image filter to hide the fact that a child affects transparent |
| // black, so the entire DAG still is treated as if it cannot compute fast bounds. If we migrate |
| // getOutputLayerBounds() to operate on float rects, and to report infinite bounds for |
| // nodes that affect transparent black, then fastBounds() and onAffectsTransparentBlack() impls |
| // can go away entirely. That's not feasible until everything else is migrated onto the new crop |
| // rect filter and the new APIs. |
| if (this->getInput(0) && !this->getInput(0)->canComputeFastBounds()) { |
| // The input bounds to the crop are effectively infinite so the output fills the crop rect. |
| return SkRect(fCropRect); |
| } |
| |
| SkRect inputBounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(bounds) : bounds; |
| if (!inputBounds.intersect(SkRect(fCropRect))) { |
| return SkRect::MakeEmpty(); |
| } |
| return inputBounds; |
| } |