use crate::{ colors::{self, blend, lerp, Color, ColorTable}, commands::{Command, Point, Rectangle, Style, Vector}, header::TvgHeader, path::{Instruction, InstructionData, Path, Sweep}, TvgFile, }; fn distance(p0: Point, p1: Point) -> f64 { (p0 - p1).magnitude().abs() } fn rotation_matrix(angle: f64) -> [[f64; 2]; 2] { let s = angle.sin(); let c = angle.cos(); [[c, -s], [s, c]] } fn apply_matrix(matrix: [[f64; 2]; 2], vector: Vector) -> Vector { Vector { x: vector.x * matrix[0][0] + vector.y * matrix[0][1], y: vector.x * matrix[1][0] + vector.y * matrix[1][1], } } fn apply_matrix_point(matrix: [[f64; 2]; 2], point: Point) -> Point { Point { x: point.x * matrix[0][0] + point.y * matrix[0][1], y: point.x * matrix[1][0] + point.y * matrix[1][1], } } fn lerp_f64(a: f64, b: f64, x: f64) -> f64 { a + (b - a) * x } fn lerp_and_reduce(vals: &[f64], f: f64) -> Vec { let mut result = Vec::with_capacity(vals.len() - 1); for i in 0..(vals.len() - 1) { result.push(lerp_f64(vals[i], vals[i + 1], f)); } result } fn lerp_and_reduce_to_one(vals: &[f64], f: f64) -> f64 { if vals.len() == 1 { vals[0] } else { lerp_and_reduce_to_one(&lerp_and_reduce(vals, f), f) } } /// A version of [`Point`] that uses usizes for a PixMap #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] struct UPoint { x: usize, y: usize, } impl UPoint { fn new(x: usize, y: usize) -> UPoint { Self { x, y } } } struct FrameBuffer { width: usize, height: usize, pixels: Box<[C]>, scale_x: f64, scale_y: f64, } impl FrameBuffer { fn new(width: usize, height: usize, scale_x: f64, scale_y: f64) -> Self { let pixel_count = width * height; Self { width, height, pixels: vec![C::default(); pixel_count].into_boxed_slice(), scale_x, scale_y, } } fn point_to_upoint(&self, point: Point) -> UPoint { todo!() } fn upoint_to_point(&self, upoint: UPoint) -> Point { todo!() } /// Blends the new color with the given pixel. Returns `None` if the destination is invalid fn set_pixel(&mut self, x: usize, y: usize, color: C) -> Option<()> { if x >= self.width || y >= self.height { return None; } let offset = y * self.width + x; let destination_pixel = self.pixels.get_mut(offset)?; blend(destination_pixel, &color); Some(()) } fn draw_line( &mut self, color_table: ColorTable, style: Style, width_start: f64, width_end: f64, line_start: Point, line_end: Point, ) -> Option<()> { let mut min_x = usize::MAX; let mut min_y = usize::MAX; let mut max_x = usize::MIN; let mut max_y = usize::MIN; let max_width = f64::max(width_start, width_end); let points = [line_start, line_end]; for pt in points { min_x = usize::min(min_x, (self.scale_x * (pt.x - max_width)).floor() as usize); min_y = usize::min(min_y, (self.scale_y * (pt.y - max_width)).floor() as usize); max_x = usize::max(max_x, (self.scale_x * (pt.x - max_width)).floor() as usize); max_y = usize::max(max_y, (self.scale_y * (pt.y - max_width)).floor() as usize); } // limit to framebuffer size min_x = usize::min(min_x, self.width); min_y = usize::min(min_y, self.height); max_x = usize::min(max_x, self.width); max_y = usize::min(max_y, self.height); for y in min_y..=max_y { for x in min_x..=max_x { let point = self.upoint_to_point(UPoint { x, y }); let dist = todo!() as f64; if dist >= 0.0 { self.set_pixel(x, y, style.get_color_at(&color_table, point)?); } } } Some(()) } } #[derive(Debug, Default)] struct PathMaker { points: Vec, width: Vec, } impl PathMaker { fn new(width: f64, start: Point) -> Self { let points = vec![start]; let width = vec![width]; Self { points, width } } fn render_line(&mut self, width: f64, to_point: Point) { self.points.push(to_point); self.width.push(width); } fn render_horizontal_line(&mut self, width: f64, x: f64) { self.points.push(Point { x, y: self.points.last().unwrap().y, }); self.width.push(width); } fn render_vertical_line(&mut self, width: f64, y: f64) { self.points.push(Point { x: self.points.last().unwrap().x, y, }); self.width.push(width); } fn render_cubic_bezier( &mut self, control_0: Point, control_1: Point, point: Point, start_width: f64, end_width: f64, ) { const BEZIER_POLY_COUNT: usize = 16; let previous = self.points.last().unwrap(); let oct0x = [previous.x, control_0.x, control_1.x, point.x]; let oct0y = [previous.y, control_0.y, control_1.y, point.y]; for i in 1..BEZIER_POLY_COUNT { let f = i as f64 / BEZIER_POLY_COUNT as f64; let x = lerp_and_reduce_to_one(&oct0x, f); let y = lerp_and_reduce_to_one(&oct0y, f); self.points.push(Point { x, y }); self.width.push(lerp_f64(start_width, end_width, f)); } self.points.push(point); self.width.push(end_width); } fn render_quadratic_bezier( &mut self, control: Point, target: Point, start_width: f64, end_width: f64, ) { const BEZIER_POLY_COUNT: usize = 16; let previous = self.points.last().unwrap(); let oct0x = [previous.x, control.x, target.x]; let oct0y = [previous.y, control.y, target.y]; for i in 1..BEZIER_POLY_COUNT { let f = i as f64 / BEZIER_POLY_COUNT as f64; let x = lerp_and_reduce_to_one(&oct0x, f); let y = lerp_and_reduce_to_one(&oct0y, f); self.points.push(Point { x, y }); self.width.push(lerp_f64(start_width, end_width, f)); } self.points.push(target); self.width.push(end_width); } fn render_circle( &mut self, p0: Point, p1: Point, mut radius: f64, large_arc: bool, turn_left: bool, end_width: f64, ) { const CIRCLE_POLY_COUNT: usize = 100; let start_width = *self.width.last().unwrap(); let left_side = turn_left == large_arc; let delta = (p1 - p0).scale(0.5); let midpoint = p0 + delta; let radius_vec = if left_side { Vector { x: -delta.y, y: delta.x, } } else { Vector { x: delta.y, y: -delta.x, } }; let len_squared = radius_vec.x * radius_vec.x + radius_vec.y * radius_vec.y; if (len_squared - 0.03 > radius * radius) || radius < 0.0 { radius = len_squared.sqrt(); } let to_center = radius_vec.scale(f64::max(0.0, radius * radius / len_squared - 1.0).sqrt()); let center = midpoint + to_center; let angle = len_squared.sqrt().clamp(-1.0, 1.0).asin() * 2.0; let arc = if large_arc { std::f64::consts::TAU - angle } else { angle }; let position = p0 - center; for i in 0..CIRCLE_POLY_COUNT { let arc = if turn_left { -arc } else { arc }; let step_matrix = rotation_matrix(i as f64 * arc / CIRCLE_POLY_COUNT as f64); let point = center + apply_matrix(step_matrix, position); self.points.push(point); self.width.push(lerp_f64( start_width, end_width, i as f64 / CIRCLE_POLY_COUNT as f64, )); } self.points.push(p1); } fn render_ellipse( &mut self, p0: Point, p1: Point, radius_x: f64, radius_y: f64, rotation: f64, large_arc: bool, turn_left: bool, end_width: f64, ) { let radius_min = distance(p0, p1) / 2.0; let radius_max = (radius_x * radius_x + radius_y * radius_y).sqrt(); let upscale = if radius_max < radius_min { radius_min / radius_max } else { 1.0 }; let ratio = radius_x / radius_y; let rotation = rotation_matrix(-rotation.to_radians()); let transform = [ [rotation[0][0] / upscale, rotation[0][1] / upscale], [ rotation[1][0] / upscale * ratio, rotation[1][1] / upscale * ratio, ], ]; let transform_back = [ [rotation[1][1] * upscale, -rotation[0][1] / ratio * upscale], [-rotation[1][0] * upscale, rotation[0][0] / ratio * upscale], ]; let mut tmp = PathMaker::default(); tmp.render_circle( apply_matrix_point(transform, p0), apply_matrix_point(transform, p1), radius_x * upscale, large_arc, turn_left, end_width, ); for i in 0..tmp.points.len() { let point = tmp.points[i]; self.points.push(apply_matrix_point(transform_back, point)); self.width.push(tmp.width[i]); } } fn close(&mut self, width: f64) { self.points.push(self.points[0]); self.width.push(width) } } fn render_path(path: Path, width: f64) { for segment in path.segments.iter() { let mut path_maker = PathMaker::new(width, segment.start); for instruction in segment.instructions.iter() { let line_width = instruction .line_width .unwrap_or(*path_maker.width.last().unwrap()); let data = instruction.data; match data { InstructionData::Line { position } => path_maker.render_line(line_width, position), InstructionData::HorizontalLine { x } => { path_maker.render_horizontal_line(line_width, x) } InstructionData::VerticalLine { y } => { path_maker.render_vertical_line(line_width, y) } InstructionData::CubicBezier { control_0, control_1, point_1, } => path_maker.render_cubic_bezier( control_0, control_1, point_1, *path_maker.width.last().unwrap(), line_width, ), InstructionData::ArcCircle { large_arc, sweep, radius, target, } => path_maker.render_circle( *path_maker.points.last().unwrap(), target, radius, large_arc, sweep == Sweep::Left, line_width, ), InstructionData::ArcEllipse { large_arc, sweep, radius_x, radius_y, rotation, target, } => path_maker.render_ellipse( *path_maker.points.last().unwrap(), target, radius_x, radius_y, rotation, large_arc, sweep == Sweep::Left, line_width, ), InstructionData::ClosePath => path_maker.close(line_width), InstructionData::QuadraticBezier { control, target } => path_maker .render_quadratic_bezier( control, target, *path_maker.width.last().unwrap(), line_width, ), } } } } pub enum AntiAliasing { X1 = 1, X4 = 2, X9 = 3, X16 = 4, X25 = 6, X49 = 7, X64 = 8, } pub fn render( file: &TvgFile, width: u32, height: u32, scale_x: f32, scale_y: f32, anti_alias: AntiAliasing, ) { let mut framebuffer: FrameBuffer = FrameBuffer::new( width as usize, height as usize, scale_x.into(), scale_y.into(), ); let header = &file.header; let color_table = &file.color_table; for command in file.commands.iter() { match command { Command::EndOfDocument => break, Command::FillPolygon { fill_style, polygon, } => { todo!() } Command::FillRectangles { fill_style, rectangles, } => todo!(), Command::FillPath { fill_style, path } => todo!(), Command::DrawLines { line_style, line_width, lines, } => todo!(), Command::DrawLineLoop { line_style, line_width, points, } => todo!(), Command::DrawLineStrip { line_style, line_width, points, } => todo!(), Command::DrawLinePath { line_style, line_width, path, } => todo!(), Command::OutlineFillPolygon { fill_style, line_style, line_width, points, } => todo!(), Command::OutlineFillRectangles { fill_style, line_style, line_width, rectangles, } => todo!(), Command::OutlineFillPath { fill_style, line_style, line_width, path, } => todo!(), } } }