Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 66 additions & 22 deletions lib/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3137,10 +3137,11 @@ class _ColorFilter extends NativeFieldWrapperClass2 {
/// this class as a child layer filter.
abstract class ImageFilter {
/// Creates an image filter that applies a Gaussian blur.
factory ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0 }) {
factory ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0, TileMode tileMode = TileMode.clamp }) {
assert(sigmaX != null); // ignore: unnecessary_null_comparison
assert(sigmaY != null); // ignore: unnecessary_null_comparison
return _GaussianBlurImageFilter(sigmaX: sigmaX, sigmaY: sigmaY);
assert(tileMode != null); // ignore: unnecessary_null_comparison
return _GaussianBlurImageFilter(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode);
}

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

class _GaussianBlurImageFilter implements ImageFilter {
_GaussianBlurImageFilter({ required this.sigmaX, required this.sigmaY });
_GaussianBlurImageFilter({ required this.sigmaX, required this.sigmaY, required this.tileMode });

final double sigmaX;
final double sigmaY;
final TileMode tileMode;

// MakeBlurFilter
late final _ImageFilter nativeFilter = _ImageFilter.blur(this);
@override
_ImageFilter _toNativeImageFilter() => nativeFilter;

String get _modeString {
switch(tileMode) {
case TileMode.clamp: return 'clamp';
case TileMode.mirror: return 'mirror';
case TileMode.repeated: return 'repeated';
case TileMode.decal: return 'decal';
}
}

@override
String get _shortDescription => 'blur($sigmaX, $sigmaY)';
String get _shortDescription => 'blur($sigmaX, $sigmaY, $_modeString)';

@override
String toString() => 'ImageFilter.blur($sigmaX, $sigmaY)';
String toString() => 'ImageFilter.blur($sigmaX, $sigmaY, $_modeString)';

@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is _GaussianBlurImageFilter
&& other.sigmaX == sigmaX
&& other.sigmaY == sigmaY;
&& other.sigmaY == sigmaY
&& other.tileMode == tileMode;
}

