1#[cfg(any(feature = "std", feature = "alloc"))]
2use alloc::{vec, vec::Vec};
3#[cfg(feature = "std")]
4use std::io::Write;
5
6use bytemuck::Pod;
7
8use crate::consts::{QOI_HEADER_SIZE, QOI_OP_INDEX, QOI_OP_RUN, QOI_PADDING, QOI_PADDING_SIZE};
9use crate::error::{Error, Result};
10use crate::header::Header;
11use crate::pixel::{Pixel, SupportedChannels};
12use crate::types::{Channels, ColorSpace, RawChannels};
13#[cfg(feature = "std")]
14use crate::utils::GenericWriter;
15use crate::utils::{unlikely, BytesMut, Writer};
16
17#[allow(clippy::cast_possible_truncation, unused_assignments, unused_variables)]
18fn encode_impl<W: Writer, const N: usize, const R: usize>(
19 mut buf: W, data: &[u8], width: usize, height: usize, stride: usize,
20 read_px: impl Fn(&mut Pixel<N>, &[u8]),
21) -> Result<usize>
22where
23 Pixel<N>: SupportedChannels,
24 [u8; N]: Pod,
25{
26 let cap = buf.capacity();
27
28 let mut index = [Pixel::new(); 256];
29 let mut px_prev = Pixel::new().with_a(0xff);
30 let mut hash_prev = px_prev.hash_index();
31 let mut run = 0_u8;
32 let mut px = Pixel::<N>::new().with_a(0xff);
33 let mut index_allowed = false;
34
35 let n_pixels = width * height;
36
37 let mut i = 0;
38 for row in data.chunks(stride).take(height) {
39 let pixel_row = &row[..width * R];
40 for chunk in pixel_row.chunks_exact(R) {
41 read_px(&mut px, chunk);
42 if px == px_prev {
43 run += 1;
44 if run == 62 || unlikely(i == n_pixels - 1) {
45 buf = buf.write_one(QOI_OP_RUN | (run - 1))?;
46 run = 0;
47 }
48 } else {
49 if run != 0 {
50 #[cfg(not(feature = "reference"))]
51 {
52 buf = buf.write_one(if run == 1 && index_allowed {
54 QOI_OP_INDEX | hash_prev
55 } else {
56 QOI_OP_RUN | (run - 1)
57 })?;
58 }
59 #[cfg(feature = "reference")]
60 {
61 buf = buf.write_one(QOI_OP_RUN | (run - 1))?;
62 }
63 run = 0;
64 }
65 index_allowed = true;
66 let px_rgba = px.as_rgba(0xff);
67 hash_prev = px_rgba.hash_index();
68 let index_px = &mut index[hash_prev as usize];
69 if *index_px == px_rgba {
70 buf = buf.write_one(QOI_OP_INDEX | hash_prev)?;
71 } else {
72 *index_px = px_rgba;
73 buf = px.encode_into(px_prev, buf)?;
74 }
75 px_prev = px;
76 }
77 i += 1;
78 }
79 }
80
81 assert_eq!(i, n_pixels);
82 buf = buf.write_many(&QOI_PADDING)?;
83 Ok(cap.saturating_sub(buf.capacity()))
84}
85
86#[inline]
90pub fn encode_max_len(width: u32, height: u32, channels: impl Into<u8>) -> usize {
91 let (width, height) = (width as usize, height as usize);
92 let n_pixels = width.saturating_mul(height);
93 QOI_HEADER_SIZE
94 + n_pixels.saturating_mul(channels.into() as usize)
95 + n_pixels
96 + QOI_PADDING_SIZE
97}
98
99#[inline]
103pub fn encode_to_buf(
104 buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>, width: u32, height: u32,
105) -> Result<usize> {
106 Encoder::new(&data, width, height)?.encode_to_buf(buf)
107}
108
109#[cfg(any(feature = "alloc", feature = "std"))]
111#[inline]
112pub fn encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<Vec<u8>> {
113 Encoder::new(&data, width, height)?.encode_to_vec()
114}
115
116pub struct EncoderBuilder<'a> {
117 data: &'a [u8],
118 width: u32,
119 height: u32,
120 stride: Option<usize>,
121 raw_channels: Option<RawChannels>,
122 colorspace: Option<ColorSpace>,
123}
124
125impl<'a> EncoderBuilder<'a> {
126 pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized), width: u32, height: u32) -> Self {
128 Self {
129 data: data.as_ref(),
130 width,
131 height,
132 stride: None,
133 raw_channels: None,
134 colorspace: None,
135 }
136 }
137
138 pub const fn stride(mut self, stride: usize) -> Self {
140 self.stride = Some(stride);
141 self
142 }
143
144 pub const fn raw_channels(mut self, raw_channels: RawChannels) -> Self {
146 self.raw_channels = Some(raw_channels);
147 self
148 }
149
150 pub const fn colorspace(mut self, colorspace: ColorSpace) -> Self {
152 self.colorspace = Some(colorspace);
153 self
154 }
155
156 pub fn build(self) -> Result<Encoder<'a>> {
158 let EncoderBuilder { data, width, height, stride, raw_channels, colorspace } = self;
159
160 let size = data.len();
161 let no_stride = stride.is_none();
162 let stride = stride.unwrap_or(
163 size.checked_div(height as usize)
164 .ok_or(Error::InvalidImageDimensions { width, height })?,
165 );
166 let raw_channels = raw_channels.unwrap_or(if stride == width as usize * 3 {
167 RawChannels::Rgb
168 } else {
169 RawChannels::Rgba
170 });
171
172 if stride < width as usize * raw_channels.bytes_per_pixel() {
173 return Err(Error::InvalidImageLength { size, width, height });
174 }
175 if stride * (height - 1) as usize + width as usize * raw_channels.bytes_per_pixel() < size {
176 return Err(Error::InvalidImageLength { size, width, height });
177 }
178 if no_stride && size != width as usize * height as usize * raw_channels.bytes_per_pixel() {
179 return Err(Error::InvalidImageLength { size, width, height });
180 }
181
182 let channels = raw_channels.into();
183 let colorspace = colorspace.unwrap_or_default();
184
185 Ok(Encoder {
186 data,
187 stride,
188 raw_channels,
189 header: Header::try_new(self.width, self.height, channels, colorspace)?,
190 })
191 }
192}
193
194pub struct Encoder<'a> {
196 data: &'a [u8],
197 stride: usize,
198 raw_channels: RawChannels,
199 header: Header,
200}
201
202impl<'a> Encoder<'a> {
203 #[inline]
209 #[allow(clippy::cast_possible_truncation)]
210 pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized), width: u32, height: u32) -> Result<Self> {
211 EncoderBuilder::new(data, width, height).build()
212 }
213
214 #[inline]
219 pub const fn with_colorspace(mut self, colorspace: ColorSpace) -> Self {
220 self.header = self.header.with_colorspace(colorspace);
221 self
222 }
223
224 #[inline]
226 pub const fn channels(&self) -> Channels {
227 self.header.channels
228 }
229
230 #[inline]
232 pub const fn header(&self) -> &Header {
233 &self.header
234 }
235
236 #[inline]
240 pub fn required_buf_len(&self) -> usize {
241 self.header.encode_max_len()
242 }
243
244 #[inline]
248 pub fn encode_to_buf(&self, mut buf: impl AsMut<[u8]>) -> Result<usize> {
249 let buf = buf.as_mut();
250 let size_required = self.required_buf_len();
251 if unlikely(buf.len() < size_required) {
252 return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size_required });
253 }
254 let (head, tail) = buf.split_at_mut(QOI_HEADER_SIZE); head.copy_from_slice(&self.header.encode());
256 let n_written = self.encode_impl_all(BytesMut::new(tail))?;
257 Ok(QOI_HEADER_SIZE + n_written)
258 }
259
260 #[cfg(any(feature = "alloc", feature = "std"))]
262 #[inline]
263 pub fn encode_to_vec(&self) -> Result<Vec<u8>> {
264 let mut out = vec![0_u8; self.required_buf_len()];
265 let size = self.encode_to_buf(&mut out)?;
266 out.truncate(size);
267 Ok(out)
268 }
269
270 #[cfg(feature = "std")]
275 #[inline]
276 pub fn encode_to_stream<W: Write>(&self, writer: &mut W) -> Result<usize> {
277 writer.write_all(&self.header.encode())?;
278 let n_written = self.encode_impl_all(GenericWriter::new(writer))?;
279 Ok(n_written + QOI_HEADER_SIZE)
280 }
281
282 #[inline]
283 fn encode_impl_all<W: Writer>(&self, out: W) -> Result<usize> {
284 let width = self.header.width as usize;
285 let height = self.header.height as usize;
286 let stride = self.stride;
287 match self.raw_channels {
288 RawChannels::Rgb => {
289 encode_impl::<_, 3, 3>(out, self.data, width, height, stride, Pixel::read)
290 }
291 RawChannels::Bgr => {
292 encode_impl::<_, 3, 3>(out, self.data, width, height, stride, |px, c| {
293 px.update_rgb(c[2], c[1], c[0]);
294 })
295 }
296 RawChannels::Rgba => {
297 encode_impl::<_, 4, 4>(out, self.data, width, height, stride, Pixel::read)
298 }
299 RawChannels::Argb => {
300 encode_impl::<_, 4, 4>(out, self.data, width, height, stride, |px, c| {
301 px.update_rgba(c[1], c[2], c[3], c[0]);
302 })
303 }
304 RawChannels::Rgbx => {
305 encode_impl::<_, 3, 4>(out, self.data, width, height, stride, |px, c| {
306 px.read(&c[..3]);
307 })
308 }
309 RawChannels::Xrgb => {
310 encode_impl::<_, 3, 4>(out, self.data, width, height, stride, |px, c| {
311 px.update_rgb(c[1], c[2], c[3]);
312 })
313 }
314 RawChannels::Bgra => {
315 encode_impl::<_, 4, 4>(out, self.data, width, height, stride, |px, c| {
316 px.update_rgba(c[2], c[1], c[0], c[3]);
317 })
318 }
319 RawChannels::Abgr => {
320 encode_impl::<_, 4, 4>(out, self.data, width, height, stride, |px, c| {
321 px.update_rgba(c[3], c[2], c[1], c[0]);
322 })
323 }
324 RawChannels::Bgrx => {
325 encode_impl::<_, 3, 4>(out, self.data, width, height, stride, |px, c| {
326 px.update_rgb(c[2], c[1], c[0]);
327 })
328 }
329 RawChannels::Xbgr => {
330 encode_impl::<_, 4, 4>(out, self.data, width, height, stride, |px, c| {
331 px.update_rgb(c[3], c[2], c[1]);
332 })
333 }
334 }
335 }
336}