use std::num::{NonZeroU32, NonZeroUsize};
use byteorder::{ByteOrder as _, WriteBytesExt as _, BE};
use crate::pixel::QoiPixel;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct QoiDesc {
width: NonZeroU32,
height: NonZeroU32,
channels: QoiChannels,
colorspace: u8,
}
impl QoiDesc {
pub fn new(width: NonZeroU32, height: NonZeroU32) -> Self {
Self::with_color_info(width, height, QoiChannels::Rgba, 0b0001)
}
pub fn with_color_info(
width: NonZeroU32,
height: NonZeroU32,
channels: QoiChannels,
colorspace: u8,
) -> Self {
assert!(
(width.get() as usize)
.checked_mul(height.get() as usize)
.is_some(),
"pixel count overflow"
);
Self {
width,
height,
channels,
colorspace,
}
}
pub fn width(&self) -> NonZeroU32 {
self.width
}
pub fn height(&self) -> NonZeroU32 {
self.height
}
pub fn channels(&self) -> QoiChannels {
self.channels
}
pub fn colorspace(&self) -> u8 {
self.colorspace
}
pub fn pixel_count(&self) -> NonZeroUsize {
NonZeroUsize::new((self.width.get() as usize) * (self.height.get() as usize))
.expect("pixel count should be positive")
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum QoiChannels {
Rgb,
Rgba,
}
impl QoiChannels {
pub(crate) fn byte_count(self) -> usize {
match self {
Self::Rgb => 3,
Self::Rgba => 4,
}
}
pub(crate) fn read_pixel_from_buffer(self, buf: impl AsRef<[u8]>) -> QoiPixel {
let buf = buf.as_ref();
match self {
Self::Rgb => {
let value = BE::read_u24(buf);
QoiPixel::from_raw((value << 8) | 0xFF)
}
Self::Rgba => {
let value = BE::read_u32(buf);
QoiPixel::from_raw(value)
}
}
}
pub(crate) fn write_pixel<W>(self, wtr: &mut W, px: QoiPixel) -> std::io::Result<()>
where
W: std::io::Write,
{
match self {
Self::Rgb => wtr.write_u24::<BE>(px.get() >> 8),
Self::Rgba => wtr.write_u32::<BE>(px.get()),
}
}
pub(crate) fn write_pixel_to_buffer(self, buf: &mut [u8], px: QoiPixel) {
match self {
Self::Rgb => BE::write_u24(buf, px.get() >> 8),
Self::Rgba => BE::write_u32(buf, px.get()),
}
}
}
pub(crate) const QOI_PADDING_LEN: usize = 4;
pub(crate) const QOI_TAG_INDEX: u8 = 0b00;
pub(crate) const QOI_TAG_RUN_8: u8 = 0b010;
pub(crate) const QOI_TAG_RUN_16: u8 = 0b011;
pub(crate) const QOI_TAG_DIFF_8: u8 = 0b10;
pub(crate) const QOI_TAG_DIFF_16: u8 = 0b110;
pub(crate) const QOI_TAG_DIFF_24: u8 = 0b1110;
pub(crate) const QOI_TAG_COLOR: u8 = 0b1111;
pub(crate) const QOI_MAGIC: &[u8] = b"qoif";
pub(crate) const QOI_HEADER_LEN: usize = 14;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qoi_desc_new() {
let desc = QoiDesc::new(NonZeroU32::new(400).unwrap(), NonZeroU32::new(300).unwrap());
assert_eq!(desc.width().get(), 400);
assert_eq!(desc.height().get(), 300);
assert_eq!(desc.channels(), QoiChannels::Rgba);
assert_eq!(desc.colorspace(), 0b0001);
assert_eq!(desc.pixel_count().get(), 400 * 300);
}
#[test]
fn test_qoi_desc_with_color_info() {
let desc = QoiDesc::with_color_info(
NonZeroU32::new(400).unwrap(),
NonZeroU32::new(300).unwrap(),
QoiChannels::Rgb,
42,
);
assert_eq!(desc.width().get(), 400);
assert_eq!(desc.height().get(), 300);
assert_eq!(desc.channels(), QoiChannels::Rgb);
assert_eq!(desc.colorspace(), 42);
assert_eq!(desc.pixel_count().get(), 400 * 300);
}
}