mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Impeller] Direct tessellation of simple filled round rects (flutter/engine#48919)
Simple round rects with the same width and height at each corner are tessellated directly for fill operations, but not yet for stroke operations.
This commit is contained in:
parent
181ced849c
commit
06f5a18499
@ -5115,6 +5115,8 @@ ORIGIN: ../../../flutter/impeller/entity/geometry/point_field_geometry.cc + ../.
|
||||
ORIGIN: ../../../flutter/impeller/entity/geometry/point_field_geometry.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/geometry/rect_geometry.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/geometry/rect_geometry.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/geometry/round_rect_geometry.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/geometry/round_rect_geometry.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/geometry/stroke_path_geometry.cc + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/geometry/stroke_path_geometry.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/impeller/entity/geometry/vertices_geometry.cc + ../../../flutter/LICENSE
|
||||
@ -7916,6 +7918,8 @@ FILE: ../../../flutter/impeller/entity/geometry/point_field_geometry.cc
|
||||
FILE: ../../../flutter/impeller/entity/geometry/point_field_geometry.h
|
||||
FILE: ../../../flutter/impeller/entity/geometry/rect_geometry.cc
|
||||
FILE: ../../../flutter/impeller/entity/geometry/rect_geometry.h
|
||||
FILE: ../../../flutter/impeller/entity/geometry/round_rect_geometry.cc
|
||||
FILE: ../../../flutter/impeller/entity/geometry/round_rect_geometry.h
|
||||
FILE: ../../../flutter/impeller/entity/geometry/stroke_path_geometry.cc
|
||||
FILE: ../../../flutter/impeller/entity/geometry/stroke_path_geometry.h
|
||||
FILE: ../../../flutter/impeller/entity/geometry/vertices_geometry.cc
|
||||
|
||||
@ -2264,6 +2264,74 @@ TEST_P(AiksTest, FilledEllipsesRenderCorrectly) {
|
||||
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, FilledRoundRectsRenderCorrectly) {
|
||||
Canvas canvas;
|
||||
canvas.Scale(GetContentScale());
|
||||
Paint paint;
|
||||
const int color_count = 3;
|
||||
Color colors[color_count] = {
|
||||
Color::Blue(),
|
||||
Color::Green(),
|
||||
Color::Crimson(),
|
||||
};
|
||||
|
||||
paint.color = Color::White();
|
||||
canvas.DrawPaint(paint);
|
||||
|
||||
int c_index = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
paint.color = colors[(c_index++) % color_count];
|
||||
canvas.DrawRRect(Rect::MakeXYWH(i * 100 + 10, j * 100 + 20, 80, 80),
|
||||
Size(i * 5 + 10, j * 5 + 10), paint);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Color> gradient_colors = {
|
||||
Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0},
|
||||
Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0},
|
||||
Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0},
|
||||
Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0},
|
||||
Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0},
|
||||
Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0},
|
||||
Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}};
|
||||
std::vector<Scalar> stops = {
|
||||
0.0,
|
||||
(1.0 / 6.0) * 1,
|
||||
(1.0 / 6.0) * 2,
|
||||
(1.0 / 6.0) * 3,
|
||||
(1.0 / 6.0) * 4,
|
||||
(1.0 / 6.0) * 5,
|
||||
1.0,
|
||||
};
|
||||
auto texture = CreateTextureForFixture("airplane.jpg",
|
||||
/*enable_mipmapping=*/true);
|
||||
|
||||
paint.color = Color::White().WithAlpha(0.1);
|
||||
|
||||
paint.color_source = ColorSource::MakeRadialGradient(
|
||||
{500, 550}, 75, std::move(gradient_colors), std::move(stops),
|
||||
Entity::TileMode::kMirror, {});
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
int j = 11 - i;
|
||||
canvas.DrawRRect(Rect::MakeLTRB(500 - i * 20, 550 - j * 20, //
|
||||
500 + i * 20, 550 + j * 20),
|
||||
Size(i * 10, j * 10), paint);
|
||||
}
|
||||
|
||||
paint.color_source = ColorSource::MakeImage(
|
||||
texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {},
|
||||
Matrix::MakeTranslation({500, 20}));
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
int j = 11 - i;
|
||||
canvas.DrawRRect(Rect::MakeLTRB(700 - i * 20, 220 - j * 20, //
|
||||
700 + i * 20, 220 + j * 20),
|
||||
Size(i * 10, j * 10), paint);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
|
||||
}
|
||||
|
||||
TEST_P(AiksTest, GradientStrokesRenderCorrectly) {
|
||||
// Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
|
||||
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
|
||||
@ -4335,9 +4403,9 @@ TEST_P(AiksTest, GaussianBlurAtPeripheryVertical) {
|
||||
|
||||
canvas.Scale(GetContentScale());
|
||||
canvas.DrawRRect(Rect::MakeLTRB(0, 0, GetWindowSize().width, 100),
|
||||
Point(10, 10), Paint{.color = Color::LimeGreen()});
|
||||
Size(10, 10), Paint{.color = Color::LimeGreen()});
|
||||
canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210),
|
||||
Point(10, 10), Paint{.color = Color::Magenta()});
|
||||
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),
|
||||
@ -4358,7 +4426,7 @@ TEST_P(AiksTest, GaussianBlurAtPeripheryHorizontal) {
|
||||
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),
|
||||
Point(10, 10), Paint{.color = Color::Magenta()});
|
||||
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),
|
||||
|
||||
@ -365,27 +365,31 @@ void Canvas::DrawOval(const Rect& rect, const Paint& paint) {
|
||||
GetCurrentPass().AddEntity(std::move(entity));
|
||||
}
|
||||
|
||||
void Canvas::DrawRRect(Rect rect, Point corner_radii, const Paint& paint) {
|
||||
if (corner_radii.x == corner_radii.y &&
|
||||
AttemptDrawBlurredRRect(rect, corner_radii.x, paint)) {
|
||||
void Canvas::DrawRRect(const Rect& rect,
|
||||
const Size& corner_radii,
|
||||
const Paint& paint) {
|
||||
if (corner_radii.IsSquare() &&
|
||||
AttemptDrawBlurredRRect(rect, corner_radii.width, paint)) {
|
||||
return;
|
||||
}
|
||||
auto path = PathBuilder{}
|
||||
.SetConvexity(Convexity::kConvex)
|
||||
.AddRoundedRect(rect, corner_radii)
|
||||
.SetBounds(rect)
|
||||
.TakePath();
|
||||
|
||||
if (paint.style == Paint::Style::kFill) {
|
||||
Entity entity;
|
||||
entity.SetTransform(GetCurrentTransform());
|
||||
entity.SetClipDepth(GetClipDepth());
|
||||
entity.SetBlendMode(paint.blend_mode);
|
||||
entity.SetContents(CreateContentsForGeometryWithFilters(
|
||||
paint, Geometry::MakeFillPath(std::move(path))));
|
||||
paint, Geometry::MakeRoundRect(rect, corner_radii)));
|
||||
|
||||
GetCurrentPass().AddEntity(std::move(entity));
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = PathBuilder{}
|
||||
.SetConvexity(Convexity::kConvex)
|
||||
.AddRoundedRect(rect, corner_radii)
|
||||
.SetBounds(rect)
|
||||
.TakePath();
|
||||
DrawPath(std::move(path), paint);
|
||||
}
|
||||
|
||||
@ -445,7 +449,7 @@ void Canvas::ClipRect(const Rect& rect, Entity::ClipOperation clip_op) {
|
||||
}
|
||||
|
||||
void Canvas::ClipRRect(const Rect& rect,
|
||||
Point corner_radii,
|
||||
const Size& corner_radii,
|
||||
Entity::ClipOperation clip_op) {
|
||||
auto path = PathBuilder{}
|
||||
.SetConvexity(Convexity::kConvex)
|
||||
@ -455,8 +459,8 @@ void Canvas::ClipRRect(const Rect& rect,
|
||||
|
||||
auto size = rect.GetSize();
|
||||
// Does the rounded rect have a flat part on the top/bottom or left/right?
|
||||
bool flat_on_TB = corner_radii.x * 2 < size.width;
|
||||
bool flat_on_LR = corner_radii.y * 2 < size.height;
|
||||
bool flat_on_TB = corner_radii.width * 2 < size.width;
|
||||
bool flat_on_LR = corner_radii.height * 2 < size.height;
|
||||
std::optional<Rect> inner_rect = (flat_on_LR && flat_on_TB)
|
||||
? rect.Expand(-corner_radii)
|
||||
: std::make_optional<Rect>();
|
||||
@ -475,7 +479,7 @@ void Canvas::ClipRRect(const Rect& rect,
|
||||
IntersectCulling(rect);
|
||||
break;
|
||||
case Entity::ClipOperation::kDifference:
|
||||
if (corner_radii.x <= 0.0 || corner_radii.y <= 0) {
|
||||
if (corner_radii.IsEmpty()) {
|
||||
SubtractCulling(rect);
|
||||
} else {
|
||||
// We subtract the inner "tall" and "wide" rectangle pieces
|
||||
@ -484,10 +488,10 @@ void Canvas::ClipRRect(const Rect& rect,
|
||||
// Since this is a subtract operation, we can subtract each
|
||||
// rectangle piece individually without fear of interference.
|
||||
if (flat_on_TB) {
|
||||
SubtractCulling(rect.Expand({-corner_radii.x, 0.0}));
|
||||
SubtractCulling(rect.Expand(Size{-corner_radii.width, 0.0}));
|
||||
}
|
||||
if (flat_on_LR) {
|
||||
SubtractCulling(rect.Expand({0.0, -corner_radii.y}));
|
||||
SubtractCulling(rect.Expand(Size{0.0, -corner_radii.height}));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -106,7 +106,9 @@ class Canvas {
|
||||
|
||||
void DrawOval(const Rect& rect, const Paint& paint);
|
||||
|
||||
void DrawRRect(Rect rect, Point corner_radii, const Paint& paint);
|
||||
void DrawRRect(const Rect& rect,
|
||||
const Size& corner_radii,
|
||||
const Paint& paint);
|
||||
|
||||
void DrawCircle(const Point& center, Scalar radius, const Paint& paint);
|
||||
|
||||
@ -136,7 +138,7 @@ class Canvas {
|
||||
|
||||
void ClipRRect(
|
||||
const Rect& rect,
|
||||
Point corner_radii,
|
||||
const Size& corner_radii,
|
||||
Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect);
|
||||
|
||||
void DrawPicture(const Picture& picture);
|
||||
|
||||
@ -195,7 +195,9 @@ class CanvasRecorder {
|
||||
paint);
|
||||
}
|
||||
|
||||
void DrawRRect(Rect rect, Point corner_radii, const Paint& paint) {
|
||||
void DrawRRect(const Rect& rect,
|
||||
const Size& corner_radii,
|
||||
const Paint& paint) {
|
||||
return ExecuteAndSerialize(FLT_CANVAS_RECORDER_OP_ARG(DrawRRect), rect,
|
||||
corner_radii, paint);
|
||||
}
|
||||
@ -248,7 +250,7 @@ class CanvasRecorder {
|
||||
|
||||
void ClipRRect(
|
||||
const Rect& rect,
|
||||
Point corner_radii,
|
||||
const Size& corner_radii,
|
||||
Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect) {
|
||||
return ExecuteAndSerialize(FLT_CANVAS_RECORDER_OP_ARG(ClipRRect), rect,
|
||||
corner_radii, clip_op);
|
||||
|
||||
@ -731,7 +731,7 @@ void DlDispatcher::clipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) {
|
||||
ToClipOperation(clip_op));
|
||||
} else if (rrect.isSimple()) {
|
||||
canvas_.ClipRRect(skia_conversions::ToRect(rrect.rect()),
|
||||
skia_conversions::ToPoint(rrect.getSimpleRadii()),
|
||||
skia_conversions::ToSize(rrect.getSimpleRadii()),
|
||||
ToClipOperation(clip_op));
|
||||
} else {
|
||||
canvas_.ClipPath(skia_conversions::ToPath(rrect), ToClipOperation(clip_op));
|
||||
@ -782,8 +782,7 @@ void DlDispatcher::drawCircle(const SkPoint& center, SkScalar radius) {
|
||||
void DlDispatcher::drawRRect(const SkRRect& rrect) {
|
||||
if (rrect.isSimple()) {
|
||||
canvas_.DrawRRect(skia_conversions::ToRect(rrect.rect()),
|
||||
skia_conversions::ToPoint(rrect.getSimpleRadii()),
|
||||
paint_);
|
||||
skia_conversions::ToSize(rrect.getSimpleRadii()), paint_);
|
||||
} else {
|
||||
canvas_.DrawPath(skia_conversions::ToPath(rrect), paint_);
|
||||
}
|
||||
@ -817,7 +816,7 @@ void DlDispatcher::SimplifyOrDrawPath(CanvasType& canvas,
|
||||
SkRRect rrect;
|
||||
if (path.isRRect(&rrect) && rrect.isSimple()) {
|
||||
canvas.DrawRRect(skia_conversions::ToRect(rrect.rect()),
|
||||
skia_conversions::ToPoint(rrect.getSimpleRadii()), paint);
|
||||
skia_conversions::ToSize(rrect.getSimpleRadii()), paint);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -141,6 +141,10 @@ Point ToPoint(const SkPoint& point) {
|
||||
return Point::MakeXY(point.fX, point.fY);
|
||||
}
|
||||
|
||||
Size ToSize(const SkPoint& point) {
|
||||
return Size(point.fX, point.fY);
|
||||
}
|
||||
|
||||
Color ToColor(const flutter::DlColor& color) {
|
||||
return {
|
||||
static_cast<Scalar>(color.getRedF()), //
|
||||
|
||||
@ -33,6 +33,8 @@ std::vector<Point> ToPoints(const SkPoint points[], int count);
|
||||
|
||||
Point ToPoint(const SkPoint& point);
|
||||
|
||||
Size ToSize(const SkPoint& point);
|
||||
|
||||
Color ToColor(const flutter::DlColor& color);
|
||||
|
||||
std::vector<Matrix> ToRSXForms(const SkRSXform xform[], int count);
|
||||
|
||||
@ -11,6 +11,24 @@
|
||||
namespace impeller {
|
||||
namespace testing {
|
||||
|
||||
TEST(SkiaConversionsTest, SkPointToPoint) {
|
||||
for (int x = -100; x < 100; x += 4) {
|
||||
for (int y = -100; y < 100; y += 4) {
|
||||
EXPECT_EQ(skia_conversions::ToPoint(SkPoint::Make(x * 0.25f, y * 0.25f)),
|
||||
Point(x * 0.25f, y * 0.25f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SkiaConversionsTest, SkPointToSize) {
|
||||
for (int x = -100; x < 100; x += 4) {
|
||||
for (int y = -100; y < 100; y += 4) {
|
||||
EXPECT_EQ(skia_conversions::ToSize(SkPoint::Make(x * 0.25f, y * 0.25f)),
|
||||
Size(x * 0.25f, y * 0.25f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SkiaConversionsTest, ToColor) {
|
||||
// Create a color with alpha, red, green, and blue values that are all
|
||||
// trivially divisible by 255 so that we can test the conversion results in
|
||||
|
||||
@ -207,6 +207,8 @@ impeller_component("entity") {
|
||||
"geometry/point_field_geometry.h",
|
||||
"geometry/rect_geometry.cc",
|
||||
"geometry/rect_geometry.h",
|
||||
"geometry/round_rect_geometry.cc",
|
||||
"geometry/round_rect_geometry.h",
|
||||
"geometry/stroke_path_geometry.cc",
|
||||
"geometry/stroke_path_geometry.h",
|
||||
"geometry/vertices_geometry.cc",
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
#include "impeller/entity/geometry/line_geometry.h"
|
||||
#include "impeller/entity/geometry/point_field_geometry.h"
|
||||
#include "impeller/entity/geometry/rect_geometry.h"
|
||||
#include "impeller/entity/geometry/round_rect_geometry.h"
|
||||
#include "impeller/entity/geometry/stroke_path_geometry.h"
|
||||
#include "impeller/geometry/rect.h"
|
||||
|
||||
@ -204,6 +205,11 @@ std::shared_ptr<Geometry> Geometry::MakeStrokedCircle(const Point& center,
|
||||
return std::make_shared<CircleGeometry>(center, radius, stroke_width);
|
||||
}
|
||||
|
||||
std::shared_ptr<Geometry> Geometry::MakeRoundRect(const Rect& rect,
|
||||
const Size& radii) {
|
||||
return std::make_shared<RoundRectGeometry>(rect, radii);
|
||||
}
|
||||
|
||||
bool Geometry::CoversArea(const Matrix& transform, const Rect& rect) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -85,6 +85,9 @@ class Geometry {
|
||||
Scalar radius,
|
||||
Scalar stroke_width);
|
||||
|
||||
static std::shared_ptr<Geometry> MakeRoundRect(const Rect& rect,
|
||||
const Size& radii);
|
||||
|
||||
static std::shared_ptr<Geometry> MakePointField(std::vector<Point> points,
|
||||
Scalar radius,
|
||||
bool round);
|
||||
|
||||
@ -62,5 +62,14 @@ TEST(EntityGeometryTest, LineGeometryCoverage) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EntityGeometryTest, RoundRectGeometryCoversArea) {
|
||||
auto geometry =
|
||||
Geometry::MakeRoundRect(Rect::MakeLTRB(0, 0, 100, 100), Size(20, 20));
|
||||
EXPECT_FALSE(geometry->CoversArea({}, Rect::MakeLTRB(15, 15, 85, 85)));
|
||||
EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(20, 20, 80, 80)));
|
||||
EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(30, 1, 70, 99)));
|
||||
EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(1, 30, 99, 70)));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace impeller
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
// 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 <algorithm>
|
||||
|
||||
#include "flutter/impeller/entity/geometry/round_rect_geometry.h"
|
||||
|
||||
#include "flutter/impeller/entity/geometry/line_geometry.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
RoundRectGeometry::RoundRectGeometry(const Rect& bounds, const Size& radii)
|
||||
: bounds_(bounds), radii_(radii) {}
|
||||
|
||||
GeometryResult RoundRectGeometry::GetPositionBuffer(
|
||||
const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const {
|
||||
return ComputePositionGeometry(renderer.GetTessellator()->FilledRoundRect(
|
||||
entity.GetTransform(), bounds_, radii_),
|
||||
entity, pass);
|
||||
}
|
||||
|
||||
// |Geometry|
|
||||
GeometryResult RoundRectGeometry::GetPositionUVBuffer(
|
||||
Rect texture_coverage,
|
||||
Matrix effect_transform,
|
||||
const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const {
|
||||
return ComputePositionUVGeometry(
|
||||
renderer.GetTessellator()->FilledRoundRect(entity.GetTransform(), bounds_,
|
||||
radii_),
|
||||
texture_coverage.GetNormalizingTransform() * effect_transform, entity,
|
||||
pass);
|
||||
}
|
||||
|
||||
GeometryVertexType RoundRectGeometry::GetVertexType() const {
|
||||
return GeometryVertexType::kPosition;
|
||||
}
|
||||
|
||||
std::optional<Rect> RoundRectGeometry::GetCoverage(
|
||||
const Matrix& transform) const {
|
||||
return bounds_.TransformBounds(transform);
|
||||
}
|
||||
|
||||
bool RoundRectGeometry::CoversArea(const Matrix& transform,
|
||||
const Rect& rect) const {
|
||||
if (!transform.IsTranslationScaleOnly()) {
|
||||
return false;
|
||||
}
|
||||
bool flat_on_tb = bounds_.GetSize().width > radii_.width * 2;
|
||||
bool flat_on_lr = bounds_.GetSize().height > radii_.height * 2;
|
||||
if (!flat_on_tb && !flat_on_lr) {
|
||||
return false;
|
||||
}
|
||||
// We either transform the bounds and delta-transform the radii,
|
||||
// or we compute the vertical and horizontal bounds and then
|
||||
// transform each. Either way there are 2 transform operations.
|
||||
// We could also get a weaker answer by computing just the
|
||||
// "inner rect" and only doing a coverage analysis on that,
|
||||
// but this process will produce more culling results.
|
||||
if (flat_on_tb) {
|
||||
Rect vertical_bounds = bounds_.Expand(Size{-radii_.width, 0});
|
||||
Rect coverage = vertical_bounds.TransformBounds(transform);
|
||||
if (coverage.Contains(rect)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (flat_on_lr) {
|
||||
Rect horizontal_bounds = bounds_.Expand(Size{0, -radii_.height});
|
||||
Rect coverage = horizontal_bounds.TransformBounds(transform);
|
||||
if (coverage.Contains(rect)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RoundRectGeometry::IsAxisAlignedRect() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
@ -0,0 +1,54 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "impeller/entity/geometry/geometry.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
// Geometry class that can generate vertices (with or without texture
|
||||
// coordinates) for filled ellipses. Generating vertices for a stroked
|
||||
// ellipse would require a lot more work since the line width must be
|
||||
// applied perpendicular to the distorted ellipse shape.
|
||||
class RoundRectGeometry final : public Geometry {
|
||||
public:
|
||||
explicit RoundRectGeometry(const Rect& bounds, const Size& radii);
|
||||
|
||||
~RoundRectGeometry() = default;
|
||||
|
||||
// |Geometry|
|
||||
bool CoversArea(const Matrix& transform, const Rect& rect) const override;
|
||||
|
||||
// |Geometry|
|
||||
bool IsAxisAlignedRect() const override;
|
||||
|
||||
private:
|
||||
// |Geometry|
|
||||
GeometryResult GetPositionBuffer(const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const override;
|
||||
|
||||
// |Geometry|
|
||||
GeometryVertexType GetVertexType() const override;
|
||||
|
||||
// |Geometry|
|
||||
std::optional<Rect> GetCoverage(const Matrix& transform) const override;
|
||||
|
||||
// |Geometry|
|
||||
GeometryResult GetPositionUVBuffer(Rect texture_coverage,
|
||||
Matrix effect_transform,
|
||||
const ContentContext& renderer,
|
||||
const Entity& entity,
|
||||
RenderPass& pass) const override;
|
||||
|
||||
const Rect bounds_;
|
||||
const Size radii_;
|
||||
|
||||
RoundRectGeometry(const RoundRectGeometry&) = delete;
|
||||
|
||||
RoundRectGeometry& operator=(const RoundRectGeometry&) = delete;
|
||||
};
|
||||
|
||||
} // namespace impeller
|
||||
@ -139,8 +139,8 @@ PathBuilder& PathBuilder::AddRoundedRect(Rect rect, Scalar radius) {
|
||||
: AddRoundedRect(rect, RoundingRadii(radius));
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::AddRoundedRect(Rect rect, Point radii) {
|
||||
return radii.x <= 0 || radii.y <= 0
|
||||
PathBuilder& PathBuilder::AddRoundedRect(Rect rect, Size radii) {
|
||||
return radii.width <= 0 || radii.height <= 0
|
||||
? AddRect(rect)
|
||||
: AddRoundedRect(rect, RoundingRadii(radii));
|
||||
}
|
||||
|
||||
@ -132,6 +132,12 @@ class PathBuilder {
|
||||
top_right(radii),
|
||||
bottom_right(radii) {}
|
||||
|
||||
explicit RoundingRadii(Size radii)
|
||||
: top_left(radii),
|
||||
bottom_left(radii),
|
||||
top_right(radii),
|
||||
bottom_right(radii) {}
|
||||
|
||||
bool AreAllZero() const {
|
||||
return top_left.IsZero() && //
|
||||
bottom_left.IsZero() && //
|
||||
@ -142,7 +148,7 @@ class PathBuilder {
|
||||
|
||||
PathBuilder& AddRoundedRect(Rect rect, RoundingRadii radii);
|
||||
|
||||
PathBuilder& AddRoundedRect(Rect rect, Point radii);
|
||||
PathBuilder& AddRoundedRect(Rect rect, Size radii);
|
||||
|
||||
PathBuilder& AddRoundedRect(Rect rect, Scalar radius);
|
||||
|
||||
|
||||
@ -79,6 +79,17 @@ TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
|
||||
ASSERT_TRUE(contour.is_closed);
|
||||
}
|
||||
|
||||
{
|
||||
Path path =
|
||||
PathBuilder{}
|
||||
.AddRoundedRect(Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20))
|
||||
.TakePath();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
ASSERT_POINT_NEAR(contour.destination, Point(110, 100));
|
||||
ASSERT_TRUE(contour.is_closed);
|
||||
}
|
||||
|
||||
// Open shapes.
|
||||
{
|
||||
Point p(100, 100);
|
||||
|
||||
@ -356,6 +356,15 @@ struct TRect {
|
||||
size.height + amount.y * 2);
|
||||
}
|
||||
|
||||
/// @brief Returns a rectangle with expanded edges in all directions.
|
||||
/// Negative expansion results in shrinking.
|
||||
constexpr TRect<T> Expand(TSize<T> amount) const {
|
||||
return TRect(origin.x - amount.width, //
|
||||
origin.y - amount.height, //
|
||||
size.width + amount.width * 2, //
|
||||
size.height + amount.height * 2);
|
||||
}
|
||||
|
||||
/// @brief Returns a new rectangle that represents the projection of the
|
||||
/// source rectangle onto this rectangle. In other words, the source
|
||||
/// rectangle is redefined in terms of the corrdinate space of this
|
||||
|
||||
@ -267,5 +267,25 @@ TEST(RectTest, GetCenter) {
|
||||
EXPECT_EQ(IRect::MakeXYWH(10, 30, 20, 19).GetCenter(), Point(20, 39.5));
|
||||
}
|
||||
|
||||
TEST(RectTest, Expand) {
|
||||
auto rect = Rect::MakeLTRB(100, 100, 200, 200);
|
||||
|
||||
// Expand(T amount)
|
||||
EXPECT_EQ(rect.Expand(10), Rect::MakeLTRB(90, 90, 210, 210));
|
||||
EXPECT_EQ(rect.Expand(-10), Rect::MakeLTRB(110, 110, 190, 190));
|
||||
|
||||
// Expand(Point amount)
|
||||
EXPECT_EQ(rect.Expand(Point{10, 10}), Rect::MakeLTRB(90, 90, 210, 210));
|
||||
EXPECT_EQ(rect.Expand(Point{10, -10}), Rect::MakeLTRB(90, 110, 210, 190));
|
||||
EXPECT_EQ(rect.Expand(Point{-10, 10}), Rect::MakeLTRB(110, 90, 190, 210));
|
||||
EXPECT_EQ(rect.Expand(Point{-10, -10}), Rect::MakeLTRB(110, 110, 190, 190));
|
||||
|
||||
// Expand(Size amount)
|
||||
EXPECT_EQ(rect.Expand(Size{10, 10}), Rect::MakeLTRB(90, 90, 210, 210));
|
||||
EXPECT_EQ(rect.Expand(Size{10, -10}), Rect::MakeLTRB(90, 110, 210, 190));
|
||||
EXPECT_EQ(rect.Expand(Size{-10, 10}), Rect::MakeLTRB(110, 90, 190, 210));
|
||||
EXPECT_EQ(rect.Expand(Size{-10, -10}), Rect::MakeLTRB(110, 110, 190, 190));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace impeller
|
||||
|
||||
@ -68,6 +68,8 @@ struct TSize {
|
||||
return {width - s.width, height - s.height};
|
||||
}
|
||||
|
||||
constexpr TSize operator-() const { return {-width, -height}; }
|
||||
|
||||
constexpr TSize Min(const TSize& o) const {
|
||||
return {
|
||||
std::min(width, o.width),
|
||||
|
||||
@ -74,5 +74,12 @@ TEST(SizeTest, MaxDimension) {
|
||||
EXPECT_EQ(ISize(21, 20).MaxDimension(), 21);
|
||||
}
|
||||
|
||||
TEST(SizeTest, NegationOperator) {
|
||||
EXPECT_EQ(-Size(10, 20), Size(-10, -20));
|
||||
EXPECT_EQ(-Size(-10, 20), Size(10, -20));
|
||||
EXPECT_EQ(-Size(10, -20), Size(-10, 20));
|
||||
EXPECT_EQ(-Size(-10, -20), Size(10, 20));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace impeller
|
||||
|
||||
@ -468,6 +468,34 @@ EllipticalVertexGenerator Tessellator::FilledEllipse(
|
||||
});
|
||||
}
|
||||
|
||||
EllipticalVertexGenerator Tessellator::FilledRoundRect(
|
||||
const Matrix& view_transform,
|
||||
const Rect& bounds,
|
||||
const Size& radii) {
|
||||
if (radii.width * 2 < bounds.GetSize().width &&
|
||||
radii.height * 2 < bounds.GetSize().height) {
|
||||
auto max_radius = radii.MaxDimension();
|
||||
auto divisions = ComputeQuadrantDivisions(
|
||||
view_transform.GetMaxBasisLength() * max_radius);
|
||||
auto upper_left = bounds.GetLeftTop() + radii;
|
||||
auto lower_right = bounds.GetRightBottom() - radii;
|
||||
return EllipticalVertexGenerator(Tessellator::GenerateFilledRoundRect,
|
||||
GetTrigsForDivisions(divisions),
|
||||
PrimitiveType::kTriangleStrip, 4,
|
||||
{
|
||||
.reference_centers =
|
||||
{
|
||||
upper_left,
|
||||
lower_right,
|
||||
},
|
||||
.radii = radii,
|
||||
.half_width = -1.0f,
|
||||
});
|
||||
} else {
|
||||
return FilledEllipse(view_transform, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
void Tessellator::GenerateFilledCircle(
|
||||
const Trigs& trigs,
|
||||
const EllipticalVertexGenerator::Data& data,
|
||||
@ -615,4 +643,35 @@ void Tessellator::GenerateFilledEllipse(
|
||||
}
|
||||
}
|
||||
|
||||
void Tessellator::GenerateFilledRoundRect(
|
||||
const Trigs& trigs,
|
||||
const EllipticalVertexGenerator::Data& data,
|
||||
const TessellatedVertexProc& proc) {
|
||||
Scalar left = data.reference_centers[0].x;
|
||||
Scalar top = data.reference_centers[0].y;
|
||||
Scalar right = data.reference_centers[1].x;
|
||||
Scalar bottom = data.reference_centers[1].y;
|
||||
auto radii = data.radii;
|
||||
|
||||
FML_DCHECK(data.half_width < 0);
|
||||
|
||||
// Quadrant 1 connecting with Quadrant 4:
|
||||
for (auto& trig : trigs) {
|
||||
auto offset = trig * radii;
|
||||
proc({left - offset.x, bottom + offset.y});
|
||||
proc({left - offset.x, top - offset.y});
|
||||
}
|
||||
|
||||
// The second half of the round rect should be iterated in reverse, but
|
||||
// we can instead iterate forward and swap the x/y values of the
|
||||
// offset as the angles should be symmetric and thus should generate
|
||||
// symmetrically reversed trig vectors.
|
||||
// Quadrant 2 connecting with Quadrant 2:
|
||||
for (auto& trig : trigs) {
|
||||
auto offset = Point(trig.sin * radii.width, trig.cos * radii.height);
|
||||
proc({right + offset.x, bottom + offset.y});
|
||||
proc({right + offset.x, top - offset.y});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace impeller
|
||||
|
||||
@ -280,6 +280,19 @@ class Tessellator {
|
||||
EllipticalVertexGenerator FilledEllipse(const Matrix& view_transform,
|
||||
const Rect& bounds);
|
||||
|
||||
/// @brief Create a |VertexGenerator| that can produce vertices for
|
||||
/// a filled round rect within the given bounds and corner radii
|
||||
/// with enough polygon sub-divisions to provide reasonable
|
||||
/// fidelity when viewed under the given view transform.
|
||||
///
|
||||
/// Note that the view transform is only used to choose the
|
||||
/// number of sample points to use per quarter circle and the
|
||||
/// returned points are not transformed by it, instead they are
|
||||
/// relative to the coordinate space of the bounds.
|
||||
EllipticalVertexGenerator FilledRoundRect(const Matrix& view_transform,
|
||||
const Rect& bounds,
|
||||
const Size& radii);
|
||||
|
||||
private:
|
||||
/// Used for polyline generation.
|
||||
std::unique_ptr<std::vector<Point>> point_buffer_;
|
||||
@ -309,6 +322,11 @@ class Tessellator {
|
||||
const EllipticalVertexGenerator::Data& data,
|
||||
const TessellatedVertexProc& proc);
|
||||
|
||||
static void GenerateFilledRoundRect(
|
||||
const Trigs& trigs,
|
||||
const EllipticalVertexGenerator::Data& data,
|
||||
const TessellatedVertexProc& proc);
|
||||
|
||||
Tessellator(const Tessellator&) = delete;
|
||||
|
||||
Tessellator& operator=(const Tessellator&) = delete;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user