rapid_qoi/encode.rs
1use super::*;
2
3#[cfg(feature = "alloc")]
4use alloc::{vec, vec::Vec};
5
6/// Errors that may occur during image encoding.
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
8pub enum EncodeError {
9 /// Pixels buffer is too small for the image.
10 NotEnoughPixelData,
11
12 /// Output buffer is too small to fit encoded image.
13 OutputIsTooSmall,
14}
15
16impl Display for EncodeError {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 match self {
19 EncodeError::NotEnoughPixelData => f.write_str("Pixels buffer is too small for image"),
20 EncodeError::OutputIsTooSmall => {
21 f.write_str("Output buffer is too small to fit encoded image")
22 }
23 }
24 }
25}
26
27#[cfg(feature = "std")]
28impl std::error::Error for EncodeError {}
29
30impl Qoi {
31 /// Encode raw RGB or RGBA pixels into a QOI image.\
32 /// Encoded image is written into `output` slice.
33 ///
34 /// On success this function returns `Ok(())`.\
35 /// On failure this function returns `Err(err)` with `err` describing cause of the error.
36 #[inline]
37 pub fn encode(&self, pixels: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
38 if output.len() <= QOI_HEADER_SIZE {
39 return Err(EncodeError::OutputIsTooSmall);
40 }
41
42 output[0..4].copy_from_slice(&QOI_MAGIC.to_be_bytes());
43 output[4..8].copy_from_slice(&self.width.to_be_bytes());
44 output[8..12].copy_from_slice(&self.height.to_be_bytes());
45
46 let channels = match self.colors {
47 Colors::Rgb => {
48 output[12] = 3;
49 output[13] = 1;
50 3
51 }
52 Colors::Rgba => {
53 output[12] = 4;
54 output[13] = 1;
55 4
56 }
57 Colors::Srgb => {
58 output[12] = 3;
59 output[13] = 0;
60 3
61 }
62 Colors::SrgbLinA => {
63 output[12] = 4;
64 output[13] = 0;
65 4
66 }
67 };
68
69 let px_len = self.width as usize * self.height as usize * channels;
70
71 let pixels = match pixels.get(..px_len) {
72 None => {
73 cold();
74 return Err(EncodeError::NotEnoughPixelData);
75 }
76 Some(pixels) => pixels,
77 };
78
79 let size = match self.colors.has_alpha() {
80 true => Self::encode_range::<4>(
81 &mut [Pixel::new(); 64],
82 &mut Pixel::new_opaque(),
83 &mut 0,
84 pixels,
85 &mut output[QOI_HEADER_SIZE..],
86 )?,
87 false => Self::encode_range::<3>(
88 &mut [Pixel::new(); 64],
89 &mut Pixel::new_opaque(),
90 &mut 0,
91 pixels,
92 &mut output[QOI_HEADER_SIZE..],
93 )?,
94 };
95
96 if output.len() < size + QOI_PADDING + QOI_HEADER_SIZE {
97 return Err(EncodeError::OutputIsTooSmall);
98 }
99
100 output[QOI_HEADER_SIZE + size..][..QOI_PADDING - 1].fill(0);
101 output[QOI_HEADER_SIZE + size + QOI_PADDING - 1] = 1;
102
103 Ok(size + QOI_PADDING + QOI_HEADER_SIZE)
104 }
105
106 /// Encode range of pixels into output slice.
107 #[inline]
108 pub fn encode_range<const N: usize>(
109 index: &mut [[u8; N]; 64],
110 px_prev: &mut [u8; N],
111 run: &mut usize,
112 pixels: &[u8],
113 output: &mut [u8],
114 ) -> Result<usize, EncodeError>
115 where
116 [u8; N]: Pixel,
117 {
118 let mut rest = &mut *output;
119
120 assert_eq!(pixels.len() % N, 0);
121
122 // let mut chunks = pixels.chunks_exact(N);
123 let mut pixels = bytemuck::cast_slice::<_, [u8; N]>(pixels);
124
125 loop {
126 match pixels {
127 // Some(chunk) => {
128 [px, tail @ ..] => {
129 pixels = tail;
130 if likely(rest.len() > 7) {
131 if *px == *px_prev {
132 if *run == 61 || unlikely(pixels.is_empty()) {
133 rest[0] = QOI_OP_RUN | (*run as u8);
134 rest = &mut rest[1..];
135 *run = 0;
136 } else {
137 *run += 1;
138 }
139 } else {
140 match run {
141 0 => {}
142 1 => {
143 // While not following reference encoder
144 // this produces valid QOI and have the exactly same size.
145 // Decoding is slightly faster.
146 let index_pos = px_prev.hash();
147 if unlikely(index_pos == 0x35 && index[0x35] == [0; N]) {
148 rest[0] = QOI_OP_RUN;
149 } else {
150 rest[0] = QOI_OP_INDEX | index_pos as u8;
151 }
152 rest = &mut rest[1..];
153 *run = 0;
154 }
155 _ => {
156 rest[0] = QOI_OP_RUN | (*run - 1) as u8;
157 rest = &mut rest[1..];
158 *run = 0;
159 }
160 }
161
162 match rest {
163 [b1, b2, b3, b4, b5, ..] => {
164 let index_pos = px.hash();
165
166 if index[index_pos as usize] == *px {
167 *b1 = QOI_OP_INDEX | index_pos as u8;
168 rest = &mut rest[1..];
169 } else {
170 index[index_pos as usize] = *px;
171
172 if N == 4 && px_prev.a() != px.a() {
173 cold();
174 let [r, g, b, a] = px.rgba();
175 *b1 = QOI_OP_RGBA;
176 *b2 = r;
177 *b3 = g;
178 *b4 = b;
179 *b5 = a;
180 rest = &mut rest[5..];
181 } else {
182 let v = px.var(px_prev);
183
184 if let Some(diff) = v.diff() {
185 *b1 = diff;
186 rest = &mut rest[1..];
187 } else if let Some([lu, ma]) = v.luma() {
188 *b1 = lu;
189 *b2 = ma;
190 rest = &mut rest[2..];
191 } else {
192 let [r, g, b] = px.rgb();
193 *b1 = QOI_OP_RGB;
194 *b2 = r;
195 *b3 = g;
196 *b4 = b;
197 rest = &mut rest[4..];
198 }
199 }
200 }
201 *px_prev = *px;
202 }
203 _ => {
204 cold();
205 unreachable!()
206 }
207 }
208 }
209 } else {
210 return Err(EncodeError::OutputIsTooSmall);
211 }
212 }
213 // None => {
214 [] => {
215 cold();
216 break;
217 }
218 }
219 }
220
221 let tail = rest.len();
222
223 Ok(output.len() - tail)
224 }
225
226 /// Returns maximum size of the `Qoi::encode` output size.\
227 /// Using smaller slice may cause `Qoi::encode` to return `Err(EncodeError::OutputIsTooSmall)`.
228 #[inline]
229 pub fn encoded_size_limit(&self) -> usize {
230 self.width as usize * self.height as usize * (self.colors.has_alpha() as usize + 4)
231 + QOI_HEADER_SIZE
232 + QOI_PADDING
233 }
234
235 /// Encode raw RGB or RGBA pixels into a QOI image.\
236 /// Encoded image is written into allocated `Vec`.
237 ///
238 /// On success this function returns `Ok(vec)` with `vec` containing encoded image.\
239 /// On failure this function returns `Err(err)` with `err` describing cause of the error.
240 #[cfg(feature = "alloc")]
241 #[inline]
242 pub fn encode_alloc(&self, pixels: &[u8]) -> Result<Vec<u8>, EncodeError> {
243 let limit = self.encoded_size_limit();
244 let mut output = vec![0; limit];
245 match self.encode(pixels, &mut output) {
246 Ok(size) => {
247 output.truncate(size);
248 Ok(output)
249 }
250 Err(EncodeError::OutputIsTooSmall) => unreachable(),
251 Err(err) => Err(err),
252 }
253 }
254}