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