Skip to content

Commit bb81b95

Browse files
authored
Allow Tile mode for blur filter and add new decal TileMode (flutter#22982)
Add a new TileMode.decal enum value and allow TileMode in ImagerFilter.blur() constructor
1 parent 2efc7c1 commit bb81b95

File tree

10 files changed

+129
-55
lines changed

10 files changed

+129
-55
lines changed

lib/ui/painting.dart

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3137,10 +3137,11 @@ class _ColorFilter extends NativeFieldWrapperClass2 {
31373137
/// this class as a child layer filter.
31383138
abstract class ImageFilter {
31393139
/// Creates an image filter that applies a Gaussian blur.
3140-
factory ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0 }) {
3140+
factory ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0, TileMode tileMode = TileMode.clamp }) {
31413141
assert(sigmaX != null); // ignore: unnecessary_null_comparison
31423142
assert(sigmaY != null); // ignore: unnecessary_null_comparison
3143-
return _GaussianBlurImageFilter(sigmaX: sigmaX, sigmaY: sigmaY);
3143+
assert(tileMode != null); // ignore: unnecessary_null_comparison
3144+
return _GaussianBlurImageFilter(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode);
31443145
}
31453146

31463147
/// Creates an image filter that applies a matrix transformation.
@@ -3206,29 +3207,40 @@ class _MatrixImageFilter implements ImageFilter {
32063207
}
32073208

32083209
class _GaussianBlurImageFilter implements ImageFilter {
3209-
_GaussianBlurImageFilter({ required this.sigmaX, required this.sigmaY });
3210+
_GaussianBlurImageFilter({ required this.sigmaX, required this.sigmaY, required this.tileMode });
32103211

32113212
final double sigmaX;
32123213
final double sigmaY;
3214+
final TileMode tileMode;
32133215

32143216
// MakeBlurFilter
32153217
late final _ImageFilter nativeFilter = _ImageFilter.blur(this);
32163218
@override
32173219
_ImageFilter _toNativeImageFilter() => nativeFilter;
32183220

3221+
String get _modeString {
3222+
switch(tileMode) {
3223+
case TileMode.clamp: return 'clamp';
3224+
case TileMode.mirror: return 'mirror';
3225+
case TileMode.repeated: return 'repeated';
3226+
case TileMode.decal: return 'decal';
3227+
}
3228+
}
3229+
32193230
@override
3220-
String get _shortDescription => 'blur($sigmaX, $sigmaY)';
3231+
String get _shortDescription => 'blur($sigmaX, $sigmaY, $_modeString)';
32213232

32223233
@override
3223-
String toString() => 'ImageFilter.blur($sigmaX, $sigmaY)';
3234+
String toString() => 'ImageFilter.blur($sigmaX, $sigmaY, $_modeString)';
32243235

32253236
@override
32263237
bool operator ==(Object other) {
32273238
if (other.runtimeType != runtimeType)
32283239
return false;
32293240
return other is _GaussianBlurImageFilter
32303241
&& other.sigmaX == sigmaX
3231-
&& other.sigmaY == sigmaY;
3242+
&& other.sigmaY == sigmaY
3243+
&& other.tileMode == tileMode;
32323244
}
32333245

32343246
@override
@@ -3278,9 +3290,9 @@ class _ImageFilter extends NativeFieldWrapperClass2 {
32783290
: assert(filter != null), // ignore: unnecessary_null_comparison
32793291
creator = filter { // ignore: prefer_initializing_formals
32803292
_constructor();
3281-
_initBlur(filter.sigmaX, filter.sigmaY);
3293+
_initBlur(filter.sigmaX, filter.sigmaY, filter.tileMode.index);
32823294
}
3283-
void _initBlur(double sigmaX, double sigmaY) native 'ImageFilter_initBlur';
3295+
void _initBlur(double sigmaX, double sigmaY, int tileMode) native 'ImageFilter_initBlur';
32843296

32853297
/// Creates an image filter that applies a matrix transformation.
32863298
///
@@ -3330,14 +3342,22 @@ class Shader extends NativeFieldWrapperClass2 {
33303342
Shader._();
33313343
}
33323344

3333-
/// Defines what happens at the edge of the gradient.
3345+
/// Defines what happens at the edge of a gradient or the sampling of a source image
3346+
/// in an [ImageFilter].
33343347
///
33353348
/// A gradient is defined along a finite inner area. In the case of a linear
33363349
/// gradient, it's between the parallel lines that are orthogonal to the line
33373350
/// drawn between two points. In the case of radial gradients, it's the disc
33383351
/// that covers the circle centered on a particular point up to a given radius.
33393352
///
3340-
/// This enum is used to define how the gradient should paint the regions
3353+
/// An image filter reads source samples from a source image and performs operations
3354+
/// on those samples to produce a result image. An image defines color samples only
3355+
/// for pixels within the bounds of the image but some filter operations, such as a blur
3356+
/// filter, read samples over a wide area to compute the output for a given pixel. Such
3357+
/// a filter would need to combine samples from inside the image with hypothetical
3358+
/// color values from outside the image.
3359+
///
3360+
/// This enum is used to define how the gradient or image filter should treat the regions
33413361
/// outside that defined inner area.
33423362
///
33433363
/// See also:
@@ -3349,36 +3369,60 @@ class Shader extends NativeFieldWrapperClass2 {
33493369
/// * [dart:ui.Gradient], the low-level class used when dealing with the
33503370
/// [Paint.shader] property directly, with its [Gradient.linear] and
33513371
/// [Gradient.radial] constructors.
3352-
// These enum values must be kept in sync with SkShader::TileMode.
3372+
/// * [dart:ui.ImageFilter.blur], an ImageFilter that may sometimes need to
3373+
/// read samples from outside an image to combine with the pixels near the
3374+
/// edge of the image.
3375+
// These enum values must be kept in sync with SkTileMode.
33533376
enum TileMode {
3354-
/// Edge is clamped to the final color.
3377+
/// Samples beyond the edge are clamped to the nearest color in the defined inner area.
3378+
///
3379+
/// A gradient will paint all the regions outside the inner area with the
3380+
/// color at the end of the color stop list closest to that region.
33553381
///
3356-
/// The gradient will paint the all the regions outside the inner area with
3357-
/// the color of the point closest to that region.
3382+
/// An image filter will substitute the nearest edge pixel for any samples taken from
3383+
/// outside its source image.
33583384
///
3385+
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_linear.png)
33593386
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_radial.png)
33603387
clamp,
33613388

3362-
/// Edge is repeated from first color to last.
3389+
/// Samples beyond the edge are repeated from the far end of the defined area.
33633390
///
3364-
/// This is as if the stop points from 0.0 to 1.0 were then repeated from 1.0
3365-
/// to 2.0, 2.0 to 3.0, and so forth (and for linear gradients, similarly from
3366-
/// -1.0 to 0.0, -2.0 to -1.0, etc).
3391+
/// For a gradient, this technique is as if the stop points from 0.0 to 1.0 were then
3392+
/// repeated from 1.0 to 2.0, 2.0 to 3.0, and so forth (and for linear gradients, similarly
3393+
/// from -1.0 to 0.0, -2.0 to -1.0, etc).
3394+
///
3395+
/// An image filter will treat its source image as if it were tiled across the enlarged
3396+
/// sample space from which it reads, each tile in the same orientation as the base image.
33673397
///
33683398
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_linear.png)
33693399
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_repeated_radial.png)
33703400
repeated,
33713401

