jqoiview/
lib.rs

1use std::{
2    fs::File,
3    io::{
4        Read,
5        Result,
6        Seek,
7        SeekFrom,
8    }
9};
10
11pub struct Header {
12    pub width: u32,
13    pub height: u32,
14    pub channels: u8,
15    pub colorspace: u8,
16}
17impl Header {
18    pub const SIZE: u64 = 14;
19    pub fn from_file(f: &mut File) -> Result<Self> {
20        f.seek(SeekFrom::Start(0))?;
21        let mut buf: [u8; 4] = [0u8; 4];
22        f.read(&mut buf)?;
23        assert_eq!(buf, [113u8, 111u8, 105u8, 102u8]);
24
25        f.read(&mut buf)?;
26        let width = u32::from_be_bytes(buf);
27
28        f.read(&mut buf)?;
29        let height = u32::from_be_bytes(buf);
30
31        f.read(&mut buf)?;
32        let channels = buf[0];
33        let colorspace = buf[1];
34
35        let mut buf: [u8; 8] = [0u8; 8];
36        f.seek(SeekFrom::End(-8))?;
37        f.read(&mut buf)?;
38        assert_eq!(buf, [0, 0, 0, 0, 0, 0, 0, 1]);
39
40        Ok(Self {
41            width,
42            height,
43            channels,
44            colorspace,
45        })
46    }
47}
48
49pub type Pix = (u8, u8, u8, u8);
50pub fn hash((r, g, b, a): (u8, u8, u8, u8)) -> usize {
51    let (r, g, b, a) = (
52        (r as usize) * 3,
53        (g as usize) * 5,
54        (b as usize) * 7,
55        (a as usize) * 11,
56    );
57    (r + g + b + a) % 64
58} 
59
60const QOI_OP_RGB: u8    = 0b11111110;
61const QOI_OP_RGBA: u8   = 0b11111111;
62const QOI_OP_INDEX: u8  = 0b00000000;
63const QOI_OP_DIFF: u8   = 0b01000000;
64const QOI_OP_LUMA: u8   = 0b10000000;
65const QOI_OP_RUN: u8    = 0b11000000;
66
67pub enum Chunk {
68    RGB(u8, u8, u8),
69    RGBA(u8, u8, u8, u8),
70    INDEX(u8),
71    DIFF(u8, u8, u8),
72    LUMA(u8, u8, u8),
73    RUN(u8),
74}
75impl Chunk {
76    pub fn parse(&self, curr: Pix, index: &[Pix]) -> (Pix, u8) {
77        match *self {
78            RGB(r, g, b) => ((r, g, b, curr.3), 0),
79            RGBA(r, g, b, a) => ((r, g, b, a), 0), 
80            INDEX(i) => (index[i as usize], 0),
81            DIFF(dr, dg, db) => ((
82                curr.0
83                    .wrapping_add(dr)
84                    .wrapping_sub(2),
85                curr.1
86                    .wrapping_add(dg)
87                    .wrapping_sub(2),
88                curr.2
89                    .wrapping_add(db)
90                    .wrapping_sub(2),
91                curr.3,
92            ), 0),
93            LUMA(dg, drdg, dbdg) => ((
94                curr.0
95                    .wrapping_add(dg)
96                    .wrapping_sub(40)
97                    .wrapping_add(drdg),
98                curr.1
99                    .wrapping_add(dg)
100                    .wrapping_sub(32),
101                curr.2
102                    .wrapping_add(dg)
103                    .wrapping_sub(40)
104                    .wrapping_add(dbdg),
105                curr.3,
106            ), 0),
107            RUN(len) => (curr, len), 
108        }
109    }
110}
111
112use Chunk::*;
113
114pub struct ChunkIter<I: Iterator<Item=u8>> (I);
115
116impl<I> Iterator for ChunkIter<I> 
117where
118    I: Iterator<Item = u8>
119{
120    type Item = Chunk;
121
122    fn next(&mut self) -> Option<Chunk> {
123        let Self(iter) = self;
124        let b1 = iter.next()?;
125        match b1 {
126            QOI_OP_RGB => Some(RGB(
127                iter.next()?, 
128                iter.next()?, 
129                iter.next()?,
130            )),
131            QOI_OP_RGBA => Some(RGBA(
132                iter.next()?, 
133                iter.next()?, 
134                iter.next()?, 
135                iter.next()?,
136            )),
137            b1 => match b1 & 0b11000000 {
138                QOI_OP_INDEX => Some(INDEX(b1 & 0b00111111)),
139                QOI_OP_DIFF => Some(DIFF(b1 >> 4 & 3, b1 >> 2 & 3, b1 & 3)),
140                QOI_OP_LUMA => {
141                    let b2 = iter.next()?;
142                    Some(LUMA(
143                        b1 & 0b00111111,
144                        (b2 & 0b11110000) >> 4,
145                        b2 & 0b00001111, 
146                    ))
147                },
148                QOI_OP_RUN => Some(RUN(b1 & 0b00111111)),
149                _ => None,
150            }
151        }
152    }
153}
154pub trait Chunks {
155    fn chunks(self) -> ChunkIter<Self> 
156    where
157        Self: Sized + Iterator<Item = u8>;
158}
159impl<I> Chunks for I 
160where
161    I: Iterator<Item = u8>
162{
163    fn chunks(self) -> ChunkIter<Self> {
164        ChunkIter(self)
165    }
166}