#![doc = include_str!("../README.md")]
#![no_std]
extern crate alloc;
use alloc::vec::Vec;
use vek::bezier::CubicBezier2;
use vek::vec::Vec2;
use vek::geom::Aabr;
use vek::geom::LineSegment2;
use vek::approx::RelativeEq;
use num_traits::real::Real;
use num_traits::cast::NumCast;
fn find_longest_segment<T: Real + DivByTwo, const D: usize>(
curve: &CubicBezier2<T>,
t: &mut T,
max_diff: T,
end_p: &mut Vec2<T>,
) -> bool {
let max_diff_sq = max_diff * max_diff;
let div: T = <T as NumCast>::from(D + 1).unwrap();
let start = *t;
let mut trial = T::one() - start;
*t = start + trial;
let start_p = curve.evaluate(start);
let mut to_end = true;
'outer: loop {
if trial < T::epsilon() {
*t = T::one() - start;
to_end = true;
}
*end_p = curve.evaluate(*t);
let p_unit = (*end_p - start_p) / div;
let t_unit = trial / div;
let mut p_tmp = p_unit;
let mut t_tmp = t_unit;
for _ in 0..D {
let a = start_p + p_tmp;
let b = curve.evaluate(start + t_tmp);
if a.distance_squared(b) > max_diff_sq {
trial.div_by_two();
*t = start + trial;
to_end = false;
continue 'outer;
}
p_tmp = p_tmp + p_unit;
t_tmp = t_tmp + t_unit;
}
break;
}
to_end
}
pub fn push_cubic_bezier_segments<T: Real + DivByTwo, const D: usize>(
curve: &CubicBezier2<T>,
max_diff: T,
points: &mut Vec<Vec2<T>>,
) {
points.push(curve.start);
let mut t = T::zero();
let mut end_p = Vec2::zero();
let mut to_end = false;
while !to_end {
to_end = find_longest_segment::<_, D>(curve, &mut t, max_diff, &mut end_p);
points.push(end_p);
}
}
fn determinant<T: Real>(a: &Vec2<T>, b: &Vec2<T>) -> T {
a.x * b.y - a.y * b.x
}
fn aabr<T: Real>(start: Vec2<T>, end: Vec2<T>, offset: isize) -> [isize; 4] {
let aabr = Aabr::<T> {
min: Vec2::partial_min(start, end),
max: Vec2::partial_max(start, end),
};
[
aabr.min.x.to_isize().unwrap() - offset,
aabr.min.y.to_isize().unwrap() - offset,
aabr.max.x.to_isize().unwrap() + offset,
aabr.max.y.to_isize().unwrap() + offset,
]
}
fn is_inside<T: Real>(p: Vec2<T>, path: &[Vec2<T>]) -> bool {
let mut winding_number = 0isize;
for segment in path.windows(2) {
let (s, e) = (segment[0], segment[1]);
let v1 = p - s;
let v2 = e - s;
let d = determinant(&v1, &v2);
if s.y <= p.y {
if e.y > p.y && d > T::epsilon() {
winding_number -= 1;
}
} else {
if e.y <= p.y && d < T::epsilon() {
winding_number += 1;
}
}
}
winding_number != 0
}
fn ssaa_point<T: Real, const SSAA: usize>(x: isize, y: isize, sx: usize, sy: usize) -> Vec2<T> {
let sub_px_width = T::one() / T::from(SSAA).unwrap();
let half_sub_px_width = sub_px_width / T::from(2).unwrap();
let x_offset = sub_px_width * T::from(sx).unwrap() + half_sub_px_width;
let y_offset = sub_px_width * T::from(sy).unwrap() + half_sub_px_width;
Vec2 {
x: T::from(x).unwrap() + x_offset,
y: T::from(y).unwrap() + y_offset,
}
}
fn ssaa_average
<T: Real, F: Fn(Vec2<T>) -> bool, const SSAA: usize>
(x: isize, y: isize, condition: F) -> u8
{
let mut in_count = 0;
for sx in 0..SSAA {
for sy in 0..SSAA {
let p = ssaa_point::<_, SSAA>(x, y, sx, sy);
if condition(p) {
in_count += 1;
}
}
}
((255 * in_count) / (SSAA * SSAA)) as u8
}
pub fn stroke<T: Real + RelativeEq, const SSAA: usize>(
path: &[Vec2<T>],
mask: &mut [u8],
mask_size: Vec2<usize>,
line_width: T,
) {
mask.fill(0);
let half_stroke_width = line_width / T::from(2).unwrap();
let half_stroke_width_sq = half_stroke_width.powi(2);
let offset = line_width.to_isize().unwrap() + 2;
let w = mask_size.x as isize;
let h = mask_size.y as isize;
for segment in path.windows(2) {
let (start, end) = (segment[0], segment[1]);
let segment = LineSegment2 {
start,
end,
};
let is_close_enough = |p| {
let projected = segment.projected_point(p);
let distance_sq = projected.distance_squared(p);
distance_sq <= half_stroke_width_sq
};
let [min_x_px, min_y_px, max_x_px, max_y_px] = aabr(start, end, offset);
let mut accumulator_x = (min_y_px as usize) * mask_size.x;
for y_px in min_y_px..max_y_px {
if !(0..h).contains(&y_px) {
continue;
}
let cov_line = &mut mask[accumulator_x..][..mask_size.x];
let mut past = false;
for x_px in min_x_px..max_x_px {
let x = x_px as usize;
if (0..w).contains(&x_px) {
let avg = ssaa_average::<_, _, SSAA>(x_px, y_px, is_close_enough);
if cov_line[x] < avg {
cov_line[x] = avg;
past = true;
}
if avg == 0 && past {
break;
}
} else {
break;
}
}
accumulator_x += mask_size.x;
}
}
}
pub fn fill<T: Real + RelativeEq, const SSAA: usize>(
path: &[Vec2<T>],
mask: &mut [u8],
mask_size: Vec2<usize>,
) {
mask.fill(0);
let w = mask_size.x as isize;
let h = mask_size.y as isize;
for segment in path.windows(2) {
let (start, end) = (segment[0], segment[1]);
let [min_x_px, min_y_px, max_x_px, max_y_px] = aabr(start, end, 3);
let mut accumulator_x = (min_y_px as usize) * mask_size.x;
for y_px in min_y_px..max_y_px {
if !(0..h).contains(&y_px) {
continue;
}
let cov_line = &mut mask[accumulator_x..][..mask_size.x];
for x_px in min_x_px..max_x_px {
if (0..w).contains(&x_px) {
let x = x_px as usize;
if cov_line[x] == 0 {
cov_line[x] = 255;
}
}
}
accumulator_x += mask_size.x;
}
}
let is_inside_path = |p| is_inside(p, path);
for y in 0..mask_size.y {
let i = y * mask_size.x;
let line = &mut mask[i..][..mask_size.x];
let mut go_back = 0;
for x in 0..mask_size.x {
if line[x] == 0 {
go_back += 1;
} else {
let avg = ssaa_average::<_, _, SSAA>(x as isize, y as isize, is_inside_path);
line[x] = avg;
for i in 1..=go_back {
line[x - i] = avg;
}
go_back = 0;
}
}
}
}
pub trait DivByTwo {
fn div_by_two(&mut self);
}
impl DivByTwo for f32 {
fn div_by_two(&mut self) {
*self /= 2.0;
}
}
impl DivByTwo for f64 {
fn div_by_two(&mut self) {
*self /= 2.0;
}
}