mirror of
https://github.com/Dangoware/sqp.git
synced 2025-04-19 07:12:55 -05:00
210 lines
5.1 KiB
Rust
210 lines
5.1 KiB
Rust
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
|
use std::io::{Cursor, Read, Write};
|
|
|
|
use crate::picture::Error;
|
|
|
|
/// A DPF file header. This must be included at the beginning
|
|
/// of a valid DPF file.
|
|
pub struct Header {
|
|
/// Identifier. Must be set to "dangoimg".
|
|
pub magic: [u8; 8],
|
|
|
|
/// Width of the image in pixels.
|
|
pub width: u32,
|
|
|
|
/// Height of the image in pixels.
|
|
pub height: u32,
|
|
|
|
/// Type of compression used on the data.
|
|
pub compression_type: CompressionType,
|
|
|
|
/// Level of compression. Only applies in Lossy mode, otherwise this value
|
|
/// should be set to 0, and ignored.
|
|
pub quality: u8,
|
|
|
|
/// Format of color data in the image.
|
|
pub color_format: ColorFormat,
|
|
}
|
|
|
|
impl Default for Header {
|
|
fn default() -> Self {
|
|
Self {
|
|
magic: *b"dangoimg",
|
|
width: 0,
|
|
height: 0,
|
|
compression_type: CompressionType::Lossless,
|
|
quality: 0,
|
|
color_format: ColorFormat::Rgba8,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Header {
|
|
pub fn to_bytes(&self) -> [u8; 19] {
|
|
let mut buf = Cursor::new(Vec::new());
|
|
|
|
buf.write_all(&self.magic).unwrap();
|
|
buf.write_u32::<LE>(self.width).unwrap();
|
|
buf.write_u32::<LE>(self.height).unwrap();
|
|
|
|
// Write compression info
|
|
buf.write_u8(self.compression_type.into()).unwrap();
|
|
buf.write_u8(self.quality).unwrap();
|
|
|
|
// Write color format
|
|
buf.write_u8(self.color_format as u8).unwrap();
|
|
|
|
buf.into_inner().try_into().unwrap()
|
|
}
|
|
|
|
/// Length of the header in bytes.
|
|
#[allow(clippy::len_without_is_empty)]
|
|
pub fn len(&self) -> usize {
|
|
19
|
|
}
|
|
|
|
/// Create a header from something implementing [`Read`].
|
|
pub fn read_from<T: Read + ReadBytesExt>(input: &mut T) -> Result<Self, Error> {
|
|
let mut magic = [0u8; 8];
|
|
input.read_exact(&mut magic).unwrap();
|
|
|
|
if magic != *b"dangoimg" {
|
|
return Err(Error::InvalidIdentifier(magic));
|
|
}
|
|
|
|
Ok(Header {
|
|
magic,
|
|
width: input.read_u32::<LE>()?,
|
|
height: input.read_u32::<LE>()?,
|
|
|
|
compression_type: input.read_u8()?.try_into().unwrap(),
|
|
quality: input.read_u8()?,
|
|
color_format: input.read_u8()?.try_into().unwrap(),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// The format of bytes in the image.
|
|
#[repr(u8)]
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum ColorFormat {
|
|
/// RGBA, 8 bits per channel
|
|
Rgba8 = 0,
|
|
|
|
/// RGB, 8 bits per channel
|
|
Rgb8 = 1,
|
|
|
|
/// Grayscale with alpha, 8 bits per channel
|
|
GrayA8 = 2,
|
|
|
|
/// Grayscale, 8 bits per channel
|
|
Gray8 = 3,
|
|
}
|
|
|
|
impl ColorFormat {
|
|
/// Bits per color channel.
|
|
///
|
|
/// Ex. `Rgba8` has `8bpc`
|
|
pub fn bpc(&self) -> u8 {
|
|
match self {
|
|
Self::Rgba8 => 8,
|
|
Self::Rgb8 => 8,
|
|
Self::GrayA8 => 8,
|
|
Self::Gray8 => 8,
|
|
}
|
|
}
|
|
|
|
/// Bits per pixel.
|
|
///
|
|
/// Ex. `Rgba8` has `32bpp`
|
|
pub fn bpp(&self) -> u16 {
|
|
match self {
|
|
Self::Rgba8 => 32,
|
|
Self::Rgb8 => 24,
|
|
Self::GrayA8 => 16,
|
|
Self::Gray8 => 8,
|
|
}
|
|
}
|
|
|
|
/// Number of color channels.
|
|
///
|
|
/// Ex. `Rgba8` has `4` channels
|
|
pub fn channels(&self) -> u16 {
|
|
match self {
|
|
Self::Rgba8 => 4,
|
|
Self::Rgb8 => 3,
|
|
Self::GrayA8 => 2,
|
|
Self::Gray8 => 1,
|
|
}
|
|
}
|
|
|
|
/// The channel in which alpha is contained, or [`None`] if there is none.
|
|
///
|
|
/// Ex. `Rgba8`'s 3rd channel is alpha
|
|
pub fn alpha_channel(&self) -> Option<usize> {
|
|
match self {
|
|
Self::Rgba8 => Some(3),
|
|
Self::Rgb8 => None,
|
|
Self::GrayA8 => Some(1),
|
|
Self::Gray8 => None,
|
|
}
|
|
}
|
|
|
|
/// Pixel Byte Count, The number of bytes per pixel.
|
|
///
|
|
/// Convenience method over [`Self::bpp`]
|
|
pub fn pbc(&self) -> usize {
|
|
(self.bpp() / 8).into()
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u8> for ColorFormat {
|
|
type Error = String;
|
|
|
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
Ok(match value {
|
|
0 => Self::Rgba8,
|
|
1 => Self::Rgb8,
|
|
2 => Self::GrayA8,
|
|
3 => Self::Gray8,
|
|
v => return Err(format!("invalid color format {v}")),
|
|
})
|
|
}
|
|
}
|
|
|
|
/// The type of compression used in the image
|
|
#[repr(u8)]
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum CompressionType {
|
|
/// No compression at all, raw bitmap
|
|
None = 0,
|
|
|
|
/// Lossless compression
|
|
Lossless = 1,
|
|
|
|
/// Lossy Discrete Cosine Transform compression
|
|
LossyDct = 2,
|
|
}
|
|
|
|
impl TryFrom<u8> for CompressionType {
|
|
type Error = String;
|
|
|
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
Ok(match value {
|
|
0 => Self::None,
|
|
1 => Self::Lossless,
|
|
2 => Self::LossyDct,
|
|
v => return Err(format!("invalid compression type {v}"))
|
|
})
|
|
}
|
|
}
|
|
|
|
impl From<CompressionType> for u8 {
|
|
fn from(val: CompressionType) -> Self {
|
|
match val {
|
|
CompressionType::None => 0,
|
|
CompressionType::Lossless => 1,
|
|
CompressionType::LossyDct => 2,
|
|
}
|
|
}
|
|
}
|