[Impeller] convert aiks blur tests to new canvas. (flutter/engine#54565)

Part of DL interop. Currently experiencing some ... issues with blur radius/sigma.
EDIT: was rrect_blur vs gaussian: fixed now.

Part of https://github.com/flutter/flutter/issues/142054

Reland because of CI issue?
This commit is contained in:
Jonah Williams 2024-08-15 10:39:18 -07:00 committed by GitHub
parent eaca267327
commit 90dcf59597
6 changed files with 985 additions and 902 deletions

View File

@ -145,6 +145,7 @@
../../../flutter/impeller/display_list/aiks_dl_atlas_unittests.cc
../../../flutter/impeller/display_list/aiks_dl_basic_unittests.cc
../../../flutter/impeller/display_list/aiks_dl_blend_unittests.cc
../../../flutter/impeller/display_list/aiks_dl_blur_unittests.cc
../../../flutter/impeller/display_list/aiks_dl_clip_unittests.cc
../../../flutter/impeller/display_list/aiks_dl_gradient_unittests.cc
../../../flutter/impeller/display_list/aiks_dl_opacity_unittests.cc

View File

@ -58,6 +58,12 @@ struct DlColor {
static constexpr DlColor kCornflowerBlue() {return DlColor(0xFF6495ED);};
static constexpr DlColor kCrimson() {return DlColor(0xFFFF5733);};
static constexpr DlColor kAqua() {return DlColor(0xFF00FFFF);};
static constexpr DlColor kOrange() {return DlColor(0xFFFFA500);};
static constexpr DlColor kPurple() {return DlColor(0xFF800080);};
static constexpr DlColor kLimeGreen() {return DlColor(0xFF32CD32);};
static constexpr DlColor kGreenYellow() {return DlColor(0xFFADFF2F);};
static constexpr DlColor kDarkMagenta() {return DlColor(0xFF8B008B);};
static constexpr DlColor kOrangeRed() {return DlColor(0xFFFF4500);};
// clang-format on
constexpr bool isOpaque() const { return getAlpha() == 0xFF; }

View File

@ -5,7 +5,6 @@
#include "flutter/impeller/aiks/aiks_unittests.h"
#include "impeller/aiks/canvas.h"
#include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h"
#include "impeller/entity/render_target_cache.h"
#include "impeller/geometry/path_builder.h"
#include "impeller/playground/widgets.h"
@ -20,210 +19,6 @@
namespace impeller {
namespace testing {
TEST_P(AiksTest, CanRenderMaskBlurHugeSigma) {
Canvas canvas;
canvas.DrawCircle({400, 400}, 300,
{.color = Color::Green(),
.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Sigma(99999),
}});
canvas.Restore();
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, CanRenderForegroundBlendWithMaskBlur) {
// This case triggers the ForegroundPorterDuffBlend path. The color filter
// should apply to the color only, and respect the alpha mask.
Canvas canvas;
canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
canvas.DrawCircle({400, 400}, 200,
{
.color = Color::White(),
.color_filter = ColorFilter::MakeBlend(
BlendMode::kSource, Color::Green()),
.mask_blur_descriptor =
Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Radius(20),
},
});
canvas.Restore();
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) {
// This case triggers the ForegroundAdvancedBlend path. The color filter
// should apply to the color only, and respect the alpha mask.
Canvas canvas;
canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
canvas.DrawCircle({400, 400}, 200,
{
.color = Color::Grey(),
.color_filter = ColorFilter::MakeBlend(
BlendMode::kColor, Color::Green()),
.mask_blur_descriptor =
Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Radius(20),
},
});
canvas.Restore();
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, CanRenderBackdropBlurInteractive) {
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
static PlaygroundPoint point_a(Point(50, 50), 30, Color::White());
static PlaygroundPoint point_b(Point(300, 200), 30, Color::White());
auto [a, b] = DrawPlaygroundLine(point_a, point_b);
Canvas canvas;
canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()});
canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()});
canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()});
canvas.ClipRRect(Rect::MakeLTRB(a.x, a.y, b.x, b.y), {20, 20});
canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kClamp));
canvas.Restore();
return canvas.EndRecordingAsPicture();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, CanRenderBackdropBlur) {
Canvas canvas;
canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
canvas.DrawCircle({300, 200}, 100, {.color = Color::GreenYellow()});
canvas.DrawCircle({140, 170}, 75, {.color = Color::DarkMagenta()});
canvas.DrawCircle({180, 120}, 100, {.color = Color::OrangeRed()});
canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), {20, 20});
canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0),
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kClamp));
canvas.Restore();
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, CanRenderBackdropBlurHugeSigma) {
Canvas canvas;
canvas.DrawCircle({400, 400}, 300, {.color = Color::Green()});
canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
ImageFilter::MakeBlur(Sigma(999999), Sigma(999999),
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kClamp));
canvas.Restore();
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, CanRenderClippedBlur) {
Canvas canvas;
canvas.ClipRect(Rect::MakeXYWH(100, 150, 400, 400));
canvas.DrawCircle(
{400, 400}, 200,
{
.color = Color::Green(),
.image_filter = ImageFilter::MakeBlur(
Sigma(20.0), Sigma(20.0), FilterContents::BlurStyle::kNormal,
Entity::TileMode::kDecal),
});
canvas.Restore();
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) {
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
static PlaygroundPoint playground_point(Point(400, 400), 20,
Color::Green());
auto point = DrawPlaygroundPoint(playground_point);
Canvas canvas;
canvas.Translate(point - Point(400, 400));
Paint paint;
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Radius{120 * 3},
};
paint.color = Color::Red();
PathBuilder builder{};
builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
canvas.DrawPath(builder.TakePath(), paint);
return canvas.EndRecordingAsPicture();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) {
Canvas canvas;
canvas.Translate(Point(0, -400));
Paint paint;
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Radius{120 * 3},
};
paint.color = Color::Red();
PathBuilder builder{};
builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
canvas.DrawPath(builder.TakePath(), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, ClearBlendWithBlur) {
Canvas canvas;
Paint white;
white.color = Color::Blue();
canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white);
Paint clear;
clear.blend_mode = BlendMode::kClear;
clear.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Sigma(20),
};
canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, BlurHasNoEdge) {
Scalar sigma = 47.6;
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Sigma", &sigma, 0, 50);
ImGui::End();
}
Canvas canvas;
canvas.Scale(GetContentScale());
canvas.DrawPaint({});
Paint blur = {
.color = Color::Green(),
.mask_blur_descriptor =
Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Sigma(sigma),
},
};
canvas.DrawRect(Rect::MakeXYWH(300, 300, 200, 200), blur);
return canvas.EndRecordingAsPicture();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, BlurredRectangleWithShader) {
Canvas canvas;
canvas.Scale(GetContentScale());
@ -274,228 +69,6 @@ TEST_P(AiksTest, BlurredRectangleWithShader) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) {
Canvas canvas;
Paint paint = {
.color = Color::Blue(),
.mask_blur_descriptor =
Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Sigma(0),
},
};
canvas.DrawCircle({300, 300}, 200, paint);
canvas.DrawRect(Rect::MakeLTRB(100, 300, 500, 600), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
struct MaskBlurTestConfig {
FilterContents::BlurStyle style = FilterContents::BlurStyle::kNormal;
Scalar sigma = 1.0f;
Scalar alpha = 1.0f;
std::shared_ptr<ImageFilter> image_filter;
bool invert_colors = false;
BlendMode blend_mode = BlendMode::kSourceOver;
};
static Picture MaskBlurVariantTest(const AiksTest& test_context,
const MaskBlurTestConfig& config) {
Canvas canvas;
canvas.Scale(test_context.GetContentScale());
canvas.Scale(Vector2{0.8f, 0.8f});
Paint paint;
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Sigma{1},
};
canvas.DrawPaint({.color = Color::AntiqueWhite()});
paint.mask_blur_descriptor->style = config.style;
paint.mask_blur_descriptor->sigma = Sigma{config.sigma};
paint.image_filter = config.image_filter;
paint.invert_colors = config.invert_colors;
paint.blend_mode = config.blend_mode;
const Scalar x = 50;
const Scalar radius = 20.0f;
const Scalar y_spacing = 100.0f;
Scalar y = 50;
paint.color = Color::Crimson().WithAlpha(config.alpha);
canvas.DrawRect(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
radius, 60.0f - radius),
paint);
y += y_spacing;
paint.color = Color::Blue().WithAlpha(config.alpha);
canvas.DrawCircle({x + 25, y + 25}, radius, paint);
y += y_spacing;
paint.color = Color::Green().WithAlpha(config.alpha);
canvas.DrawOval(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
radius, 60.0f - radius),
paint);
y += y_spacing;
paint.color = Color::Purple().WithAlpha(config.alpha);
canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), //
{radius, radius}, //
paint);
y += y_spacing;
paint.color = Color::Orange().WithAlpha(config.alpha);
canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), //
{radius, 5.0f}, paint);
y += y_spacing;
paint.color = Color::Maroon().WithAlpha(config.alpha);
canvas.DrawPath(PathBuilder{}
.MoveTo({x + 0, y + 60})
.LineTo({x + 30, y + 0})
.LineTo({x + 60, y + 60})
.Close()
.TakePath(),
paint);
y += y_spacing;
paint.color = Color::Maroon().WithAlpha(config.alpha);
canvas.DrawPath(PathBuilder{}
.AddArc(Rect::MakeXYWH(x + 5, y, 50, 50),
Radians{kPi / 2}, Radians{kPi})
.AddArc(Rect::MakeXYWH(x + 25, y, 50, 50),
Radians{kPi / 2}, Radians{kPi})
.Close()
.TakePath(),
paint);
return canvas.EndRecordingAsPicture();
}
static const std::map<std::string, MaskBlurTestConfig> kPaintVariations = {
// 1. Normal style, translucent, zero sigma.
{"NormalTranslucentZeroSigma",
{.style = FilterContents::BlurStyle::kNormal,
.sigma = 0.0f,
.alpha = 0.5f}},
// 2. Normal style, translucent.
{"NormalTranslucent",
{.style = FilterContents::BlurStyle::kNormal,
.sigma = 8.0f,
.alpha = 0.5f}},
// 3. Solid style, translucent.
{"SolidTranslucent",
{.style = FilterContents::BlurStyle::kSolid,
.sigma = 8.0f,
.alpha = 0.5f}},
// 4. Solid style, opaque.
{"SolidOpaque",
{.style = FilterContents::BlurStyle::kSolid, .sigma = 8.0f}},
// 5. Solid style, translucent, color & image filtered.
{"SolidTranslucentWithFilters",
{.style = FilterContents::BlurStyle::kSolid,
.sigma = 8.0f,
.alpha = 0.5f,
.image_filter = ImageFilter::MakeBlur(Sigma{3},
Sigma{3},
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kClamp),
.invert_colors = true}},
// 6. Solid style, translucent, exclusion blended.
{"SolidTranslucentExclusionBlend",
{.style = FilterContents::BlurStyle::kSolid,
.sigma = 8.0f,
.alpha = 0.5f,
.blend_mode = BlendMode::kExclusion}},
// 7. Inner style, translucent.
{"InnerTranslucent",
{.style = FilterContents::BlurStyle::kInner,
.sigma = 8.0f,
.alpha = 0.5f}},
// 8. Inner style, translucent, blurred.
{"InnerTranslucentWithBlurImageFilter",
{.style = FilterContents::BlurStyle::kInner,
.sigma = 8.0f,
.alpha = 0.5f,
.image_filter = ImageFilter::MakeBlur(Sigma{3},
Sigma{3},
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kClamp)}},
// 9. Outer style, translucent.
{"OuterTranslucent",
{.style = FilterContents::BlurStyle::kOuter,
.sigma = 8.0f,
.alpha = 0.5f}},
// 10. Outer style, opaque, image filtered.
{"OuterOpaqueWithBlurImageFilter",
{.style = FilterContents::BlurStyle::kOuter,
.sigma = 8.0f,
.image_filter = ImageFilter::MakeBlur(Sigma{3},
Sigma{3},
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kClamp)}},
};
#define MASK_BLUR_VARIANT_TEST(config) \
TEST_P(AiksTest, MaskBlurVariantTest##config) { \
ASSERT_TRUE(OpenPlaygroundHere( \
MaskBlurVariantTest(*this, kPaintVariations.at(#config)))); \
}
MASK_BLUR_VARIANT_TEST(NormalTranslucentZeroSigma)
MASK_BLUR_VARIANT_TEST(NormalTranslucent)
MASK_BLUR_VARIANT_TEST(SolidTranslucent)
MASK_BLUR_VARIANT_TEST(SolidOpaque)
MASK_BLUR_VARIANT_TEST(SolidTranslucentWithFilters)
MASK_BLUR_VARIANT_TEST(SolidTranslucentExclusionBlend)
MASK_BLUR_VARIANT_TEST(InnerTranslucent)
MASK_BLUR_VARIANT_TEST(InnerTranslucentWithBlurImageFilter)
MASK_BLUR_VARIANT_TEST(OuterTranslucent)
MASK_BLUR_VARIANT_TEST(OuterOpaqueWithBlurImageFilter)
#undef MASK_BLUR_VARIANT_TEST
TEST_P(AiksTest, GaussianBlurAtPeripheryVertical) {
Canvas canvas;
canvas.Scale(GetContentScale());
canvas.DrawRRect(Rect::MakeLTRB(0, 0, GetWindowSize().width, 100),
Size(10, 10), Paint{.color = Color::LimeGreen()});
canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210),
Size(10, 10), Paint{.color = Color::Magenta()});
canvas.ClipRect(Rect::MakeLTRB(100, 0, 200, GetWindowSize().height));
canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kClamp));
canvas.Restore();
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, GaussianBlurAtPeripheryHorizontal) {
Canvas canvas;
canvas.Scale(GetContentScale());
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
canvas.DrawImageRect(
std::make_shared<Image>(boston),
Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height),
Rect::MakeLTRB(0, 0, GetWindowSize().width, 100), Paint{});
canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210),
Size(10, 10), Paint{.color = Color::Magenta()});
canvas.ClipRect(Rect::MakeLTRB(0, 50, GetWindowSize().width, 150));
canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kClamp));
canvas.Restore();
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
#define FLT_FORWARD(mock, real, method) \
EXPECT_CALL(*mock, method()) \
.WillRepeatedly(::testing::Return(real->method()));
@ -548,162 +121,6 @@ TEST_P(AiksTest, GaussianBlurWithoutDecalSupport) {
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, GaussianBlurOneDimension) {
Canvas canvas;
canvas.Scale(GetContentScale());
canvas.Scale({0.5, 0.5, 1.0});
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
canvas.DrawImage(std::make_shared<Image>(boston), Point(100, 100), Paint{});
canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
ImageFilter::MakeBlur(Sigma(50.0), Sigma(0.0),
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kClamp));
canvas.Restore();
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
// Smoketest to catch issues with the coverage hint.
// Draws a rotated blurred image within a rectangle clip. The center of the clip
// rectangle is the center of the rotated image. The entire area of the clip
// rectangle should be filled with opaque colors output by the blur.
TEST_P(AiksTest, GaussianBlurRotatedAndClipped) {
Canvas canvas;
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
Rect bounds =
Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
Vector2 image_center = Vector2(bounds.GetSize() / 2);
Paint paint = {.image_filter =
ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kDecal)};
Vector2 clip_size = {150, 75};
Vector2 center = Vector2(1024, 768) / 2;
canvas.Scale(GetContentScale());
canvas.ClipRect(
Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size));
canvas.Translate({center.x, center.y, 0});
canvas.Scale({0.6, 0.6, 1});
canvas.Rotate(Degrees(25));
canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
/*dest=*/bounds.Shift(-image_center), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, GaussianBlurScaledAndClipped) {
Canvas canvas;
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
Rect bounds =
Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
Vector2 image_center = Vector2(bounds.GetSize() / 2);
Paint paint = {.image_filter =
ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kDecal)};
Vector2 clip_size = {150, 75};
Vector2 center = Vector2(1024, 768) / 2;
canvas.Scale(GetContentScale());
canvas.ClipRect(
Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size));
canvas.Translate({center.x, center.y, 0});
canvas.Scale({0.6, 0.6, 1});
canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
/*dest=*/bounds.Shift(-image_center), paint);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) {
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
const Entity::TileMode tile_modes[] = {
Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
Entity::TileMode::kMirror, Entity::TileMode::kDecal};
static float rotation = 0;
static float scale = 0.6;
static int selected_tile_mode = 3;
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180);
ImGui::SliderFloat("Scale", &scale, 0, 2.0);
ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
sizeof(tile_mode_names) / sizeof(char*));
ImGui::End();
}
Canvas canvas;
Rect bounds =
Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
Vector2 image_center = Vector2(bounds.GetSize() / 2);
Paint paint = {.image_filter =
ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
FilterContents::BlurStyle::kNormal,
tile_modes[selected_tile_mode])};
static PlaygroundPoint point_a(Point(362, 309), 20, Color::Red());
static PlaygroundPoint point_b(Point(662, 459), 20, Color::Red());
auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b);
Vector2 center = Vector2(1024, 768) / 2;
canvas.Scale(GetContentScale());
canvas.ClipRect(
Rect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
canvas.Translate({center.x, center.y, 0});
canvas.Scale({scale, scale, 1});
canvas.Rotate(Degrees(rotation));
canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
/*dest=*/bounds.Shift(-image_center), paint);
return canvas.EndRecordingAsPicture();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, GaussianBlurRotatedNonUniform) {
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
const Entity::TileMode tile_modes[] = {
Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
Entity::TileMode::kMirror, Entity::TileMode::kDecal};
static float rotation = 45;
static float scale = 0.6;
static int selected_tile_mode = 3;
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180);
ImGui::SliderFloat("Scale", &scale, 0, 2.0);
ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
sizeof(tile_mode_names) / sizeof(char*));
ImGui::End();
}
Canvas canvas;
Paint paint = {.color = Color::Green(),
.image_filter =
ImageFilter::MakeBlur(Sigma(50.0), Sigma(0.0),
FilterContents::BlurStyle::kNormal,
tile_modes[selected_tile_mode])};
Vector2 center = Vector2(1024, 768) / 2;
canvas.Scale(GetContentScale());
canvas.Translate({center.x, center.y, 0});
canvas.Scale({scale, scale, 1});
canvas.Rotate(Degrees(rotation));
canvas.DrawRRect(Rect::MakeXYWH(-100, -100, 200, 200), Size(10, 10), paint);
return canvas.EndRecordingAsPicture();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
// This addresses a bug where tiny blurs could result in mip maps that beyond
// the limits for the textures used for blurring.
// See also: b/323402168
@ -760,321 +177,5 @@ TEST_P(AiksTest, GaussianBlurBackdropTinyMipMap) {
}
}
TEST_P(AiksTest, GaussianBlurAnimatedBackdrop) {
// This test is for checking out how stable rendering is when content is
// translated underneath a blur. Animating under a blur can cause
// *shimmering* to happen as a result of pixel alignment.
// See also: https://github.com/flutter/flutter/issues/140193
auto boston = std::make_shared<Image>(
CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true));
ASSERT_TRUE(boston);
int64_t count = 0;
Scalar sigma = 20.0;
Scalar freq = 0.1;
Scalar amp = 50.0;
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Sigma", &sigma, 0, 200);
ImGui::SliderFloat("Frequency", &freq, 0.01, 2.0);
ImGui::SliderFloat("Amplitude", &amp, 1, 100);
ImGui::End();
}
Canvas canvas;
canvas.Scale(GetContentScale());
Scalar y = amp * sin(freq * 2.0 * M_PI * count / 60);
canvas.DrawImage(boston,
Point(1024 / 2 - boston->GetSize().width / 2,
(768 / 2 - boston->GetSize().height / 2) + y),
{});
static PlaygroundPoint point_a(Point(100, 100), 20, Color::Red());
static PlaygroundPoint point_b(Point(900, 700), 20, Color::Red());
auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b);
canvas.ClipRect(
Rect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
canvas.ClipRect(Rect::MakeLTRB(100, 100, 900, 700));
canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
ImageFilter::MakeBlur(Sigma(sigma), Sigma(sigma),
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kClamp));
count += 1;
return canvas.EndRecordingAsPicture();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, GaussianBlurStyleInnerGradient) {
Canvas canvas;
canvas.Scale(GetContentScale());
canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
Color{0.7568, 0.2627, 0.2118, 1.0}};
std::vector<Scalar> stops = {0.0, 1.0};
Paint paint;
paint.color_source = ColorSource::MakeLinearGradient(
{0, 0}, {200, 200}, std::move(colors), std::move(stops),
Entity::TileMode::kMirror, {});
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kInner,
.sigma = Sigma(30),
};
canvas.DrawPath(PathBuilder()
.MoveTo({200, 200})
.LineTo({300, 400})
.LineTo({100, 400})
.Close()
.TakePath(),
paint);
// Draw another thing to make sure the clip area is reset.
Paint red;
red.color = Color::Red();
canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, GaussianBlurStyleSolidGradient) {
Canvas canvas;
canvas.Scale(GetContentScale());
canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
Color{0.7568, 0.2627, 0.2118, 1.0}};
std::vector<Scalar> stops = {0.0, 1.0};
Paint paint;
paint.color_source = ColorSource::MakeLinearGradient(
{0, 0}, {200, 200}, std::move(colors), std::move(stops),
Entity::TileMode::kMirror, {});
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kSolid,
.sigma = Sigma(30),
};
canvas.DrawPath(PathBuilder()
.MoveTo({200, 200})
.LineTo({300, 400})
.LineTo({100, 400})
.Close()
.TakePath(),
paint);
// Draw another thing to make sure the clip area is reset.
Paint red;
red.color = Color::Red();
canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, GaussianBlurStyleOuterGradient) {
Canvas canvas;
canvas.Scale(GetContentScale());
canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
std::vector<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
Color{0.7568, 0.2627, 0.2118, 1.0}};
std::vector<Scalar> stops = {0.0, 1.0};
Paint paint;
paint.color_source = ColorSource::MakeLinearGradient(
{0, 0}, {200, 200}, std::move(colors), std::move(stops),
Entity::TileMode::kMirror, {});
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kOuter,
.sigma = Sigma(30),
};
canvas.DrawPath(PathBuilder()
.MoveTo({200, 200})
.LineTo({300, 400})
.LineTo({100, 400})
.Close()
.TakePath(),
paint);
// Draw another thing to make sure the clip area is reset.
Paint red;
red.color = Color::Red();
canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, GaussianBlurStyleInner) {
Canvas canvas;
canvas.Scale(GetContentScale());
canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
Paint paint;
paint.color = Color::Green();
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kInner,
.sigma = Sigma(30),
};
canvas.DrawPath(PathBuilder()
.MoveTo({200, 200})
.LineTo({300, 400})
.LineTo({100, 400})
.Close()
.TakePath(),
paint);
// Draw another thing to make sure the clip area is reset.
Paint red;
red.color = Color::Red();
canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, GaussianBlurStyleOuter) {
Canvas canvas;
canvas.Scale(GetContentScale());
canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
Paint paint;
paint.color = Color::Green();
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kOuter,
.sigma = Sigma(30),
};
canvas.DrawPath(PathBuilder()
.MoveTo({200, 200})
.LineTo({300, 400})
.LineTo({100, 400})
.Close()
.TakePath(),
paint);
// Draw another thing to make sure the clip area is reset.
Paint red;
red.color = Color::Red();
canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, GaussianBlurStyleSolid) {
Canvas canvas;
canvas.Scale(GetContentScale());
canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
Paint paint;
paint.color = Color::Green();
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kSolid,
.sigma = Sigma(30),
};
canvas.DrawPath(PathBuilder()
.MoveTo({200, 200})
.LineTo({300, 400})
.LineTo({100, 400})
.Close()
.TakePath(),
paint);
// Draw another thing to make sure the clip area is reset.
Paint red;
red.color = Color::Red();
canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
TEST_P(AiksTest, MaskBlurTexture) {
Scalar sigma = 30;
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Sigma", &sigma, 0, 500);
ImGui::End();
}
Canvas canvas;
canvas.Scale(GetContentScale());
Paint paint;
paint.color = Color::Green();
paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Sigma(sigma),
};
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
canvas.DrawImage(std::make_shared<Image>(boston), {200, 200}, paint);
Paint red;
red.color = Color::Red();
canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), red);
return canvas.EndRecordingAsPicture();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, GuassianBlurUpdatesMipmapContents) {
// This makes sure if mip maps are recycled across invocations of blurs the
// contents get updated each frame correctly. If they aren't updated the color
// inside the blur and outside the blur will be different.
//
// If there is some change to render target caching this could display a false
// positive in the future. Also, if the LOD that is rendered is 1 it could
// present a false positive.
int32_t count = 0;
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
Canvas canvas;
if (count++ == 0) {
canvas.DrawCircle({100, 100}, 50, {.color = Color::CornflowerBlue()});
} else {
canvas.DrawCircle({100, 100}, 50, {.color = Color::Chartreuse()});
}
canvas.ClipRRect(Rect::MakeLTRB(75, 50, 375, 275), {20, 20});
canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
ImageFilter::MakeBlur(Sigma(30.0), Sigma(30.0),
FilterContents::BlurStyle::kNormal,
Entity::TileMode::kClamp));
canvas.Restore();
return canvas.EndRecordingAsPicture();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, MaskBlurDoesntStretchContents) {
Scalar sigma = 70;
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Sigma", &sigma, 0, 500);
ImGui::End();
}
Canvas canvas;
canvas.Scale(GetContentScale());
canvas.DrawPaint({.color = Color(0.1, 0.1, 0.1, 1.0)});
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
ColorSource image_source = ColorSource::MakeImage(
boston, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, {});
canvas.Transform(Matrix::MakeTranslation({100, 100, 0}) *
Matrix::MakeScale({0.5, 0.5, 1.0}));
Paint paint = {
.color_source = image_source,
.mask_blur_descriptor =
Paint::MaskBlurDescriptor{
.style = FilterContents::BlurStyle::kNormal,
.sigma = Sigma(sigma),
},
};
canvas.DrawRect(
Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height),
paint);
return canvas.EndRecordingAsPicture();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
} // namespace testing
} // namespace impeller

