summaryrefslogtreecommitdiff
path: root/tvg
diff options
context:
space:
mode:
authorMicha White <botahamec@outlook.com>2024-08-15 20:14:15 -0400
committerMicha White <botahamec@outlook.com>2024-08-15 20:14:15 -0400
commitf8a80039c74332e2101a177ef3fde31ef2077224 (patch)
treef887c96bf9879a28b7ce914ad96161f63ee83190 /tvg
parent488c7ed94b0662222fa0d825ab81b60b0b1e5d6c (diff)
Lots a changes
Diffstat (limited to 'tvg')
-rw-r--r--tvg/src/colors.rs171
-rw-r--r--tvg/src/commands.rs153
-rw-r--r--tvg/src/header.rs8
-rw-r--r--tvg/src/lib.rs19
-rw-r--r--tvg/src/path.rs20
-rw-r--r--tvg/src/render.rs506
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!(),
+ }
+ }
+}