1use crate::error::Result;
4use crate::lzw::LzwEncoder;
5use std::io::Write;
6
7pub struct GifOptions {
9 pub width: u16,
11 pub height: u16,
13 pub has_global_palette: bool,
15 pub palette_size: u8,
17}
18
19pub struct ImageDescriptor {
21 pub x: u16,
23 pub y: u16,
25 pub width: u16,
27 pub height: u16,
29 pub lzw_min_code_size: u8,
31}
32
33pub struct GifWriter<W: Write> {
35 writer: W,
36}
37
38impl<W: Write> GifWriter<W> {
39 pub fn new(writer: W) -> Self {
41 Self { writer }
42 }
43
44 pub fn write_header(&mut self) -> Result<()> {
46 self.writer.write_all(b"GIF89a")?;
47 Ok(())
48 }
49
50 pub fn write_logical_screen_descriptor(&mut self, options: &GifOptions) -> Result<()> {
52 self.writer.write_all(&options.width.to_le_bytes())?;
53 self.writer.write_all(&options.height.to_le_bytes())?;
54
55 let mut packed = 0u8;
56 if options.has_global_palette {
57 packed |= 0x80;
58 packed |= (options.palette_size - 1) & 0x07;
59 packed |= 0x70; }
61
62 self.writer.write_all(&[packed, 0, 0])?; Ok(())
64 }
65
66 pub fn write_netscape_loop_block(&mut self) -> Result<()> {
68 self.writer.write_all(&[0x21, 0xFF, 0x0B])?; self.writer.write_all(b"NETSCAPE2.0")?;
70 self.writer.write_all(&[0x03, 0x01])?; self.writer.write_all(&0u16.to_le_bytes())?; self.writer.write_all(&[0])?; Ok(())
74 }
75
76 pub fn write_global_palette(&mut self, palette: &[u8]) -> Result<()> {
78 self.writer.write_all(palette)?;
79 Ok(())
80 }
81
82 pub fn write_graphic_control_extension(
84 &mut self,
85 delay: u16,
86 transparent_idx: Option<u8>,
87 ) -> Result<()> {
88 self.writer.write_all(&[0x21, 0xF9, 0x04])?; let mut packed = 0x04; if transparent_idx.is_some() {
92 packed |= 0x01;
93 }
94 self.writer.write_all(&[packed])?;
95 self.writer.write_all(&delay.to_le_bytes())?;
96 self.writer.write_all(&[transparent_idx.unwrap_or(0), 0])?; Ok(())
98 }
99
100 pub fn write_image_data(
102 &mut self,
103 descriptor: &ImageDescriptor,
104 indices: &[u8],
105 encoder: &mut LzwEncoder,
106 ) -> Result<()> {
107 self.writer.write_all(&[0x2C])?; self.writer.write_all(&descriptor.x.to_le_bytes())?;
110 self.writer.write_all(&descriptor.y.to_le_bytes())?;
111 self.writer.write_all(&descriptor.width.to_le_bytes())?;
112 self.writer.write_all(&descriptor.height.to_le_bytes())?;
113 self.writer.write_all(&[0])?; self.writer.write_all(&[descriptor.lzw_min_code_size])?;
117
118 let mut lzw_data = Vec::new();
120 encoder.encode(indices, &mut lzw_data)?;
121
122 for chunk in lzw_data.chunks(255) {
124 self.writer.write_all(&[chunk.len() as u8])?;
125 self.writer.write_all(chunk)?;
126 }
127 self.writer.write_all(&[0])?; Ok(())
130 }
131
132 pub fn write_trailer(&mut self) -> Result<()> {
134 self.writer.write_all(&[0x3B])?;
135 Ok(())
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_write_minimal_gif() {
145 let mut buffer = Vec::new();
146 {
147 let mut writer = GifWriter::new(&mut buffer);
148 let options = GifOptions {
149 width: 1,
150 height: 1,
151 has_global_palette: true,
152 palette_size: 1, };
154
155 writer.write_header().unwrap();
156 writer.write_logical_screen_descriptor(&options).unwrap();
157
158 let mut palette = vec![0u8; 6]; palette[0] = 255; writer.write_global_palette(&palette).unwrap();
161
162 let mut encoder = LzwEncoder::new(2);
163 let descriptor = ImageDescriptor {
164 x: 0,
165 y: 0,
166 width: 1,
167 height: 1,
168 lzw_min_code_size: 2,
169 };
170 writer.write_image_data(&descriptor, &[0], &mut encoder).unwrap();
171 writer.write_trailer().unwrap();
172 }
173
174 assert!(buffer.starts_with(b"GIF89a"));
175 assert_eq!(*buffer.last().unwrap(), 0x3B);
176 }
177}