summaryrefslogtreecommitdiff
path: root/tvg/src/colors.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tvg/src/colors.rs')
-rw-r--r--tvg/src/colors.rs333
1 files changed, 333 insertions, 0 deletions
diff --git a/tvg/src/colors.rs b/tvg/src/colors.rs
new file mode 100644
index 0000000..10bc41c
--- /dev/null
+++ b/tvg/src/colors.rs
@@ -0,0 +1,333 @@
+use std::io::{self, Read};
+
+use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
+use num_enum::TryFromPrimitive;
+
+/// 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
+ pub fn read_from_encoding(
+ 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::<Rgba16>::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.
+ fn len(&self) -> usize {
+ self.colors.len()
+ }
+
+ /// Returns a reference to a color, or `None` if out-of-bounds.
+ 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;
+
+ fn red_u16(&self) -> u16;
+ fn blue_u16(&self) -> u16;
+ fn green_u16(&self) -> u16;
+ fn alpha_u16(&self) -> u16;
+}
+
+/// Each color value is encoded as a sequence of four bytes.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+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 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
+ }
+}
+
+/// Each color value is encoded as a sequence of 2 bytes.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+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 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
+ }
+}
+
+/// Each color value is encoded as a sequence of 16 bytes.
+#[derive(Debug, Clone, Copy, PartialEq)]
+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 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
+ }
+}
+
+/// 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 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
+ }
+}