Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkMatrixConvolutionImageFilter.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
17#include "include/core/SkM44.h"
19#include "include/core/SkRect.h"
24#include "include/core/SkSize.h"
35#include "src/base/SkMathPriv.h"
36#include "src/base/SkSafeMath.h"
39#include "src/core/SkLRUCache.h"
42#include "src/core/SkRectPriv.h"
45
46#include <cstdint>
47#include <cstring>
48#include <optional>
49#include <utility>
50
51using namespace skia_private;
52
53namespace {
54
55// The matrix convolution image filter applies the convolution naively, it does not use any DFT to
56// convert the input images into the frequency domain. As such, kernels can quickly become too
57// slow to run in a reasonable amount of time (and anyone using a giant kernel should not be
58// relying on Skia to perform the calculations). 256 as a limit on the kernel size is somewhat
59// arbitrary but should, hopefully, not cause existing clients/websites to fail when historically
60// there was no upper limit.
61// Note: SkSL balks (w/ a "program is too large" error) whenever the number of kernel values
62// is >= 2048 (e.g., 8x256, 16x128, ...) so that should be a pretty good upper limit for what
63// is being seen in the wild.
64static constexpr int kMaxKernelSize = 256;
65// The uniform-based kernel shader can store 28 values in any order layout (28x1, 1x25, 5x5, and
66// smaller orders like 3x3 or 5x4, etc.), but must be a multiple of 4 for better packing in std140.
67static constexpr int kMaxUniformKernelSize = 28;
68
69SkBitmap create_kernel_bitmap(const SkISize& kernelSize, const float* kernel,
70 float* innerGain, float* innerBias);
71
72class SkMatrixConvolutionImageFilter final : public SkImageFilter_Base {
73public:
74 SkMatrixConvolutionImageFilter(const SkISize& kernelSize, const SkScalar* kernel,
75 SkScalar gain, SkScalar bias, const SkIPoint& kernelOffset,
76 bool convolveAlpha, sk_sp<SkImageFilter> const* input)
77 : SkImageFilter_Base(input, 1)
78 , fKernel(kernel, kernelSize.width() * kernelSize.height())
79 , fKernelSize(kernelSize)
80 , fKernelOffset({kernelOffset.fX, kernelOffset.fY})
81 , fGain(gain)
82 , fBias(bias)
83 , fConvolveAlpha(convolveAlpha) {
84 // The public factory should have ensured these before creating this object.
85 SkASSERT(SkSafeMath::Mul(kernelSize.fWidth, kernelSize.fHeight) <= kMaxKernelSize);
86 SkASSERT(kernelSize.fWidth >= 1 && kernelSize.fHeight >= 1);
87 SkASSERT(kernelOffset.fX >= 0 && kernelOffset.fX < kernelSize.fWidth);
88 SkASSERT(kernelOffset.fY >= 0 && kernelOffset.fY < kernelSize.fHeight);
89
90 // Does nothing for small kernels, otherwise encodes kernel into an A8 image.
91 fKernelBitmap = create_kernel_bitmap(kernelSize, kernel, &fInnerGain, &fInnerBias);
92 }
93
94 SkRect computeFastBounds(const SkRect& bounds) const override;
95
96protected:
97 void flatten(SkWriteBuffer&) const override;
98
99private:
100 friend void ::SkRegisterMatrixConvolutionImageFilterFlattenable();
101 SK_FLATTENABLE_HOOKS(SkMatrixConvolutionImageFilter)
102
103 bool onAffectsTransparentBlack() const override {
104 // affectsTransparentBlack() is conflated with "canComputeFastBounds" and MatrixConvolution
105 // is unique in that it might not produce unbounded output, but we can't calculate the
106 // fast bounds because the kernel is applied in device space and no transform is provided
107 // with that API.
108 // TODO(skbug.com/14617): Accept a matrix in computeFastBounds() so that we can handle the
109 // layer-space kernel case.
110
111 // That issue aside, a matrix convolution can affect transparent black when it has a
112 // non-zero bias and convolves alpha (if it doesn't convolve the alpha channel then the bias
113 // applied to RGB doesn't matter for transparent black pixels).
114 // NOTE: The crop image filters that wrap the matrix convolution to apply tile modes will
115 // reset this property when possible.
116 return true;
117 }
118
119 skif::FilterResult onFilterImage(const skif::Context& context) const override;
120
122 const skif::Mapping& mapping,
123 const skif::LayerSpace<SkIRect>& desiredOutput,
124 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
125
126 std::optional<skif::LayerSpace<SkIRect>> onGetOutputLayerBounds(
127 const skif::Mapping& mapping,
128 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
129
130 // Helper functions to adjust 'bounds' by the kernel size and offset, either for what would be
131 // sampled when covering 'bounds', or what could produce values when applied to 'bounds'.
132 skif::LayerSpace<SkIRect> boundsSampledByKernel(const skif::LayerSpace<SkIRect>& bounds) const;
133 skif::LayerSpace<SkIRect> boundsAffectedByKernel(const skif::LayerSpace<SkIRect>& bounds) const;
134
135 sk_sp<SkShader> createShader(const skif::Context& ctx, sk_sp<SkShader> input) const;
136
137 // Original kernel data, preserved for serialization even if it was encoded into fKernelBitmap
138 TArray<float> fKernel;
139
140 // Unlike the majority of image filters, the kernel is applied as-is to the layer-space pixels.
141 // This means that the kernel size and offset are always in the layer coordinate system.
142 skif::LayerSpace<SkISize> fKernelSize;
144
145 float fGain;
146 float fBias; // NOTE: This is assumed to be in [0-255] for historical reasons
147 bool fConvolveAlpha;
148
149 // Derived from fKernel when larger than what we will upload as uniforms; fInnerBias and
150 // fInnerGain reconstruct the original coefficient from unorm8 data as (a+innerBias)*innerGain
151 // Since these are derived, they are not serialized.
152 SkBitmap fKernelBitmap;
153 float fInnerBias;
154 float fInnerGain;
155};
156
157// LayerSpace doesn't have a clean type to represent 4 separate edge deltas, but the result
158// is a valid layer-space rectangle, so just go back to the underlying SkIRect temporarily.
160 int dl, int dt, int dr, int db) {
161 SkIRect adjusted = SkIRect(rect);
162 adjusted.adjust(dl, dt, dr, db);
163 return skif::LayerSpace<SkIRect>(adjusted);
164}
165
166SkBitmap create_kernel_bitmap(const SkISize& kernelSize, const float* kernel,
167 float* innerGain, float* innerBias) {
168 int length = kernelSize.fWidth * kernelSize.fHeight;
169 if (length <= kMaxUniformKernelSize) {
170 // No bitmap is needed to store the kernel on the GPU
171 *innerGain = 1.f;
172 *innerBias = 0.f;
173 return {};
174 }
175
176 // The convolution kernel is "big". The SVG spec has no upper limit on what's supported so
177 // store the kernel in a SkBitmap that will be uploaded to a data texture. We could
178 // implement a more straight forward evaluation loop for the CPU backend, but kernels of
179 // this size are already going to be very slow so we accept the extra indirection to
180 // keep the code paths consolidated.
181 //
182 // We store the data in A8 for universal support, but this requires normalizing the values
183 // and adding an extra inner bias operation to the shader. We could store values in A16 or
184 // A32 for improved accuracy but that would require querying GPU capabilities, which
185 // prevents creating the bitmap once during initialization. Even on the GPU, kernels larger
186 // than 5x5 quickly exceed realtime capabilities, so the loss of precision isn't a great
187 // concern either.
188 float min = kernel[0];
189 float max = kernel[0];
190 for (int i = 1; i < length; ++i) {
191 if (kernel[i] < min) {
192 min = kernel[i];
193 }
194 if (kernel[i] > max) {
195 max = kernel[i];
196 }
197 }
198
199 *innerGain = max - min;
200 *innerBias = min;
201 // Treat a near-0 gain (i.e. box blur) as 1 and let innerBias move everything to final value.
202 if (SkScalarNearlyZero(*innerGain)) {
203 *innerGain = 1.f;
204 }
205
206 SkBitmap kernelBM;
207 if (!kernelBM.tryAllocPixels(SkImageInfo::Make(kernelSize,
210 // OOM so return an empty bitmap, which will be detected later on in onFilterImage().
211 return {};
212 }
213
214 for (int y = 0; y < kernelSize.fHeight; ++y) {
215 for (int x = 0; x < kernelSize.fWidth; ++x) {
216 int i = y * kernelSize.fWidth + x;
217 *kernelBM.getAddr8(x, y) = SkScalarRoundToInt(255 * (kernel[i] - min) / *innerGain);
218 }
219 }
220
221 kernelBM.setImmutable();
222 return kernelBM;
223}
224
225} // end namespace
226
228 const SkScalar kernel[],
229 SkScalar gain,
230 SkScalar bias,
231 const SkIPoint& kernelOffset,
232 SkTileMode tileMode,
233 bool convolveAlpha,
235 const CropRect& cropRect) {
236 if (kernelSize.width() < 1 || kernelSize.height() < 1) {
237 return nullptr;
238 }
239 if (SkSafeMath::Mul(kernelSize.width(), kernelSize.height()) > kMaxKernelSize) {
240 return nullptr;
241 }
242 if (!kernel) {
243 return nullptr;
244 }
245 if ((kernelOffset.fX < 0) || (kernelOffset.fX >= kernelSize.fWidth) ||
246 (kernelOffset.fY < 0) || (kernelOffset.fY >= kernelSize.fHeight)) {
247 return nullptr;
248 }
249
250 // The 'tileMode' behavior is not well-defined if there is no crop, so we only apply it if
251 // there is a provided 'cropRect'.
252 sk_sp<SkImageFilter> filter = std::move(input);
253 if (cropRect && tileMode != SkTileMode::kDecal) {
254 // Historically the input image was restricted to the cropRect when tiling was not kDecal
255 // so that the kernel evaluated the tiled edge conditions, while a kDecal crop only affected
256 // the output.
257 filter = SkImageFilters::Crop(*cropRect, tileMode, std::move(filter));
258 }
259 filter = sk_sp<SkImageFilter>(new SkMatrixConvolutionImageFilter(
260 kernelSize, kernel, gain, bias, kernelOffset, convolveAlpha, &filter));
261 if (cropRect) {
262 // But regardless of the tileMode, the output is decal cropped.
263 filter = SkImageFilters::Crop(*cropRect, SkTileMode::kDecal, std::move(filter));
264 }
265 return filter;
266}
267
269 SK_REGISTER_FLATTENABLE(SkMatrixConvolutionImageFilter);
270 // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name
271 SkFlattenable::Register("SkMatrixConvolutionImageFilterImpl",
272 SkMatrixConvolutionImageFilter::CreateProc);
273}
274
275sk_sp<SkFlattenable> SkMatrixConvolutionImageFilter::CreateProc(SkReadBuffer& buffer) {
277
278 SkISize kernelSize;
279 kernelSize.fWidth = buffer.readInt();
280 kernelSize.fHeight = buffer.readInt();
281 const int count = buffer.getArrayCount();
282
283 const int64_t kernelArea = sk_64_mul(kernelSize.width(), kernelSize.height());
284 if (!buffer.validate(kernelArea == count)) {
285 return nullptr;
286 }
287 if (!buffer.validateCanReadN<SkScalar>(count)) {
288 return nullptr;
289 }
291 if (!buffer.readScalarArray(kernel.get(), count)) {
292 return nullptr;
293 }
294 SkScalar gain = buffer.readScalar();
295 SkScalar bias = buffer.readScalar();
296 SkIPoint kernelOffset;
297 kernelOffset.fX = buffer.readInt();
298 kernelOffset.fY = buffer.readInt();
299
302 tileMode = buffer.read32LE(SkTileMode::kLastTileMode);
303 } // else SkCropImageFilter handles the tile mode (if any)
304
305 bool convolveAlpha = buffer.readBool();
306
307 if (!buffer.isValid()) {
308 return nullptr;
309 }
310 // NOTE: For SKPs with version >= kConvolutionImageFilterTilingUpdate, tileMode will be kDecal
311 // and common.cropRect() will be null (so the factory also ignores tileMode). Any
312 // cropping/tiling will have been handled by the deserialized input/output Crop image filters.
314 kernelSize, kernel.get(), gain, bias, kernelOffset, tileMode,
315 convolveAlpha, common.getInput(0), common.cropRect());
316}
317
318void SkMatrixConvolutionImageFilter::flatten(SkWriteBuffer& buffer) const {
319 this->SkImageFilter_Base::flatten(buffer);
320 buffer.writeInt(fKernelSize.width());
321 buffer.writeInt(fKernelSize.height());
322 buffer.writeScalarArray(fKernel.data(), fKernel.size());
323 buffer.writeScalar(fGain);
324 buffer.writeScalar(fBias);
325 buffer.writeInt(fKernelOffset.x());
326 buffer.writeInt(fKernelOffset.y());
327 buffer.writeBool(fConvolveAlpha);
328}
329
330///////////////////////////////////////////////////////////////////////////////////////////////////
331
332skif::LayerSpace<SkIRect> SkMatrixConvolutionImageFilter::boundsSampledByKernel(
333 const skif::LayerSpace<SkIRect>& bounds) const {
334 return adjust(bounds,
335 -fKernelOffset.x(),
336 -fKernelOffset.y(),
337 fKernelSize.width() - fKernelOffset.x() - 1,
338 fKernelSize.height() - fKernelOffset.y() - 1);
339}
340
341skif::LayerSpace<SkIRect> SkMatrixConvolutionImageFilter::boundsAffectedByKernel(
342 const skif::LayerSpace<SkIRect>& bounds) const {
343 return adjust(bounds,
344 fKernelOffset.x() - fKernelSize.width() + 1,
345 fKernelOffset.y() - fKernelSize.height() + 1,
346 fKernelOffset.x(),
347 fKernelOffset.y());
348}
349
350// There are two shader variants: a small kernel version that stores the matrix in uniforms
351// and iterates in 1D (selected when texWidth==0 & texHeight==0); and a large kernel version
352// that stores the matrix in a texture. The 2D texture kernel shader still uses constant-length
353// for loops (up to the texWidth and texHeight passed in); the actual kernel size is uploaded
354// as a uniform, allowing the shaders to be quantized.
355static sk_sp<SkRuntimeEffect> get_runtime_effect(int texWidth, int texHeight) {
356 // While the loop structure and uniforms are different, pieces of the algorithm are common and
357 // defined statically for re-use in the two shaders:
358 static const char* kHeaderSkSL =
359 "uniform int2 size;"
360 "uniform int2 offset;"
361 "uniform half2 gainAndBias;"
362 "uniform int convolveAlpha;" // FIXME not a full int?
363
364 "uniform shader child;"
365
366 "half4 main(float2 coord) {"
367 "half4 sum = half4(0);"
368 "half origAlpha = 0;";
369
370 // Used in the inner loop to accumulate convolution sum
371 static const char* kAccumulateSkSL =
372 "half4 c = child.eval(coord + half2(kernelPos) - half2(offset));"
373 "if (convolveAlpha == 0) {"
374 // When not convolving alpha, remember the original alpha for actual sample
375 // coord, and perform accumulation on unpremul colors.
376 "if (kernelPos == offset) {"
377 "origAlpha = c.a;"
378 "}"
379 "c = unpremul(c);"
380 "}"
381 "sum += c*k;";
382
383 // Used after the loop to calculate final color
384 static const char* kFooterSkSL =
385 "half4 color = sum*gainAndBias.x + gainAndBias.y;"
386 "if (convolveAlpha == 0) {"
387 // Reset the alpha to the original and convert to premul RGB
388 "color = half4(color.rgb*origAlpha, origAlpha);"
389 "} else {"
390 // Ensure convolved alpha is within [0, 1]
391 "color.a = saturate(color.a);"
392 "}"
393 // Make RGB valid premul w/ respect to the alpha (either original or convolved)
394 "color.rgb = clamp(color.rgb, 0, color.a);"
395 "return color;"
396 "}";
397
398 // The uniform array storing the kernel is packed into half4's so that we don't waste space
399 // forcing array elements out to 16-byte alignment when using std140.
400 static_assert(kMaxUniformKernelSize % 4 == 0, "Must be a multiple of 4");
402 SkStringPrintf("const int kMaxUniformKernelSize = %d / 4;"
403 "uniform half4 kernel[kMaxUniformKernelSize];"
404 "%s" // kHeaderSkSL
405 "int2 kernelPos = int2(0);"
406 "for (int i = 0; i < kMaxUniformKernelSize; ++i) {"
407 "if (kernelPos.y >= size.y) { break; }"
408
409 "half4 k4 = kernel[i];"
410 "for (int j = 0; j < 4; ++j) {"
411 "if (kernelPos.y >= size.y) { break; }"
412 "half k = k4[j];"
413 "%s" // kAccumulateSkSL
414
415 // The 1D index has to be "constant", so reconstruct 2D coords
416 // instead of a more conventional double for-loop and i=y*w+x
417 "kernelPos.x += 1;"
418 "if (kernelPos.x >= size.x) {"
419 "kernelPos.x = 0;"
420 "kernelPos.y += 1;"
421 "}"
422 "}"
423 "}"
424 "%s", // kFooterSkSL
425 kMaxUniformKernelSize, kHeaderSkSL, kAccumulateSkSL, kFooterSkSL).c_str());
426
427 // The texture-backed kernel creates shaders with quantized upper bounds on the kernel size and
428 // then stored in a thread-safe LRU cache.
429 static SkMutex cacheLock;
431 textureShaderCache SK_GUARDED_BY(cacheLock) {/*maxCount=*/5};
432 static const auto makeTextureEffect = [](SkISize maxKernelSize) {
434 SkStringPrintf("const int kMaxKernelWidth = %d;"
435 "const int kMaxKernelHeight = %d;"
436 "uniform shader kernel;"
437 "uniform half2 innerGainAndBias;"
438 "%s" // kHeaderSkSL
439 "for (int y = 0; y < kMaxKernelHeight; ++y) {"
440 "if (y >= size.y) { break; }"
441 "for (int x = 0; x < kMaxKernelWidth; ++x) {"
442 "if (x >= size.x) { break; }"
443
444 "int2 kernelPos = int2(x,y);"
445 "half k = kernel.eval(half2(kernelPos) + 0.5).a;"
446 "k = k * innerGainAndBias.x + innerGainAndBias.y;"
447 "%s" // kAccumulateSkSL
448 "}"
449 "}"
450 "%s", // kFooterSkSL
451 maxKernelSize.fWidth, maxKernelSize.fHeight,
452 kHeaderSkSL, kAccumulateSkSL, kFooterSkSL).c_str());
453 };
454
455
456 if (texWidth == 0 && texHeight == 0) {
457 return sk_ref_sp(uniformEffect);
458 } else {
459 static_assert(kMaxKernelSize % 4 == 0, "Must be a multiple of 4");
460 SkASSERT(SkSafeMath::Mul(texWidth, texHeight) <= kMaxKernelSize);
461 const SkISize key = {SkNextPow2(texWidth), SkNextPow2(texHeight)};
462
463 SkAutoMutexExclusive acquire{cacheLock};
464 sk_sp<SkRuntimeEffect>* effect = textureShaderCache.find(key);
465 if (!effect) {
466 // Adopt the raw pointer returned by makeTextureEffect so that it will be deleted if
467 // it's removed from the LRU cache.
468 sk_sp<SkRuntimeEffect> newEffect{makeTextureEffect(key)};
469 effect = textureShaderCache.insert(key, std::move(newEffect));
470 }
471
472 return *effect;
473 }
474}
475
476sk_sp<SkShader> SkMatrixConvolutionImageFilter::createShader(const skif::Context& ctx,
477 sk_sp<SkShader> input) const {
478 const int kernelLength = fKernelSize.width() * fKernelSize.height();
479 const bool useTextureShader = kernelLength > kMaxUniformKernelSize;
480 if (useTextureShader && fKernelBitmap.empty()) {
481 return nullptr; // No actual kernel data to work with from a prior OOM
482 }
483
484 auto effect = get_runtime_effect(useTextureShader ? fKernelSize.width() : 0,
485 useTextureShader ? fKernelSize.height() : 0);
486 SkRuntimeShaderBuilder builder(std::move(effect));
487 builder.child("child") = std::move(input);
488
489 if (useTextureShader) {
490 sk_sp<SkImage> cachedKernel = ctx.backend()->getCachedBitmap(fKernelBitmap);
491 if (!cachedKernel) {
492 return nullptr;
493 }
494 builder.child("kernel") = cachedKernel->makeRawShader(SkFilterMode::kNearest);
495 builder.uniform("innerGainAndBias") = SkV2{fInnerGain, fInnerBias};
496 } else {
497 float paddedKernel[kMaxUniformKernelSize];
498 memcpy(paddedKernel, fKernel.data(), kernelLength*sizeof(float));
499 memset(paddedKernel+kernelLength, 0, (kMaxUniformKernelSize - kernelLength)*sizeof(float));
500
501 builder.uniform("kernel").set(paddedKernel, kMaxUniformKernelSize);
502 }
503
504 builder.uniform("size") = SkISize(fKernelSize);
505 builder.uniform("offset") = skif::IVector(fKernelOffset);
506 // Scale the user-provided bias by 1/255 to match the [0,1] color channel range
507 builder.uniform("gainAndBias") = SkV2{fGain, fBias / 255.f};
508 builder.uniform("convolveAlpha") = fConvolveAlpha ? 1 : 0;
509
510 return builder.makeShader();
511}
512
513skif::FilterResult SkMatrixConvolutionImageFilter::onFilterImage(
514 const skif::Context& context) const {
515 using ShaderFlags = skif::FilterResult::ShaderFlags;
516
517 skif::LayerSpace<SkIRect> requiredInput = this->boundsSampledByKernel(context.desiredOutput());
518 skif::FilterResult childOutput =
519 this->getChildOutput(0, context.withNewDesiredOutput(requiredInput));
520
521 skif::LayerSpace<SkIRect> outputBounds;
522 if (fConvolveAlpha && fBias != 0.f) {
523 // The convolution will produce a non-trivial value for every pixel so fill desired output.
524 outputBounds = context.desiredOutput();
525 } else {
526 // Calculate the possible extent of the convolution given what was actually produced by the
527 // child filter and then intersect that with the desired output.
528 outputBounds = this->boundsAffectedByKernel(childOutput.layerBounds());
529 if (!outputBounds.intersect(context.desiredOutput())) {
530 return {};
531 }
532 }
533
535 builder.add(childOutput,
536 this->boundsSampledByKernel(outputBounds),
537 ShaderFlags::kSampledRepeatedly);
538 return builder.eval([&](SkSpan<sk_sp<SkShader>> inputs) {
539 return this->createShader(context, inputs[0]);
540 }, outputBounds);
541}
542
543skif::LayerSpace<SkIRect> SkMatrixConvolutionImageFilter::onGetInputLayerBounds(
544 const skif::Mapping& mapping,
545 const skif::LayerSpace<SkIRect>& desiredOutput,
546 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
547 // Adjust the desired output bounds by the kernel size to avoid evaluating edge conditions, and
548 // then recurse to the child filter.
549 skif::LayerSpace<SkIRect> requiredInput = this->boundsSampledByKernel(desiredOutput);
550 return this->getChildInputLayerBounds(0, mapping, requiredInput, contentBounds);
551}
552
553std::optional<skif::LayerSpace<SkIRect>> SkMatrixConvolutionImageFilter::onGetOutputLayerBounds(
554 const skif::Mapping& mapping,
555 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
556 if (fConvolveAlpha && fBias != 0.f) {
557 // Applying the kernel as a convolution to fully transparent black will result in 0 for
558 // each channel, unless the bias itself shifts this "zero-point". However, when the alpha
559 // channel is not convolved, the original a=0 is preserved and producing a premul color
560 // discards the non-zero bias. Convolving the alpha channel and a non-zero bias can mean
561 // the transparent black pixels outside of any input image become non-transparent black.
563 }
564
565 // Otherwise apply the kernel to the output bounds of the child filter.
566 auto outputBounds = this->getChildOutputLayerBounds(0, mapping, contentBounds);
567 if (outputBounds) {
568 return this->boundsAffectedByKernel(*outputBounds);
569 } else {
571 }
572}
573
574SkRect SkMatrixConvolutionImageFilter::computeFastBounds(const SkRect& bounds) const {
575 // See onAffectsTransparentBlack(), but without knowing the local-to-device transform, we don't
576 // know how many pixels will be sampled by the kernel. Return unbounded to match the
577 // expectations of an image filter that "affects" transparent black.
579}
int count
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition SkAlphaType.h:29
#define SkASSERT(cond)
Definition SkAssert.h:116
@ kAlpha_8_SkColorType
pixel with alpha in 8-bit byte
Definition SkColorType.h:21
#define SK_FLATTENABLE_HOOKS(type)
#define SK_REGISTER_FLATTENABLE(type)
#define SK_IMAGEFILTER_UNFLATTEN_COMMON(localVar, expectedCount)
static int SkNextPow2(int value)
Definition SkMathPriv.h:272
static int64_t sk_64_mul(int64_t a, int64_t b)
Definition SkMath.h:33
void SkRegisterMatrixConvolutionImageFilterFlattenable()
static sk_sp< SkRuntimeEffect > get_runtime_effect(int texWidth, int texHeight)
sk_sp< T > sk_ref_sp(T *obj)
Definition SkRefCnt.h:381
SkRuntimeEffect * SkMakeRuntimeEffect(SkRuntimeEffect::Result(*make)(SkString, const SkRuntimeEffect::Options &), const char *sksl, SkRuntimeEffect::Options options=SkRuntimeEffect::Options{})
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:101
#define SkScalarRoundToInt(x)
Definition SkScalar.h:37
SK_API SkString static SkString SkStringPrintf()
Definition SkString.h:287
#define SK_GUARDED_BY(x)
SkTileMode
Definition SkTileMode.h:13
void setImmutable()
Definition SkBitmap.cpp:400
uint8_t * getAddr8(int x, int y) const
Definition SkBitmap.h:1270
bool tryAllocPixels(const SkImageInfo &info, size_t rowBytes)
Definition SkBitmap.cpp:271
static void Register(const char name[], Factory)
virtual skif::LayerSpace< SkIRect > onGetInputLayerBounds(const skif::Mapping &mapping, const skif::LayerSpace< SkIRect > &desiredOutput, std::optional< skif::LayerSpace< SkIRect > > contentBounds) const =0
virtual bool onAffectsTransparentBlack() const
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 > MatrixConvolution(const SkISize &kernelSize, const SkScalar kernel[], SkScalar gain, SkScalar bias, const SkIPoint &kernelOffset, SkTileMode tileMode, bool convolveAlpha, sk_sp< SkImageFilter > input, const CropRect &cropRect={})
static sk_sp< SkImageFilter > Crop(const SkRect &rect, SkTileMode tileMode, sk_sp< SkImageFilter > input)
@ kConvolutionImageFilterTilingUpdate
static SkRect MakeLargeS32()
Definition SkRectPriv.h:33
static Result MakeForShader(SkString sksl, const Options &)
static size_t Mul(size_t x, size_t y)
const char * c_str() const
Definition SkString.h:133
virtual sk_sp< SkImage > getCachedBitmap(const SkBitmap &data) const =0
const Backend * backend() const
const LayerSpace< SkIRect > & desiredOutput() const
Context withNewDesiredOutput(const LayerSpace< SkIRect > &desiredOutput) const
LayerSpace< SkIRect > layerBounds() const
float SkScalar
Definition extension.cpp:12
static const uint8_t buffer[]
static float max(float r, float g, float b)
Definition hsl.cpp:49
static float min(float r, float g, float b)
Definition hsl.cpp:48
size_t length
double y
double x
int32_t height
int32_t width
int32_t fX
x-axis value
int32_t fY
y-axis value
void adjust(int32_t dL, int32_t dT, int32_t dR, int32_t dB)
Definition SkRect.h:446
int32_t fHeight
Definition SkSize.h:18
int32_t fWidth
Definition SkSize.h:17
constexpr int32_t width() const
Definition SkSize.h:36
constexpr int32_t height() const
Definition SkSize.h:37
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)
Definition SkM44.h:19