blob: b68e9b28456e22d525c0ceccadddb780d81900eb [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "impeller/geometry/path.h"
#include <optional>
#include <variant>
#include "flutter/fml/logging.h"
#include "impeller/geometry/path_component.h"
#include "impeller/geometry/point.h"
namespace impeller {
Path::Path() {
AddContourComponent({});
};
Path::~Path() = default;
std::tuple<size_t, size_t> Path::Polyline::GetContourPointBounds(
size_t contour_index) const {
if (contour_index >= contours.size()) {
return {points->size(), points->size()};
}
const size_t start_index = contours.at(contour_index).start_index;
const size_t end_index = (contour_index >= contours.size() - 1)
? points->size()
: contours.at(contour_index + 1).start_index;
return std::make_tuple(start_index, end_index);
}
size_t Path::GetComponentCount(std::optional<ComponentType> type) const {
if (!type.has_value()) {
return components_.size();
}
auto type_value = type.value();
if (type_value == ComponentType::kContour) {
return contours_.size();
}
size_t count = 0u;
for (const auto& component : components_) {
if (component.type == type_value) {
count++;
}
}
return count;
}
void Path::SetFillType(FillType fill) {
fill_ = fill;
}
FillType Path::GetFillType() const {
return fill_;
}
bool Path::IsConvex() const {
return convexity_ == Convexity::kConvex;
}
void Path::SetConvexity(Convexity value) {
convexity_ = value;
}
void Path::Shift(Point shift) {
for (auto i = 0u; i < points_.size(); i++) {
points_[i] += shift;
}
for (auto& contour : contours_) {
contour.destination += shift;
}
}
Path Path::Clone() const {
Path new_path = *this;
return new_path;
}
Path& Path::AddLinearComponent(const Point& p1, const Point& p2) {
auto index = points_.size();
points_.emplace_back(p1);
points_.emplace_back(p2);
components_.emplace_back(ComponentType::kLinear, index);
return *this;
}
Path& Path::AddQuadraticComponent(const Point& p1,
const Point& cp,
const Point& p2) {
auto index = points_.size();
points_.emplace_back(p1);
points_.emplace_back(cp);
points_.emplace_back(p2);
components_.emplace_back(ComponentType::kQuadratic, index);
return *this;
}
Path& Path::AddCubicComponent(const Point& p1,
const Point& cp1,
const Point& cp2,
const Point& p2) {
auto index = points_.size();
points_.emplace_back(p1);
points_.emplace_back(cp1);
points_.emplace_back(cp2);
points_.emplace_back(p2);
components_.emplace_back(ComponentType::kCubic, index);
return *this;
}
Path& Path::AddContourComponent(const Point& destination, bool is_closed) {
if (components_.size() > 0 &&
components_.back().type == ComponentType::kContour) {
// Never insert contiguous contours.
contours_.back() = ContourComponent(destination, is_closed);
} else {
contours_.emplace_back(ContourComponent(destination, is_closed));
components_.emplace_back(ComponentType::kContour, contours_.size() - 1);
}
return *this;
}
void Path::SetContourClosed(bool is_closed) {
contours_.back().is_closed = is_closed;
}
void Path::EnumerateComponents(
const Applier<LinearPathComponent>& linear_applier,
const Applier<QuadraticPathComponent>& quad_applier,
const Applier<CubicPathComponent>& cubic_applier,
const Applier<ContourComponent>& contour_applier) const {
size_t currentIndex = 0;
for (const auto& component : components_) {
switch (component.type) {
case ComponentType::kLinear:
if (linear_applier) {
linear_applier(currentIndex,
LinearPathComponent(points_[component.index],
points_[component.index + 1]));
}
break;
case ComponentType::kQuadratic:
if (quad_applier) {
quad_applier(currentIndex,
QuadraticPathComponent(points_[component.index],
points_[component.index + 1],
points_[component.index + 2]));
}
break;
case ComponentType::kCubic:
if (cubic_applier) {
cubic_applier(currentIndex,
CubicPathComponent(points_[component.index],
points_[component.index + 1],
points_[component.index + 2],
points_[component.index + 3]));
}
break;
case ComponentType::kContour:
if (contour_applier) {
contour_applier(currentIndex, contours_[component.index]);
}
break;
}
currentIndex++;
}
}
bool Path::GetLinearComponentAtIndex(size_t index,
LinearPathComponent& linear) const {
if (index >= components_.size()) {
return false;
}
if (components_[index].type != ComponentType::kLinear) {
return false;
}
auto point_index = components_[index].index;
linear = LinearPathComponent(points_[point_index], points_[point_index + 1]);
return true;
}
bool Path::GetQuadraticComponentAtIndex(
size_t index,
QuadraticPathComponent& quadratic) const {
if (index >= components_.size()) {
return false;
}
if (components_[index].type != ComponentType::kQuadratic) {
return false;
}
auto point_index = components_[index].index;
quadratic = QuadraticPathComponent(
points_[point_index], points_[point_index + 1], points_[point_index + 2]);
return true;
}
bool Path::GetCubicComponentAtIndex(size_t index,
CubicPathComponent& cubic) const {
if (index >= components_.size()) {
return false;
}
if (components_[index].type != ComponentType::kCubic) {
return false;
}
auto point_index = components_[index].index;
cubic =
CubicPathComponent(points_[point_index], points_[point_index + 1],
points_[point_index + 2], points_[point_index + 3]);
return true;
}
bool Path::GetContourComponentAtIndex(size_t index,
ContourComponent& move) const {
if (index >= components_.size()) {
return false;
}
if (components_[index].type != ComponentType::kContour) {
return false;
}
move = contours_[components_[index].index];
return true;
}
Path::Polyline::Polyline(Path::Polyline::PointBufferPtr point_buffer,
Path::Polyline::ReclaimPointBufferCallback reclaim)
: points(std::move(point_buffer)), reclaim_points_(std::move(reclaim)) {
FML_DCHECK(points);
}
Path::Polyline::Polyline(Path::Polyline&& other) {
points = std::move(other.points);
reclaim_points_ = std::move(other.reclaim_points_);
contours = std::move(other.contours);
}
Path::Polyline::~Polyline() {
if (reclaim_points_) {
points->clear();
reclaim_points_(std::move(points));
}
}
Path::Polyline Path::CreatePolyline(
Scalar scale,
Path::Polyline::PointBufferPtr point_buffer,
Path::Polyline::ReclaimPointBufferCallback reclaim) const {
Polyline polyline(std::move(point_buffer), std::move(reclaim));
auto get_path_component = [this](size_t component_i) -> PathComponentVariant {
if (component_i >= components_.size()) {
return std::monostate{};
}
const auto& component = components_[component_i];
switch (component.type) {
case ComponentType::kLinear:
return reinterpret_cast<const LinearPathComponent*>(
&points_[component.index]);
case ComponentType::kQuadratic:
return reinterpret_cast<const QuadraticPathComponent*>(
&points_[component.index]);
case ComponentType::kCubic:
return reinterpret_cast<const CubicPathComponent*>(
&points_[component.index]);
case ComponentType::kContour:
return std::monostate{};
}
};
auto compute_contour_start_direction =
[&get_path_component](size_t current_path_component_index) {
size_t next_component_index = current_path_component_index + 1;
while (!std::holds_alternative<std::monostate>(
get_path_component(next_component_index))) {
auto next_component = get_path_component(next_component_index);
auto maybe_vector =
std::visit(PathComponentStartDirectionVisitor(), next_component);
if (maybe_vector.has_value()) {
return maybe_vector.value();
} else {
next_component_index++;
}
}
return Vector2(0, -1);
};
std::vector<PolylineContour::Component> components;
std::optional<size_t> previous_path_component_index;
auto end_contour = [&polyline, &previous_path_component_index,
&get_path_component, &components]() {
// Whenever a contour has ended, extract the exact end direction from
// the last component.
if (polyline.contours.empty()) {
return;
}
if (!previous_path_component_index.has_value()) {
return;
}
auto& contour = polyline.contours.back();
contour.end_direction = Vector2(0, 1);
contour.components = components;
components.clear();
size_t previous_index = previous_path_component_index.value();
while (!std::holds_alternative<std::monostate>(
get_path_component(previous_index))) {
auto previous_component = get_path_component(previous_index);
auto maybe_vector =
std::visit(PathComponentEndDirectionVisitor(), previous_component);
if (maybe_vector.has_value()) {
contour.end_direction = maybe_vector.value();
break;
} else {
if (previous_index == 0) {
break;
}
previous_index--;
}
}
};
for (size_t component_i = 0; component_i < components_.size();
component_i++) {
const auto& component = components_[component_i];
switch (component.type) {
case ComponentType::kLinear:
components.push_back({
.component_start_index = polyline.points->size() - 1,
.is_curve = false,
});
reinterpret_cast<const LinearPathComponent*>(&points_[component.index])
->AppendPolylinePoints(*polyline.points);
previous_path_component_index = component_i;
break;
case ComponentType::kQuadratic:
components.push_back({
.component_start_index = polyline.points->size() - 1,
.is_curve = true,
});
reinterpret_cast<const QuadraticPathComponent*>(
&points_[component.index])
->AppendPolylinePoints(scale, *polyline.points);
previous_path_component_index = component_i;
break;
case ComponentType::kCubic:
components.push_back({
.component_start_index = polyline.points->size() - 1,
.is_curve = true,
});
reinterpret_cast<const CubicPathComponent*>(&points_[component.index])
->AppendPolylinePoints(scale, *polyline.points);
previous_path_component_index = component_i;
break;
case ComponentType::kContour:
if (component_i == components_.size() - 1) {
// If the last component is a contour, that means it's an empty
// contour, so skip it.
continue;
}
end_contour();
Vector2 start_direction = compute_contour_start_direction(component_i);
const auto& contour = contours_[component.index];
polyline.contours.push_back({.start_index = polyline.points->size(),
.is_closed = contour.is_closed,
.start_direction = start_direction,
.components = components});
polyline.points->push_back(contour.destination);
break;
}
}
end_contour();
return polyline;
}
std::optional<Rect> Path::GetBoundingBox() const {
return computed_bounds_;
}
void Path::ComputeBounds() {
auto min_max = GetMinMaxCoveragePoints();
if (!min_max.has_value()) {
computed_bounds_ = std::nullopt;
return;
}
auto min = min_max->first;
auto max = min_max->second;
const auto difference = max - min;
computed_bounds_ = Rect::MakeXYWH(min.x, min.y, difference.x, difference.y);
}
std::optional<Rect> Path::GetTransformedBoundingBox(
const Matrix& transform) const {
auto bounds = GetBoundingBox();
if (!bounds.has_value()) {
return std::nullopt;
}
return bounds->TransformBounds(transform);
}
std::optional<std::pair<Point, Point>> Path::GetMinMaxCoveragePoints() const {
if (points_.empty()) {
return std::nullopt;
}
std::optional<Point> min, max;
auto clamp = [&min, &max](const Point& point) {
if (min.has_value()) {
min = min->Min(point);
} else {
min = point;
}
if (max.has_value()) {
max = max->Max(point);
} else {
max = point;
}
};
for (const auto& component : components_) {
switch (component.type) {
case ComponentType::kLinear: {
auto* linear = reinterpret_cast<const LinearPathComponent*>(
&points_[component.index]);
clamp(linear->p1);
clamp(linear->p2);
break;
}
case ComponentType::kQuadratic:
for (const auto& extrema :
reinterpret_cast<const QuadraticPathComponent*>(
&points_[component.index])
->Extrema()) {
clamp(extrema);
}
break;
case ComponentType::kCubic:
for (const auto& extrema : reinterpret_cast<const CubicPathComponent*>(
&points_[component.index])
->Extrema()) {
clamp(extrema);
}
break;
case ComponentType::kContour:
break;
}
}
if (!min.has_value() || !max.has_value()) {
return std::nullopt;
}
return std::make_pair(min.value(), max.value());
}
void Path::SetBounds(Rect rect) {
computed_bounds_ = rect;
}
} // namespace impeller