diff --git a/display_list/display_list_builder.cc b/display_list/display_list_builder.cc index 9b5d517c01d6a..182b8ddc89686 100644 --- a/display_list/display_list_builder.cc +++ b/display_list/display_list_builder.cc @@ -193,6 +193,20 @@ void DisplayListBuilder::onSetImageFilter(const DlImageFilter* filter) { new (pod) DlBlurImageFilter(blur_filter); break; } + case DlImageFilterType::kDilate: { + const DlDilateImageFilter* dilate_filter = filter->asDilate(); + FML_DCHECK(dilate_filter); + void* pod = Push(dilate_filter->size(), 0); + new (pod) DlDilateImageFilter(dilate_filter); + break; + } + case DlImageFilterType::kErode: { + const DlErodeImageFilter* erode_filter = filter->asErode(); + FML_DCHECK(erode_filter); + void* pod = Push(erode_filter->size(), 0); + new (pod) DlErodeImageFilter(erode_filter); + break; + } case DlImageFilterType::kMatrix: { const DlMatrixImageFilter* matrix_filter = filter->asMatrix(); FML_DCHECK(matrix_filter); diff --git a/display_list/display_list_canvas_unittests.cc b/display_list/display_list_canvas_unittests.cc index 99b2358f0ad92..a88d445b52e5e 100644 --- a/display_list/display_list_canvas_unittests.cc +++ b/display_list/display_list_canvas_unittests.cc @@ -1121,6 +1121,65 @@ class CanvasCompareTester { } } + { + // Being able to see a dilate requires some non-default attributes, + // like a non-trivial stroke width and a shader rather than a color + // (for drawPaint) so we create a new environment for these tests. + RenderEnvironment dilate_env = RenderEnvironment::MakeN32(); + CvSetup cv_dilate_setup = [=](SkCanvas*, SkPaint& p) { + p.setShader(testImageColorSource.skia_object()); + p.setStrokeWidth(5.0); + }; + DlRenderer dl_dilate_setup = [=](DisplayListBuilder& b) { + b.setColorSource(&testImageColorSource); + b.setStrokeWidth(5.0); + }; + dilate_env.init_ref(cv_dilate_setup, testP.cv_renderer(), + dl_dilate_setup); + DlDilateImageFilter filter_5(5.0, 5.0); + RenderWith(testP, dilate_env, tolerance, + CaseParameters( + "ImageFilter == Dilate 5", + [=](SkCanvas* cv, SkPaint& p) { + cv_dilate_setup(cv, p); + p.setImageFilter(filter_5.skia_object()); + }, + [=](DisplayListBuilder& b) { + dl_dilate_setup(b); + b.setImageFilter(&filter_5); + })); + } + + { + // Being able to see an erode requires some non-default attributes, + // like a non-trivial stroke width and a shader rather than a color + // (for drawPaint) so we create a new environment for these tests. + RenderEnvironment erode_env = RenderEnvironment::MakeN32(); + CvSetup cv_erode_setup = [=](SkCanvas*, SkPaint& p) { + p.setShader(testImageColorSource.skia_object()); + p.setStrokeWidth(6.0); + }; + DlRenderer dl_erode_setup = [=](DisplayListBuilder& b) { + b.setColorSource(&testImageColorSource); + b.setStrokeWidth(6.0); + }; + erode_env.init_ref(cv_erode_setup, testP.cv_renderer(), dl_erode_setup); + // do not erode too much, because some tests assert there are enough + // pixels that are changed. + DlErodeImageFilter filter_1(1.0, 1.0); + RenderWith(testP, erode_env, tolerance, + CaseParameters( + "ImageFilter == Erode 1", + [=](SkCanvas* cv, SkPaint& p) { + cv_erode_setup(cv, p); + p.setImageFilter(filter_1.skia_object()); + }, + [=](DisplayListBuilder& b) { + dl_erode_setup(b); + b.setImageFilter(&filter_1); + })); + } + { // clang-format off constexpr float rotate_color_matrix[20] = { diff --git a/display_list/display_list_image_filter.h b/display_list/display_list_image_filter.h index f576567b83ce6..25d2e93bb207c 100644 --- a/display_list/display_list_image_filter.h +++ b/display_list/display_list_image_filter.h @@ -28,6 +28,8 @@ namespace flutter { // provided as a fallback. enum class DlImageFilterType { kBlur, + kDilate, + kErode, kMatrix, kComposeFilter, kColorFilter, @@ -35,6 +37,8 @@ enum class DlImageFilterType { }; class DlBlurImageFilter; +class DlDilateImageFilter; +class DlErodeImageFilter; class DlMatrixImageFilter; class DlComposeImageFilter; class DlColorFilterImageFilter; @@ -64,6 +68,14 @@ class DlImageFilter // type of ImageFilter, otherwise return nullptr. virtual const DlBlurImageFilter* asBlur() const { return nullptr; } + // Return a DlDilateImageFilter pointer to this object iff it is a Dilate + // type of ImageFilter, otherwise return nullptr. + virtual const DlDilateImageFilter* asDilate() const { return nullptr; } + + // Return a DlErodeImageFilter pointer to this object iff it is an Erode + // type of ImageFilter, otherwise return nullptr. + virtual const DlErodeImageFilter* asErode() const { return nullptr; } + // Return a DlMatrixImageFilter pointer to this object iff it is a Matrix // type of ImageFilter, otherwise return nullptr. virtual const DlMatrixImageFilter* asMatrix() const { return nullptr; } @@ -140,7 +152,7 @@ class DlBlurImageFilter final : public DlImageFilter { SkIRect* map_device_bounds(const SkIRect& input_bounds, const SkMatrix& ctm, SkIRect& output_bounds) const override { - SkVector device_sigma = ctm.mapVector(sigma_x_, sigma_y_); + SkVector device_sigma = ctm.mapVector(sigma_x_ * 3, sigma_y_ * 3); if (!SkScalarIsFinite(device_sigma.fX)) { device_sigma.fX = 0; } @@ -174,6 +186,126 @@ class DlBlurImageFilter final : public DlImageFilter { DlTileMode tile_mode_; }; +class DlDilateImageFilter final : public DlImageFilter { + public: + DlDilateImageFilter(SkScalar radius_x, SkScalar radius_y) + : radius_x_(radius_x), radius_y_(radius_y) {} + explicit DlDilateImageFilter(const DlDilateImageFilter* filter) + : DlDilateImageFilter(filter->radius_x_, filter->radius_y_) {} + explicit DlDilateImageFilter(const DlDilateImageFilter& filter) + : DlDilateImageFilter(&filter) {} + + std::shared_ptr shared() const override { + return std::make_shared(this); + } + + DlImageFilterType type() const override { return DlImageFilterType::kDilate; } + size_t size() const override { return sizeof(*this); } + + const DlDilateImageFilter* asDilate() const override { return this; } + + bool modifies_transparent_black() const override { return false; } + + SkRect* map_local_bounds(const SkRect& input_bounds, + SkRect& output_bounds) const override { + output_bounds = input_bounds.makeOutset(radius_x_, radius_y_); + return &output_bounds; + } + + SkIRect* map_device_bounds(const SkIRect& input_bounds, + const SkMatrix& ctm, + SkIRect& output_bounds) const override { + SkVector device_radius = ctm.mapVector(radius_x_, radius_y_); + if (!SkScalarIsFinite(device_radius.fX)) { + device_radius.fX = 0; + } + if (!SkScalarIsFinite(device_radius.fY)) { + device_radius.fY = 0; + } + output_bounds = input_bounds.makeOutset(ceil(abs(device_radius.fX)), + ceil(abs(device_radius.fY))); + return &output_bounds; + } + + SkScalar radius_x() const { return radius_x_; } + SkScalar radius_y() const { return radius_y_; } + + sk_sp skia_object() const override { + return SkImageFilters::Dilate(radius_x_, radius_y_, nullptr); + } + + protected: + bool equals_(const DlImageFilter& other) const override { + FML_DCHECK(other.type() == DlImageFilterType::kDilate); + auto that = static_cast(&other); + return (radius_x_ == that->radius_x_ && radius_y_ == that->radius_y_); + } + + private: + SkScalar radius_x_; + SkScalar radius_y_; +}; + +class DlErodeImageFilter final : public DlImageFilter { + public: + DlErodeImageFilter(SkScalar radius_x, SkScalar radius_y) + : radius_x_(radius_x), radius_y_(radius_y) {} + explicit DlErodeImageFilter(const DlErodeImageFilter* filter) + : DlErodeImageFilter(filter->radius_x_, filter->radius_y_) {} + explicit DlErodeImageFilter(const DlErodeImageFilter& filter) + : DlErodeImageFilter(&filter) {} + + std::shared_ptr shared() const override { + return std::make_shared(this); + } + + DlImageFilterType type() const override { return DlImageFilterType::kErode; } + size_t size() const override { return sizeof(*this); } + + const DlErodeImageFilter* asErode() const override { return this; } + + bool modifies_transparent_black() const override { return false; } + + SkRect* map_local_bounds(const SkRect& input_bounds, + SkRect& output_bounds) const override { + output_bounds = input_bounds.makeOutset(radius_x_, radius_y_); + return &output_bounds; + } + + SkIRect* map_device_bounds(const SkIRect& input_bounds, + const SkMatrix& ctm, + SkIRect& output_bounds) const override { + SkVector device_radius = ctm.mapVector(radius_x_, radius_y_); + if (!SkScalarIsFinite(device_radius.fX)) { + device_radius.fX = 0; + } + if (!SkScalarIsFinite(device_radius.fY)) { + device_radius.fY = 0; + } + output_bounds = input_bounds.makeOutset(ceil(abs(device_radius.fX)), + ceil(abs(device_radius.fY))); + return &output_bounds; + } + + SkScalar radius_x() const { return radius_x_; } + SkScalar radius_y() const { return radius_y_; } + + sk_sp skia_object() const override { + return SkImageFilters::Erode(radius_x_, radius_y_, nullptr); + } + + protected: + bool equals_(const DlImageFilter& other) const override { + FML_DCHECK(other.type() == DlImageFilterType::kErode); + auto that = static_cast(&other); + return (radius_x_ == that->radius_x_ && radius_y_ == that->radius_y_); + } + + private: + SkScalar radius_x_; + SkScalar radius_y_; +}; + class DlMatrixImageFilter final : public DlImageFilter { public: DlMatrixImageFilter(const SkMatrix& matrix, const SkSamplingOptions& sampling) diff --git a/display_list/display_list_image_filter_unittests.cc b/display_list/display_list_image_filter_unittests.cc index f55c29849120d..7e33bfb782488 100644 --- a/display_list/display_list_image_filter_unittests.cc +++ b/display_list/display_list_image_filter_unittests.cc @@ -43,6 +43,40 @@ TEST(DisplayListImageFilter, FromSkiaBlurImageFilter) { // We cannot recapture the blur parameters from an SkBlurImageFilter ASSERT_EQ(filter->asBlur(), nullptr); + ASSERT_EQ(filter->asDilate(), nullptr); + ASSERT_EQ(filter->asErode(), nullptr); + ASSERT_EQ(filter->asMatrix(), nullptr); + ASSERT_EQ(filter->asCompose(), nullptr); + ASSERT_EQ(filter->asColorFilter(), nullptr); +} + +TEST(DisplayListImageFilter, FromSkiaDilateImageFilter) { + sk_sp sk_image_filter = + SkImageFilters::Dilate(5.0, 5.0, nullptr); + std::shared_ptr filter = DlImageFilter::From(sk_image_filter); + + ASSERT_EQ(filter->type(), DlImageFilterType::kUnknown); + + // We cannot recapture the dilate parameters from an SkDilateImageFilter + ASSERT_EQ(filter->asBlur(), nullptr); + ASSERT_EQ(filter->asDilate(), nullptr); + ASSERT_EQ(filter->asErode(), nullptr); + ASSERT_EQ(filter->asMatrix(), nullptr); + ASSERT_EQ(filter->asCompose(), nullptr); + ASSERT_EQ(filter->asColorFilter(), nullptr); +} + +TEST(DisplayListImageFilter, FromSkiaErodeImageFilter) { + sk_sp sk_image_filter = + SkImageFilters::Erode(5.0, 5.0, nullptr); + std::shared_ptr filter = DlImageFilter::From(sk_image_filter); + + ASSERT_EQ(filter->type(), DlImageFilterType::kUnknown); + + // We cannot recapture the erode parameters from an SkErodeImageFilter + ASSERT_EQ(filter->asBlur(), nullptr); + ASSERT_EQ(filter->asDilate(), nullptr); + ASSERT_EQ(filter->asErode(), nullptr); ASSERT_EQ(filter->asMatrix(), nullptr); ASSERT_EQ(filter->asCompose(), nullptr); ASSERT_EQ(filter->asColorFilter(), nullptr); @@ -57,6 +91,8 @@ TEST(DisplayListImageFilter, FromSkiaMatrixImageFilter) { // We cannot recapture the blur parameters from an SkMatrixImageFilter ASSERT_EQ(filter->asBlur(), nullptr); + ASSERT_EQ(filter->asDilate(), nullptr); + ASSERT_EQ(filter->asErode(), nullptr); ASSERT_EQ(filter->asMatrix(), nullptr); ASSERT_EQ(filter->asCompose(), nullptr); ASSERT_EQ(filter->asColorFilter(), nullptr); @@ -75,6 +111,8 @@ TEST(DisplayListImageFilter, FromSkiaComposeImageFilter) { // We cannot recapture the blur parameters from an SkComposeImageFilter ASSERT_EQ(filter->asBlur(), nullptr); + ASSERT_EQ(filter->asDilate(), nullptr); + ASSERT_EQ(filter->asErode(), nullptr); ASSERT_EQ(filter->asMatrix(), nullptr); ASSERT_EQ(filter->asCompose(), nullptr); ASSERT_EQ(filter->asColorFilter(), nullptr); @@ -96,6 +134,8 @@ TEST(DisplayListImageFilter, FromSkiaColorFilterImageFilter) { ASSERT_EQ(*filter->asColorFilter()->color_filter(), dl_color_filter); ASSERT_EQ(filter->asBlur(), nullptr); + ASSERT_EQ(filter->asDilate(), nullptr); + ASSERT_EQ(filter->asErode(), nullptr); ASSERT_EQ(filter->asMatrix(), nullptr); ASSERT_EQ(filter->asCompose(), nullptr); ASSERT_NE(filter->asColorFilter(), nullptr); @@ -145,6 +185,88 @@ TEST(DisplayListImageFilter, BlurNotEquals) { TestNotEquals(filter1, filter4, "Tile Mode differs"); } +TEST(DisplayListImageFilter, DilateConstructor) { + DlDilateImageFilter filter(5.0, 6.0); +} + +TEST(DisplayListImageFilter, DilateShared) { + DlDilateImageFilter filter(5.0, 6.0); + + ASSERT_NE(filter.shared().get(), &filter); + ASSERT_EQ(*filter.shared(), filter); +} + +TEST(DisplayListImageFilter, DilateAsDilate) { + DlDilateImageFilter filter(5.0, 6.0); + + ASSERT_NE(filter.asDilate(), nullptr); + ASSERT_EQ(filter.asDilate(), &filter); +} + +TEST(DisplayListImageFilter, DilateContents) { + DlDilateImageFilter filter(5.0, 6.0); + + ASSERT_EQ(filter.radius_x(), 5.0); + ASSERT_EQ(filter.radius_y(), 6.0); +} + +TEST(DisplayListImageFilter, DilateEquals) { + DlDilateImageFilter filter1(5.0, 6.0); + DlDilateImageFilter filter2(5.0, 6.0); + + TestEquals(filter1, filter2); +} + +TEST(DisplayListImageFilter, DilateNotEquals) { + DlDilateImageFilter filter1(5.0, 6.0); + DlDilateImageFilter filter2(7.0, 6.0); + DlDilateImageFilter filter3(5.0, 8.0); + + TestNotEquals(filter1, filter2, "Radius X differs"); + TestNotEquals(filter1, filter3, "Radius Y differs"); +} + +TEST(DisplayListImageFilter, ErodeConstructor) { + DlErodeImageFilter filter(5.0, 6.0); +} + +TEST(DisplayListImageFilter, ErodeShared) { + DlErodeImageFilter filter(5.0, 6.0); + + ASSERT_NE(filter.shared().get(), &filter); + ASSERT_EQ(*filter.shared(), filter); +} + +TEST(DisplayListImageFilter, ErodeAsErode) { + DlErodeImageFilter filter(5.0, 6.0); + + ASSERT_NE(filter.asErode(), nullptr); + ASSERT_EQ(filter.asErode(), &filter); +} + +TEST(DisplayListImageFilter, ErodeContents) { + DlErodeImageFilter filter(5.0, 6.0); + + ASSERT_EQ(filter.radius_x(), 5.0); + ASSERT_EQ(filter.radius_y(), 6.0); +} + +TEST(DisplayListImageFilter, ErodeEquals) { + DlErodeImageFilter filter1(5.0, 6.0); + DlErodeImageFilter filter2(5.0, 6.0); + + TestEquals(filter1, filter2); +} + +TEST(DisplayListImageFilter, ErodeNotEquals) { + DlErodeImageFilter filter1(5.0, 6.0); + DlErodeImageFilter filter2(7.0, 6.0); + DlErodeImageFilter filter3(5.0, 8.0); + + TestNotEquals(filter1, filter2, "Radius X differs"); + TestNotEquals(filter1, filter3, "Radius Y differs"); +} + TEST(DisplayListImageFilter, MatrixConstructor) { DlMatrixImageFilter filter(SkMatrix::MakeAll(2.0, 0.0, 10, // 0.5, 3.0, 15, // diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc index 6c8bf6cafb399..720fc7a0dd6ca 100644 --- a/display_list/display_list_unittests.cc +++ b/display_list/display_list_unittests.cc @@ -149,6 +149,12 @@ static const DlBlurImageFilter TestBlurImageFilter3(5.0, static const DlBlurImageFilter TestBlurImageFilter4(5.0, 5.0, DlTileMode::kDecal); +static const DlDilateImageFilter TestDilateImageFilter1(5.0, 5.0); +static const DlDilateImageFilter TestDilateImageFilter2(6.0, 5.0); +static const DlDilateImageFilter TestDilateImageFilter3(5.0, 6.0); +static const DlErodeImageFilter TestErodeImageFilter1(5.0, 5.0); +static const DlErodeImageFilter TestErodeImageFilter2(6.0, 5.0); +static const DlErodeImageFilter TestErodeImageFilter3(5.0, 6.0); static const DlMatrixImageFilter TestMatrixImageFilter1(SkMatrix::RotateDeg(45), NearestSampling); static const DlMatrixImageFilter TestMatrixImageFilter2(SkMatrix::RotateDeg(85), @@ -391,6 +397,12 @@ std::vector allGroups = { {0, 32, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(&TestBlurImageFilter2);}}, {0, 32, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(&TestBlurImageFilter3);}}, {0, 32, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(&TestBlurImageFilter4);}}, + {0, 24, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(&TestDilateImageFilter1);}}, + {0, 24, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(&TestDilateImageFilter2);}}, + {0, 24, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(&TestDilateImageFilter3);}}, + {0, 24, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(&TestErodeImageFilter1);}}, + {0, 24, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(&TestErodeImageFilter2);}}, + {0, 24, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(&TestErodeImageFilter3);}}, {0, 80, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(&TestMatrixImageFilter1);}}, {0, 80, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(&TestMatrixImageFilter2);}}, {0, 80, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(&TestMatrixImageFilter3);}}, diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 018dfd7cfcc97..67545e378885e 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -3224,6 +3224,22 @@ abstract class ImageFilter { return _GaussianBlurImageFilter(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); } + /// Creates an image filter that dilates each input pixel's channel values + /// to the max value within the given radii along the x and y axes. + factory ImageFilter.dilate({ double radiusX = 0.0, double radiusY = 0.0 }) { + assert(radiusX != null); + assert(radiusY != null); + return _DilateImageFilter(radiusX: radiusX, radiusY: radiusY); + } + + /// Create a filter that erodes each input pixel's channel values + /// to the minimum channel value within the given radii along the x and y axes. + factory ImageFilter.erode({ double radiusX = 0.0, double radiusY = 0.0 }) { + assert(radiusX != null); + assert(radiusY != null); + return _ErodeImageFilter(radiusX: radiusX, radiusY: radiusY); + } + /// Creates an image filter that applies a matrix transformation. /// /// For example, applying a positive scale matrix (see [Matrix4.diagonal3]) @@ -3327,6 +3343,64 @@ class _GaussianBlurImageFilter implements ImageFilter { int get hashCode => hashValues(sigmaX, sigmaY); } +class _DilateImageFilter implements ImageFilter { + _DilateImageFilter({ required this.radiusX, required this.radiusY }); + + final double radiusX; + final double radiusY; + + late final _ImageFilter nativeFilter = _ImageFilter.dilate(this); + @override + _ImageFilter _toNativeImageFilter() => nativeFilter; + + @override + String get _shortDescription => 'dilate($radiusX, $radiusY)'; + + @override + String toString() => 'ImageFilter.dilate($radiusX, $radiusY)'; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) + return false; + return other is _DilateImageFilter + && other.radiusX == radiusX + && other.radiusY == radiusY; + } + + @override + int get hashCode => hashValues(radiusX, radiusY); +} + +class _ErodeImageFilter implements ImageFilter { + _ErodeImageFilter({ required this.radiusX, required this.radiusY }); + + final double radiusX; + final double radiusY; + + late final _ImageFilter nativeFilter = _ImageFilter.erode(this); + @override + _ImageFilter _toNativeImageFilter() => nativeFilter; + + @override + String get _shortDescription => 'erode($radiusX, $radiusY)'; + + @override + String toString() => 'ImageFilter.erode($radiusX, $radiusY)'; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) + return false; + return other is _ErodeImageFilter + && other.radiusX == radiusX + && other.radiusY == radiusY; + } + + @override + int get hashCode => hashValues(radiusX, radiusY); +} + class _ComposeImageFilter implements ImageFilter { _ComposeImageFilter({ required this.innerFilter, required this.outerFilter }); @@ -3374,6 +3448,26 @@ class _ImageFilter extends NativeFieldWrapperClass1 { } void _initBlur(double sigmaX, double sigmaY, int tileMode) native 'ImageFilter_initBlur'; + /// Creates an image filter that dilates each input pixel's channel values + /// to the max value within the given radii along the x and y axes. + _ImageFilter.dilate(_DilateImageFilter filter) + : assert(filter != null), + creator = filter { // ignore: prefer_initializing_formals + _constructor(); + _initDilate(filter.radiusX, filter.radiusY); + } + void _initDilate(double radiusX, double radiusY) native 'ImageFilter_initDilate'; + + /// Create a filter that erodes each input pixel's channel values + /// to the minimum channel value within the given radii along the x and y axes. + _ImageFilter.erode(_ErodeImageFilter filter) + : assert(filter != null), + creator = filter { // ignore: prefer_initializing_formals + _constructor(); + _initErode(filter.radiusX, filter.radiusY); + } + void _initErode(double radiusX, double radiusY) native 'ImageFilter_initErode'; + /// Creates an image filter that applies a matrix transformation. /// /// For example, applying a positive scale matrix (see [Matrix4.diagonal3]) diff --git a/lib/ui/painting/image_filter.cc b/lib/ui/painting/image_filter.cc index b8780be7727dc..47132fc1b538f 100644 --- a/lib/ui/painting/image_filter.cc +++ b/lib/ui/painting/image_filter.cc @@ -22,6 +22,8 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, ImageFilter); #define FOR_EACH_BINDING(V) \ V(ImageFilter, initBlur) \ + V(ImageFilter, initDilate) \ + V(ImageFilter, initErode) \ V(ImageFilter, initMatrix) \ V(ImageFilter, initColorFilter) \ V(ImageFilter, initComposeFilter) @@ -74,6 +76,14 @@ void ImageFilter::initBlur(double sigma_x, std::make_shared(sigma_x, sigma_y, ToDl(tile_mode)); } +void ImageFilter::initDilate(double radius_x, double radius_y) { + filter_ = std::make_shared(radius_x, radius_y); +} + +void ImageFilter::initErode(double radius_x, double radius_y) { + filter_ = std::make_shared(radius_x, radius_y); +} + void ImageFilter::initMatrix(const tonic::Float64List& matrix4, int filterQualityIndex) { auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex); diff --git a/lib/ui/painting/image_filter.h b/lib/ui/painting/image_filter.h index 72d359b8e59b0..101162e779ea6 100644 --- a/lib/ui/painting/image_filter.h +++ b/lib/ui/painting/image_filter.h @@ -30,6 +30,8 @@ class ImageFilter : public RefCountedDartWrappable { static SkFilterMode FilterModeFromIndex(int index); void initBlur(double sigma_x, double sigma_y, SkTileMode tile_mode); + void initDilate(double radius_x, double radius_y); + void initErode(double radius_x, double radius_y); void initMatrix(const tonic::Float64List& matrix4, int filter_quality_index); void initColorFilter(ColorFilter* colorFilter); void initComposeFilter(ImageFilter* outer, ImageFilter* inner); diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index 98d53dda22455..09c249115e193 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -405,6 +405,20 @@ class ImageFilter { return engine.EngineImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); } + // ignore: avoid_unused_constructor_parameters + factory ImageFilter.dilate({ double radiusX = 0.0, double radiusY = 0.0 }) { + // TODO(fzyzcjy): implement dilate. https://github.com/flutter/flutter/issues/101085 + throw UnimplementedError( + 'ImageFilter.dilate not implemented for web platform.'); + } + + // ignore: avoid_unused_constructor_parameters + factory ImageFilter.erode({ double radiusX = 0.0, double radiusY = 0.0 }) { + // TODO(fzyzcjy): implement erode. https://github.com/flutter/flutter/issues/101085 + throw UnimplementedError( + 'ImageFilter.erode not implemented for web platform.'); + } + factory ImageFilter.matrix(Float64List matrix4, {FilterQuality filterQuality = FilterQuality.low}) { if (matrix4.length != 16) throw ArgumentError('"matrix4" must have 16 entries.'); diff --git a/testing/dart/compositing_test.dart b/testing/dart/compositing_test.dart index 72bf643d5721a..2b2b0be433ad6 100644 --- a/testing/dart/compositing_test.dart +++ b/testing/dart/compositing_test.dart @@ -394,6 +394,18 @@ void main() { oldLayer: oldLayer as ImageFilterEngineLayer?, ); }); + testNoSharing((SceneBuilder builder, EngineLayer? oldLayer) { + return builder.pushImageFilter( + ImageFilter.dilate(radiusX: 10.0, radiusY: 10.0), + oldLayer: oldLayer as ImageFilterEngineLayer?, + ); + }); + testNoSharing((SceneBuilder builder, EngineLayer? oldLayer) { + return builder.pushImageFilter( + ImageFilter.erode(radiusX: 10.0, radiusY: 10.0), + oldLayer: oldLayer as ImageFilterEngineLayer?, + ); + }); testNoSharing((SceneBuilder builder, EngineLayer? oldLayer) { return builder.pushImageFilter( ImageFilter.matrix(Float64List.fromList([ diff --git a/testing/dart/image_filter_test.dart b/testing/dart/image_filter_test.dart index 87af30ab6e6af..98435912f8f37 100644 --- a/testing/dart/image_filter_test.dart +++ b/testing/dart/image_filter_test.dart @@ -74,6 +74,12 @@ void main() { ImageFilter makeBlur(double sigmaX, double sigmaY, [TileMode tileMode = TileMode.clamp]) => ImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); + ImageFilter makeDilate(double radiusX, double radiusY) => + ImageFilter.dilate(radiusX: radiusX, radiusY: radiusY); + + ImageFilter makeErode(double radiusX, double radiusY) => + ImageFilter.erode(radiusX: radiusX, radiusY: radiusY); + ImageFilter makeScale(double scX, double scY, [double trX = 0.0, double trY = 0.0, FilterQuality quality = FilterQuality.low]) { @@ -105,6 +111,12 @@ void main() { makeBlur(10.0, 10.0, TileMode.decal), makeBlur(10.0, 20.0), makeBlur(20.0, 20.0), + makeDilate(10.0, 20.0), + makeDilate(20.0, 20.0), + makeDilate(20.0, 10.0), + makeErode(10.0, 20.0), + makeErode(20.0, 20.0), + makeErode(20.0, 10.0), makeScale(10.0, 10.0), makeScale(10.0, 20.0), makeScale(20.0, 10.0), @@ -170,6 +182,24 @@ void main() { checkBytes(bytes, greenCenterBlurred, greenSideBlurred, greenCornerBlurred); }); + test('ImageFilter - dilate', () async { + final Paint paint = Paint() + ..color = green + ..imageFilter = makeDilate(1.0, 1.0); + + final Uint32List bytes = await getBytesForPaint(paint); + checkBytes(bytes, green.value, green.value, green.value); + }); + + test('ImageFilter - erode', () async { + final Paint paint = Paint() + ..color = green + ..imageFilter = makeErode(1.0, 1.0); + + final Uint32List bytes = await getBytesForPaint(paint); + checkBytes(bytes, 0, 0, 0); + }); + test('ImageFilter - matrix', () async { final Paint paint = Paint() ..color = green