3372-
/// Edge is mirrored from last color to first.
3402+
/// Samples beyond the edge are mirrored back and forth across the defined area.
33733403
///
3374-
/// This is as if the stop points from 0.0 to 1.0 were then repeated backwards
3375-
/// from 2.0 to 1.0, then forwards from 2.0 to 3.0, then backwards again from
3376-
/// 4.0 to 3.0, and so forth (and for linear gradients, similarly from in the
3404+
/// For a gradient, this technique is as if the stop points from 0.0 to 1.0 were then
3405+
/// repeated backwards from 2.0 to 1.0, then forwards from 2.0 to 3.0, then backwards
3406+
/// again from 4.0 to 3.0, and so forth (and for linear gradients, similarly in the
33773407
/// negative direction).
33783408
///
3409+
/// An image filter will treat its source image as tiled in an alternating forwards and
3410+
/// backwards or upwards and downwards direction across the sample space from which
3411+
/// it is reading.
3412+
///
33793413
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_linear.png)
33803414
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_radial.png)
33813415
mirror,
3416+
3417+
/// Samples beyond the edge are treated as transparent black.
3418+
///
3419+
/// A gradient will render transparency over any region that is outside the circle of a
3420+
/// radial gradient or outside the parallel lines that define the inner area of a linear
3421+
/// gradient.
3422+
///
3423+
/// An image filter will substitute transparent black for any sample it must read from
3424+
/// outside its source image.
3425+
decal,
33823426
}
33833427

