Skip to content

Commit

Permalink
Engine support for ImageFiltered widget (flutter#14491)
Browse files Browse the repository at this point in the history
web_ui support coming in flutter/flutter#47163
  • Loading branch information
flar authored Dec 20, 2019
1 parent bd58af7 commit 929b1ed
Show file tree
Hide file tree
Showing 15 changed files with 445 additions and 3 deletions.
3 changes: 3 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ FILE: ../../../flutter/flow/layers/elevated_container_layer.cc
FILE: ../../../flutter/flow/layers/elevated_container_layer.h
FILE: ../../../flutter/flow/layers/fuchsia_system_composited_layer.cc
FILE: ../../../flutter/flow/layers/fuchsia_system_composited_layer.h
FILE: ../../../flutter/flow/layers/image_filter_layer.cc
FILE: ../../../flutter/flow/layers/image_filter_layer.h
FILE: ../../../flutter/flow/layers/image_filter_layer_unittests.cc
FILE: ../../../flutter/flow/layers/layer.cc
FILE: ../../../flutter/flow/layers/layer.h
FILE: ../../../flutter/flow/layers/layer_tree.cc
Expand Down
3 changes: 3 additions & 0 deletions flow/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ source_set("flow") {
"layers/container_layer.h",
"layers/elevated_container_layer.cc",
"layers/elevated_container_layer.h",
"layers/image_filter_layer.cc",
"layers/image_filter_layer.h",
"layers/layer.cc",
"layers/layer.h",
"layers/layer_tree.cc",
Expand Down Expand Up @@ -139,6 +141,7 @@ executable("flow_unittests") {
"layers/clip_rrect_layer_unittests.cc",
"layers/color_filter_layer_unittests.cc",
"layers/container_layer_unittests.cc",
"layers/image_filter_layer_unittests.cc",
"layers/layer_tree_unittests.cc",
"layers/opacity_layer_unittests.cc",
"layers/performance_overlay_layer_unittests.cc",
Expand Down
2 changes: 1 addition & 1 deletion flow/layers/color_filter_layer_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ TEST_F(ColorFilterLayerTest, PaintingEmptyLayerDies) {
"needs_painting\\(\\)");
}

TEST_F(ColorFilterLayerTest, PaintBeforePreollDies) {
TEST_F(ColorFilterLayerTest, PaintBeforePrerollDies) {
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
const SkPath child_path = SkPath().addRect(child_bounds);
auto mock_layer = std::make_shared<MockLayer>(child_path);
Expand Down
56 changes: 56 additions & 0 deletions flow/layers/image_filter_layer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/flow/layers/image_filter_layer.h"

namespace flutter {

ImageFilterLayer::ImageFilterLayer(sk_sp<SkImageFilter> filter)
: filter_(std::move(filter)) {}

void ImageFilterLayer::Preroll(PrerollContext* context,
const SkMatrix& matrix) {
Layer::AutoPrerollSaveLayerState save =
Layer::AutoPrerollSaveLayerState::Create(context);
ContainerLayer::Preroll(context, matrix);

if (!context->has_platform_view && context->raster_cache &&
SkRect::Intersects(context->cull_rect, paint_bounds())) {
SkMatrix ctm = matrix;
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
ctm = RasterCache::GetIntegralTransCTM(ctm);
#endif
context->raster_cache->Prepare(context, this, ctm);
}
}

void ImageFilterLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("flutter", "ImageFilterLayer::Paint");
FML_DCHECK(needs_painting());

#ifndef SUPPORT_FRACTIONAL_TRANSLATION
SkAutoCanvasRestore save(context.leaf_nodes_canvas, true);
context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM(
context.leaf_nodes_canvas->getTotalMatrix()));
#endif

if (context.raster_cache) {
const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix();
RasterCacheResult layer_cache =
context.raster_cache->Get((Layer*)this, ctm);
if (layer_cache.is_valid()) {
layer_cache.draw(*context.leaf_nodes_canvas);
return;
}
}

SkPaint paint;
paint.setImageFilter(filter_);

Layer::AutoSaveLayer save_layer =
Layer::AutoSaveLayer::Create(context, paint_bounds(), &paint);
PaintChildren(context);
}

} // namespace flutter
30 changes: 30 additions & 0 deletions flow/layers/image_filter_layer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_
#define FLUTTER_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_

#include "flutter/flow/layers/container_layer.h"

#include "third_party/skia/include/core/SkImageFilter.h"

namespace flutter {

class ImageFilterLayer : public ContainerLayer {
public:
ImageFilterLayer(sk_sp<SkImageFilter> filter);

void Preroll(PrerollContext* context, const SkMatrix& matrix) override;

void Paint(PaintContext& context) const override;

private:
sk_sp<SkImageFilter> filter_;

FML_DISALLOW_COPY_AND_ASSIGN(ImageFilterLayer);
};

} // namespace flutter

#endif // FLUTTER_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_
236 changes: 236 additions & 0 deletions flow/layers/image_filter_layer_unittests.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/flow/layers/image_filter_layer.h"

#include "flutter/flow/testing/layer_test.h"
#include "flutter/flow/testing/mock_layer.h"
#include "flutter/fml/macros.h"
#include "flutter/testing/mock_canvas.h"
#include "third_party/skia/include/core/SkImageFilter.h"

namespace flutter {
namespace testing {

using ImageFilterLayerTest = LayerTest;

#ifndef NDEBUG
TEST_F(ImageFilterLayerTest, PaintingEmptyLayerDies) {
auto layer = std::make_shared<ImageFilterLayer>(sk_sp<SkImageFilter>());

layer->Preroll(preroll_context(), SkMatrix());
EXPECT_EQ(layer->paint_bounds(), kEmptyRect);
EXPECT_FALSE(layer->needs_painting());
EXPECT_FALSE(layer->needs_system_composite());

EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
"needs_painting\\(\\)");
}

TEST_F(ImageFilterLayerTest, PaintBeforePrerollDies) {
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
const SkPath child_path = SkPath().addRect(child_bounds);
auto mock_layer = std::make_shared<MockLayer>(child_path);
auto layer = std::make_shared<ImageFilterLayer>(sk_sp<SkImageFilter>());
layer->Add(mock_layer);

EXPECT_EQ(layer->paint_bounds(), kEmptyRect);
EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),
"needs_painting\\(\\)");
}
#endif

TEST_F(ImageFilterLayerTest, EmptyFilter) {
const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f);
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);
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
auto layer = std::make_shared<ImageFilterLayer>(nullptr);
layer->Add(mock_layer);

