blob: ab5ed9be7f65a75aaa9975920a4aac4a8e909c1c [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() : data_(new Data()) {}
Path::Path(Data data) : data_(std::make_shared<Data>(std::move(data))) {}
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 data_->components.size();
}
auto type_value = type.value();
if (type_value == ComponentType::kContour) {
return data_->contours.size();
}
size_t count = 0u;
for (const auto& component : data_->components) {
if (component.type == type_value) {
count++;
}
}
return count;
}
FillType Path::GetFillType() const {
return data_->fill;
}
bool Path::IsConvex() const {
return data_->convexity == Convexity::kConvex;
}
bool Path::IsEmpty() const {
return data_->points.empty();
}
void Path::EnumerateComponents(
const Applier<LinearPathComponent>& linear_applier,
const Applier<QuadraticPathComponent>& quad_applier,
const Applier<CubicPathComponent>& cubic_applier,
const Applier<ContourComponent>& contour_applier) const {
auto& points = data_->points;
size_t currentIndex = 0;
for (const auto& component : data_->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, data_->contours[component.index]);
}
break;
}
currentIndex++;
}
}
bool Path::GetLinearComponentAtIndex(size_t index,
LinearPathComponent& linear) const {
auto& components = data_->components;
if (index >= components.size()) {
return false;
}
if (components[index].type != ComponentType::kLinear) {
return false;
}
auto& points = data_->points;
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 {
auto& components = data_->components;
if (index >= components.size()) {
return false;
}
if (components[index].type != ComponentType::kQuadratic) {
return false;
}
auto& points = data_->points;
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 {
auto& components = data_->components;
if (index >= components.size()) {
return false;
}
if (components[index].type != ComponentType::kCubic) {
return false;
}
auto& points = data_->points;
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 {
auto& components = data_->components;
if (index >= components.size()) {
return false;
}
if (components[index].type != ComponentType::kContour) {
return false;
}
move = data_->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& path_components = data_->components;
auto& path_points = data_->points;
auto get_path_component = [&path_components, &path_points](
size_t component_i) -> PathComponentVariant {
if (component_i >= path_components.size()) {
return std::monostate{};
}
const auto& component = path_components[component_i];
switch (component.type) {
case ComponentType::kLinear:
return reinterpret_cast<const LinearPathComponent*>(
&path_points[component.index]);
case ComponentType::kQuadratic:
return reinterpret_cast<const QuadraticPathComponent*>(
&path_points[component.index]);
case ComponentType::kCubic:
return reinterpret_cast<const CubicPathComponent*>(
&path_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> poly_components;
std::optional<size_t> previous_path_component_index;
auto end_contour = [&polyline, &previous_path_component_index,
&get_path_component, &poly_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 = poly_components;
poly_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 < path_components.size();
component_i++) {
const auto& path_component = path_components[component_i];
switch (path_component.type) {
case ComponentType::kLinear:
poly_components.push_back({
.component_start_index = polyline.points->size() - 1,
.is_curve = false,
});
reinterpret_cast<const LinearPathComponent*>(
&path_points[path_component.index])
->AppendPolylinePoints(*polyline.points);
previous_path_component_index = component_i;
break;
case ComponentType::kQuadratic:
poly_components.push_back({
.component_start_index = polyline.points->size() - 1,
.is_curve = true,
});
reinterpret_cast<const QuadraticPathComponent*>(
&path_points[path_component.index])
->AppendPolylinePoints(scale, *polyline.points);
previous_path_component_index = component_i;
break;
case ComponentType::kCubic:
poly_components.push_back({
.component_start_index = polyline.points->size() - 1,
.is_curve = true,
});
reinterpret_cast<const CubicPathComponent*>(
&path_points[path_component.index])
->AppendPolylinePoints(scale, *polyline.points);
previous_path_component_index = component_i;
break;
case ComponentType::kContour:
if (component_i == path_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 = data_->contours[path_component.index];
polyline.contours.push_back({.start_index = polyline.points->size(),
.is_closed = contour.is_closed,
.start_direction = start_direction,
.components = poly_components});
polyline.points->push_back(contour.destination);
break;
}
}
end_contour();
return polyline;
}
std::optional<Rect> Path::GetBoundingBox() const {
return data_->bounds;
}
std::optional<Rect> Path::GetTransformedBoundingBox(
const Matrix& transform) const {
auto bounds = GetBoundingBox();
if (!bounds.has_value()) {
return std::nullopt;
}
return bounds->TransformBounds(transform);
}
void Path::WritePolyline(Scalar scale, VertexWriter& writer) const {
auto& path_components = data_->components;
auto& path_points = data_->points;
for (size_t component_i = 0; component_i < path_components.size();
component_i++) {
const auto& path_component = path_components[component_i];
switch (path_component.type) {
case ComponentType::kLinear: {
const LinearPathComponent* linear =
reinterpret_cast<const LinearPathComponent*>(
&path_points[path_component.index]);
writer.Write(linear->p2);
break;
}
case ComponentType::kQuadratic: {
const QuadraticPathComponent* quad =
reinterpret_cast<const QuadraticPathComponent*>(
&path_points[path_component.index]);
quad->ToLinearPathComponents(scale, writer);
break;
}
case ComponentType::kCubic: {
const CubicPathComponent* cubic =
reinterpret_cast<const CubicPathComponent*>(
&path_points[path_component.index]);
cubic->ToLinearPathComponents(scale, writer);
break;
}
case ComponentType::kContour:
if (component_i == path_components.size() - 1) {
// If the last component is a contour, that means it's an empty
// contour, so skip it.
continue;
}
writer.EndContour();
// Insert contour start.
const auto& next_component = path_components[component_i + 1];
// It doesn't matter what type this is as all structs are laid out
// with p1 as the first member.
const LinearPathComponent* linear =
reinterpret_cast<const LinearPathComponent*>(
&path_points[next_component.index]);
writer.Write(linear->p1);
break;
}
}
}
} // namespace impeller