use super::ColumnSampledContour;
use super::sampled_contour::*;
use super::distance_field::*;
use crate::bezier::vectorize::InterceptScanEdgeIterator;
use crate::geo::*;
use crate::bezier::*;
use crate::bezier::path::*;
use smallvec::*;
use std::collections::{HashMap};
use std::ops::{Range};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
struct ContourEdgePair(ContourEdge, ContourEdge);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum ConnectedEdges {
None,
One(ContourEdgePair),
Two(ContourEdgePair, ContourEdgePair),
}
impl ContourCell {
#[inline]
const fn connected_edges(&self) -> ConnectedEdges {
match self.0 {
0 | 15 => { ConnectedEdges::None }
1 => { ConnectedEdges::One(ContourEdgePair(ContourEdge::left(), ContourEdge::top())) }
2 => { ConnectedEdges::One(ContourEdgePair(ContourEdge::top(), ContourEdge::right())) }
3 => { ConnectedEdges::One(ContourEdgePair(ContourEdge::left(), ContourEdge::right())) }
4 => { ConnectedEdges::One(ContourEdgePair(ContourEdge::left(), ContourEdge::bottom())) }
5 => { ConnectedEdges::One(ContourEdgePair(ContourEdge::top(), ContourEdge::bottom())) }
6 => { ConnectedEdges::Two(ContourEdgePair(ContourEdge::left(), ContourEdge::bottom()), ContourEdgePair(ContourEdge::top(), ContourEdge::right())) }
7 => { ConnectedEdges::One(ContourEdgePair(ContourEdge::bottom(), ContourEdge::right())) }
8 => { ConnectedEdges::One(ContourEdgePair(ContourEdge::bottom(), ContourEdge::right())) }
9 => { ConnectedEdges::Two(ContourEdgePair(ContourEdge::left(), ContourEdge::top()), ContourEdgePair(ContourEdge::bottom(), ContourEdge::right())) }
10 => { ConnectedEdges::One(ContourEdgePair(ContourEdge::top(), ContourEdge::bottom())) }
11 => { ConnectedEdges::One(ContourEdgePair(ContourEdge::left(), ContourEdge::bottom())) }
12 => { ConnectedEdges::One(ContourEdgePair(ContourEdge::left(), ContourEdge::right())) }
13 => { ConnectedEdges::One(ContourEdgePair(ContourEdge::top(), ContourEdge::right())) }
14 => { ConnectedEdges::One(ContourEdgePair(ContourEdge::left(), ContourEdge::top())) }
_ => { unreachable!() }
}
}
}
pub fn trace_contours_from_edges(edges: impl Iterator<Item=(ContourPosition, ContourCell)>, contour_size: ContourSize) -> Vec<Vec<ContourEdge>> {
let mut edge_graph = HashMap::<_, SmallVec<[_; 2]>>::new();
for (pos, cell) in edges {
match cell.connected_edges() {
ConnectedEdges::None => { }
ConnectedEdges::One(ContourEdgePair(a, b)) => {
let a = a.at_coordinates(contour_size, pos);
let b = b.at_coordinates(contour_size, pos);
edge_graph.entry(a).or_insert_with(|| smallvec![]).push(b);
edge_graph.entry(b).or_insert_with(|| smallvec![]).push(a);
}
ConnectedEdges::Two(ContourEdgePair(a, b), ContourEdgePair(c, d)) => {
let a = a.at_coordinates(contour_size, pos);
let b = b.at_coordinates(contour_size, pos);
let c = c.at_coordinates(contour_size, pos);
let d = d.at_coordinates(contour_size, pos);
edge_graph.entry(a).or_insert_with(|| smallvec![]).push(b);
edge_graph.entry(b).or_insert_with(|| smallvec![]).push(a);
edge_graph.entry(c).or_insert_with(|| smallvec![]).push(d);
edge_graph.entry(d).or_insert_with(|| smallvec![]).push(c);
}
}
}
let mut result = vec![];
loop {
let (first_edge, next_edge) = if let Some((initial_edge, following_edges)) = edge_graph.iter().next() {
(*initial_edge, following_edges[0])
} else {
break;
};
let mut edge_loop = vec![first_edge];
let mut previous_edge = first_edge;
let mut current_edge = next_edge;
edge_graph.remove(&first_edge);
while current_edge != first_edge {
edge_loop.push(current_edge);
let following = edge_graph.remove(¤t_edge).unwrap();
let next_edge = if following[0] != previous_edge {
following[0]
} else {
following[1]
};
previous_edge = current_edge;
current_edge = next_edge;
}
edge_loop.push(edge_loop[0]);
result.push(edge_loop);
}
result
}
#[inline]
pub fn trace_contours_from_samples(contours: &impl SampledContour) -> Vec<Vec<ContourEdge>> {
trace_contours_from_edges(contours.edge_cell_iterator(), contours.contour_size())
}
#[inline]
fn find_intercept(intercepts: &SmallVec<[Range<f64>; 4]>, initial_estimate: usize) -> f64 {
let initial_estimate = initial_estimate as f64;
let mut estimate = initial_estimate;
let mut distance = f64::MAX;
for range in intercepts.iter() {
let start_distance = (initial_estimate - range.start).abs();
let end_distance = (initial_estimate - range.end).abs();
if start_distance < distance {
estimate = range.start;
distance = start_distance;
}
if end_distance < distance {
estimate = range.end;
distance = end_distance;
}
}
if distance > 2.0 {
estimate = initial_estimate;
}
estimate
}
fn remove_overlapping_points(coords: &mut Vec<impl Coordinate>) {
const MIN_DISTANCE: f64 = 0.2;
const MIN_DISTANCE_SQ: f64 = MIN_DISTANCE * MIN_DISTANCE;
let mut keep = None;
let mut previous_idx = 0;
let mut idx = 1;
while idx < coords.len() {
let p1 = &coords[previous_idx];
let p2 = &coords[idx];
let offset = *p1-*p2;
let distance_sq = offset.dot(&offset);
if distance_sq < MIN_DISTANCE_SQ {
let keep = if let Some(keep) = &mut keep {
keep
} else {
keep = Some(vec![true; coords.len()]);
keep.as_mut().unwrap()
};
keep[idx] = false;
} else {
previous_idx = idx;
}
idx += 1;
}
if let Some(keep) = keep {
let mut keep = keep.into_iter();
coords.retain(|_| {
keep.next() == Some(true)
})
}
}
pub fn trace_contours_from_intercepts<TCoord>(contours: &impl ColumnSampledContour) -> Vec<Vec<TCoord>>
where
TCoord: Coordinate + Coordinate2D,
{
let contour_size = contours.contour_size();
let width = contour_size.width();
let height = contour_size.height();
let line_intercepts = (0..height).map(|y| contours.intercepts_on_line(y as _)).collect::<Vec<_>>();
let column_intercepts = (0..width).map(|x| contours.intercepts_on_column(x as _)).collect::<Vec<_>>();
let edges = InterceptScanEdgeIterator::from_iterator(line_intercepts.iter()
.map(|intercepts| {
let intercepts = intercepts
.into_iter()
.map(|intercept| {
let min_x_ceil = intercept.start.ceil();
let max_x_ceil = intercept.end.ceil();
let min_x = min_x_ceil as usize;
let max_x = max_x_ceil as usize;
min_x..max_x
})
.filter(|intercept| intercept.start != intercept.end)
.collect::<SmallVec<_>>();
if intercepts.len() <= 1 {
intercepts
} else {
merge_overlapping_intercepts(intercepts)
}
}));
let edges = trace_contours_from_edges(edges, contour_size);
let adjusted_edges = edges.into_iter()
.map(|edge_loop| {
edge_loop.into_iter()
.map(|edge| {
let (_from, to) = edge.to_contour_coords(contour_size);
if edge.is_vertical() {
let ypos = find_intercept(&column_intercepts[to.x()-1], to.y()-1);
TCoord::from_components(&[to.x() as f64, ypos + 1.0])
} else {
let xpos = find_intercept(&line_intercepts[to.y()-1], to.x()-1);
TCoord::from_components(&[xpos + 1.0, to.y() as f64])
}
})
.collect()
}).map(|mut coords| { remove_overlapping_points(&mut coords); coords });
adjusted_edges.collect()
}
pub fn trace_paths_from_samples<TPathFactory>(contours: &impl SampledContour, accuracy: f64) -> Vec<TPathFactory>
where
TPathFactory: BezierPathFactory,
TPathFactory::Point: Coordinate + Coordinate2D,
{
let contour_size = contours.contour_size();
let contours = trace_contours_from_samples(contours);
contours.into_iter()
.map(|edges| edges.into_iter().map(|edge| edge.to_coords(contour_size)).collect::<Vec<_>>())
.filter_map(|points| {
let curves = fit_curve_loop::<Curve<TPathFactory::Point>>(&points, accuracy)?;
Some(TPathFactory::from_points(curves[0].start_point(), curves.into_iter().map(|curve| {
let (cp1, cp2) = curve.control_points();
let end_point = curve.end_point();
(cp1, cp2, end_point)
})))
})
.collect()
}
pub fn trace_contours_from_distance_field<TCoord>(distance_field: &impl SampledSignedDistanceField) -> Vec<Vec<TCoord>>
where
TCoord: Coordinate + Coordinate2D,
{
let field_size = distance_field.field_size();
let contours = distance_field.as_contour();
let loops = trace_contours_from_samples(contours);
#[inline]
fn edge_coord_to_field_coord(pos: ContourPosition) -> ContourPosition {
ContourPosition(pos.0-1, pos.1-1)
}
loops.into_iter()
.map(|edge_loop| {
edge_loop.into_iter()
.map(|edge| {
let (from, to) = edge.to_contour_coords(field_size);
let from_distance = if from.0 > 0 && from.1 > 0 { distance_field.distance_at_point(edge_coord_to_field_coord(from)) } else { f64::MAX };
let to_distance = if to.0 > 0 && to.1 > 0 { distance_field.distance_at_point(edge_coord_to_field_coord(to)) } else { f64::MAX };
let zero_point = if from_distance != to_distance {
from_distance / (from_distance - to_distance)
} else {
0.5
};
let zero_point = zero_point.max(-1.0).min(1.0);
let x = ((to.0 as f64) - (from.0 as f64)) * zero_point + (from.0 as f64);
let y = ((to.1 as f64) - (from.1 as f64)) * zero_point + (from.1 as f64);
debug_assert!(!x.is_nan() && !y.is_nan());
TCoord::from_components(&[x, y])
}).collect()
})
.map(|mut coords| { remove_overlapping_points(&mut coords); coords })
.collect()
}
pub fn trace_paths_from_intercepts<TPathFactory>(contours: &impl ColumnSampledContour, accuracy: f64) -> Vec<TPathFactory>
where
TPathFactory: BezierPathFactory,
TPathFactory::Point: Coordinate + Coordinate2D,
{
let contours = trace_contours_from_intercepts(contours);
contours.into_iter()
.filter_map(|points| {
let curves = fit_curve_loop::<Curve<TPathFactory::Point>>(&points, accuracy)?;
Some(TPathFactory::from_points(curves[0].start_point(), curves.into_iter().map(|curve| {
let (cp1, cp2) = curve.control_points();
let end_point = curve.end_point();
(cp1, cp2, end_point)
})))
})
.collect()
}
pub fn trace_paths_from_distance_field<TPathFactory>(distance_field: &impl SampledSignedDistanceField, accuracy: f64) -> Vec<TPathFactory>
where
TPathFactory: BezierPathFactory,
TPathFactory::Point: Coordinate + Coordinate2D,
{
let contours = trace_contours_from_distance_field(distance_field);
contours.into_iter()
.filter_map(|points| {
let curves = fit_curve_loop::<Curve<TPathFactory::Point>>(&points, accuracy)?;
Some(TPathFactory::from_points(curves[0].start_point(), curves.into_iter().map(|curve| {
let (cp1, cp2) = curve.control_points();
let end_point = curve.end_point();
(cp1, cp2, end_point)
})))
})
.collect()
}