mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Change default TileMode for blur ImageFilter objects to null (flutter/engine#55552)
Fixes https://github.com/flutter/flutter/issues/154935 Historically ImageFilter.blur supported setting a TileMode and had a default mode of `clamp`, but few developers actually set the value and the default was not appropriate for some common uses like as a backdrop filter where the clamp mode produces flashing when scrolling high frequency pixel content underneath a blurred title bar. This PR removes the default tile mode instead allowing a null value as the default which will allow the engine to use an appropriate context-dependent default tile mode depending on the action being performed. Typically: - decal for rendering operations and saveLayers and ImageFilterLayer - clamp for image operations - mirror for backdrop filters
This commit is contained in:
parent
728997cd8f
commit
80d757ef56
@ -152,7 +152,8 @@ void SceneBuilder::pushImageFilter(Dart_Handle layer_handle,
|
||||
double dy,
|
||||
const fml::RefPtr<EngineLayer>& old_layer) {
|
||||
auto layer = std::make_shared<flutter::ImageFilterLayer>(
|
||||
image_filter->filter(), SkPoint::Make(SafeNarrow(dx), SafeNarrow(dy)));
|
||||
image_filter->filter(DlTileMode::kDecal),
|
||||
SkPoint::Make(SafeNarrow(dx), SafeNarrow(dy)));
|
||||
PushLayer(layer);
|
||||
EngineLayer::MakeRetained(layer_handle, layer);
|
||||
|
||||
@ -175,7 +176,7 @@ void SceneBuilder::pushBackdropFilter(
|
||||
}
|
||||
|
||||
auto layer = std::make_shared<flutter::BackdropFilterLayer>(
|
||||
filter->filter(), static_cast<DlBlendMode>(blend_mode),
|
||||
filter->filter(DlTileMode::kMirror), static_cast<DlBlendMode>(blend_mode),
|
||||
converted_backdrop_id);
|
||||
PushLayer(layer);
|
||||
EngineLayer::MakeRetained(layer_handle, layer);
|
||||
|
||||
@ -4008,7 +4008,7 @@ abstract class ImageFilter {
|
||||
ImageFilter._(); // ignore: unused_element
|
||||
|
||||
/// Creates an image filter that applies a Gaussian blur.
|
||||
factory ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0, TileMode tileMode = TileMode.clamp }) {
|
||||
factory ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0, TileMode? tileMode }) {
|
||||
return _GaussianBlurImageFilter(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode);
|
||||
}
|
||||
|
||||
@ -4090,7 +4090,7 @@ class _GaussianBlurImageFilter implements ImageFilter {
|
||||
|
||||
final double sigmaX;
|
||||
final double sigmaY;
|
||||
final TileMode tileMode;
|
||||
final TileMode? tileMode;
|
||||
|
||||
// MakeBlurFilter
|
||||
late final _ImageFilter nativeFilter = _ImageFilter.blur(this);
|
||||
@ -4103,6 +4103,7 @@ class _GaussianBlurImageFilter implements ImageFilter {
|
||||
case TileMode.mirror: return 'mirror';
|
||||
case TileMode.repeated: return 'repeated';
|
||||
case TileMode.decal: return 'decal';
|
||||
case null: return 'unspecified';
|
||||
}
|
||||
}
|
||||
|
||||
@ -4228,7 +4229,7 @@ base class _ImageFilter extends NativeFieldWrapperClass1 {
|
||||
_ImageFilter.blur(_GaussianBlurImageFilter filter)
|
||||
: creator = filter {
|
||||
_constructor();
|
||||
_initBlur(filter.sigmaX, filter.sigmaY, filter.tileMode.index);
|
||||
_initBlur(filter.sigmaX, filter.sigmaY, filter.tileMode?.index ?? -1);
|
||||
}
|
||||
|
||||
/// Creates an image filter that dilates each input pixel's channel values
|
||||
|
||||
@ -60,7 +60,8 @@ void Canvas::saveLayerWithoutBounds(Dart_Handle paint_objects,
|
||||
FML_DCHECK(paint.isNotNull());
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
const DlPaint* save_paint = paint.paint(dl_paint, kSaveLayerWithPaintFlags);
|
||||
const DlPaint* save_paint =
|
||||
paint.paint(dl_paint, kSaveLayerWithPaintFlags, DlTileMode::kDecal);
|
||||
FML_DCHECK(save_paint);
|
||||
TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)");
|
||||
builder()->SaveLayer(nullptr, save_paint);
|
||||
@ -80,7 +81,8 @@ void Canvas::saveLayer(double left,
|
||||
SafeNarrow(right), SafeNarrow(bottom));
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
const DlPaint* save_paint = paint.paint(dl_paint, kSaveLayerWithPaintFlags);
|
||||
const DlPaint* save_paint =
|
||||
paint.paint(dl_paint, kSaveLayerWithPaintFlags, DlTileMode::kDecal);
|
||||
FML_DCHECK(save_paint);
|
||||
TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)");
|
||||
builder()->SaveLayer(&bounds, save_paint);
|
||||
@ -229,7 +231,7 @@ void Canvas::drawLine(double x1,
|
||||
FML_DCHECK(paint.isNotNull());
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
paint.paint(dl_paint, kDrawLineFlags);
|
||||
paint.paint(dl_paint, kDrawLineFlags, DlTileMode::kDecal);
|
||||
builder()->DrawLine(SkPoint::Make(SafeNarrow(x1), SafeNarrow(y1)),
|
||||
SkPoint::Make(SafeNarrow(x2), SafeNarrow(y2)),
|
||||
dl_paint);
|
||||
@ -242,7 +244,7 @@ void Canvas::drawPaint(Dart_Handle paint_objects, Dart_Handle paint_data) {
|
||||
FML_DCHECK(paint.isNotNull());
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
paint.paint(dl_paint, kDrawPaintFlags);
|
||||
paint.paint(dl_paint, kDrawPaintFlags, DlTileMode::kClamp);
|
||||
std::shared_ptr<const DlImageFilter> filter = dl_paint.getImageFilter();
|
||||
if (filter && !filter->asColorFilter()) {
|
||||
// drawPaint does an implicit saveLayer if an SkImageFilter is
|
||||
@ -264,7 +266,7 @@ void Canvas::drawRect(double left,
|
||||
FML_DCHECK(paint.isNotNull());
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
paint.paint(dl_paint, kDrawRectFlags);
|
||||
paint.paint(dl_paint, kDrawRectFlags, DlTileMode::kDecal);
|
||||
builder()->DrawRect(SkRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top),
|
||||
SafeNarrow(right), SafeNarrow(bottom)),
|
||||
dl_paint);
|
||||
@ -279,7 +281,7 @@ void Canvas::drawRRect(const RRect& rrect,
|
||||
FML_DCHECK(paint.isNotNull());
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
paint.paint(dl_paint, kDrawRRectFlags);
|
||||
paint.paint(dl_paint, kDrawRRectFlags, DlTileMode::kDecal);
|
||||
builder()->DrawRRect(rrect.sk_rrect, dl_paint);
|
||||
}
|
||||
}
|
||||
@ -293,7 +295,7 @@ void Canvas::drawDRRect(const RRect& outer,
|
||||
FML_DCHECK(paint.isNotNull());
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
paint.paint(dl_paint, kDrawDRRectFlags);
|
||||
paint.paint(dl_paint, kDrawDRRectFlags, DlTileMode::kDecal);
|
||||
builder()->DrawDRRect(outer.sk_rrect, inner.sk_rrect, dl_paint);
|
||||
}
|
||||
}
|
||||
@ -309,7 +311,7 @@ void Canvas::drawOval(double left,
|
||||
FML_DCHECK(paint.isNotNull());
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
paint.paint(dl_paint, kDrawOvalFlags);
|
||||
paint.paint(dl_paint, kDrawOvalFlags, DlTileMode::kDecal);
|
||||
builder()->DrawOval(SkRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top),
|
||||
SafeNarrow(right), SafeNarrow(bottom)),
|
||||
dl_paint);
|
||||
@ -326,7 +328,7 @@ void Canvas::drawCircle(double x,
|
||||
FML_DCHECK(paint.isNotNull());
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
paint.paint(dl_paint, kDrawCircleFlags);
|
||||
paint.paint(dl_paint, kDrawCircleFlags, DlTileMode::kDecal);
|
||||
builder()->DrawCircle(SkPoint::Make(SafeNarrow(x), SafeNarrow(y)),
|
||||
SafeNarrow(radius), dl_paint);
|
||||
}
|
||||
@ -346,9 +348,9 @@ void Canvas::drawArc(double left,
|
||||
FML_DCHECK(paint.isNotNull());
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
paint.paint(dl_paint, useCenter //
|
||||
? kDrawArcWithCenterFlags
|
||||
: kDrawArcNoCenterFlags);
|
||||
paint.paint(dl_paint,
|
||||
useCenter ? kDrawArcWithCenterFlags : kDrawArcNoCenterFlags,
|
||||
DlTileMode::kDecal);
|
||||
builder()->DrawArc(
|
||||
SkRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top), SafeNarrow(right),
|
||||
SafeNarrow(bottom)),
|
||||
@ -371,7 +373,7 @@ void Canvas::drawPath(const CanvasPath* path,
|
||||
}
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
paint.paint(dl_paint, kDrawPathFlags);
|
||||
paint.paint(dl_paint, kDrawPathFlags, DlTileMode::kDecal);
|
||||
builder()->DrawPath(path->path(), dl_paint);
|
||||
}
|
||||
}
|
||||
@ -401,7 +403,8 @@ Dart_Handle Canvas::drawImage(const CanvasImage* image,
|
||||
auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
const DlPaint* opt_paint = paint.paint(dl_paint, kDrawImageWithPaintFlags);
|
||||
const DlPaint* opt_paint =
|
||||
paint.paint(dl_paint, kDrawImageWithPaintFlags, DlTileMode::kClamp);
|
||||
builder()->DrawImage(dl_image, SkPoint::Make(SafeNarrow(x), SafeNarrow(y)),
|
||||
sampling, opt_paint);
|
||||
}
|
||||
@ -444,7 +447,7 @@ Dart_Handle Canvas::drawImageRect(const CanvasImage* image,
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
const DlPaint* opt_paint =
|
||||
paint.paint(dl_paint, kDrawImageRectWithPaintFlags);
|
||||
paint.paint(dl_paint, kDrawImageRectWithPaintFlags, DlTileMode::kClamp);
|
||||
builder()->DrawImageRect(dl_image, src, dst, sampling, opt_paint,
|
||||
DlCanvas::SrcRectConstraint::kFast);
|
||||
}
|
||||
@ -489,7 +492,7 @@ Dart_Handle Canvas::drawImageNine(const CanvasImage* image,
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
const DlPaint* opt_paint =
|
||||
paint.paint(dl_paint, kDrawImageNineWithPaintFlags);
|
||||
paint.paint(dl_paint, kDrawImageNineWithPaintFlags, DlTileMode::kClamp);
|
||||
builder()->DrawImageNine(dl_image, icenter, dst, filter, opt_paint);
|
||||
}
|
||||
return Dart_Null();
|
||||
@ -524,13 +527,13 @@ void Canvas::drawPoints(Dart_Handle paint_objects,
|
||||
DlPaint dl_paint;
|
||||
switch (point_mode) {
|
||||
case DlCanvas::PointMode::kPoints:
|
||||
paint.paint(dl_paint, kDrawPointsAsPointsFlags);
|
||||
paint.paint(dl_paint, kDrawPointsAsPointsFlags, DlTileMode::kDecal);
|
||||
break;
|
||||
case DlCanvas::PointMode::kLines:
|
||||
paint.paint(dl_paint, kDrawPointsAsLinesFlags);
|
||||
paint.paint(dl_paint, kDrawPointsAsLinesFlags, DlTileMode::kDecal);
|
||||
break;
|
||||
case DlCanvas::PointMode::kPolygon:
|
||||
paint.paint(dl_paint, kDrawPointsAsPolygonFlags);
|
||||
paint.paint(dl_paint, kDrawPointsAsPolygonFlags, DlTileMode::kDecal);
|
||||
break;
|
||||
}
|
||||
builder()->DrawPoints(point_mode,
|
||||
@ -554,7 +557,7 @@ void Canvas::drawVertices(const Vertices* vertices,
|
||||
FML_DCHECK(paint.isNotNull());
|
||||
if (display_list_builder_) {
|
||||
DlPaint dl_paint;
|
||||
paint.paint(dl_paint, kDrawVerticesFlags);
|
||||
paint.paint(dl_paint, kDrawVerticesFlags, DlTileMode::kDecal);
|
||||
builder()->DrawVertices(vertices->vertices(), blend_mode, dl_paint);
|
||||
}
|
||||
}
|
||||
@ -603,7 +606,8 @@ Dart_Handle Canvas::drawAtlas(Dart_Handle paint_objects,
|
||||
}
|
||||
|
||||
DlPaint dl_paint;
|
||||
const DlPaint* opt_paint = paint.paint(dl_paint, kDrawAtlasWithPaintFlags);
|
||||
const DlPaint* opt_paint =
|
||||
paint.paint(dl_paint, kDrawAtlasWithPaintFlags, DlTileMode::kClamp);
|
||||
builder()->DrawAtlas(
|
||||
dl_image, reinterpret_cast<const SkRSXform*>(transforms.data()),
|
||||
reinterpret_cast<const SkRect*>(rects.data()), dl_color.data(),
|
||||
|
||||
@ -51,37 +51,69 @@ ImageFilter::ImageFilter() {}
|
||||
|
||||
ImageFilter::~ImageFilter() {}
|
||||
|
||||
const std::shared_ptr<const DlImageFilter> ImageFilter::filter(
|
||||
DlTileMode mode) const {
|
||||
if (is_dynamic_tile_mode_) {
|
||||
FML_DCHECK(filter_.get() != nullptr);
|
||||
const DlBlurImageFilter* blur_filter = filter_->asBlur();
|
||||
FML_DCHECK(blur_filter != nullptr);
|
||||
if (blur_filter->tile_mode() != mode) {
|
||||
return DlBlurImageFilter::Make(blur_filter->sigma_x(),
|
||||
blur_filter->sigma_y(), mode);
|
||||
}
|
||||
}
|
||||
return filter_;
|
||||
}
|
||||
|
||||
void ImageFilter::initBlur(double sigma_x,
|
||||
double sigma_y,
|
||||
DlTileMode tile_mode) {
|
||||
int tile_mode_index) {
|
||||
DlTileMode tile_mode;
|
||||
bool is_dynamic;
|
||||
if (tile_mode_index < 0) {
|
||||
is_dynamic = true;
|
||||
tile_mode = DlTileMode::kClamp;
|
||||
} else {
|
||||
is_dynamic = false;
|
||||
tile_mode = static_cast<DlTileMode>(tile_mode_index);
|
||||
}
|
||||
filter_ = DlBlurImageFilter::Make(SafeNarrow(sigma_x), SafeNarrow(sigma_y),
|
||||
tile_mode);
|
||||
// If it was a NOP filter, don't bother processing dynamic substitutions
|
||||
// (They'd fail the FML_DCHECK anyway)
|
||||
is_dynamic_tile_mode_ = is_dynamic && filter_;
|
||||
}
|
||||
|
||||
void ImageFilter::initDilate(double radius_x, double radius_y) {
|
||||
is_dynamic_tile_mode_ = false;
|
||||
filter_ =
|
||||
DlDilateImageFilter::Make(SafeNarrow(radius_x), SafeNarrow(radius_y));
|
||||
}
|
||||
|
||||
void ImageFilter::initErode(double radius_x, double radius_y) {
|
||||
is_dynamic_tile_mode_ = false;
|
||||
filter_ =
|
||||
DlErodeImageFilter::Make(SafeNarrow(radius_x), SafeNarrow(radius_y));
|
||||
}
|
||||
|
||||
void ImageFilter::initMatrix(const tonic::Float64List& matrix4,
|
||||
int filterQualityIndex) {
|
||||
is_dynamic_tile_mode_ = false;
|
||||
auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);
|
||||
filter_ = DlMatrixImageFilter::Make(ToSkMatrix(matrix4), sampling);
|
||||
}
|
||||
|
||||
void ImageFilter::initColorFilter(ColorFilter* colorFilter) {
|
||||
FML_DCHECK(colorFilter);
|
||||
is_dynamic_tile_mode_ = false;
|
||||
filter_ = DlColorFilterImageFilter::Make(colorFilter->filter());
|
||||
}
|
||||
|
||||
void ImageFilter::initComposeFilter(ImageFilter* outer, ImageFilter* inner) {
|
||||
FML_DCHECK(outer && inner);
|
||||
filter_ = DlComposeImageFilter::Make(outer->filter(), inner->filter());
|
||||
is_dynamic_tile_mode_ = false;
|
||||
filter_ = DlComposeImageFilter::Make(outer->filter(DlTileMode::kClamp),
|
||||
inner->filter(DlTileMode::kClamp));
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@ -28,14 +28,14 @@ class ImageFilter : public RefCountedDartWrappable<ImageFilter> {
|
||||
static DlImageSampling SamplingFromIndex(int filterQualityIndex);
|
||||
static DlFilterMode FilterModeFromIndex(int index);
|
||||
|
||||
void initBlur(double sigma_x, double sigma_y, DlTileMode tile_mode);
|
||||
void initBlur(double sigma_x, double sigma_y, int tile_mode_index);
|
||||
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);
|
||||
|
||||
const std::shared_ptr<const DlImageFilter> filter() const { return filter_; }
|
||||
const std::shared_ptr<const DlImageFilter> filter(DlTileMode mode) const;
|
||||
|
||||
static void RegisterNatives(tonic::DartLibraryNatives* natives);
|
||||
|
||||
@ -43,6 +43,7 @@ class ImageFilter : public RefCountedDartWrappable<ImageFilter> {
|
||||
ImageFilter();
|
||||
|
||||
std::shared_ptr<const DlImageFilter> filter_;
|
||||
bool is_dynamic_tile_mode_ = false;
|
||||
};
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@ -83,7 +83,8 @@ Paint::Paint(Dart_Handle paint_objects, Dart_Handle paint_data)
|
||||
: paint_objects_(paint_objects), paint_data_(paint_data) {}
|
||||
|
||||
const DlPaint* Paint::paint(DlPaint& paint,
|
||||
const DisplayListAttributeFlags& flags) const {
|
||||
const DisplayListAttributeFlags& flags,
|
||||
DlTileMode tile_mode) const {
|
||||
if (isNull()) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -148,7 +149,7 @@ const DlPaint* Paint::paint(DlPaint& paint,
|
||||
} else {
|
||||
ImageFilter* decoded =
|
||||
tonic::DartConverter<ImageFilter*>::FromDart(image_filter);
|
||||
paint.setImageFilter(decoded->filter());
|
||||
paint.setImageFilter(decoded->filter(tile_mode));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -208,7 +209,7 @@ const DlPaint* Paint::paint(DlPaint& paint,
|
||||
return &paint;
|
||||
}
|
||||
|
||||
void Paint::toDlPaint(DlPaint& paint) const {
|
||||
void Paint::toDlPaint(DlPaint& paint, DlTileMode tile_mode) const {
|
||||
if (isNull()) {
|
||||
return;
|
||||
}
|
||||
@ -252,7 +253,7 @@ void Paint::toDlPaint(DlPaint& paint) const {
|
||||
if (!Dart_IsNull(image_filter)) {
|
||||
ImageFilter* decoded =
|
||||
tonic::DartConverter<ImageFilter*>::FromDart(image_filter);
|
||||
paint.setImageFilter(decoded->filter());
|
||||
paint.setImageFilter(decoded->filter(tile_mode));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -18,9 +18,10 @@ class Paint {
|
||||
Paint(Dart_Handle paint_objects, Dart_Handle paint_data);
|
||||
|
||||
const DlPaint* paint(DlPaint& paint,
|
||||
const DisplayListAttributeFlags& flags) const;
|
||||
const DisplayListAttributeFlags& flags,
|
||||
DlTileMode tile_mode) const;
|
||||
|
||||
void toDlPaint(DlPaint& paint) const;
|
||||
void toDlPaint(DlPaint& paint, DlTileMode tile_mode) const;
|
||||
|
||||
bool isNull() const { return Dart_IsNull(paint_data_); }
|
||||
bool isNotNull() const { return !Dart_IsNull(paint_data_); }
|
||||
|
||||
@ -21,7 +21,7 @@ TEST_F(ShellTest, ConvertPaintToDlPaint) {
|
||||
Dart_GetField(dart_paint, tonic::ToDart("_objects"));
|
||||
Dart_Handle paint_data = Dart_GetField(dart_paint, tonic::ToDart("_data"));
|
||||
Paint ui_paint(paint_objects, paint_data);
|
||||
ui_paint.toDlPaint(dl_paint);
|
||||
ui_paint.toDlPaint(dl_paint, DlTileMode::kClamp);
|
||||
message_latch->Signal();
|
||||
};
|
||||
|
||||
|
||||
@ -458,7 +458,7 @@ void ParagraphBuilder::pushStyle(const tonic::Int32List& encoded,
|
||||
Paint background(background_objects, background_data);
|
||||
if (background.isNotNull()) {
|
||||
DlPaint dl_paint;
|
||||
background.toDlPaint(dl_paint);
|
||||
background.toDlPaint(dl_paint, DlTileMode::kDecal);
|
||||
style.background = dl_paint;
|
||||
}
|
||||
}
|
||||
@ -467,7 +467,7 @@ void ParagraphBuilder::pushStyle(const tonic::Int32List& encoded,
|
||||
Paint foreground(foreground_objects, foreground_data);
|
||||
if (foreground.isNotNull()) {
|
||||
DlPaint dl_paint;
|
||||
foreground.toDlPaint(dl_paint);
|
||||
foreground.toDlPaint(dl_paint, DlTileMode::kDecal);
|
||||
style.foreground = dl_paint;
|
||||
}
|
||||
}
|
||||
|
||||
@ -591,7 +591,7 @@ class ImageFilter {
|
||||
factory ImageFilter.blur({
|
||||
double sigmaX = 0.0,
|
||||
double sigmaY = 0.0,
|
||||
TileMode tileMode = TileMode.clamp
|
||||
TileMode? tileMode
|
||||
}) => engine.renderer.createBlurImageFilter(
|
||||
sigmaX: sigmaX,
|
||||
sigmaY: sigmaY,
|
||||
|
||||
@ -101,7 +101,7 @@ class CkCanvas {
|
||||
Uint32List? colors,
|
||||
ui.BlendMode blendMode,
|
||||
) {
|
||||
final skPaint = paint.toSkPaint();
|
||||
final skPaint = paint.toSkPaint(defaultBlurTileMode: ui.TileMode.clamp);
|
||||
skCanvas.drawAtlas(
|
||||
atlas.skImage,
|
||||
rects,
|
||||
@ -143,7 +143,7 @@ class CkCanvas {
|
||||
|
||||
void drawImage(CkImage image, ui.Offset offset, CkPaint paint) {
|
||||
final ui.FilterQuality filterQuality = paint.filterQuality;
|
||||
final skPaint = paint.toSkPaint();
|
||||
final skPaint = paint.toSkPaint(defaultBlurTileMode: ui.TileMode.clamp);
|
||||
if (filterQuality == ui.FilterQuality.high) {
|
||||
skCanvas.drawImageCubic(
|
||||
image.skImage,
|
||||
@ -168,7 +168,7 @@ class CkCanvas {
|
||||
|
||||
void drawImageRect(CkImage image, ui.Rect src, ui.Rect dst, CkPaint paint) {
|
||||
final ui.FilterQuality filterQuality = paint.filterQuality;
|
||||
final skPaint = paint.toSkPaint();
|
||||
final skPaint = paint.toSkPaint(defaultBlurTileMode: ui.TileMode.clamp);
|
||||
if (filterQuality == ui.FilterQuality.high) {
|
||||
skCanvas.drawImageRectCubic(
|
||||
image.skImage,
|
||||
@ -193,7 +193,7 @@ class CkCanvas {
|
||||
|
||||
void drawImageNine(
|
||||
CkImage image, ui.Rect center, ui.Rect dst, CkPaint paint) {
|
||||
final skPaint = paint.toSkPaint();
|
||||
final skPaint = paint.toSkPaint(defaultBlurTileMode: ui.TileMode.clamp);
|
||||
skCanvas.drawImageNine(
|
||||
image.skImage,
|
||||
toSkRect(center),
|
||||
@ -315,13 +315,14 @@ class CkCanvas {
|
||||
toSkRect(bounds),
|
||||
null,
|
||||
null,
|
||||
canvasKit.TileMode.Clamp,
|
||||
);
|
||||
skPaint?.delete();
|
||||
}
|
||||
|
||||
void saveLayerWithoutBounds(CkPaint? paint) {
|
||||
final skPaint = paint?.toSkPaint();
|
||||
skCanvas.saveLayer(skPaint, null, null, null);
|
||||
skCanvas.saveLayer(skPaint, null, null, null, canvasKit.TileMode.Clamp);
|
||||
skPaint?.delete();
|
||||
}
|
||||
|
||||
@ -333,16 +334,26 @@ class CkCanvas {
|
||||
} else {
|
||||
convertible = filter as CkManagedSkImageFilterConvertible;
|
||||
}
|
||||
// There are 2 ImageFilter objects applied here. The filter in the paint
|
||||
// object is applied to the contents and its default tile mode is decal
|
||||
// (automatically applied by toSkPaint).
|
||||
// The filter supplied as an argument to this function [convertible] will
|
||||
// be applied to the backdrop and its default tile mode will be mirror.
|
||||
// We also pass in the blur tile mode as an argument to saveLayer because
|
||||
// that operation will not adopt the tile mode from the backdrop filter
|
||||
// and instead needs it supplied to the saveLayer call itself as a
|
||||
// separate argument.
|
||||
convertible.withSkImageFilter((SkImageFilter filter) {
|
||||
final skPaint = paint?.toSkPaint();
|
||||
final skPaint = paint?.toSkPaint(/*ui.TileMode.decal*/);
|
||||
skCanvas.saveLayer(
|
||||
skPaint,
|
||||
toSkRect(bounds),
|
||||
filter,
|
||||
0,
|
||||
toSkTileMode(convertible.backdropTileMode ?? ui.TileMode.mirror),
|
||||
);
|
||||
skPaint?.delete();
|
||||
});
|
||||
}, defaultBlurTileMode: ui.TileMode.mirror);
|
||||
}
|
||||
|
||||
void scale(double sx, double sy) {
|
||||
|
||||
@ -987,8 +987,8 @@ final List<SkTileMode> _skTileModes = <SkTileMode>[
|
||||
canvasKit.TileMode.Decal,
|
||||
];
|
||||
|
||||
SkTileMode toSkTileMode(ui.TileMode mode) {
|
||||
return _skTileModes[mode.index];
|
||||
SkTileMode toSkTileMode(ui.TileMode? mode) {
|
||||
return mode == null ? canvasKit.TileMode.Clamp : _skTileModes[mode.index];
|
||||
}
|
||||
|
||||
@JS()
|
||||
@ -2579,13 +2579,15 @@ extension SkCanvasExtension on SkCanvas {
|
||||
JSFloat32Array? bounds,
|
||||
SkImageFilter? backdrop,
|
||||
JSNumber? flags,
|
||||
SkTileMode backdropTileMode,
|
||||
);
|
||||
void saveLayer(
|
||||
SkPaint? paint,
|
||||
Float32List? bounds,
|
||||
SkImageFilter? backdrop,
|
||||
int? flags,
|
||||
) => _saveLayer(paint, bounds?.toJS, backdrop, flags?.toJS);
|
||||
SkTileMode backdropTileMode,
|
||||
) => _saveLayer(paint, bounds?.toJS, backdrop, flags?.toJS, backdropTileMode);
|
||||
|
||||
external JSVoid restore();
|
||||
|
||||
|
||||
@ -71,7 +71,9 @@ abstract class CkColorFilter implements CkManagedSkImageFilterConvertible {
|
||||
SkColorFilter _initRawColorFilter();
|
||||
|
||||
@override
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow) {
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
// Since ColorFilter has a const constructor it cannot store dynamically
|
||||
// created Skia objects. Therefore a new SkImageFilter is created every time
|
||||
// it's used. However, once used it's no longer needed, so it's deleted
|
||||
@ -81,6 +83,13 @@ abstract class CkColorFilter implements CkManagedSkImageFilterConvertible {
|
||||
skImageFilter.delete();
|
||||
}
|
||||
|
||||
/// The blur ImageFilter will override this and return the necessary
|
||||
/// value to hand to the saveLayer call. It is the only filter type that
|
||||
/// needs to pass along a tile mode so we just return a default value of
|
||||
/// clamp for color filters.
|
||||
@override
|
||||
ui.TileMode? get backdropTileMode => ui.TileMode.clamp;
|
||||
|
||||
@override
|
||||
Matrix4 get transform => Matrix4.identity();
|
||||
}
|
||||
|
||||
@ -24,9 +24,16 @@ abstract class CkManagedSkImageFilterConvertible implements ui.ImageFilter {
|
||||
/// Creates a temporary [SkImageFilter], passes it to [borrow], and then
|
||||
/// immediately deletes it.
|
||||
///
|
||||
/// If (and only if) the filter is a blur ImageFilter, then the indicated
|
||||
/// [defaultBlurTileMode] is used in place of a missing (null) tile mode.
|
||||
///
|
||||
/// [SkImageFilter] objects are not kept around so that their memory is
|
||||
/// reclaimed immediately, rather than waiting for the GC cycle.
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow);
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
});
|
||||
|
||||
ui.TileMode? get backdropTileMode;
|
||||
|
||||
Matrix4 get transform;
|
||||
}
|
||||
@ -38,7 +45,7 @@ abstract class CkImageFilter implements CkManagedSkImageFilterConvertible {
|
||||
factory CkImageFilter.blur(
|
||||
{required double sigmaX,
|
||||
required double sigmaY,
|
||||
required ui.TileMode tileMode}) = _CkBlurImageFilter;
|
||||
required ui.TileMode? tileMode}) = _CkBlurImageFilter;
|
||||
factory CkImageFilter.color({required CkColorFilter colorFilter}) =
|
||||
CkColorFilterImageFilter;
|
||||
factory CkImageFilter.matrix(
|
||||
@ -55,6 +62,13 @@ abstract class CkImageFilter implements CkManagedSkImageFilterConvertible {
|
||||
|
||||
CkImageFilter._();
|
||||
|
||||
// The blur ImageFilter will override this and return the necessary
|
||||
// value to hand to the saveLayer call. It is the only filter type that
|
||||
// needs to pass along a tile mode so we just return a default value of
|
||||
// clamp for all other image filters.
|
||||
@override
|
||||
ui.TileMode? get backdropTileMode => ui.TileMode.clamp;
|
||||
|
||||
@override
|
||||
Matrix4 get transform => Matrix4.identity();
|
||||
}
|
||||
@ -65,7 +79,9 @@ class CkColorFilterImageFilter extends CkImageFilter {
|
||||
final CkColorFilter colorFilter;
|
||||
|
||||
@override
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow) {
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
final skImageFilter = colorFilter.initRawImageFilter();
|
||||
borrow(skImageFilter);
|
||||
skImageFilter.delete();
|
||||
@ -94,10 +110,15 @@ class _CkBlurImageFilter extends CkImageFilter {
|
||||
|
||||
final double sigmaX;
|
||||
final double sigmaY;
|
||||
final ui.TileMode tileMode;
|
||||
final ui.TileMode? tileMode;
|
||||
|
||||
@override
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow) {
|
||||
ui.TileMode? get backdropTileMode => tileMode;
|
||||
|
||||
@override
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
/// Return the identity matrix when both sigmaX and sigmaY are 0. Replicates
|
||||
/// effect of applying no filter
|
||||
final SkImageFilter skImageFilter;
|
||||
@ -110,7 +131,7 @@ class _CkBlurImageFilter extends CkImageFilter {
|
||||
skImageFilter = canvasKit.ImageFilter.MakeBlur(
|
||||
sigmaX,
|
||||
sigmaY,
|
||||
toSkTileMode(tileMode),
|
||||
toSkTileMode(tileMode ?? defaultBlurTileMode),
|
||||
null,
|
||||
);
|
||||
}
|
||||
@ -151,7 +172,9 @@ class _CkMatrixImageFilter extends CkImageFilter {
|
||||
final Matrix4 _transform;
|
||||
|
||||
@override
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow) {
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
final skImageFilter =
|
||||
canvasKit.ImageFilter.MakeMatrixTransform(
|
||||
toSkMatrixFromFloat64(matrix),
|
||||
@ -190,7 +213,9 @@ class _CkDilateImageFilter extends CkImageFilter {
|
||||
final double radiusY;
|
||||
|
||||
@override
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow) {
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
final skImageFilter = canvasKit.ImageFilter.MakeDilate(
|
||||
radiusX,
|
||||
radiusY,
|
||||
@ -227,7 +252,9 @@ class _CkErodeImageFilter extends CkImageFilter {
|
||||
final double radiusY;
|
||||
|
||||
@override
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow) {
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
final skImageFilter = canvasKit.ImageFilter.MakeErode(
|
||||
radiusX,
|
||||
radiusY,
|
||||
@ -264,7 +291,9 @@ class _CkComposeImageFilter extends CkImageFilter {
|
||||
final CkImageFilter inner;
|
||||
|
||||
@override
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow) {
|
||||
void withSkImageFilter(SkImageFilterBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
outer.withSkImageFilter((skOuter) {
|
||||
inner.withSkImageFilter((skInner) {
|
||||
final skImageFilter = canvasKit.ImageFilter.MakeCompose(
|
||||
@ -273,8 +302,8 @@ class _CkComposeImageFilter extends CkImageFilter {
|
||||
);
|
||||
borrow(skImageFilter);
|
||||
skImageFilter.delete();
|
||||
});
|
||||
});
|
||||
}, defaultBlurTileMode: defaultBlurTileMode);
|
||||
}, defaultBlurTileMode: defaultBlurTileMode);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -402,7 +402,7 @@ class MeasureVisitor extends LayerVisitor {
|
||||
transformedBounds = rectFromSkIRect(
|
||||
skFilter.getOutputBounds(toSkRect(transformedBounds)),
|
||||
);
|
||||
});
|
||||
}, defaultBlurTileMode: ui.TileMode.decal);
|
||||
}
|
||||
picture.sceneBounds = transformedBounds;
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ class CkPaint implements ui.Paint {
|
||||
///
|
||||
/// The caller is responsible for deleting the returned object when it's no
|
||||
/// longer needed.
|
||||
SkPaint toSkPaint() {
|
||||
SkPaint toSkPaint({ui.TileMode defaultBlurTileMode = ui.TileMode.decal}) {
|
||||
final skPaint = SkPaint();
|
||||
skPaint.setAntiAlias(isAntiAlias);
|
||||
skPaint.setBlendMode(toSkBlendMode(blendMode));
|
||||
@ -65,7 +65,7 @@ class CkPaint implements ui.Paint {
|
||||
if (localImageFilter != null) {
|
||||
localImageFilter.withSkImageFilter((skImageFilter) {
|
||||
skPaint.setImageFilter(skImageFilter);
|
||||
});
|
||||
}, defaultBlurTileMode: defaultBlurTileMode);
|
||||
}
|
||||
|
||||
return skPaint;
|
||||
|
||||
@ -184,7 +184,7 @@ class CanvasKitRenderer implements Renderer {
|
||||
ui.ImageFilter createBlurImageFilter(
|
||||
{double sigmaX = 0.0,
|
||||
double sigmaY = 0.0,
|
||||
ui.TileMode tileMode = ui.TileMode.clamp}) =>
|
||||
ui.TileMode? tileMode}) =>
|
||||
CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode);
|
||||
|
||||
@override
|
||||
|
||||
@ -111,7 +111,7 @@ class HtmlRenderer implements Renderer {
|
||||
ui.ImageFilter createBlurImageFilter(
|
||||
{double sigmaX = 0.0,
|
||||
double sigmaY = 0.0,
|
||||
ui.TileMode tileMode = ui.TileMode.clamp}) =>
|
||||
ui.TileMode? tileMode}) =>
|
||||
EngineImageFilter.blur(
|
||||
sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode);
|
||||
|
||||
|
||||
@ -717,7 +717,7 @@ abstract class EngineImageFilter implements ui.ImageFilter {
|
||||
factory EngineImageFilter.blur({
|
||||
required double sigmaX,
|
||||
required double sigmaY,
|
||||
required ui.TileMode tileMode,
|
||||
required ui.TileMode? tileMode,
|
||||
}) = _BlurEngineImageFilter;
|
||||
|
||||
factory EngineImageFilter.matrix({
|
||||
@ -732,11 +732,11 @@ abstract class EngineImageFilter implements ui.ImageFilter {
|
||||
}
|
||||
|
||||
class _BlurEngineImageFilter extends EngineImageFilter {
|
||||
_BlurEngineImageFilter({ this.sigmaX = 0.0, this.sigmaY = 0.0, this.tileMode = ui.TileMode.clamp }) : super._();
|
||||
_BlurEngineImageFilter({ this.sigmaX = 0.0, this.sigmaY = 0.0, this.tileMode }) : super._();
|
||||
|
||||
final double sigmaX;
|
||||
final double sigmaY;
|
||||
final ui.TileMode tileMode;
|
||||
final ui.TileMode? tileMode;
|
||||
|
||||
// TODO(ferhat): implement TileMode.
|
||||
@override
|
||||
|
||||
@ -120,7 +120,7 @@ abstract class Renderer {
|
||||
ui.ImageFilter createBlurImageFilter({
|
||||
double sigmaX = 0.0,
|
||||
double sigmaY = 0.0,
|
||||
ui.TileMode tileMode = ui.TileMode.clamp});
|
||||
ui.TileMode? tileMode});
|
||||
ui.ImageFilter createDilateImageFilter({ double radiusX = 0.0, double radiusY = 0.0});
|
||||
ui.ImageFilter createErodeImageFilter({ double radiusX = 0.0, double radiusY = 0.0});
|
||||
ui.ImageFilter createMatrixImageFilter(
|
||||
|
||||
@ -32,28 +32,41 @@ class SkwasmCanvas implements SceneCanvas {
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint();
|
||||
if (bounds != null) {
|
||||
withStackScope((StackScope s) {
|
||||
canvasSaveLayer(_handle, s.convertRectToNative(bounds), paintHandle, nullptr);
|
||||
canvasSaveLayer(_handle, s.convertRectToNative(bounds), paintHandle, nullptr,
|
||||
ui.TileMode.clamp.index);
|
||||
});
|
||||
} else {
|
||||
canvasSaveLayer(_handle, nullptr, paintHandle, nullptr);
|
||||
canvasSaveLayer(_handle, nullptr, paintHandle, nullptr, ui.TileMode.clamp.index);
|
||||
}
|
||||
paintDispose(paintHandle);
|
||||
}
|
||||
|
||||
@override
|
||||
void saveLayerWithFilter(ui.Rect? bounds, ui.Paint paint, ui.ImageFilter imageFilter) {
|
||||
// There are 2 ImageFilter objects applied here. The filter in the paint
|
||||
// object is applied to the contents and its default tile mode is decal
|
||||
// (automatically applied by toSkPaint).
|
||||
// The filter supplied as an argument to this function [nativeFilter] will
|
||||
// be applied to the backdrop and its default tile mode will be mirror.
|
||||
// We also pass in the blur tile mode as an argument to saveLayer because
|
||||
// that operation will not adopt the tile mode from the backdrop filter
|
||||
// and instead needs it supplied to the saveLayer call itself as a
|
||||
// separate argument.
|
||||
final SkwasmImageFilter nativeFilter = SkwasmImageFilter.fromUiFilter(imageFilter);
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint();
|
||||
final ui.TileMode? backdropTileMode = nativeFilter.backdropTileMode;
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint(/*ui.TileMode.decal*/);
|
||||
if (bounds != null) {
|
||||
withStackScope((StackScope s) {
|
||||
nativeFilter.withRawImageFilter((nativeFilterHandle) {
|
||||
canvasSaveLayer(_handle, s.convertRectToNative(bounds), paintHandle, nativeFilterHandle);
|
||||
});
|
||||
canvasSaveLayer(_handle, s.convertRectToNative(bounds), paintHandle, nativeFilterHandle,
|
||||
(backdropTileMode ?? ui.TileMode.mirror).index);
|
||||
}, defaultBlurTileMode: ui.TileMode.mirror);
|
||||
});
|
||||
} else {
|
||||
nativeFilter.withRawImageFilter((nativeFilterHandle) {
|
||||
canvasSaveLayer(_handle, nullptr, paintHandle, nativeFilterHandle);
|
||||
});
|
||||
canvasSaveLayer(_handle, nullptr, paintHandle, nativeFilterHandle,
|
||||
(backdropTileMode ?? ui.TileMode.mirror).index);
|
||||
}, defaultBlurTileMode: ui.TileMode.mirror);
|
||||
}
|
||||
paintDispose(paintHandle);
|
||||
}
|
||||
@ -212,7 +225,9 @@ class SkwasmCanvas implements SceneCanvas {
|
||||
|
||||
@override
|
||||
void drawImage(ui.Image image, ui.Offset offset, ui.Paint paint) {
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint();
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint(
|
||||
defaultBlurTileMode: ui.TileMode.clamp,
|
||||
);
|
||||
canvasDrawImage(
|
||||
_handle,
|
||||
(image as SkwasmImage).handle,
|
||||
@ -234,7 +249,9 @@ class SkwasmCanvas implements SceneCanvas {
|
||||
withStackScope((StackScope scope) {
|
||||
final Pointer<Float> sourceRect = scope.convertRectToNative(src);
|
||||
final Pointer<Float> destRect = scope.convertRectToNative(dst);
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint();
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint(
|
||||
defaultBlurTileMode: ui.TileMode.clamp,
|
||||
);
|
||||
canvasDrawImageRect(
|
||||
_handle,
|
||||
(image as SkwasmImage).handle,
|
||||
@ -257,7 +274,9 @@ class SkwasmCanvas implements SceneCanvas {
|
||||
withStackScope((StackScope scope) {
|
||||
final Pointer<Int32> centerRect = scope.convertIRectToNative(center);
|
||||
final Pointer<Float> destRect = scope.convertRectToNative(dst);
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint();
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint(
|
||||
defaultBlurTileMode: ui.TileMode.clamp,
|
||||
);
|
||||
canvasDrawImageNine(
|
||||
_handle,
|
||||
(image as SkwasmImage).handle,
|
||||
@ -355,7 +374,9 @@ class SkwasmCanvas implements SceneCanvas {
|
||||
final RawRect rawCullRect = cullRect != null
|
||||
? scope.convertRectToNative(cullRect)
|
||||
: nullptr;
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint();
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint(
|
||||
defaultBlurTileMode: ui.TileMode.clamp,
|
||||
);
|
||||
canvasDrawAtlas(
|
||||
_handle,
|
||||
(atlas as SkwasmImage).handle,
|
||||
@ -388,7 +409,9 @@ class SkwasmCanvas implements SceneCanvas {
|
||||
final RawRect rawCullRect = cullRect != null
|
||||
? scope.convertRectToNative(cullRect)
|
||||
: nullptr;
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint();
|
||||
final paintHandle = (paint as SkwasmPaint).toRawPaint(
|
||||
defaultBlurTileMode: ui.TileMode.clamp,
|
||||
);
|
||||
canvasDrawAtlas(
|
||||
_handle,
|
||||
(atlas as SkwasmImage).handle,
|
||||
|
||||
@ -16,7 +16,7 @@ abstract class SkwasmImageFilter implements SceneImageFilter {
|
||||
factory SkwasmImageFilter.blur({
|
||||
double sigmaX = 0.0,
|
||||
double sigmaY = 0.0,
|
||||
ui.TileMode tileMode = ui.TileMode.clamp,
|
||||
ui.TileMode? tileMode,
|
||||
}) => SkwasmBlurFilter(sigmaX, sigmaY, tileMode);
|
||||
|
||||
factory SkwasmImageFilter.dilate({
|
||||
@ -58,9 +58,16 @@ abstract class SkwasmImageFilter implements SceneImageFilter {
|
||||
/// Creates a temporary [ImageFilterHandle] and passes it to the [borrow]
|
||||
/// function.
|
||||
///
|
||||
/// If (and only if) the filter is a blur ImageFilter, then the indicated
|
||||
/// [defaultBlurTileMode] is used in place of a missing (null) tile mode.
|
||||
///
|
||||
/// The handle is deleted immediately after [borrow] returns. The [borrow]
|
||||
/// function must not store the handle to avoid dangling pointer bugs.
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow);
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
});
|
||||
|
||||
ui.TileMode? get backdropTileMode => ui.TileMode.clamp;
|
||||
|
||||
@override
|
||||
ui.Rect filterBounds(ui.Rect inputBounds) => withStackScope((StackScope scope) {
|
||||
@ -77,11 +84,16 @@ class SkwasmBlurFilter extends SkwasmImageFilter {
|
||||
|
||||
final double sigmaX;
|
||||
final double sigmaY;
|
||||
final ui.TileMode tileMode;
|
||||
final ui.TileMode? tileMode;
|
||||
|
||||
@override
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow) {
|
||||
final rawImageFilter = imageFilterCreateBlur(sigmaX, sigmaY, tileMode.index);
|
||||
ui.TileMode? get backdropTileMode => tileMode;
|
||||
|
||||
@override
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
final rawImageFilter = imageFilterCreateBlur(sigmaX, sigmaY, (tileMode ?? defaultBlurTileMode).index);
|
||||
borrow(rawImageFilter);
|
||||
imageFilterDispose(rawImageFilter);
|
||||
}
|
||||
@ -100,7 +112,9 @@ class SkwasmDilateFilter extends SkwasmImageFilter {
|
||||
final double radiusY;
|
||||
|
||||
@override
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow) {
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
final rawImageFilter = imageFilterCreateDilate(radiusX, radiusY);
|
||||
borrow(rawImageFilter);
|
||||
imageFilterDispose(rawImageFilter);
|
||||
@ -120,7 +134,9 @@ class SkwasmErodeFilter extends SkwasmImageFilter {
|
||||
final double radiusY;
|
||||
|
||||
@override
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow) {
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
final rawImageFilter = imageFilterCreateErode(radiusX, radiusY);
|
||||
borrow(rawImageFilter);
|
||||
imageFilterDispose(rawImageFilter);
|
||||
@ -140,7 +156,9 @@ class SkwasmMatrixFilter extends SkwasmImageFilter {
|
||||
final ui.FilterQuality filterQuality;
|
||||
|
||||
@override
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow) {
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
withStackScope((scope) {
|
||||
final rawImageFilter = imageFilterCreateMatrix(
|
||||
scope.convertMatrix4toSkMatrix(matrix4),
|
||||
@ -164,7 +182,9 @@ class SkwasmColorImageFilter extends SkwasmImageFilter {
|
||||
final SkwasmColorFilter filter;
|
||||
|
||||
@override
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow) {
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
filter.withRawColorFilter((colroFilterHandle) {
|
||||
final rawImageFilter = imageFilterCreateFromColorFilter(colroFilterHandle);
|
||||
borrow(rawImageFilter);
|
||||
@ -186,14 +206,16 @@ class SkwasmComposedImageFilter extends SkwasmImageFilter {
|
||||
final SkwasmImageFilter inner;
|
||||
|
||||
@override
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow) {
|
||||
void withRawImageFilter(ImageFilterHandleBorrow borrow, {
|
||||
ui.TileMode defaultBlurTileMode = ui.TileMode.clamp,
|
||||
}) {
|
||||
outer.withRawImageFilter((outerHandle) {
|
||||
inner.withRawImageFilter((innerHandle) {
|
||||
final rawImageFilter = imageFilterCompose(outerHandle, innerHandle);
|
||||
borrow(rawImageFilter);
|
||||
imageFilterDispose(rawImageFilter);
|
||||
});
|
||||
});
|
||||
}, defaultBlurTileMode: defaultBlurTileMode);
|
||||
}, defaultBlurTileMode: defaultBlurTileMode);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -16,7 +16,7 @@ class SkwasmPaint implements ui.Paint {
|
||||
///
|
||||
/// It is the responsibility of the caller to dispose of the returned handle
|
||||
/// when it's no longer needed.
|
||||
PaintHandle toRawPaint() {
|
||||
PaintHandle toRawPaint({ui.TileMode defaultBlurTileMode = ui.TileMode.decal}) {
|
||||
final rawPaint = paintCreate(
|
||||
isAntiAlias,
|
||||
blendMode.index,
|
||||
@ -47,7 +47,7 @@ class SkwasmPaint implements ui.Paint {
|
||||
final skwasmImageFilter = SkwasmImageFilter.fromUiFilter(filter);
|
||||
skwasmImageFilter.withRawImageFilter((nativeHandle) {
|
||||
paintSetImageFilter(rawPaint, nativeHandle);
|
||||
});
|
||||
}, defaultBlurTileMode: defaultBlurTileMode);
|
||||
}
|
||||
|
||||
return rawPaint;
|
||||
|
||||
@ -21,12 +21,14 @@ external void canvasSave(CanvasHandle canvas);
|
||||
RawRect,
|
||||
PaintHandle,
|
||||
ImageFilterHandle,
|
||||
Int
|
||||
)>(symbol: 'canvas_saveLayer', isLeaf: true)
|
||||
external void canvasSaveLayer(
|
||||
CanvasHandle canvas,
|
||||
RawRect rect,
|
||||
PaintHandle paint,
|
||||
ImageFilterHandle handle,
|
||||
int backdropTileMode,
|
||||
);
|
||||
|
||||
@Native<Void Function(CanvasHandle)>(symbol: 'canvas_restore', isLeaf: true)
|
||||
|
||||
@ -58,7 +58,7 @@ class SkwasmRenderer implements Renderer {
|
||||
ui.ImageFilter createBlurImageFilter({
|
||||
double sigmaX = 0.0,
|
||||
double sigmaY = 0.0,
|
||||
ui.TileMode tileMode = ui.TileMode.clamp
|
||||
ui.TileMode? tileMode,
|
||||
}) => SkwasmImageFilter.blur(
|
||||
sigmaX: sigmaX,
|
||||
sigmaY: sigmaY,
|
||||
|
||||
@ -28,7 +28,7 @@ class SkwasmRenderer implements Renderer {
|
||||
}
|
||||
|
||||
@override
|
||||
ui.ImageFilter createBlurImageFilter({double sigmaX = 0.0, double sigmaY = 0.0, ui.TileMode tileMode = ui.TileMode.clamp}) {
|
||||
ui.ImageFilter createBlurImageFilter({double sigmaX = 0.0, double sigmaY = 0.0, ui.TileMode? tileMode}) {
|
||||
throw UnimplementedError('Skwasm not implemented on this platform.');
|
||||
}
|
||||
|
||||
|
||||
@ -887,7 +887,7 @@ class LruCache<K extends Object, V extends Object> {
|
||||
}
|
||||
|
||||
/// Returns the VM-compatible string for the tile mode.
|
||||
String tileModeString(ui.TileMode tileMode) {
|
||||
String tileModeString(ui.TileMode? tileMode) {
|
||||
switch (tileMode) {
|
||||
case ui.TileMode.clamp:
|
||||
return 'clamp';
|
||||
@ -897,6 +897,8 @@ String tileModeString(ui.TileMode tileMode) {
|
||||
return 'repeated';
|
||||
case ui.TileMode.decal:
|
||||
return 'decal';
|
||||
case null:
|
||||
return 'unspecified';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -33,8 +33,10 @@ constexpr SkScalar kShadowLightYOffset = -450;
|
||||
SKWASM_EXPORT void canvas_saveLayer(SkCanvas* canvas,
|
||||
SkRect* rect,
|
||||
SkPaint* paint,
|
||||
SkImageFilter* backdrop) {
|
||||
canvas->saveLayer(SkCanvas::SaveLayerRec(rect, paint, backdrop, 0));
|
||||
SkImageFilter* backdrop,
|
||||
SkTileMode backdropTileMode) {
|
||||
canvas->saveLayer(SkCanvas::SaveLayerRec(rect, paint, backdrop,
|
||||
backdropTileMode, nullptr, 0));
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_save(SkCanvas* canvas) {
|
||||
|
||||
@ -450,7 +450,7 @@ void drawTestPicture(CkCanvas canvas) {
|
||||
canvas.drawCircle(const ui.Offset(30, 30), 10, CkPaint());
|
||||
{
|
||||
canvas.saveLayerWithFilter(
|
||||
kDefaultRegion, ui.ImageFilter.blur(sigmaX: 5, sigmaY: 10));
|
||||
kDefaultRegion, ui.ImageFilter.blur(sigmaX: 5, sigmaY: 10, tileMode: ui.TileMode.clamp));
|
||||
canvas.drawCircle(const ui.Offset(10, 10), 10, CkPaint());
|
||||
canvas.drawCircle(const ui.Offset(50, 50), 10, CkPaint());
|
||||
canvas.restore();
|
||||
|
||||
@ -1126,11 +1126,12 @@ void _canvasTests() {
|
||||
toSkRect(const ui.Rect.fromLTRB(0, 0, 100, 100)),
|
||||
null,
|
||||
null,
|
||||
canvasKit.TileMode.Clamp,
|
||||
);
|
||||
});
|
||||
|
||||
test('saveLayer without bounds', () {
|
||||
canvas.saveLayer(SkPaint(), null, null, null);
|
||||
canvas.saveLayer(SkPaint(), null, null, null, canvasKit.TileMode.Clamp);
|
||||
});
|
||||
|
||||
test('saveLayer with filter', () {
|
||||
@ -1139,6 +1140,7 @@ void _canvasTests() {
|
||||
toSkRect(const ui.Rect.fromLTRB(0, 0, 100, 100)),
|
||||
canvasKit.ImageFilter.MakeBlur(1, 2, canvasKit.TileMode.Repeat, null),
|
||||
0,
|
||||
canvasKit.TileMode.Repeat,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -189,4 +189,244 @@ Future<void> testMain() async {
|
||||
await drawTestImageWithPaint(ui.Paint()..maskFilter = maskFilter);
|
||||
await matchGoldenFile('ui_filter_blur_maskfilter.png', region: region);
|
||||
});
|
||||
|
||||
ui.Image makeCheckerBoard(int width, int height) {
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = ui.Canvas(recorder);
|
||||
|
||||
const double left = 0;
|
||||
final double centerX = width * 0.5;
|
||||
final double right = width.toDouble();
|
||||
|
||||
const double top = 0;
|
||||
final double centerY = height * 0.5;
|
||||
final double bottom = height.toDouble();
|
||||
|
||||
canvas.drawRect(ui.Rect.fromLTRB(left, top, centerX, centerY),
|
||||
ui.Paint()..color = const ui.Color.fromARGB(255, 0, 255, 0));
|
||||
canvas.drawRect(ui.Rect.fromLTRB(centerX, top, right, centerY),
|
||||
ui.Paint()..color = const ui.Color.fromARGB(255, 255, 255, 0));
|
||||
canvas.drawRect(ui.Rect.fromLTRB(left, centerY, centerX, bottom),
|
||||
ui.Paint()..color = const ui.Color.fromARGB(255, 0, 0, 255));
|
||||
canvas.drawRect(ui.Rect.fromLTRB(centerX, centerY, right, bottom),
|
||||
ui.Paint()..color = const ui.Color.fromARGB(255, 255, 0, 0));
|
||||
|
||||
final picture = recorder.endRecording();
|
||||
return picture.toImageSync(width, height);
|
||||
}
|
||||
|
||||
Future<ui.Rect> renderingOpsWithTileMode(ui.TileMode? tileMode) async {
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = ui.Canvas(recorder);
|
||||
canvas.drawColor(const ui.Color.fromARGB(255, 224, 224, 224), ui.BlendMode.src);
|
||||
|
||||
const ui.Rect zone = ui.Rect.fromLTWH(15, 15, 20, 20);
|
||||
final ui.Rect arena = zone.inflate(15);
|
||||
const ui.Rect ovalZone = ui.Rect.fromLTWH(20, 15, 10, 20);
|
||||
|
||||
final gradient = ui.Gradient.linear(
|
||||
zone.topLeft,
|
||||
zone.bottomRight,
|
||||
<ui.Color>[
|
||||
const ui.Color.fromARGB(255, 0, 255, 0),
|
||||
const ui.Color.fromARGB(255, 0, 0, 255),
|
||||
],
|
||||
<double>[0, 1],
|
||||
);
|
||||
final filter = ui.ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0, tileMode: tileMode);
|
||||
final ui.Paint white = ui.Paint()..color = const ui.Color.fromARGB(255, 255, 255, 255);
|
||||
final ui.Paint grey = ui.Paint()..color = const ui.Color.fromARGB(255, 127, 127, 127);
|
||||
final ui.Paint unblurredFill = ui.Paint()..shader = gradient;
|
||||
final ui.Paint blurredFill = ui.Paint.from(unblurredFill)
|
||||
..imageFilter = filter;
|
||||
final ui.Paint unblurredStroke = ui.Paint.from(unblurredFill)
|
||||
..style = ui.PaintingStyle.stroke
|
||||
..strokeCap = ui.StrokeCap.round
|
||||
..strokeJoin = ui.StrokeJoin.round
|
||||
..strokeWidth = 10;
|
||||
final ui.Paint blurredStroke = ui.Paint.from(unblurredStroke)
|
||||
..imageFilter = filter;
|
||||
final ui.Image image = makeCheckerBoard(20, 20);
|
||||
const ui.Rect imageBounds = ui.Rect.fromLTRB(0, 0, 20, 20);
|
||||
const ui.Rect imageCenter = ui.Rect.fromLTRB(5, 5, 9, 9);
|
||||
final points = <ui.Offset>[
|
||||
zone.topLeft,
|
||||
zone.topCenter,
|
||||
zone.topRight,
|
||||
zone.centerLeft,
|
||||
zone.center,
|
||||
zone.centerRight,
|
||||
zone.bottomLeft,
|
||||
zone.bottomCenter,
|
||||
zone.bottomRight,
|
||||
];
|
||||
final vertices = ui.Vertices(
|
||||
ui.VertexMode.triangles,
|
||||
<ui.Offset> [
|
||||
zone.topLeft,
|
||||
zone.bottomRight,
|
||||
zone.topRight,
|
||||
zone.topLeft,
|
||||
zone.bottomRight,
|
||||
zone.bottomLeft,
|
||||
],
|
||||
colors: <ui.Color>[
|
||||
const ui.Color.fromARGB(255, 0, 255, 0),
|
||||
const ui.Color.fromARGB(255, 255, 0, 0),
|
||||
const ui.Color.fromARGB(255, 255, 255, 0),
|
||||
const ui.Color.fromARGB(255, 0, 255, 0),
|
||||
const ui.Color.fromARGB(255, 255, 0, 0),
|
||||
const ui.Color.fromARGB(255, 0, 0, 255),
|
||||
],
|
||||
);
|
||||
final atlasXforms = <ui.RSTransform>[
|
||||
ui.RSTransform.fromComponents(
|
||||
rotation: 0.0,
|
||||
scale: 1.0,
|
||||
anchorX: 0,
|
||||
anchorY: 0,
|
||||
translateX: zone.topLeft.dx,
|
||||
translateY: zone.topLeft.dy,
|
||||
),
|
||||
ui.RSTransform.fromComponents(
|
||||
rotation: math.pi / 2,
|
||||
scale: 1.0,
|
||||
anchorX: 0,
|
||||
anchorY: 0,
|
||||
translateX: zone.topRight.dx,
|
||||
translateY: zone.topRight.dy,
|
||||
),
|
||||
ui.RSTransform.fromComponents(
|
||||
rotation: math.pi,
|
||||
scale: 1.0,
|
||||
anchorX: 0,
|
||||
anchorY: 0,
|
||||
translateX: zone.bottomRight.dx,
|
||||
translateY: zone.bottomRight.dy,
|
||||
),
|
||||
ui.RSTransform.fromComponents(
|
||||
rotation: math.pi * 3 / 2,
|
||||
scale: 1.0,
|
||||
anchorX: 0,
|
||||
anchorY: 0,
|
||||
translateX: zone.bottomLeft.dx,
|
||||
translateY: zone.bottomLeft.dy,
|
||||
),
|
||||
ui.RSTransform.fromComponents(
|
||||
rotation: math.pi / 4,
|
||||
scale: 1.0,
|
||||
anchorX: 4,
|
||||
anchorY: 4,
|
||||
translateX: zone.center.dx,
|
||||
translateY: zone.center.dy,
|
||||
),
|
||||
];
|
||||
const atlasRects = <ui.Rect>[
|
||||
ui.Rect.fromLTRB(6, 6, 14, 14),
|
||||
ui.Rect.fromLTRB(6, 6, 14, 14),
|
||||
ui.Rect.fromLTRB(6, 6, 14, 14),
|
||||
ui.Rect.fromLTRB(6, 6, 14, 14),
|
||||
ui.Rect.fromLTRB(6, 6, 14, 14),
|
||||
];
|
||||
|
||||
const double pad = 10;
|
||||
final double offset = arena.width + pad;
|
||||
const int columns = 5;
|
||||
final ui.Rect pairArena = ui.Rect.fromLTRB(arena.left - 3, arena.top - 3,
|
||||
arena.right + 3, arena.bottom + offset + 3);
|
||||
|
||||
final List<void Function(ui.Canvas canvas, ui.Paint fill, ui.Paint stroke)> renderers = [
|
||||
(canvas, fill, stroke) {
|
||||
canvas.saveLayer(zone.inflate(5), fill);
|
||||
canvas.drawLine(zone.topLeft, zone.bottomRight, unblurredStroke);
|
||||
canvas.drawLine(zone.topRight, zone.bottomLeft, unblurredStroke);
|
||||
canvas.restore();
|
||||
},
|
||||
(canvas, fill, stroke) => canvas.drawLine(zone.topLeft, zone.bottomRight, stroke),
|
||||
(canvas, fill, stroke) => canvas.drawRect(zone, fill),
|
||||
(canvas, fill, stroke) => canvas.drawOval(ovalZone, fill),
|
||||
(canvas, fill, stroke) => canvas.drawCircle(zone.center, zone.width * 0.5, fill),
|
||||
(canvas, fill, stroke) => canvas.drawRRect(ui.RRect.fromRectXY(zone, 4.0, 4.0), fill),
|
||||
(canvas, fill, stroke) => canvas.drawDRRect(
|
||||
ui.RRect.fromRectXY(zone, 4.0, 4.0),
|
||||
ui.RRect.fromRectXY(zone.deflate(4), 4.0, 4.0),
|
||||
fill),
|
||||
(canvas, fill, stroke) => canvas.drawArc(zone, math.pi / 4, math.pi * 3 / 2, true, fill),
|
||||
(canvas, fill, stroke) => canvas.drawPath(ui.Path()
|
||||
..moveTo(zone.left, zone.top)
|
||||
..lineTo(zone.right, zone.top)
|
||||
..lineTo(zone.left, zone.bottom)
|
||||
..lineTo(zone.right, zone.bottom),
|
||||
stroke),
|
||||
(canvas, fill, stroke) => canvas.drawImage(image, zone.topLeft, fill),
|
||||
(canvas, fill, stroke) => canvas.drawImageRect(image, imageBounds, zone.inflate(2), fill),
|
||||
(canvas, fill, stroke) => canvas.drawImageNine(image, imageCenter, zone.inflate(2), fill),
|
||||
(canvas, fill, stroke) => canvas.drawPoints(ui.PointMode.points, points, stroke),
|
||||
(canvas, fill, stroke) => canvas.drawVertices(vertices, ui.BlendMode.dstOver, fill),
|
||||
(canvas, fill, stroke) => canvas.drawAtlas(image, atlasXforms, atlasRects,
|
||||
null, null, null, fill),
|
||||
];
|
||||
|
||||
canvas.save();
|
||||
canvas.translate(pad, pad);
|
||||
int renderIndex = 0;
|
||||
int rows = 0;
|
||||
while (renderIndex < renderers.length) {
|
||||
rows += 2;
|
||||
canvas.save();
|
||||
for (int col = 0; col < columns && renderIndex < renderers.length; col++) {
|
||||
final renderer = renderers[renderIndex++];
|
||||
canvas.drawRect(pairArena, grey);
|
||||
canvas.drawRect(arena, white);
|
||||
renderer(canvas, unblurredFill, unblurredStroke);
|
||||
canvas.save();
|
||||
canvas.translate(0, offset);
|
||||
canvas.drawRect(arena, white);
|
||||
renderer(canvas, blurredFill, blurredStroke);
|
||||
canvas.restore();
|
||||
canvas.translate(offset, 0);
|
||||
}
|
||||
canvas.restore();
|
||||
canvas.translate(0, offset * 2);
|
||||
}
|
||||
canvas.restore();
|
||||
|
||||
await drawPictureUsingCurrentRenderer(recorder.endRecording());
|
||||
return ui.Rect.fromLTWH(0, 0, offset * columns + pad, offset * rows + pad);
|
||||
}
|
||||
|
||||
test('Rendering ops with ImageFilter blur with default tile mode', () async {
|
||||
final region = await renderingOpsWithTileMode(null);
|
||||
await matchGoldenFile('ui_filter_blurred_rendering_with_default_tile_mode.png', region: region);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml);
|
||||
|
||||
test('Rendering ops with ImageFilter blur with clamp tile mode', () async {
|
||||
final region = await renderingOpsWithTileMode(ui.TileMode.clamp);
|
||||
await matchGoldenFile('ui_filter_blurred_rendering_with_clamp_tile_mode.png', region: region);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml);
|
||||
|
||||
test('Rendering ops with ImageFilter blur with decal tile mode', () async {
|
||||
final region = await renderingOpsWithTileMode(ui.TileMode.decal);
|
||||
await matchGoldenFile('ui_filter_blurred_rendering_with_decal_tile_mode.png', region: region);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml);
|
||||
|
||||
test('Rendering ops with ImageFilter blur with mirror tile mode', () async {
|
||||
final region = await renderingOpsWithTileMode(ui.TileMode.mirror);
|
||||
await matchGoldenFile('ui_filter_blurred_rendering_with_mirror_tile_mode.png', region: region);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml);
|
||||
|
||||
test('Rendering ops with ImageFilter blur with repeated tile mode', () async {
|
||||
final region = await renderingOpsWithTileMode(ui.TileMode.repeated);
|
||||
await matchGoldenFile('ui_filter_blurred_rendering_with_repeated_tile_mode.png', region: region);
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml);
|
||||
}
|
||||
|
||||
@ -501,6 +501,61 @@ Future<void> testMain() async {
|
||||
'scene_builder_opacity_layer_with_transformed_children.png',
|
||||
region: region);
|
||||
});
|
||||
|
||||
test('backdrop layer with default blur tile mode', () async {
|
||||
final scene = backdropBlurWithTileMode(null, 10, 50);
|
||||
await renderScene(scene);
|
||||
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_default_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10*50, 10*50));
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml);
|
||||
|
||||
test('backdrop layer with clamp blur tile mode', () async {
|
||||
final scene = backdropBlurWithTileMode(ui.TileMode.clamp, 10, 50);
|
||||
await renderScene(scene);
|
||||
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_clamp_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10*50, 10*50));
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml);
|
||||
|
||||
test('backdrop layer with mirror blur tile mode', () async {
|
||||
final scene = backdropBlurWithTileMode(ui.TileMode.mirror, 10, 50);
|
||||
await renderScene(scene);
|
||||
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_mirror_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10*50, 10*50));
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml);
|
||||
|
||||
test('backdrop layer with repeated blur tile mode', () async {
|
||||
final scene = backdropBlurWithTileMode(ui.TileMode.repeated, 10, 50);
|
||||
await renderScene(scene);
|
||||
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_repeated_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10*50, 10*50));
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml);
|
||||
|
||||
test('backdrop layer with decal blur tile mode', () async {
|
||||
final scene = backdropBlurWithTileMode(ui.TileMode.decal, 10, 50);
|
||||
await renderScene(scene);
|
||||
|
||||
await matchGoldenFile(
|
||||
'scene_builder_backdrop_filter_blur_decal_tile_mode.png',
|
||||
region: const ui.Rect.fromLTWH(0, 0, 10*50, 10*50));
|
||||
},
|
||||
// HTML renderer doesn't have tile modes
|
||||
skip: isHtml);
|
||||
});
|
||||
}
|
||||
|
||||
@ -510,3 +565,58 @@ ui.Picture drawPicture(void Function(ui.Canvas) drawCommands) {
|
||||
drawCommands(canvas);
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
ui.Scene backdropBlurWithTileMode(ui.TileMode? tileMode,
|
||||
final double rectSize,
|
||||
final int count) {
|
||||
final double imgSize = rectSize * count;
|
||||
|
||||
const ui.Color white = ui.Color(0xFFFFFFFF);
|
||||
const ui.Color purple = ui.Color(0xFFFF00FF);
|
||||
const ui.Color blue = ui.Color(0xFF0000FF);
|
||||
const ui.Color green = ui.Color(0xFF00FF00);
|
||||
const ui.Color yellow = ui.Color(0xFFFFFF00);
|
||||
const ui.Color red = ui.Color(0xFFFF0000);
|
||||
|
||||
final ui.Picture blueGreenGridPicture = drawPicture((ui.Canvas canvas) {
|
||||
canvas.drawColor(white, ui.BlendMode.src);
|
||||
for (int i = 0; i < count; i++) {
|
||||
for (int j = 0; j < count; j++) {
|
||||
final bool rectOdd = (i + j) & 1 == 0;
|
||||
final ui.Color fg = (i < count / 2)
|
||||
? ((j < count / 2) ? green : blue)
|
||||
: ((j < count / 2) ? yellow : red);
|
||||
canvas.drawRect(ui.Rect.fromLTWH(i * rectSize, j * rectSize, rectSize, rectSize),
|
||||
ui.Paint()..color = rectOdd ? fg : white);
|
||||
}
|
||||
}
|
||||
canvas.drawRect(ui.Rect.fromLTWH(0, 0, imgSize, 1), ui.Paint()..color = purple);
|
||||
canvas.drawRect(ui.Rect.fromLTWH(0, 0, 1, imgSize), ui.Paint()..color = purple);
|
||||
canvas.drawRect(ui.Rect.fromLTWH(0, imgSize - 1, imgSize, 1), ui.Paint()..color = purple);
|
||||
canvas.drawRect(ui.Rect.fromLTWH(imgSize - 1, 0, 1, imgSize), ui.Paint()..color = purple);
|
||||
});
|
||||
|
||||
final ui.SceneBuilder sceneBuilder = ui.SceneBuilder();
|
||||
// We push a clipRect layer with the SaveLayer behavior so that it creates
|
||||
// a layer of predetermined size in which the backdrop filter will apply
|
||||
// its filter to show the edge effects on predictable edges.
|
||||
sceneBuilder.pushClipRect(ui.Rect.fromLTWH(0, 0, imgSize, imgSize),
|
||||
clipBehavior: ui.Clip.antiAliasWithSaveLayer);
|
||||
sceneBuilder.addPicture(ui.Offset.zero, blueGreenGridPicture);
|
||||
sceneBuilder.pushBackdropFilter(ui.ImageFilter.blur(sigmaX: 20, sigmaY: 20, tileMode: tileMode));
|
||||
// The following picture prevents the saveLayer in the backdrop filter from
|
||||
// being completely ignored on the skwasm backend due to an interaction with
|
||||
// SkPictureRecorder eliminating saveLayer entries with no content even if
|
||||
// they have a backdrop filter. It draws nothing because the pixels below
|
||||
// it are opaque and dstOver is a NOP in that case, but it is unlikely that
|
||||
// a recording process would be able to figure that out without extensive
|
||||
// analysis between the pictures and layers.
|
||||
sceneBuilder.addPicture(ui.Offset.zero, drawPicture((ui.Canvas canvas) {
|
||||
canvas.drawRect(ui.Rect.fromLTWH(imgSize * 0.5 - 10, imgSize * 0.5 - 10, 20, 20),
|
||||
ui.Paint()..color = purple..blendMode = ui.BlendMode.dstOver);
|
||||
}));
|
||||
sceneBuilder.pop();
|
||||
sceneBuilder.pop();
|
||||
|
||||
return sceneBuilder.build();
|
||||
}
|
||||
|
||||
@ -1394,6 +1394,236 @@ void main() async {
|
||||
final ByteData? data = await resultImage.toByteData();
|
||||
expect(data, isNotNull);
|
||||
});
|
||||
|
||||
Image makeCheckerBoard(int width, int height) {
|
||||
final recorder = PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
|
||||
const double left = 0;
|
||||
final double centerX = width * 0.5;
|
||||
final double right = width.toDouble();
|
||||
|
||||
const double top = 0;
|
||||
final double centerY = height * 0.5;
|
||||
final double bottom = height.toDouble();
|
||||
|
||||
canvas.drawRect(Rect.fromLTRB(left, top, centerX, centerY),
|
||||
Paint()..color = const Color.fromARGB(255, 0, 255, 0));
|
||||
canvas.drawRect(Rect.fromLTRB(centerX, top, right, centerY),
|
||||
Paint()..color = const Color.fromARGB(255, 255, 255, 0));
|
||||
canvas.drawRect(Rect.fromLTRB(left, centerY, centerX, bottom),
|
||||
Paint()..color = const Color.fromARGB(255, 0, 0, 255));
|
||||
canvas.drawRect(Rect.fromLTRB(centerX, centerY, right, bottom),
|
||||
Paint()..color = const Color.fromARGB(255, 255, 0, 0));
|
||||
|
||||
final picture = recorder.endRecording();
|
||||
return picture.toImageSync(width, height);
|
||||
}
|
||||
|
||||
Image renderingOpsWithTileMode(TileMode? tileMode) {
|
||||
final recorder = PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
canvas.drawColor(const Color.fromARGB(255, 224, 224, 224), BlendMode.src);
|
||||
|
||||
const Rect zone = Rect.fromLTWH(15, 15, 20, 20);
|
||||
final Rect arena = zone.inflate(15);
|
||||
const Rect ovalZone = Rect.fromLTWH(20, 15, 10, 20);
|
||||
|
||||
final gradient = Gradient.linear(
|
||||
zone.topLeft,
|
||||
zone.bottomRight,
|
||||
<Color>[
|
||||
const Color.fromARGB(255, 0, 255, 0),
|
||||
const Color.fromARGB(255, 0, 0, 255),
|
||||
],
|
||||
<double>[0, 1],
|
||||
);
|
||||
final filter = ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0, tileMode: tileMode);
|
||||
final Paint white = Paint()..color = const Color.fromARGB(255, 255, 255, 255);
|
||||
final Paint grey = Paint()..color = const Color.fromARGB(255, 127, 127, 127);
|
||||
final Paint unblurredFill = Paint()..shader = gradient;
|
||||
final Paint blurredFill = Paint.from(unblurredFill)
|
||||
..imageFilter = filter;
|
||||
final Paint unblurredStroke = Paint.from(unblurredFill)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeCap = StrokeCap.round
|
||||
..strokeJoin = StrokeJoin.round
|
||||
..strokeWidth = 10;
|
||||
final Paint blurredStroke = Paint.from(unblurredStroke)
|
||||
..imageFilter = filter;
|
||||
final Image image = makeCheckerBoard(20, 20);
|
||||
const Rect imageBounds = Rect.fromLTRB(0, 0, 20, 20);
|
||||
const Rect imageCenter = Rect.fromLTRB(5, 5, 9, 9);
|
||||
final points = <Offset>[
|
||||
zone.topLeft,
|
||||
zone.topCenter,
|
||||
zone.topRight,
|
||||
zone.centerLeft,
|
||||
zone.center,
|
||||
zone.centerRight,
|
||||
zone.bottomLeft,
|
||||
zone.bottomCenter,
|
||||
zone.bottomRight,
|
||||
];
|
||||
final vertices = Vertices(
|
||||
VertexMode.triangles,
|
||||
<Offset> [
|
||||
zone.topLeft,
|
||||
zone.bottomRight,
|
||||
zone.topRight,
|
||||
zone.topLeft,
|
||||
zone.bottomRight,
|
||||
zone.bottomLeft,
|
||||
],
|
||||
colors: <Color>[
|
||||
const Color.fromARGB(255, 0, 255, 0),
|
||||
const Color.fromARGB(255, 255, 0, 0),
|
||||
const Color.fromARGB(255, 255, 255, 0),
|
||||
const Color.fromARGB(255, 0, 255, 0),
|
||||
const Color.fromARGB(255, 255, 0, 0),
|
||||
const Color.fromARGB(255, 0, 0, 255),
|
||||
],
|
||||
);
|
||||
final atlasXforms = <RSTransform>[
|
||||
RSTransform.fromComponents(
|
||||
rotation: 0.0,
|
||||
scale: 1.0,
|
||||
anchorX: 0,
|
||||
anchorY: 0,
|
||||
translateX: zone.topLeft.dx,
|
||||
translateY: zone.topLeft.dy,
|
||||
),
|
||||
RSTransform.fromComponents(
|
||||
rotation: pi / 2,
|
||||
scale: 1.0,
|
||||
anchorX: 0,
|
||||
anchorY: 0,
|
||||
translateX: zone.topRight.dx,
|
||||
translateY: zone.topRight.dy,
|
||||
),
|
||||
RSTransform.fromComponents(
|
||||
rotation: pi,
|
||||
scale: 1.0,
|
||||
anchorX: 0,
|
||||
anchorY: 0,
|
||||
translateX: zone.bottomRight.dx,
|
||||
translateY: zone.bottomRight.dy,
|
||||
),
|
||||
RSTransform.fromComponents(
|
||||
rotation: pi * 3 / 2,
|
||||
scale: 1.0,
|
||||
anchorX: 0,
|
||||
anchorY: 0,
|
||||
translateX: zone.bottomLeft.dx,
|
||||
translateY: zone.bottomLeft.dy,
|
||||
),
|
||||
RSTransform.fromComponents(
|
||||
rotation: pi / 4,
|
||||
scale: 1.0,
|
||||
anchorX: 4,
|
||||
anchorY: 4,
|
||||
translateX: zone.center.dx,
|
||||
translateY: zone.center.dy,
|
||||
),
|
||||
];
|
||||
const atlasRects = <Rect>[
|
||||
Rect.fromLTRB(6, 6, 14, 14),
|
||||
Rect.fromLTRB(6, 6, 14, 14),
|
||||
Rect.fromLTRB(6, 6, 14, 14),
|
||||
Rect.fromLTRB(6, 6, 14, 14),
|
||||
Rect.fromLTRB(6, 6, 14, 14),
|
||||
];
|
||||
|
||||
const double pad = 10;
|
||||
final double offset = arena.width + pad;
|
||||
const int columns = 5;
|
||||
final Rect pairArena = Rect.fromLTRB(arena.left - 3, arena.top - 3,
|
||||
arena.right + 3, arena.bottom + offset + 3);
|
||||
|
||||
final List<void Function(Canvas canvas, Paint fill, Paint stroke)> renderers = [
|
||||
(canvas, fill, stroke) {
|
||||
canvas.saveLayer(zone.inflate(5), fill);
|
||||
canvas.drawLine(zone.topLeft, zone.bottomRight, unblurredStroke);
|
||||
canvas.drawLine(zone.topRight, zone.bottomLeft, unblurredStroke);
|
||||
canvas.restore();
|
||||
},
|
||||
(canvas, fill, stroke) => canvas.drawLine(zone.topLeft, zone.bottomRight, stroke),
|
||||
(canvas, fill, stroke) => canvas.drawRect(zone, fill),
|
||||
(canvas, fill, stroke) => canvas.drawOval(ovalZone, fill),
|
||||
(canvas, fill, stroke) => canvas.drawCircle(zone.center, zone.width * 0.5, fill),
|
||||
(canvas, fill, stroke) => canvas.drawRRect(RRect.fromRectXY(zone, 4.0, 4.0), fill),
|
||||
(canvas, fill, stroke) => canvas.drawDRRect(RRect.fromRectXY(zone, 4.0, 4.0),
|
||||
RRect.fromRectXY(zone.deflate(4), 4.0, 4.0),
|
||||
fill),
|
||||
(canvas, fill, stroke) => canvas.drawArc(zone, pi / 4, pi * 3 / 2, true, fill),
|
||||
(canvas, fill, stroke) => canvas.drawPath(Path()
|
||||
..moveTo(zone.left, zone.top)
|
||||
..lineTo(zone.right, zone.top)
|
||||
..lineTo(zone.left, zone.bottom)
|
||||
..lineTo(zone.right, zone.bottom),
|
||||
stroke),
|
||||
(canvas, fill, stroke) => canvas.drawImage(image, zone.topLeft, fill),
|
||||
(canvas, fill, stroke) => canvas.drawImageRect(image, imageBounds, zone.inflate(2), fill),
|
||||
(canvas, fill, stroke) => canvas.drawImageNine(image, imageCenter, zone.inflate(2), fill),
|
||||
(canvas, fill, stroke) => canvas.drawPoints(PointMode.points, points, stroke),
|
||||
(canvas, fill, stroke) => canvas.drawVertices(vertices, BlendMode.dstOver, fill),
|
||||
(canvas, fill, stroke) => canvas.drawAtlas(image, atlasXforms, atlasRects,
|
||||
null, null, null, fill),
|
||||
];
|
||||
|
||||
canvas.save();
|
||||
canvas.translate(pad, pad);
|
||||
int renderIndex = 0;
|
||||
int rows = 0;
|
||||
while (renderIndex < renderers.length) {
|
||||
rows += 2;
|
||||
canvas.save();
|
||||
for (int col = 0; col < columns && renderIndex < renderers.length; col++) {
|
||||
final renderer = renderers[renderIndex++];
|
||||
canvas.drawRect(pairArena, grey);
|
||||
canvas.drawRect(arena, white);
|
||||
renderer(canvas, unblurredFill, unblurredStroke);
|
||||
canvas.save();
|
||||
canvas.translate(0, offset);
|
||||
canvas.drawRect(arena, white);
|
||||
renderer(canvas, blurredFill, blurredStroke);
|
||||
canvas.restore();
|
||||
canvas.translate(offset, 0);
|
||||
}
|
||||
canvas.restore();
|
||||
canvas.translate(0, offset * 2);
|
||||
}
|
||||
canvas.restore();
|
||||
|
||||
final picture = recorder.endRecording();
|
||||
return picture.toImageSync((offset * columns + pad).round(),
|
||||
(offset * rows + pad).round());
|
||||
}
|
||||
|
||||
test('Rendering ops with ImageFilter blur with default tile mode', () async {
|
||||
final image = renderingOpsWithTileMode(null);
|
||||
await comparer.addGoldenImage(image, 'canvas_test_blurred_rendering_with_default_tile_mode.png');
|
||||
});
|
||||
|
||||
test('Rendering ops with ImageFilter blur with clamp tile mode', () async {
|
||||
final image = renderingOpsWithTileMode(TileMode.clamp);
|
||||
await comparer.addGoldenImage(image, 'canvas_test_blurred_rendering_with_clamp_tile_mode.png');
|
||||
});
|
||||
|
||||
test('Rendering ops with ImageFilter blur with mirror tile mode', () async {
|
||||
final image = renderingOpsWithTileMode(TileMode.mirror);
|
||||
await comparer.addGoldenImage(image, 'canvas_test_blurred_rendering_with_mirror_tile_mode.png');
|
||||
});
|
||||
|
||||
test('Rendering ops with ImageFilter blur with repeated tile mode', () async {
|
||||
final image = renderingOpsWithTileMode(TileMode.repeated);
|
||||
await comparer.addGoldenImage(image, 'canvas_test_blurred_rendering_with_repeated_tile_mode.png');
|
||||
});
|
||||
|
||||
test('Rendering ops with ImageFilter blur with decal tile mode', () async {
|
||||
final image = renderingOpsWithTileMode(TileMode.decal);
|
||||
await comparer.addGoldenImage(image, 'canvas_test_blurred_rendering_with_decal_tile_mode.png');
|
||||
});
|
||||
}
|
||||
|
||||
Future<Image> createTestImage() async {
|
||||
|
||||
@ -336,7 +336,7 @@ void main() {
|
||||
return builder.pushOpacity(100, oldLayer: oldLayer as OpacityEngineLayer?);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer? oldLayer) {
|
||||
return builder.pushBackdropFilter(ImageFilter.blur(), oldLayer: oldLayer as BackdropFilterEngineLayer?);
|
||||
return builder.pushBackdropFilter(ImageFilter.blur(sigmaX: 1.0), oldLayer: oldLayer as BackdropFilterEngineLayer?);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer? oldLayer) {
|
||||
return builder.pushShaderMask(
|
||||
|
||||
@ -68,7 +68,7 @@ void main() async {
|
||||
return bytes.buffer.asUint32List();
|
||||
}
|
||||
|
||||
ImageFilter makeBlur(double sigmaX, double sigmaY, [TileMode tileMode = TileMode.clamp]) =>
|
||||
ImageFilter makeBlur(double sigmaX, double sigmaY, [TileMode? tileMode]) =>
|
||||
ImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode);
|
||||
|
||||
ImageFilter makeDilate(double radiusX, double radiusY) =>
|
||||
@ -106,6 +106,9 @@ void main() async {
|
||||
return <ImageFilter>[
|
||||
makeBlur(10.0, 10.0),
|
||||
makeBlur(10.0, 10.0, TileMode.decal),
|
||||
makeBlur(10.0, 10.0, TileMode.clamp),
|
||||
makeBlur(10.0, 10.0, TileMode.mirror),
|
||||
makeBlur(10.0, 10.0, TileMode.repeated),
|
||||
makeBlur(10.0, 20.0),
|
||||
makeBlur(20.0, 20.0),
|
||||
makeDilate(10.0, 20.0),
|
||||
@ -183,6 +186,24 @@ void main() async {
|
||||
checkBytes(bytes, greenCenterBlurred, greenSideBlurred, greenCornerBlurred);
|
||||
});
|
||||
|
||||
test('ImageFilter - blur toString', () async {
|
||||
|
||||
var filter = makeBlur(1.9, 2.1);
|
||||
expect(filter.toString(), 'ImageFilter.blur(1.9, 2.1, unspecified)');
|
||||
|
||||
filter = makeBlur(1.9, 2.1, TileMode.decal);
|
||||
expect(filter.toString(), 'ImageFilter.blur(1.9, 2.1, decal)');
|
||||
|
||||
filter = makeBlur(1.9, 2.1, TileMode.clamp);
|
||||
expect(filter.toString(), 'ImageFilter.blur(1.9, 2.1, clamp)');
|
||||
|
||||
filter = makeBlur(1.9, 2.1, TileMode.mirror);
|
||||
expect(filter.toString(), 'ImageFilter.blur(1.9, 2.1, mirror)');
|
||||
|
||||
filter = makeBlur(1.9, 2.1, TileMode.repeated);
|
||||
expect(filter.toString(), 'ImageFilter.blur(1.9, 2.1, repeated)');
|
||||
});
|
||||
|
||||
test('ImageFilter - dilate', () async {
|
||||
final Paint paint = Paint()
|
||||
..color = green
|
||||
@ -279,7 +300,7 @@ void main() async {
|
||||
test('Composite ImageFilter toString', () {
|
||||
expect(
|
||||
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)'),
|
||||
contains('blur(10.0, 10.0, unspecified) -> blur(20.0, 20.0, decal)'),
|
||||
);
|
||||
|
||||
// Produces a flat list of filters
|
||||
|
||||
@ -109,7 +109,7 @@ void main() {
|
||||
redClippedPicture.dispose();
|
||||
});
|
||||
|
||||
Image backdropBlurWithTileMode(TileMode tileMode) {
|
||||
Image backdropBlurWithTileMode(TileMode? tileMode) {
|
||||
Picture makePicture(CanvasCallback callback) {
|
||||
final PictureRecorder recorder = PictureRecorder();
|
||||
final Canvas canvas = Canvas(recorder);
|
||||
@ -194,6 +194,20 @@ void main() {
|
||||
image.dispose();
|
||||
});
|
||||
|
||||
test('BackdropFilter with Blur default TileMode acts as TileMode.mirror', () async {
|
||||
final Image image = backdropBlurWithTileMode(null);
|
||||
|
||||
final ImageComparer comparer = await ImageComparer.create();
|
||||
// It would be nice to compare the output here to the "mirror" golden
|
||||
// image generated above, but this file name is where the results of
|
||||
// this test will be written and the comparison will be done independently
|
||||
// in a separate step. If we repeated the name of the "mirror" golden,
|
||||
// we would just overwrite the results of the mirror test above.
|
||||
await comparer.addGoldenImage(image, 'dart_ui_backdrop_filter_blur_default_tile_mode.png');
|
||||
|
||||
image.dispose();
|
||||
});
|
||||
|
||||
test('ImageFilter.matrix defaults to FilterQuality.medium', () {
|
||||
final Float64List data = Matrix4.identity().storage;
|
||||
expect(
|
||||
|
||||
@ -2089,7 +2089,7 @@ class PlatformViewWithOtherBackDropFilter extends PlatformViewScenario {
|
||||
final Picture picture = recorder.endRecording();
|
||||
builder.addPicture(Offset.zero, picture);
|
||||
|
||||
final ImageFilter filter = ImageFilter.blur(sigmaX: 8, sigmaY: 8);
|
||||
final ImageFilter filter = ImageFilter.blur(sigmaX: 8, sigmaY: 8, tileMode: TileMode.clamp);
|
||||
builder.pushBackdropFilter(filter);
|
||||
|
||||
final PictureRecorder recorder2 = PictureRecorder();
|
||||
@ -2190,7 +2190,7 @@ class TwoPlatformViewsWithOtherBackDropFilter extends Scenario
|
||||
final Picture picture2 = recorder2.endRecording();
|
||||
builder.addPicture(const Offset(100, 100), picture2);
|
||||
|
||||
final ImageFilter filter = ImageFilter.blur(sigmaX: 8, sigmaY: 8);
|
||||
final ImageFilter filter = ImageFilter.blur(sigmaX: 8, sigmaY: 8, tileMode: TileMode.clamp);
|
||||
builder.pushBackdropFilter(filter);
|
||||
|
||||
builder.pushOffset(0, 600);
|
||||
@ -2277,7 +2277,7 @@ class PlatformViewWithNegativeBackDropFilter extends Scenario
|
||||
final Picture picture2 = recorder2.endRecording();
|
||||
builder.addPicture(const Offset(100, 100), picture2);
|
||||
|
||||
final ImageFilter filter = ImageFilter.blur(sigmaX: -8, sigmaY: 8);
|
||||
final ImageFilter filter = ImageFilter.blur(sigmaX: -8, sigmaY: 8, tileMode: TileMode.clamp);
|
||||
builder.pushBackdropFilter(filter);
|
||||
|
||||
final Scene scene = builder.build();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user