#![no_std]
pub const QOI_MAGIC: [u8; 4] = *b"qoif";
pub const QOI_FOOTER: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 1];
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct Pixel {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Pixel {
pub const ZERO: Self = Pixel {
r: 0,
g: 0,
b: 0,
a: 0,
};
pub fn rgb(r: u8, g: u8, b: u8) -> Self {
Self::rgba(r, g, b, 255)
}
pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
pub fn pixel_hash(&self) -> u8 {
(((self.r as usize) * 3
+ (self.g as usize) * 5
+ (self.b as usize) * 7
+ (self.a as usize) * 11)
% 64) as u8
}
}
pub struct CoderState {
pub previous: Pixel,
pub index: [Pixel; 64],
pub run: u8,
}
impl Default for CoderState {
fn default() -> Self {
Self {
previous: Pixel::rgba(0, 0, 0, 255),
index: [Pixel::ZERO; 64],
run: 0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum QoiChannels {
Rgb = 3,
Rgba = 4,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum QoiColorSpace {
SRgbWithLinearAlpha = 0,
AllChannelsLinear = 1,
}
#[derive(Debug, PartialEq, Eq)]
pub struct QoiHeader {
pub width: u32,
pub height: u32,
pub channels: QoiChannels,
pub color_space: QoiColorSpace,
}
impl QoiHeader {
pub fn new(width: u32, height: u32, channels: QoiChannels, color_space: QoiColorSpace) -> Self {
Self {
width,
height,
channels,
color_space,
}
}
pub fn to_bytes(&self) -> [u8; 14] {
let mut bytes = [0; 14];
for (i, &b) in QOI_MAGIC.iter().enumerate() {
bytes[i] = b;
}
for (i, b) in self.width.to_be_bytes().into_iter().enumerate() {
bytes[i + QOI_MAGIC.len()] = b;
}
for (i, b) in self.height.to_be_bytes().into_iter().enumerate() {
bytes[i + QOI_MAGIC.len() + (u32::BITS / 8) as usize] = b;
}
bytes[QOI_MAGIC.len() + 2 * (u32::BITS / 8) as usize] = self.channels.clone() as u8;
bytes[QOI_MAGIC.len() + 2 * (u32::BITS / 8) as usize + 1] = self.color_space.clone() as u8;
bytes
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QoiChunk {
#[non_exhaustive]
Rgb { r: u8, g: u8, b: u8 },
#[non_exhaustive]
Rgba { r: u8, g: u8, b: u8, a: u8 },
#[non_exhaustive]
Index { idx: u8 },
#[non_exhaustive]
Diff {
dr: i8, dg: i8, db: i8, },
#[non_exhaustive]
Luma {
dg: i8, dr_dg: i8, db_dg: i8, },
#[non_exhaustive]
Run { run: u8 },
}
impl QoiChunk {
pub fn new_run(run: u8) -> Self {
debug_assert!(0 < run && run <= 62);
Self::Run { run }
}
pub fn new_index(idx: u8) -> Self {
debug_assert!(idx <= 63);
Self::Index { idx }
}
pub fn new_diff(dr: i8, dg: i8, db: i8) -> Self {
debug_assert!((-2..=1).contains(&dr));
debug_assert!((-2..=1).contains(&dg));
debug_assert!((-2..=1).contains(&db));
Self::Diff { dr, dg, db }
}
pub fn new_luma(dg: i8, dr_dg: i8, db_dg: i8) -> Self {
debug_assert!((-32..=31).contains(&dg));
debug_assert!((-8..=7).contains(&dr_dg));
debug_assert!((-8..=7).contains(&db_dg));
Self::Luma { dg, dr_dg, db_dg }
}
pub fn new_rgb(r: u8, g: u8, b: u8) -> Self {
Self::Rgb { r, g, b }
}
pub fn new_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self::Rgba { r, g, b, a }
}
fn write_to_chunk_buffer(&self, buf: &mut ChunkBuf) {
match self.clone() {
QoiChunk::Rgb { r, g, b } => {
buf.set([0b11111110, r, g, b])
}
QoiChunk::Rgba { r, g, b, a } => {
buf.set([0b11111111, r, g, b, a])
}
QoiChunk::Index { idx } => {
buf.set([0b00111111 & idx])
}
QoiChunk::Diff { dr, dg, db } => {
buf.set([0b01000000
| (0b00111111
& ((0b11 & (dr + 2) as u8) << 4
| (0b11 & (dg + 2) as u8) << 2
| (0b11 & (db + 2) as u8)))])
}
QoiChunk::Luma { dg, dr_dg, db_dg } => {
buf.set([
0b10000000 | (0b00111111 & (dg + 32) as u8),
(0b1111 & (dr_dg + 8) as u8) << 4 | (0b1111 & (db_dg + 8) as u8),
])
}
QoiChunk::Run { run } => {
debug_assert!(run <= 62);
buf.set([0b11000000 | (run - 1)]);
}
}
}
}
impl IntoIterator for QoiChunk {
type Item = u8;
type IntoIter = ChunkBuf;
fn into_iter(self) -> Self::IntoIter {
let mut buf = ChunkBuf::new();
self.write_to_chunk_buffer(&mut buf);
buf
}
}
pub struct ChunkBuf {
data: [u8; 5],
len: u8,
offset: u8,
}
trait ChunkData {}
impl ChunkData for [u8; 1] {}
impl ChunkData for [u8; 2] {}
impl ChunkData for [u8; 3] {}
impl ChunkData for [u8; 4] {}
impl ChunkData for [u8; 5] {}
impl ChunkBuf {
pub fn new() -> Self {
ChunkBuf {
data: [0; 5],
len: 0,
offset: 0,
}
}
fn set<const N: usize>(&mut self, data: [u8; N])
where
[u8; N]: ChunkData,
{
(0..N).for_each(|i| {
self.data[i] = data[i];
});
self.offset = 0;
self.len = N as u8;
}
pub fn as_slice(&self) -> &[u8] {
&self.data[0..self.len as usize]
}
}
impl Iterator for ChunkBuf {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
if self.offset < self.len {
let res = self.data[self.offset as usize];
self.offset += 1;
Some(res)
} else {
None
}
}
}
impl Default for ChunkBuf {
fn default() -> Self {
Self::new()
}
}