33843428
Int32List _encodeColorList(List<Color> colors) {

lib/ui/painting/image_filter.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ void ImageFilter::initPicture(Picture* picture) {
5555
filter_ = SkPictureImageFilter::Make(picture->picture());
5656
}
5757

58-
void ImageFilter::initBlur(double sigma_x, double sigma_y) {
59-
filter_ = SkBlurImageFilter::Make(sigma_x, sigma_y, nullptr, nullptr,
60-
SkBlurImageFilter::kClamp_TileMode);
58+
void ImageFilter::initBlur(double sigma_x,
59+
double sigma_y,
60+
SkTileMode tile_mode) {
61+
filter_ = SkImageFilters::Blur(sigma_x, sigma_y, tile_mode, nullptr, nullptr);
6162
}
6263

6364
void ImageFilter::initMatrix(const tonic::Float64List& matrix4,

lib/ui/painting/image_filter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class ImageFilter : public RefCountedDartWrappable<ImageFilter> {
2424

2525
void initImage(CanvasImage* image);
2626
void initPicture(Picture*);
27-
void initBlur(double sigma_x, double sigma_y);
27+
void initBlur(double sigma_x, double sigma_y, SkTileMode tile_mode);
2828
void initMatrix(const tonic::Float64List& matrix4, int filter_quality);
2929
void initColorFilter(ColorFilter* colorFilter);
3030
void initComposeFilter(ImageFilter* outer, ImageFilter* inner);

lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,7 @@ class SkTileModeEnum {
642642
external SkTileMode get Clamp;
643643
external SkTileMode get Repeat;
644644
external SkTileMode get Mirror;
645+
external SkTileMode get Decal;
645646
}
646647

647648
@JS()
@@ -653,6 +654,7 @@ final List<SkTileMode> _skTileModes = <SkTileMode>[
653654
canvasKit.TileMode.Clamp,
654655
canvasKit.TileMode.Repeat,
655656
canvasKit.TileMode.Mirror,
657+
canvasKit.TileMode.Decal,
656658
];
657659

658660
SkTileMode toSkTileMode(ui.TileMode mode) {

lib/web_ui/lib/src/engine/canvaskit/image_filter.dart

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ abstract class _CkManagedSkImageFilterConvertible<T extends Object> implements u
2020
///
2121
/// Currently only supports `blur`.
2222
abstract class CkImageFilter extends ManagedSkiaObject<SkImageFilter> implements _CkManagedSkImageFilterConvertible<SkImageFilter> {
23-
factory CkImageFilter.blur({ required double sigmaX, required double sigmaY }) = _CkBlurImageFilter;
23+
factory CkImageFilter.blur({ required double sigmaX, required double sigmaY, required ui.TileMode tileMode }) = _CkBlurImageFilter;
2424
factory CkImageFilter.color({ required CkColorFilter colorFilter }) = _CkColorFilterImageFilter;
2525

2626
CkImageFilter._();
@@ -66,17 +66,27 @@ class _CkColorFilterImageFilter extends CkImageFilter {
6666
}
6767

6868
class _CkBlurImageFilter extends CkImageFilter {
69-
_CkBlurImageFilter({ required this.sigmaX, required this.sigmaY }) : super._();
69+
_CkBlurImageFilter({ required this.sigmaX, required this.sigmaY, required this.tileMode }) : super._();
7070

7171
final double sigmaX;
7272
final double sigmaY;
73+
final ui.TileMode tileMode;
74+
75+
String get _modeString {
76+
switch (tileMode) {
77+
case ui.TileMode.clamp: return 'clamp';
78+
case ui.TileMode.mirror: return 'mirror';
79+
case ui.TileMode.repeated: return 'repeated';
80+
case ui.TileMode.decal: return 'decal';
81+
}
82+
}
7383

7484
@override
7585
SkImageFilter _initSkiaObject() {
7686
return canvasKit.ImageFilter.MakeBlur(
7787
sigmaX,
7888
sigmaY,
79-
canvasKit.TileMode.Clamp,
89+
toSkTileMode(tileMode),
8090
null,
8191
);
8292
}
@@ -87,15 +97,16 @@ class _CkBlurImageFilter extends CkImageFilter {
8797
return false;
8898
return other is _CkBlurImageFilter
8999
&& other.sigmaX == sigmaX
90-
&& other.sigmaY == sigmaY;
100+
&& other.sigmaY == sigmaY
101+
&& other.tileMode == tileMode;
91102
}
92103

93104
@override
94-
int get hashCode => ui.hashValues(sigmaX, sigmaY);
105+
int get hashCode => ui.hashValues(sigmaX, sigmaY, tileMode);
95106

96107
@override
97108
String toString() {
98-
return 'ImageFilter.blur($sigmaX, $sigmaY)';
109+
return 'ImageFilter.blur($sigmaX, $sigmaY, $_modeString)';
99110
}
100111
}
101112

lib/web_ui/lib/src/engine/html/shaders/shader.dart

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ class GradientLinear extends EngineGradient {
143143
@override
144144
Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
145145
ui.Rect? shaderBounds, double density) {
146-
if (tileMode == ui.TileMode.clamp) {
146+
if (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal) {
147147
return _createCanvasGradient(ctx, shaderBounds, density);
148148
} else {
149149
initWebGl();
@@ -172,7 +172,7 @@ class GradientLinear extends EngineGradient {
172172
from.dx - offsetX, from.dy - offsetY, to.dx - offsetX,
173173
to.dy - offsetY);
174174
}
175-
_addColorStopsToCanvasGradient(gradient, colors, colorStops);
175+
_addColorStopsToCanvasGradient(gradient, colors, colorStops, tileMode == ui.TileMode.decal);
176176
return gradient;
177177
}
178178

@@ -281,16 +281,28 @@ class GradientLinear extends EngineGradient {
281281
}
282282

283283
void _addColorStopsToCanvasGradient(html.CanvasGradient gradient,
284-
List<ui.Color> colors, List<double>? colorStops) {
284+
List<ui.Color> colors, List<double>? colorStops, bool isDecal) {
285+
double scale, offset;
286+
if (isDecal) {
287+
scale = 0.999;
288+
offset = (1.0 - scale) / 2.0;
289+
gradient.addColorStop(0, '#00000000');
290+
} else {
291+
scale = 1.0;
292+
offset = 0.0;
293+
}
285294
if (colorStops == null) {
286295
assert(colors.length == 2);
287-
gradient.addColorStop(0, colorToCssString(colors[0])!);
288-
gradient.addColorStop(1, colorToCssString(colors[1])!);
296+
gradient.addColorStop(offset, colorToCssString(colors[0])!);
297+
gradient.addColorStop(1 - offset, colorToCssString(colors[1])!);
289298
} else {
290299
for (int i = 0; i < colors.length; i++) {
291-
gradient.addColorStop(colorStops[i], colorToCssString(colors[i])!);
300+
gradient.addColorStop(colorStops[i] * scale + offset, colorToCssString(colors[i])!);
292301
}
293302
}
303+
if (isDecal) {
304+
gradient.addColorStop(1, '#00000000');
305+
}
294306
}
295307

296308
/// Writes shader code to map fragment value to gradient color.
@@ -311,11 +323,12 @@ String _writeSharedGradientShader(ShaderBuilder builder,
311323
builder.addUniform(ShaderType.kVec4, name: 'scale_$i');
312324
}
313325

314-
// Use st variable name if clamped, otherwise write code to comnpute
326+
// Use st variable name if clamped or decaled, otherwise write code to compute
315327
// tiled_st.
316328
String probeName = 'st';
317329
switch (tileMode) {
318330
case ui.TileMode.clamp:
331+
case ui.TileMode.decal:
319332
break;
320333
case ui.TileMode.repeated:
321334
method.addStatement('float tiled_st = fract(st);');
@@ -349,7 +362,7 @@ class GradientRadial extends EngineGradient {
349362
@override
350363
Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
351364
ui.Rect? shaderBounds, double density) {
352-
if (tileMode == ui.TileMode.clamp) {
365+
if (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal) {
353366
return _createCanvasGradient(ctx, shaderBounds, density);
354367
} else {
355368
initWebGl();
@@ -364,7 +377,7 @@ class GradientRadial extends EngineGradient {
364377
final html.CanvasGradient gradient = ctx!.createRadialGradient(
365378
center.dx - offsetX, center.dy - offsetY, 0,
366379
center.dx - offsetX, center.dy - offsetY, radius);
367-
_addColorStopsToCanvasGradient(gradient, colors, colorStops);
380+
_addColorStopsToCanvasGradient(gradient, colors, colorStops, tileMode == ui.TileMode.decal);
368381
return gradient;
369382
}
370383

lib/web_ui/lib/src/ui/painting.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,9 +394,9 @@ enum FilterQuality {
394394
}
395395

396396
class ImageFilter {
397-
factory ImageFilter.blur({double sigmaX = 0.0, double sigmaY = 0.0}) {
397+
factory ImageFilter.blur({double sigmaX = 0.0, double sigmaY = 0.0, TileMode tileMode = TileMode.clamp}) {
398398
if (engine.useCanvasKit) {
399-
return engine.CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY);
399+
return engine.CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode);
400400
}
401401
return engine.EngineImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY);
402402
}

lib/web_ui/lib/src/ui/tile_mode.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ enum TileMode {
1010
clamp,
1111
repeated,
1212
mirror,
13+
decal,
1314
}

0 commit comments

Comments
 (0)