Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 21ce09a

Browse files
authored
Add optional offset parameter to ImageFilterLayer (#36863)
* add optional offset parameter to ImageFilterLayer * update unit test for LayerStateStack and fix HTML implementation
1 parent 49db609 commit 21ce09a

11 files changed

+142
-17
lines changed

flow/layers/image_filter_layer.cc

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99

1010
namespace flutter {
1111

12-
ImageFilterLayer::ImageFilterLayer(std::shared_ptr<const DlImageFilter> filter)
12+
ImageFilterLayer::ImageFilterLayer(std::shared_ptr<const DlImageFilter> filter,
13+
const SkPoint& offset)
1314
: CacheableContainerLayer(
1415
RasterCacheUtil::kMinimumRendersBeforeCachingFilterLayer),
16+
offset_(offset),
1517
filter_(std::move(filter)),
1618
transformed_filter_(nullptr) {}
1719

@@ -20,11 +22,12 @@ void ImageFilterLayer::Diff(DiffContext* context, const Layer* old_layer) {
2022
auto* prev = static_cast<const ImageFilterLayer*>(old_layer);
2123
if (!context->IsSubtreeDirty()) {
2224
FML_DCHECK(prev);
23-
if (NotEquals(filter_, prev->filter_)) {
25+
if (NotEquals(filter_, prev->filter_) || offset_ != prev->offset_) {
2426
context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));
2527
}
2628
}
2729

30+
context->PushTransform(SkMatrix::Translate(offset_.fX, offset_.fY));
2831
if (context->has_raster_cache()) {
2932
context->SetTransform(
3033
RasterCacheUtil::GetIntegralTransCTM(context->GetTransform()));
@@ -47,6 +50,9 @@ void ImageFilterLayer::Diff(DiffContext* context, const Layer* old_layer) {
4750
}
4851

4952
void ImageFilterLayer::Preroll(PrerollContext* context) {
53+
auto mutator = context->state_stack.save();
54+
mutator.translate(offset_);
55+
5056
Layer::AutoPrerollSaveLayerState save =
5157
Layer::AutoPrerollSaveLayerState::Create(context);
5258

@@ -73,7 +79,8 @@ void ImageFilterLayer::Preroll(PrerollContext* context) {
7379
SkIRect filter_out_bounds;
7480
filter_->map_device_bounds(filter_in_bounds, SkMatrix::I(),
7581
filter_out_bounds);
76-
child_bounds = SkRect::Make(filter_out_bounds);
82+
child_bounds.set(filter_out_bounds);
83+
child_bounds.offset(offset_);
7784

7885
set_paint_bounds(child_bounds);
7986

@@ -92,6 +99,7 @@ void ImageFilterLayer::Paint(PaintContext& context) const {
9299
FML_DCHECK(needs_painting(context));
93100

94101
auto mutator = context.state_stack.save();
102+
mutator.translate(offset_);
95103

96104
if (context.raster_cache) {
97105
// Always apply the integral transform in the presence of a raster cache

flow/layers/image_filter_layer.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ namespace flutter {
1515

1616
class ImageFilterLayer : public CacheableContainerLayer {
1717
public:
18-
explicit ImageFilterLayer(std::shared_ptr<const DlImageFilter> filter);
18+
explicit ImageFilterLayer(std::shared_ptr<const DlImageFilter> filter,
19+
const SkPoint& offset = SkPoint::Make(0, 0));
1920

2021
void Diff(DiffContext* context, const Layer* old_layer) override;
2122

@@ -24,6 +25,7 @@ class ImageFilterLayer : public CacheableContainerLayer {
2425
void Paint(PaintContext& context) const override;
2526

2627
private:
28+
SkPoint offset_;
2729
std::shared_ptr<const DlImageFilter> filter_;
2830
std::shared_ptr<const DlImageFilter> transformed_filter_;
2931

flow/layers/image_filter_layer_unittests.cc

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,59 @@ TEST_F(ImageFilterLayerTest, SimpleFilter) {
114114
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_display_list));
115115
}
116116

117+
TEST_F(ImageFilterLayerTest, SimpleFilterWithOffset) {
118+
const SkMatrix initial_transform = SkMatrix::Translate(0.5f, 1.0f);
119+
const SkRect initial_cull_rect = SkRect::MakeLTRB(0, 0, 100, 100);
120+
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
121+
const SkPath child_path = SkPath().addRect(child_bounds);
122+
const SkPaint child_paint = SkPaint(SkColors::kYellow);
123+
const SkPoint layer_offset = SkPoint::Make(5.5, 6.5);
124+
auto dl_image_filter = std::make_shared<DlMatrixImageFilter>(
125+
SkMatrix(), DlImageSampling::kMipmapLinear);
126+
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
127+
auto layer =
128+
std::make_shared<ImageFilterLayer>(dl_image_filter, layer_offset);
129+
layer->Add(mock_layer);
130+
131+
SkMatrix child_matrix = initial_transform;
132+
child_matrix.preTranslate(layer_offset.fX, layer_offset.fY);
133+
const SkRect child_rounded_bounds =
134+
SkRect::MakeLTRB(10.5f, 12.5f, 26.5f, 28.5f);
135+
136+
preroll_context()->state_stack.set_preroll_delegate(initial_cull_rect,
137+
initial_transform);
138+
layer->Preroll(preroll_context());
139+
EXPECT_EQ(layer->paint_bounds(), child_rounded_bounds);
140+
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
141+
EXPECT_TRUE(layer->needs_painting(paint_context()));
142+
EXPECT_EQ(mock_layer->parent_matrix(), child_matrix);
143+
EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),
144+
initial_cull_rect);
145+
146+
DisplayListBuilder expected_builder;
147+
/* ImageFilterLayer::Paint() */ {
148+
expected_builder.save();
149+
{
150+
expected_builder.translate(layer_offset.fX, layer_offset.fY);
151+
DlPaint dl_paint;
152+
dl_paint.setImageFilter(dl_image_filter.get());
153+
expected_builder.saveLayer(&child_bounds, &dl_paint);
154+
{
155+
/* MockLayer::Paint() */ {
156+
expected_builder.drawPath(child_path,
157+
DlPaint().setColor(DlColor::kYellow()));
158+
}
159+
}
160+
expected_builder.restore();
161+
}
162+
expected_builder.restore();
163+
}
164+
auto expected_display_list = expected_builder.Build();
165+
166+
layer->Paint(display_list_paint_context());
167+
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_display_list));
168+
}
169+
117170
TEST_F(ImageFilterLayerTest, SimpleFilterBounds) {
118171
const SkMatrix initial_transform = SkMatrix::Translate(0.5f, 1.0f);
119172
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);

lib/ui/compositing.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -520,21 +520,22 @@ class SceneBuilder extends NativeFieldWrapperClass1 {
520520
/// See [pop] for details about the operation stack.
521521
ImageFilterEngineLayer pushImageFilter(
522522
ImageFilter filter, {
523+
Offset offset = Offset.zero,
523524
ImageFilterEngineLayer? oldLayer,
524525
}) {
525526
assert(filter != null);
526527
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushImageFilter'));
527528
final _ImageFilter nativeFilter = filter._toNativeImageFilter();
528529
assert(nativeFilter != null);
529530
final EngineLayer engineLayer = EngineLayer._();
530-
_pushImageFilter(engineLayer, nativeFilter, oldLayer?._nativeLayer);
531+
_pushImageFilter(engineLayer, nativeFilter, offset.dx, offset.dy, oldLayer?._nativeLayer);
531532
final ImageFilterEngineLayer layer = ImageFilterEngineLayer._(engineLayer);
532533
assert(_debugPushLayer(layer));
533534
return layer;
534535
}
535536

536-
@FfiNative<Void Function(Pointer<Void>, Handle, Pointer<Void>, Handle)>('SceneBuilder::pushImageFilter')
537-
external void _pushImageFilter(EngineLayer outEngineLayer, _ImageFilter filter, EngineLayer? oldLayer);
537+
@FfiNative<Void Function(Pointer<Void>, Handle, Pointer<Void>, Double, Double, Handle)>('SceneBuilder::pushImageFilter')
538+
external void _pushImageFilter(EngineLayer outEngineLayer, _ImageFilter filter, double dx, double dy, EngineLayer? oldLayer);
538539

539540
/// Pushes a backdrop filter operation onto the operation stack.
540541
///

lib/ui/compositing/scene_builder.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,11 @@ void SceneBuilder::pushColorFilter(Dart_Handle layer_handle,
151151

152152
void SceneBuilder::pushImageFilter(Dart_Handle layer_handle,
153153
const ImageFilter* image_filter,
154+
double dx,
155+
double dy,
154156
const fml::RefPtr<EngineLayer>& oldLayer) {
155-
auto layer =
156-
std::make_shared<flutter::ImageFilterLayer>(image_filter->filter());
157+
auto layer = std::make_shared<flutter::ImageFilterLayer>(
158+
image_filter->filter(), SkPoint::Make(dx, dy));
157159
PushLayer(layer);
158160
EngineLayer::MakeRetained(layer_handle, layer);
159161

lib/ui/compositing/scene_builder.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class SceneBuilder : public RefCountedDartWrappable<SceneBuilder> {
7474
const fml::RefPtr<EngineLayer>& oldLayer);
7575
void pushImageFilter(Dart_Handle layer_handle,
7676
const ImageFilter* image_filter,
77+
double dx,
78+
double dy,
7779
const fml::RefPtr<EngineLayer>& oldLayer);
7880
void pushBackdropFilter(Dart_Handle layer_handle,
7981
ImageFilter* filter,

lib/web_ui/lib/compositing.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ abstract class SceneBuilder {
7171
});
7272
ImageFilterEngineLayer pushImageFilter(
7373
ImageFilter filter, {
74+
Offset offset = Offset.zero,
7475
ImageFilterEngineLayer? oldLayer,
7576
});
7677
BackdropFilterEngineLayer pushBackdropFilter(

lib/web_ui/lib/src/engine/canvaskit/layer.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,18 +394,22 @@ class OffsetEngineLayer extends TransformEngineLayer
394394
/// A layer that applies an [ui.ImageFilter] to its children.
395395
class ImageFilterEngineLayer extends ContainerLayer
396396
implements ui.ImageFilterEngineLayer {
397-
ImageFilterEngineLayer(this._filter);
397+
ImageFilterEngineLayer(this._filter, this._offset);
398398

399+
final ui.Offset _offset;
399400
final ui.ImageFilter _filter;
400401

401402
@override
402403
void paint(PaintContext paintContext) {
403404
assert(needsPainting);
405+
paintContext.internalNodesCanvas.save();
406+
paintContext.internalNodesCanvas.translate(_offset.dx, _offset.dy);
404407
final CkPaint paint = CkPaint();
405408
paint.imageFilter = _filter;
406409
paintContext.internalNodesCanvas.saveLayer(paintBounds, paint);
407410
paintChildren(paintContext);
408411
paintContext.internalNodesCanvas.restore();
412+
paintContext.internalNodesCanvas.restore();
409413
}
410414

411415
// TODO(dnfield): dispose of the _filter

lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,10 @@ class LayerSceneBuilder implements ui.SceneBuilder {
155155
ImageFilterEngineLayer pushImageFilter(
156156
ui.ImageFilter filter, {
157157
ui.ImageFilterEngineLayer? oldLayer,
158+
ui.Offset offset = ui.Offset.zero,
158159
}) {
159160
assert(filter != null);
160-
return pushLayer<ImageFilterEngineLayer>(ImageFilterEngineLayer(filter));
161+
return pushLayer<ImageFilterEngineLayer>(ImageFilterEngineLayer(filter, offset));
161162
}
162163

163164
@override

lib/web_ui/lib/src/engine/html/image_filter.dart

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,81 @@ import 'package:ui/ui.dart' as ui;
77
import '../color_filter.dart';
88
import '../dom.dart';
99
import '../embedder.dart';
10+
import '../util.dart';
11+
import '../vector_math.dart';
1012
import 'shaders/shader.dart';
1113
import 'surface.dart';
14+
import 'surface_stats.dart';
1215

1316
/// A surface that applies an [imageFilter] to its children.
1417
class PersistedImageFilter extends PersistedContainerSurface
1518
implements ui.ImageFilterEngineLayer {
16-
PersistedImageFilter(PersistedImageFilter? super.oldLayer, this.filter);
19+
PersistedImageFilter(PersistedImageFilter? super.oldLayer, this.filter, this.offset);
1720

1821
final ui.ImageFilter filter;
22+
final ui.Offset offset;
23+
24+
@override
25+
void recomputeTransformAndClip() {
26+
transform = parent!.transform;
27+
28+
final double dx = offset.dx;
29+
final double dy = offset.dy;
30+
31+
if (dx != 0.0 || dy != 0.0) {
32+
transform = transform!.clone();
33+
transform!.translate(dx, dy);
34+
}
35+
projectedClip = null;
36+
}
37+
38+
/// Cached inverse of transform on this node. Unlike transform, this
39+
/// Matrix only contains local transform (not chain multiplied since root).
40+
Matrix4? _localTransformInverse;
41+
42+
@override
43+
Matrix4 get localTransformInverse => _localTransformInverse ??=
44+
Matrix4.translationValues(-offset.dx, -offset.dy, 0);
1945

2046
DomElement? _svgFilter;
47+
@override
48+
DomElement? get childContainer => _childContainer;
49+
DomElement? _childContainer;
2150

2251
@override
2352
void adoptElements(PersistedImageFilter oldSurface) {
2453
super.adoptElements(oldSurface);
2554
_svgFilter = oldSurface._svgFilter;
55+
_childContainer = oldSurface._childContainer;
56+
oldSurface._svgFilter = null;
57+
oldSurface._childContainer = null;
2658
}
2759

2860
@override
2961
void discard() {
3062
super.discard();
3163
flutterViewEmbedder.removeResource(_svgFilter);
3264
_svgFilter = null;
65+
_childContainer = null;
3366
}
3467

3568
@override
3669
DomElement createElement() {
37-
return defaultCreateElement('flt-image-filter');
70+
final DomElement element = defaultCreateElement('flt-image-filter');
71+
final DomElement container = defaultCreateElement('flt-image-filter-interior');
72+
if (debugExplainSurfaceStats) {
73+
// This creates an additional interior element. Count it too.
74+
surfaceStatsFor(this).allocatedDomNodeCount++;
75+
}
76+
77+
setElementStyle(container, 'position', 'absolute');
78+
setElementStyle(container, 'transform-origin', '0 0 0');
79+
setElementStyle(element, 'position', 'absolute');
80+
setElementStyle(element, 'transform-origin', '0 0 0');
81+
82+
_childContainer = container;
83+
element.appendChild(container);
84+
return element;
3885
}
3986

4087
@override
@@ -57,15 +104,18 @@ class PersistedImageFilter extends PersistedContainerSurface
57104
_svgFilter = backendFilter.makeSvgFilter(rootElement);
58105
}
59106

60-
rootElement!.style.filter = backendFilter.filterAttribute;
61-
rootElement!.style.transform = backendFilter.transformAttribute;
107+
_childContainer!.style.filter = backendFilter.filterAttribute;
108+
_childContainer!.style.transform = backendFilter.transformAttribute;
109+
rootElement!.style
110+
..left = '${offset.dx}px'
111+
..top = '${offset.dy}px';
62112
}
63113

64114
@override
65115
void update(PersistedImageFilter oldSurface) {
66116
super.update(oldSurface);
67117

68-
if (oldSurface.filter != filter) {
118+
if (oldSurface.filter != filter || oldSurface.offset != offset) {
69119
apply();
70120
}
71121
}

lib/web_ui/lib/src/engine/html/scene_builder.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,12 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
223223
@override
224224
ui.ImageFilterEngineLayer pushImageFilter(
225225
ui.ImageFilter filter, {
226+
ui.Offset offset = ui.Offset.zero,
226227
ui.ImageFilterEngineLayer? oldLayer,
227228
}) {
228229
assert(filter != null);
229230
return _pushSurface<PersistedImageFilter>(
230-
PersistedImageFilter(oldLayer as PersistedImageFilter?, filter));
231+
PersistedImageFilter(oldLayer as PersistedImageFilter?, filter, offset));
231232
}
232233

233234
/// Pushes a backdrop filter operation onto the operation stack.

0 commit comments

Comments
 (0)