View File

@ -55,6 +55,7 @@ template("display_list_unittests_component") {
"aiks_dl_atlas_unittests.cc",
"aiks_dl_basic_unittests.cc",
"aiks_dl_blend_unittests.cc",
"aiks_dl_blur_unittests.cc",
"aiks_dl_clip_unittests.cc",
"aiks_dl_gradient_unittests.cc",
"aiks_dl_opacity_unittests.cc",

View File

@ -0,0 +1,977 @@
// 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 "display_list/display_list.h"
#include "display_list/dl_blend_mode.h"
#include "display_list/dl_builder.h"
#include "display_list/dl_color.h"
#include "display_list/dl_paint.h"
#include "display_list/dl_sampling_options.h"
#include "display_list/dl_tile_mode.h"
#include "display_list/effects/dl_color_filter.h"
#include "display_list/effects/dl_color_source.h"
#include "display_list/effects/dl_image_filter.h"
#include "display_list/effects/dl_mask_filter.h"
#include "flutter/impeller/aiks/aiks_unittests.h"
#include "impeller/display_list/dl_image_impeller.h"
#include "impeller/playground/widgets.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "third_party/imgui/imgui.h"
////////////////////////////////////////////////////////////////////////////////
// This is for tests of Canvas that are interested the results of rendering
// blurs.
////////////////////////////////////////////////////////////////////////////////
namespace impeller {
namespace testing {
using namespace flutter;
TEST_P(AiksTest, CanRenderMaskBlurHugeSigma) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kGreen());
paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 99999));
builder.DrawCircle({400, 400}, 300, paint);
builder.Restore();
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderForegroundBlendWithMaskBlur) {
// This case triggers the ForegroundPorterDuffBlend path. The color filter
// should apply to the color only, and respect the alpha mask.
DisplayListBuilder builder;
builder.ClipRect(SkRect::MakeXYWH(100, 150, 400, 400));
DlPaint paint;
paint.setColor(DlColor::kWhite());
Sigma sigma = Radius(20);
paint.setMaskFilter(
DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma));
paint.setColorFilter(
DlBlendColorFilter::Make(DlColor::kGreen(), DlBlendMode::kSrc));
builder.DrawCircle({400, 400}, 200, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderForegroundAdvancedBlendWithMaskBlur) {
// This case triggers the ForegroundAdvancedBlend path. The color filter
// should apply to the color only, and respect the alpha mask.
DisplayListBuilder builder;
builder.ClipRect(SkRect::MakeXYWH(100, 150, 400, 400));
DlPaint paint;
paint.setColor(
DlColor::RGBA(128.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f, 1.0f));
Sigma sigma = Radius(20);
paint.setMaskFilter(
DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma));
paint.setColorFilter(
DlBlendColorFilter::Make(DlColor::kGreen(), DlBlendMode::kColor));
builder.DrawCircle({400, 400}, 200, paint);
builder.Restore();
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderBackdropBlurInteractive) {
auto callback = [&]() -> sk_sp<DisplayList> {
static PlaygroundPoint point_a(Point(50, 50), 30, Color::White());
static PlaygroundPoint point_b(Point(300, 200), 30, Color::White());
auto [a, b] = DrawPlaygroundLine(point_a, point_b);
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kCornflowerBlue());
builder.DrawCircle({100, 100}, 50, paint);
paint.setColor(DlColor::kGreenYellow());
builder.DrawCircle({300, 200}, 100, paint);
paint.setColor(DlColor::kDarkMagenta());
builder.DrawCircle({140, 170}, 75, paint);
paint.setColor(DlColor::kOrangeRed());
builder.DrawCircle({180, 120}, 100, paint);
SkRRect rrect =
SkRRect::MakeRectXY(SkRect::MakeLTRB(a.x, a.y, b.x, b.y), 20, 20);
builder.ClipRRect(rrect);
DlPaint save_paint;
save_paint.setBlendMode(DlBlendMode::kSrc);
auto backdrop_filter = DlBlurImageFilter::Make(20, 20, DlTileMode::kClamp);
builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get());
builder.Restore();
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, CanRenderBackdropBlur) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kCornflowerBlue());
builder.DrawCircle({100, 100}, 50, paint);
paint.setColor(DlColor::kGreenYellow());
builder.DrawCircle({300, 200}, 100, paint);
paint.setColor(DlColor::kDarkMagenta());
builder.DrawCircle({140, 170}, 75, paint);
paint.setColor(DlColor::kOrangeRed());
builder.DrawCircle({180, 120}, 100, paint);
SkRRect rrect =
SkRRect::MakeRectXY(SkRect::MakeLTRB(75, 50, 375, 275), 20, 20);
builder.ClipRRect(rrect);
DlPaint save_paint;
save_paint.setBlendMode(DlBlendMode::kSrc);
auto backdrop_filter = DlBlurImageFilter::Make(30, 30, DlTileMode::kClamp);
builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get());
builder.Restore();
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderBackdropBlurHugeSigma) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kGreen());
builder.DrawCircle({400, 400}, 300, paint);
DlPaint save_paint;
save_paint.setBlendMode(DlBlendMode::kSrc);
auto backdrop_filter =
DlBlurImageFilter::Make(999999, 999999, DlTileMode::kClamp);
builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get());
builder.Restore();
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, CanRenderClippedBlur) {
DisplayListBuilder builder;
builder.ClipRect(SkRect::MakeXYWH(100, 150, 400, 400));
DlPaint paint;
paint.setColor(DlColor::kGreen());
paint.setImageFilter(DlBlurImageFilter::Make(20, 20, DlTileMode::kDecal));
builder.DrawCircle({400, 400}, 200, paint);
builder.Restore();
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) {
auto callback = [&]() -> sk_sp<DisplayList> {
static PlaygroundPoint playground_point(Point(400, 400), 20,
Color::Green());
auto point = DrawPlaygroundPoint(playground_point);
DisplayListBuilder builder;
auto location = point - Point(400, 400);
builder.Translate(location.x, location.y);
DlPaint paint;
Sigma sigma = Radius{120 * 3};
paint.setMaskFilter(
DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma));
paint.setColor(DlColor::kRed());
SkPath path = SkPath::Rect(SkRect::MakeLTRB(0, 0, 800, 800));
path.addCircle(0, 0, 0.5);
builder.DrawPath(path, paint);
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) {
DisplayListBuilder builder;
builder.Translate(0, -400);
DlPaint paint;
Sigma sigma = Radius{120 * 3};
paint.setMaskFilter(
DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma.sigma));
paint.setColor(DlColor::kRed());
SkPath path = SkPath::Rect(SkRect::MakeLTRB(0, 0, 800, 800));
path.addCircle(0, 0, 0.5);
builder.DrawPath(path, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, ClearBlendWithBlur) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kBlue());
builder.DrawRect(SkRect::MakeXYWH(0, 0, 600.0, 600.0), paint);
DlPaint clear;
clear.setBlendMode(DlBlendMode::kClear);
clear.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 20));
builder.DrawCircle({300.0, 300.0}, 200.0, clear);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, BlurHasNoEdge) {
Scalar sigma = 47.6;
auto callback = [&]() -> sk_sp<DisplayList> {
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Sigma", &sigma, 0, 50);
ImGui::End();
}
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
builder.DrawPaint({});
DlPaint paint;
paint.setColor(DlColor::kGreen());
paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma));
builder.DrawRect(SkRect::MakeXYWH(300, 300, 200, 200), paint);
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) {
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kBlue());
paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 0));
builder.DrawCircle({300, 300}, 200, paint);
builder.DrawRect(SkRect::MakeLTRB(100, 300, 500, 600), paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
struct MaskBlurTestConfig {
DlBlurStyle style = DlBlurStyle::kNormal;
Scalar sigma = 1.0f;
Scalar alpha = 1.0f;
std::shared_ptr<DlImageFilter> image_filter;
bool invert_colors = false;
DlBlendMode blend_mode = DlBlendMode::kSrcOver;
};
static sk_sp<DisplayList> MaskBlurVariantTest(
const AiksTest& test_context,
const MaskBlurTestConfig& config) {
DisplayListBuilder builder;
builder.Scale(test_context.GetContentScale().x,
test_context.GetContentScale().y);
builder.Scale(0.8f, 0.8f);
DlPaint draw_paint;
draw_paint.setColor(
DlColor::RGBA(Color::AntiqueWhite().red, Color::AntiqueWhite().green,
Color::AntiqueWhite().blue, Color::AntiqueWhite().alpha));
builder.DrawPaint(draw_paint);
DlPaint paint;
paint.setMaskFilter(DlBlurMaskFilter::Make(config.style, config.sigma));
paint.setInvertColors(config.invert_colors);
paint.setImageFilter(config.image_filter);
paint.setBlendMode(config.blend_mode);
const Scalar x = 50;
const Scalar radius = 20.0f;
const Scalar y_spacing = 100.0f;
Scalar alpha = config.alpha * 255;
Scalar y = 50;
paint.setColor(DlColor::kCrimson().withAlpha(alpha));
builder.DrawRect(SkRect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
radius, 60.0f - radius),
paint);
y += y_spacing;
paint.setColor(DlColor::kBlue().withAlpha(alpha));
builder.DrawCircle({x + 25, y + 25}, radius, paint);
y += y_spacing;
paint.setColor(DlColor::kGreen().withAlpha(alpha));
builder.DrawOval(SkRect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, //
radius, 60.0f - radius),
paint);
y += y_spacing;
paint.setColor(DlColor::kPurple().withAlpha(alpha));
SkRRect rrect =
SkRRect::MakeRectXY(SkRect::MakeXYWH(x, y, 60.0f, 60.0f), radius, radius);
builder.DrawRRect(rrect, paint);
y += y_spacing;
paint.setColor(DlColor::kOrange().withAlpha(alpha));
rrect =
SkRRect::MakeRectXY(SkRect::MakeXYWH(x, y, 60.0f, 60.0f), radius, 5.0);
builder.DrawRRect(rrect, paint);
y += y_spacing;
paint.setColor(DlColor::kMaroon().withAlpha(alpha));
{
SkPath path;
path.moveTo(x + 0, y + 60);
path.lineTo(x + 30, y + 0);
path.lineTo(x + 60, y + 60);
path.close();
builder.DrawPath(path, paint);
}
y += y_spacing;
paint.setColor(DlColor::kMaroon().withAlpha(alpha));
{
SkPath path;
path.addArc(SkRect::MakeXYWH(x + 5, y, 50, 50), 90, 180);
path.addArc(SkRect::MakeXYWH(x + 25, y, 50, 50), 90, 180);
path.close();
builder.DrawPath(path, paint);
}
return builder.Build();
}
static const std::map<std::string, MaskBlurTestConfig> kPaintVariations = {
// 1. Normal style, translucent, zero sigma.
{"NormalTranslucentZeroSigma",
{.style = DlBlurStyle::kNormal, .sigma = 0.0f, .alpha = 0.5f}},
// 2. Normal style, translucent.
{"NormalTranslucent",
{.style = DlBlurStyle::kNormal, .sigma = 8.0f, .alpha = 0.5f}},
// 3. Solid style, translucent.
{"SolidTranslucent",
{.style = DlBlurStyle::kSolid, .sigma = 8.0f, .alpha = 0.5f}},
// 4. Solid style, opaque.
{"SolidOpaque", {.style = DlBlurStyle::kSolid, .sigma = 8.0f}},
// 5. Solid style, translucent, color & image filtered.
{"SolidTranslucentWithFilters",
{.style = DlBlurStyle::kSolid,
.sigma = 8.0f,
.alpha = 0.5f,
.image_filter = DlBlurImageFilter::Make(3, 3, DlTileMode::kClamp),
.invert_colors = true}},
// 6. Solid style, translucent, exclusion blended.
{"SolidTranslucentExclusionBlend",
{.style = DlBlurStyle::kSolid,
.sigma = 8.0f,
.alpha = 0.5f,
.blend_mode = DlBlendMode::kExclusion}},
// 7. Inner style, translucent.
{"InnerTranslucent",
{.style = DlBlurStyle::kInner, .sigma = 8.0f, .alpha = 0.5f}},
// 8. Inner style, translucent, blurred.
{"InnerTranslucentWithBlurImageFilter",
{.style = DlBlurStyle::kInner,
.sigma = 8.0f,
.alpha = 0.5f,
.image_filter = DlBlurImageFilter::Make(3, 3, DlTileMode::kClamp)}},
// 9. Outer style, translucent.
{"OuterTranslucent",
{.style = DlBlurStyle::kOuter, .sigma = 8.0f, .alpha = 0.5f}},
// 10. Outer style, opaque, image filtered.
{"OuterOpaqueWithBlurImageFilter",
{.style = DlBlurStyle::kOuter,
.sigma = 8.0f,
.image_filter = DlBlurImageFilter::Make(3, 3, DlTileMode::kClamp)}},
};
#define MASK_BLUR_VARIANT_TEST(config) \
TEST_P(AiksTest, MaskBlurVariantTest##config) { \
ASSERT_TRUE(OpenPlaygroundHere( \
MaskBlurVariantTest(*this, kPaintVariations.at(#config)))); \
}
MASK_BLUR_VARIANT_TEST(NormalTranslucentZeroSigma)
MASK_BLUR_VARIANT_TEST(NormalTranslucent)
MASK_BLUR_VARIANT_TEST(SolidTranslucent)
MASK_BLUR_VARIANT_TEST(SolidOpaque)
MASK_BLUR_VARIANT_TEST(SolidTranslucentWithFilters)
MASK_BLUR_VARIANT_TEST(SolidTranslucentExclusionBlend)
MASK_BLUR_VARIANT_TEST(InnerTranslucent)
MASK_BLUR_VARIANT_TEST(InnerTranslucentWithBlurImageFilter)
MASK_BLUR_VARIANT_TEST(OuterTranslucent)
MASK_BLUR_VARIANT_TEST(OuterOpaqueWithBlurImageFilter)
#undef MASK_BLUR_VARIANT_TEST
TEST_P(AiksTest, GaussianBlurStyleInner) {
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
DlPaint paint;
paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1));
builder.DrawPaint(paint);
paint.setColor(DlColor::kGreen());
paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kInner, 30));
SkPath path;
path.moveTo(200, 200);
path.lineTo(300, 400);
path.lineTo(100, 400);
path.close();
builder.DrawPath(path, paint);
// Draw another thing to make sure the clip area is reset.
DlPaint red;
red.setColor(DlColor::kRed());
builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, GaussianBlurStyleOuter) {
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
DlPaint paint;
paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0));
builder.DrawPaint(paint);
paint.setColor(DlColor::kGreen());
paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kOuter, 30));
SkPath path;
path.moveTo(200, 200);
path.lineTo(300, 400);
path.lineTo(100, 400);
path.close();
builder.DrawPath(path, paint);
// Draw another thing to make sure the clip area is reset.
DlPaint red;
red.setColor(DlColor::kRed());
builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, GaussianBlurStyleSolid) {
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
DlPaint paint;
paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0));
builder.DrawPaint(paint);
paint.setColor(DlColor::kGreen());
paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kSolid, 30));
SkPath path;
path.moveTo(200, 200);
path.lineTo(300, 400);
path.lineTo(100, 400);
path.close();
builder.DrawPath(path, paint);
// Draw another thing to make sure the clip area is reset.
DlPaint red;
red.setColor(DlColor::kRed());
builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, MaskBlurTexture) {
Scalar sigma = 30;
auto callback = [&]() -> sk_sp<DisplayList> {
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Sigma", &sigma, 0, 500);
ImGui::End();
}
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
DlPaint paint;
paint.setColor(DlColor::kGreen());
paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma));
builder.DrawImage(
DlImageImpeller::Make(CreateTextureForFixture("boston.jpg")),
{200, 200}, DlImageSampling::kNearestNeighbor, &paint);
DlPaint red;
red.setColor(DlColor::kRed());
builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red);
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, MaskBlurDoesntStretchContents) {
Scalar sigma = 70;
auto callback = [&]() -> sk_sp<DisplayList> {
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Sigma", &sigma, 0, 500);
ImGui::End();
}
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
DlPaint paint;
paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0));
builder.DrawPaint(paint);
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
builder.Transform(SkMatrix::Translate(100, 100) *
SkMatrix::Scale(0.5, 0.5));
paint.setColorSource(std::make_shared<DlImageColorSource>(
DlImageImpeller::Make(boston), DlTileMode::kRepeat, DlTileMode::kRepeat,
DlImageSampling::kMipmapLinear));
paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, sigma));
builder.DrawRect(SkRect::MakeXYWH(0, 0, boston->GetSize().width,
boston->GetSize().height),
paint);
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, GaussianBlurAtPeripheryVertical) {
DisplayListBuilder builder;
DlPaint paint;
builder.Scale(GetContentScale().x, GetContentScale().y);
paint.setColor(DlColor::kLimeGreen());
SkRRect rrect = SkRRect::MakeRectXY(
SkRect::MakeLTRB(0, 0, GetWindowSize().width, 100), 10, 10);
builder.DrawRRect(rrect, paint);
paint.setColor(DlColor::kMagenta());
rrect = SkRRect::MakeRectXY(
SkRect::MakeLTRB(0, 110, GetWindowSize().width, 210), 10, 10);
builder.DrawRRect(rrect, paint);
builder.ClipRect(SkRect::MakeLTRB(100, 0, 200, GetWindowSize().height));
DlPaint save_paint;
save_paint.setBlendMode(DlBlendMode::kSrc);
auto backdrop_filter = DlBlurImageFilter::Make(20, 20, DlTileMode::kClamp);
builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get());
builder.Restore();
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, GaussianBlurAtPeripheryHorizontal) {
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
builder.DrawImageRect(
DlImageImpeller::Make(boston),
SkRect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height),
SkRect::MakeLTRB(0, 0, GetWindowSize().width, 100),
DlImageSampling::kNearestNeighbor);
DlPaint paint;
paint.setColor(DlColor::kMagenta());
SkRRect rrect = SkRRect::MakeRectXY(
SkRect::MakeLTRB(0, 110, GetWindowSize().width, 210), 10, 10);
builder.DrawRRect(rrect, paint);
builder.ClipRect(SkRect::MakeLTRB(0, 50, GetWindowSize().width, 150));
DlPaint save_paint;
save_paint.setBlendMode(DlBlendMode::kSrc);
auto backdrop_filter = DlBlurImageFilter::Make(20, 20, DlTileMode::kClamp);
builder.SaveLayer(nullptr, &save_paint, backdrop_filter.get());
builder.Restore();
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, GaussianBlurAnimatedBackdrop) {
// This test is for checking out how stable rendering is when content is
// translated underneath a blur. Animating under a blur can cause
// *shimmering* to happen as a result of pixel alignment.
// See also: https://github.com/flutter/flutter/issues/140193
auto boston =
CreateTextureForFixture("boston.jpg", /*enable_mipmapping=*/true);
ASSERT_TRUE(boston);
int64_t count = 0;
Scalar sigma = 20.0;
Scalar freq = 0.1;
Scalar amp = 50.0;
auto callback = [&]() -> sk_sp<DisplayList> {
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Sigma", &sigma, 0, 200);
ImGui::SliderFloat("Frequency", &freq, 0.01, 2.0);
ImGui::SliderFloat("Amplitude", &amp, 1, 100);
ImGui::End();
}
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
Scalar y = amp * sin(freq * 2.0 * M_PI * count / 60);
builder.DrawImage(
DlImageImpeller::Make(boston),
SkPoint::Make(1024 / 2 - boston->GetSize().width / 2,
(768 / 2 - boston->GetSize().height / 2) + y),
DlImageSampling::kMipmapLinear);
static PlaygroundPoint point_a(Point(100, 100), 20, Color::Red());
static PlaygroundPoint point_b(Point(900, 700), 20, Color::Red());
auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b);
builder.ClipRect(
SkRect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
builder.ClipRect(SkRect::MakeLTRB(100, 100, 900, 700));
DlPaint paint;
paint.setBlendMode(DlBlendMode::kSrc);
auto backdrop_filter =
DlBlurImageFilter::Make(sigma, sigma, DlTileMode::kClamp);
builder.SaveLayer(nullptr, &paint, backdrop_filter.get());
count += 1;
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, GaussianBlurStyleInnerGradient) {
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
DlPaint paint;
paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0));
builder.DrawPaint(paint);
std::vector<DlColor> colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0),
DlColor::RGBA(0.7568, 0.2627, 0.2118, 1.0)};
std::vector<Scalar> stops = {0.0, 1.0};
paint = DlPaint{};
paint.setColorSource(DlColorSource::MakeLinear(
/*start_point=*/{0, 0},
/*end_point=*/{200, 200},
/*stop_count=*/colors.size(),
/*colors=*/colors.data(),
/*stops=*/stops.data(),
/*tile_mode=*/DlTileMode::kMirror));
paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kInner, 30));
SkPath path;
path.moveTo(200, 200);
path.lineTo(300, 400);
path.lineTo(100, 400);
path.close();
builder.DrawPath(path, paint);
// Draw another thing to make sure the clip area is reset.
DlPaint red;
red.setColor(DlColor::kRed());
builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, GaussianBlurStyleSolidGradient) {
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
DlPaint paint;
paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0));
builder.DrawPaint(paint);
std::vector<DlColor> colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0),
DlColor::RGBA(0.7568, 0.2627, 0.2118, 1.0)};
std::vector<Scalar> stops = {0.0, 1.0};
paint = DlPaint{};
paint.setColorSource(DlColorSource::MakeLinear(
/*start_point=*/{0, 0},
/*end_point=*/{200, 200},
/*stop_count=*/colors.size(),
/*colors=*/colors.data(),
/*stops=*/stops.data(),
/*tile_mode=*/DlTileMode::kMirror));
paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kSolid, 30));
SkPath path;
path.moveTo(200, 200);
path.lineTo(300, 400);
path.lineTo(100, 400);
path.close();
builder.DrawPath(path, paint);
// Draw another thing to make sure the clip area is reset.
DlPaint red;
red.setColor(DlColor::kRed());
builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, GaussianBlurStyleOuterGradient) {
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
DlPaint paint;
paint.setColor(DlColor::RGBA(0.1, 0.1, 0.1, 1.0));
builder.DrawPaint(paint);
std::vector<DlColor> colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0),
DlColor::RGBA(0.7568, 0.2627, 0.2118, 1.0)};
std::vector<Scalar> stops = {0.0, 1.0};
paint = DlPaint{};
paint.setColorSource(DlColorSource::MakeLinear(
/*start_point=*/{0, 0},
/*end_point=*/{200, 200},
/*stop_count=*/colors.size(),
/*colors=*/colors.data(),
/*stops=*/stops.data(),
/*tile_mode=*/DlTileMode::kMirror));
paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kOuter, 30));
SkPath path;
path.moveTo(200, 200);
path.lineTo(300, 400);
path.lineTo(100, 400);
path.close();
builder.DrawPath(path, paint);
// Draw another thing to make sure the clip area is reset.
DlPaint red;
red.setColor(DlColor::kRed());
builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), red);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, GaussianBlurScaledAndClipped) {
DisplayListBuilder builder;
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
Rect bounds =
Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
Vector2 image_center = Vector2(bounds.GetSize() / 2);
DlPaint paint;
paint.setImageFilter(DlBlurImageFilter::Make(20, 20, DlTileMode::kDecal));
Vector2 clip_size = {150, 75};
Vector2 center = Vector2(1024, 768) / 2;
builder.Scale(GetContentScale().x, GetContentScale().y);
auto rect =
Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size);
builder.ClipRect(SkRect::MakeLTRB(rect.GetLeft(), rect.GetTop(),
rect.GetRight(), rect.GetBottom()));
builder.Translate(center.x, center.y);
builder.Scale(0.6, 0.6);
SkRect sk_bounds = SkRect::MakeLTRB(bounds.GetLeft(), bounds.GetTop(),
bounds.GetRight(), bounds.GetBottom());
Rect dest = bounds.Shift(-image_center);
SkRect sk_dst = SkRect::MakeLTRB(dest.GetLeft(), dest.GetTop(),
dest.GetRight(), dest.GetBottom());
builder.DrawImageRect(DlImageImpeller::Make(boston), /*src=*/sk_bounds,
/*dst=*/sk_dst, DlImageSampling::kNearestNeighbor,
&paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) {
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
auto callback = [&]() -> sk_sp<DisplayList> {
const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
const DlTileMode tile_modes[] = {DlTileMode::kClamp, DlTileMode::kRepeat,
DlTileMode::kMirror, DlTileMode::kDecal};
static float rotation = 0;
static float scale = 0.6;
static int selected_tile_mode = 3;
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180);
ImGui::SliderFloat("Scale", &scale, 0, 2.0);
ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
sizeof(tile_mode_names) / sizeof(char*));
ImGui::End();
}
DisplayListBuilder builder;
Rect bounds =
Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
Vector2 image_center = Vector2(bounds.GetSize() / 2);
DlPaint paint;
paint.setImageFilter(
DlBlurImageFilter::Make(20, 20, tile_modes[selected_tile_mode]));
static PlaygroundPoint point_a(Point(362, 309), 20, Color::Red());
static PlaygroundPoint point_b(Point(662, 459), 20, Color::Red());
auto [handle_a, handle_b] = DrawPlaygroundLine(point_a, point_b);
Vector2 center = Vector2(1024, 768) / 2;
builder.Scale(GetContentScale().x, GetContentScale().y);
builder.ClipRect(
SkRect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y));
builder.Translate(center.x, center.y);
builder.Scale(scale, scale);
builder.Rotate(rotation);
SkRect sk_bounds = SkRect::MakeLTRB(bounds.GetLeft(), bounds.GetTop(),
bounds.GetRight(), bounds.GetBottom());
Rect dest = bounds.Shift(-image_center);
SkRect sk_dst = SkRect::MakeLTRB(dest.GetLeft(), dest.GetTop(),
dest.GetRight(), dest.GetBottom());
builder.DrawImageRect(DlImageImpeller::Make(boston), /*src=*/sk_bounds,
/*dst=*/sk_dst, DlImageSampling::kNearestNeighbor,
&paint);
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(AiksTest, GaussianBlurOneDimension) {
DisplayListBuilder builder;
builder.Scale(GetContentScale().x, GetContentScale().y);
builder.Scale(0.5, 0.5);
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
builder.DrawImage(DlImageImpeller::Make(boston), {100, 100}, {});
DlPaint paint;
paint.setBlendMode(DlBlendMode::kSrc);
auto backdrop_filter = DlBlurImageFilter::Make(50, 0, DlTileMode::kClamp);
builder.SaveLayer(nullptr, &paint, backdrop_filter.get());
builder.Restore();
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
// Smoketest to catch issues with the coverage hint.
// Draws a rotated blurred image within a rectangle clip. The center of the clip
// rectangle is the center of the rotated image. The entire area of the clip
// rectangle should be filled with opaque colors output by the blur.
TEST_P(AiksTest, GaussianBlurRotatedAndClipped) {
DisplayListBuilder builder;
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
Rect bounds =
Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
DlPaint paint;
paint.setImageFilter(DlBlurImageFilter::Make(20, 20, DlTileMode::kDecal));
Vector2 image_center = Vector2(bounds.GetSize() / 2);
Vector2 clip_size = {150, 75};
Vector2 center = Vector2(1024, 768) / 2;
builder.Scale(GetContentScale().x, GetContentScale().y);
auto clip_bounds =
Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size);
builder.ClipRect(SkRect::MakeLTRB(clip_bounds.GetLeft(), clip_bounds.GetTop(),
clip_bounds.GetRight(),
clip_bounds.GetBottom()));
builder.Translate(center.x, center.y);
builder.Scale(0.6, 0.6);
builder.Rotate(25);
auto dst_rect = bounds.Shift(-image_center);
builder.DrawImageRect(
DlImageImpeller::Make(boston), /*src=*/
SkRect::MakeLTRB(bounds.GetLeft(), bounds.GetTop(), bounds.GetRight(),
bounds.GetBottom()),
/*dst=*/
SkRect::MakeLTRB(dst_rect.GetLeft(), dst_rect.GetTop(),
dst_rect.GetRight(), dst_rect.GetBottom()),
DlImageSampling::kMipmapLinear, &paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(AiksTest, GaussianBlurRotatedNonUniform) {
auto callback = [&]() -> sk_sp<DisplayList> {
const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
const DlTileMode tile_modes[] = {DlTileMode::kClamp, DlTileMode::kRepeat,
DlTileMode::kMirror, DlTileMode::kDecal};
static float rotation = 45;
static float scale = 0.6;
static int selected_tile_mode = 3;
if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180);
ImGui::SliderFloat("Scale", &scale, 0, 2.0);
ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
sizeof(tile_mode_names) / sizeof(char*));
ImGui::End();
}
DisplayListBuilder builder;
DlPaint paint;
paint.setColor(DlColor::kGreen());
paint.setImageFilter(
DlBlurImageFilter::Make(50, 0, tile_modes[selected_tile_mode]));
Vector2 center = Vector2(1024, 768) / 2;
builder.Scale(GetContentScale().x, GetContentScale().y);
builder.Translate(center.x, center.y);
builder.Scale(scale, scale);
builder.Rotate(rotation);
SkRRect rrect =
SkRRect::MakeRectXY(SkRect::MakeXYWH(-100, -100, 200, 200), 10, 10);
builder.DrawRRect(rrect, paint);
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
} // namespace testing
} // namespace impeller

View File

@ -702,9 +702,6 @@ impeller_Play_AiksTest_GaussianBlurWithoutDecalSupport_Metal.png
impeller_Play_AiksTest_GradientStrokesRenderCorrectly_Metal.png
impeller_Play_AiksTest_GradientStrokesRenderCorrectly_OpenGLES.png
impeller_Play_AiksTest_GradientStrokesRenderCorrectly_Vulkan.png
impeller_Play_AiksTest_GuassianBlurUpdatesMipmapContents_Metal.png
impeller_Play_AiksTest_GuassianBlurUpdatesMipmapContents_OpenGLES.png
impeller_Play_AiksTest_GuassianBlurUpdatesMipmapContents_Vulkan.png
impeller_Play_AiksTest_ImageColorSourceEffectTransform_Metal.png
impeller_Play_AiksTest_ImageColorSourceEffectTransform_OpenGLES.png
impeller_Play_AiksTest_ImageColorSourceEffectTransform_Vulkan.png