From 9601934bdad26799704e1791eefbebf74b8fe8a0 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 8 Dec 2020 22:04:08 -0800 Subject: [PATCH 1/4] Add a new TileMode.decal value and accept it in ImagerFilter.blur() --- lib/ui/painting.dart | 50 +++++++++++++++++++++++++-------- lib/ui/painting/image_filter.cc | 6 ++-- lib/ui/painting/image_filter.h | 2 +- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index a1fd6584a6f44..58aa4afdd5ed4 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -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. @@ -3206,10 +3207,11 @@ 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); @@ -3217,10 +3219,10 @@ class _GaussianBlurImageFilter implements ImageFilter { _ImageFilter _toNativeImageFilter() => nativeFilter; @override - String get _shortDescription => 'blur($sigmaX, $sigmaY)'; + String get _shortDescription => 'blur($sigmaX, $sigmaY, $tileMode)'; @override - String toString() => 'ImageFilter.blur($sigmaX, $sigmaY)'; + String toString() => 'ImageFilter.blur($sigmaX, $sigmaY, $tileMode)'; @override bool operator ==(Object other) { @@ -3228,7 +3230,8 @@ class _GaussianBlurImageFilter implements ImageFilter { return false; return other is _GaussianBlurImageFilter && other.sigmaX == sigmaX - && other.sigmaY == sigmaY; + && other.sigmaY == sigmaY + && other.tileMode == tileMode; } @override @@ -3278,9 +3281,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. /// @@ -3330,14 +3333,21 @@ 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. The bounds of the source image contain +/// the pixels over which it is defined, but the operation performed by the image +/// filter may need to combine those samples with other samples read from outside +/// the image - particularly in the case of a blur operation. +/// +/// This enum is used to define how the gradient or image filter should treat the regions /// outside that defined inner area. /// /// See also: @@ -3349,13 +3359,16 @@ 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. +// These enum values must be kept in sync with SkTileMode. enum TileMode { /// Edge is clamped to the final color. /// /// The gradient will paint the all the regions outside the inner area with - /// the color of the point closest to that region. + /// the color of the point closest to that region. An image filter will continue + /// to read the nearest edge pixel of an image for all samples required from + /// outside the 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, @@ -3365,6 +3378,9 @@ enum TileMode { /// 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 the image as tiled across the sample space from + /// which it is reading. + /// /// ![](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, @@ -3376,9 +3392,19 @@ enum TileMode { /// 4.0 to 3.0, and so forth (and for linear gradients, similarly from in the /// negative direction). /// + /// An image filter will treat the 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, + + /// Edge is transparent black. + /// + /// The gradient or image sampling operation will evaluate as transparent black pixels + /// all regions outside of the image or primary gradient area. + decal, } Int32List _encodeColorList(List colors) { diff --git a/lib/ui/painting/image_filter.cc b/lib/ui/painting/image_filter.cc index 6161fb7d4f5b4..bb135f5f64356 100644 --- a/lib/ui/painting/image_filter.cc +++ b/lib/ui/painting/image_filter.cc @@ -55,9 +55,9 @@ 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, diff --git a/lib/ui/painting/image_filter.h b/lib/ui/painting/image_filter.h index b1654d865dce8..fc5f929388a85 100644 --- a/lib/ui/painting/image_filter.h +++ b/lib/ui/painting/image_filter.h @@ -24,7 +24,7 @@ class ImageFilter : public RefCountedDartWrappable { 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); From 4af022b937875cc29a2f1242ad6e7b645068ec98 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 9 Dec 2020 19:27:59 -0800 Subject: [PATCH 2/4] fix tests, analyze, format, and implement on web as best can --- lib/ui/painting.dart | 13 ++++++-- lib/ui/painting/image_filter.cc | 3 +- .../src/engine/canvaskit/canvaskit_api.dart | 2 ++ .../src/engine/canvaskit/image_filter.dart | 23 ++++++++++---- .../lib/src/engine/html/shaders/shader.dart | 31 +++++++++++++------ lib/web_ui/lib/src/ui/painting.dart | 4 +-- lib/web_ui/lib/src/ui/tile_mode.dart | 1 + testing/dart/image_filter_test.dart | 15 ++++----- 8 files changed, 65 insertions(+), 27 deletions(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 58aa4afdd5ed4..a1683317ad1c2 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -3218,11 +3218,20 @@ class _GaussianBlurImageFilter implements ImageFilter { @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, $tileMode)'; + String get _shortDescription => 'blur($sigmaX, $sigmaY, $_modeString)'; @override - String toString() => 'ImageFilter.blur($sigmaX, $sigmaY, $tileMode)'; + String toString() => 'ImageFilter.blur($sigmaX, $sigmaY, $_modeString)'; @override bool operator ==(Object other) { diff --git a/lib/ui/painting/image_filter.cc b/lib/ui/painting/image_filter.cc index bb135f5f64356..65c67792e221a 100644 --- a/lib/ui/painting/image_filter.cc +++ b/lib/ui/painting/image_filter.cc @@ -55,7 +55,8 @@ void ImageFilter::initPicture(Picture* picture) { filter_ = SkPictureImageFilter::Make(picture->picture()); } -void ImageFilter::initBlur(double sigma_x, double sigma_y, +void ImageFilter::initBlur(double sigma_x, + double sigma_y, SkTileMode tile_mode) { filter_ = SkImageFilters::Blur(sigma_x, sigma_y, tile_mode, nullptr, nullptr); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index a5fe4df54d0cd..f4bf266ee1252 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -642,6 +642,7 @@ class SkTileModeEnum { external SkTileMode get Clamp; external SkTileMode get Repeat; external SkTileMode get Mirror; + external SkTileMode get Decal; } @JS() @@ -653,6 +654,7 @@ final List _skTileModes = [ canvasKit.TileMode.Clamp, canvasKit.TileMode.Repeat, canvasKit.TileMode.Mirror, + canvasKit.TileMode.Decal, ]; SkTileMode toSkTileMode(ui.TileMode mode) { diff --git a/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart b/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart index 9d993bea868aa..114515b6fc5fe 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart @@ -20,7 +20,7 @@ abstract class _CkManagedSkImageFilterConvertible implements u /// /// Currently only supports `blur`. abstract class CkImageFilter extends ManagedSkiaObject implements _CkManagedSkImageFilterConvertible { - 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._(); @@ -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, ); } @@ -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)'; } } diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart index 40b0e8497e7dc..d9ee28fa42201 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -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(); @@ -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; } @@ -281,16 +281,28 @@ class GradientLinear extends EngineGradient { } void _addColorStopsToCanvasGradient(html.CanvasGradient gradient, - List colors, List? colorStops) { + List colors, List? 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. @@ -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);'); @@ -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(); @@ -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; } diff --git a/lib/web_ui/lib/src/ui/painting.dart b/lib/web_ui/lib/src/ui/painting.dart index 3b3f14694e85b..fbc4f6824ee47 100644 --- a/lib/web_ui/lib/src/ui/painting.dart +++ b/lib/web_ui/lib/src/ui/painting.dart @@ -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); } diff --git a/lib/web_ui/lib/src/ui/tile_mode.dart b/lib/web_ui/lib/src/ui/tile_mode.dart index 45bce2e2f5a66..38c203eb3569d 100644 --- a/lib/web_ui/lib/src/ui/tile_mode.dart +++ b/lib/web_ui/lib/src/ui/tile_mode.dart @@ -10,4 +10,5 @@ enum TileMode { clamp, repeated, mirror, + decal, } diff --git a/testing/dart/image_filter_test.dart b/testing/dart/image_filter_test.dart index b3f747d9f6cd7..fb5405915d267 100644 --- a/testing/dart/image_filter_test.dart +++ b/testing/dart/image_filter_test.dart @@ -72,8 +72,8 @@ void main() { return bytes.buffer.asUint32List(); } - ImageFilter makeBlur(double sigmaX, double sigmaY) => - ImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY); + ImageFilter makeBlur(double sigmaX, double sigmaY, [TileMode tileMode = TileMode.clamp]) => + ImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); ImageFilter makeScale(double scX, double scY, [double trX = 0.0, double trY = 0.0, @@ -105,6 +105,7 @@ void main() { List makeList() { return [ makeBlur(10.0, 10.0), + makeBlur(10.0, 10.0, TileMode.decal), makeBlur(10.0, 20.0), makeBlur(20.0, 20.0), makeScale(10.0, 10.0), @@ -272,14 +273,14 @@ void main() { test('Composite ImageFilter toString', () { expect( - ImageFilter.compose(outer: makeBlur(20.0, 20.0), inner: makeBlur(10.0, 10.0)).toString(), - contains('blur(10.0, 10.0) -> blur(20.0, 20.0)'), + ImageFilter.compose(outer: makeBlur(20.0, 20.0, TileMode.decal), inner: makeBlur(10.0, 10.0)).toString(), + contains('blur(10.0, 10.0, clamp) -> blur(20.0, 20.0, decal)'), ); // Produces a flat list of filters expect( ImageFilter.compose( - outer: ImageFilter.compose(outer: makeBlur(30.0, 30.0), inner: makeBlur(20.0, 20.0)), + outer: ImageFilter.compose(outer: makeBlur(30.0, 30.0, TileMode.mirror), inner: makeBlur(20.0, 20.0, TileMode.repeated)), inner: ImageFilter.compose( outer: const ColorFilter.mode(null, null), inner: makeScale(10.0, 10.0), @@ -288,8 +289,8 @@ void main() { contains( 'matrix([10.0, 0.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -0.0, -0.0, 0.0, 1.0], FilterQuality.low) -> ' 'ColorFilter.mode(null, null) -> ' - 'blur(20.0, 20.0) -> ' - 'blur(30.0, 30.0)' + 'blur(20.0, 20.0, repeated) -> ' + 'blur(30.0, 30.0, mirror)' ), ); }); From 0f4c9f37e904663d98d6d32dfa2975382eb01f53 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Wed, 9 Dec 2020 23:19:04 -0800 Subject: [PATCH 3/4] fix web_ui filter tests --- lib/web_ui/test/canvaskit/filter_test.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/web_ui/test/canvaskit/filter_test.dart b/lib/web_ui/test/canvaskit/filter_test.dart index b548ed7a972a6..397e5e652ed70 100644 --- a/lib/web_ui/test/canvaskit/filter_test.dart +++ b/lib/web_ui/test/canvaskit/filter_test.dart @@ -40,8 +40,9 @@ void testMain() { List createImageFilters() { return [ - CkImageFilter.blur(sigmaX: 5, sigmaY: 6), - CkImageFilter.blur(sigmaX: 6, sigmaY: 5), + CkImageFilter.blur(sigmaX: 5, sigmaY: 6, tileMode: ui.TileMode.clamp), + CkImageFilter.blur(sigmaX: 6, sigmaY: 5, tileMode: ui.TileMode.clamp), + CkImageFilter.blur(sigmaX: 6, sigmaY: 5, tileMode: ui.TileMode.decal), for (final CkColorFilter colorFilter in createColorFilters()) CkImageFilter.color(colorFilter: colorFilter), ]; } @@ -50,7 +51,7 @@ void testMain() { setUpCanvasKitTest(); test('can be constructed', () { - final CkImageFilter imageFilter = CkImageFilter.blur(sigmaX: 5, sigmaY: 10); + final CkImageFilter imageFilter = CkImageFilter.blur(sigmaX: 5, sigmaY: 10, tileMode: ui.TileMode.clamp); expect(imageFilter, isA()); expect(imageFilter.createDefault(), isNotNull); expect(imageFilter.resurrect(), isNotNull); @@ -80,12 +81,12 @@ void testMain() { test('reuses the Skia filter', () { final CkPaint paint = CkPaint(); - paint.imageFilter = CkImageFilter.blur(sigmaX: 5, sigmaY: 10); + paint.imageFilter = CkImageFilter.blur(sigmaX: 5, sigmaY: 10, tileMode: ui.TileMode.clamp); final ManagedSkiaObject managedFilter = paint.imageFilter as ManagedSkiaObject; final Object skiaFilter = managedFilter?.skiaObject; - paint.imageFilter = CkImageFilter.blur(sigmaX: 5, sigmaY: 10); + paint.imageFilter = CkImageFilter.blur(sigmaX: 5, sigmaY: 10, tileMode: ui.TileMode.clamp); expect((paint.imageFilter as ManagedSkiaObject).skiaObject, same(skiaFilter)); }); From c017b76a9fcd6212409c0026fadea744f3ab91e2 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Thu, 10 Dec 2020 00:21:01 -0800 Subject: [PATCH 4/4] update doc comments per review feedback --- lib/ui/painting.dart | 55 ++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index a1683317ad1c2..49efc1d0d3e3e 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -3351,10 +3351,11 @@ class Shader extends NativeFieldWrapperClass2 { /// that covers the circle centered on a particular point up to a given radius. /// /// An image filter reads source samples from a source image and performs operations -/// on those samples to produce a result image. The bounds of the source image contain -/// the pixels over which it is defined, but the operation performed by the image -/// filter may need to combine those samples with other samples read from outside -/// the image - particularly in the case of a blur operation. +/// 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. @@ -3368,40 +3369,44 @@ 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. +/// * [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. /// - /// 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 continue - /// to read the nearest edge pixel of an image for all samples required from - /// outside the source image. + /// 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. + /// + /// 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 the image as tiled across the sample space from - /// which it is reading. + /// 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 the image as tiled in an alternating forwards and + /// 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. /// @@ -3409,10 +3414,14 @@ enum TileMode { /// ![](https://flutter.github.io/assets-for-api-docs/assets/dart-ui/tile_mode_mirror_radial.png) mirror, - /// Edge is transparent black. + /// 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. /// - /// The gradient or image sampling operation will evaluate as transparent black pixels - /// all regions outside of the image or primary gradient area. + /// An image filter will substitute transparent black for any sample it must read from + /// outside its source image. decal, }