• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 Google LLC
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/effects/imagefilters/SkCropImageFilter.h"
9 
10 #include "include/core/SkFlattenable.h"
11 #include "include/core/SkImageFilter.h"
12 #include "include/core/SkRect.h"
13 #include "src/core/SkImageFilterTypes.h"
14 #include "src/core/SkImageFilter_Base.h"
15 #include "src/core/SkReadBuffer.h"
16 #include "src/core/SkValidationUtils.h"
17 #include "src/core/SkWriteBuffer.h"
18 
19 #include <utility>
20 
21 namespace {
22 
23 class SkCropImageFilter final : public SkImageFilter_Base {
24 public:
SkCropImageFilter(const SkRect & cropRect,sk_sp<SkImageFilter> input)25     SkCropImageFilter(const SkRect& cropRect, sk_sp<SkImageFilter> input)
26             : SkImageFilter_Base(&input, 1, /*cropRect=*/nullptr)
27             , fCropRect(cropRect) {
28         SkASSERT(cropRect.isFinite());
29         SkASSERT(cropRect.isSorted());
30     }
31 
32     SkRect computeFastBounds(const SkRect& bounds) const override;
33 
34 protected:
35     void flatten(SkWriteBuffer&) const override;
36 
37 private:
38     friend void ::SkRegisterCropImageFilterFlattenable();
39     SK_FLATTENABLE_HOOKS(SkCropImageFilter)
40 
41     skif::FilterResult onFilterImage(const skif::Context& context) const override;
42 
43     skif::LayerSpace<SkIRect> onGetInputLayerBounds(
44             const skif::Mapping& mapping,
45             const skif::LayerSpace<SkIRect>& desiredOutput,
46             const skif::LayerSpace<SkIRect>& contentBounds,
47             VisitChildren recurse) const override;
48 
49     skif::LayerSpace<SkIRect> onGetOutputLayerBounds(
50             const skif::Mapping& mapping,
51             const skif::LayerSpace<SkIRect>& contentBounds) const override;
52 
53     // The crop rect is specified in floating point to allow cropping to partial local pixels,
54     // that could become whole pixels in the layer-space image if the canvas is scaled.
55     // For now it's always rounded to integer pixels as if it were non-AA.
cropRect(const skif::Mapping & mapping) const56     skif::LayerSpace<SkIRect> cropRect(const skif::Mapping& mapping) const {
57         return mapping.paramToLayer(fCropRect).roundOut();
58     }
59 
60     skif::ParameterSpace<SkRect> fCropRect;
61 };
62 
63 } // end namespace
64 
SkMakeCropImageFilter(const SkRect & rect,sk_sp<SkImageFilter> input)65 sk_sp<SkImageFilter> SkMakeCropImageFilter(const SkRect& rect, sk_sp<SkImageFilter> input) {
66     if (!rect.isFinite()) {
67         return nullptr;
68     }
69     return sk_sp<SkImageFilter>(new SkCropImageFilter(rect, std::move(input)));
70 }
71 
SkRegisterCropImageFilterFlattenable()72 void SkRegisterCropImageFilterFlattenable() {
73     SK_REGISTER_FLATTENABLE(SkCropImageFilter);
74 }
75 
CreateProc(SkReadBuffer & buffer)76 sk_sp<SkFlattenable> SkCropImageFilter::CreateProc(SkReadBuffer& buffer) {
77     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
78     SkRect cropRect = buffer.readRect();
79     if (!buffer.isValid() || !buffer.validate(SkIsValidRect(cropRect))) {
80         return nullptr;
81     }
82     return SkMakeCropImageFilter(cropRect, common.getInput(0));
83 }
84 
flatten(SkWriteBuffer & buffer) const85 void SkCropImageFilter::flatten(SkWriteBuffer& buffer) const {
86     this->SkImageFilter_Base::flatten(buffer);
87     buffer.writeRect(SkRect(fCropRect));
88 }
89 
90 ///////////////////////////////////////////////////////////////////////////////////////////////////
91 
onFilterImage(const skif::Context & context) const92 skif::FilterResult SkCropImageFilter::onFilterImage(const skif::Context& context) const {
93     skif::LayerSpace<SkIRect> cropBounds = this->cropRect(context.mapping());
94     if (cropBounds.isEmpty()) {
95         // Don't bother evaluating the input filter if the crop wouldn't show anything
96         return {};
97     }
98 
99     skif::FilterResult childOutput = this->filterInput(0, context);
100     // While filterInput() adjusts the context passed to our child filter to account for the
101     // crop rect and desired output, 'childOutput' does not necessarily fit that exactly. Calling
102     // applyCrop() ensures this is true, optimally avoiding rendering a new image if possible.
103     return childOutput.applyCrop(context, cropBounds);
104 }
105 
106 // TODO(michaelludwig) - onGetInputLayerBounds() and onGetOutputLayerBounds() are tightly coupled
107 // to both each other's behavior and to onFilterImage(). If onFilterImage() had a concept of a
108 // dry-run (e.g. FilterResult had null images but tracked the bounds the images would be) then
109 // onGetInputLayerBounds() is the union of all requested inputs at the leaf nodes of the DAG, and
110 // onGetOutputLayerBounds() is the bounds of the dry-run result. This might have more overhead, but
111 // would reduce the complexity of implementations by quite a bit.
onGetInputLayerBounds(const skif::Mapping & mapping,const skif::LayerSpace<SkIRect> & desiredOutput,const skif::LayerSpace<SkIRect> & contentBounds,VisitChildren recurse) const112 skif::LayerSpace<SkIRect> SkCropImageFilter::onGetInputLayerBounds(
113         const skif::Mapping& mapping,
114         const skif::LayerSpace<SkIRect>& desiredOutput,
115         const skif::LayerSpace<SkIRect>& contentBounds,
116         VisitChildren recurse) const {
117     // Assuming unbounded desired output, this filter only needs to process an image that's at most
118     // sized to our crop rect.
119     skif::LayerSpace<SkIRect> requiredInput = this->cropRect(mapping);
120     // But we can restrict the crop rect to just what's requested, since anything beyond that won't
121     // be rendered.
122     if (!requiredInput.intersect(desiredOutput)) {
123         // We wouldn't draw anything when filtering, so return empty bounds now to skip a layer.
124         return skif::LayerSpace<SkIRect>::Empty();
125     }
126 
127     if (recurse == VisitChildren::kNo) {
128         return requiredInput;
129     } else {
130         // Our required input is the desired output for our child image filter.
131         return this->visitInputLayerBounds(mapping, requiredInput, contentBounds);
132     }
133 }
134 
onGetOutputLayerBounds(const skif::Mapping & mapping,const skif::LayerSpace<SkIRect> & contentBounds) const135 skif::LayerSpace<SkIRect> SkCropImageFilter::onGetOutputLayerBounds(
136         const skif::Mapping& mapping,
137         const skif::LayerSpace<SkIRect>& contentBounds) const {
138     // Assuming unbounded child content, our output is a decal-tiled image sized to our crop rect.
139     skif::LayerSpace<SkIRect> output = this->cropRect(mapping);
140     // But the child output image is drawn into our output surface with its own decal tiling, which
141     // may allow the output dimensions to be reduced.
142     skif::LayerSpace<SkIRect> childOutput = this->visitOutputLayerBounds(mapping, contentBounds);
143 
144     if (output.intersect(childOutput)) {
145         return output;
146     } else {
147         // Nothing would be drawn into our crop rect, so nothing would be output.
148         return skif::LayerSpace<SkIRect>::Empty();
149     }
150 }
151 
computeFastBounds(const SkRect & bounds) const152 SkRect SkCropImageFilter::computeFastBounds(const SkRect& bounds) const {
153     // TODO(michaelludwig) - This is conceptually very similar to calling onGetOutputLayerBounds()
154     // with an identity skif::Mapping (hence why fCropRect can be used directly), but it also does
155     // not involve any rounding to pixels for both the content bounds or the output.
156     // FIXME(michaelludwig) - There is a limitation in the current system for "fast bounds", since
157     // there's no way for the crop image filter to hide the fact that a child affects transparent
158     // black, so the entire DAG still is treated as if it cannot compute fast bounds. If we migrate
159     // getOutputLayerBounds() to operate on float rects, and to report infinite bounds for
160     // nodes that affect transparent black, then fastBounds() and onAffectsTransparentBlack() impls
161     // can go away entirely. That's not feasible until everything else is migrated onto the new crop
162     // rect filter and the new APIs.
163     if (this->getInput(0) && !this->getInput(0)->canComputeFastBounds()) {
164         // The input bounds to the crop are effectively infinite so the output fills the crop rect.
165         return SkRect(fCropRect);
166     }
167 
168     SkRect inputBounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(bounds) : bounds;
169     if (!inputBounds.intersect(SkRect(fCropRect))) {
170         return SkRect::MakeEmpty();
171     }
172     return inputBounds;
173 }
174