blob: 88bb359e583215daa809aa49e0bc2a60ae862d76 [file] [log] [blame]
// 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 <array>
#include <cmath>
#include <memory>
#include <vector>
#include "flutter/display_list/dl_blend_mode.h"
#include "flutter/display_list/dl_builder.h"
#include "flutter/display_list/dl_color.h"
#include "flutter/display_list/dl_paint.h"
#include "flutter/display_list/dl_tile_mode.h"
#include "flutter/display_list/effects/dl_color_filter.h"
#include "flutter/display_list/effects/dl_color_source.h"
#include "flutter/display_list/effects/dl_image_filter.h"
#include "flutter/display_list/effects/dl_mask_filter.h"
#include "flutter/testing/testing.h"
#include "gtest/gtest.h"
#include "impeller/display_list/dl_dispatcher.h"
#include "impeller/display_list/dl_image_impeller.h"
#include "impeller/display_list/dl_playground.h"
#include "impeller/entity/contents/clip_contents.h"
#include "impeller/entity/contents/solid_color_contents.h"
#include "impeller/entity/contents/solid_rrect_blur_contents.h"
#include "impeller/geometry/constants.h"
#include "impeller/geometry/point.h"
#include "impeller/geometry/scalar.h"
#include "impeller/playground/widgets.h"
#include "impeller/scene/node.h"
#include "third_party/imgui/imgui.h"
#include "third_party/skia/include/core/SkBlurTypes.h"
#include "third_party/skia/include/core/SkClipOp.h"
#include "third_party/skia/include/core/SkPathBuilder.h"
#include "third_party/skia/include/core/SkRRect.h"
namespace impeller {
namespace testing {
flutter::DlColor toColor(const float* components) {
return flutter::DlColor(Color::ToIColor(
Color(components[0], components[1], components[2], components[3])));
}
using DisplayListTest = DlPlayground;
INSTANTIATE_PLAYGROUND_SUITE(DisplayListTest);
TEST_P(DisplayListTest, DrawPictureWithAClip) {
flutter::DisplayListBuilder sub_builder;
sub_builder.ClipRect(SkRect::MakeXYWH(0, 0, 24, 24));
sub_builder.DrawPaint(flutter::DlPaint(flutter::DlColor::kBlue()));
auto display_list = sub_builder.Build();
flutter::DisplayListBuilder builder;
builder.DrawDisplayList(display_list);
builder.DrawRect(SkRect::MakeXYWH(30, 30, 24, 24),
flutter::DlPaint(flutter::DlColor::kRed()));
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawRect) {
flutter::DisplayListBuilder builder;
builder.DrawRect(SkRect::MakeXYWH(10, 10, 100, 100),
flutter::DlPaint(flutter::DlColor::kBlue()));
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawTextBlob) {
flutter::DisplayListBuilder builder;
builder.DrawTextBlob(SkTextBlob::MakeFromString("Hello", CreateTestFont()),
100, 100, flutter::DlPaint(flutter::DlColor::kBlue()));
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawTextBlobWithGradient) {
flutter::DisplayListBuilder builder;
std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
flutter::DlColor::kRed()};
const float stops[2] = {0.0, 1.0};
auto linear = flutter::DlColorSource::MakeLinear({0.0, 0.0}, {300.0, 300.0},
2, colors.data(), stops,
flutter::DlTileMode::kClamp);
flutter::DlPaint paint;
paint.setColorSource(linear);
builder.DrawTextBlob(
SkTextBlob::MakeFromString("Hello World", CreateTestFont()), 100, 100,
paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawTextWithSaveLayer) {
flutter::DisplayListBuilder builder;
builder.DrawTextBlob(SkTextBlob::MakeFromString("Hello", CreateTestFont()),
100, 100, flutter::DlPaint(flutter::DlColor::kRed()));
flutter::DlPaint save_paint;
float alpha = 0.5;
save_paint.setAlpha(static_cast<uint8_t>(255 * alpha));
builder.SaveLayer(nullptr, &save_paint);
builder.DrawTextBlob(SkTextBlob::MakeFromString("Hello with half alpha",
CreateTestFontOfSize(100)),
100, 300, flutter::DlPaint(flutter::DlColor::kRed()));
builder.Restore();
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawImage) {
auto texture = CreateTextureForFixture("embarcadero.jpg");
flutter::DisplayListBuilder builder;
builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
flutter::DlImageSampling::kNearestNeighbor, nullptr);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawCapsAndJoins) {
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
paint.setStrokeWidth(30);
paint.setColor(flutter::DlColor::kRed());
auto path =
SkPathBuilder{}.moveTo(-50, 0).lineTo(0, -50).lineTo(50, 0).snapshot();
builder.Translate(100, 100);
{
paint.setStrokeCap(flutter::DlStrokeCap::kButt);
paint.setStrokeJoin(flutter::DlStrokeJoin::kMiter);
paint.setStrokeMiter(4);
builder.DrawPath(path, paint);
}
{
builder.Save();
builder.Translate(0, 100);
// The joint in the path is 45 degrees. A miter length of 1 convert to a
// bevel in this case.
paint.setStrokeMiter(1);
builder.DrawPath(path, paint);
builder.Restore();
}
builder.Translate(150, 0);
{
paint.setStrokeCap(flutter::DlStrokeCap::kSquare);
paint.setStrokeJoin(flutter::DlStrokeJoin::kBevel);
builder.DrawPath(path, paint);
}
builder.Translate(150, 0);
{
paint.setStrokeCap(flutter::DlStrokeCap::kRound);
paint.setStrokeJoin(flutter::DlStrokeJoin::kRound);
builder.DrawPath(path, paint);
}
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawArc) {
auto callback = [&]() {
static float start_angle = 45;
static float sweep_angle = 270;
static float stroke_width = 10;
static bool use_center = true;
static int selected_cap = 0;
const char* cap_names[] = {"Butt", "Round", "Square"};
flutter::DlStrokeCap cap;
ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::SliderFloat("Start angle", &start_angle, -360, 360);
ImGui::SliderFloat("Sweep angle", &sweep_angle, -360, 360);
ImGui::SliderFloat("Stroke width", &stroke_width, 0, 300);
ImGui::Combo("Cap", &selected_cap, cap_names,
sizeof(cap_names) / sizeof(char*));
ImGui::Checkbox("Use center", &use_center);
ImGui::End();
switch (selected_cap) {
case 0:
cap = flutter::DlStrokeCap::kButt;
break;
case 1:
cap = flutter::DlStrokeCap::kRound;
break;
case 2:
cap = flutter::DlStrokeCap::kSquare;
break;
default:
cap = flutter::DlStrokeCap::kButt;
break;
}
auto [p1, p2] = IMPELLER_PLAYGROUND_LINE(
Point(200, 200), Point(400, 400), 20, Color::White(), Color::White());
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
Vector2 scale = GetContentScale();
builder.Scale(scale.x, scale.y);
paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
paint.setStrokeCap(cap);
paint.setStrokeJoin(flutter::DlStrokeJoin::kMiter);
paint.setStrokeMiter(10);
auto rect = SkRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y);
paint.setColor(flutter::DlColor::kGreen());
paint.setStrokeWidth(2);
builder.DrawRect(rect, paint);
paint.setColor(flutter::DlColor::kRed());
paint.setStrokeWidth(stroke_width);
builder.DrawArc(rect, start_angle, sweep_angle, use_center, paint);
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(DisplayListTest, StrokedPathsDrawCorrectly) {
auto callback = [&]() {
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
paint.setColor(flutter::DlColor::kRed());
paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
static float stroke_width = 10.0f;
static int selected_stroke_type = 0;
static int selected_join_type = 0;
const char* stroke_types[] = {"Butte", "Round", "Square"};
const char* join_type[] = {"kMiter", "Round", "kBevel"};
ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::Combo("Cap", &selected_stroke_type, stroke_types,
sizeof(stroke_types) / sizeof(char*));
ImGui::Combo("Join", &selected_join_type, join_type,
sizeof(join_type) / sizeof(char*));
ImGui::SliderFloat("Stroke Width", &stroke_width, 10.0f, 50.0f);
ImGui::End();
flutter::DlStrokeCap cap;
flutter::DlStrokeJoin join;
switch (selected_stroke_type) {
case 0:
cap = flutter::DlStrokeCap::kButt;
break;
case 1:
cap = flutter::DlStrokeCap::kRound;
break;
case 2:
cap = flutter::DlStrokeCap::kSquare;
break;
default:
cap = flutter::DlStrokeCap::kButt;
break;
}
switch (selected_join_type) {
case 0:
join = flutter::DlStrokeJoin::kMiter;
break;
case 1:
join = flutter::DlStrokeJoin::kRound;
break;
case 2:
join = flutter::DlStrokeJoin::kBevel;
break;
default:
join = flutter::DlStrokeJoin::kMiter;
break;
}
paint.setStrokeCap(cap);
paint.setStrokeJoin(join);
paint.setStrokeWidth(stroke_width);
// Make rendering better to watch.
builder.Scale(1.5f, 1.5f);
// Rectangle
builder.Translate(100, 100);
builder.DrawRect(SkRect::MakeSize({100, 100}), paint);
// Rounded rectangle
builder.Translate(150, 0);
builder.DrawRRect(SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10),
paint);
// Double rounded rectangle
builder.Translate(150, 0);
builder.DrawDRRect(
SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10),
SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 30), 10, 10), paint);
// Contour with duplicate join points
{
builder.Translate(150, 0);
SkPath path;
path.moveTo(0, 0);
path.lineTo(0, 0);
path.lineTo({100, 0});
path.lineTo({100, 0});
path.lineTo({100, 100});
builder.DrawPath(path, paint);
}
// Contour with duplicate start and end points
// Line.
builder.Translate(200, 0);
{
builder.Save();
SkPath line_path;
line_path.moveTo(0, 0);
line_path.moveTo(0, 0);
line_path.lineTo({0, 0});
line_path.lineTo({0, 0});
line_path.lineTo({50, 50});
line_path.lineTo({50, 50});
line_path.lineTo({100, 0});
line_path.lineTo({100, 0});
builder.DrawPath(line_path, paint);
builder.Translate(0, 100);
builder.DrawPath(line_path, paint);
builder.Translate(0, 100);
SkPath line_path2;
line_path2.moveTo(0, 0);
line_path2.lineTo(0, 0);
line_path2.lineTo(0, 0);
builder.DrawPath(line_path2, paint);
builder.Restore();
}
// Cubic.
builder.Translate(150, 0);
{
builder.Save();
SkPath cubic_path;
cubic_path.moveTo({0, 0});
cubic_path.cubicTo(0, 0, 140.0, 100.0, 140, 20);
builder.DrawPath(cubic_path, paint);
builder.Translate(0, 100);
SkPath cubic_path2;
cubic_path2.moveTo({0, 0});
cubic_path2.cubicTo(0, 0, 0, 0, 150, 150);
builder.DrawPath(cubic_path2, paint);
builder.Translate(0, 100);
SkPath cubic_path3;
cubic_path3.moveTo({0, 0});
cubic_path3.cubicTo(0, 0, 0, 0, 0, 0);
builder.DrawPath(cubic_path3, paint);
builder.Restore();
}
// Quad.
builder.Translate(200, 0);
{
builder.Save();
SkPath quad_path;
quad_path.moveTo(0, 0);
quad_path.moveTo(0, 0);
quad_path.quadTo({100, 40}, {50, 80});
builder.DrawPath(quad_path, paint);
builder.Translate(0, 150);
SkPath quad_path2;
quad_path2.moveTo(0, 0);
quad_path2.moveTo(0, 0);
quad_path2.quadTo({0, 0}, {100, 100});
builder.DrawPath(quad_path2, paint);
builder.Translate(0, 100);
SkPath quad_path3;
quad_path3.moveTo(0, 0);
quad_path3.quadTo({0, 0}, {0, 0});
builder.DrawPath(quad_path3, paint);
builder.Restore();
}
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(DisplayListTest, CanDrawWithOddPathWinding) {
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
paint.setColor(flutter::DlColor::kRed());
paint.setDrawStyle(flutter::DlDrawStyle::kFill);
builder.Translate(300, 300);
SkPath path;
path.setFillType(SkPathFillType::kEvenOdd);
path.addCircle(0, 0, 100);
path.addCircle(0, 0, 50);
builder.DrawPath(path, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
// Regression test for https://github.com/flutter/flutter/issues/134816.
//
// It should be possible to draw 3 lines, and not have an implicit close path.
TEST_P(DisplayListTest, CanDrawAnOpenPath) {
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
paint.setColor(flutter::DlColor::kRed());
paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
paint.setStrokeWidth(10);
builder.Translate(300, 300);
// Move to (50, 50) and draw lines from:
// 1. (50, height)
// 2. (width, height)
// 3. (width, 50)
SkPath path;
path.moveTo(50, 50);
path.lineTo(50, 100);
path.lineTo(100, 100);
path.lineTo(100, 50);
builder.DrawPath(path, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawWithMaskBlur) {
auto texture = CreateTextureForFixture("embarcadero.jpg");
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
// Mask blurred image.
{
auto filter =
flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kNormal, 10.0f);
paint.setMaskFilter(&filter);
builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
flutter::DlImageSampling::kNearestNeighbor, &paint);
}
// Mask blurred filled path.
{
paint.setColor(flutter::DlColor::kYellow());
auto filter =
flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kOuter, 10.0f);
paint.setMaskFilter(&filter);
builder.DrawArc(SkRect::MakeXYWH(410, 110, 100, 100), 45, 270, true, paint);
}
// Mask blurred text.
{
auto filter =
flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kSolid, 10.0f);
paint.setMaskFilter(&filter);
builder.DrawTextBlob(
SkTextBlob::MakeFromString("Testing", CreateTestFont()), 220, 170,
paint);
}
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawStrokedText) {
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
paint.setColor(flutter::DlColor::kRed());
builder.DrawTextBlob(
SkTextBlob::MakeFromString("stoked about stroked text", CreateTestFont()),
250, 250, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
// Regression test for https://github.com/flutter/flutter/issues/133157.
TEST_P(DisplayListTest, StrokedTextNotOffsetFromNormalText) {
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
auto const& text_blob = SkTextBlob::MakeFromString("00000", CreateTestFont());
// https://api.flutter.dev/flutter/material/Colors/blue-constant.html.
auto const& mat_blue = flutter::DlColor(0xFF2196f3);
// Draw a blue filled rectangle so the text is easier to see.
paint.setDrawStyle(flutter::DlDrawStyle::kFill);
paint.setColor(mat_blue);
builder.DrawRect(SkRect::MakeXYWH(0, 0, 500, 500), paint);
// Draw stacked text, with stroked text on top.
paint.setDrawStyle(flutter::DlDrawStyle::kFill);
paint.setColor(flutter::DlColor::kWhite());
builder.DrawTextBlob(text_blob, 250, 250, paint);
paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
paint.setColor(flutter::DlColor::kBlack());
builder.DrawTextBlob(text_blob, 250, 250, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, IgnoreMaskFilterWhenSavingLayer) {
auto texture = CreateTextureForFixture("embarcadero.jpg");
flutter::DisplayListBuilder builder;
auto filter = flutter::DlBlurMaskFilter(flutter::DlBlurStyle::kNormal, 10.0f);
flutter::DlPaint paint;
paint.setMaskFilter(&filter);
builder.SaveLayer(nullptr, &paint);
builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
flutter::DlImageSampling::kNearestNeighbor);
builder.Restore();
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawWithBlendColorFilter) {
auto texture = CreateTextureForFixture("embarcadero.jpg");
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
// Pipeline blended image.
{
auto filter = flutter::DlBlendColorFilter(flutter::DlColor::kYellow(),
flutter::DlBlendMode::kModulate);
paint.setColorFilter(&filter);
builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
flutter::DlImageSampling::kNearestNeighbor, &paint);
}
// Advanced blended image.
{
auto filter = flutter::DlBlendColorFilter(flutter::DlColor::kRed(),
flutter::DlBlendMode::kScreen);
paint.setColorFilter(&filter);
builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(250, 250),
flutter::DlImageSampling::kNearestNeighbor, &paint);
}
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawWithColorFilterImageFilter) {
const float invert_color_matrix[20] = {
-1, 0, 0, 0, 1, //
0, -1, 0, 0, 1, //
0, 0, -1, 0, 1, //
0, 0, 0, 1, 0, //
};
auto texture = CreateTextureForFixture("boston.jpg");
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
auto color_filter =
std::make_shared<flutter::DlMatrixColorFilter>(invert_color_matrix);
auto image_filter =
std::make_shared<flutter::DlColorFilterImageFilter>(color_filter);
paint.setImageFilter(image_filter.get());
builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
flutter::DlImageSampling::kNearestNeighbor, &paint);
builder.Translate(0, 700);
paint.setColorFilter(color_filter.get());
builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
flutter::DlImageSampling::kNearestNeighbor, &paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawWithImageBlurFilter) {
auto texture = CreateTextureForFixture("embarcadero.jpg");
auto callback = [&]() {
static float sigma[] = {10, 10};
ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::SliderFloat2("Sigma", sigma, 0, 100);
ImGui::End();
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1],
flutter::DlTileMode::kClamp);
paint.setImageFilter(&filter);
builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(200, 200),
flutter::DlImageSampling::kNearestNeighbor, &paint);
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(DisplayListTest, CanDrawWithComposeImageFilter) {
auto texture = CreateTextureForFixture("boston.jpg");
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
auto dilate = std::make_shared<flutter::DlDilateImageFilter>(10.0, 10.0);
auto erode = std::make_shared<flutter::DlErodeImageFilter>(10.0, 10.0);
auto open = std::make_shared<flutter::DlComposeImageFilter>(dilate, erode);
auto close = std::make_shared<flutter::DlComposeImageFilter>(erode, dilate);
paint.setImageFilter(open.get());
builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
flutter::DlImageSampling::kNearestNeighbor, &paint);
builder.Translate(0, 700);
paint.setImageFilter(close.get());
builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
flutter::DlImageSampling::kNearestNeighbor, &paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanClampTheResultingColorOfColorMatrixFilter) {
auto texture = CreateTextureForFixture("boston.jpg");
const float inner_color_matrix[20] = {
1, 0, 0, 0, 0, //
0, 1, 0, 0, 0, //
0, 0, 1, 0, 0, //
0, 0, 0, 2, 0, //
};
const float outer_color_matrix[20] = {
1, 0, 0, 0, 0, //
0, 1, 0, 0, 0, //
0, 0, 1, 0, 0, //
0, 0, 0, 0.5, 0, //
};
auto inner_color_filter =
std::make_shared<flutter::DlMatrixColorFilter>(inner_color_matrix);
auto outer_color_filter =
std::make_shared<flutter::DlMatrixColorFilter>(outer_color_matrix);
auto inner =
std::make_shared<flutter::DlColorFilterImageFilter>(inner_color_filter);
auto outer =
std::make_shared<flutter::DlColorFilterImageFilter>(outer_color_filter);
auto compose = std::make_shared<flutter::DlComposeImageFilter>(outer, inner);
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
paint.setImageFilter(compose.get());
builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
flutter::DlImageSampling::kNearestNeighbor, &paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawBackdropFilter) {
auto texture = CreateTextureForFixture("embarcadero.jpg");
auto callback = [&]() {
static float sigma[] = {10, 10};
static float ctm_scale = 1;
static bool use_bounds = true;
static bool draw_circle = true;
static bool add_clip = true;
ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::SliderFloat2("Sigma", sigma, 0, 100);
ImGui::SliderFloat("Scale", &ctm_scale, 0, 10);
ImGui::NewLine();
ImGui::TextWrapped(
"If everything is working correctly, none of the options below should "
"impact the filter's appearance.");
ImGui::Checkbox("Use SaveLayer bounds", &use_bounds);
ImGui::Checkbox("Draw child element", &draw_circle);
ImGui::Checkbox("Add pre-clip", &add_clip);
ImGui::End();
flutter::DisplayListBuilder builder;
Vector2 scale = ctm_scale * GetContentScale();
builder.Scale(scale.x, scale.y);
auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1],
flutter::DlTileMode::kClamp);
std::optional<SkRect> bounds;
if (use_bounds) {
auto [p1, p2] = IMPELLER_PLAYGROUND_LINE(
Point(350, 150), Point(800, 600), 20, Color::White(), Color::White());
bounds = SkRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y);
}
// Insert a clip to test that the backdrop filter handles stencil depths > 0
// correctly.
if (add_clip) {
builder.ClipRect(SkRect::MakeLTRB(0, 0, 99999, 99999),
flutter::DlCanvas::ClipOp::kIntersect, true);
}
builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(200, 200),
flutter::DlImageSampling::kNearestNeighbor, nullptr);
builder.SaveLayer(bounds.has_value() ? &bounds.value() : nullptr, nullptr,
&filter);
if (draw_circle) {
auto circle_center =
IMPELLER_PLAYGROUND_POINT(Point(500, 400), 20, Color::Red());
flutter::DlPaint paint;
paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
paint.setStrokeCap(flutter::DlStrokeCap::kButt);
paint.setStrokeJoin(flutter::DlStrokeJoin::kBevel);
paint.setStrokeWidth(10);
paint.setColor(flutter::DlColor::kRed().withAlpha(100));
builder.DrawCircle({circle_center.x, circle_center.y}, 100, paint);
}
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(DisplayListTest, CanDrawNinePatchImage) {
// Image is drawn with corners to scale and center pieces stretched to fit.
auto texture = CreateTextureForFixture("embarcadero.jpg");
flutter::DisplayListBuilder builder;
auto size = texture->GetSize();
builder.DrawImageNine(
DlImageImpeller::Make(texture),
SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
size.height * 3 / 4),
SkRect::MakeLTRB(0, 0, size.width * 2, size.height * 2),
flutter::DlFilterMode::kNearest, nullptr);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawNinePatchImageCenterWidthBiggerThanDest) {
// Edge case, the width of the corners does not leave any room for the
// center slice. The center (across the vertical axis) is folded out of the
// resulting image.
auto texture = CreateTextureForFixture("embarcadero.jpg");
flutter::DisplayListBuilder builder;
auto size = texture->GetSize();
builder.DrawImageNine(
DlImageImpeller::Make(texture),
SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
size.height * 3 / 4),
SkRect::MakeLTRB(0, 0, size.width / 2, size.height),
flutter::DlFilterMode::kNearest, nullptr);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawNinePatchImageCenterHeightBiggerThanDest) {
// Edge case, the height of the corners does not leave any room for the
// center slice. The center (across the horizontal axis) is folded out of the
// resulting image.
auto texture = CreateTextureForFixture("embarcadero.jpg");
flutter::DisplayListBuilder builder;
auto size = texture->GetSize();
builder.DrawImageNine(
DlImageImpeller::Make(texture),
SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
size.height * 3 / 4),
SkRect::MakeLTRB(0, 0, size.width, size.height / 2),
flutter::DlFilterMode::kNearest, nullptr);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawNinePatchImageCenterBiggerThanDest) {
// Edge case, the width and height of the corners does not leave any
// room for the center slices. Only the corners are displayed.
auto texture = CreateTextureForFixture("embarcadero.jpg");
flutter::DisplayListBuilder builder;
auto size = texture->GetSize();
builder.DrawImageNine(
DlImageImpeller::Make(texture),
SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
size.height * 3 / 4),
SkRect::MakeLTRB(0, 0, size.width / 2, size.height / 2),
flutter::DlFilterMode::kNearest, nullptr);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawNinePatchImageCornersScaledDown) {
// Edge case, there is not enough room for the corners to be drawn
// without scaling them down.
auto texture = CreateTextureForFixture("embarcadero.jpg");
flutter::DisplayListBuilder builder;
auto size = texture->GetSize();
builder.DrawImageNine(
DlImageImpeller::Make(texture),
SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
size.height * 3 / 4),
SkRect::MakeLTRB(0, 0, size.width / 4, size.height / 4),
flutter::DlFilterMode::kNearest, nullptr);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawPoints) {
flutter::DisplayListBuilder builder;
SkPoint points[7] = {
{0, 0}, //
{100, 100}, //
{100, 0}, //
{0, 100}, //
{0, 0}, //
{48, 48}, //
{52, 52}, //
};
std::vector<flutter::DlStrokeCap> caps = {
flutter::DlStrokeCap::kButt,
flutter::DlStrokeCap::kRound,
flutter::DlStrokeCap::kSquare,
};
flutter::DlPaint paint =
flutter::DlPaint() //
.setColor(flutter::DlColor::kYellow().withAlpha(127)) //
.setStrokeWidth(20);
builder.Translate(50, 50);
for (auto cap : caps) {
paint.setStrokeCap(cap);
builder.Save();
builder.DrawPoints(flutter::DlCanvas::PointMode::kPoints, 7, points, paint);
builder.Translate(150, 0);
builder.DrawPoints(flutter::DlCanvas::PointMode::kLines, 5, points, paint);
builder.Translate(150, 0);
builder.DrawPoints(flutter::DlCanvas::PointMode::kPolygon, 5, points,
paint);
builder.Restore();
builder.Translate(0, 150);
}
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawZeroLengthLine) {
flutter::DisplayListBuilder builder;
std::vector<flutter::DlStrokeCap> caps = {
flutter::DlStrokeCap::kButt,
flutter::DlStrokeCap::kRound,
flutter::DlStrokeCap::kSquare,
};
flutter::DlPaint paint =
flutter::DlPaint() //
.setColor(flutter::DlColor::kYellow().withAlpha(127)) //
.setDrawStyle(flutter::DlDrawStyle::kStroke) //
.setStrokeCap(flutter::DlStrokeCap::kButt) //
.setStrokeWidth(20);
SkPath path = SkPath().addPoly({{150, 50}, {150, 50}}, false);
for (auto cap : caps) {
paint.setStrokeCap(cap);
builder.DrawLine({50, 50}, {50, 50}, paint);
builder.DrawPath(path, paint);
builder.Translate(0, 150);
}
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawShadow) {
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
auto content_scale = GetContentScale() * 0.8;
builder.Scale(content_scale.x, content_scale.y);
constexpr size_t star_spikes = 5;
constexpr SkScalar half_spike_rotation = kPi / star_spikes;
constexpr SkScalar radius = 40;
constexpr SkScalar spike_size = 10;
constexpr SkScalar outer_radius = radius + spike_size;
constexpr SkScalar inner_radius = radius - spike_size;
std::array<SkPoint, star_spikes * 2> star;
for (size_t i = 0; i < star_spikes; i++) {
const SkScalar rotation = half_spike_rotation * i * 2;
star[i * 2] = SkPoint::Make(50 + std::sin(rotation) * outer_radius,
50 - std::cos(rotation) * outer_radius);
star[i * 2 + 1] = SkPoint::Make(
50 + std::sin(rotation + half_spike_rotation) * inner_radius,
50 - std::cos(rotation + half_spike_rotation) * inner_radius);
}
std::array<SkPath, 4> paths = {
SkPath{}.addRect(SkRect::MakeXYWH(0, 0, 200, 100)),
SkPath{}.addRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(20, 0, 200, 100), 30, 30)),
SkPath{}.addCircle(100, 50, 50),
SkPath{}.addPoly(star.data(), star.size(), true),
};
paint.setColor(flutter::DlColor::kWhite());
builder.DrawPaint(paint);
paint.setColor(flutter::DlColor::kCyan());
builder.Translate(100, 50);
for (size_t x = 0; x < paths.size(); x++) {
builder.Save();
for (size_t y = 0; y < 6; y++) {
builder.DrawShadow(paths[x], flutter::DlColor::kBlack(), 3 + y * 8, false,
1);
builder.DrawPath(paths[x], paint);
builder.Translate(0, 150);
}
builder.Restore();
builder.Translate(250, 0);
}
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest,
DispatcherDoesNotCullPerspectiveTransformedChildDisplayLists) {
// Regression test for https://github.com/flutter/flutter/issues/130613
flutter::DisplayListBuilder sub_builder(true);
sub_builder.DrawRect(SkRect::MakeXYWH(0, 0, 50, 50),
flutter::DlPaint(flutter::DlColor::kRed()));
auto display_list = sub_builder.Build();
DlDispatcher dispatcher(Rect::MakeLTRB(0, 0, 2400, 1800));
dispatcher.scale(2.0, 2.0);
dispatcher.translate(-93.0, 0.0);
// clang-format off
dispatcher.transformFullPerspective(
0.8, -0.2, -0.1, -0.0,
0.0, 1.0, 0.0, 0.0,
1.4, 1.3, 1.0, 0.0,
63.2, 65.3, 48.6, 1.1
);
// clang-format on
dispatcher.translate(35.0, 75.0);
dispatcher.drawDisplayList(display_list, 1.0f);
auto picture = dispatcher.EndRecordingAsPicture();
bool found = false;
picture.pass->IterateAllEntities([&found](Entity& entity) {
if (std::static_pointer_cast<SolidColorContents>(entity.GetContents())
->GetColor() == Color::Red()) {
found = true;
return false;
}
return true;
});
EXPECT_TRUE(found);
}
TEST_P(DisplayListTest, TransparentShadowProducesCorrectColor) {
DlDispatcher dispatcher;
dispatcher.save();
dispatcher.scale(1.618, 1.618);
dispatcher.drawShadow(SkPath{}.addRect(SkRect::MakeXYWH(0, 0, 200, 100)),
flutter::DlColor::kTransparent(), 15, false, 1);
dispatcher.restore();
auto picture = dispatcher.EndRecordingAsPicture();
std::shared_ptr<SolidRRectBlurContents> rrect_blur;
picture.pass->IterateAllEntities([&rrect_blur](Entity& entity) {
if (ScalarNearlyEqual(entity.GetTransformation().GetScale().x, 1.618f)) {
rrect_blur = std::static_pointer_cast<SolidRRectBlurContents>(
entity.GetContents());
return false;
}
return true;
});
ASSERT_NE(rrect_blur, nullptr);
ASSERT_EQ(rrect_blur->GetColor().red, 0);
ASSERT_EQ(rrect_blur->GetColor().green, 0);
ASSERT_EQ(rrect_blur->GetColor().blue, 0);
ASSERT_EQ(rrect_blur->GetColor().alpha, 0);
}
// Draw a hexagon using triangle fan
TEST_P(DisplayListTest, CanConvertTriangleFanToTriangles) {
constexpr Scalar hexagon_radius = 125;
auto hex_start = Point(200.0, -hexagon_radius + 200.0);
auto center_to_flat = 1.73 / 2 * hexagon_radius;
// clang-format off
std::vector<SkPoint> vertices = {
SkPoint::Make(hex_start.x, hex_start.y),
SkPoint::Make(hex_start.x + center_to_flat, hex_start.y + 0.5 * hexagon_radius),
SkPoint::Make(hex_start.x + center_to_flat, hex_start.y + 1.5 * hexagon_radius),
SkPoint::Make(hex_start.x + center_to_flat, hex_start.y + 1.5 * hexagon_radius),
SkPoint::Make(hex_start.x, hex_start.y + 2 * hexagon_radius),
SkPoint::Make(hex_start.x, hex_start.y + 2 * hexagon_radius),
SkPoint::Make(hex_start.x - center_to_flat, hex_start.y + 1.5 * hexagon_radius),
SkPoint::Make(hex_start.x - center_to_flat, hex_start.y + 1.5 * hexagon_radius),
SkPoint::Make(hex_start.x - center_to_flat, hex_start.y + 0.5 * hexagon_radius)
};
// clang-format on
auto paint = flutter::DlPaint(flutter::DlColor::kDarkGrey());
auto dl_vertices = flutter::DlVertices::Make(
flutter::DlVertexMode::kTriangleFan, vertices.size(), vertices.data(),
nullptr, nullptr);
flutter::DisplayListBuilder builder;
builder.DrawVertices(dl_vertices, flutter::DlBlendMode::kSrcOver, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawZeroWidthLine) {
flutter::DisplayListBuilder builder;
std::vector<flutter::DlStrokeCap> caps = {
flutter::DlStrokeCap::kButt,
flutter::DlStrokeCap::kRound,
flutter::DlStrokeCap::kSquare,
};
flutter::DlPaint paint = //
flutter::DlPaint() //
.setColor(flutter::DlColor::kWhite()) //
.setDrawStyle(flutter::DlDrawStyle::kStroke) //
.setStrokeWidth(0);
flutter::DlPaint outline_paint = //
flutter::DlPaint() //
.setColor(flutter::DlColor::kYellow()) //
.setDrawStyle(flutter::DlDrawStyle::kStroke) //
.setStrokeCap(flutter::DlStrokeCap::kSquare) //
.setStrokeWidth(1);
SkPath path = SkPath().addPoly({{150, 50}, {160, 50}}, false);
for (auto cap : caps) {
paint.setStrokeCap(cap);
builder.DrawLine({50, 50}, {60, 50}, paint);
builder.DrawRect({45, 45, 65, 55}, outline_paint);
builder.DrawLine({100, 50}, {100, 50}, paint);
if (cap != flutter::DlStrokeCap::kButt) {
builder.DrawRect({95, 45, 105, 55}, outline_paint);
}
builder.DrawPath(path, paint);
builder.DrawRect(path.getBounds().makeOutset(5, 5), outline_paint);
builder.Translate(0, 150);
}
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawWithMatrixFilter) {
auto boston = CreateTextureForFixture("boston.jpg");
auto callback = [&]() {
static int selected_matrix_type = 0;
const char* matrix_type_names[] = {"Matrix", "Local Matrix"};
static float ctm_translation[2] = {200, 200};
static float ctm_scale[2] = {0.65, 0.65};
static float ctm_skew[2] = {0, 0};
static bool enable = true;
static float translation[2] = {100, 100};
static float scale[2] = {0.8, 0.8};
static float skew[2] = {0.2, 0.2};
static bool enable_savelayer = true;
ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
{
ImGui::Combo("Filter type", &selected_matrix_type, matrix_type_names,
sizeof(matrix_type_names) / sizeof(char*));
ImGui::TextWrapped("Current Transform");
ImGui::SliderFloat2("CTM Translation", ctm_translation, 0, 1000);
ImGui::SliderFloat2("CTM Scale", ctm_scale, 0, 3);
ImGui::SliderFloat2("CTM Skew", ctm_skew, -3, 3);
ImGui::TextWrapped(
"MatrixFilter and LocalMatrixFilter modify the CTM in the same way. "
"The only difference is that MatrixFilter doesn't affect the effect "
"transform, whereas LocalMatrixFilter does.");
// Note: See this behavior in:
// https://fiddle.skia.org/c/6cbb551ab36d06f163db8693972be954
ImGui::Checkbox("Enable", &enable);
ImGui::SliderFloat2("Filter Translation", translation, 0, 1000);
ImGui::SliderFloat2("Filter Scale", scale, 0, 3);
ImGui::SliderFloat2("Filter Skew", skew, -3, 3);
ImGui::TextWrapped(
"Rendering the filtered image within a layer can expose bounds "
"issues. If the rendered image gets cut off when this setting is "
"enabled, there's a coverage bug in the filter.");
ImGui::Checkbox("Render in layer", &enable_savelayer);
}
ImGui::End();
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
if (enable_savelayer) {
builder.SaveLayer(nullptr, nullptr);
}
{
auto content_scale = GetContentScale();
builder.Scale(content_scale.x, content_scale.y);
// Set the current transform
auto ctm_matrix =
SkMatrix::MakeAll(ctm_scale[0], ctm_skew[0], ctm_translation[0], //
ctm_skew[1], ctm_scale[1], ctm_translation[1], //
0, 0, 1);
builder.Transform(ctm_matrix);
// Set the matrix filter
auto filter_matrix =
SkMatrix::MakeAll(scale[0], skew[0], translation[0], //
skew[1], scale[1], translation[1], //
0, 0, 1);
if (enable) {
switch (selected_matrix_type) {
case 0: {
auto filter = flutter::DlMatrixImageFilter(
filter_matrix, flutter::DlImageSampling::kLinear);
paint.setImageFilter(&filter);
break;
}
case 1: {
auto internal_filter =
flutter::DlBlurImageFilter(10, 10, flutter::DlTileMode::kDecal)
.shared();
auto filter = flutter::DlLocalMatrixImageFilter(filter_matrix,
internal_filter);
paint.setImageFilter(&filter);
break;
}
}
}
builder.DrawImage(DlImageImpeller::Make(boston), {},
flutter::DlImageSampling::kLinear, &paint);
}
if (enable_savelayer) {
builder.Restore();
}
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(DisplayListTest, CanDrawWithMatrixFilterWhenSavingLayer) {
auto callback = [&]() {
static float translation[2] = {0, 0};
static bool enable_save_layer = true;
ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
ImGui::SliderFloat2("Translation", translation, -130, 130);
ImGui::Checkbox("Enable save layer", &enable_save_layer);
ImGui::End();
flutter::DisplayListBuilder builder;
builder.Save();
builder.Scale(2.0, 2.0);
flutter::DlPaint paint;
paint.setColor(flutter::DlColor::kYellow());
builder.DrawRect(SkRect::MakeWH(300, 300), paint);
paint.setStrokeWidth(1.0);
paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
paint.setColor(flutter::DlColor::kBlack().withAlpha(0x80));
builder.DrawLine(SkPoint::Make(150, 0), SkPoint::Make(150, 300), paint);
builder.DrawLine(SkPoint::Make(0, 150), SkPoint::Make(300, 150), paint);
flutter::DlPaint save_paint;
SkRect bounds = SkRect::MakeXYWH(100, 100, 100, 100);
SkMatrix translate_matrix =
SkMatrix::Translate(translation[0], translation[1]);
if (enable_save_layer) {
auto filter = flutter::DlMatrixImageFilter(
translate_matrix, flutter::DlImageSampling::kNearestNeighbor);
save_paint.setImageFilter(filter.shared());
builder.SaveLayer(&bounds, &save_paint);
} else {
builder.Save();
builder.Transform(translate_matrix);
}
SkMatrix filter_matrix = SkMatrix::I();
filter_matrix.postTranslate(-150, -150);
filter_matrix.postScale(0.2f, 0.2f);
filter_matrix.postTranslate(150, 150);
auto filter = flutter::DlMatrixImageFilter(
filter_matrix, flutter::DlImageSampling::kNearestNeighbor);
save_paint.setImageFilter(filter.shared());
builder.SaveLayer(&bounds, &save_paint);
flutter::DlPaint paint2;
paint2.setColor(flutter::DlColor::kBlue());
builder.DrawRect(bounds, paint2);
builder.Restore();
builder.Restore();
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
TEST_P(DisplayListTest, CanDrawRectWithLinearToSrgbColorFilter) {
flutter::DlPaint paint;
paint.setColor(flutter::DlColor(0xFF2196F3).withAlpha(128));
flutter::DisplayListBuilder builder;
paint.setColorFilter(
flutter::DlLinearToSrgbGammaColorFilter::kInstance.get());
builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
builder.Translate(0, 200);
paint.setColorFilter(
flutter::DlSrgbToLinearGammaColorFilter::kInstance.get());
builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawPaintWithColorSource) {
const flutter::DlColor colors[2] = {
flutter::DlColor(0xFFF44336),
flutter::DlColor(0xFF2196F3),
};
const float stops[2] = {0.0, 1.0};
flutter::DlPaint paint;
flutter::DisplayListBuilder builder;
auto clip_bounds = SkRect::MakeWH(300.0, 300.0);
builder.Save();
builder.Translate(100, 100);
builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
auto linear =
flutter::DlColorSource::MakeLinear({0.0, 0.0}, {100.0, 100.0}, 2, colors,
stops, flutter::DlTileMode::kRepeat);
paint.setColorSource(linear);
builder.DrawPaint(paint);
builder.Restore();
builder.Save();
builder.Translate(500, 100);
builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
auto radial = flutter::DlColorSource::MakeRadial(
{100.0, 100.0}, 100.0, 2, colors, stops, flutter::DlTileMode::kRepeat);
paint.setColorSource(radial);
builder.DrawPaint(paint);
builder.Restore();
builder.Save();
builder.Translate(100, 500);
builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
auto sweep =
flutter::DlColorSource::MakeSweep({100.0, 100.0}, 180.0, 270.0, 2, colors,
stops, flutter::DlTileMode::kRepeat);
paint.setColorSource(sweep);
builder.DrawPaint(paint);
builder.Restore();
builder.Save();
builder.Translate(500, 500);
builder.ClipRect(clip_bounds, flutter::DlCanvas::ClipOp::kIntersect, false);
auto texture = CreateTextureForFixture("table_mountain_nx.png");
auto image = std::make_shared<flutter::DlImageColorSource>(
DlImageImpeller::Make(texture), flutter::DlTileMode::kRepeat,
flutter::DlTileMode::kRepeat);
paint.setColorSource(image);
builder.DrawPaint(paint);
builder.Restore();
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanBlendDstOverAndDstCorrectly) {
flutter::DisplayListBuilder builder;
{
builder.SaveLayer(nullptr, nullptr);
builder.Translate(100, 100);
flutter::DlPaint paint;
paint.setColor(flutter::DlColor::kRed());
builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
paint.setBlendMode(flutter::DlBlendMode::kSrcOver);
builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
builder.Restore();
}
{
builder.SaveLayer(nullptr, nullptr);
builder.Translate(300, 100);
flutter::DlPaint paint;
paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
paint.setColor(flutter::DlColor::kRed());
paint.setBlendMode(flutter::DlBlendMode::kDstOver);
builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
builder.Restore();
}
{
builder.SaveLayer(nullptr, nullptr);
builder.Translate(100, 300);
flutter::DlPaint paint;
paint.setColor(flutter::DlColor::kRed());
builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
paint.setBlendMode(flutter::DlBlendMode::kSrc);
builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
builder.Restore();
}
{
builder.SaveLayer(nullptr, nullptr);
builder.Translate(300, 300);
flutter::DlPaint paint;
paint.setColor(flutter::DlColor::kBlue().withAlpha(127));
builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
paint.setColor(flutter::DlColor::kRed());
paint.setBlendMode(flutter::DlBlendMode::kDst);
builder.DrawRect(SkRect::MakeSize({200, 200}), paint);
builder.Restore();
}
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, CanDrawCorrectlyWithColorFilterAndImageFilter) {
flutter::DisplayListBuilder builder;
const float green_color_matrix[20] = {
0, 0, 0, 0, 0, //
0, 0, 0, 0, 1, //
0, 0, 0, 0, 0, //
0, 0, 0, 1, 0, //
};
const float blue_color_matrix[20] = {
0, 0, 0, 0, 0, //
0, 0, 0, 0, 0, //
0, 0, 0, 0, 1, //
0, 0, 0, 1, 0, //
};
auto green_color_filter =
std::make_shared<flutter::DlMatrixColorFilter>(green_color_matrix);
auto blue_color_filter =
std::make_shared<flutter::DlMatrixColorFilter>(blue_color_matrix);
auto blue_image_filter =
std::make_shared<flutter::DlColorFilterImageFilter>(blue_color_filter);
flutter::DlPaint paint;
paint.setColor(flutter::DlColor::kRed());
paint.setColorFilter(green_color_filter);
paint.setImageFilter(blue_image_filter);
builder.DrawRect(SkRect::MakeLTRB(100, 100, 500, 500), paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, MaskBlursApplyCorrectlyToColorSources) {
auto blur_filter = std::make_shared<flutter::DlBlurMaskFilter>(
flutter::DlBlurStyle::kNormal, 10);
flutter::DisplayListBuilder builder;
std::array<flutter::DlColor, 2> colors = {flutter::DlColor::kBlue(),
flutter::DlColor::kGreen()};
std::array<float, 2> stops = {0, 1};
std::array<std::shared_ptr<flutter::DlColorSource>, 2> color_sources = {
std::make_shared<flutter::DlColorColorSource>(flutter::DlColor::kWhite()),
flutter::DlColorSource::MakeLinear(
SkPoint::Make(0, 0), SkPoint::Make(100, 50), 2, colors.data(),
stops.data(), flutter::DlTileMode::kClamp)};
int offset = 100;
for (const auto& color_source : color_sources) {
flutter::DlPaint paint;
paint.setColorSource(color_source);
paint.setMaskFilter(blur_filter);
paint.setDrawStyle(flutter::DlDrawStyle::kFill);
builder.DrawRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(100, offset, 100, 50), 30, 30),
paint);
paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
paint.setStrokeWidth(10);
builder.DrawRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(300, offset, 100, 50), 30, 30),
paint);
offset += 100;
}
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, DrawVerticesSolidColorTrianglesWithoutIndices) {
// Use negative coordinates and then scale the transform by -1, -1 to make
// sure coverage is taking the transform into account.
std::vector<SkPoint> positions = {SkPoint::Make(-100, -300),
SkPoint::Make(-200, -100),
SkPoint::Make(-300, -300)};
std::vector<flutter::DlColor> colors = {flutter::DlColor::kWhite(),
flutter::DlColor::kGreen(),
flutter::DlColor::kWhite()};
auto vertices = flutter::DlVertices::Make(
flutter::DlVertexMode::kTriangles, 3, positions.data(),
/*texture_coordinates=*/nullptr, colors.data());
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
paint.setColor(flutter::DlColor::kRed().modulateOpacity(0.5));
builder.Scale(-1, -1);
builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, DrawVerticesLinearGradientWithoutIndices) {
std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
SkPoint::Make(200, 100),
SkPoint::Make(300, 300)};
auto vertices = flutter::DlVertices::Make(
flutter::DlVertexMode::kTriangles, 3, positions.data(),
/*texture_coordinates=*/nullptr, /*colors=*/nullptr);
std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
flutter::DlColor::kRed()};
const float stops[2] = {0.0, 1.0};
auto linear = flutter::DlColorSource::MakeLinear(
{100.0, 100.0}, {300.0, 300.0}, 2, colors.data(), stops,
flutter::DlTileMode::kRepeat);
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
paint.setColorSource(linear);
builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, DrawVerticesLinearGradientWithTextureCoordinates) {
std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
SkPoint::Make(200, 100),
SkPoint::Make(300, 300)};
std::vector<SkPoint> texture_coordinates = {SkPoint::Make(300, 100),
SkPoint::Make(100, 200),
SkPoint::Make(300, 300)};
auto vertices = flutter::DlVertices::Make(
flutter::DlVertexMode::kTriangles, 3, positions.data(),
texture_coordinates.data(), /*colors=*/nullptr);
std::vector<flutter::DlColor> colors = {flutter::DlColor::kBlue(),
flutter::DlColor::kRed()};
const float stops[2] = {0.0, 1.0};
auto linear = flutter::DlColorSource::MakeLinear(
{100.0, 100.0}, {300.0, 300.0}, 2, colors.data(), stops,
flutter::DlTileMode::kRepeat);
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
paint.setColorSource(linear);
builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, DrawVerticesImageSourceWithTextureCoordinates) {
auto texture = CreateTextureForFixture("embarcadero.jpg");
auto dl_image = DlImageImpeller::Make(texture);
std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
SkPoint::Make(200, 100),
SkPoint::Make(300, 300)};
std::vector<SkPoint> texture_coordinates = {
SkPoint::Make(0, 0), SkPoint::Make(100, 200), SkPoint::Make(200, 100)};
auto vertices = flutter::DlVertices::Make(
flutter::DlVertexMode::kTriangles, 3, positions.data(),
texture_coordinates.data(), /*colors=*/nullptr);
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
auto image_source = flutter::DlImageColorSource(
dl_image, flutter::DlTileMode::kRepeat, flutter::DlTileMode::kRepeat);
paint.setColorSource(&image_source);
builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest,
DrawVerticesImageSourceWithTextureCoordinatesAndColorBlending) {
auto texture = CreateTextureForFixture("embarcadero.jpg");
auto dl_image = DlImageImpeller::Make(texture);
std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
SkPoint::Make(200, 100),
SkPoint::Make(300, 300)};
std::vector<flutter::DlColor> colors = {flutter::DlColor::kWhite(),
flutter::DlColor::kGreen(),
flutter::DlColor::kWhite()};
std::vector<SkPoint> texture_coordinates = {
SkPoint::Make(0, 0), SkPoint::Make(100, 200), SkPoint::Make(200, 100)};
auto vertices = flutter::DlVertices::Make(
flutter::DlVertexMode::kTriangles, 3, positions.data(),
texture_coordinates.data(), colors.data());
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
auto image_source = flutter::DlImageColorSource(
dl_image, flutter::DlTileMode::kRepeat, flutter::DlTileMode::kRepeat);
paint.setColorSource(&image_source);
builder.DrawVertices(vertices, flutter::DlBlendMode::kModulate, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, DrawVerticesSolidColorTrianglesWithIndices) {
std::vector<SkPoint> positions = {
SkPoint::Make(100, 300), SkPoint::Make(200, 100), SkPoint::Make(300, 300),
SkPoint::Make(200, 500)};
std::vector<uint16_t> indices = {0, 1, 2, 0, 2, 3};
auto vertices = flutter::DlVertices::Make(
flutter::DlVertexMode::kTriangles, 6, positions.data(),
/*texture_coordinates=*/nullptr, /*colors=*/nullptr, 6, indices.data());
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
paint.setColor(flutter::DlColor::kWhite());
builder.DrawVertices(vertices, flutter::DlBlendMode::kSrcOver, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, DrawVerticesPremultipliesColors) {
std::vector<SkPoint> positions = {
SkPoint::Make(100, 300), SkPoint::Make(200, 100), SkPoint::Make(300, 300),
SkPoint::Make(200, 500)};
auto color = flutter::DlColor::kBlue().withAlpha(0x99);
std::vector<uint16_t> indices = {0, 1, 2, 0, 2, 3};
std::vector<flutter::DlColor> colors = {color, color, color, color};
auto vertices = flutter::DlVertices::Make(
flutter::DlVertexMode::kTriangles, 6, positions.data(),
/*texture_coordinates=*/nullptr, colors.data(), 6, indices.data());
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
paint.setBlendMode(flutter::DlBlendMode::kSrcOver);
paint.setColor(flutter::DlColor::kRed());
builder.DrawRect(SkRect::MakeLTRB(0, 0, 400, 400), paint);
builder.DrawVertices(vertices, flutter::DlBlendMode::kDst, paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, DrawShapes) {
flutter::DisplayListBuilder builder;
std::vector<flutter::DlStrokeJoin> joins = {
flutter::DlStrokeJoin::kBevel,
flutter::DlStrokeJoin::kRound,
flutter::DlStrokeJoin::kMiter,
};
flutter::DlPaint paint = //
flutter::DlPaint() //
.setColor(flutter::DlColor::kWhite()) //
.setDrawStyle(flutter::DlDrawStyle::kFill) //
.setStrokeWidth(10);
flutter::DlPaint stroke_paint = //
flutter::DlPaint() //
.setColor(flutter::DlColor::kWhite()) //
.setDrawStyle(flutter::DlDrawStyle::kStroke) //
.setStrokeWidth(10);
SkPath path = SkPath().addPoly({{150, 50}, {160, 50}}, false);
builder.Translate(300, 50);
builder.Scale(0.8, 0.8);
for (auto join : joins) {
paint.setStrokeJoin(join);
stroke_paint.setStrokeJoin(join);
builder.DrawRect(SkRect::MakeXYWH(0, 0, 100, 100), paint);
builder.DrawRect(SkRect::MakeXYWH(0, 150, 100, 100), stroke_paint);
builder.DrawRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(150, 0, 100, 100), 30, 30), paint);
builder.DrawRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(150, 150, 100, 100), 30, 30),
stroke_paint);
builder.DrawCircle({350, 50}, 50, paint);
builder.DrawCircle({350, 200}, 50, stroke_paint);
builder.Translate(0, 300);
}
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, ClipDrawRRectWithNonCircularRadii) {
flutter::DisplayListBuilder builder;
flutter::DlPaint fill_paint = //
flutter::DlPaint() //
.setColor(flutter::DlColor::kBlue()) //
.setDrawStyle(flutter::DlDrawStyle::kFill) //
.setStrokeWidth(10);
flutter::DlPaint stroke_paint = //
flutter::DlPaint() //
.setColor(flutter::DlColor::kGreen()) //
.setDrawStyle(flutter::DlDrawStyle::kStroke) //
.setStrokeWidth(10);
builder.DrawRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 100, 300, 300), 120, 40),
fill_paint);
builder.DrawRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 100, 300, 300), 120, 40),
stroke_paint);
builder.DrawRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 500, 300, 300), 40, 120),
fill_paint);
builder.DrawRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 500, 300, 300), 40, 120),
stroke_paint);
flutter::DlPaint reference_paint = //
flutter::DlPaint() //
.setColor(flutter::DlColor::kMidGrey()) //
.setDrawStyle(flutter::DlDrawStyle::kFill) //
.setStrokeWidth(10);
builder.DrawRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(500, 500, 300, 300), 40, 40),
reference_paint);
builder.DrawRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 100, 300, 300), 120, 120),
reference_paint);
flutter::DlPaint clip_fill_paint = //
flutter::DlPaint() //
.setColor(flutter::DlColor::kCyan()) //
.setDrawStyle(flutter::DlDrawStyle::kFill) //
.setStrokeWidth(10);
builder.Save();
builder.ClipRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(900, 100, 300, 300), 120, 40));
builder.DrawPaint(clip_fill_paint);
builder.Restore();
builder.Save();
builder.ClipRRect(
SkRRect::MakeRectXY(SkRect::MakeXYWH(100, 900, 300, 300), 40, 120));
builder.DrawPaint(clip_fill_paint);
builder.Restore();
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
TEST_P(DisplayListTest, DrawVerticesBlendModes) {
std::vector<const char*> blend_mode_names;
std::vector<flutter::DlBlendMode> blend_mode_values;
{
const std::vector<std::tuple<const char*, flutter::DlBlendMode>> blends = {
// Pipeline blends (Porter-Duff alpha compositing)
{"Clear", flutter::DlBlendMode::kClear},
{"Source", flutter::DlBlendMode::kSrc},
{"Destination", flutter::DlBlendMode::kDst},
{"SourceOver", flutter::DlBlendMode::kSrcOver},
{"DestinationOver", flutter::DlBlendMode::kDstOver},
{"SourceIn", flutter::DlBlendMode::kSrcIn},
{"DestinationIn", flutter::DlBlendMode::kDstIn},
{"SourceOut", flutter::DlBlendMode::kSrcOut},
{"DestinationOut", flutter::DlBlendMode::kDstOut},
{"SourceATop", flutter::DlBlendMode::kSrcATop},
{"DestinationATop", flutter::DlBlendMode::kDstATop},
{"Xor", flutter::DlBlendMode::kXor},
{"Plus", flutter::DlBlendMode::kPlus},
{"Modulate", flutter::DlBlendMode::kModulate},
// Advanced blends (color component blends)
{"Screen", flutter::DlBlendMode::kScreen},
{"Overlay", flutter::DlBlendMode::kOverlay},
{"Darken", flutter::DlBlendMode::kDarken},
{"Lighten", flutter::DlBlendMode::kLighten},
{"ColorDodge", flutter::DlBlendMode::kColorDodge},
{"ColorBurn", flutter::DlBlendMode::kColorBurn},
{"HardLight", flutter::DlBlendMode::kHardLight},
{"SoftLight", flutter::DlBlendMode::kSoftLight},
{"Difference", flutter::DlBlendMode::kDifference},
{"Exclusion", flutter::DlBlendMode::kExclusion},
{"Multiply", flutter::DlBlendMode::kMultiply},
{"Hue", flutter::DlBlendMode::kHue},
{"Saturation", flutter::DlBlendMode::kSaturation},
{"Color", flutter::DlBlendMode::kColor},
{"Luminosity", flutter::DlBlendMode::kLuminosity},
};
assert(blends.size() ==
static_cast<size_t>(flutter::DlBlendMode::kLastMode) + 1);
for (const auto& [name, mode] : blends) {
blend_mode_names.push_back(name);
blend_mode_values.push_back(mode);
}
}
auto callback = [&]() {
static int current_blend_index = 3;
static float dst_alpha = 1;
static float src_alpha = 1;
static float color0[4] = {1.0f, 0.0f, 0.0f, 1.0f};
static float color1[4] = {0.0f, 1.0f, 0.0f, 1.0f};
static float color2[4] = {0.0f, 0.0f, 1.0f, 1.0f};
static float src_color[4] = {1.0f, 1.0f, 1.0f, 1.0f};
ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
{
ImGui::ListBox("Blending mode", &current_blend_index,
blend_mode_names.data(), blend_mode_names.size());
ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1);
ImGui::ColorEdit4("Color A", color0);
ImGui::ColorEdit4("Color B", color1);
ImGui::ColorEdit4("Color C", color2);
ImGui::ColorEdit4("Source Color", src_color);
ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1);
}
ImGui::End();
std::vector<SkPoint> positions = {SkPoint::Make(100, 300),
SkPoint::Make(200, 100),
SkPoint::Make(300, 300)};
std::vector<flutter::DlColor> colors = {
toColor(color0).modulateOpacity(dst_alpha),
toColor(color1).modulateOpacity(dst_alpha),
toColor(color2).modulateOpacity(dst_alpha)};
auto vertices = flutter::DlVertices::Make(
flutter::DlVertexMode::kTriangles, 3, positions.data(),
/*texture_coordinates=*/nullptr, colors.data());
flutter::DisplayListBuilder builder;
flutter::DlPaint paint;
paint.setColor(toColor(src_color).modulateOpacity(src_alpha));
builder.DrawVertices(vertices, blend_mode_values[current_blend_index],
paint);
return builder.Build();
};
ASSERT_TRUE(OpenPlaygroundHere(callback));
}
template <typename Contents>
static std::optional<Rect> GetCoverageOfFirstEntity(const Picture& picture) {
std::optional<Rect> coverage;
picture.pass->IterateAllEntities([&coverage](Entity& entity) {
if (std::static_pointer_cast<Contents>(entity.GetContents())) {
auto contents = std::static_pointer_cast<Contents>(entity.GetContents());
Entity entity;
coverage = contents->GetCoverage(entity);
return false;
}
return true;
});
return coverage;
}
TEST(DisplayListTest, RRectBoundsComputation) {
SkRRect rrect = SkRRect::MakeRectXY(SkRect::MakeLTRB(0, 0, 100, 100), 4, 4);
SkPath path = SkPath().addRRect(rrect);
flutter::DlPaint paint;
flutter::DisplayListBuilder builder;
builder.DrawPath(path, paint);
auto display_list = builder.Build();
DlDispatcher dispatcher;
display_list->Dispatch(dispatcher);
auto picture = dispatcher.EndRecordingAsPicture();
std::optional<Rect> coverage =
GetCoverageOfFirstEntity<SolidColorContents>(picture);
// Validate that the RRect coverage is _exactly_ the same as the input rect.
ASSERT_TRUE(coverage.has_value());
ASSERT_EQ(coverage.value_or(Rect::MakeMaximum()),
Rect::MakeLTRB(0, 0, 100, 100));
}
TEST(DisplayListTest, CircleBoundsComputation) {
SkPath path = SkPath().addCircle(0, 0, 5);
flutter::DlPaint paint;
flutter::DisplayListBuilder builder;
builder.DrawPath(path, paint);
auto display_list = builder.Build();
DlDispatcher dispatcher;
display_list->Dispatch(dispatcher);
auto picture = dispatcher.EndRecordingAsPicture();
std::optional<Rect> coverage =
GetCoverageOfFirstEntity<SolidColorContents>(picture);
ASSERT_TRUE(coverage.has_value());
ASSERT_EQ(coverage.value_or(Rect::MakeMaximum()),
Rect::MakeLTRB(-5, -5, 5, 5));
}
#ifdef IMPELLER_ENABLE_3D
TEST_P(DisplayListTest, SceneColorSource) {
// Load up the scene.
auto mapping =
flutter::testing::OpenFixtureAsMapping("flutter_logo_baked.glb.ipscene");
ASSERT_NE(mapping, nullptr);
std::shared_ptr<scene::Node> gltf_scene =
impeller::scene::Node::MakeFromFlatbuffer(
*mapping, *GetContext()->GetResourceAllocator());
ASSERT_NE(gltf_scene, nullptr);
flutter::DisplayListBuilder builder;
auto color_source = std::make_shared<flutter::DlSceneColorSource>(
gltf_scene,
Matrix::MakePerspective(Degrees(45), GetWindowSize(), 0.1, 1000) *
Matrix::MakeLookAt({3, 2, -5}, {0, 0, 0}, {0, 1, 0}));
flutter::DlPaint paint = flutter::DlPaint().setColorSource(color_source);
builder.DrawPaint(paint);
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
#endif
} // namespace testing
} // namespace impeller