Add optional offset parameter to ImageFilterLayer (flutter/engine#36863)

* add optional offset parameter to ImageFilterLayer

* update unit test for LayerStateStack and fix HTML implementation
This commit is contained in:
Jim Graham
2022-11-21 09:59:55 -08:00
committed by GitHub
parent 2a9bcb43d1
commit 07fc5a59f9
11 changed files with 142 additions and 17 deletions

View File

@@ -9,9 +9,11 @@
namespace flutter {
ImageFilterLayer::ImageFilterLayer(std::shared_ptr<const DlImageFilter> filter)
ImageFilterLayer::ImageFilterLayer(std::shared_ptr<const DlImageFilter> filter,
const SkPoint& offset)
: CacheableContainerLayer(
RasterCacheUtil::kMinimumRendersBeforeCachingFilterLayer),
offset_(offset),
filter_(std::move(filter)),
transformed_filter_(nullptr) {}
@@ -20,11 +22,12 @@ void ImageFilterLayer::Diff(DiffContext* context, const Layer* old_layer) {
auto* prev = static_cast<const ImageFilterLayer*>(old_layer);
if (!context->IsSubtreeDirty()) {
FML_DCHECK(prev);
if (NotEquals(filter_, prev->filter_)) {
if (NotEquals(filter_, prev->filter_) || offset_ != prev->offset_) {
context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));
}
}
context->PushTransform(SkMatrix::Translate(offset_.fX, offset_.fY));
if (context->has_raster_cache()) {
context->SetTransform(
RasterCacheUtil::GetIntegralTransCTM(context->GetTransform()));
@@ -47,6 +50,9 @@ void ImageFilterLayer::Diff(DiffContext* context, const Layer* old_layer) {
}
void ImageFilterLayer::Preroll(PrerollContext* context) {
auto mutator = context->state_stack.save();
mutator.translate(offset_);
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context);
@@ -73,7 +79,8 @@ void ImageFilterLayer::Preroll(PrerollContext* context) {
SkIRect filter_out_bounds;
filter_->map_device_bounds(filter_in_bounds, SkMatrix::I(),
filter_out_bounds);
child_bounds = SkRect::Make(filter_out_bounds);
child_bounds.set(filter_out_bounds);
child_bounds.offset(offset_);
set_paint_bounds(child_bounds);
@@ -92,6 +99,7 @@ void ImageFilterLayer::Paint(PaintContext& context) const {
FML_DCHECK(needs_painting(context));
auto mutator = context.state_stack.save();
mutator.translate(offset_);
if (context.raster_cache) {
// Always apply the integral transform in the presence of a raster cache

View File

@@ -15,7 +15,8 @@ namespace flutter {
class ImageFilterLayer : public CacheableContainerLayer {
public:
explicit ImageFilterLayer(std::shared_ptr<const DlImageFilter> filter);
explicit ImageFilterLayer(std::shared_ptr<const DlImageFilter> filter,
const SkPoint& offset = SkPoint::Make(0, 0));
void Diff(DiffContext* context, const Layer* old_layer) override;
@@ -24,6 +25,7 @@ class ImageFilterLayer : public CacheableContainerLayer {
void Paint(PaintContext& context) const override;
private:
SkPoint offset_;
std::shared_ptr<const DlImageFilter> filter_;
std::shared_ptr<const DlImageFilter> transformed_filter_;

View File

@@ -114,6 +114,59 @@ TEST_F(ImageFilterLayerTest, SimpleFilter) {
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_display_list));
}
TEST_F(ImageFilterLayerTest, SimpleFilterWithOffset) {
const SkMatrix initial_transform = SkMatrix::Translate(0.5f, 1.0f);
const SkRect initial_cull_rect = SkRect::MakeLTRB(0, 0, 100, 100);
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
const SkPath child_path = SkPath().addRect(child_bounds);
const SkPaint child_paint = SkPaint(SkColors::kYellow);
const SkPoint layer_offset = SkPoint::Make(5.5, 6.5);
auto dl_image_filter = std::make_shared<DlMatrixImageFilter>(
SkMatrix(), DlImageSampling::kMipmapLinear);
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
auto layer =
std::make_shared<ImageFilterLayer>(dl_image_filter, layer_offset);
layer->Add(mock_layer);
SkMatrix child_matrix = initial_transform;
child_matrix.preTranslate(layer_offset.fX, layer_offset.fY);
const SkRect child_rounded_bounds =
SkRect::MakeLTRB(10.5f, 12.5f, 26.5f, 28.5f);
preroll_context()->state_stack.set_preroll_delegate(initial_cull_rect,
initial_transform);
layer->Preroll(preroll_context());
EXPECT_EQ(layer->paint_bounds(), child_rounded_bounds);
EXPECT_EQ(layer->child_paint_bounds(), child_bounds);
EXPECT_TRUE(layer->needs_painting(paint_context()));
EXPECT_EQ(mock_layer->parent_matrix(), child_matrix);
EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),
initial_cull_rect);
DisplayListBuilder expected_builder;
/* ImageFilterLayer::Paint() */ {
expected_builder.save();
{
expected_builder.translate(layer_offset.fX, layer_offset.fY);
DlPaint dl_paint;
dl_paint.setImageFilter(dl_image_filter.get());
expected_builder.saveLayer(&child_bounds, &dl_paint);
{
/* MockLayer::Paint() */ {
expected_builder.drawPath(child_path,
DlPaint().setColor(DlColor::kYellow()));
}
}
expected_builder.restore();
}
expected_builder.restore();
}
auto expected_display_list = expected_builder.Build();
layer->Paint(display_list_paint_context());
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_display_list));
}
TEST_F(ImageFilterLayerTest, SimpleFilterBounds) {
const SkMatrix initial_transform = SkMatrix::Translate(0.5f, 1.0f);
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);

View File

@@ -520,6 +520,7 @@ class SceneBuilder extends NativeFieldWrapperClass1 {
/// See [pop] for details about the operation stack.
ImageFilterEngineLayer pushImageFilter(
ImageFilter filter, {
Offset offset = Offset.zero,
ImageFilterEngineLayer? oldLayer,
}) {
assert(filter != null);
@@ -527,14 +528,14 @@ class SceneBuilder extends NativeFieldWrapperClass1 {
final _ImageFilter nativeFilter = filter._toNativeImageFilter();
assert(nativeFilter != null);
final EngineLayer engineLayer = EngineLayer._();
_pushImageFilter(engineLayer, nativeFilter, oldLayer?._nativeLayer);
_pushImageFilter(engineLayer, nativeFilter, offset.dx, offset.dy, oldLayer?._nativeLayer);
final ImageFilterEngineLayer layer = ImageFilterEngineLayer._(engineLayer);
assert(_debugPushLayer(layer));
return layer;
}
@FfiNative<Void Function(Pointer<Void>, Handle, Pointer<Void>, Handle)>('SceneBuilder::pushImageFilter')
external void _pushImageFilter(EngineLayer outEngineLayer, _ImageFilter filter, EngineLayer? oldLayer);
@FfiNative<Void Function(Pointer<Void>, Handle, Pointer<Void>, Double, Double, Handle)>('SceneBuilder::pushImageFilter')
external void _pushImageFilter(EngineLayer outEngineLayer, _ImageFilter filter, double dx, double dy, EngineLayer? oldLayer);
/// Pushes a backdrop filter operation onto the operation stack.
///

View File

@@ -151,9 +151,11 @@ void SceneBuilder::pushColorFilter(Dart_Handle layer_handle,
void SceneBuilder::pushImageFilter(Dart_Handle layer_handle,
const ImageFilter* image_filter,
double dx,
double dy,
const fml::RefPtr<EngineLayer>& oldLayer) {
auto layer =
std::make_shared<flutter::ImageFilterLayer>(image_filter->filter());
auto layer = std::make_shared<flutter::ImageFilterLayer>(
image_filter->filter(), SkPoint::Make(dx, dy));
PushLayer(layer);
EngineLayer::MakeRetained(layer_handle, layer);

View File

@@ -74,6 +74,8 @@ class SceneBuilder : public RefCountedDartWrappable<SceneBuilder> {
const fml::RefPtr<EngineLayer>& oldLayer);
void pushImageFilter(Dart_Handle layer_handle,
const ImageFilter* image_filter,
double dx,
double dy,
const fml::RefPtr<EngineLayer>& oldLayer);
void pushBackdropFilter(Dart_Handle layer_handle,
ImageFilter* filter,

View File

@@ -71,6 +71,7 @@ abstract class SceneBuilder {
});
ImageFilterEngineLayer pushImageFilter(
ImageFilter filter, {
Offset offset = Offset.zero,
ImageFilterEngineLayer? oldLayer,
});
BackdropFilterEngineLayer pushBackdropFilter(

View File

@@ -394,18 +394,22 @@ class OffsetEngineLayer extends TransformEngineLayer
/// A layer that applies an [ui.ImageFilter] to its children.
class ImageFilterEngineLayer extends ContainerLayer
implements ui.ImageFilterEngineLayer {
ImageFilterEngineLayer(this._filter);
ImageFilterEngineLayer(this._filter, this._offset);
final ui.Offset _offset;
final ui.ImageFilter _filter;
@override
void paint(PaintContext paintContext) {
assert(needsPainting);
paintContext.internalNodesCanvas.save();
paintContext.internalNodesCanvas.translate(_offset.dx, _offset.dy);
final CkPaint paint = CkPaint();
paint.imageFilter = _filter;
paintContext.internalNodesCanvas.saveLayer(paintBounds, paint);
paintChildren(paintContext);
paintContext.internalNodesCanvas.restore();
paintContext.internalNodesCanvas.restore();
}
// TODO(dnfield): dispose of the _filter

View File

@@ -155,9 +155,10 @@ class LayerSceneBuilder implements ui.SceneBuilder {
ImageFilterEngineLayer pushImageFilter(
ui.ImageFilter filter, {
ui.ImageFilterEngineLayer? oldLayer,
ui.Offset offset = ui.Offset.zero,
}) {
assert(filter != null);
return pushLayer<ImageFilterEngineLayer>(ImageFilterEngineLayer(filter));
return pushLayer<ImageFilterEngineLayer>(ImageFilterEngineLayer(filter, offset));
}
@override

View File

@@ -7,22 +7,54 @@ import 'package:ui/ui.dart' as ui;
import '../color_filter.dart';
import '../dom.dart';
import '../embedder.dart';
import '../util.dart';
import '../vector_math.dart';
import 'shaders/shader.dart';
import 'surface.dart';
import 'surface_stats.dart';
/// A surface that applies an [imageFilter] to its children.
class PersistedImageFilter extends PersistedContainerSurface
implements ui.ImageFilterEngineLayer {
PersistedImageFilter(PersistedImageFilter? super.oldLayer, this.filter);
PersistedImageFilter(PersistedImageFilter? super.oldLayer, this.filter, this.offset);
final ui.ImageFilter filter;
final ui.Offset offset;
@override
void recomputeTransformAndClip() {
transform = parent!.transform;
final double dx = offset.dx;
final double dy = offset.dy;
if (dx != 0.0 || dy != 0.0) {
transform = transform!.clone();
transform!.translate(dx, dy);
}
projectedClip = null;
}
/// Cached inverse of transform on this node. Unlike transform, this
/// Matrix only contains local transform (not chain multiplied since root).
Matrix4? _localTransformInverse;
@override
Matrix4 get localTransformInverse => _localTransformInverse ??=
Matrix4.translationValues(-offset.dx, -offset.dy, 0);
DomElement? _svgFilter;
@override
DomElement? get childContainer => _childContainer;
DomElement? _childContainer;
@override
void adoptElements(PersistedImageFilter oldSurface) {
super.adoptElements(oldSurface);
_svgFilter = oldSurface._svgFilter;
_childContainer = oldSurface._childContainer;
oldSurface._svgFilter = null;
oldSurface._childContainer = null;
}
@override
@@ -30,11 +62,26 @@ class PersistedImageFilter extends PersistedContainerSurface
super.discard();
flutterViewEmbedder.removeResource(_svgFilter);
_svgFilter = null;
_childContainer = null;
}
@override
DomElement createElement() {
return defaultCreateElement('flt-image-filter');
final DomElement element = defaultCreateElement('flt-image-filter');
final DomElement container = defaultCreateElement('flt-image-filter-interior');
if (debugExplainSurfaceStats) {
// This creates an additional interior element. Count it too.
surfaceStatsFor(this).allocatedDomNodeCount++;
}
setElementStyle(container, 'position', 'absolute');
setElementStyle(container, 'transform-origin', '0 0 0');
setElementStyle(element, 'position', 'absolute');
setElementStyle(element, 'transform-origin', '0 0 0');
_childContainer = container;
element.appendChild(container);
return element;
}
@override
@@ -57,15 +104,18 @@ class PersistedImageFilter extends PersistedContainerSurface
_svgFilter = backendFilter.makeSvgFilter(rootElement);
}
rootElement!.style.filter = backendFilter.filterAttribute;
rootElement!.style.transform = backendFilter.transformAttribute;
_childContainer!.style.filter = backendFilter.filterAttribute;
_childContainer!.style.transform = backendFilter.transformAttribute;
rootElement!.style
..left = '${offset.dx}px'
..top = '${offset.dy}px';
}
@override
void update(PersistedImageFilter oldSurface) {
super.update(oldSurface);
if (oldSurface.filter != filter) {
if (oldSurface.filter != filter || oldSurface.offset != offset) {
apply();
}
}

View File

@@ -223,11 +223,12 @@ class SurfaceSceneBuilder implements ui.SceneBuilder {
@override
ui.ImageFilterEngineLayer pushImageFilter(
ui.ImageFilter filter, {
ui.Offset offset = ui.Offset.zero,
ui.ImageFilterEngineLayer? oldLayer,
}) {
assert(filter != null);
return _pushSurface<PersistedImageFilter>(
PersistedImageFilter(oldLayer as PersistedImageFilter?, filter));
PersistedImageFilter(oldLayer as PersistedImageFilter?, filter, offset));
}
/// Pushes a backdrop filter operation onto the operation stack.