layer->Preroll(preroll_context(), initial_transform);
EXPECT_EQ(layer->paint_bounds(), child_bounds);
EXPECT_TRUE(layer->needs_painting());
EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);

SkPaint filter_paint;
filter_paint.setImageFilter(nullptr);
layer->Paint(paint_context());
EXPECT_EQ(mock_canvas().draw_calls(),
std::vector({
MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},
MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkMatrix()}},
MockCanvas::DrawCall{
1, MockCanvas::SaveLayerData{child_bounds, filter_paint,
nullptr, 2}},
MockCanvas::DrawCall{
2, MockCanvas::DrawPathData{child_path, child_paint}},
MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}},
}));
}

TEST_F(ImageFilterLayerTest, SimpleFilter) {
const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f);
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);
auto layer_filter = SkImageFilter::MakeMatrixFilter(
SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr);
auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);
auto layer = std::make_shared<ImageFilterLayer>(layer_filter);
layer->Add(mock_layer);

layer->Preroll(preroll_context(), initial_transform);
EXPECT_EQ(layer->paint_bounds(), child_bounds);
EXPECT_TRUE(layer->needs_painting());
EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);

SkPaint filter_paint;
filter_paint.setImageFilter(layer_filter);
layer->Paint(paint_context());
EXPECT_EQ(mock_canvas().draw_calls(),
std::vector({
MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},
MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkMatrix()}},
MockCanvas::DrawCall{
1, MockCanvas::SaveLayerData{child_bounds, filter_paint,
nullptr, 2}},
MockCanvas::DrawCall{
2, MockCanvas::DrawPathData{child_path, child_paint}},
MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}},
}));
}

TEST_F(ImageFilterLayerTest, MultipleChildren) {
const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f);
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f);
const SkPath child_path1 = SkPath().addRect(child_bounds);
const SkPath child_path2 =
SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f));
const SkPaint child_paint1 = SkPaint(SkColors::kYellow);
const SkPaint child_paint2 = SkPaint(SkColors::kCyan);
auto layer_filter = SkImageFilter::MakeMatrixFilter(
SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr);
auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);
auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);
auto layer = std::make_shared<ImageFilterLayer>(layer_filter);
layer->Add(mock_layer1);
layer->Add(mock_layer2);

