diff options
| author | Micha White <botahamec@outlook.com> | 2024-08-15 20:14:15 -0400 |
|---|---|---|
| committer | Micha White <botahamec@outlook.com> | 2024-08-15 20:14:15 -0400 |
| commit | f8a80039c74332e2101a177ef3fde31ef2077224 (patch) | |
| tree | f887c96bf9879a28b7ce914ad96161f63ee83190 /tvg/src | |
| parent | 488c7ed94b0662222fa0d825ab81b60b0b1e5d6c (diff) | |
Lots a changes
Diffstat (limited to 'tvg/src')
| -rw-r--r-- | tvg/src/colors.rs | 171 | ||||
| -rw-r--r-- | tvg/src/commands.rs | 153 | ||||
| -rw-r--r-- | tvg/src/header.rs | 8 | ||||
| -rw-r--r-- | tvg/src/lib.rs | 19 | ||||
| -rw-r--r-- | tvg/src/path.rs | 20 | ||||
| -rw-r--r-- | tvg/src/render.rs | 506 |
6 files changed, 841 insertions, 36 deletions
diff --git a/tvg/src/colors.rs b/tvg/src/colors.rs index 494e6aa..0dd8831 100644 --- a/tvg/src/colors.rs +++ b/tvg/src/colors.rs @@ -5,13 +5,16 @@ use num_enum::TryFromPrimitive; use crate::TvgError; +const GAMMA: f32 = 2.2; +const INVERT_GAMMA: f32 = 1.0 / GAMMA; + /// The color table encodes the palette for this file. /// /// It’s binary content is defined by the `color_encoding` field in the header. /// For the three defined color encodings, each will yield a list of /// `color_count` RGBA tuples. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct ColorTable<C: Color> { +pub struct ColorTable<C: Color> { colors: Box<[C]>, } @@ -59,12 +62,17 @@ impl<C: Color> ColorTable<C> { } /// Returns the number of colors in the table. - fn len(&self) -> usize { + pub fn len(&self) -> usize { self.colors.len() } + /// Returns `true` if the color table has no colors in it + pub fn is_empty(&self) -> bool { + self.colors.is_empty() + } + /// Returns a reference to a color, or `None` if out-of-bounds. - fn get(&self, index: usize) -> Option<&C> { + pub fn get(&self, index: usize) -> Option<&C> { self.colors.get(index) } @@ -128,10 +136,68 @@ pub trait Color: Sized { /// Convert from the RGBA16 format to this format. This may be lossy. fn from_rgba16_lossy(red: u16, green: u16, blue: u16, alpha: u16) -> Self; + /// Convert from the RGBAF32 format to this format. This may be lossy. + fn from_rgbaf32_lossy(red: f32, green: f32, blue: f32, alpha: f32) -> Self; + fn red_u16(&self) -> u16; fn blue_u16(&self) -> u16; fn green_u16(&self) -> u16; fn alpha_u16(&self) -> u16; + + fn red_f32(&self) -> f32; + fn green_f32(&self) -> f32; + fn blue_f32(&self) -> f32; + fn alpha_f32(&self) -> f32; +} + +fn to_color_space(val: f32) -> f32 { + val.powf(INVERT_GAMMA) +} + +fn to_linear(val: f32) -> f32 { + val.powf(GAMMA) +} + +pub(crate) fn blend<C: Color + Clone>(dest: &mut C, src: &C) { + fn lerp_color(src: f32, dst: f32, src_alpha: f32, dst_alpha: f32, alpha: f32) -> f32 { + let src = to_linear(src); + let dst = to_linear(dst); + + let val = (1.0 / alpha) * (src_alpha * src + (1.0 - src_alpha) * dst_alpha * dst); + + to_color_space(val) + } + + if src.alpha_f32() == 1.0 || dest.alpha_u16() == 0 { + *dest = src.clone(); + return; + } else if src.alpha_u16() == 0 { + return; + } + + let src_a = src.alpha_f32(); + let dst_a = dest.alpha_f32(); + let alpha = src_a + (1.0 - src_a) * dst_a; + let red = lerp_color(src.red_f32(), dest.red_f32(), src_a, dst_a, alpha); + let green = lerp_color(src.green_f32(), dest.green_f32(), src_a, dst_a, alpha); + let blue = lerp_color(src.blue_f32(), dest.blue_f32(), src_a, dst_a, alpha); + + *dest = C::from_rgbaf32_lossy(red, green, blue, alpha); +} + +pub(crate) fn lerp<C: Color>(first: &C, second: &C, f: f64) -> C { + fn lerp_color(a: f32, b: f32, f: f64) -> f32 { + a + (b - a) * f as f32 + } + + let f = f.clamp(0.0, 1.0); + + let red = to_color_space(lerp_color(first.red_f32(), second.red_f32(), f)); + let green = to_color_space(lerp_color(first.green_f32(), second.green_f32(), f)); + let blue = to_color_space(lerp_color(first.blue_f32(), second.blue_f32(), f)); + let alpha = lerp_color(first.alpha_f32(), second.alpha_f32(), f); + + C::from_rgbaf32_lossy(red, green, blue, alpha) } /// Each color value is encoded as a sequence of four bytes. @@ -172,6 +238,15 @@ impl Color for Rgba8888 { } } + fn from_rgbaf32_lossy(red: f32, green: f32, blue: f32, alpha: f32) -> Self { + Self { + red: (red * u8::MAX as f32) as u8, + green: (green * u8::MAX as f32) as u8, + blue: (blue * u8::MAX as f32) as u8, + alpha: (alpha * u8::MAX as f32) as u8, + } + } + fn red_u16(&self) -> u16 { (self.red as u16) << 8 } @@ -187,6 +262,22 @@ impl Color for Rgba8888 { fn alpha_u16(&self) -> u16 { (self.alpha as u16) << 8 } + + fn red_f32(&self) -> f32 { + self.red as f32 / u8::MAX as f32 + } + + fn green_f32(&self) -> f32 { + self.green as f32 / u8::MAX as f32 + } + + fn blue_f32(&self) -> f32 { + self.blue as f32 / u8::MAX as f32 + } + + fn alpha_f32(&self) -> f32 { + self.alpha as f32 / u8::MAX as f32 + } } /// Each color value is encoded as a sequence of 2 bytes. @@ -224,6 +315,14 @@ impl Color for Rgb565 { } } + fn from_rgbaf32_lossy(red: f32, green: f32, blue: f32, alpha: f32) -> Self { + Self { + red: (red * (0b011111) as f32) as u8, + green: (green * (0b111111) as f32) as u8, + blue: (blue * (0b011111) as f32) as u8, + } + } + fn red_u16(&self) -> u16 { (self.red as u16) << 11 } @@ -239,6 +338,22 @@ impl Color for Rgb565 { fn alpha_u16(&self) -> u16 { 0 } + + fn red_f32(&self) -> f32 { + self.red as f32 / (0b011111) as f32 + } + + fn green_f32(&self) -> f32 { + self.green as f32 / (0b111111) as f32 + } + + fn blue_f32(&self) -> f32 { + self.blue as f32 / (0b011111) as f32 + } + + fn alpha_f32(&self) -> f32 { + 0.0 + } } /// Each color value is encoded as a sequence of 16 bytes. @@ -279,6 +394,15 @@ impl Color for RgbaF32 { } } + fn from_rgbaf32_lossy(red: f32, green: f32, blue: f32, alpha: f32) -> Self { + Self { + red, + green, + blue, + alpha, + } + } + fn red_u16(&self) -> u16 { (self.red * (u16::MAX as f32)) as u16 } @@ -294,6 +418,22 @@ impl Color for RgbaF32 { fn alpha_u16(&self) -> u16 { (self.alpha * (u16::MAX as f32)) as u16 } + + fn red_f32(&self) -> f32 { + self.red + } + + fn green_f32(&self) -> f32 { + self.green + } + + fn blue_f32(&self) -> f32 { + self.blue + } + + fn alpha_f32(&self) -> f32 { + self.alpha + } } /// Each color value is encoded as a sequence of 8 bytes. @@ -334,6 +474,15 @@ impl Color for Rgba16 { } } + fn from_rgbaf32_lossy(red: f32, green: f32, blue: f32, alpha: f32) -> Self { + Self { + red: (red * u16::MAX as f32) as u16, + green: (green * u16::MAX as f32) as u16, + blue: (blue * u16::MAX as f32) as u16, + alpha: (alpha * u16::MAX as f32) as u16, + } + } + fn red_u16(&self) -> u16 { self.red } @@ -349,4 +498,20 @@ impl Color for Rgba16 { fn alpha_u16(&self) -> u16 { self.alpha } + + fn red_f32(&self) -> f32 { + self.red as f32 / u16::MAX as f32 + } + + fn green_f32(&self) -> f32 { + self.green as f32 / u16::MAX as f32 + } + + fn blue_f32(&self) -> f32 { + self.blue as f32 / u16::MAX as f32 + } + + fn alpha_f32(&self) -> f32 { + self.alpha as f32 / u16::MAX as f32 + } } diff --git a/tvg/src/commands.rs b/tvg/src/commands.rs index f316a53..c21099b 100644 --- a/tvg/src/commands.rs +++ b/tvg/src/commands.rs @@ -1,24 +1,101 @@ use std::io::{self, Read}; +use std::ops::{Add, Mul, Sub}; use byteorder::ReadBytesExt; use raise::yeet; -use crate::{header::TvgHeader, path::Path, read_unit, read_varuint, Decode, Point, TvgError}; +use crate::{ + colors::{self, Color, ColorTable}, + header::TvgHeader, + path::Path, + read_unit, read_varuint, Decode, TvgError, +}; + +/// A X and Y coordinate pair. +#[derive(Debug, Clone, Copy)] +pub struct Vector { + /// Horizontal distance of the point to the origin. + pub x: f64, + /// Vertical distance of the point to the origin. + pub y: f64, +} + +impl Vector { + pub fn magnitude(&self) -> f64 { + (self.x * self.x + self.y * self.y).sqrt() + } + + pub fn scale(&self, scale: f64) -> Vector { + Vector { + x: self.x * scale, + y: self.y * scale, + } + } +} + +impl Mul for Vector { + type Output = f64; + + fn mul(self, rhs: Self) -> Self::Output { + self.x * rhs.x + self.y * rhs.y + } +} + +/// A X and Y coordinate pair. +#[derive(Debug, Clone, Copy)] +pub struct Point { + /// Horizontal distance of the point to the origin. + pub x: f64, + /// Vertical distance of the point to the origin. + pub y: f64, +} + +impl Sub for Point { + type Output = Vector; + + fn sub(self, rhs: Self) -> Self::Output { + Vector { + x: self.x - rhs.x, + y: self.y - rhs.y, + } + } +} + +impl Add<Vector> for Point { + type Output = Self; + + fn add(self, vector: Vector) -> Self::Output { + Self { + x: self.x + vector.x, + y: self.y + vector.y, + } + } +} + +impl Decode for Point { + fn read(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> { + Ok(Self { + x: read_unit(reader, header)?, + y: read_unit(reader, header)?, + }) + } +} #[derive(Debug, Clone, Copy, PartialEq)] pub struct Rectangle { /// Horizontal distance of the left side to the origin. - x: f64, + pub x: f64, /// Vertical distance of the upper side to the origin. - y: f64, + pub y: f64, /// Horizontal extent of the rectangle. - width: f64, + pub width: f64, /// Vertical extent of the rectangle. - height: f64, + pub height: f64, } impl Decode for Rectangle { fn read(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> { + let x = 5; Ok(Self { x: read_unit(reader, header)?, y: read_unit(reader, header)?, @@ -203,6 +280,72 @@ impl Style { color_index_1: read_varuint(reader)?, }) } + + fn linear_get_color_at<C: Color + Clone>( + &self, + color_table: &ColorTable<C>, + point: Point, + ) -> Option<C> { + let Self::LinearGradient { + point_0, + point_1, + color_index_0, + color_index_1 } = self else { panic!() }; + + // TODO remove these unwraps + let color_0 = color_table.get(*color_index_0 as usize)?; + let color_1 = color_table.get(*color_index_1 as usize)?; + + let direction = *point_1 - *point_0; + let delta = point - *point_0; + + if direction * delta <= 0.0 { + return Some(color_0.clone()); + } else if direction * (point - *point_1) >= 0.0 { + return Some(color_1.clone()); + } + + let gradient_length = direction.magnitude(); + let proj = (direction * delta) / (direction * direction); + let gradient_position = direction.scale(proj).magnitude(); + let f = gradient_position / gradient_length; + + Some(colors::lerp(color_0, color_1, f)) + } + + fn radial_get_color_at<C: Color>( + &self, + color_table: &ColorTable<C>, + point: Point, + ) -> Option<C> { + let Self::RadialGradient { + point_0, + point_1, + color_index_0, + color_index_1 } = self else { panic!() }; + + // TODO remove these unwraps + let color_0 = color_table.get(*color_index_0 as usize)?; + let color_1 = color_table.get(*color_index_1 as usize)?; + + let distance_max = (*point_1 - *point_0).magnitude(); + let distance_is = (point - *point_0).magnitude(); + let f = distance_is / distance_max; + + Some(colors::lerp(color_0, color_1, f)) + } + + pub fn get_color_at<C: Color + Clone>( + &self, + color_table: &ColorTable<C>, + point: Point, + ) -> Option<C> { + match self { + Self::FlatColored { color_index } => color_table.get(*color_index as usize).cloned(), + Self::LinearGradient { .. } => self.linear_get_color_at(color_table, point), + Self::RadialGradient { .. } => self.radial_get_color_at(color_table, point), + } + } } /// TinyVG files contain a sequence of draw commands that must be executed in diff --git a/tvg/src/header.rs b/tvg/src/header.rs index b3be494..d444f66 100644 --- a/tvg/src/header.rs +++ b/tvg/src/header.rs @@ -135,6 +135,14 @@ impl TvgHeader { pub fn color_count(&self) -> u32 { self.color_count } + + pub fn width(&self) -> u32 { + self.width + } + + pub fn height(&self) -> u32 { + self.height + } } fn read_unsigned_unit<R: Read>( diff --git a/tvg/src/lib.rs b/tvg/src/lib.rs index 5c9e1d2..16a95bd 100644 --- a/tvg/src/lib.rs +++ b/tvg/src/lib.rs @@ -10,6 +10,7 @@ pub mod colors; mod commands; mod header; mod path; +mod render; pub use header::SUPPORTED_VERSION; @@ -146,24 +147,6 @@ fn read_unit(reader: &mut impl Read, header: &TvgHeader) -> io::Result<f64> { Ok((value as f64) / fractional) } -/// A X and Y coordinate pair. -#[derive(Debug, Clone, Copy)] -pub struct Point { - /// Horizontal distance of the point to the origin. - x: f64, - /// Vertical distance of the point to the origin. - y: f64, -} - -impl Decode for Point { - fn read(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> { - Ok(Self { - x: read_unit(reader, header)?, - y: read_unit(reader, header)?, - }) - } -} - fn read_varuint(reader: &mut impl Read) -> io::Result<u32> { let mut count = 0; let mut result = 0; diff --git a/tvg/src/path.rs b/tvg/src/path.rs index 2bc0448..a576eb8 100644 --- a/tvg/src/path.rs +++ b/tvg/src/path.rs @@ -4,7 +4,7 @@ use byteorder::ReadBytesExt; use num_enum::TryFromPrimitive; use raise::yeet; -use crate::{header::TvgHeader, read_unit, read_varuint, Decode, Point, TvgError}; +use crate::{commands::Point, header::TvgHeader, read_unit, read_varuint, Decode, TvgError}; /// Returns an error if the padding isn't zero fn check_padding(padding: u8) -> Result<(), TvgError> { @@ -17,7 +17,7 @@ fn check_padding(padding: u8) -> Result<(), TvgError> { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)] #[repr(u8)] -enum Sweep { +pub(crate) enum Sweep { Right = 0, Left = 1, } @@ -47,7 +47,7 @@ enum InstructionKind { } #[derive(Debug, Clone, Copy)] -enum InstructionData { +pub(crate) enum InstructionData { /// The line instruction draws a straight line to the position. Line { /// The end point of the line. @@ -231,11 +231,11 @@ impl InstructionData { } #[derive(Debug, Clone)] -struct Instruction { +pub(crate) struct Instruction { /// The width of the line the "pen" makes, if it makes one at all. - line_width: Option<f64>, + pub(crate) line_width: Option<f64>, /// The arguments to the instruction. - data: InstructionData, + pub(crate) data: InstructionData, } impl Instruction { @@ -258,11 +258,11 @@ impl Instruction { } #[derive(Debug, Clone)] -struct Segment { +pub(crate) struct Segment { /// The starting point of the segment. - start: Point, + pub(crate) start: Point, /// The list of instructions for tha segment. - instructions: Box<[Instruction]>, + pub(crate) instructions: Box<[Instruction]>, } impl Segment { @@ -294,7 +294,7 @@ impl Segment { /// outline of the shape. #[derive(Debug, Clone)] pub struct Path { - segments: Box<[Segment]>, + pub(crate) segments: Box<[Segment]>, } impl Path { diff --git a/tvg/src/render.rs b/tvg/src/render.rs new file mode 100644 index 0000000..f8c15a1 --- /dev/null +++ b/tvg/src/render.rs @@ -0,0 +1,506 @@ +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<f64> { + 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<C: Color> { + width: usize, + height: usize, + pixels: Box<[C]>, + scale_x: f64, + scale_y: f64, +} + +impl<C: Color + Clone + Default> FrameBuffer<C> { + 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<C>, + 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<Point>, + width: Vec<f64>, +} + +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<C: Color + Clone + Default>( + file: &TvgFile<C>, + width: u32, + height: u32, + scale_x: f32, + scale_y: f32, + anti_alias: AntiAliasing, +) { + let mut framebuffer: FrameBuffer<C> = 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!(), + } + } +} |
