Revert "[Impeller] add support for rational bezier conics to Path (#63282)" (#163624)

This reverts commit db0bdfd117077a69f79ce0d4c03ab387a78ef7dd to fix a
tree breakage.
This commit is contained in:
Matan Lurey 2025-02-19 10:51:42 -08:00 committed by GitHub
parent 39b4951f8f
commit 485c9105fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 274 additions and 932 deletions

View File

@ -139,10 +139,6 @@ inline const SkPoint* ToSkPoints(const DlPoint* points) {
return points == nullptr ? nullptr : reinterpret_cast<const SkPoint*>(points);
}
inline SkPoint* ToSkPoints(DlPoint* points) {
return points == nullptr ? nullptr : reinterpret_cast<SkPoint*>(points);
}
inline const SkRect& ToSkRect(const DlRect& rect) {
return *reinterpret_cast<const SkRect*>(&rect);
}

View File

@ -265,46 +265,39 @@ SkPath DlPath::ConvertToSkiaPath(const Path& path, const DlPoint& shift) {
}
};
for (auto it = path.begin(), end = path.end(); it != end; ++it) {
switch (it.type()) {
size_t count = path.GetComponentCount();
for (size_t i = 0; i < count; i++) {
switch (path.GetComponentTypeAtIndex(i)) {
case ComponentType::kContour: {
const impeller::ContourComponent* contour = it.contour();
FML_DCHECK(contour != nullptr);
impeller::ContourComponent contour;
path.GetContourComponentAtIndex(i, contour);
if (subpath_needs_close) {
sk_path.close();
}
pending_moveto = contour->destination;
subpath_needs_close = contour->IsClosed();
pending_moveto = contour.destination;
subpath_needs_close = contour.IsClosed();
break;
}
case ComponentType::kLinear: {
const impeller::LinearPathComponent* linear = it.linear();
FML_DCHECK(linear != nullptr);
impeller::LinearPathComponent linear;
path.GetLinearComponentAtIndex(i, linear);
resolve_moveto();
sk_path.lineTo(ToSkPoint(linear->p2));
sk_path.lineTo(ToSkPoint(linear.p2));
break;
}
case ComponentType::kQuadratic: {
const impeller::QuadraticPathComponent* quadratic = it.quadratic();
FML_DCHECK(quadratic != nullptr);
impeller::QuadraticPathComponent quadratic;
path.GetQuadraticComponentAtIndex(i, quadratic);
resolve_moveto();
sk_path.quadTo(ToSkPoint(quadratic->cp), ToSkPoint(quadratic->p2));
break;
}
case ComponentType::kConic: {
const impeller::ConicPathComponent* conic = it.conic();
FML_DCHECK(conic != nullptr);
resolve_moveto();
sk_path.conicTo(ToSkPoint(conic->cp), ToSkPoint(conic->p2),
conic->weight.x);
sk_path.quadTo(ToSkPoint(quadratic.cp), ToSkPoint(quadratic.p2));
break;
}
case ComponentType::kCubic: {
const impeller::CubicPathComponent* cubic = it.cubic();
FML_DCHECK(cubic != nullptr);
impeller::CubicPathComponent cubic;
path.GetCubicComponentAtIndex(i, cubic);
resolve_moveto();
sk_path.cubicTo(ToSkPoint(cubic->cp1), ToSkPoint(cubic->cp2),
ToSkPoint(cubic->p2));
sk_path.cubicTo(ToSkPoint(cubic.cp1), ToSkPoint(cubic.cp2),
ToSkPoint(cubic.p2));
break;
}
}
@ -346,26 +339,27 @@ Path DlPath::ConvertToImpellerPath(const SkPath& path, const DlPoint& shift) {
builder.QuadraticCurveTo(ToDlPoint(data.points[1]),
ToDlPoint(data.points[2]));
break;
case SkPath::kConic_Verb:
// We might eventually have conic conversion math that deals with
// degenerate conics gracefully (or just handle them directly),
// but until then, we will detect and ignore them.
if (data.points[0] != data.points[1]) {
if (data.points[1] != data.points[2]) {
std::array<DlPoint, 5> points;
impeller::ConicPathComponent conic(
ToDlPoint(data.points[0]), ToDlPoint(data.points[1]),
ToDlPoint(data.points[2]), iterator.conicWeight());
conic.SubdivideToQuadraticPoints(points);
builder.QuadraticCurveTo(points[1], points[2]);
builder.QuadraticCurveTo(points[3], points[4]);
} else {
builder.LineTo(ToDlPoint(data.points[1]));
}
} else if (data.points[1] != data.points[2]) {
builder.LineTo(ToDlPoint(data.points[2]));
case SkPath::kConic_Verb: {
constexpr auto kPow2 = 1; // Only works for sweeps up to 90 degrees.
constexpr auto kQuadCount = 1 + (2 * (1 << kPow2));
SkPoint points[kQuadCount];
const auto curve_count =
SkPath::ConvertConicToQuads(data.points[0], //
data.points[1], //
data.points[2], //
iterator.conicWeight(), //
points, //
kPow2 //
);
for (int curve_index = 0, point_index = 0; //
curve_index < curve_count; //
curve_index++, point_index += 2 //
) {
builder.QuadraticCurveTo(ToDlPoint(points[point_index + 1]),
ToDlPoint(points[point_index + 2]));
}
break;
} break;
case SkPath::kCubic_Verb:
builder.CubicCurveTo(ToDlPoint(data.points[1]),
ToDlPoint(data.points[2]),

View File

@ -11,7 +11,6 @@
#include "flutter/display_list/effects/dl_color_sources.h"
#include "flutter/display_list/effects/dl_image_filters.h"
#include "flutter/display_list/skia/dl_sk_conversions.h"
#include "flutter/impeller/geometry/path_component.h"
#include "gtest/gtest.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkSamplingOptions.h"
@ -373,92 +372,5 @@ TEST(DisplayListSkConversions, ToSkRSTransform) {
}
}
// This tests the new conic subdivision code in the Impeller conic path
// component object vs the code we used to rely on inside Skia
TEST(DisplayListSkConversions, ConicToQuads) {
SkScalar weights[4] = {
0.02f,
0.5f,
SK_ScalarSqrt2 * 0.5f,
1.0f,
};
for (SkScalar weight : weights) {
SkPoint sk_points[5];
int ncurves = SkPath::ConvertConicToQuads(
SkPoint::Make(10, 10), SkPoint::Make(20, 10), SkPoint::Make(20, 20),
weight, sk_points, 1);
ASSERT_EQ(ncurves, 2) << "weight: " << weight;
std::array<DlPoint, 5> i_points;
impeller::ConicPathComponent i_conic(DlPoint(10, 10), DlPoint(20, 10),
DlPoint(20, 20), weight);
i_conic.SubdivideToQuadraticPoints(i_points);
for (int i = 0; i < 5; i++) {
EXPECT_FLOAT_EQ(sk_points[i].fX, i_points[i].x)
<< "weight: " << weight << "point[" << i << "].x";
EXPECT_FLOAT_EQ(sk_points[i].fY, i_points[i].y)
<< "weight: " << weight << "point[" << i << "].y";
}
}
}
// This tests the new conic subdivision code in the Impeller conic path
// component object vs the code we used to rely on inside Skia
TEST(DisplayListSkConversions, ConicPathToQuads) {
// If we execute conicTo with a weight of exactly 1.0, SkPath will turn
// it into a quadTo, so we avoid that by using 0.999
SkScalar weights[4] = {
0.02f,
0.5f,
SK_ScalarSqrt2 * 0.5f,
1.0f - kEhCloseEnough,
};
for (SkScalar weight : weights) {
SkPath sk_path;
sk_path.moveTo(10, 10);
sk_path.conicTo(20, 10, 20, 20, weight);
DlPath dl_path(sk_path);
impeller::Path i_path = dl_path.GetPath();
auto it = i_path.begin();
ASSERT_EQ(it.type(), impeller::Path::ComponentType::kContour);
++it;
ASSERT_EQ(it.type(), impeller::Path::ComponentType::kQuadratic);
auto quad1 = it.quadratic();
ASSERT_NE(quad1, nullptr);
++it;
ASSERT_EQ(it.type(), impeller::Path::ComponentType::kQuadratic);
auto quad2 = it.quadratic();
ASSERT_NE(quad2, nullptr);
++it;
SkPoint sk_points[5];
int ncurves = SkPath::ConvertConicToQuads(
SkPoint::Make(10, 10), SkPoint::Make(20, 10), SkPoint::Make(20, 20),
weight, sk_points, 1);
ASSERT_EQ(ncurves, 2);
EXPECT_FLOAT_EQ(sk_points[0].fX, quad1->p1.x) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[0].fY, quad1->p1.y) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[1].fX, quad1->cp.x) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[1].fY, quad1->cp.y) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[2].fX, quad1->p2.x) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[2].fY, quad1->p2.y) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[2].fX, quad2->p1.x) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[2].fY, quad2->p1.y) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[3].fX, quad2->cp.x) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[3].fY, quad2->cp.y) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[4].fX, quad2->p2.x) << "weight: " << weight;
EXPECT_FLOAT_EQ(sk_points[4].fY, quad2->p2.y) << "weight: " << weight;
}
}
} // namespace testing
} // namespace flutter

