Flutter Engine
The Flutter Engine
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,
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:
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,
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
111 SK_REGISTER_FLATTENABLE(SkMagnifierImageFilter);
112}
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
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}
SkAssertResult(font.textToGlyphs("Hello", 5, SkTextEncoding::kUTF8, glyphs, std::size(glyphs))==count)
#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
bool intersect(const LayerSpace< SkIRect > &r)
float rc(int row, int col) const
LayerSpace< SkIRect > roundOut() const
bool contains(const LayerSpace< SkRect > &r) const
LayerSpace< SkPoint > center() const
LayerSpace< SkPoint > clamp(LayerSpace< SkPoint > pt) const
bool intersect(const LayerSpace< SkRect > &r)
LayerSpace< T > paramToLayer(const ParameterSpace< T > &paramGeometry) const
float SkScalar
Definition: extension.cpp:12
static float min(float r, float g, float b)
Definition: hsl.cpp:48
const SkRuntimeEffect * GetKnownRuntimeEffect(StableKey stableKey)
Optional< SkRect > bounds
Definition: SkRecords.h:189
SkSamplingOptions sampling
Definition: SkRecords.h:337
SK_API sk_sp< SkShader > Empty()
Definition: common.py:1
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
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