Skip to main content

ai_gif/
encoder.rs

1//! # Minimal gif encoder
2
3use alloc::borrow::Cow;
4use alloc::fmt;
5use alloc::vec::Vec;
6use core::error;
7use no_std_io::io;
8use no_std_io::io::Write;
9
10use weezl::{encode::Encoder as LzwEncoder, BitOrder};
11
12use crate::common::{AnyExtension, Block, DisposalMethod, Extension, Frame};
13use crate::traits::WriteBytesExt;
14
15/// The image has incorrect properties, making it impossible to encode as a gif.
16#[derive(Debug)]
17#[non_exhaustive]
18pub enum EncodingFormatError {
19    /// The image has too many colors.
20    TooManyColors,
21    /// The image has no color palette which is required.
22    MissingColorPalette,
23    /// LZW data is not valid for GIF. This may happen when wrong buffer is given to `write_lzw_pre_encoded_frame`
24    InvalidMinCodeSize,
25}
26
27impl error::Error for EncodingFormatError {}
28impl fmt::Display for EncodingFormatError {
29    #[cold]
30    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Self::TooManyColors => write!(fmt, "the image has too many colors"),
33            Self::MissingColorPalette => write!(
34                fmt,
35                "the GIF format requires a color palette but none was given"
36            ),
37            Self::InvalidMinCodeSize => write!(fmt, "LZW data is invalid"),
38        }
39    }
40}
41
42/// Encoding error.
43#[derive(Debug)]
44#[non_exhaustive]
45pub enum EncodingError {
46    /// Frame buffer is too small for the declared dimensions.
47    FrameBufferTooSmallForDimensions,
48    /// Failed to internally allocate a buffer of sufficient size.
49    OutOfMemory,
50    /// Expected a writer but none found.
51    WriterNotFound,
52    /// Returned if the to image is not encodable as a gif.
53    Format(EncodingFormatError),
54    /// Wraps `std::io::Error`.
55    Io(io::Error),
56}
57
58impl fmt::Display for EncodingError {
59    #[cold]
60    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
61        match self {
62            Self::FrameBufferTooSmallForDimensions => {
63                fmt.write_str("Frame Buffer Too Small for Dimensions")
64            }
65            Self::OutOfMemory => fmt.write_str("Out of Memory"),
66            Self::WriterNotFound => fmt.write_str("Writer Not Found"),
67            Self::Io(err) => err.fmt(fmt),
68            Self::Format(err) => err.fmt(fmt),
69        }
70    }
71}
72
73impl error::Error for EncodingError {
74    #[cold]
75    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
76        match self {
77            Self::FrameBufferTooSmallForDimensions => None,
78            Self::OutOfMemory => None,
79            Self::WriterNotFound => None,
80            Self::Format(err) => Some(err),
81            #[cfg(feature = "std")]
82            Self::Io(err) => Some(err),
83            #[cfg(not(feature = "std"))]
84            Self::Io(_) => None,
85        }
86    }
87}
88
89impl From<io::Error> for EncodingError {
90    #[cold]
91    fn from(err: io::Error) -> Self {
92        Self::Io(err)
93    }
94}
95
96impl From<EncodingFormatError> for EncodingError {
97    #[cold]
98    fn from(err: EncodingFormatError) -> Self {
99        Self::Format(err)
100    }
101}
102
103/// Number of repetitions
104#[derive(Copy, Clone, Debug, PartialEq, Eq)]
105pub enum Repeat {
106    /// Finite number of repetitions
107    Finite(u16),
108    /// Infinite number of repetitions
109    Infinite,
110}
111
112impl Default for Repeat {
113    fn default() -> Self {
114        Self::Finite(0)
115    }
116}
117
118/// Extension data.
119#[non_exhaustive]
120pub enum ExtensionData {
121    /// Control extension. Use `ExtensionData::new_control_ext` to construct.
122    Control {
123        /// Flags.
124        flags: u8,
125        /// Frame delay.
126        delay: u16,
127        /// Transparent index.
128        trns: u8,
129    },
130    /// Sets the number of repetitions
131    Repetitions(Repeat),
132}
133
134impl ExtensionData {
135    /// Constructor for control extension data.
136    ///
137    /// `delay` is given in units of 10 ms.
138    #[must_use]
139    pub fn new_control_ext(
140        delay: u16,
141        dispose: DisposalMethod,
142        needs_user_input: bool,
143        trns: Option<u8>,
144    ) -> Self {
145        let mut flags = 0;
146        let trns = match trns {
147            Some(trns) => {
148                flags |= 1;
149                trns
150            }
151            None => 0,
152        };
153        flags |= u8::from(needs_user_input) << 1;
154        flags |= (dispose as u8) << 2;
155        Self::Control { flags, delay, trns }
156    }
157}
158
159impl<W: Write> Encoder<W> {
160    /// Creates a new encoder.
161    ///
162    /// `global_palette` gives the global color palette in the format `[r, g, b, ...]`,
163    /// if no global palette shall be used an empty slice may be supplied.
164    pub fn new(
165        w: W,
166        width: u16,
167        height: u16,
168        global_palette: &[u8],
169    ) -> Result<Self, EncodingError> {
170        Self {
171            w: Some(w),
172            global_palette: false,
173            width,
174            height,
175            buffer: Vec::new(),
176        }
177        .write_global_palette(global_palette)
178    }
179
180    /// Write an extension block that signals a repeat behaviour.
181    pub fn set_repeat(&mut self, repeat: Repeat) -> Result<(), EncodingError> {
182        self.write_extension(ExtensionData::Repetitions(repeat))
183    }
184
185    /// Writes the global color palette.
186    fn write_global_palette(mut self, palette: &[u8]) -> Result<Self, EncodingError> {
187        let mut flags = 0;
188        flags |= 0b1000_0000;
189        let (palette, padding, table_size) = Self::check_color_table(palette)?;
190        self.global_palette = !palette.is_empty();
191        // Size of global color table.
192        flags |= table_size;
193        // Color resolution .. FIXME. This is mostly ignored (by ImageMagick at least) but hey, we
194        // should use some sensible value here or even allow configuring it?
195        flags |= table_size << 4; // wtf flag
196        self.write_screen_desc(flags)?;
197        Self::write_color_table(self.writer()?, palette, padding)?;
198        Ok(self)
199    }
200
201    /// Writes a frame to the image.
202    ///
203    /// Note: This function also writes a control extension if necessary.
204    pub fn write_frame(&mut self, frame: &Frame<'_>) -> Result<(), EncodingError> {
205        if usize::from(frame.width)
206            .checked_mul(usize::from(frame.height))
207            .map_or(true, |size| frame.buffer.len() < size)
208        {
209            return Err(EncodingError::FrameBufferTooSmallForDimensions);
210        }
211        debug_assert!(
212            (frame.width > 0 && frame.height > 0) || frame.buffer.is_empty(),
213            "the frame has 0 pixels, but non-empty buffer"
214        );
215        self.write_frame_header(frame)?;
216        self.write_image_block(&frame.buffer)
217    }
218
219    fn write_frame_header(&mut self, frame: &Frame<'_>) -> Result<(), EncodingError> {
220        self.write_extension(ExtensionData::new_control_ext(
221            frame.delay,
222            frame.dispose,
223            frame.needs_user_input,
224            frame.transparent,
225        ))?;
226        let mut flags = 0;
227        if frame.interlaced {
228            flags |= 0b0100_0000;
229        }
230        let palette = match frame.palette {
231            Some(ref palette) => {
232                flags |= 0b1000_0000;
233                let (palette, padding, table_size) = Self::check_color_table(palette)?;
234                flags |= table_size;
235                Some((palette, padding))
236            }
237            None if self.global_palette => None,
238            _ => {
239                return Err(EncodingError::from(
240                    EncodingFormatError::MissingColorPalette,
241                ))
242            }
243        };
244        let mut tmp = tmp_buf::<10>();
245        tmp.write_le(Block::Image as u8)?;
246        tmp.write_le(frame.left)?;
247        tmp.write_le(frame.top)?;
248        tmp.write_le(frame.width)?;
249        tmp.write_le(frame.height)?;
250        tmp.write_le(flags)?;
251        let writer = self.writer()?;
252        tmp.finish(&mut *writer)?;
253        if let Some((palette, padding)) = palette {
254            Self::write_color_table(writer, palette, padding)?;
255        }
256        Ok(())
257    }
258
259    fn write_image_block(&mut self, data: &[u8]) -> Result<(), EncodingError> {
260        self.buffer.clear();
261        self.buffer
262            .try_reserve(data.len() / 4)
263            .map_err(|_| EncodingError::OutOfMemory)?;
264        lzw_encode(data, &mut self.buffer);
265
266        let writer = self.w.as_mut().ok_or(EncodingError::WriterNotFound)?;
267        Self::write_encoded_image_block(writer, &self.buffer)
268    }
269
270    fn write_encoded_image_block(
271        writer: &mut W,
272        data_with_min_code_size: &[u8],
273    ) -> Result<(), EncodingError> {
274        let (&min_code_size, data) = data_with_min_code_size.split_first().unwrap_or((&2, &[]));
275        writer.write_le(min_code_size)?;
276
277        // Write blocks. `chunks_exact` seems to be slightly faster
278        // than `chunks` according to both Rust docs and benchmark results.
279        let mut iter = data.chunks_exact(0xFF);
280        for full_block in iter.by_ref() {
281            writer.write_le(0xFFu8)?;
282            writer.write_all(full_block)?;
283        }
284        let last_block = iter.remainder();
285        if !last_block.is_empty() {
286            writer.write_le(last_block.len() as u8)?;
287            writer.write_all(last_block)?;
288        }
289        writer.write_le(0u8).map_err(Into::into)
290    }
291
292    fn write_color_table(
293        writer: &mut W,
294        table: &[u8],
295        padding: usize,
296    ) -> Result<(), EncodingError> {
297        writer.write_all(table)?;
298        // Waste some space as of gif spec
299        for _ in 0..padding {
300            writer.write_all(&[0, 0, 0])?;
301        }
302        Ok(())
303    }
304
305    /// returns rounded palette size, number of missing colors, and table size flag
306    fn check_color_table(table: &[u8]) -> Result<(&[u8], usize, u8), EncodingError> {
307        let num_colors = table.len() / 3;
308        if num_colors > 256 {
309            return Err(EncodingError::from(EncodingFormatError::TooManyColors));
310        }
311        let table_size = flag_size(num_colors);
312        let padding = (2 << table_size) - num_colors;
313        Ok((&table[..num_colors * 3], padding, table_size))
314    }
315
316    /// Writes an extension to the image.
317    ///
318    /// It is normally not necessary to call this method manually.
319    pub fn write_extension(&mut self, extension: ExtensionData) -> Result<(), EncodingError> {
320        use self::ExtensionData::*;
321        // 0 finite repetitions can only be achieved
322        // if the corresponting extension is not written
323        if let Repetitions(Repeat::Finite(0)) = extension {
324            return Ok(());
325        }
326        let writer = self.writer()?;
327        writer.write_le(Block::Extension as u8)?;
328        match extension {
329            Control { flags, delay, trns } => {
330                let mut tmp = tmp_buf::<6>();
331                tmp.write_le(Extension::Control as u8)?;
332                tmp.write_le(4u8)?;
333                tmp.write_le(flags)?;
334                tmp.write_le(delay)?;
335                tmp.write_le(trns)?;
336                tmp.finish(&mut *writer)?;
337            }
338            Repetitions(repeat) => {
339                let mut tmp = tmp_buf::<17>();
340                tmp.write_le(Extension::Application as u8)?;
341                tmp.write_le(11u8)?;
342                tmp.write_all(b"NETSCAPE2.0")?;
343                tmp.write_le(3u8)?;
344                tmp.write_le(1u8)?;
345                tmp.write_le(match repeat {
346                    Repeat::Finite(no) => no,
347                    Repeat::Infinite => 0u16,
348                })?;
349                tmp.finish(&mut *writer)?;
350            }
351        }
352        writer.write_le(0u8).map_err(Into::into)
353    }
354
355    /// Writes a raw extension to the image.
356    ///
357    /// This method can be used to write an unsupported extension to the file. `func` is the extension
358    /// identifier (e.g. `Extension::Application as u8`). `data` are the extension payload blocks. If any
359    /// contained slice has a lenght > 255 it is automatically divided into sub-blocks.
360    pub fn write_raw_extension(
361        &mut self,
362        func: AnyExtension,
363        data: &[&[u8]],
364    ) -> Result<(), EncodingError> {
365        let writer = self.writer()?;
366        writer.write_le(Block::Extension as u8)?;
367        writer.write_le(func.0)?;
368        for block in data {
369            for chunk in block.chunks(0xFF) {
370                writer.write_le(chunk.len() as u8)?;
371                writer.write_all(chunk)?;
372            }
373        }
374        Ok(writer.write_le(0u8)?)
375    }
376
377    /// Writes a frame to the image, but expects `Frame.buffer` to contain LZW-encoded data
378    /// from [`Frame::make_lzw_pre_encoded`].
379    ///
380    /// Note: This function also writes a control extension if necessary.
381    pub fn write_lzw_pre_encoded_frame(&mut self, frame: &Frame<'_>) -> Result<(), EncodingError> {
382        // empty data is allowed
383        if let Some(&min_code_size) = frame.buffer.first() {
384            if min_code_size > 11 || min_code_size < 2 {
385                return Err(EncodingError::Format(
386                    EncodingFormatError::InvalidMinCodeSize,
387                ));
388            }
389        }
390
391        self.write_frame_header(frame)?;
392        let writer = self.writer()?;
393        Self::write_encoded_image_block(writer, &frame.buffer)
394    }
395
396    /// Writes the logical screen desriptor
397    fn write_screen_desc(&mut self, flags: u8) -> Result<(), EncodingError> {
398        let mut tmp = tmp_buf::<13>();
399        tmp.write_all(b"GIF89a")?;
400        tmp.write_le(self.width)?;
401        tmp.write_le(self.height)?;
402        tmp.write_le(flags)?; // packed field
403        tmp.write_le(0u8)?; // bg index
404        tmp.write_le(0u8)?; // aspect ratio
405        Ok(tmp.finish(self.writer()?)?)
406    }
407
408    /// Gets a reference to the writer instance used by this encoder.
409    pub fn get_ref(&self) -> &W {
410        self.w.as_ref().unwrap()
411    }
412
413    /// Gets a mutable reference to the writer instance used by this encoder.
414    ///
415    /// It is inadvisable to directly write to the underlying writer.
416    pub fn get_mut(&mut self) -> &mut W {
417        self.w.as_mut().unwrap()
418    }
419
420    /// Finishes writing, and returns the `io::Write` instance used by this encoder
421    pub fn into_inner(mut self) -> Result<W, EncodingError> {
422        self.write_trailer()?;
423        self.w.take().ok_or(EncodingError::WriterNotFound)
424    }
425
426    /// Write the final tailer.
427    fn write_trailer(&mut self) -> Result<(), EncodingError> {
428        Ok(self.writer()?.write_le(Block::Trailer as u8)?)
429    }
430
431    #[inline]
432    fn writer(&mut self) -> Result<&mut W, EncodingError> {
433        self.w.as_mut().ok_or(EncodingError::WriterNotFound)
434    }
435}
436
437/// Encodes the data into the provided buffer.
438///
439/// The first byte is the minimum code size, followed by LZW data.
440fn lzw_encode(data: &[u8], buffer: &mut Vec<u8>) {
441    let mut max_byte = 0;
442    for &byte in data {
443        if byte > max_byte {
444            max_byte = byte;
445            // code size is the same after that
446            if byte > 127 {
447                break;
448            }
449        }
450    }
451    let palette_min_len = u32::from(max_byte) + 1;
452    // As per gif spec: The minimal code size has to be >= 2
453    let min_code_size = palette_min_len.max(4).next_power_of_two().trailing_zeros() as u8;
454    buffer.push(min_code_size);
455    let mut enc = LzwEncoder::new(BitOrder::Lsb, min_code_size);
456    let len = enc.into_vec(buffer).encode_all(data).consumed_out;
457    buffer.truncate(len + 1);
458}
459
460impl Frame<'_> {
461    /// Replace frame's buffer with a LZW-compressed one for use with [`Encoder::write_lzw_pre_encoded_frame`].
462    ///
463    /// Frames can be compressed in any order, separately from the `Encoder`, which can be used to compress frames in parallel.
464    pub fn make_lzw_pre_encoded(&mut self) {
465        let mut buffer = Vec::new();
466        buffer.try_reserve(self.buffer.len() / 2).expect("OOM");
467        lzw_encode(&self.buffer, &mut buffer);
468        self.buffer = Cow::Owned(buffer);
469    }
470}
471
472/// GIF encoder.
473pub struct Encoder<W: Write> {
474    w: Option<W>,
475    global_palette: bool,
476    width: u16,
477    height: u16,
478    buffer: Vec<u8>,
479}
480
481impl<W: Write> Drop for Encoder<W> {
482    #[cfg(feature = "raii_no_panic")]
483    fn drop(&mut self) {
484        if self.w.is_some() {
485            let _ = self.write_trailer();
486        }
487    }
488
489    #[cfg(not(feature = "raii_no_panic"))]
490    fn drop(&mut self) {
491        if self.w.is_some() {
492            self.write_trailer().unwrap();
493        }
494    }
495}
496
497// Color table size converted to flag bits
498fn flag_size(size: usize) -> u8 {
499    (size.clamp(2, 255).next_power_of_two().trailing_zeros() - 1) as u8
500}
501
502#[test]
503fn test_flag_size() {
504    #[rustfmt::skip]
505    fn expected(size: usize) -> u8 {
506        match size {
507            0  ..=2   => 0,
508            3  ..=4   => 1,
509            5  ..=8   => 2,
510            9  ..=16  => 3,
511            17 ..=32  => 4,
512            33 ..=64  => 5,
513            65 ..=128 => 6,
514            129..=256 => 7,
515            _ => 7
516        }
517    }
518
519    for i in 0..300 {
520        assert_eq!(flag_size(i), expected(i));
521    }
522    for i in 4..=255u8 {
523        let expected = match flag_size(1 + i as usize) + 1 {
524            1 => 2,
525            n => n,
526        };
527        let actual = (u32::from(i) + 1)
528            .max(4)
529            .next_power_of_two()
530            .trailing_zeros() as u8;
531        assert_eq!(actual, expected);
532    }
533}
534
535struct Buf<const N: usize> {
536    buf: [u8; N],
537    pos: usize,
538}
539
540impl<const N: usize> Write for Buf<N> {
541    #[inline(always)]
542    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
543        let len = buf.len();
544        let pos = self.pos;
545        self.buf
546            .get_mut(pos..pos + len)
547            .ok_or(io::ErrorKind::WriteZero)?
548            .copy_from_slice(buf);
549        self.pos += len;
550        Ok(len)
551    }
552
553    fn flush(&mut self) -> io::Result<()> {
554        Ok(())
555    }
556}
557
558fn tmp_buf<const N: usize>() -> Buf<N> {
559    Buf {
560        buf: [0; N],
561        pos: 0,
562    }
563}
564
565impl<const N: usize> Buf<N> {
566    #[inline(always)]
567    fn finish(&self, mut w: impl Write) -> io::Result<()> {
568        debug_assert_eq!(self.pos, N);
569        w.write_all(&self.buf)
570    }
571}
572
573#[test]
574fn error_cast() {
575    use alloc::boxed::Box;
576    let _: Box<dyn error::Error> =
577        EncodingError::from(EncodingFormatError::MissingColorPalette).into();
578}