#![forbid(unsafe_code)]
#![deny(missing_copy_implementations)]
#![deny(missing_debug_implementations)]
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "alloc")]
extern crate alloc;
use core::{
convert::TryInto,
fmt::{self, Display},
};
mod decode;
mod encode;
pub use decode::DecodeError;
pub use encode::EncodeError;
const QOI_OP_INDEX: u8 = 0x00;
const QOI_OP_DIFF: u8 = 0x40;
const QOI_OP_LUMA: u8 = 0x80;
const QOI_OP_RUN: u8 = 0xc0;
const QOI_OP_RGB: u8 = 0xfe;
const QOI_OP_RGBA: u8 = 0xff;
const QOI_MAGIC: u32 = u32::from_be_bytes(*b"qoif");
const QOI_HEADER_SIZE: usize = 14;
const QOI_PADDING: usize = 8;
pub trait Pixel: Copy + Eq {
const HAS_ALPHA: bool;
fn new() -> Self;
fn new_opaque() -> Self;
fn read(&mut self, bytes: &[u8]);
fn write(&self, bytes: &mut [u8]);
fn var(&self, prev: &Self) -> Var;
fn rgb(&self) -> [u8; 3];
fn rgba(&self) -> [u8; 4];
fn r(&self) -> u8;
fn g(&self) -> u8;
fn b(&self) -> u8;
fn a(&self) -> u8;
fn set_r(&mut self, r: u8);
fn set_g(&mut self, g: u8);
fn set_b(&mut self, b: u8);
fn set_a(&mut self, a: u8);
fn set_rgb(&mut self, r: u8, g: u8, b: u8);
fn set_rgba(&mut self, r: u8, g: u8, b: u8, a: u8);
fn add_rgb(&mut self, r: u8, g: u8, b: u8);
fn hash(&self) -> u8;
}
impl Pixel for [u8; 3] {
const HAS_ALPHA: bool = false;
#[inline]
fn new() -> Self {
[0; 3]
}
#[inline]
fn new_opaque() -> Self {
[0; 3]
}
#[inline]
fn read(&mut self, bytes: &[u8]) {
self.copy_from_slice(bytes);
}
#[inline]
fn write(&self, bytes: &mut [u8]) {
assert_eq!(bytes.len(), self.len());
bytes.copy_from_slice(self)
}
#[inline]
fn var(&self, prev: &Self) -> Var {
let r = self[0].wrapping_sub(prev[0]);
let g = self[1].wrapping_sub(prev[1]);
let b = self[2].wrapping_sub(prev[2]);
Var { r, g, b }
}
#[inline]
fn r(&self) -> u8 {
self[0]
}
#[inline]
fn g(&self) -> u8 {
self[1]
}
#[inline]
fn b(&self) -> u8 {
self[2]
}
#[inline]
fn rgb(&self) -> [u8; 3] {
*self
}
#[inline]
fn rgba(&self) -> [u8; 4] {
unreachable!()
}
#[inline]
fn a(&self) -> u8 {
255
}
#[inline]
fn set_r(&mut self, r: u8) {
self[0] = r;
}
#[inline]
fn set_g(&mut self, g: u8) {
self[1] = g;
}
#[inline]
fn set_b(&mut self, b: u8) {
self[2] = b;
}
#[inline]
fn set_a(&mut self, a: u8) {
debug_assert_eq!(a, 255);
}
#[inline]
fn set_rgb(&mut self, r: u8, g: u8, b: u8) {
self[0] = r;
self[1] = g;
self[2] = b;
}
#[inline]
fn set_rgba(&mut self, r: u8, g: u8, b: u8, a: u8) {
debug_assert_eq!(a, 255);
self[0] = r;
self[1] = g;
self[2] = b;
}
#[inline]
fn add_rgb(&mut self, r: u8, g: u8, b: u8) {
self[0] = self[0].wrapping_add(r);
self[1] = self[1].wrapping_add(g);
self[2] = self[2].wrapping_add(b);
}
#[inline]
fn hash(&self) -> u8 {
let [r, g, b] = *self;
let v = u32::from_ne_bytes([r, g, b, 0xff]);
let s = (((v as u64) << 32) | (v as u64)) & 0xFF00FF0000FF00FF;
s.wrapping_mul(0x030007000005000Bu64.to_le()).swap_bytes() as u8 & 63
}
}
impl Pixel for [u8; 4] {
const HAS_ALPHA: bool = true;
#[inline]
fn new() -> Self {
[0; 4]
}
#[inline]
fn new_opaque() -> Self {
[0, 0, 0, 0xff]
}
#[inline]
fn read(&mut self, bytes: &[u8]) {
match bytes.try_into() {
Ok(rgba) => {
*self = rgba;
}
_ => unreachable(),
}
}
#[inline]
fn write(&self, bytes: &mut [u8]) {
assert_eq!(bytes.len(), self.len());
bytes.copy_from_slice(self)
}
#[inline]
fn var(&self, prev: &Self) -> Var {
let [r, g, b, a] = *self;
let [pr, pg, pb, pa] = *prev;
debug_assert_eq!(a, pa);
let r = r.wrapping_sub(pr);
let g = g.wrapping_sub(pg);
let b = b.wrapping_sub(pb);
Var { r, g, b }
}
#[inline]
fn r(&self) -> u8 {
self[0]
}
#[inline]
fn g(&self) -> u8 {
self[1]
}
#[inline]
fn b(&self) -> u8 {
self[2]
}
#[inline]
fn rgb(&self) -> [u8; 3] {
let [r, g, b, _] = *self;
[r, g, b]
}
#[inline]
fn rgba(&self) -> [u8; 4] {
*self
}
#[inline]
fn a(&self) -> u8 {
self[3]
}
#[inline]
fn set_r(&mut self, r: u8) {
self[0] = r;
}
#[inline]
fn set_g(&mut self, g: u8) {
self[1] = g;
}
#[inline]
fn set_b(&mut self, b: u8) {
self[2] = b;
}
#[inline]
fn set_a(&mut self, a: u8) {
self[3] = a;
}
#[inline]
fn set_rgb(&mut self, r: u8, g: u8, b: u8) {
*self = [r, g, b, self[3]];
}
#[inline]
fn set_rgba(&mut self, r: u8, g: u8, b: u8, a: u8) {
*self = [r, g, b, a];
}
#[inline]
fn add_rgb(&mut self, r: u8, g: u8, b: u8) {
self[0] = self[0].wrapping_add(r);
self[1] = self[1].wrapping_add(g);
self[2] = self[2].wrapping_add(b);
}
#[inline]
fn hash(&self) -> u8 {
let v = u32::from_ne_bytes(*self);
let s = (((v as u64) << 32) | (v as u64)) & 0xFF00FF0000FF00FF;
s.wrapping_mul(0x030007000005000Bu64.to_le()).swap_bytes() as u8 & 63
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct Var {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl Var {
#[inline]
fn diff(&self) -> Option<u8> {
let r = self.r.wrapping_add(2);
let g = self.g.wrapping_add(2);
let b = self.b.wrapping_add(2);
match r | g | b {
0x00..=0x03 => Some(QOI_OP_DIFF | (r << 4) as u8 | (g << 2) as u8 | b as u8),
_ => None,
}
}
#[inline]
fn luma(&self) -> Option<[u8; 2]> {
let r = self.r.wrapping_add(8).wrapping_sub(self.g);
let g = self.g.wrapping_add(32);
let b = self.b.wrapping_add(8).wrapping_sub(self.g);
match (r | b, g) {
(0x00..=0x0F, 0x00..=0x3F) => Some([QOI_OP_LUMA | g, r << 4 | b]),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum Colors {
Srgb,
SrgbLinA,
Rgb,
Rgba,
}
impl Colors {
#[inline]
pub const fn has_alpha(&self) -> bool {
match self {
Colors::Rgb | Colors::Srgb => false,
Colors::Rgba | Colors::SrgbLinA => true,
}
}
#[inline]
pub const fn channels(&self) -> usize {
match self {
Colors::Rgb | Colors::Srgb => 3,
Colors::Rgba | Colors::SrgbLinA => 4,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Qoi {
pub width: u32,
pub height: u32,
pub colors: Colors,
}
#[inline]
#[cold]
const fn cold() {}
#[inline]
const fn likely(b: bool) -> bool {
if !b {
cold();
}
b
}
#[inline]
const fn unlikely(b: bool) -> bool {
if !b {
cold();
}
b
}
#[inline]
#[cold]
#[allow(clippy::empty_loop)]
const fn unreachable() -> ! {
loop {}
}