View File

@ -46,9 +46,8 @@ constexpr float k2OverSqrtPi = 1.12837916709551257390f;
// sqrt(2)
constexpr float kSqrt2 = 1.41421356237309504880f;
// sqrt(2) / 2 == 1/sqrt(2)
// 1/sqrt(2)
constexpr float k1OverSqrt2 = 0.70710678118654752440f;
constexpr float kSqrt2Over2 = 0.70710678118654752440f;
// phi
constexpr float kPhi = 1.61803398874989484820f;

View File

@ -19,64 +19,6 @@ Path::Path(Data data) : data_(std::make_shared<Data>(std::move(data))) {}
Path::~Path() = default;
Path::ComponentType Path::ComponentIterator::type() const {
return path_.data_->components[component_index_];
}
#define CHECK_COMPONENT(type) \
(component_index_ < path_.data_->components.size() && \
path_.data_->components[component_index_] == type && \
storage_offset_ + VerbToOffset(type) <= path_.data_->points.size())
const LinearPathComponent* Path::ComponentIterator::linear() const {
if (!CHECK_COMPONENT(Path::ComponentType::kLinear)) {
return nullptr;
}
const Point* points = &(path_.data_->points[storage_offset_]);
return reinterpret_cast<const LinearPathComponent*>(points);
}
const QuadraticPathComponent* Path::ComponentIterator::quadratic() const {
if (!CHECK_COMPONENT(Path::ComponentType::kQuadratic)) {
return nullptr;
}
const Point* points = &(path_.data_->points[storage_offset_]);
return reinterpret_cast<const QuadraticPathComponent*>(points);
}
const ConicPathComponent* Path::ComponentIterator::conic() const {
if (!CHECK_COMPONENT(Path::ComponentType::kConic)) {
return nullptr;
}
const Point* points = &(path_.data_->points[storage_offset_]);
return reinterpret_cast<const ConicPathComponent*>(points);
}
const CubicPathComponent* Path::ComponentIterator::cubic() const {
if (!CHECK_COMPONENT(Path::ComponentType::kCubic)) {
return nullptr;
}
const Point* points = &(path_.data_->points[storage_offset_]);
return reinterpret_cast<const CubicPathComponent*>(points);
}
const ContourComponent* Path::ComponentIterator::contour() const {
if (!CHECK_COMPONENT(Path::ComponentType::kContour)) {
return nullptr;
}
const Point* points = &(path_.data_->points[storage_offset_]);
return reinterpret_cast<const ContourComponent*>(points);
}
Path::ComponentIterator& Path::ComponentIterator::operator++() {
auto components = path_.data_->components;
if (component_index_ < components.size()) {
storage_offset_ += VerbToOffset(path_.data_->components[component_index_]);
component_index_++;
}
return *this;
}
std::tuple<size_t, size_t> Path::Polyline::GetContourPointBounds(
size_t contour_index) const {
if (contour_index >= contours.size()) {
@ -145,13 +87,6 @@ std::pair<size_t, size_t> Path::CountStorage(Scalar scale) const {
points += quad->CountLinearPathComponents(scale);
break;
}
case ComponentType::kConic: {
const ConicPathComponent* conic =
reinterpret_cast<const ConicPathComponent*>(
&path_points[storage_offset]);
points += conic->CountLinearPathComponents(scale);
break;
}
case ComponentType::kCubic: {
const CubicPathComponent* cubic =
reinterpret_cast<const CubicPathComponent*>(
@ -200,17 +135,6 @@ void Path::WritePolyline(Scalar scale, VertexWriter& writer) const {
quad->ToLinearPathComponents(scale, writer);
break;
}
case ComponentType::kConic: {
const ConicPathComponent* conic =
reinterpret_cast<const ConicPathComponent*>(
&path_points[storage_offset]);
if (first_point) {
writer.Write(conic->p1);
first_point = false;
}
conic->ToLinearPathComponents(scale, writer);
break;
}
case ComponentType::kCubic: {
const CubicPathComponent* cubic =
reinterpret_cast<const CubicPathComponent*>(
@ -246,13 +170,86 @@ void Path::WritePolyline(Scalar scale, VertexWriter& writer) const {
}
}
Path::ComponentIterator Path::begin() const {
return ComponentIterator(*this, 0u, 0u);
Path::ComponentType Path::GetComponentTypeAtIndex(size_t index) const {
auto& components = data_->components;
return components[index];
}
Path::ComponentIterator Path::end() const {
return ComponentIterator(*this, data_->components.size(),
data_->points.size());
bool Path::GetLinearComponentAtIndex(size_t index,
LinearPathComponent& linear) const {
auto& components = data_->components;
if (index >= components.size() ||
components[index] != ComponentType::kLinear) {
return false;
}
size_t storage_offset = 0u;
for (auto i = 0u; i < index; i++) {
storage_offset += VerbToOffset(components[i]);
}
auto& points = data_->points;
linear =
LinearPathComponent(points[storage_offset], points[storage_offset + 1]);
return true;
}
bool Path::GetQuadraticComponentAtIndex(
size_t index,
QuadraticPathComponent& quadratic) const {
auto& components = data_->components;
if (index >= components.size() ||
components[index] != ComponentType::kQuadratic) {
return false;
}
size_t storage_offset = 0u;
for (auto i = 0u; i < index; i++) {
storage_offset += VerbToOffset(components[i]);
}
auto& points = data_->points;
quadratic =
QuadraticPathComponent(points[storage_offset], points[storage_offset + 1],
points[storage_offset + 2]);
return true;
}
bool Path::GetCubicComponentAtIndex(size_t index,
CubicPathComponent& cubic) const {
auto& components = data_->components;
if (index >= components.size() ||
components[index] != ComponentType::kCubic) {
return false;
}
size_t storage_offset = 0u;
for (auto i = 0u; i < index; i++) {
storage_offset += VerbToOffset(components[i]);
}
auto& points = data_->points;
cubic = CubicPathComponent(points[storage_offset], points[storage_offset + 1],
points[storage_offset + 2],
points[storage_offset + 3]);
return true;
}
bool Path::GetContourComponentAtIndex(size_t index,
ContourComponent& move) const {
auto& components = data_->components;
if (index >= components.size() ||
components[index] != ComponentType::kContour) {
return false;
}
size_t storage_offset = 0u;
for (auto i = 0u; i < index; i++) {
storage_offset += VerbToOffset(components[i]);
}
auto& points = data_->points;
move = ContourComponent(points[storage_offset], points[storage_offset + 1]);
return true;
}
Path::Polyline::Polyline(Path::Polyline::PointBufferPtr point_buffer,
@ -318,16 +315,6 @@ void Path::EndContour(
}
break;
}
case ComponentType::kConic: {
auto* conic = reinterpret_cast<const ConicPathComponent*>(
&path_points[storage_offset]);
auto maybe_end = conic->GetEndDirection();
if (maybe_end.has_value()) {
contour.end_direction = maybe_end.value();
return;
}
break;
}
case ComponentType::kCubic: {
auto* cubic = reinterpret_cast<const CubicPathComponent*>(
&path_points[storage_offset]);
@ -390,19 +377,6 @@ Path::Polyline Path::CreatePolyline(
}
break;
}
case ComponentType::kConic: {
poly_components.push_back({
.component_start_index = polyline.points->size() - 1,
.is_curve = true,
});
auto* conic = reinterpret_cast<const ConicPathComponent*>(
&path_points[storage_offset]);
conic->AppendPolylinePoints(scale, *polyline.points);
if (!start_direction.has_value()) {
start_direction = conic->GetStartDirection();
}
break;
}
case ComponentType::kCubic: {
poly_components.push_back({
.component_start_index = polyline.points->size() - 1,

View File

@ -8,7 +8,6 @@
#include <functional>
#include <memory>
#include <optional>
#include <ostream>
#include <tuple>
#include <vector>
@ -56,50 +55,16 @@ class Path {
enum class ComponentType {
kLinear,
kQuadratic,
kConic,
kCubic,
kContour,
};
class ComponentIterator {
public:
ComponentType type() const;
// Return pointer to path component or null if the type is wrong or
// the iterator is past the end of the path.
const LinearPathComponent* linear() const;
const QuadraticPathComponent* quadratic() const;
const ConicPathComponent* conic() const;
const CubicPathComponent* cubic() const;
const ContourComponent* contour() const;
ComponentIterator& operator++();
bool operator==(const ComponentIterator& other) const {
return component_index_ == other.component_index_;
}
bool operator!=(const ComponentIterator& other) const {
return component_index_ != other.component_index_;
}
private:
ComponentIterator(const Path& path, size_t index, size_t offset)
: path_(path), component_index_(index), storage_offset_(offset) {}
const Path& path_;
size_t component_index_ = 0u;
size_t storage_offset_ = 0u;
friend class Path;
};
static constexpr size_t VerbToOffset(Path::ComponentType verb) {
switch (verb) {
case Path::ComponentType::kLinear:
return 2u;
case Path::ComponentType::kQuadratic:
return 3u;
case Path::ComponentType::kConic:
return 4u;
case Path::ComponentType::kCubic:
return 4u;
case Path::ComponentType::kContour:
@ -192,8 +157,18 @@ class Path {
/// @brief Whether the line contains a single contour.
bool IsSingleContour() const;
ComponentIterator begin() const;
ComponentIterator end() const;
ComponentType GetComponentTypeAtIndex(size_t index) const;
bool GetLinearComponentAtIndex(size_t index,
LinearPathComponent& linear) const;
bool GetQuadraticComponentAtIndex(size_t index,
QuadraticPathComponent& quadratic) const;
bool GetCubicComponentAtIndex(size_t index, CubicPathComponent& cubic) const;
bool GetContourComponentAtIndex(size_t index,
ContourComponent& contour) const;
/// Callers must provide the scale factor for how this path will be
/// transformed.

View File

@ -252,17 +252,6 @@ PathBuilder& PathBuilder::QuadraticCurveTo(Point controlPoint,
return *this;
}
PathBuilder& PathBuilder::ConicCurveTo(Point controlPoint,
Point point,
Scalar weight,
bool relative) {
point = relative ? current_ + point : point;
controlPoint = relative ? current_ + controlPoint : controlPoint;
AddConicComponent(current_, controlPoint, point, weight);
current_ = point;
return *this;
}
PathBuilder& PathBuilder::SetConvexity(Convexity value) {
prototype_.convexity = value;
return *this;
@ -280,33 +269,22 @@ PathBuilder& PathBuilder::CubicCurveTo(Point controlPoint1,
return *this;
}
PathBuilder& PathBuilder::AddQuadraticCurve(const Point& p1,
const Point& cp,
const Point& p2) {
PathBuilder& PathBuilder::AddQuadraticCurve(Point p1, Point cp, Point p2) {
MoveTo(p1);
AddQuadraticComponent(p1, cp, p2);
return *this;
}
PathBuilder& PathBuilder::AddConicCurve(const Point& p1,
const Point& cp,
const Point& p2,
Scalar weight) {
MoveTo(p1);
AddConicComponent(p1, cp, p2, weight);
return *this;
}
PathBuilder& PathBuilder::AddCubicCurve(const Point& p1,
const Point& cp1,
const Point& cp2,
const Point& p2) {
PathBuilder& PathBuilder::AddCubicCurve(Point p1,
Point cp1,
Point cp2,
Point p2) {
MoveTo(p1);
AddCubicComponent(p1, cp1, cp2, p2);
return *this;
}
PathBuilder& PathBuilder::AddRect(const Rect& rect) {
PathBuilder& PathBuilder::AddRect(Rect rect) {
auto origin = rect.GetOrigin();
auto size = rect.GetSize();
@ -537,28 +515,6 @@ void PathBuilder::AddQuadraticComponent(const Point& p1,
prototype_.bounds.reset();
}
void PathBuilder::AddConicComponent(const Point& p1,
const Point& cp,
const Point& p2,
Scalar weight) {
if (!std::isfinite(weight)) {
AddLinearComponent(p1, cp);
AddLinearComponent(cp, p2);
} else if (weight <= 0) {
AddLinearComponent(p1, p2);
} else if (weight == 1) {
AddQuadraticComponent(p1, cp, p2);
} else {
auto& points = prototype_.points;
points.push_back(p1);
points.push_back(cp);
points.push_back(p2);
points.emplace_back(weight, weight);
prototype_.components.push_back(Path::ComponentType::kConic);
prototype_.bounds.reset();
}
}
void PathBuilder::AddCubicComponent(const Point& p1,
const Point& cp1,
const Point& cp2,
@ -728,13 +684,6 @@ PathBuilder& PathBuilder::Shift(Point offset) {
quad->p2 += offset;
quad->cp += offset;
} break;
case Path::ComponentType::kConic: {
auto* conic =
reinterpret_cast<ConicPathComponent*>(&points[storage_offset]);
conic->p1 += offset;
conic->p2 += offset;
conic->cp += offset;
} break;
case Path::ComponentType::kCubic: {
auto* cubic =
reinterpret_cast<CubicPathComponent*>(&points[storage_offset]);
@ -818,13 +767,6 @@ std::optional<std::pair<Point, Point>> PathBuilder::GetMinMaxCoveragePoints()
clamp(extrema);
}
break;
case Path::ComponentType::kConic:
for (const auto& extrema : reinterpret_cast<const ConicPathComponent*>(
&points[storage_offset])
->Extrema()) {
clamp(extrema);
}
break;
case Path::ComponentType::kCubic:
for (const auto& extrema : reinterpret_cast<const CubicPathComponent*>(
&points[storage_offset])

View File

@ -60,16 +60,6 @@ class PathBuilder {
Point point,
bool relative = false);
/// @brief Insert a conic curve from the current position to `point` using
/// the control point `controlPoint` and the weight `weight`.
///
/// If `relative` is true the `point` and `controlPoint` are relative to
/// current location.
PathBuilder& ConicCurveTo(Point controlPoint,
Point point,
Scalar weight,
bool relative = false);
/// @brief Insert a cubic curve from the curren position to `point` using the
/// control points `controlPoint1` and `controlPoint2`.
///
@ -80,7 +70,7 @@ class PathBuilder {
Point point,
bool relative = false);
PathBuilder& AddRect(const Rect& rect);
PathBuilder& AddRect(Rect rect);
PathBuilder& AddCircle(const Point& center, Scalar radius);
@ -96,23 +86,11 @@ class PathBuilder {
/// @brief Move to point `p1`, then insert a quadradic curve from `p1` to `p2`
/// with the control point `cp`.
PathBuilder& AddQuadraticCurve(const Point& p1,
const Point& cp,
const Point& p2);
/// @brief Move to point `p1`, then insert a conic curve from `p1` to `p2`
/// with the control point `cp` and weight `weight`.
PathBuilder& AddConicCurve(const Point& p1,
const Point& cp,
const Point& p2,
Scalar weight);
PathBuilder& AddQuadraticCurve(Point p1, Point cp, Point p2);
/// @brief Move to point `p1`, then insert a cubic curve from `p1` to `p2`
/// with control points `cp1` and `cp2`.
PathBuilder& AddCubicCurve(const Point& p1,
const Point& cp1,
const Point& cp2,
const Point& p2);
PathBuilder& AddCubicCurve(Point p1, Point cp1, Point cp2, Point p2);
/// @brief Transform the existing path segments and contours by the given
/// `offset`.
@ -157,11 +135,6 @@ class PathBuilder {
void AddQuadraticComponent(const Point& p1, const Point& cp, const Point& p2);
void AddConicComponent(const Point& p1,
const Point& cp,
const Point& p2,
Scalar weight);
void AddCubicComponent(const Point& p1,
const Point& cp1,
const Point& cp2,

View File

@ -7,9 +7,8 @@
#include <cmath>
#include <utility>
#include "flutter/fml/logging.h"
#include "flutter/impeller/geometry/scalar.h"
#include "flutter/impeller/geometry/wangs_formula.h"
#include "impeller/geometry/scalar.h"
#include "impeller/geometry/wangs_formula.h"
namespace impeller {
@ -184,20 +183,6 @@ static inline Scalar QuadraticSolveDerivative(Scalar t,
2 * t * (p2 - p1);
}
static inline Scalar ConicSolve(Scalar t,
Scalar p0,
Scalar p1,
Scalar p2,
Scalar w) {
auto u = (1 - t);
auto coefficient_p0 = t * t;
auto coefficient_p1 = 2 * t * u * w;
auto coefficient_p2 = u * u;
return ((p0 * coefficient_p0 + p1 * coefficient_p1 + p2 * coefficient_p2) /
(coefficient_p0 + coefficient_p1 + coefficient_p2));
}
static inline Scalar CubicSolve(Scalar t,
Scalar p0,
Scalar p1,
@ -324,115 +309,6 @@ std::optional<Vector2> QuadraticPathComponent::GetEndDirection() const {
return std::nullopt;
}
Point ConicPathComponent::Solve(Scalar time) const {
return {
ConicSolve(time, p1.x, cp.x, p2.x, weight.x), // x
ConicSolve(time, p1.y, cp.y, p2.y, weight.y), // y
};
}
void ConicPathComponent::AppendPolylinePoints(
Scalar scale_factor,
std::vector<Point>& points) const {
for (auto quad : ToQuadraticPathComponents()) {
quad.AppendPolylinePoints(scale_factor, points);
}
}
void ConicPathComponent::ToLinearPathComponents(Scalar scale,
VertexWriter& writer) const {
for (auto quad : ToQuadraticPathComponents()) {
quad.ToLinearPathComponents(scale, writer);
}
}
size_t ConicPathComponent::CountLinearPathComponents(Scalar scale) const {
size_t count = 0;
for (auto quad : ToQuadraticPathComponents()) {
count += quad.CountLinearPathComponents(scale);
}
return count;
}
std::vector<Point> ConicPathComponent::Extrema() const {
std::vector<Point> points;
for (auto quad : ToQuadraticPathComponents()) {
auto quad_extrema = quad.Extrema();
points.insert(points.end(), quad_extrema.begin(), quad_extrema.end());
}
return points;
}
std::optional<Vector2> ConicPathComponent::GetStartDirection() const {
if (p1 != cp) {
return (p1 - cp).Normalize();
}
if (p1 != p2) {
return (p1 - p2).Normalize();
}
return std::nullopt;
}
std::optional<Vector2> ConicPathComponent::GetEndDirection() const {
if (p2 != cp) {
return (p2 - cp).Normalize();
}
if (p2 != p1) {
return (p2 - p1).Normalize();
}
return std::nullopt;
}
void ConicPathComponent::SubdivideToQuadraticPoints(
std::array<Point, 5>& points) const {
FML_DCHECK(weight.IsFinite() && weight.x > 0 && weight.y > 0);
// Observe that scale will always be smaller than 1 because weight > 0.
const Scalar scale = 1.0f / (1.0f + weight.x);
// The subdivided control points below are the sums of the following three
// terms. Because the terms are multiplied by something <1, and the resulting
// control points lie within the control points of the original then the
// terms and the sums below will not overflow. Note that weight * scale
// approaches 1 as weight becomes very large.
Point tp1 = p1 * scale;
Point tcp = cp * (weight.x * scale);
Point tp2 = p2 * scale;
// Calculate the subdivided control points
Point sub_cp1 = tp1 + tcp;
Point sub_cp2 = tcp + tp2;
// The middle point shared by the 2 sub-divisions, the interpolation of
// the original curve at its halfway point.
Point sub_mid = (tp1 + tcp + tcp + tp2) * 0.5f;
FML_DCHECK(sub_cp1.IsFinite() && sub_mid.IsFinite() && sub_cp2.IsFinite());
points[0] = p1;
points[1] = sub_cp1;
points[2] = sub_mid;
points[3] = sub_cp2;
points[4] = p2;
// Update w.
// Currently this method only subdivides a single time directly to 2
// quadratics, but if we eventually want to keep the weights for further
// subdivision, this was the code that did it in Skia:
// sub_w1 = sub_w2 = SkScalarSqrt(SK_ScalarHalf + w * SK_ScalarHalf)
}
std::array<QuadraticPathComponent, 2>
ConicPathComponent::ToQuadraticPathComponents() const {
std::array<Point, 5> points;
SubdivideToQuadraticPoints(points);
return {
QuadraticPathComponent(points[0], points[1], points[2]),
QuadraticPathComponent(points[2], points[3], points[4]),
};
}
Point CubicPathComponent::Solve(Scalar time) const {
return {
CubicSolve(time, p1.x, cp1.x, cp2.x, p2.x), // x

View File

@ -5,7 +5,6 @@
#ifndef FLUTTER_IMPELLER_GEOMETRY_PATH_COMPONENT_H_
#define FLUTTER_IMPELLER_GEOMETRY_PATH_COMPONENT_H_
#include <array>
#include <functional>
#include <optional>
#include <type_traits>
@ -171,107 +170,6 @@ struct QuadraticPathComponent {
std::optional<Vector2> GetEndDirection() const;
};
// A component that represets a Conic section curve.
//
// A conic section is basically nearly a quadratic bezier curve, but it
// has an additional weight that is applied to the middle term (the control
// point term).
//
// Starting with the equation for a quadratic curve which is:
// (A) P1 * (1 - t) * (1 - t)
// + CP * 2 * t * (1 - t)
// + P2 * t * t
// One thing to note is that the quadratic coefficients always sum to 1:
// (B) (1-t)(1-t) + 2t(1-t) + tt
// == 1 - 2t + tt + 2t - 2tt + tt
// == 1
// which means that the resulting point is always a weighted average of
// the 3 points without having to "divide by the sum of the coefficients"
// that is normally done when computing weighted averages.
//
// The conic equation, though, would then be:
// (C) P1 * (1 - t) * (1 - t)
// + CP * 2 * t * (1 - t) * w
// + P2 * t * t
// That would be the final equation, but if we look at the coefficients:
// (D) (1-t)(1-t) + 2wt(1-t) + tt
// == 1 - 2t + tt + 2wt - 2wtt + tt
// == 1 + (2w - 2)t + (2 - 2w)tt
// These only sum to 1 if the weight (w) is 1. In order for this math to
// produce a point that is the weighted average of the 3 points, we have
// to compute both and divide the equation (C) by the equation (D).
//
// Note that there are important potential optimizations we could apply.
// If w is 0,
// then this equation devolves into a straight line from P1 to P2.
// Note that the "progress" from P1 to P2, as a function of t, is
// quadratic if you compute it as the indicated numerator and denominator,
// but the actual points generated are all on the line from P1 to P2
// If w is (sqrt(2) / 2),
// then this math is exactly an elliptical section, provided the 3 points
// P1, CP, P2 form a right angle, and a circular section if they are also
// of equal length (|P1,CP| == |CP,P2|)
// If w is 1,
// then we really don't need the division since the denominator will always
// be 1 and we could just treat that curve as a quadratic.
// If w is (infinity/large enough),
// then the equation devolves into 2 straight lines P1->CP->P2, but
// the straightforward math may encounter infinity/NaN values in the
// intermediate stages. The limit as w approaches infinity are those
// two lines.
//
// Some examples: https://fiddle.skia.org/c/986b521feb3b832f04cbdfeefc9fbc58
// Note that the quadratic drawn in red in the center is identical to the
// conic with a weight of 1, drawn in green in the lower left.
struct ConicPathComponent {
// Start point.
Point p1;
// Control point.
Point cp;
// End point.
Point p2;
// Weight
//
// We only need one value, but the underlying storage allocation is always
// a multiple of Point objects. To avoid confusion over which field the
// weight is stored in, and what the value of the other field may be, we
// store it in both x,y components of the |weight| field.
//
// This may also be an advantage eventually for code that can vectorize
// the conic calculations on both X & Y simultaneously.
Point weight;
ConicPathComponent() {}
ConicPathComponent(Point ap1, Point acp, Point ap2, Scalar weight)
: p1(ap1), cp(acp), p2(ap2), weight(weight, weight) {}
Point Solve(Scalar time) const;
void AppendPolylinePoints(Scalar scale_factor,
std::vector<Point>& points) const;
void ToLinearPathComponents(Scalar scale, VertexWriter& writer) const;
size_t CountLinearPathComponents(Scalar scale) const;
std::vector<Point> Extrema() const;
bool operator==(const ConicPathComponent& other) const {
return p1 == other.p1 && cp == other.cp && p2 == other.p2 &&
weight == other.weight;
}
std::optional<Vector2> GetStartDirection() const;
std::optional<Vector2> GetEndDirection() const;
std::array<QuadraticPathComponent, 2> ToQuadraticPathComponents() const;
void SubdivideToQuadraticPoints(std::array<Point, 5>& points) const;
};
// A component that represets a Cubic Bézier curve.
struct CubicPathComponent {
// Start point.

View File

@ -113,14 +113,6 @@ TEST(PathTest, PathSingleContour) {
EXPECT_TRUE(path.IsSingleContour());
}
{
Path path = PathBuilder{}
.AddConicCurve({100, 100}, {100, 50}, {200, 100}, 0.75f)
.TakePath();
EXPECT_TRUE(path.IsSingleContour());
}
}
TEST(PathTest, PathSingleContourDoubleShapes) {
@ -223,50 +215,34 @@ TEST(PathTest, PathSingleContourDoubleShapes) {
EXPECT_FALSE(path.IsSingleContour());
}
{
Path path = PathBuilder{}
.AddConicCurve({100, 100}, {100, 50}, {200, 100}, 0.75f)
.Close()
.AddConicCurve({100, 100}, {100, 50}, {200, 100}, 0.75f)
.TakePath();
EXPECT_FALSE(path.IsSingleContour());
}
}
TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
// Closed shapes.
{
Path path = PathBuilder{}.AddCircle({100, 100}, 50).TakePath();
EXPECT_NE(path.begin(), path.end());
EXPECT_EQ(path.begin().type(), Path::ComponentType::kContour);
auto contour = path.begin().contour();
ASSERT_NE(contour, nullptr);
EXPECT_POINT_NEAR(contour->destination, Point(100, 50));
EXPECT_TRUE(contour->IsClosed());
ContourComponent contour;
path.GetContourComponentAtIndex(0, contour);
EXPECT_POINT_NEAR(contour.destination, Point(100, 50));
EXPECT_TRUE(contour.IsClosed());
}
{
Path path =
PathBuilder{}.AddOval(Rect::MakeXYWH(100, 100, 100, 100)).TakePath();
EXPECT_NE(path.begin(), path.end());
EXPECT_EQ(path.begin().type(), Path::ComponentType::kContour);
auto contour = path.begin().contour();
ASSERT_NE(contour, nullptr);
EXPECT_POINT_NEAR(contour->destination, Point(150, 100));
EXPECT_TRUE(contour->IsClosed());
ContourComponent contour;
path.GetContourComponentAtIndex(0, contour);
EXPECT_POINT_NEAR(contour.destination, Point(150, 100));
EXPECT_TRUE(contour.IsClosed());
}
{
Path path =
PathBuilder{}.AddRect(Rect::MakeXYWH(100, 100, 100, 100)).TakePath();
EXPECT_NE(path.begin(), path.end());
EXPECT_EQ(path.begin().type(), Path::ComponentType::kContour);
auto contour = path.begin().contour();
ASSERT_NE(contour, nullptr);
EXPECT_POINT_NEAR(contour->destination, Point(100, 100));
EXPECT_TRUE(contour->IsClosed());
ContourComponent contour;
path.GetContourComponentAtIndex(0, contour);
EXPECT_POINT_NEAR(contour.destination, Point(100, 100));
EXPECT_TRUE(contour.IsClosed());
}
{
@ -274,12 +250,10 @@ TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
.AddRoundRect(RoundRect::MakeRectRadius(
Rect::MakeXYWH(100, 100, 100, 100), 10))
.TakePath();
EXPECT_NE(path.begin(), path.end());
EXPECT_EQ(path.begin().type(), Path::ComponentType::kContour);
auto contour = path.begin().contour();
ASSERT_NE(contour, nullptr);
EXPECT_POINT_NEAR(contour->destination, Point(110, 100));
EXPECT_TRUE(contour->IsClosed());
ContourComponent contour;
path.GetContourComponentAtIndex(0, contour);
EXPECT_POINT_NEAR(contour.destination, Point(110, 100));
EXPECT_TRUE(contour.IsClosed());
}
{
@ -287,12 +261,10 @@ TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
.AddRoundRect(RoundRect::MakeRectXY(
Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20)))
.TakePath();
EXPECT_NE(path.begin(), path.end());
EXPECT_EQ(path.begin().type(), Path::ComponentType::kContour);
auto contour = path.begin().contour();
ASSERT_NE(contour, nullptr);
EXPECT_POINT_NEAR(contour->destination, Point(110, 100));
EXPECT_TRUE(contour->IsClosed());
ContourComponent contour;
path.GetContourComponentAtIndex(0, contour);
EXPECT_POINT_NEAR(contour.destination, Point(110, 100));
EXPECT_TRUE(contour.IsClosed());
}
{
@ -300,12 +272,10 @@ TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
.AddRoundSuperellipse(RoundSuperellipse::MakeRectRadius(
Rect::MakeXYWH(100, 100, 100, 100), 10))
.TakePath();
EXPECT_NE(path.begin(), path.end());
EXPECT_EQ(path.begin().type(), Path::ComponentType::kContour);
auto contour = path.begin().contour();
ASSERT_NE(contour, nullptr);
EXPECT_POINT_NEAR(contour->destination, Point(150, 100));
EXPECT_TRUE(contour->IsClosed());
ContourComponent contour;
path.GetContourComponentAtIndex(0, contour);
EXPECT_POINT_NEAR(contour.destination, Point(150, 100));
EXPECT_TRUE(contour.IsClosed());
}
{
@ -313,24 +283,20 @@ TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
.AddRoundSuperellipse(RoundSuperellipse::MakeRectXY(
Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20)))
.TakePath();
EXPECT_NE(path.begin(), path.end());
EXPECT_EQ(path.begin().type(), Path::ComponentType::kContour);
auto contour = path.begin().contour();
ASSERT_NE(contour, nullptr);
EXPECT_POINT_NEAR(contour->destination, Point(150, 100));
EXPECT_TRUE(contour->IsClosed());
ContourComponent contour;
path.GetContourComponentAtIndex(0, contour);
EXPECT_POINT_NEAR(contour.destination, Point(150, 100));
EXPECT_TRUE(contour.IsClosed());
}
// Open shapes.
{
Point p(100, 100);
Path path = PathBuilder{}.AddLine(p, {200, 100}).TakePath();
EXPECT_NE(path.begin(), path.end());
EXPECT_EQ(path.begin().type(), Path::ComponentType::kContour);
auto contour = path.begin().contour();
ASSERT_NE(contour, nullptr);
EXPECT_POINT_NEAR(contour->destination, p);
EXPECT_FALSE(contour->IsClosed());
ContourComponent contour;
path.GetContourComponentAtIndex(0, contour);
ASSERT_POINT_NEAR(contour.destination, p);
ASSERT_FALSE(contour.IsClosed());
}
{
@ -338,36 +304,20 @@ TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
PathBuilder{}
.AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100})
.TakePath();
EXPECT_NE(path.begin(), path.end());
EXPECT_EQ(path.begin().type(), Path::ComponentType::kContour);
auto contour = path.begin().contour();
ASSERT_NE(contour, nullptr);
EXPECT_POINT_NEAR(contour->destination, Point(100, 100));
EXPECT_FALSE(contour->IsClosed());
ContourComponent contour;
path.GetContourComponentAtIndex(0, contour);
ASSERT_POINT_NEAR(contour.destination, Point(100, 100));
ASSERT_FALSE(contour.IsClosed());
}
{
Path path = PathBuilder{}
.AddQuadraticCurve({100, 100}, {100, 50}, {200, 100})
.TakePath();
EXPECT_NE(path.begin(), path.end());
EXPECT_EQ(path.begin().type(), Path::ComponentType::kContour);
auto contour = path.begin().contour();
ASSERT_NE(contour, nullptr);
EXPECT_POINT_NEAR(contour->destination, Point(100, 100));
EXPECT_FALSE(contour->IsClosed());
}
{
Path path = PathBuilder{}
.AddConicCurve({100, 100}, {100, 50}, {200, 100}, 0.75f)
.TakePath();
EXPECT_NE(path.begin(), path.end());
EXPECT_EQ(path.begin().type(), Path::ComponentType::kContour);
auto contour = path.begin().contour();
ASSERT_NE(contour, nullptr);
EXPECT_POINT_NEAR(contour->destination, Point(100, 100));
EXPECT_FALSE(contour->IsClosed());
ContourComponent contour;
path.GetContourComponentAtIndex(0, contour);
ASSERT_POINT_NEAR(contour.destination, Point(100, 100));
ASSERT_FALSE(contour.IsClosed());
}
}
@ -483,68 +433,35 @@ TEST(PathTest, PathShifting) {
auto path =
builder.AddLine(Point(0, 0), Point(10, 10))
.AddQuadraticCurve(Point(10, 10), Point(15, 15), Point(20, 20))
.AddConicCurve(Point(10, 10), Point(15, 10), Point(15, 15), 0.75f)
.AddCubicCurve(Point(20, 20), Point(25, 25), Point(-5, -5),
Point(30, 30))
.Close()
.Shift(Point(1, 1))
.TakePath();
auto it = path.begin();
ContourComponent contour;
LinearPathComponent linear;
QuadraticPathComponent quad;
CubicPathComponent cubic;
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
const ContourComponent* contour = it.contour();
ASSERT_NE(contour, nullptr);
++it;
ASSERT_TRUE(path.GetContourComponentAtIndex(0, contour));
ASSERT_TRUE(path.GetLinearComponentAtIndex(1, linear));
ASSERT_TRUE(path.GetQuadraticComponentAtIndex(3, quad));
ASSERT_TRUE(path.GetCubicComponentAtIndex(5, cubic));
ASSERT_EQ(it.type(), Path::ComponentType::kLinear);
const LinearPathComponent* linear = it.linear();
ASSERT_NE(linear, nullptr);
++it;
EXPECT_EQ(contour.destination, Point(1, 1));
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
++it;
EXPECT_EQ(linear.p1, Point(1, 1));
EXPECT_EQ(linear.p2, Point(11, 11));
ASSERT_EQ(it.type(), Path::ComponentType::kQuadratic);
const QuadraticPathComponent* quad = it.quadratic();
ASSERT_NE(quad, nullptr);
++it;
EXPECT_EQ(quad.cp, Point(16, 16));
EXPECT_EQ(quad.p1, Point(11, 11));
EXPECT_EQ(quad.p2, Point(21, 21));
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
++it;
ASSERT_EQ(it.type(), Path::ComponentType::kConic);
const ConicPathComponent* conic = it.conic();
ASSERT_NE(conic, nullptr);
++it;
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
++it;
ASSERT_EQ(it.type(), Path::ComponentType::kCubic);
const CubicPathComponent* cubic = it.cubic();
ASSERT_NE(cubic, nullptr);
++it;
// Close always opens a new contour, even if it isn't needed
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
++it;
EXPECT_EQ(it, path.end());
EXPECT_EQ(contour->destination, Point(1, 1));
EXPECT_EQ(linear->p1, Point(1, 1));
EXPECT_EQ(linear->p2, Point(11, 11));
EXPECT_EQ(quad->cp, Point(16, 16));
EXPECT_EQ(quad->p1, Point(11, 11));
EXPECT_EQ(quad->p2, Point(21, 21));
EXPECT_EQ(cubic->cp1, Point(26, 26));
EXPECT_EQ(cubic->cp2, Point(-4, -4));
EXPECT_EQ(cubic->p1, Point(21, 21));
EXPECT_EQ(cubic->p2, Point(31, 31));
EXPECT_EQ(cubic.cp1, Point(26, 26));
EXPECT_EQ(cubic.cp2, Point(-4, -4));
EXPECT_EQ(cubic.p1, Point(21, 21));
EXPECT_EQ(cubic.p2, Point(31, 31));
}
TEST(PathTest, PathBuilderWillComputeBounds) {
@ -573,68 +490,34 @@ TEST(PathTest, PathHorizontalLine) {
PathBuilder builder;
auto path = builder.HorizontalLineTo(10).TakePath();
auto it = path.begin();
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
++it;
LinearPathComponent linear;
path.GetLinearComponentAtIndex(1, linear);
ASSERT_EQ(it.type(), Path::ComponentType::kLinear);
const LinearPathComponent* linear = it.linear();
ASSERT_NE(linear, nullptr);
EXPECT_EQ(linear->p1, Point(0, 0));
EXPECT_EQ(linear->p2, Point(10, 0));
EXPECT_EQ(linear.p1, Point(0, 0));
EXPECT_EQ(linear.p2, Point(10, 0));
}
TEST(PathTest, PathVerticalLine) {
PathBuilder builder;
auto path = builder.VerticalLineTo(10).TakePath();
auto it = path.begin();
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
++it;
LinearPathComponent linear;
path.GetLinearComponentAtIndex(1, linear);
ASSERT_EQ(it.type(), Path::ComponentType::kLinear);
const LinearPathComponent* linear = it.linear();
ASSERT_NE(linear, nullptr);
EXPECT_EQ(linear->p1, Point(0, 0));
EXPECT_EQ(linear->p2, Point(0, 10));
EXPECT_EQ(linear.p1, Point(0, 0));
EXPECT_EQ(linear.p2, Point(0, 10));
}
TEST(PathTest, QuadradicPath) {
PathBuilder builder;
auto path = builder.QuadraticCurveTo(Point(10, 10), Point(20, 20)).TakePath();
auto it = path.begin();
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
++it;
QuadraticPathComponent quad;
path.GetQuadraticComponentAtIndex(1, quad);
ASSERT_EQ(it.type(), Path::ComponentType::kQuadratic);
const QuadraticPathComponent* quad = it.quadratic();
ASSERT_NE(quad, nullptr);
EXPECT_EQ(quad->p1, Point(0, 0));
EXPECT_EQ(quad->cp, Point(10, 10));
EXPECT_EQ(quad->p2, Point(20, 20));
}
TEST(PathTest, ConicPath) {
PathBuilder builder;
auto path =
builder.ConicCurveTo(Point(10, 10), Point(20, 20), 0.75f).TakePath();
auto it = path.begin();
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
++it;
ASSERT_EQ(it.type(), Path::ComponentType::kConic);
const ConicPathComponent* conic = it.conic();
ASSERT_NE(conic, nullptr);
EXPECT_EQ(conic->p1, Point(0, 0));
EXPECT_EQ(conic->cp, Point(10, 10));
EXPECT_EQ(conic->p2, Point(20, 20));
EXPECT_EQ(conic->weight, Point(0.75f, 0.75f));
EXPECT_EQ(quad.p1, Point(0, 0));
EXPECT_EQ(quad.cp, Point(10, 10));
EXPECT_EQ(quad.p2, Point(20, 20));
}
TEST(PathTest, CubicPath) {
@ -643,18 +526,13 @@ TEST(PathTest, CubicPath) {
builder.CubicCurveTo(Point(10, 10), Point(-10, -10), Point(20, 20))
.TakePath();
auto it = path.begin();
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
++it;
CubicPathComponent cubic;
path.GetCubicComponentAtIndex(1, cubic);
ASSERT_EQ(it.type(), Path::ComponentType::kCubic);
const CubicPathComponent* cubic = it.cubic();
ASSERT_NE(cubic, nullptr);
EXPECT_EQ(cubic->p1, Point(0, 0));
EXPECT_EQ(cubic->cp1, Point(10, 10));
EXPECT_EQ(cubic->cp2, Point(-10, -10));
EXPECT_EQ(cubic->p2, Point(20, 20));
EXPECT_EQ(cubic.p1, Point(0, 0));
EXPECT_EQ(cubic.cp1, Point(10, 10));
EXPECT_EQ(cubic.cp2, Point(-10, -10));
EXPECT_EQ(cubic.p2, Point(20, 20));
}
TEST(PathTest, BoundingBoxCubic) {
@ -707,8 +585,9 @@ TEST(PathTest, EmptyPath) {
auto path = PathBuilder{}.TakePath();
ASSERT_EQ(path.GetComponentCount(), 1u);
const ContourComponent* c = path.begin().contour();
ASSERT_POINT_NEAR(c->destination, Point());
ContourComponent c;
path.GetContourComponentAtIndex(0, c);
ASSERT_POINT_NEAR(c.destination, Point());
Path::Polyline polyline = path.CreatePolyline(1.0f);
ASSERT_TRUE(polyline.points->empty());
@ -720,122 +599,77 @@ TEST(PathTest, SimplePath) {
auto path = builder.AddLine({0, 0}, {100, 100})
.AddQuadraticCurve({100, 100}, {200, 200}, {300, 300})
.AddConicCurve({100, 100}, {200, 200}, {300, 300}, 0.75f)
.AddCubicCurve({300, 300}, {400, 400}, {500, 500}, {600, 600})
.TakePath();
EXPECT_EQ(path.GetComponentCount(), 8u);
EXPECT_EQ(path.GetComponentCount(), 6u);
EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kLinear), 1u);
EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kQuadratic), 1u);
EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kConic), 1u);
EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kCubic), 1u);
EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kContour), 4u);
auto it = path.begin();
EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kContour), 3u);
{
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
const ContourComponent* contour = it.contour();
ASSERT_NE(contour, nullptr);
++it;
Point p1(0, 0);
EXPECT_EQ(contour->destination, p1);
EXPECT_FALSE(contour->IsClosed());
}
{
ASSERT_EQ(it.type(), Path::ComponentType::kLinear);
const LinearPathComponent* linear = it.linear();
ASSERT_NE(linear, nullptr);
++it;
LinearPathComponent linear;
EXPECT_TRUE(path.GetLinearComponentAtIndex(1, linear));
Point p1(0, 0);
Point p2(100, 100);
EXPECT_EQ(linear->p1, p1);
EXPECT_EQ(linear->p2, p2);
EXPECT_EQ(linear.p1, p1);
EXPECT_EQ(linear.p2, p2);
}
{
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
const ContourComponent* contour = it.contour();
ASSERT_NE(contour, nullptr);
++it;
Point p1(100, 100);
EXPECT_EQ(contour->destination, p1);
EXPECT_FALSE(contour->IsClosed());
}
{
ASSERT_EQ(it.type(), Path::ComponentType::kQuadratic);
const QuadraticPathComponent* quad = it.quadratic();
ASSERT_NE(quad, nullptr);
++it;
QuadraticPathComponent quad;
EXPECT_TRUE(path.GetQuadraticComponentAtIndex(3, quad));
Point p1(100, 100);
Point cp(200, 200);
Point p2(300, 300);
EXPECT_EQ(quad->p1, p1);
EXPECT_EQ(quad->cp, cp);
EXPECT_EQ(quad->p2, p2);
EXPECT_EQ(quad.p1, p1);
EXPECT_EQ(quad.cp, cp);
EXPECT_EQ(quad.p2, p2);
}
{
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
const ContourComponent* contour = it.contour();
ASSERT_NE(contour, nullptr);
++it;
Point p1(100, 100);
EXPECT_EQ(contour->destination, p1);
EXPECT_FALSE(contour->IsClosed());
}
{
ASSERT_EQ(it.type(), Path::ComponentType::kConic);
const ConicPathComponent* conic = it.conic();
ASSERT_NE(conic, nullptr);
++it;
Point p1(100, 100);
Point cp(200, 200);
Point p2(300, 300);
Point weight(0.75f, 0.75f);
EXPECT_EQ(conic->p1, p1);
EXPECT_EQ(conic->cp, cp);
EXPECT_EQ(conic->p2, p2);
EXPECT_EQ(conic->weight, weight);
}
{
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
const ContourComponent* contour = it.contour();
ASSERT_NE(contour, nullptr);
++it;
Point p1(300, 300);
EXPECT_EQ(contour->destination, p1);
EXPECT_FALSE(contour->IsClosed());
}
{
ASSERT_EQ(it.type(), Path::ComponentType::kCubic);
const CubicPathComponent* cubic = it.cubic();
ASSERT_NE(cubic, nullptr);
++it;
CubicPathComponent cubic;
EXPECT_TRUE(path.GetCubicComponentAtIndex(5, cubic));
Point p1(300, 300);
Point cp1(400, 400);
Point cp2(500, 500);
Point p2(600, 600);
EXPECT_EQ(cubic->p1, p1);
EXPECT_EQ(cubic->cp1, cp1);
EXPECT_EQ(cubic->cp2, cp2);
EXPECT_EQ(cubic->p2, p2);
EXPECT_EQ(cubic.p1, p1);
EXPECT_EQ(cubic.cp1, cp1);
EXPECT_EQ(cubic.cp2, cp2);
EXPECT_EQ(cubic.p2, p2);
}
EXPECT_EQ(it, path.end());
{
ContourComponent contour;
EXPECT_TRUE(path.GetContourComponentAtIndex(0, contour));
Point p1(0, 0);
EXPECT_EQ(contour.destination, p1);
EXPECT_FALSE(contour.IsClosed());
}
{
ContourComponent contour;
EXPECT_TRUE(path.GetContourComponentAtIndex(2, contour));
Point p1(100, 100);
EXPECT_EQ(contour.destination, p1);
EXPECT_FALSE(contour.IsClosed());
}
{
ContourComponent contour;
EXPECT_TRUE(path.GetContourComponentAtIndex(4, contour));
Point p1(300, 300);
EXPECT_EQ(contour.destination, p1);
EXPECT_FALSE(contour.IsClosed());
}
}
TEST(PathTest, RepeatCloseDoesNotAddNewLines) {
@ -1081,36 +915,23 @@ TEST(PathTest, PathBuilderDoesNotMutateCopiedPaths) {
} else {
EXPECT_EQ(path.GetComponentCount(), 3u) << label;
}
auto it = path.begin();
{
ASSERT_EQ(it.type(), Path::ComponentType::kContour) << label;
const ContourComponent* contour = it.contour();
ASSERT_NE(contour, nullptr) << label;
++it;
EXPECT_EQ(contour->destination, offset + Point(10, 10)) << label;
EXPECT_EQ(contour->IsClosed(), is_closed) << label;
ContourComponent contour;
EXPECT_TRUE(path.GetContourComponentAtIndex(0, contour)) << label;
EXPECT_EQ(contour.destination, offset + Point(10, 10)) << label;
EXPECT_EQ(contour.IsClosed(), is_closed) << label;
}
{
ASSERT_EQ(it.type(), Path::ComponentType::kLinear) << label;
const LinearPathComponent* line = it.linear();
ASSERT_NE(line, nullptr) << label;
++it;
EXPECT_EQ(line->p1, offset + Point(10, 10)) << label;
EXPECT_EQ(line->p2, offset + Point(20, 20)) << label;
LinearPathComponent line;
EXPECT_TRUE(path.GetLinearComponentAtIndex(1, line)) << label;
EXPECT_EQ(line.p1, offset + Point(10, 10)) << label;
EXPECT_EQ(line.p2, offset + Point(20, 20)) << label;
}
{
ASSERT_EQ(it.type(), Path::ComponentType::kLinear) << label;
const LinearPathComponent* line = it.linear();
ASSERT_NE(line, nullptr) << label;
++it;
EXPECT_EQ(line->p1, offset + Point(20, 20)) << label;
EXPECT_EQ(line->p2, offset + Point(20, 10)) << label;
}
if (!is_mutated) {
EXPECT_EQ(it, path.end()) << label;
LinearPathComponent line;
EXPECT_TRUE(path.GetLinearComponentAtIndex(2, line)) << label;
EXPECT_EQ(line.p1, offset + Point(20, 20)) << label;
EXPECT_EQ(line.p2, offset + Point(20, 10)) << label;
}
};
@ -1213,18 +1034,6 @@ TEST(PathTest, PathBuilderDoesNotMutateCopiedPaths) {
},
false, {}, "Relative QuadraticCurveTo");
test_isolation(
[](PathBuilder& builder) {
builder.ConicCurveTo({20, 30}, {30, 20}, 0.75f, false);
},
false, {}, "Absolute ConicCurveTo");
test_isolation(
[](PathBuilder& builder) {
builder.ConicCurveTo({20, 30}, {30, 20}, 0.75f, true);
},
false, {}, "Relative ConicCurveTo");
test_isolation(
[](PathBuilder& builder) {
builder.CubicCurveTo({20, 30}, {30, 20}, {30, 30}, false);
@ -1274,12 +1083,6 @@ TEST(PathTest, PathBuilderDoesNotMutateCopiedPaths) {
},
false, {}, "AddQuadraticCurve");
test_isolation(
[](PathBuilder& builder) {
builder.AddConicCurve({100, 100}, {150, 100}, {150, 150}, 0.75f);
},
false, {}, "AddConicCurve");
test_isolation(
[](PathBuilder& builder) {
builder.AddCubicCurve({100, 100}, {150, 100}, {100, 150}, {150, 150});