SkRect children_bounds = child_path1.getBounds();
children_bounds.join(child_path2.getBounds());
layer->Preroll(preroll_context(), initial_transform);
EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds());
EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds());
EXPECT_EQ(layer->paint_bounds(), children_bounds);
EXPECT_TRUE(mock_layer1->needs_painting());
EXPECT_TRUE(mock_layer2->needs_painting());
EXPECT_TRUE(layer->needs_painting());
EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);
EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);

SkPaint filter_paint;
filter_paint.setImageFilter(layer_filter);
layer->Paint(paint_context());
EXPECT_EQ(mock_canvas().draw_calls(),
std::vector(
{MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},
MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkMatrix()}},
MockCanvas::DrawCall{
1, MockCanvas::SaveLayerData{children_bounds, filter_paint,
nullptr, 2}},
MockCanvas::DrawCall{
2, MockCanvas::DrawPathData{child_path1, child_paint1}},
MockCanvas::DrawCall{
2, MockCanvas::DrawPathData{child_path2, child_paint2}},
MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));
}

TEST_F(ImageFilterLayerTest, Nested) {
const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f);
const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f);
const SkPath child_path1 = SkPath().addRect(child_bounds);
const SkPath child_path2 =
SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f));
const SkPaint child_paint1 = SkPaint(SkColors::kYellow);
const SkPaint child_paint2 = SkPaint(SkColors::kCyan);
auto layer_filter1 = SkImageFilter::MakeMatrixFilter(
SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr);
auto layer_filter2 = SkImageFilter::MakeMatrixFilter(
SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr);
auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);
auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);
auto layer1 = std::make_shared<ImageFilterLayer>(layer_filter1);
auto layer2 = std::make_shared<ImageFilterLayer>(layer_filter2);
layer2->Add(mock_layer2);
layer1->Add(mock_layer1);
layer1->Add(layer2);

SkRect children_bounds = child_path1.getBounds();
children_bounds.join(child_path2.getBounds());
layer1->Preroll(preroll_context(), initial_transform);
EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds());
EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds());
EXPECT_EQ(layer1->paint_bounds(), children_bounds);
EXPECT_EQ(layer2->paint_bounds(), mock_layer2->paint_bounds());
EXPECT_TRUE(mock_layer1->needs_painting());
EXPECT_TRUE(mock_layer2->needs_painting());
EXPECT_TRUE(layer1->needs_painting());
EXPECT_TRUE(layer2->needs_painting());
EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);
EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);

SkPaint filter_paint1, filter_paint2;
filter_paint1.setImageFilter(layer_filter1);
filter_paint2.setImageFilter(layer_filter2);
layer1->Paint(paint_context());
EXPECT_EQ(mock_canvas().draw_calls(),
std::vector({
MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},
MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkMatrix()}},
MockCanvas::DrawCall{
1, MockCanvas::SaveLayerData{children_bounds, filter_paint1,
nullptr, 2}},
MockCanvas::DrawCall{
2, MockCanvas::DrawPathData{child_path1, child_paint1}},
MockCanvas::DrawCall{2, MockCanvas::SaveData{3}},
MockCanvas::DrawCall{3, MockCanvas::SetMatrixData{SkMatrix()}},
MockCanvas::DrawCall{
3, MockCanvas::SaveLayerData{child_path2.getBounds(),
filter_paint2, nullptr, 4}},
MockCanvas::DrawCall{
4, MockCanvas::DrawPathData{child_path2, child_paint2}},
MockCanvas::DrawCall{4, MockCanvas::RestoreData{3}},
MockCanvas::DrawCall{3, MockCanvas::RestoreData{2}},
MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},
MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}},
}));
}

TEST_F(ImageFilterLayerTest, Readback) {
auto layer_filter = SkImageFilter::MakeMatrixFilter(
SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr);
auto initial_transform = SkMatrix();

// ImageFilterLayer does not read from surface
auto layer = std::make_shared<ImageFilterLayer>(layer_filter);
preroll_context()->surface_needs_readback = false;
layer->Preroll(preroll_context(), initial_transform);
EXPECT_FALSE(preroll_context()->surface_needs_readback);

// ImageFilterLayer blocks child with readback
auto mock_layer =
std::make_shared<MockLayer>(SkPath(), SkPaint(), false, false, true);
layer->Add(mock_layer);
preroll_context()->surface_needs_readback = false;
layer->Preroll(preroll_context(), initial_transform);
EXPECT_FALSE(preroll_context()->surface_needs_readback);
}

} // namespace testing
} // namespace flutter
Loading

0 comments on commit 929b1ed

Please sign in to comment.