Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkMagnifierImageFilter.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2012 The Android Open Source Project
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
9
12#include "include/core/SkM44.h"
15#include "include/core/SkRect.h"
20#include "include/core/SkSize.h"
21#include "include/core/SkSpan.h"
31
32#include <algorithm>
33#include <optional>
34#include <utility>
35
36namespace {
37
38class SkMagnifierImageFilter final : public SkImageFilter_Base {
39public:
40 SkMagnifierImageFilter(const SkRect& lensBounds,
41 float zoomAmount,
42 float inset,
43 const SkSamplingOptions& sampling,
45 : SkImageFilter_Base(&input, 1)
46 , fLensBounds(lensBounds)
47 , fZoomAmount(zoomAmount)
48 , fInset(inset)
49 , fSampling(sampling) {}
50
51 SkRect computeFastBounds(const SkRect&) const override;
52
53protected:
54 void flatten(SkWriteBuffer&) const override;
55
56private:
57 friend void ::SkRegisterMagnifierImageFilterFlattenable();
58 SK_FLATTENABLE_HOOKS(SkMagnifierImageFilter)
59
60 skif::FilterResult onFilterImage(const skif::Context& context) const override;
61
63 const skif::Mapping& mapping,
64 const skif::LayerSpace<SkIRect>& desiredOutput,
65 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
66
67 std::optional<skif::LayerSpace<SkIRect>> onGetOutputLayerBounds(
68 const skif::Mapping& mapping,
69 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
70
72 // Zoom is relative so does not belong to a coordinate space, see note in onFilterImage().
73 float fZoomAmount;
74 // Inset is really a ParameterSpace<SkSize> where width = height = fInset, but we store just the
75 // float here for easier serialization and convert to a size in onFilterImage().
76 float fInset;
77 SkSamplingOptions fSampling;
78};
79
80} // end namespace
81
83 SkScalar zoomAmount,
85 const SkSamplingOptions& sampling,
87 const CropRect& cropRect) {
88 if (lensBounds.isEmpty() || !lensBounds.isFinite() ||
89 zoomAmount <= 0.f || inset < 0.f ||
90 !SkIsFinite(zoomAmount, inset)) {
91 return nullptr; // invalid
92 }
93 // The magnifier automatically restricts its output based on the size of the image it receives
94 // as input, so 'cropRect' only applies to its input.
95 if (cropRect) {
96 input = SkImageFilters::Crop(*cropRect, std::move(input));
97 }
98
99 if (zoomAmount > 1.f) {
100 return sk_sp<SkImageFilter>(new SkMagnifierImageFilter(lensBounds, zoomAmount, inset,
101 sampling, std::move(input)));
102 } else {
103 // Zooming with a value less than 1 is technically a downscaling, which "works" but the
104 // non-linear distortion behaves unintuitively. At zoomAmount = 1, this filter is an
105 // expensive identity function so treat zoomAmount <= 1 as a no-op.
106 return input;
107 }
108}
109
113
114sk_sp<SkFlattenable> SkMagnifierImageFilter::CreateProc(SkReadBuffer& buffer) {
116 // This was actually a legacy magnifier image filter that was serialized. Chrome is the
117 // only known client of the magnifier and its not used on webpages, so there shouldn't be
118 // SKPs that actually contain a flattened magnifier filter (legacy or new).
119 return nullptr;
120 }
121
123
124 SkRect lensBounds;
125 buffer.readRect(&lensBounds);
126 SkScalar zoomAmount = buffer.readScalar();
127 SkScalar inset = buffer.readScalar();
128 SkSamplingOptions sampling = buffer.readSampling();
129 return SkImageFilters::Magnifier(lensBounds, zoomAmount, inset, sampling, common.getInput(0));
130}
131
132void SkMagnifierImageFilter::flatten(SkWriteBuffer& buffer) const {
133 this->SkImageFilter_Base::flatten(buffer);
134 buffer.writeRect(SkRect(fLensBounds));
135 buffer.writeScalar(fZoomAmount);
136 buffer.writeScalar(fInset);
137 buffer.writeSampling(fSampling);
138}
139
140////////////////////////////////////////////////////////////////////////////////
141
143 sk_sp<SkShader> input,
144 const skif::LayerSpace<SkRect>& lensBounds,
145 const skif::LayerSpace<SkMatrix>& zoomXform,
147 const SkRuntimeEffect* magEffect =
148 GetKnownRuntimeEffect(SkKnownRuntimeEffects::StableKey::kMagnifier);
149
150 SkRuntimeShaderBuilder builder(sk_ref_sp(magEffect));
151 builder.child("src") = std::move(input);
152
153 SkASSERT(inset.width() > 0.f && inset.height() > 0.f);
154 builder.uniform("lensBounds") = SkRect(lensBounds);
155 builder.uniform("zoomXform") = SkV4{/*Tx*/zoomXform.rc(0, 2), /*Ty*/zoomXform.rc(1, 2),
156 /*Sx*/zoomXform.rc(0, 0), /*Sy*/zoomXform.rc(1, 1)};
157 builder.uniform("invInset") = SkV2{1.f / inset.width(),
158 1.f / inset.height()};
159
160 return builder.makeShader();
161}
162
163////////////////////////////////////////////////////////////////////////////////
164
165skif::FilterResult SkMagnifierImageFilter::onFilterImage(const skif::Context& context) const {
166 // These represent the full lens bounds and the ideal zoom center if everything is visible.
167 skif::LayerSpace<SkRect> lensBounds = context.mapping().paramToLayer(fLensBounds);
168 skif::LayerSpace<SkPoint> zoomCenter = lensBounds.center();
169
170 // When magnifying near the edge of the screen, it's common for part of the lens bounds to be
171 // offscreen, which also means its input filter cannot provide the full required input.
172 // The magnifier's auto-sizing's goal is to cover the visible portion of the lens bounds.
173 skif::LayerSpace<SkRect> visibleLensBounds = lensBounds;
174 if (!visibleLensBounds.intersect(skif::LayerSpace<SkRect>(context.desiredOutput()))) {
175 return {};
176 }
177
178 // We pre-emptively fit the zoomed-in src rect to what we expect the child input filter to
179 // produce. This should be correct in all cases except for failure to create an offscreen image,
180 // at which point there's nothing to be done anyway.
181 skif::LayerSpace<SkRect> expectedChildOutput = lensBounds;
182 if (std::optional<skif::LayerSpace<SkIRect>> output =
183 this->getChildOutputLayerBounds(0, context.mapping(), context.source().layerBounds())) {
184 expectedChildOutput = skif::LayerSpace<SkRect>(*output);
185 }
186
187 // Clamp the zoom center to be within the childOutput image
188 zoomCenter = expectedChildOutput.clamp(zoomCenter);
189
190 // The zoom we want to apply in layer-space is equal to
191 // mapping.paramToLayer(SkMatrix::Scale(fZoomAmount)).decomposeScale(&layerZoom).
192 // Because this filter only supports scale+translate matrices, the paramToLayer transform of
193 // the parameter-space scale matrix is a no-op. Thus layerZoom == fZoomAmount and we can avoid
194 // all of that math. This assumption is invalid if the matrix complexity is more than S+T.
195 SkASSERT(this->getCTMCapability() == MatrixCapability::kScaleTranslate);
196 float invZoom = 1.f / fZoomAmount;
197
198 // The srcRect is the bounding box of the pixels that are linearly scaled up, about zoomCenter.
199 // This is not the visual bounds of this upscaled region, but the bounds of the source pixels
200 // that will fill the main magnified region (which is simply the inset of lensBounds). When
201 // lensBounds has not been cropped by the actual input image, these equations are identical to
202 // the more intuitive L/R = center.x -/+ width/(2*zoom) and T/B = center.y -/+ height/(2*zoom).
203 // However, when lensBounds is cropped this automatically shifts the source rectangle away from
204 // the original zoom center such that the upscaled area is contained within the input image.
206 lensBounds.left() * invZoom + zoomCenter.x()*(1.f - invZoom),
207 lensBounds.top() * invZoom + zoomCenter.y()*(1.f - invZoom),
208 lensBounds.right() * invZoom + zoomCenter.x()*(1.f - invZoom),
209 lensBounds.bottom()* invZoom + zoomCenter.y()*(1.f - invZoom)}};
210
211 // The above adjustment helps to account for offscreen, but when the magnifier is combined with
212 // backdrop offsets, more significant fitting needs to be performed to pin the visible src
213 // rect to what's available.
214 auto zoomXform = skif::LayerSpace<SkMatrix>::RectToRect(lensBounds, srcRect);
215 if (!expectedChildOutput.contains(visibleLensBounds)) {
216 // We need to pick a new srcRect such that srcRect is contained within fitRect and fills
217 // visibleLens, while maintaining the aspect ratio of the original srcRect -> lensBounds.
218 srcRect = zoomXform.mapRect(visibleLensBounds);
219
220 if (expectedChildOutput.width() >= srcRect.width() &&
221 expectedChildOutput.height() >= srcRect.height()) {
222 float left = srcRect.left() < expectedChildOutput.left() ?
223 expectedChildOutput.left() :
224 std::min(srcRect.right(), expectedChildOutput.right()) - srcRect.width();
225 float top = srcRect.top() < expectedChildOutput.top() ?
226 expectedChildOutput.top() :
227 std::min(srcRect.bottom(), expectedChildOutput.bottom()) - srcRect.height();
228
229 // Update transform to reflect fitted src
230 srcRect = skif::LayerSpace<SkRect>(
231 SkRect::MakeXYWH(left, top, srcRect.width(), srcRect.height()));
232 zoomXform = skif::LayerSpace<SkMatrix>::RectToRect(visibleLensBounds, srcRect);
233 } // Else not enough of the target is available to cover, so don't try adjusting
234 }
235
236 // When there is no SkSL support, or there's a 0 inset, the magnifier is equivalent to a
237 // rect->rect transform and crop.
239 skif::ParameterSpace<SkSize>({fInset, fInset}));
240 if (inset.width() <= 0.f || inset.height() <= 0.f)
241 {
242 // When applying the zoom as a direct transform, we only require the visibleSrcRect as
243 // input from the child filter, and transform it by the inverse of zoomXform (to go from
244 // src to lens bounds, since it was constructed to go from lens to src).
245 skif::LayerSpace<SkMatrix> invZoomXform;
246 SkAssertResult(zoomXform.invert(&invZoomXform));
247 skif::FilterResult childOutput =
248 this->getChildOutput(0, context.withNewDesiredOutput(srcRect.roundOut()));
249 return childOutput.applyTransform(context, invZoomXform, fSampling)
250 .applyCrop(context, lensBounds.roundOut());
251 }
252
253 using ShaderFlags = skif::FilterResult::ShaderFlags;
255 builder.add(this->getChildOutput(0, context.withNewDesiredOutput(visibleLensBounds.roundOut())),
256 {}, ShaderFlags::kNonTrivialSampling, fSampling);
257 return builder.eval([&](SkSpan<sk_sp<SkShader>> inputs) {
258 // If the input resolved to a null shader, the magnified output will be transparent too
259 return inputs[0] ? make_magnifier_shader(inputs[0], lensBounds, zoomXform, inset)
260 : nullptr;
261 }, lensBounds.roundOut());
262}
263
264skif::LayerSpace<SkIRect> SkMagnifierImageFilter::onGetInputLayerBounds(
265 const skif::Mapping& mapping,
266 const skif::LayerSpace<SkIRect>& desiredOutput,
267 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
268 // The required input is always the lens bounds. The filter distorts the pixels contained within
269 // these bounds to zoom in on a portion of it, depending on the inset and zoom amount. However,
270 // it adjusts the region based on cropping that occurs between what's requested and what's
271 // provided. Theoretically it's possible that we could restrict the required input by the
272 // desired output, but that cropping should not adjust the zoom region or inset. This is non
273 // trivial to separate and is an unlikely use case so for now just require fLensBounds.
274 skif::LayerSpace<SkIRect> requiredInput = mapping.paramToLayer(fLensBounds).roundOut();
275 // Our required input is the desired output for our child image filter.
276 return this->getChildInputLayerBounds(0, mapping, requiredInput, contentBounds);
277}
278
279std::optional<skif::LayerSpace<SkIRect>> SkMagnifierImageFilter::onGetOutputLayerBounds(
280 const skif::Mapping& mapping,
281 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
282 // The output of this filter is fLensBounds intersected with its child's output.
283 auto output = this->getChildOutputLayerBounds(0, mapping, contentBounds);
284 skif::LayerSpace<SkIRect> lensBounds = mapping.paramToLayer(fLensBounds).roundOut();
285 if (!output || lensBounds.intersect(*output)) {
286 return lensBounds;
287 } else {
288 // Nothing to magnify
290 }
291}
292
293SkRect SkMagnifierImageFilter::computeFastBounds(const SkRect& src) const {
294 SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
295 if (bounds.intersect(SkRect(fLensBounds))) {
296 return bounds;
297 } else {
298 return SkRect::MakeEmpty();
299 }
300}
#define SkAssertResult(cond)
Definition SkAssert.h:123
#define SkASSERT(cond)
Definition SkAssert.h:116
#define SK_FLATTENABLE_HOOKS(type)
#define SK_REGISTER_FLATTENABLE(type)
static bool SkIsFinite(T x, Pack... values)
#define SK_IMAGEFILTER_UNFLATTEN_COMMON(localVar, expectedCount)
void SkRegisterMagnifierImageFilterFlattenable()
static sk_sp< SkShader > make_magnifier_shader(sk_sp< SkShader > input, const skif::LayerSpace< SkRect > &lensBounds, const skif::LayerSpace< SkMatrix > &zoomXform, const skif::LayerSpace< SkSize > &inset)
static bool left(const SkPoint &p0, const SkPoint &p1)
sk_sp< T > sk_ref_sp(T *obj)
Definition SkRefCnt.h:381
virtual skif::LayerSpace< SkIRect > onGetInputLayerBounds(const skif::Mapping &mapping, const skif::LayerSpace< SkIRect > &desiredOutput, std::optional< skif::LayerSpace< SkIRect > > contentBounds) const =0
void flatten(SkWriteBuffer &) const override
virtual std::optional< skif::LayerSpace< SkIRect > > onGetOutputLayerBounds(const skif::Mapping &mapping, std::optional< skif::LayerSpace< SkIRect > > contentBounds) const =0
virtual skif::FilterResult onFilterImage(const skif::Context &context) const =0
virtual SkRect computeFastBounds(const SkRect &bounds) const
static sk_sp< SkImageFilter > Magnifier(const SkRect &lensBounds, SkScalar zoomAmount, SkScalar inset, const SkSamplingOptions &sampling, sk_sp< SkImageFilter > input, const CropRect &cropRect={})
static sk_sp< SkImageFilter > Crop(const SkRect &rect, SkTileMode tileMode, sk_sp< SkImageFilter > input)
const LayerSpace< SkIRect > & desiredOutput() const
const FilterResult & source() const
Context withNewDesiredOutput(const LayerSpace< SkIRect > &desiredOutput) const
const Mapping & mapping() const
FilterResult applyCrop(const Context &ctx, const LayerSpace< SkIRect > &crop, SkTileMode tileMode=SkTileMode::kDecal) const
LayerSpace< SkIRect > layerBounds() const
FilterResult applyTransform(const Context &ctx, const LayerSpace< SkMatrix > &transform, const SkSamplingOptions &sampling) const
LayerSpace< T > paramToLayer(const ParameterSpace< T > &paramGeometry) const
float SkScalar
Definition extension.cpp:12
static const uint8_t buffer[]
Optional< SkRect > bounds
Definition SkRecords.h:189
SkSamplingOptions sampling
Definition SkRecords.h:337
static SkRect inset(const SkRect &r)
static constexpr SkRect MakeEmpty()
Definition SkRect.h:595
bool isFinite() const
Definition SkRect.h:711
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition SkRect.h:659
constexpr float height() const
Definition SkRect.h:769
constexpr float width() const
Definition SkRect.h:762
bool isEmpty() const
Definition SkRect.h:693
Definition SkM44.h:19
Definition SkM44.h:98