diff options
Diffstat (limited to 'tvg')
| -rw-r--r-- | tvg/Cargo.toml | 13 | ||||
| -rw-r--r-- | tvg/src/colors.rs | 517 | ||||
| -rw-r--r-- | tvg/src/commands.rs | 756 | ||||
| -rw-r--r-- | tvg/src/header.rs | 157 | ||||
| -rw-r--r-- | tvg/src/lib.rs | 167 | ||||
| -rw-r--r-- | tvg/src/path.rs | 320 | ||||
| -rw-r--r-- | tvg/src/render.rs | 506 | ||||
| -rw-r--r-- | tvg/tests/examples/tvg/everything-32.tvg | bin | 2637 -> 0 bytes | |||
| -rw-r--r-- | tvg/tests/examples/tvg/everything.tvg | bin | 1447 -> 0 bytes | |||
| -rw-r--r-- | tvg/tests/examples/tvg/shield-16.tvg | bin | 203 -> 0 bytes | |||
| -rw-r--r-- | tvg/tests/examples/tvg/shield-32.tvg | bin | 371 -> 0 bytes | |||
| -rw-r--r-- | tvg/tests/examples/tvg/shield-8.tvg | bin | 119 -> 0 bytes | |||
| -rw-r--r-- | tvg/tests/parse.rs | 19 |
13 files changed, 0 insertions, 2455 deletions
diff --git a/tvg/Cargo.toml b/tvg/Cargo.toml deleted file mode 100644 index 385de09..0000000 --- a/tvg/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "alligator_tvg" -version = "0.1.0" -edition = "2021" -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -byteorder = "1" -thiserror = "1" -raise = "2" -num_enum = "0.5" diff --git a/tvg/src/colors.rs b/tvg/src/colors.rs deleted file mode 100644 index 0dd8831..0000000 --- a/tvg/src/colors.rs +++ /dev/null @@ -1,517 +0,0 @@ -use std::io::{self, Read}; - -use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; -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 struct ColorTable<C: Color> { - colors: Box<[C]>, -} - -impl<C: Color> ColorTable<C> { - /// Read in one encoding, and convert it to another. If `encoding` is - /// [`ColorEncoding::Custom`], then return - /// [`TvgError::UnsupportedColorEncoding`]. - pub fn read_from_encoding( - reader: &mut impl Read, - color_count: u32, - encoding: ColorEncoding, - ) -> Result<Self, TvgError> { - Ok(match encoding { - ColorEncoding::Rgba8888 => (&ColorTable::<Rgba8888>::read(reader, color_count)?).into(), - ColorEncoding::Rgb565 => (&ColorTable::<Rgb565>::read(reader, color_count)?).into(), - ColorEncoding::RgbaF32 => (&ColorTable::<RgbaF32>::read(reader, color_count)?).into(), - ColorEncoding::Custom => (&ColorTable::<Rgba16>::read(reader, color_count)?).into(), - }) - } - - /// Read in one encoding, and convert it into another. If `encoding` is - /// [`ColorEncoding::Custom`], then use the given encoding. - pub fn read_from_encoding_with_custom<Custom: Color>( - reader: &mut impl Read, - color_count: u32, - encoding: ColorEncoding, - ) -> io::Result<Self> { - Ok(match encoding { - ColorEncoding::Rgba8888 => (&ColorTable::<Rgba8888>::read(reader, color_count)?).into(), - ColorEncoding::Rgb565 => (&ColorTable::<Rgb565>::read(reader, color_count)?).into(), - ColorEncoding::RgbaF32 => (&ColorTable::<RgbaF32>::read(reader, color_count)?).into(), - ColorEncoding::Custom => (&ColorTable::<Custom>::read(reader, color_count)?).into(), - }) - } - - /// Parse a color table. - fn read(reader: &mut impl Read, color_count: u32) -> io::Result<Self> { - let mut colors = Vec::with_capacity(color_count as usize); - for _ in 0..color_count { - colors.push(C::parse_bytes(reader)?); - } - - let colors = colors.into_boxed_slice(); - Ok(Self { colors }) - } - - /// Returns the number of colors in the table. - 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. - pub fn get(&self, index: usize) -> Option<&C> { - self.colors.get(index) - } - - fn iter(&self) -> impl Iterator<Item = &C> { - self.colors.iter() - } -} - -impl ColorTable<Rgba16> {} - -impl<Old: Color, New: Color> From<&ColorTable<Old>> for ColorTable<New> { - fn from(value: &ColorTable<Old>) -> Self { - let mut colors = Vec::with_capacity(value.len()); - - for color in value.iter() { - let r = color.red_u16(); - let g = color.green_u16(); - let b = color.blue_u16(); - let a = color.alpha_u16(); - colors.push(New::from_rgba16_lossy(r, g, b, a)); - } - - let colors = colors.into_boxed_slice(); - Self { colors } - } -} - -/// The color encoding defines which format the colors in the color table will -/// have. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)] -#[repr(u8)] -pub enum ColorEncoding { - /// Each color is a 4-tuple (red, green, blue, alpha) of bytes with the - /// color channels encoded in sRGB and the alpha as linear alpha. - Rgba8888 = 0, - /// Each color is encoded as a 3-tuple (red, green, blue) with 16 bits per - /// color. - /// - /// While red and blue both use 5 bits, the green channel uses 6 bits. Red - /// uses bit range 0...4, green bits 5...10 and blue bits 11...15. This - /// color also uses the sRGB color space. - Rgb565 = 1, - /// Each color is a 4-tuple (red, green, blue, alpha) of binary32 IEEE 754 - /// floating point value with the color channels encoded in scRGB and the - /// alpha as linear alpha. A color value of 1.0 is full intensity, while a - /// value of 0.0 is zero intensity. - RgbaF32 = 2, - /// The custom color encoding is defined *undefined*. The information how - /// these colors are encoded must be implemented via external means. - Custom = 3, -} - -pub trait Color: Sized { - /// The size of the color's representation in bits - const SIZE: usize; - - /// Attempt to read the color. Returns `Err` if an error occurred while - /// attempting to read [`SIZE`] bytes from `bytes`. - fn parse_bytes(reader: &mut impl Read) -> io::Result<Self>; - - /// 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. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Rgba8888 { - /// Red color channel between 0 and 100% intensity, mapped to byte values 0 - /// to 255. - red: u8, - /// Green color channel between 0 and 100% intensity, mapped to byte values - /// 0 to 255. - green: u8, - /// Blue color channel between 0 and 100% intensity, mapped to byte values - /// 0 to 255. - blue: u8, - /// Transparency channel between 0 and 100% transparency, mapped to byte - /// values 0 to 255. - alpha: u8, -} - -impl Color for Rgba8888 { - const SIZE: usize = 4; - - fn parse_bytes(reader: &mut impl Read) -> io::Result<Self> { - Ok(Self { - red: reader.read_u8()?, - green: reader.read_u8()?, - blue: reader.read_u8()?, - alpha: reader.read_u8()?, - }) - } - - fn from_rgba16_lossy(red: u16, green: u16, blue: u16, alpha: u16) -> Self { - Self { - red: (red >> 8) as u8, - green: (green >> 8) as u8, - blue: (blue >> 8) as u8, - alpha: (alpha >> 8) as u8, - } - } - - 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 - } - - fn green_u16(&self) -> u16 { - (self.green as u16) << 8 - } - - fn blue_u16(&self) -> u16 { - (self.blue as u16) << 8 - } - - 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. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Rgb565 { - /// Red color channel between 0 and 100% intensity, mapped to integer - /// values 0 to 31. - red: u8, - /// Green color channel between 0 and 100% intensity, mapped to integer - /// values 0 to 63. - green: u8, - /// Blue color channel between 0 and 100% intensity, mapped to integer - /// values 0 to 31. - blue: u8, -} - -impl Color for Rgb565 { - const SIZE: usize = 2; - - fn parse_bytes(reader: &mut impl Read) -> io::Result<Self> { - let color = reader.read_u16::<LittleEndian>()?; - - let red = ((color & 0x001F) << 3) as u8; - let green = ((color & 0x07E0) >> 3) as u8; - let blue = ((color & 0xF800) >> 8) as u8; - - Ok(Self { red, blue, green }) - } - - fn from_rgba16_lossy(red: u16, green: u16, blue: u16, _a: u16) -> Self { - Self { - red: (red >> 11) as u8, - green: (green >> 10) as u8, - blue: (blue >> 11) as u8, - } - } - - 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 - } - - fn green_u16(&self) -> u16 { - (self.green as u16) << 11 - } - - fn blue_u16(&self) -> u16 { - (self.blue as u16) << 10 - } - - 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. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct RgbaF32 { - /// Red color channel, using 0.0 for 0% intensity and 1.0 for 100% - /// intensity. - red: f32, - /// Green color channel, using 0.0 for 0% intensity and 1.0 for 100% - /// intensity. - green: f32, - /// Blue color channel, using 0.0 for 0% intensity and 1.0 for 100% - /// intensity. - blue: f32, - /// Transparency channel between 0 and 100% transparency, mapped to byte - /// values 0.0 to 1.0. - alpha: f32, -} - -impl Color for RgbaF32 { - const SIZE: usize = 16; - - fn parse_bytes(reader: &mut impl Read) -> io::Result<Self> { - Ok(Self { - red: reader.read_f32::<LittleEndian>()?, - green: reader.read_f32::<LittleEndian>()?, - blue: reader.read_f32::<LittleEndian>()?, - alpha: reader.read_f32::<LittleEndian>()?, - }) - } - - fn from_rgba16_lossy(red: u16, green: u16, blue: u16, alpha: u16) -> Self { - Self { - red: (red as f32) / (u16::MAX as f32), - green: (green as f32) / (u16::MAX as f32), - blue: (blue as f32) / (u16::MAX as f32), - alpha: (alpha as f32) / (u16::MAX as f32), - } - } - - 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 - } - - fn green_u16(&self) -> u16 { - (self.green * (u16::MAX as f32)) as u16 - } - - fn blue_u16(&self) -> u16 { - (self.blue * (u16::MAX as f32)) as u16 - } - - 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. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Rgba16 { - /// Red color channel between 0 and 100% intensity, mapped to a big-endian - /// 16-bit integer. - red: u16, - /// Green color channel between 0 and 100% intensity, mapped to a - /// big-endian 16-bit integer. - green: u16, - /// Blue color channel between 0 and 100% intensity, mapped to a big-endian - /// 16-bit integer. - blue: u16, - /// Transparency channel between 0 and 100% intensity, mapped to a - /// big-endian 16-bit integer. - alpha: u16, -} - -impl Color for Rgba16 { - const SIZE: usize = 8; - - fn parse_bytes(reader: &mut impl Read) -> io::Result<Self> { - Ok(Self { - red: reader.read_u16::<BigEndian>()?, - green: reader.read_u16::<BigEndian>()?, - blue: reader.read_u16::<BigEndian>()?, - alpha: reader.read_u16::<BigEndian>()?, - }) - } - - fn from_rgba16_lossy(red: u16, green: u16, blue: u16, alpha: u16) -> Self { - Self { - red, - green, - blue, - alpha, - } - } - - 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 - } - - fn green_u16(&self) -> u16 { - self.green - } - - fn blue_u16(&self) -> u16 { - self.blue - } - - 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 deleted file mode 100644 index c21099b..0000000 --- a/tvg/src/commands.rs +++ /dev/null @@ -1,756 +0,0 @@ -use std::io::{self, Read}; -use std::ops::{Add, Mul, Sub}; - -use byteorder::ReadBytesExt; -use raise::yeet; - -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. - pub x: f64, - /// Vertical distance of the upper side to the origin. - pub y: f64, - /// Horizontal extent of the rectangle. - pub width: f64, - /// Vertical extent of the rectangle. - 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)?, - width: read_unit(reader, header)?, - height: read_unit(reader, header)?, - }) - } -} - -#[derive(Debug, Clone, Copy)] -pub struct Line { - /// Start point of the line. - start: Point, - /// End point of the line. - end: Point, -} - -impl Decode for Line { - fn read(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> { - Ok(Self { - start: Point::read(reader, header)?, - end: Point::read(reader, header)?, - }) - } -} - -/// A command that can be encoded. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u8)] -enum CommandName { - /// Determines end of file. - EndOfDocument = 0, - /// Fills an N-gon. - FillPolygon = 1, - /// Fills a set of [`Rectangle`]s. - FillRectangles = 2, - /// Fills a free-form [`Path`]. - FillPath = 3, - /// Draws a set of lines. - DrawLines = 4, - /// Draws the outline of a polygon. - DrawLineLoop = 5, - /// Draws a list of end-to-end lines. - DrawLineStrip = 6, - /// Draws a free-form [`Path`]. - DrawLinePath = 7, - /// Draws a filled polygon with an outline. - OutlineFillPolygon = 8, - /// Draws several filled [`Rectangle`]s with an outline. - OutlineFillRectangles = 9, - /// This command combines the [`FillPath`] and [`DrawLinePath`] command - /// into one. - OutlineFillPath = 10, -} - -impl TryFrom<u8> for CommandName { - type Error = TvgError; - - fn try_from(value: u8) -> Result<Self, Self::Error> { - match value { - 0 => Ok(Self::EndOfDocument), - 1 => Ok(Self::FillPolygon), - 2 => Ok(Self::FillRectangles), - 3 => Ok(Self::FillPath), - 4 => Ok(Self::DrawLines), - 5 => Ok(Self::DrawLineLoop), - 6 => Ok(Self::DrawLineStrip), - 7 => Ok(Self::DrawLinePath), - 8 => Ok(Self::OutlineFillPolygon), - 9 => Ok(Self::OutlineFillRectangles), - 10 => Ok(Self::OutlineFillPath), - v => Err(TvgError::InvalidCommand(v)), - } - } -} - -/// The type of style the command uses as a primary style. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u8)] -pub enum StyleKind { - /// The shape is uniformly colored with a single color. - FlatColored = 0, - /// The shape is colored with a linear gradient. - LinearGradient = 1, - /// The shape is colored with a radial gradient. - RadialGradient = 2, -} - -impl TryFrom<u8> for StyleKind { - type Error = TvgError; - - fn try_from(value: u8) -> Result<Self, Self::Error> { - match value { - 0 => Ok(Self::FlatColored), - 1 => Ok(Self::LinearGradient), - 2 => Ok(Self::RadialGradient), - _ => Err(TvgError::InvalidStyleKind), - } - } -} - -/// The style kind, along with the colors and points used to render the style. -#[derive(Debug, Clone, Copy)] -pub enum Style { - /// The shape is uniformly colored with the color at `color_index` in the - /// [`ColorTable`]. - FlatColored { - /// The index in the [`ColorTable`]. - color_index: u32, - }, - /// The gradient is formed by a mental line between `point_0` and - /// `point_1`. - /// - /// The color at `point_0` is the color at `color_index_0` in the color - /// table. The color at `point_1` is the color at `color_index_1` in the - /// [`ColorTable`]. On the line, the color is interpolated between the two - /// points. Each point that is not on the line is orthogonally projected to - /// the line and the color at that point is sampled. Points that are not - /// projectable onto the line have either the color at `point_0` if they - /// are closed to `point_0` or vice versa for `point_1`. - LinearGradient { - /// The start point of the gradient. - point_0: Point, - /// The end point of the gradient. - point_1: Point, - /// The color at [`point_0`]. - color_index_0: u32, - /// The color at [`point_1`]. - color_index_1: u32, - }, - /// The gradient is formed by a mental circle with the center at `point_0` - /// and `point_1` being somewhere on the circle outline. Thus, the radius - /// of said circle is the distance between `point_0` and `point_1`. - /// - /// The color at `point_0` is the color at `color_index_0` in the color - /// table. The color on the circle outline is the color at `color_index_1` - /// in the [`ColorTable`]. If a sampled point is inside the circle, the - /// color is interpolated based on the distance to the center and the - /// radius. If the point is not in the circle itself, the color at - /// `color_index_1` is always taken. - RadialGradient { - /// The center point of the mental circle. - point_0: Point, - /// The end point of the gradient. - point_1: Point, - /// The color at `point_0`. - color_index_0: u32, - /// The color at `point_1`. - color_index_1: u32, - }, -} - -impl Style { - fn read(reader: &mut impl Read, header: &TvgHeader, kind: StyleKind) -> io::Result<Self> { - match kind { - StyleKind::FlatColored => Self::read_flat_colored(reader), - StyleKind::LinearGradient => Self::read_linear_gradient(reader, header), - StyleKind::RadialGradient => Self::read_radial_gradient(reader, header), - } - } - - fn read_flat_colored(reader: &mut impl Read) -> io::Result<Self> { - Ok(Self::FlatColored { - color_index: read_varuint(reader)?, - }) - } - - fn read_linear_gradient(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> { - Ok(Self::LinearGradient { - point_0: Point::read(reader, header)?, - point_1: Point::read(reader, header)?, - color_index_0: read_varuint(reader)?, - color_index_1: read_varuint(reader)?, - }) - } - - fn read_radial_gradient(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self> { - Ok(Self::RadialGradient { - point_0: Point::read(reader, header)?, - point_1: Point::read(reader, header)?, - color_index_0: read_varuint(reader)?, - 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 -/// the defined order to get the final result. Each draw command adds a new 2D -/// primitive to the graphic. -#[derive(Debug, Clone)] -pub enum Command { - /// If this command is read, the TinyVG file has ended. - /// - /// This command must have prim_style_kind to be set to 0, so the last byte - /// of every TinyVG file is `0x00`. Every byte after this command is - /// considered not part of the TinyVG data and can be used for other - /// purposes like metadata or similar. - EndOfDocument, - /// Fills a polygon with N [`Point`]s. - /// - /// The number of points must be at least 3. Files that encode a lower - /// value must be discarded as ”invalid” by a conforming implementation. - /// - /// The polygon specified in polygon must be drawn using the even-odd rule. - /// That means that if for any point to be inside the polygon, a line to - /// infinity must cross an odd number of polygon segments. - FillPolygon { - /// The style that is used to fill the polygon. - fill_style: Style, - /// The points of the polygon. - polygon: Box<[Point]>, - }, - /// Fills a list of [`Rectangle`]s. - /// - /// The rectangles must be drawn first to last, which is the order they - /// appear in the file. - FillRectangles { - /// The style that is used to fill all rectangles. - fill_style: Style, - /// The list of rectangles to be filled. - rectangles: Box<[Rectangle]>, - }, - /// Fills a [`Path`]. - /// - /// For the filling, all path segments are considered a polygon each (drawn - /// with even-odd rule) that, when overlap, also perform the even odd rule. - /// This allows the user to carve out parts of the path and create - /// arbitrarily shaped surfaces. - FillPath { - /// The style that is used to fill the path. - fill_style: Style, - /// A [`Path`] with `segment_count` segments. - path: Path, - }, - /// Draws a set of [`Line`]s. - /// - /// Each line is `line_width` units wide, and at least a single display - /// pixel. This means that line_width of 0 is still visible, even though - /// only marginally. This allows very thin outlines. - DrawLines { - /// The style that is used to draw the all lines. - line_style: Style, - /// The width of the lines. - line_width: f64, - /// The list of lines. - lines: Box<[Line]>, - }, - /// Draws a polygon. - /// - /// Each line is `line_width` units wide. The lines are drawn between - /// consecutive points as well as the first and the last point. - DrawLineLoop { - /// The style that is used to draw the all lines. - line_style: Style, - /// The width of the line. - line_width: f64, - /// The points of the polygon. - points: Box<[Point]>, - }, - /// Draws a list of consecutive lines. - /// - /// The lines are drawn between consecutive points, but contrary to - /// [`DrawLineLoop`], the first and the last point are not connected. - DrawLineStrip { - /// The style that is used to draw the all rectangles. - line_style: Style, - /// The width of the line. - line_width: f64, - /// The points of the line strip. - points: Box<[Point]>, - }, - /// Draws a [`Path`]. - /// - /// The outline of the path is `line_width` units wide. - DrawLinePath { - /// The style that is used to draw the path. - line_style: Style, - /// The width of the line. - line_width: f64, - /// A path with `segment_count` segments. - path: Path, - }, - /// Fills a polygon and draws an outline at the same time. - /// - /// This command is a combination of [`FillPolygon`] and [`DrawLineLoop`]. - /// It first performs a [`FillPolygon`] with the `fill_style`, then - /// performs [`DrawLineLoop`] with `line_style` and `line_width`. - /// - /// The outline commands use a reduced number of elements. The maximum - /// number of points is 64. - OutlineFillPolygon { - /// The style that is used to fill the polygon. - fill_style: Style, - /// The style that is used to draw the outline of the polygon. - line_style: Style, - /// The width of the line. - line_width: f64, - /// The set of points of this polygon. - points: Box<[Point]>, - }, - /// Fills and outlines a list of [`Rectangle`]s. - /// - /// For each rectangle, it is first filled, then its outline is drawn, then - /// the next rectangle is drawn. - /// - /// The outline commands use a reduced number of elements, the maximum - /// number of points is 64. - OutlineFillRectangles { - /// The style that is used to fill the rectangles. - fill_style: Style, - /// The style that is used to draw the outline of the rectangles. - line_style: Style, - /// The width of the line. - line_width: f64, - /// The list of rectangles to be drawn. - rectangles: Box<[Rectangle]>, - }, - /// Fills a path and draws an outline at the same time. - /// - /// This command is a combination of [`FillPath`] and [`DrawLinePath`]. It - /// first performs a [`FillPath`] with the `fill_style`, then performs - /// [`DrawLinePath`] with `line_style` and `line_width`. - OutlineFillPath { - /// The style that is used to fill the path. - fill_style: Style, - /// The style that is used to draw the outline of the path. - line_style: Style, - /// The width of the line. - line_width: f64, - /// The path that should be drawn. - path: Path, - }, -} - -/// The header is different for outline commands, so we use this as a helper -struct OutlineHeader { - count: u32, - fill_style: Style, - line_style: Style, - line_width: f64, -} - -impl OutlineHeader { - fn read( - reader: &mut impl Read, - header: &TvgHeader, - prim_style_kind: StyleKind, - ) -> Result<Self, TvgError> { - // the count and secondary style kind are stores in the same byte - let byte = reader.read_u8()?; - let count = (byte & 0b0011_1111) as u32 + 1; - let sec_style_kind = StyleKind::try_from((byte & 0b1100_0000) >> 6)?; - - let fill_style = Style::read(reader, header, prim_style_kind)?; - let line_style = Style::read(reader, header, sec_style_kind)?; - - let line_width = read_unit(reader, header)?; - - Ok(Self { - count, - fill_style, - line_style, - line_width, - }) - } -} - -impl Command { - pub fn read(reader: &mut impl Read, header: &TvgHeader) -> Result<Self, TvgError> { - // the command name and primary style kind are stores in the same byte - let byte = reader.read_u8()?; - let command = CommandName::try_from(byte & 0b0011_1111)?; - let style_kind = StyleKind::try_from((byte & 0b1100_0000) >> 6)?; - - match command { - CommandName::EndOfDocument => Self::end_of_document(style_kind), - CommandName::FillPolygon => Self::read_fill_polygon(reader, header, style_kind), - CommandName::FillRectangles => Self::read_fill_rectangles(reader, header, style_kind), - CommandName::FillPath => Self::read_fill_path(reader, header, style_kind), - CommandName::DrawLines => Self::read_draw_lines(reader, header, style_kind), - CommandName::DrawLineLoop => Self::read_draw_line_loop(reader, header, style_kind), - CommandName::DrawLineStrip => Self::read_draw_line_strip(reader, header, style_kind), - CommandName::DrawLinePath => Self::read_draw_line_path(reader, header, style_kind), - CommandName::OutlineFillPolygon => { - Self::read_outline_fill_polygon(reader, header, style_kind) - } - CommandName::OutlineFillRectangles => { - Self::read_outline_fill_rectangles(reader, header, style_kind) - } - CommandName::OutlineFillPath => { - Self::read_outline_fill_path(reader, header, style_kind) - } - } - } - - pub fn is_end_of_document(&self) -> bool { - matches!(self, Self::EndOfDocument) - } - - fn read_command_header( - reader: &mut impl Read, - header: &TvgHeader, - style_kind: StyleKind, - ) -> io::Result<(u32, Style)> { - // every command adds one to the count - let count = read_varuint(reader)? + 1; - let style = Style::read(reader, header, style_kind)?; - - Ok((count, style)) - } - - fn end_of_document(style_kind: StyleKind) -> Result<Self, TvgError> { - if style_kind != StyleKind::FlatColored { - Err(TvgError::InvalidEndOfDocument(style_kind as u8)) - } else { - Ok(Self::EndOfDocument) - } - } - - fn read_fill_polygon( - reader: &mut impl Read, - header: &TvgHeader, - style_kind: StyleKind, - ) -> Result<Self, TvgError> { - let (point_count, fill_style) = Self::read_command_header(reader, header, style_kind)?; - if point_count < 3 { - yeet!(TvgError::InvalidPolygon(point_count)); - } - - let polygon = Point::read_multiple(reader, header, point_count)?; - - Ok(Self::FillPolygon { - fill_style, - polygon, - }) - } - - fn read_fill_rectangles( - reader: &mut impl Read, - header: &TvgHeader, - style_kind: StyleKind, - ) -> Result<Self, TvgError> { - let (rectangle_count, fill_style) = Self::read_command_header(reader, header, style_kind)?; - let rectangles = Rectangle::read_multiple(reader, header, rectangle_count)?; - - Ok(Self::FillRectangles { - fill_style, - rectangles, - }) - } - - fn read_fill_path( - reader: &mut impl Read, - header: &TvgHeader, - style_kind: StyleKind, - ) -> Result<Self, TvgError> { - let (segment_count, fill_style) = Self::read_command_header(reader, header, style_kind)?; - let path = Path::read(reader, header, segment_count)?; - - Ok(Self::FillPath { fill_style, path }) - } - - fn read_draw_lines( - reader: &mut impl Read, - header: &TvgHeader, - style_kind: StyleKind, - ) -> Result<Self, TvgError> { - let (line_count, line_style) = Self::read_command_header(reader, header, style_kind)?; - let line_width = read_unit(reader, header)?; - let lines = Line::read_multiple(reader, header, line_count)?; - - Ok(Self::DrawLines { - line_style, - line_width, - lines, - }) - } - - fn read_draw_line_loop( - reader: &mut impl Read, - header: &TvgHeader, - style_kind: StyleKind, - ) -> Result<Self, TvgError> { - let (point_count, line_style) = Self::read_command_header(reader, header, style_kind)?; - let line_width = read_unit(reader, header)?; - let points = Point::read_multiple(reader, header, point_count)?; - - Ok(Self::DrawLineLoop { - line_style, - line_width, - points, - }) - } - - fn read_draw_line_strip( - reader: &mut impl Read, - header: &TvgHeader, - style_kind: StyleKind, - ) -> Result<Self, TvgError> { - let (point_count, line_style) = Self::read_command_header(reader, header, style_kind)?; - let line_width = read_unit(reader, header)?; - let points = Point::read_multiple(reader, header, point_count)?; - - Ok(Self::DrawLineStrip { - line_style, - line_width, - points, - }) - } - - fn read_draw_line_path( - reader: &mut impl Read, - header: &TvgHeader, - style_kind: StyleKind, - ) -> Result<Self, TvgError> { - let (segment_count, line_style) = Self::read_command_header(reader, header, style_kind)?; - let line_width = read_unit(reader, header)?; - let path = Path::read(reader, header, segment_count)?; - - Ok(Self::DrawLinePath { - line_style, - line_width, - path, - }) - } - - fn read_outline_fill_polygon( - reader: &mut impl Read, - header: &TvgHeader, - style_kind: StyleKind, - ) -> Result<Self, TvgError> { - let OutlineHeader { - count: segment_count, - fill_style, - line_style, - line_width, - } = OutlineHeader::read(reader, header, style_kind)?; - - let points = Point::read_multiple(reader, header, segment_count)?; - - Ok(Self::OutlineFillPolygon { - fill_style, - line_style, - line_width, - points, - }) - } - - fn read_outline_fill_rectangles( - reader: &mut impl Read, - header: &TvgHeader, - style_kind: StyleKind, - ) -> Result<Self, TvgError> { - let OutlineHeader { - count: rect_count, - fill_style, - line_style, - line_width, - } = OutlineHeader::read(reader, header, style_kind)?; - - let rectangles = Rectangle::read_multiple(reader, header, rect_count)?; - - Ok(Self::OutlineFillRectangles { - fill_style, - line_style, - line_width, - rectangles, - }) - } - - fn read_outline_fill_path( - reader: &mut impl Read, - header: &TvgHeader, - style_kind: StyleKind, - ) -> Result<Self, TvgError> { - let OutlineHeader { - count: segment_count, - fill_style, - line_style, - line_width, - } = OutlineHeader::read(reader, header, style_kind)?; - - let path = Path::read(reader, header, segment_count)?; - - Ok(Self::OutlineFillPath { - fill_style, - line_style, - line_width, - path, - }) - } -} diff --git a/tvg/src/header.rs b/tvg/src/header.rs deleted file mode 100644 index d444f66..0000000 --- a/tvg/src/header.rs +++ /dev/null @@ -1,157 +0,0 @@ -use std::io::{self, Read}; - -use byteorder::{LittleEndian, ReadBytesExt}; -use num_enum::TryFromPrimitive; -use raise::yeet; - -use crate::colors::ColorEncoding; -use crate::read_varuint; -use crate::TvgError; - -const MAGIC: [u8; 2] = [0x72, 0x56]; -pub const SUPPORTED_VERSION: u8 = 1; - -/// The coordinate range defines how many bits a Unit value uses. -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)] -#[repr(u8)] -pub enum CoordinateRange { - /// Each [`Unit`] takes up 16 bits. - #[default] - Default = 0, - /// Each [`Unit`] takes up 8 bits. - Reduced = 1, - /// Each [`Unit`] takes up 32 bits. - Enhanced = 2, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)] -#[repr(u8)] -pub enum Scale { - _1 = 0, - _2 = 1, - _4 = 2, - _8 = 3, - _16 = 4, - _32 = 5, - _64 = 6, - _128 = 7, - _256 = 8, - _512 = 9, - _1024 = 10, - _2048 = 11, - _4096 = 12, - _8192 = 13, - _16384 = 14, - _32768 = 15, -} - -/// Each TVG file starts with a header defining some global values for the file -/// like scale and image size. This is a representation of the header, but not -/// necessarily an exact representation of the bits of a TVG header. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct TvgHeader { - /// Must always be [0x72, 0x56] - magic: [u8; 2], - /// Must be 1. For future versions, this field might decide how the rest of - /// the format looks like. - version: u8, - /// Defines the number of fraction bits in a Unit value. - scale: Scale, - /// Defines the type of color information that is used in the - /// [`ColorTable`]. - color_encoding: ColorEncoding, - /// Defines the number of total bits in a Unit value and thus the overall - /// precision of the file. - coordinate_range: CoordinateRange, - /// Encodes the maximum width of the output file in *display units*. - /// - /// A value of 0 indicates that the image has the maximum possible width. - width: u32, - /// Encodes the maximum height of the output file in *display units*. - /// - /// A value of 0 indicates that the image has the maximum possible height. - height: u32, - /// The number of colors in the color table. - color_count: u32, -} - -impl TvgHeader { - pub fn read<R: Read>(reader: &mut R) -> Result<Self, TvgError> { - // magic number is used as a first line defense against invalid data - let magic = [reader.read_u8()?, reader.read_u8()?]; - if magic != MAGIC { - yeet!(TvgError::InvalidFile); - } - - // the version of tvg being used - let version = reader.read_u8()?; - if version != SUPPORTED_VERSION { - yeet!(TvgError::UnsupportedVersion(version)) - } - - // scale, color_encoding, and coordinate_range are stored in one byte - let byte = reader.read_u8()?; - let scale = Scale::try_from_primitive(byte & 0b0000_1111).expect("invalid scale"); - let color_encoding = ColorEncoding::try_from_primitive((byte & 0b0011_0000) >> 4) - .expect("invalid color encoding"); - let coordinate_range = CoordinateRange::try_from_primitive((byte & 0b1100_0000) >> 6) - .expect("invalid coordinate range"); - - // width and height depend on the coordinate range - let width = read_unsigned_unit(coordinate_range, reader)?; - let height = read_unsigned_unit(coordinate_range, reader)?; - - let color_count = read_varuint(reader)?; - - Ok(Self { - magic, - version, - scale, - color_encoding, - coordinate_range, - width, - height, - color_count, - }) - } - - /// Defines the number of total bits in a Unit value and thus the overall - /// precision of the file. - pub fn coordinate_range(&self) -> CoordinateRange { - self.coordinate_range - } - - /// Defines the type of color information that is used in the [`ColorTable`]. - pub fn color_encoding(&self) -> ColorEncoding { - self.color_encoding - } - - /// Defines the number of fraction bits in a Unit value. - pub fn scale(&self) -> Scale { - self.scale - } - - /// The number of colors in the [`ColorTable`]. - 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>( - coordinate_range: CoordinateRange, - bytes: &mut R, -) -> io::Result<u32> { - Ok(match coordinate_range { - CoordinateRange::Reduced => bytes.read_u8()? as u32, - CoordinateRange::Default => bytes.read_u16::<LittleEndian>()? as u32, - CoordinateRange::Enhanced => bytes.read_u32::<LittleEndian>()?, - }) -} diff --git a/tvg/src/lib.rs b/tvg/src/lib.rs deleted file mode 100644 index 16a95bd..0000000 --- a/tvg/src/lib.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::io::{self, Read}; - -use byteorder::{LittleEndian, ReadBytesExt}; -use colors::{Color, ColorTable}; -use commands::Command; -use header::{CoordinateRange, Scale, TvgHeader}; -use thiserror::Error; - -pub mod colors; -mod commands; -mod header; -mod path; -mod render; - -pub use header::SUPPORTED_VERSION; - -#[derive(Debug, Clone)] -pub struct TvgFile<C: Color> { - header: TvgHeader, - color_table: ColorTable<C>, - commands: Box<[Command]>, -} - -impl<C: Color> TvgFile<C> { - /// Read a TinyVG file. - pub fn read_from(reader: &mut impl Read) -> Result<Self, TvgError> { - let header = TvgHeader::read(reader)?; - let color_table = - ColorTable::read_from_encoding(reader, header.color_count(), header.color_encoding())?; - - let mut commands = Vec::new(); - loop { - let command = Command::read(reader, &header)?; - commands.push(command.clone()); - - if command.is_end_of_document() { - break; - } - } - - Ok(Self { - header, - color_table, - commands: commands.into_boxed_slice(), - }) - } - - /// Read a TinyVG file. If a Custom color encoding if found, use the specified encoding. - pub fn read_with_custom_encoding<Custom: Color>( - reader: &mut impl Read, - ) -> Result<Self, TvgError> { - let header = TvgHeader::read(reader)?; - let color_table = ColorTable::read_from_encoding_with_custom::<Custom>( - reader, - header.color_count(), - header.color_encoding(), - )?; - - let mut commands = Vec::new(); - loop { - let command = Command::read(reader, &header)?; - commands.push(command.clone()); - - if command.is_end_of_document() { - break; - } - } - - Ok(Self { - header, - color_table, - commands: commands.into_boxed_slice(), - }) - } -} - -#[derive(Debug, Error)] -pub enum TvgError { - #[error("Not a valid TVG file. The magic number was invalid.")] - InvalidFile, - #[error("Expected version 1, but found version {}.", .0)] - UnsupportedVersion(u8), - #[error("Found a coordinate range with an index of 3, which is invalid.")] - InvalidCoordinateRange, - #[error("Found a Custom color encoding, which is unsupported.")] - UnsupportedColorEncoding, - #[error("Found a command with an index of {}, which is invalid.", .0)] - InvalidCommand(u8), - #[error("Found a style kind with an index of 3, which is invalid.")] - InvalidStyleKind, - #[error("Polygons must have at least 3 points, found {}.", .0)] - InvalidPolygon(u32), - #[error("All padding bits must be zero. Found {}.", .0)] - NonZeroPadding(u8), - #[error("The end of the document must have a `prim_style_kind` value of 0. Found {}.", .0)] - InvalidEndOfDocument(u8), - #[error("{}", .0)] - IoError(#[from] io::Error), -} - -trait Decode: Sized { - fn read(reader: &mut impl Read, header: &TvgHeader) -> io::Result<Self>; - - fn read_multiple( - reader: &mut impl Read, - header: &TvgHeader, - count: u32, - ) -> io::Result<Box<[Self]>> { - let mut vec = Vec::with_capacity(count as usize); - for _ in 0..count { - vec.push(Self::read(reader, header)?); - } - - Ok(vec.into_boxed_slice()) - } -} - -/// The unit is the common type for both positions and sizes in the vector -/// graphic. It is encoded as a signed integer with a configurable amount of -/// bits (see [`CoordinateRange`]) and fractional bits. -fn read_unit(reader: &mut impl Read, header: &TvgHeader) -> io::Result<f64> { - let value = match header.coordinate_range() { - CoordinateRange::Reduced => reader.read_i8()? as i32, - CoordinateRange::Default => reader.read_i16::<LittleEndian>()? as i32, - CoordinateRange::Enhanced => reader.read_i32::<LittleEndian>()?, - }; - - let fractional = match header.scale() { - Scale::_1 => 1.0, - Scale::_2 => 2.0, - Scale::_4 => 4.0, - Scale::_8 => 8.0, - Scale::_16 => 16.0, - Scale::_32 => 32.0, - Scale::_64 => 64.0, - Scale::_128 => 128.0, - Scale::_256 => 256.0, - Scale::_512 => 512.0, - Scale::_1024 => 1024.0, - Scale::_2048 => 2048.0, - Scale::_4096 => 4096.0, - Scale::_8192 => 8192.0, - Scale::_16384 => 16_384.0, - Scale::_32768 => 32_768.0, - }; - - Ok((value as f64) / fractional) -} - -fn read_varuint(reader: &mut impl Read) -> io::Result<u32> { - let mut count = 0; - let mut result = 0; - - loop { - let byte = reader.read_u8()? as u32; - let value = (byte & 0x7F) << (7 * count); - result |= value; - - if (byte & 0x80) == 0 { - break; - } - - count += 1; - } - - Ok(result) -} diff --git a/tvg/src/path.rs b/tvg/src/path.rs deleted file mode 100644 index a576eb8..0000000 --- a/tvg/src/path.rs +++ /dev/null @@ -1,320 +0,0 @@ -use std::io::Read; - -use byteorder::ReadBytesExt; -use num_enum::TryFromPrimitive; -use raise::yeet; - -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> { - if padding != 0 { - yeet!(TvgError::NonZeroPadding(padding)) - } - - Ok(()) -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)] -#[repr(u8)] -pub(crate) enum Sweep { - Right = 0, - Left = 1, -} - -/// An instruction to move a hypothetical "pen". -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)] -#[repr(u8)] -enum InstructionKind { - /// A straight line is drawn from the current point to a new point. - Line = 0, - /// A straight horizontal line is drawn from the current point to a new x - /// coordinate. - HorizontalLine = 1, - /// A straight vertical line is drawn from the current point to a new y - /// coordiante. - VerticalLine = 2, - /// A cubic Bézier curve is drawn from the current point to a new point. - CubicBezier = 3, - /// A circle segment is drawn from current point to a new point. - ArcCircle = 4, - /// An ellipse segment is drawn from current point to a new point. - ArcEllipse = 5, - /// The path is closed, and a straight line is drawn to the starting point. - ClosePath = 6, - /// A quadratic Bézier curve is drawn from the current point to a new point. - QuadraticBezier = 7, -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum InstructionData { - /// The line instruction draws a straight line to the position. - Line { - /// The end point of the line. - position: Point, - }, - /// The horizontal line instruction draws a straight horizontal line to a - /// given x coordinate. - HorizontalLine { - /// The new x coordinate. - x: f64, - }, - /// The vertical line instruction draws a straight vertical line to a given - /// y coordinate. - VerticalLine { - /// The new y coordinate. - y: f64, - }, - /// The cubic bezier instruction draws a Bézier curve with two control - /// points. - /// - /// The curve is drawn between the current location and `point_1` with - /// `control_0` being the first control point and `control_1` being the - /// second one. - CubicBezier { - /// The first control point. - control_0: Point, - /// The second control point. - control_1: Point, - /// The end point of the Bézier curve. - point_1: Point, - }, - /// Draws a circle segment between the current and the target point. - /// - /// The `radius` field determines the radius of the circle. If the distance - /// between the current point and `target` is larger than `radius`, the - /// distance is used as the radius. - ArcCircle { - /// If `true`, the large portion of the circle segment is drawn. - large_arc: bool, - /// Determines if the circle segment is left- or right bending. - sweep: Sweep, - /// The radius of the circle. - radius: f64, - /// The end point of the circle segment. - target: Point, - }, - /// Draws an ellipse segment between the current and the target point. - /// - /// The `radius_x` and `radius_y` fields determine the both radii of the - /// ellipse. If the distance between the current point and target is not - /// enough to fit any ellipse segment between the two points, `radius_x` - /// and `radius_y` are scaled uniformly so that it fits exactly. - ArcEllipse { - /// If `true`, the large portion of the ellipse segment is drawn. - large_arc: bool, - /// Determines if the ellipse segment is left- or right bending. - sweep: Sweep, - /// The radius of the ellipse segment in the horizontal direction. - radius_x: f64, - /// The radius of the ellipse segment in the vertical direction. - radius_y: f64, - /// The rotation of the ellipse in mathematical negative direction, in - /// degrees. - rotation: f64, - /// The end point of the ellipse segment. - target: Point, - }, - /// A straight line is drawn to the start location of the current segment. - /// This instruction doesn’t have additional data encoded. - ClosePath, - /// The quadratic bezier instruction draws a Bézier curve with a single - /// control point. - /// - /// The curve is drawn between the current location and `point_1` with - /// control being the control point. - QuadraticBezier { - /// The control point. - control: Point, - /// The end point of the Bézier curve. - target: Point, - }, -} - -impl InstructionData { - fn read( - reader: &mut impl Read, - header: &TvgHeader, - kind: InstructionKind, - ) -> Result<Self, TvgError> { - match kind { - InstructionKind::Line => Self::read_line(reader, header), - InstructionKind::HorizontalLine => Self::read_horizontal_line(reader, header), - InstructionKind::VerticalLine => Self::read_vertical_line(reader, header), - InstructionKind::CubicBezier => Self::read_cubic_bezier(reader, header), - InstructionKind::ArcCircle => Self::read_arc_circle(reader, header), - InstructionKind::ArcEllipse => Self::read_arc_ellipse(reader, header), - InstructionKind::ClosePath => Ok(Self::ClosePath), - InstructionKind::QuadraticBezier => Self::read_quadratic_bezier(reader, header), - } - } - - fn read_line(reader: &mut impl Read, header: &TvgHeader) -> Result<Self, TvgError> { - Ok(Self::Line { - position: Point::read(reader, header)?, - }) - } - - fn read_horizontal_line(reader: &mut impl Read, header: &TvgHeader) -> Result<Self, TvgError> { - Ok(Self::HorizontalLine { - x: read_unit(reader, header)?, - }) - } - - fn read_vertical_line(reader: &mut impl Read, header: &TvgHeader) -> Result<Self, TvgError> { - Ok(Self::VerticalLine { - y: read_unit(reader, header)?, - }) - } - - fn read_cubic_bezier(reader: &mut impl Read, header: &TvgHeader) -> Result<Self, TvgError> { - Ok(Self::CubicBezier { - control_0: Point::read(reader, header)?, - control_1: Point::read(reader, header)?, - point_1: Point::read(reader, header)?, - }) - } - - fn read_arc_header( - reader: &mut impl Read, - header: &TvgHeader, - ) -> Result<(bool, Sweep), TvgError> { - // large_arc and sweep are stored in the same byte - let byte = reader.read_u8()?; - let large_arc = (byte & 1) != 0; - let sweep = match byte & 2 { - 0 => Sweep::Left, - _ => Sweep::Right, - }; - - check_padding((byte & 0b1111_1100) >> 2)?; - - Ok((large_arc, sweep)) - } - - fn read_arc_circle(reader: &mut impl Read, header: &TvgHeader) -> Result<Self, TvgError> { - let (large_arc, sweep) = Self::read_arc_header(reader, header)?; - let radius = read_unit(reader, header)?; - let target = Point::read(reader, header)?; - - Ok(Self::ArcCircle { - large_arc, - sweep, - radius, - target, - }) - } - - fn read_arc_ellipse(reader: &mut impl Read, header: &TvgHeader) -> Result<Self, TvgError> { - let (large_arc, sweep) = Self::read_arc_header(reader, header)?; - let radius_x = read_unit(reader, header)?; - let radius_y = read_unit(reader, header)?; - let rotation = read_unit(reader, header)?; - let target = Point::read(reader, header)?; - - Ok(Self::ArcEllipse { - large_arc, - sweep, - radius_x, - radius_y, - rotation, - target, - }) - } - - fn read_quadratic_bezier(reader: &mut impl Read, header: &TvgHeader) -> Result<Self, TvgError> { - Ok(Self::QuadraticBezier { - control: Point::read(reader, header)?, - target: Point::read(reader, header)?, - }) - } -} - -#[derive(Debug, Clone)] -pub(crate) struct Instruction { - /// The width of the line the "pen" makes, if it makes one at all. - pub(crate) line_width: Option<f64>, - /// The arguments to the instruction. - pub(crate) data: InstructionData, -} - -impl Instruction { - fn read(reader: &mut impl Read, header: &TvgHeader) -> Result<Self, TvgError> { - let byte = reader.read_u8()?; - let instruction_kind = - InstructionKind::try_from_primitive(byte & 0b0000_0111).expect("invalid instruction"); - let has_line_width = (byte & 0b0001_0000) != 0; - - check_padding((byte & 0b0000_1000) >> 3)?; - check_padding((byte & 0b1110_0000) >> 5)?; - - let line_width = has_line_width - .then(|| read_unit(reader, header)) - .transpose()?; - let data = InstructionData::read(reader, header, instruction_kind)?; - - Ok(Self { line_width, data }) - } -} - -#[derive(Debug, Clone)] -pub(crate) struct Segment { - /// The starting point of the segment. - pub(crate) start: Point, - /// The list of instructions for tha segment. - pub(crate) instructions: Box<[Instruction]>, -} - -impl Segment { - fn read( - reader: &mut impl Read, - header: &TvgHeader, - segment_length: u32, - ) -> Result<Self, TvgError> { - let start = Point::read(reader, header)?; - - let mut instructions = Vec::with_capacity(segment_length as usize); - for _ in 0..segment_length { - instructions.push(Instruction::read(reader, header)?) - } - - Ok(Segment { - start, - instructions: instructions.into_boxed_slice(), - }) - } -} - -/// Paths describe instructions to create complex 2D graphics. -/// -/// Each path segment generates a shape by moving a ”pen” around. The path this -/// ”pen” takes is the outline of our segment. Each segment, the ”pen” starts -/// at a defined position and is moved by instructions. Each instruction will -/// leave the ”pen” at a new position. The line drawn by our ”pen” is the -/// outline of the shape. -#[derive(Debug, Clone)] -pub struct Path { - pub(crate) segments: Box<[Segment]>, -} - -impl Path { - pub(crate) fn read( - reader: &mut impl Read, - header: &TvgHeader, - segment_count: u32, - ) -> Result<Self, TvgError> { - let mut segment_lengths = Vec::with_capacity(segment_count as usize); - for _ in 0..segment_count { - segment_lengths.push(read_varuint(reader)? + 1); - } - - let mut segments = Vec::with_capacity(segment_count as usize); - for segment_length in segment_lengths { - segments.push(Segment::read(reader, header, segment_length)?); - } - - Ok(Self { - segments: segments.into_boxed_slice(), - }) - } -} diff --git a/tvg/src/render.rs b/tvg/src/render.rs deleted file mode 100644 index f8c15a1..0000000 --- a/tvg/src/render.rs +++ /dev/null @@ -1,506 +0,0 @@ -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!(), - } - } -} diff --git a/tvg/tests/examples/tvg/everything-32.tvg b/tvg/tests/examples/tvg/everything-32.tvg Binary files differdeleted file mode 100644 index 7ea4bdd..0000000 --- a/tvg/tests/examples/tvg/everything-32.tvg +++ /dev/null diff --git a/tvg/tests/examples/tvg/everything.tvg b/tvg/tests/examples/tvg/everything.tvg Binary files differdeleted file mode 100644 index b211c53..0000000 --- a/tvg/tests/examples/tvg/everything.tvg +++ /dev/null diff --git a/tvg/tests/examples/tvg/shield-16.tvg b/tvg/tests/examples/tvg/shield-16.tvg Binary files differdeleted file mode 100644 index aacd3ea..0000000 --- a/tvg/tests/examples/tvg/shield-16.tvg +++ /dev/null diff --git a/tvg/tests/examples/tvg/shield-32.tvg b/tvg/tests/examples/tvg/shield-32.tvg Binary files differdeleted file mode 100644 index a2abc92..0000000 --- a/tvg/tests/examples/tvg/shield-32.tvg +++ /dev/null diff --git a/tvg/tests/examples/tvg/shield-8.tvg b/tvg/tests/examples/tvg/shield-8.tvg Binary files differdeleted file mode 100644 index 57033be..0000000 --- a/tvg/tests/examples/tvg/shield-8.tvg +++ /dev/null diff --git a/tvg/tests/parse.rs b/tvg/tests/parse.rs deleted file mode 100644 index 017ea2c..0000000 --- a/tvg/tests/parse.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::fs::File; - -use alligator_tvg::{colors::Rgba16, TvgFile}; - -#[test] -fn main() { - for entry in std::fs::read_dir("tests/examples/tvg").unwrap() { - let entry = entry.unwrap().file_name(); - let entry = entry.to_string_lossy(); - let mut file = File::open(format!("./tests/examples/tvg/{}", &entry)).unwrap(); - let start = std::time::Instant::now(); - let _: TvgFile<Rgba16> = TvgFile::read_from(&mut file).unwrap(); - println!( - "{:?} successfully pased in {} seconds!", - entry, - start.elapsed().as_secs_f32() - ); - } -} |
