use crate::fig::Fig;
use crate::geom::{float_lerp, Pt, Transform, WidePt};
use crate::path::{FillRule, PathOp};
use crate::stroker::{JoinStyle, Stroke};
use pix::chan::{Ch8, Linear, Premultiplied};
use pix::el::Pixel;
use pix::Raster;
use std::borrow::Borrow;
pub struct Plotter<P>
where
P: Pixel<Chan = Ch8, Alpha = Premultiplied, Gamma = Linear>,
{
raster: Raster<P>,
sgn_area: Vec<i16>,
pen: WidePt,
transform: Transform,
tol_sq: f32,
s_width: f32,
join_style: JoinStyle,
}
trait PlotDest {
fn add_point(&mut self, pt: WidePt);
fn close(&mut self, joined: bool);
}
impl PlotDest for Fig {
fn add_point(&mut self, pt: WidePt) {
Fig::add_point(self, pt.0);
}
fn close(&mut self, _joined: bool) {
Fig::close(self);
}
}
impl PlotDest for Stroke {
fn add_point(&mut self, pt: WidePt) {
Stroke::add_point(self, pt);
}
fn close(&mut self, joined: bool) {
Stroke::close(self, joined);
}
}
impl<P> Plotter<P>
where
P: Pixel<Chan = Ch8, Alpha = Premultiplied, Gamma = Linear>,
{
pub fn new(raster: Raster<P>) -> Self {
let tol = 0.3;
let len = raster.width() as usize;
let cap = ((len + 7) >> 3) << 3;
let mut sgn_area = vec![0; cap];
for _ in 0..cap - len {
sgn_area.pop();
}
Plotter {
raster,
sgn_area,
pen: WidePt::default(),
transform: Transform::default(),
tol_sq: tol * tol,
s_width: 1.0,
join_style: JoinStyle::Miter(4.0),
}
}
pub fn width(&self) -> u32 {
self.raster.width()
}
pub fn height(&self) -> u32 {
self.raster.height()
}
fn reset(&mut self) {
self.pen = WidePt(Pt::default(), self.s_width);
}
pub fn set_tolerance(&mut self, t: f32) -> &mut Self {
let tol = t.max(0.01);
self.tol_sq = tol * tol;
self
}
pub fn set_transform(&mut self, t: Transform) -> &mut Self {
self.transform = t;
self
}
fn pen_width(&mut self, width: f32) {
self.s_width = width;
}
pub fn set_join(&mut self, js: JoinStyle) -> &mut Self {
self.join_style = js;
self
}
fn move_pen(&mut self, p: WidePt) {
self.pen = p;
}
fn transform_point(&self, p: WidePt) -> WidePt {
let pt = self.transform * p.0;
WidePt(pt, p.w())
}
fn add_ops<T, D>(&mut self, ops: T, dst: &mut D)
where
T: IntoIterator,
T::Item: Borrow<PathOp>,
D: PlotDest,
{
self.reset();
for op in ops {
self.add_op(dst, op.borrow());
}
}
fn add_op<D: PlotDest>(&mut self, dst: &mut D, op: &PathOp) {
match *op {
PathOp::Close() => self.close(dst),
PathOp::Move(pb) => self.move_to(dst, pb),
PathOp::Line(pb) => self.line_to(dst, pb),
PathOp::Quad(pb, pc) => self.quad_to(dst, pb, pc),
PathOp::Cubic(pb, pc, pd) => self.cubic_to(dst, pb, pc, pd),
PathOp::PenWidth(w) => self.pen_width(w),
};
}
fn close<D: PlotDest>(&mut self, dst: &mut D) {
dst.close(true);
self.reset();
}
fn move_to<D: PlotDest>(&mut self, dst: &mut D, pb: Pt) {
let p = WidePt(pb, self.s_width);
dst.close(false);
let b = self.transform_point(p);
dst.add_point(b);
self.move_pen(p);
}
fn line_to<D: PlotDest>(&mut self, dst: &mut D, pb: Pt) {
let p = WidePt(pb, self.s_width);
let b = self.transform_point(p);
dst.add_point(b);
self.move_pen(p);
}
fn quad_to<D: PlotDest>(&mut self, dst: &mut D, cp: Pt, end: Pt) {
let pen = self.pen;
let bb = WidePt(cp, (pen.w() + self.s_width) / 2.0);
let cc = WidePt(end, self.s_width);
let a = self.transform_point(pen);
let b = self.transform_point(bb);
let c = self.transform_point(cc);
self.quad_to_tran(dst, a, b, c);
self.move_pen(cc);
}
fn quad_to_tran<D: PlotDest>(
&self,
dst: &mut D,
a: WidePt,
b: WidePt,
c: WidePt,
) {
let ab = a.midpoint(b);
let bc = b.midpoint(c);
let ab_bc = ab.midpoint(bc);
let ac = a.midpoint(c);
if self.is_within_tolerance(ab_bc, ac) {
dst.add_point(c);
} else {
self.quad_to_tran(dst, a, ab, ab_bc);
self.quad_to_tran(dst, ab_bc, bc, c);
}
}
fn is_within_tolerance(&self, a: WidePt, b: WidePt) -> bool {
self.is_within_tolerance2(a.0, b.0)
}
fn is_within_tolerance2(&self, a: Pt, b: Pt) -> bool {
assert!(self.tol_sq > 0.0);
a.dist_sq(b) <= self.tol_sq
}
fn cubic_to<D: PlotDest>(
&mut self,
dst: &mut D,
cp0: Pt,
cp1: Pt,
end: Pt,
) {
let pen = self.pen;
let w0 = float_lerp(pen.w(), self.s_width, 1.0 / 3.0);
let w1 = float_lerp(pen.w(), self.s_width, 2.0 / 3.0);
let bb = WidePt(cp0, w0);
let cc = WidePt(cp1, w1);
let dd = WidePt(end, self.s_width);
let a = self.transform_point(pen);
let b = self.transform_point(bb);
let c = self.transform_point(cc);
let d = self.transform_point(dd);
self.cubic_to_tran(dst, a, b, c, d);
self.move_pen(dd);
}
fn cubic_to_tran<D: PlotDest>(
&self,
dst: &mut D,
pa: WidePt,
pb: WidePt,
pc: WidePt,
pd: WidePt,
) {
let ab = pa.midpoint(pb);
let bc = pb.midpoint(pc);
let cd = pc.midpoint(pd);
let ab_bc = ab.midpoint(bc);
let bc_cd = bc.midpoint(cd);
let pe = ab_bc.midpoint(bc_cd);
let ad = pa.midpoint(pd);
if self.is_within_tolerance(pe, ad) {
dst.add_point(pd);
} else {
self.cubic_to_tran(dst, pa, ab, ab_bc, pe);
self.cubic_to_tran(dst, pe, bc_cd, cd, pd);
}
}
pub fn fill<T>(&mut self, rule: FillRule, ops: T, clr: P) -> &mut Raster<P>
where
T: IntoIterator,
T::Item: Borrow<PathOp>,
{
let mut fig = Fig::new();
self.add_ops(ops, &mut fig);
fig.close();
fig.fill(rule, &mut self.raster, clr, &mut self.sgn_area[..]);
&mut self.raster
}
pub fn stroke<T>(&mut self, ops: T, clr: P) -> &mut Raster<P>
where
T: IntoIterator,
T::Item: Borrow<PathOp>,
{
let mut stroke = Stroke::new(self.join_style, self.tol_sq);
self.add_ops(ops, &mut stroke);
let ops = stroke.path_ops();
self.fill(FillRule::NonZero, ops.iter(), clr)
}
pub fn raster(self) -> Raster<P> {
self.raster
}
}
#[cfg(test)]
mod test {
use crate::*;
use pix::matte::Matte8;
use pix::Raster;
#[test]
fn overlapping() {
let path = Path2D::default()
.absolute()
.move_to(8.0, 4.0)
.line_to(8.0, 3.0)
.cubic_to(8.0, 3.0, 8.0, 3.0, 9.0, 3.75)
.line_to(8.0, 3.75)
.line_to(8.5, 3.75)
.line_to(8.5, 3.5)
.finish();
let r = Raster::with_clear(16, 16);
let mut p = Plotter::new(r);
p.fill(FillRule::NonZero, &path, Matte8::new(255));
}
}