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
}
}
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>()?,
})
}
|