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(reader: &mut R) -> Result { // 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 } } fn read_unsigned_unit( coordinate_range: CoordinateRange, bytes: &mut R, ) -> io::Result { Ok(match coordinate_range { CoordinateRange::Reduced => bytes.read_u8()? as u32, CoordinateRange::Default => bytes.read_u16::()? as u32, CoordinateRange::Enhanced => bytes.read_u32::()?, }) }