@override
Expand Down Expand Up @@ -3278,9 +3290,9 @@ class _ImageFilter extends NativeFieldWrapperClass2 {
: assert(filter != null), // ignore: unnecessary_null_comparison
creator = filter { // ignore: prefer_initializing_formals
_constructor();
_initBlur(filter.sigmaX, filter.sigmaY);
_initBlur(filter.sigmaX, filter.sigmaY, filter.tileMode.index);
}
void _initBlur(double sigmaX, double sigmaY) native 'ImageFilter_initBlur';
void _initBlur(double sigmaX, double sigmaY, int tileMode) native 'ImageFilter_initBlur';

/// Creates an image filter that applies a matrix transformation.
///
Expand Down Expand Up @@ -3330,14 +3342,22 @@ class Shader extends NativeFieldWrapperClass2 {
Shader._();
}

/// Defines what happens at the edge of the gradient.
/// Defines what happens at the edge of a gradient or the sampling of a source image
/// in an [ImageFilter].
///
/// A gradient is defined along a finite inner area. In the case of a linear
/// gradient, it's between the parallel lines that are orthogonal to the line
/// drawn between two points. In the case of radial gradients, it's the disc
/// that covers the circle centered on a particular point up to a given radius.
///
/// This enum is used to define how the gradient should paint the regions
/// An image filter reads source samples from a source image and performs operations
/// on those samples to produce a result image. An image defines color samples only
/// for pixels within the bounds of the image but some filter operations, such as a blur
/// filter, read samples over a wide area to compute the output for a given pixel. Such
/// a filter would need to combine samples from inside the image with hypothetical
/// color values from outside the image.
///
/// This enum is used to define how the gradient or image filter should treat the regions
/// outside that defined inner area.
///
/// See also:
Expand All @@ -3349,36 +3369,60 @@ class Shader extends NativeFieldWrapperClass2 {
/// * [dart:ui.Gradient], the low-level class used when dealing with the
/// [Paint.shader] property directly, with its [Gradient.linear] and
/// [Gradient.radial] constructors.
// These enum values must be kept in sync with SkShader::TileMode.
/// * [dart:ui.ImageFilter.blur], an ImageFilter that may sometimes need to
/// read samples from outside an image to combine with the pixels near the
/// edge of the image.
// These enum values must be kept in sync with SkTileMode.
enum TileMode {
/// Edge is clamped to the final color.
/// Samples beyond the edge are clamped to the nearest color in the defined inner area.
///
/// A gradient will paint all the regions outside the inner area with the
/// color at the end of the color stop list closest to that region.
///
/// The gradient will paint the all the regions outside the inner area with
/// the color of the point closest to that region.
/// An image filter will substitute the nearest edge pixel for any samples taken from
/// outside its source image.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_linear.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_clamp_radial.png)
clamp,

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

/// Edge is mirrored from last color to first.
/// Samples beyond the edge are mirrored back and forth across the defined area.
///
/// This is as if the stop points from 0.0 to 1.0 were then repeated backwards
/// from 2.0 to 1.0, then forwards from 2.0 to 3.0, then backwards again from
/// 4.0 to 3.0, and so forth (and for linear gradients, similarly from in the
/// For a gradient, this technique is as if the stop points from 0.0 to 1.0 were then
/// repeated backwards from 2.0 to 1.0, then forwards from 2.0 to 3.0, then backwards
/// again from 4.0 to 3.0, and so forth (and for linear gradients, similarly in the
/// negative direction).
///
/// An image filter will treat its source image as tiled in an alternating forwards and
/// backwards or upwards and downwards direction across the sample space from which
/// it is reading.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_linear.png)
/// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_radial.png)
mirror,

/// Samples beyond the edge are treated as transparent black.
///
/// A gradient will render transparency over any region that is outside the circle of a
/// radial gradient or outside the parallel lines that define the inner area of a linear
/// gradient.
///
/// An image filter will substitute transparent black for any sample it must read from
/// outside its source image.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't forget to add the new image, too. :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan was to add that as soon as it propagates to the framework and I can modify the built assets. I also plan to add equivalent ImageFilter.blur example images to all of the TileMode values.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I landed new assets today which included the assets for decal. (I forgot about ImageFilter.blur examples, sorry, so if they haven't been added yet they're still missing.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was that just for the gradient? I have been waiting to update these assets until Skia fixes bugs in the tile mode handling for the ImageFilter.blur(). It was supposed to go in a few days after this PR, but the fix has been re-un-re-verted since then and still hasn't landed and rolled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracking the blur/TileMode doc image issue in flutter/flutter#74367

decal,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess decal is as good a name for this as any, and has the advantage of matching Skia.

}

Int32List _encodeColorList(List<Color> colors) {
Expand Down
7 changes: 4 additions & 3 deletions lib/ui/painting/image_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ void ImageFilter::initPicture(Picture* picture) {
filter_ = SkPictureImageFilter::Make(picture->picture());
}

void ImageFilter::initBlur(double sigma_x, double sigma_y) {
filter_ = SkBlurImageFilter::Make(sigma_x, sigma_y, nullptr, nullptr,
SkBlurImageFilter::kClamp_TileMode);
void ImageFilter::initBlur(double sigma_x,
double sigma_y,
SkTileMode tile_mode) {
filter_ = SkImageFilters::Blur(sigma_x, sigma_y, tile_mode, nullptr, nullptr);
}

void ImageFilter::initMatrix(const tonic::Float64List& matrix4,
Expand Down
2 changes: 1 addition & 1 deletion lib/ui/painting/image_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ImageFilter : public RefCountedDartWrappable<ImageFilter> {

void initImage(CanvasImage* image);
void initPicture(Picture*);
void initBlur(double sigma_x, double sigma_y);
void initBlur(double sigma_x, double sigma_y, SkTileMode tile_mode);
void initMatrix(const tonic::Float64List& matrix4, int filter_quality);
void initColorFilter(ColorFilter* colorFilter);
void initComposeFilter(ImageFilter* outer, ImageFilter* inner);
Expand Down
2 changes: 2 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,7 @@ class SkTileModeEnum {
external SkTileMode get Clamp;
external SkTileMode get Repeat;
external SkTileMode get Mirror;
external SkTileMode get Decal;
}

@JS()
Expand All @@ -653,6 +654,7 @@ final List<SkTileMode> _skTileModes = <SkTileMode>[
canvasKit.TileMode.Clamp,
canvasKit.TileMode.Repeat,
canvasKit.TileMode.Mirror,
canvasKit.TileMode.Decal,
];

SkTileMode toSkTileMode(ui.TileMode mode) {
Expand Down
23 changes: 17 additions & 6 deletions lib/web_ui/lib/src/engine/canvaskit/image_filter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ abstract class _CkManagedSkImageFilterConvertible<T extends Object> implements u
///
/// Currently only supports `blur`.
abstract class CkImageFilter extends ManagedSkiaObject<SkImageFilter> implements _CkManagedSkImageFilterConvertible<SkImageFilter> {
factory CkImageFilter.blur({ required double sigmaX, required double sigmaY }) = _CkBlurImageFilter;
factory CkImageFilter.blur({ required double sigmaX, required double sigmaY, required ui.TileMode tileMode }) = _CkBlurImageFilter;
factory CkImageFilter.color({ required CkColorFilter colorFilter }) = _CkColorFilterImageFilter;

CkImageFilter._();
Expand Down Expand Up @@ -66,17 +66,27 @@ class _CkColorFilterImageFilter extends CkImageFilter {
}

class _CkBlurImageFilter extends CkImageFilter {
_CkBlurImageFilter({ required this.sigmaX, required this.sigmaY }) : super._();
_CkBlurImageFilter({ required this.sigmaX, required this.sigmaY, required this.tileMode }) : super._();

final double sigmaX;
final double sigmaY;
final ui.TileMode tileMode;

String get _modeString {
switch (tileMode) {
case ui.TileMode.clamp: return 'clamp';
case ui.TileMode.mirror: return 'mirror';
case ui.TileMode.repeated: return 'repeated';
case ui.TileMode.decal: return 'decal';
}
}

@override
SkImageFilter _initSkiaObject() {
return canvasKit.ImageFilter.MakeBlur(
sigmaX,
sigmaY,
canvasKit.TileMode.Clamp,
toSkTileMode(tileMode),
null,
);
}
Expand All @@ -87,15 +97,16 @@ class _CkBlurImageFilter extends CkImageFilter {
return false;
return other is _CkBlurImageFilter
&& other.sigmaX == sigmaX
&& other.sigmaY == sigmaY;
&& other.sigmaY == sigmaY
&& other.tileMode == tileMode;
}

@override
int get hashCode => ui.hashValues(sigmaX, sigmaY);
int get hashCode => ui.hashValues(sigmaX, sigmaY, tileMode);

@override
String toString() {
return 'ImageFilter.blur($sigmaX, $sigmaY)';
return 'ImageFilter.blur($sigmaX, $sigmaY, $_modeString)';
}
}

31 changes: 22 additions & 9 deletions lib/web_ui/lib/src/engine/html/shaders/shader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class GradientLinear extends EngineGradient {
@override
Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds, double density) {
if (tileMode == ui.TileMode.clamp) {
if (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal) {
return _createCanvasGradient(ctx, shaderBounds, density);
} else {
initWebGl();
Expand Down Expand Up @@ -172,7 +172,7 @@ class GradientLinear extends EngineGradient {
from.dx - offsetX, from.dy - offsetY, to.dx - offsetX,
to.dy - offsetY);
}
_addColorStopsToCanvasGradient(gradient, colors, colorStops);
_addColorStopsToCanvasGradient(gradient, colors, colorStops, tileMode == ui.TileMode.decal);
return gradient;
}

Expand Down Expand Up @@ -281,16 +281,28 @@ class GradientLinear extends EngineGradient {
}

void _addColorStopsToCanvasGradient(html.CanvasGradient gradient,
List<ui.Color> colors, List<double>? colorStops) {
List<ui.Color> colors, List<double>? colorStops, bool isDecal) {
double scale, offset;
if (isDecal) {
scale = 0.999;
offset = (1.0 - scale) / 2.0;
gradient.addColorStop(0, '#00000000');
} else {
scale = 1.0;
offset = 0.0;
}
if (colorStops == null) {
assert(colors.length == 2);
gradient.addColorStop(0, colorToCssString(colors[0])!);
gradient.addColorStop(1, colorToCssString(colors[1])!);
gradient.addColorStop(offset, colorToCssString(colors[0])!);
gradient.addColorStop(1 - offset, colorToCssString(colors[1])!);
} else {
for (int i = 0; i < colors.length; i++) {
gradient.addColorStop(colorStops[i], colorToCssString(colors[i])!);
gradient.addColorStop(colorStops[i] * scale + offset, colorToCssString(colors[i])!);
}
}
if (isDecal) {
gradient.addColorStop(1, '#00000000');
}
}

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

// Use st variable name if clamped, otherwise write code to comnpute
// Use st variable name if clamped or decaled, otherwise write code to compute
// tiled_st.
String probeName = 'st';
switch (tileMode) {
case ui.TileMode.clamp:
case ui.TileMode.decal:
break;
case ui.TileMode.repeated:
method.addStatement('float tiled_st = fract(st);');
Expand Down Expand Up @@ -349,7 +362,7 @@ class GradientRadial extends EngineGradient {
@override
Object createPaintStyle(html.CanvasRenderingContext2D? ctx,
ui.Rect? shaderBounds, double density) {
if (tileMode == ui.TileMode.clamp) {
if (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal) {
return _createCanvasGradient(ctx, shaderBounds, density);
} else {
initWebGl();
Expand All @@ -364,7 +377,7 @@ class GradientRadial extends EngineGradient {
final html.CanvasGradient gradient = ctx!.createRadialGradient(
center.dx - offsetX, center.dy - offsetY, 0,
center.dx - offsetX, center.dy - offsetY, radius);
_addColorStopsToCanvasGradient(gradient, colors, colorStops);
_addColorStopsToCanvasGradient(gradient, colors, colorStops, tileMode == ui.TileMode.decal);
return gradient;
}

Expand Down
4 changes: 2 additions & 2 deletions lib/web_ui/lib/src/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -394,9 +394,9 @@ enum FilterQuality {
}

class ImageFilter {
factory ImageFilter.blur({double sigmaX = 0.0, double sigmaY = 0.0}) {
factory ImageFilter.blur({double sigmaX = 0.0, double sigmaY = 0.0, TileMode tileMode = TileMode.clamp}) {
if (engine.useCanvasKit) {
return engine.CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY);
return engine.CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode);
}
return engine.EngineImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY);
}
Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/ui/tile_mode.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ enum TileMode {
clamp,
repeated,
mirror,
decal,
}
Loading