1use std::io;
29use std::iter::repeat;
30use png::{BitDepth, ColorType, HasParameters};
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum Error {
35 PngEncoding,
37
38 Io,
40
41 PaletteTooBig,
43
44 PaletteTooSmall,
46}
47
48impl From<png::EncodingError> for Error {
49 fn from(err: png::EncodingError) -> Error {
50 use png::EncodingError;
51
52 match err {
53 EncodingError::IoError(_) => Error::Io,
54 EncodingError::Format(_) => Error::PngEncoding,
55 }
56 }
57}
58
59#[derive(Clone, Copy, PartialEq, Eq, Debug)]
61pub enum Color {
62 Rgb(u8, u8, u8),
64
65 Rgba(u8, u8, u8, u8),
67}
68
69pub const WHITE: Color = Color::Rgb(255, 255, 255);
70pub const BLACK: Color = Color::Rgb(0, 0, 0);
71
72pub struct Image<'a> {
73 pub palette: &'a [Color],
75
76 pub pixels: &'a [u8],
78
79 pub width: usize,
81
82 pub scale: usize,
84}
85
86impl<'a> Image<'a> {
87 pub fn render<W: io::Write>(&self, writer: W) -> Result<(), Error> {
89 if self.palette.len() > 256 {
90 return Err(Error::PaletteTooBig);
91 }
92
93 if self.palette.len() < 2 {
94 return Err(Error::PaletteTooSmall);
95 }
96
97 let bit_depth = self.bit_depth();
98 let depth = bit_depth as usize;
99 let pixels_in_byte = 8 / depth;
100
101 let (img_width, img_height) = self.dimensions();
102 let bytes_per_line = ceil_div(img_width, pixels_in_byte);
103
104 let mut data = vec![0; bytes_per_line * img_height];
105
106 let chunk_size = bytes_per_line * self.scale;
107
108 for (chunk, pixels) in data.chunks_mut(chunk_size).zip(self.pixels.chunks(self.width)) {
109 let (first_line, chunk) = chunk.split_at_mut(bytes_per_line);
110
111 let mut pixels = pixels.iter().flat_map(|pixel| repeat(*pixel).take(self.scale));
113
114 for byte in first_line.iter_mut() {
116 for (idx, pixel) in (&mut pixels).take(pixels_in_byte).enumerate() {
118 *byte |= pixel << (8 - depth) - (idx * depth);
119 }
120 }
121
122 for row in chunk.chunks_mut(bytes_per_line) {
124 row.copy_from_slice(first_line);
125 }
126 }
127
128 let mut encoder = png::Encoder::new(writer, img_width as u32, img_height as u32);
129
130 encoder.set(ColorType::Indexed).set(bit_depth);
131
132 let mut writer = encoder.write_header()?;
133
134 writer.write_chunk(png::chunk::PLTE, &self.palette_data())?;
135
136 if let Some(transparency) = self.transparency() {
137 writer.write_chunk(png::chunk::tRNS, &transparency)?;
138 }
139
140 writer.write_image_data(&data)?;
141
142 Ok(())
143 }
144
145 fn dimensions(&self) -> (usize, usize) {
146 let width = self.width * self.scale;
147 let height = (self.pixels.len() / self.width) * self.scale;
148
149 (width, height)
150 }
151
152 fn bit_depth(&self) -> BitDepth {
153 match self.palette.len() as u8 {
154 0...2 => BitDepth::One,
155 3...4 => BitDepth::Two,
156 5...16 => BitDepth::Four,
157 _ => BitDepth::Eight,
158 }
159 }
160
161 fn palette_data(&self) -> Vec<u8> {
162 let mut data = Vec::with_capacity(self.palette.len() * 3);
163
164 for color in self.palette.iter().cloned() {
165 match color {
166 Color::Rgb(r, g, b) | Color::Rgba(r, g, b, _) => data.extend_from_slice(&[r,g,b])
167 }
168 }
169
170 data
171 }
172
173 fn transparency(&self) -> Option<Vec<u8>> {
174 let mut data = None;
175
176 let len = self.palette.len();
177
178 for (idx, color) in self.palette.iter().enumerate() {
179 let alpha = match color {
180 Color::Rgb(_, _, _) => continue,
181 Color::Rgba(_, _, _, alpha) => *alpha,
182 };
183
184 let mut buf = data.take().unwrap_or_else(|| vec![255; len]);
185
186 buf[idx] = alpha;
187
188 data = Some(buf);
189 }
190
191 data
192 }
193}
194
195fn ceil_div(a: usize, b: usize) -> usize {
196 a / b + (a % b != 0) as usize
197}