Skip to content

Commit

Permalink
Allow Tile mode for blur filter and add new decal TileMode (flutter#2…
Browse files Browse the repository at this point in the history
…2982)

Add a new TileMode.decal enum value and allow TileMode in ImagerFilter.blur() constructor
  • Loading branch information
flar committed Dec 10, 2020
1 parent 2efc7c1 commit bb81b95
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 55 deletions.
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.
decal,
}

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,
}

0 comments on commit bb81b95

Please sign in to comment.