[Impeller] add support for rational bezier conics to Path (#163282)
Add basic support for storing rational bezier conics in impeller::Path. The support is very thin and just degrades the conics into a pair of quadratic curves just as Impeller has always done for the conic segments that it receives via SkPath objects, but it puts in place the framework for eventually handling the conics more directly and allows the unit tests to be rewritten on top of Impeller paths rather than SkPaths, paving the way for reduced internal API dependencies.
This commit is contained in:
parent
7e155bc18e
commit
db0bdfd117
@ -139,6 +139,10 @@ 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);
|
||||
}
|
||||
|
@ -265,39 +265,46 @@ SkPath DlPath::ConvertToSkiaPath(const Path& path, const DlPoint& shift) {
|
||||
}
|
||||
};
|
||||
|
||||
size_t count = path.GetComponentCount();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
switch (path.GetComponentTypeAtIndex(i)) {
|
||||
for (auto it = path.begin(), end = path.end(); it != end; ++it) {
|
||||
switch (it.type()) {
|
||||
case ComponentType::kContour: {
|
||||
impeller::ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(i, contour);
|
||||
const impeller::ContourComponent* contour = it.contour();
|
||||
FML_DCHECK(contour != nullptr);
|
||||
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: {
|
||||
impeller::LinearPathComponent linear;
|
||||
path.GetLinearComponentAtIndex(i, linear);
|
||||
const impeller::LinearPathComponent* linear = it.linear();
|
||||
FML_DCHECK(linear != nullptr);
|
||||
resolve_moveto();
|
||||
sk_path.lineTo(ToSkPoint(linear.p2));
|
||||
sk_path.lineTo(ToSkPoint(linear->p2));
|
||||
break;
|
||||
}
|
||||
case ComponentType::kQuadratic: {
|
||||
impeller::QuadraticPathComponent quadratic;
|
||||
path.GetQuadraticComponentAtIndex(i, quadratic);
|
||||
const impeller::QuadraticPathComponent* quadratic = it.quadratic();
|
||||
FML_DCHECK(quadratic != nullptr);
|
||||
resolve_moveto();
|
||||
sk_path.quadTo(ToSkPoint(quadratic.cp), ToSkPoint(quadratic.p2));
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case ComponentType::kCubic: {
|
||||
impeller::CubicPathComponent cubic;
|
||||
path.GetCubicComponentAtIndex(i, cubic);
|
||||
const impeller::CubicPathComponent* cubic = it.cubic();
|
||||
FML_DCHECK(cubic != nullptr);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -339,27 +346,26 @@ Path DlPath::ConvertToImpellerPath(const SkPath& path, const DlPoint& shift) {
|
||||
builder.QuadraticCurveTo(ToDlPoint(data.points[1]),
|
||||
ToDlPoint(data.points[2]));
|
||||
break;
|
||||
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]));
|
||||
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]));
|
||||
}
|
||||
} break;
|
||||
break;
|
||||
case SkPath::kCubic_Verb:
|
||||
builder.CubicCurveTo(ToDlPoint(data.points[1]),
|
||||
ToDlPoint(data.points[2]),
|
||||
|
@ -11,6 +11,7 @@
|
||||
#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"
|
||||
@ -372,5 +373,92 @@ 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
|
||||
|
@ -46,8 +46,9 @@ constexpr float k2OverSqrtPi = 1.12837916709551257390f;
|
||||
// sqrt(2)
|
||||
constexpr float kSqrt2 = 1.41421356237309504880f;
|
||||
|
||||
// 1/sqrt(2)
|
||||
// sqrt(2) / 2 == 1/sqrt(2)
|
||||
constexpr float k1OverSqrt2 = 0.70710678118654752440f;
|
||||
constexpr float kSqrt2Over2 = 0.70710678118654752440f;
|
||||
|
||||
// phi
|
||||
constexpr float kPhi = 1.61803398874989484820f;
|
||||
|
@ -19,6 +19,64 @@ 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()) {
|
||||
@ -87,6 +145,13 @@ 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*>(
|
||||
@ -135,6 +200,17 @@ 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*>(
|
||||
@ -170,86 +246,13 @@ void Path::WritePolyline(Scalar scale, VertexWriter& writer) const {
|
||||
}
|
||||
}
|
||||
|
||||
Path::ComponentType Path::GetComponentTypeAtIndex(size_t index) const {
|
||||
auto& components = data_->components;
|
||||
return components[index];
|
||||
Path::ComponentIterator Path::begin() const {
|
||||
return ComponentIterator(*this, 0u, 0u);
|
||||
}
|
||||
|
||||
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::ComponentIterator Path::end() const {
|
||||
return ComponentIterator(*this, data_->components.size(),
|
||||
data_->points.size());
|
||||
}
|
||||
|
||||
Path::Polyline::Polyline(Path::Polyline::PointBufferPtr point_buffer,
|
||||
@ -315,6 +318,16 @@ 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]);
|
||||
@ -377,6 +390,19 @@ 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,
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
@ -55,16 +56,50 @@ 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:
|
||||
@ -157,18 +192,8 @@ class Path {
|
||||
/// @brief Whether the line contains a single contour.
|
||||
bool IsSingleContour() 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;
|
||||
ComponentIterator begin() const;
|
||||
ComponentIterator end() const;
|
||||
|
||||
/// Callers must provide the scale factor for how this path will be
|
||||
/// transformed.
|
||||
|
@ -252,6 +252,17 @@ 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;
|
||||
@ -269,22 +280,33 @@ PathBuilder& PathBuilder::CubicCurveTo(Point controlPoint1,
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::AddQuadraticCurve(Point p1, Point cp, Point p2) {
|
||||
PathBuilder& PathBuilder::AddQuadraticCurve(const Point& p1,
|
||||
const Point& cp,
|
||||
const Point& p2) {
|
||||
MoveTo(p1);
|
||||
AddQuadraticComponent(p1, cp, p2);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::AddCubicCurve(Point p1,
|
||||
Point cp1,
|
||||
Point cp2,
|
||||
Point p2) {
|
||||
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) {
|
||||
MoveTo(p1);
|
||||
AddCubicComponent(p1, cp1, cp2, p2);
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::AddRect(Rect rect) {
|
||||
PathBuilder& PathBuilder::AddRect(const Rect& rect) {
|
||||
auto origin = rect.GetOrigin();
|
||||
auto size = rect.GetSize();
|
||||
|
||||
@ -515,6 +537,28 @@ 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,
|
||||
@ -684,6 +728,13 @@ 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]);
|
||||
@ -767,6 +818,13 @@ 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])
|
||||
|
@ -60,6 +60,16 @@ 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`.
|
||||
///
|
||||
@ -70,7 +80,7 @@ class PathBuilder {
|
||||
Point point,
|
||||
bool relative = false);
|
||||
|
||||
PathBuilder& AddRect(Rect rect);
|
||||
PathBuilder& AddRect(const Rect& rect);
|
||||
|
||||
PathBuilder& AddCircle(const Point& center, Scalar radius);
|
||||
|
||||
@ -86,11 +96,23 @@ class PathBuilder {
|
||||
|
||||
/// @brief Move to point `p1`, then insert a quadradic curve from `p1` to `p2`
|
||||
/// with the control point `cp`.
|
||||
PathBuilder& AddQuadraticCurve(Point p1, Point cp, Point p2);
|
||||
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);
|
||||
|
||||
/// @brief Move to point `p1`, then insert a cubic curve from `p1` to `p2`
|
||||
/// with control points `cp1` and `cp2`.
|
||||
PathBuilder& AddCubicCurve(Point p1, Point cp1, Point cp2, Point p2);
|
||||
PathBuilder& AddCubicCurve(const Point& p1,
|
||||
const Point& cp1,
|
||||
const Point& cp2,
|
||||
const Point& p2);
|
||||
|
||||
/// @brief Transform the existing path segments and contours by the given
|
||||
/// `offset`.
|
||||
@ -135,6 +157,11 @@ 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,
|
||||
|
@ -7,8 +7,9 @@
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "impeller/geometry/scalar.h"
|
||||
#include "impeller/geometry/wangs_formula.h"
|
||||
#include "flutter/fml/logging.h"
|
||||
#include "flutter/impeller/geometry/scalar.h"
|
||||
#include "flutter/impeller/geometry/wangs_formula.h"
|
||||
|
||||
namespace impeller {
|
||||
|
||||
@ -183,6 +184,20 @@ 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,
|
||||
@ -309,6 +324,115 @@ 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
|
||||
|
@ -5,6 +5,7 @@
|
||||
#ifndef FLUTTER_IMPELLER_GEOMETRY_PATH_COMPONENT_H_
|
||||
#define FLUTTER_IMPELLER_GEOMETRY_PATH_COMPONENT_H_
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
@ -170,6 +171,107 @@ 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.
|
||||
|
@ -113,6 +113,14 @@ 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) {
|
||||
@ -215,34 +223,50 @@ 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();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
EXPECT_POINT_NEAR(contour.destination, Point(100, 50));
|
||||
EXPECT_TRUE(contour.IsClosed());
|
||||
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());
|
||||
}
|
||||
|
||||
{
|
||||
Path path =
|
||||
PathBuilder{}.AddOval(Rect::MakeXYWH(100, 100, 100, 100)).TakePath();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
EXPECT_POINT_NEAR(contour.destination, Point(150, 100));
|
||||
EXPECT_TRUE(contour.IsClosed());
|
||||
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());
|
||||
}
|
||||
|
||||
{
|
||||
Path path =
|
||||
PathBuilder{}.AddRect(Rect::MakeXYWH(100, 100, 100, 100)).TakePath();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
EXPECT_POINT_NEAR(contour.destination, Point(100, 100));
|
||||
EXPECT_TRUE(contour.IsClosed());
|
||||
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());
|
||||
}
|
||||
|
||||
{
|
||||
@ -250,10 +274,12 @@ TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
|
||||
.AddRoundRect(RoundRect::MakeRectRadius(
|
||||
Rect::MakeXYWH(100, 100, 100, 100), 10))
|
||||
.TakePath();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
EXPECT_POINT_NEAR(contour.destination, Point(110, 100));
|
||||
EXPECT_TRUE(contour.IsClosed());
|
||||
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());
|
||||
}
|
||||
|
||||
{
|
||||
@ -261,10 +287,12 @@ TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
|
||||
.AddRoundRect(RoundRect::MakeRectXY(
|
||||
Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20)))
|
||||
.TakePath();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
EXPECT_POINT_NEAR(contour.destination, Point(110, 100));
|
||||
EXPECT_TRUE(contour.IsClosed());
|
||||
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());
|
||||
}
|
||||
|
||||
{
|
||||
@ -272,10 +300,12 @@ TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
|
||||
.AddRoundSuperellipse(RoundSuperellipse::MakeRectRadius(
|
||||
Rect::MakeXYWH(100, 100, 100, 100), 10))
|
||||
.TakePath();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
EXPECT_POINT_NEAR(contour.destination, Point(150, 100));
|
||||
EXPECT_TRUE(contour.IsClosed());
|
||||
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());
|
||||
}
|
||||
|
||||
{
|
||||
@ -283,20 +313,24 @@ TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
|
||||
.AddRoundSuperellipse(RoundSuperellipse::MakeRectXY(
|
||||
Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20)))
|
||||
.TakePath();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
EXPECT_POINT_NEAR(contour.destination, Point(150, 100));
|
||||
EXPECT_TRUE(contour.IsClosed());
|
||||
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());
|
||||
}
|
||||
|
||||
// Open shapes.
|
||||
{
|
||||
Point p(100, 100);
|
||||
Path path = PathBuilder{}.AddLine(p, {200, 100}).TakePath();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
ASSERT_POINT_NEAR(contour.destination, p);
|
||||
ASSERT_FALSE(contour.IsClosed());
|
||||
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());
|
||||
}
|
||||
|
||||
{
|
||||
@ -304,20 +338,36 @@ TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) {
|
||||
PathBuilder{}
|
||||
.AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100})
|
||||
.TakePath();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
ASSERT_POINT_NEAR(contour.destination, Point(100, 100));
|
||||
ASSERT_FALSE(contour.IsClosed());
|
||||
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{}
|
||||
.AddQuadraticCurve({100, 100}, {100, 50}, {200, 100})
|
||||
.TakePath();
|
||||
ContourComponent contour;
|
||||
path.GetContourComponentAtIndex(0, contour);
|
||||
ASSERT_POINT_NEAR(contour.destination, Point(100, 100));
|
||||
ASSERT_FALSE(contour.IsClosed());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,35 +483,68 @@ 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();
|
||||
|
||||
ContourComponent contour;
|
||||
LinearPathComponent linear;
|
||||
QuadraticPathComponent quad;
|
||||
CubicPathComponent cubic;
|
||||
auto it = path.begin();
|
||||
|
||||
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::kContour);
|
||||
const ContourComponent* contour = it.contour();
|
||||
ASSERT_NE(contour, nullptr);
|
||||
++it;
|
||||
|
||||
EXPECT_EQ(contour.destination, Point(1, 1));
|
||||
ASSERT_EQ(it.type(), Path::ComponentType::kLinear);
|
||||
const LinearPathComponent* linear = it.linear();
|
||||
ASSERT_NE(linear, nullptr);
|
||||
++it;
|
||||
|
||||
EXPECT_EQ(linear.p1, Point(1, 1));
|
||||
EXPECT_EQ(linear.p2, Point(11, 11));
|
||||
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
|
||||
++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::kQuadratic);
|
||||
const QuadraticPathComponent* quad = it.quadratic();
|
||||
ASSERT_NE(quad, nullptr);
|
||||
++it;
|
||||
|
||||
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));
|
||||
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));
|
||||
}
|
||||
|
||||
TEST(PathTest, PathBuilderWillComputeBounds) {
|
||||
@ -490,34 +573,68 @@ TEST(PathTest, PathHorizontalLine) {
|
||||
PathBuilder builder;
|
||||
auto path = builder.HorizontalLineTo(10).TakePath();
|
||||
|
||||
LinearPathComponent linear;
|
||||
path.GetLinearComponentAtIndex(1, linear);
|
||||
auto it = path.begin();
|
||||
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
|
||||
++it;
|
||||
|
||||
EXPECT_EQ(linear.p1, Point(0, 0));
|
||||
EXPECT_EQ(linear.p2, Point(10, 0));
|
||||
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));
|
||||
}
|
||||
|
||||
TEST(PathTest, PathVerticalLine) {
|
||||
PathBuilder builder;
|
||||
auto path = builder.VerticalLineTo(10).TakePath();
|
||||
|
||||
LinearPathComponent linear;
|
||||
path.GetLinearComponentAtIndex(1, linear);
|
||||
auto it = path.begin();
|
||||
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
|
||||
++it;
|
||||
|
||||
EXPECT_EQ(linear.p1, Point(0, 0));
|
||||
EXPECT_EQ(linear.p2, Point(0, 10));
|
||||
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));
|
||||
}
|
||||
|
||||
TEST(PathTest, QuadradicPath) {
|
||||
PathBuilder builder;
|
||||
auto path = builder.QuadraticCurveTo(Point(10, 10), Point(20, 20)).TakePath();
|
||||
|
||||
QuadraticPathComponent quad;
|
||||
path.GetQuadraticComponentAtIndex(1, quad);
|
||||
auto it = path.begin();
|
||||
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
|
||||
++it;
|
||||
|
||||
EXPECT_EQ(quad.p1, Point(0, 0));
|
||||
EXPECT_EQ(quad.cp, Point(10, 10));
|
||||
EXPECT_EQ(quad.p2, Point(20, 20));
|
||||
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));
|
||||
}
|
||||
|
||||
TEST(PathTest, CubicPath) {
|
||||
@ -526,13 +643,18 @@ TEST(PathTest, CubicPath) {
|
||||
builder.CubicCurveTo(Point(10, 10), Point(-10, -10), Point(20, 20))
|
||||
.TakePath();
|
||||
|
||||
CubicPathComponent cubic;
|
||||
path.GetCubicComponentAtIndex(1, cubic);
|
||||
auto it = path.begin();
|
||||
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
|
||||
++it;
|
||||
|
||||
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));
|
||||
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));
|
||||
}
|
||||
|
||||
TEST(PathTest, BoundingBoxCubic) {
|
||||
@ -585,9 +707,8 @@ TEST(PathTest, EmptyPath) {
|
||||
auto path = PathBuilder{}.TakePath();
|
||||
ASSERT_EQ(path.GetComponentCount(), 1u);
|
||||
|
||||
ContourComponent c;
|
||||
path.GetContourComponentAtIndex(0, c);
|
||||
ASSERT_POINT_NEAR(c.destination, Point());
|
||||
const ContourComponent* c = path.begin().contour();
|
||||
ASSERT_POINT_NEAR(c->destination, Point());
|
||||
|
||||
Path::Polyline polyline = path.CreatePolyline(1.0f);
|
||||
ASSERT_TRUE(polyline.points->empty());
|
||||
@ -599,77 +720,122 @@ 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(), 6u);
|
||||
EXPECT_EQ(path.GetComponentCount(), 8u);
|
||||
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), 3u);
|
||||
EXPECT_EQ(path.GetComponentCount(Path::ComponentType::kContour), 4u);
|
||||
|
||||
auto it = path.begin();
|
||||
|
||||
{
|
||||
LinearPathComponent linear;
|
||||
EXPECT_TRUE(path.GetLinearComponentAtIndex(1, linear));
|
||||
ASSERT_EQ(it.type(), Path::ComponentType::kContour);
|
||||
const ContourComponent* contour = it.contour();
|
||||
ASSERT_NE(contour, nullptr);
|
||||
++it;
|
||||
|
||||
Point p1(0, 0);
|
||||
Point p2(100, 100);
|
||||
EXPECT_EQ(linear.p1, p1);
|
||||
EXPECT_EQ(linear.p2, p2);
|
||||
EXPECT_EQ(contour->destination, p1);
|
||||
EXPECT_FALSE(contour->IsClosed());
|
||||
}
|
||||
|
||||
{
|
||||
QuadraticPathComponent quad;
|
||||
EXPECT_TRUE(path.GetQuadraticComponentAtIndex(3, quad));
|
||||
ASSERT_EQ(it.type(), Path::ComponentType::kLinear);
|
||||
const LinearPathComponent* linear = it.linear();
|
||||
ASSERT_NE(linear, nullptr);
|
||||
++it;
|
||||
|
||||
Point p1(0, 0);
|
||||
Point p2(100, 100);
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
{
|
||||
CubicPathComponent cubic;
|
||||
EXPECT_TRUE(path.GetCubicComponentAtIndex(5, cubic));
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
{
|
||||
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());
|
||||
}
|
||||
EXPECT_EQ(it, path.end());
|
||||
}
|
||||
|
||||
TEST(PathTest, RepeatCloseDoesNotAddNewLines) {
|
||||
@ -915,23 +1081,36 @@ TEST(PathTest, PathBuilderDoesNotMutateCopiedPaths) {
|
||||
} else {
|
||||
EXPECT_EQ(path.GetComponentCount(), 3u) << label;
|
||||
}
|
||||
auto it = path.begin();
|
||||
{
|
||||
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::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;
|
||||
}
|
||||
{
|
||||
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(10, 10)) << label;
|
||||
EXPECT_EQ(line->p2, offset + Point(20, 20)) << 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;
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1034,6 +1213,18 @@ 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);
|
||||
@ -1083,6 +1274,12 @@ 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});
|
||||
|
Loading…
x
Reference in New Issue
Block a user