gif_dispose/
screen.rs

1use rgb::bytemuck;
2use super::Error;
3use crate::disposal::Disposal;
4use imgref::{Img, ImgRef, ImgVec};
5use rgb::{RGB8, RGBA8};
6use std::io;
7
8/// Combined GIF frames forming a "virtual screen". See [`Screen::new_decoder`].
9///
10/// Pixel type can be `RGB8` or `RGBA8`. The size is overall GIF size (grater or equal individual frame sizes).
11pub struct Screen {
12    /// Result of combining all frames so far. It's in RGB/RGBA.
13    internal_pixels: ImgVec<RGBA8>,
14
15    global_pal: Option<[RGB8; 256]>,
16    next_disposal: Disposal,
17}
18
19impl Screen {
20    /// Create an new `Screen`
21    ///
22    /// Make sure Reader is set to use `Indexed` color.
23    /// `options.set_color_output(gif::ColorOutput::Indexed);`
24    #[must_use]
25    pub fn new_decoder<T: io::Read>(reader: &gif::Decoder<T>) -> Self {
26        let w = reader.width();
27        let h = reader.height();
28        let pal = reader.global_palette().map(bytemuck::cast_slice);
29        Self::new(w.into(), h.into(), pal)
30    }
31
32    /// Manual setup of the canvas. You probably should use `new_decoder` instead.
33    ///
34    /// Use `rgb` crate's `as_rgb()` if you have palette as `&[u8]`.
35    #[inline]
36    #[must_use]
37    pub fn new(width: usize, height: usize, global_pal: Option<&[RGB8]>) -> Self {
38        Screen {
39            internal_pixels: Img::new(vec![RGBA8::default(); width * height], width, height),
40            global_pal: global_pal.map(|g| std::array::from_fn(move |i| g.get(i).copied().unwrap_or_default())),
41            next_disposal: Disposal::default(),
42        }
43    }
44
45    /// Advance the screen by one frame.
46    ///
47    /// Use `pixels_rgba()` to get pixels afterwards
48    pub fn blit_frame(&mut self, frame: &gif::Frame<'_>) -> Result<(), Error> {
49        let local_pal = frame.palette.as_deref().map(bytemuck::cast_slice);
50        self.blit(local_pal, frame.dispose,
51            frame.left, frame.top,
52            ImgRef::new(&frame.buffer, frame.width.into(), frame.height.into()), frame.transparent)
53    }
54
55    /// Low-level version of `blit_frame`
56    pub fn blit(&mut self, local_pal: Option<&[RGB8]>, method: gif::DisposalMethod, left: u16, top: u16, buffer: ImgRef<'_, u8>, transparent: Option<u8>) -> Result<(), Error> {
57        self.dispose_only().then_blit(local_pal, method, left, top, buffer, transparent)?;
58        Ok(())
59    }
60
61    fn blit_without_dispose(&mut self, local_pal: Option<&[RGB8]>, method: gif::DisposalMethod, left: u16, top: u16, buffer: ImgRef<'_, u8>, transparent: Option<u8>) -> Result<(), Error> {
62        self.next_disposal = Disposal::new(method, left, top, buffer.width() as u16, buffer.height() as u16, self.internal_pixels.as_ref());
63
64        let pal_slice = local_pal.or(self.global_pal.as_ref().map(|p| &p[..])).ok_or(Error::NoPalette)?;
65        let pal: [_; 256] = std::array::from_fn(|i| {
66            pal_slice.get(i).copied().unwrap_or_default()
67        });
68
69        for (dst, src) in self.internal_pixels.sub_image_mut(left.into(), top.into(), buffer.width(), buffer.height()).pixels_mut().zip(buffer.pixels()) {
70            if Some(src) == transparent {
71                continue;
72            }
73            *dst = pal[src as usize].with_alpha(255);
74        }
75        Ok(())
76    }
77
78    /// Access the currently rendered pixels
79    #[inline(always)]
80    pub fn pixels_rgba(&mut self) -> ImgRef<'_, RGBA8> {
81        self.internal_pixels.as_ref()
82    }
83
84    /// Use [`pixels_rgba`]
85    #[deprecated(note = "use pixels_rgba() instead. This method will return a different type in the next version")]
86    pub fn pixels(&mut self) -> ImgRef<'_, RGBA8> {
87        self.pixels_rgba()
88    }
89
90    /// Advanced usage. You do not need to call this. It exposes an incompletely-drawn screen.
91    ///
92    /// Call to this method must always be followed by `.then_blit()` to fix the incomplete state.
93    ///
94    /// The state is after previous frame has been disposed, but before the next frame has been drawn.
95    /// This state is never visible on screen.
96    ///
97    /// This method is for GIF encoders to help find minimal difference between frames, especially
98    /// when transparency is involved ("background" disposal method).
99    ///
100    /// ```rust
101    /// # fn example(buffer: imgref::ImgRef<u8>) -> Result<(), gif_dispose::Error> {
102    /// use gif_dispose::*;
103    /// let mut screen = Screen::new(320, 200, None);
104    /// let mut tmp_screen = screen.dispose_only();
105    /// let incomplete_pixels = tmp_screen.pixels();
106    /// tmp_screen.then_blit(None, gif::DisposalMethod::Keep, 0, 0, buffer, None)?;
107    /// # Ok(()) }
108    /// ```
109    #[inline]
110    pub fn dispose_only(&mut self) -> TempDisposedStateScreen<'_> {
111        self.next_disposal.dispose(self.internal_pixels.as_mut());
112        TempDisposedStateScreen(self)
113    }
114
115    #[must_use]
116    pub fn width(&self) -> usize {
117        self.internal_pixels.width()
118    }
119
120    #[must_use]
121    pub fn height(&self) -> usize {
122        self.internal_pixels.height()
123    }
124}
125
126/// Screen that has a temporary state between frames
127#[must_use]
128pub struct TempDisposedStateScreen<'screen>(&'screen mut Screen);
129
130/// Extends borrow to the end of scope, reminding to use `then_blit`
131impl Drop for TempDisposedStateScreen<'_> {
132    fn drop(&mut self) {
133    }
134}
135
136impl<'s, > TempDisposedStateScreen<'s> {
137    #[inline(always)]
138    pub fn then_blit(self, local_pal: Option<&[RGB8]>, method: gif::DisposalMethod, left: u16, top: u16, buffer: ImgRef<'_, u8>, transparent: Option<u8>) -> Result<(), Error> {
139        self.0.blit_without_dispose(local_pal, method, left, top, buffer, transparent)
140    }
141
142    /// Access pixels in the in-between state
143    #[inline(always)]
144    pub fn pixels_rgba(&mut self) -> ImgRef<'_, RGBA8> {
145        self.0.internal_pixels.as_ref()
146    }
147
148    /// Use [`pixels_rgba`]
149    #[deprecated(note = "use pixels_rgba() instead. This method will return a different type in the next version")]
150    pub fn pixels(&mut self) -> ImgRef<'_, RGBA8> {
151        self.pixels_rgba()
152    }
153}