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