1 use super::Error;
2 use crate::disposal::Disposal;
3 use imgref::*;
4 use rgb::*;
5 use std::io;
6 
7 /// Combined GIF frames forming a "virtual screen". See [Screen::new_decoder].
8 ///
9 /// Pixel type can be `RGB8` or `RGBA8`. The size is overall GIF size (grater or equal individual frame sizes).
10 pub struct Screen<PixelType = RGBA8> {
11     /// Result of combining all frames so far. It's in RGB/RGBA.
12     pub pixels: ImgVec<PixelType>,
13 
14     global_pal: Option<Vec<PixelType>>,
15     disposal: Disposal<PixelType>,
16 }
17 
18 impl Screen<RGBA8> {
19     /// Create an new `Screen` with RGBA pixel type (the best choice for GIF)
20     ///
21     /// Make sure Reader is set to use `Indexed` color.
22     /// `options.set_color_output(gif::ColorOutput::Indexed);`
new_decoder<T: io::Read>(reader: &gif::Decoder<T>) -> Self23     pub fn new_decoder<T: io::Read>(reader: &gif::Decoder<T>) -> Self {
24         Self::from_decoder(reader)
25     }
26 }
27 
28 impl<PixelType: From<RGB8> + Copy + Default> Screen<PixelType> {
29     /// Create an new `Screen` with either `RGB8` or `RGBA8` pixel type. Allows ignoring transparency.
30     ///
31     /// You may need type hints or use the `screen.pixels` to tell Rust whether you want `RGB8` or `RGBA8`.
32     #[must_use]
from_decoder<T: io::Read>(reader: &gif::Decoder<T>) -> Self33     pub fn from_decoder<T: io::Read>(reader: &gif::Decoder<T>) -> Self {
34         let w = reader.width();
35         let h = reader.height();
36         let pal = reader.global_palette().map(convert_pixels);
37 
38         Self::new(w.into(), h.into(), PixelType::default(), pal)
39     }
40 
41     /// Manual setup of the canvas. You probably should use `from_reader` instead.
42     ///
43     /// `bg_color` argument will be ignored. It appears that nobody tries to follow the GIF spec,
44     /// and background must always be transparent.
45     #[inline]
46     #[must_use]
new(width: usize, height: usize, _bg_color: PixelType, global_pal: Option<Vec<PixelType>>) -> Self47     pub fn new(width: usize, height: usize, _bg_color: PixelType, global_pal: Option<Vec<PixelType>>) -> Self {
48         Screen {
49             pixels: Img::new(vec![PixelType::default(); width * height], width, height),
50             global_pal,
51             disposal: Disposal::default(),
52         }
53     }
54 
55     /// Advance the screen by one frame.
56     ///
57     /// The result will be in `screen.pixels.buf`
blit_frame(&mut self, frame: &gif::Frame<'_>) -> Result<ImgRef<'_, PixelType>, Error>58     pub fn blit_frame(&mut self, frame: &gif::Frame<'_>) -> Result<ImgRef<'_, PixelType>, Error> {
59         let local_pal : Option<Vec<_>> = frame.palette.as_ref().map(|bytes| convert_pixels(bytes));
60         self.blit(local_pal.as_deref(), frame.dispose,
61             frame.left, frame.top,
62             ImgRef::new(&frame.buffer, frame.width.into(), frame.height.into()), frame.transparent)
63     }
64 
65 
66     /// Low-level version of `blit_frame`
blit(&mut self, local_pal: Option<&[PixelType]>, method: gif::DisposalMethod, left: u16, top: u16, buffer: ImgRef<'_, u8>, transparent: Option<u8>) -> Result<ImgRef<'_, PixelType>, Error>67     pub fn blit(&mut self, local_pal: Option<&[PixelType]>, method: gif::DisposalMethod, left: u16, top: u16, buffer: ImgRef<'_, u8>, transparent: Option<u8>) -> Result<ImgRef<'_, PixelType>, Error> {
68         self.dispose().then_blit(local_pal, method, left, top, buffer, transparent)?;
69         Ok(self.pixels.as_ref())
70     }
71 
blit_without_dispose(&mut self, local_pal: Option<&[PixelType]>, method: gif::DisposalMethod, left: u16, top: u16, buffer: ImgRef<'_, u8>, transparent: Option<u8>) -> Result<(), Error>72     fn blit_without_dispose(&mut self, local_pal: Option<&[PixelType]>, method: gif::DisposalMethod, left: u16, top: u16, buffer: ImgRef<'_, u8>, transparent: Option<u8>) -> Result<(), Error> {
73         let mut pal = local_pal.or(self.global_pal.as_deref()).ok_or(Error::NoPalette)?;
74         let mut tmp;
75         // For backwards-compat only
76         if pal.len() < 256 {
77             tmp = Vec::with_capacity(256);
78             tmp.extend_from_slice(pal);
79             tmp.resize(256, Default::default());
80             pal = &tmp;
81         };
82         // Some images contain out-of-pal colors. The fastest way is to extend the palette instead of doing per-pixel checks.
83         let pal = &pal[0..256];
84 
85         self.disposal = Disposal::new(method, left, top, buffer.width() as u16, buffer.height() as u16, self.pixels.as_ref());
86 
87         for (dst, src) in self.pixels.sub_image_mut(left.into(), top.into(), buffer.width(), buffer.height()).pixels_mut().zip(buffer.pixels()) {
88             if Some(src) == transparent {
89                 continue;
90             }
91             *dst = pal[src as usize];
92         }
93         Ok(())
94     }
95 
96     #[inline(always)]
97     #[doc(hidden)]
pixels(&mut self) -> ImgRef<'_, PixelType>98     pub fn pixels(&mut self) -> ImgRef<'_, PixelType> {
99         self.pixels.as_ref()
100     }
101 
102     /// Advanced usage. You do not need to call this. It exposes an incompletely-drawn screen.
103     ///
104     /// Call to this method must always be followed by `.then_blit()` to fix the incomplete state.
105     ///
106     /// The state is after previous frame has been disposed, but before the next frame has been drawn.
107     /// This state is never visible on screen.
108     ///
109     /// This method is for GIF encoders to help find minimal difference between frames, especially
110     /// when transparency is involved ("background" disposal method).
111     ///
112     /// ```rust
113     /// # fn example(buffer: imgref::ImgRef<u8>) -> Result<(), gif_dispose::Error> {
114     /// use gif_dispose::*;
115     /// let mut screen = Screen::new(320, 200, RGBA8::new(0,0,0,0), None);
116     /// let mut tmp_screen = screen.dispose();
117     /// let incomplete_pixels = tmp_screen.pixels();
118     /// tmp_screen.then_blit(None, gif::DisposalMethod::Keep, 0, 0, buffer, None)?;
119     /// # Ok(()) }
120     /// ```
121     #[inline]
dispose(&mut self) -> TempDisposedStateScreen<'_, PixelType>122     pub fn dispose(&mut self) -> TempDisposedStateScreen<'_, PixelType> {
123         self.disposal.dispose(self.pixels.as_mut());
124         TempDisposedStateScreen(self)
125     }
126 }
127 
128 
129 /// Screen that has a temporary state between frames
130 #[must_use]
131 pub struct TempDisposedStateScreen<'screen, PixelType>(&'screen mut Screen<PixelType>);
132 
133 /// Extends borrow to the end of scope, reminding to use `then_blit`
134 impl<T> Drop for TempDisposedStateScreen<'_, T> {
drop(&mut self)135     fn drop(&mut self) {
136     }
137 }
138 
139 impl<'s, PixelType: From<RGB8> + Copy + Default> TempDisposedStateScreen<'s, PixelType> {
140     #[inline(always)]
then_blit(self, local_pal: Option<&[PixelType]>, method: gif::DisposalMethod, left: u16, top: u16, buffer: ImgRef<'_, u8>, transparent: Option<u8>) -> Result<(), Error>141     pub fn then_blit(self, local_pal: Option<&[PixelType]>, method: gif::DisposalMethod, left: u16, top: u16, buffer: ImgRef<'_, u8>, transparent: Option<u8>) -> Result<(), Error> {
142         self.0.blit_without_dispose(local_pal, method, left, top, buffer, transparent)
143     }
144 
145     /// Access pixels in the in-between state
146     #[inline(always)]
pixels(&mut self) -> ImgRef<'_, PixelType>147     pub fn pixels(&mut self) -> ImgRef<'_, PixelType> {
148         self.0.pixels.as_ref()
149     }
150 }
151 
convert_pixels<T: From<RGB8> + Default>(palette_bytes: &[u8]) -> Vec<T>152 fn convert_pixels<T: From<RGB8> + Default>(palette_bytes: &[u8]) -> Vec<T> {
153     let mut res = Vec::with_capacity(256);
154     res.extend(palette_bytes.chunks(3).map(|byte| RGB8{r:byte[0], g:byte[1], b:byte[2]}.into()));
155     while res.len() < 256 {
156         res.push(Default::default());
157     }
158     res
159 }
160 
161 #[test]
screen_rgb_rgba()162 fn screen_rgb_rgba() {
163     let _ = Screen::new(1, 1, RGBA8::new(0, 0, 0, 0), None);
164     let _ = Screen::new(1, 1, RGB8::new(0, 0, 0), None);
165 }
166