1use std::fmt;
2
3use image::{DynamicImage, GrayImage, RgbImage};
4
5use crate::error::RenderError;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct Raster {
9 pixel: u32,
10 gap: u32,
11}
12
13impl Raster {
14 pub const fn new(pixel: u32, gap: u32) -> Option<Self> {
15 if pixel == 0 {
16 return None;
17 }
18
19 Some(Self { pixel, gap })
20 }
21
22 pub fn pixel(self) -> u32 {
23 self.pixel
24 }
25
26 pub fn gap(self) -> u32 {
27 self.gap
28 }
29
30 fn extent(self, count: u32) -> Option<u32> {
31 let cells = count.checked_mul(self.pixel)?;
32 let gaps = count.saturating_sub(1).checked_mul(self.gap)?;
33
34 cells.checked_add(gaps)
35 }
36}
37
38impl Default for Raster {
39 fn default() -> Self {
40 Self { pixel: 3, gap: 1 }
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum PixelLayout {
46 Rgb888,
47 Bgr888,
48 Gray8,
49 Gray4,
50}
51
52impl PixelLayout {
53 fn byte_len(self, width: u32, height: u32) -> usize {
54 let pixels = (width as usize) * (height as usize);
55
56 match self {
57 Self::Rgb888 | Self::Bgr888 => pixels * 3,
58 Self::Gray8 => pixels,
59 Self::Gray4 => pixels.div_ceil(2),
60 }
61 }
62}
63
64impl fmt::Display for PixelLayout {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 Self::Rgb888 => f.write_str("rgb888"),
68 Self::Bgr888 => f.write_str("bgr888"),
69 Self::Gray8 => f.write_str("l8"),
70 Self::Gray4 => f.write_str("l4"),
71 }
72 }
73}
74
75#[derive(Debug, Clone, PartialEq)]
76pub struct RawImage {
77 width: u32,
78 height: u32,
79 image: DynamicImage,
80}
81
82impl RawImage {
83 pub fn new(
84 width: u32,
85 height: u32,
86 layout: PixelLayout,
87 pixels: &[u8],
88 ) -> Result<Self, RenderError> {
89 let expected = layout.byte_len(width, height);
90
91 if pixels.len() != expected {
92 return Err(RenderError::UnexpectedSize {
93 width,
94 height,
95 layout,
96 expected,
97 actual: pixels.len(),
98 });
99 }
100
101 let image = match layout {
102 PixelLayout::Rgb888 => DynamicImage::ImageRgb8(
103 RgbImage::from_raw(width, height, pixels.to_vec()).expect("checked above"),
104 ),
105 PixelLayout::Bgr888 => {
106 let mut pixels = pixels.to_vec();
107
108 for pixel in pixels.chunks_exact_mut(3) {
110 pixel.swap(0, 2);
111 }
112
113 DynamicImage::ImageRgb8(
114 RgbImage::from_raw(width, height, pixels).expect("checked above"),
115 )
116 }
117 PixelLayout::Gray8 => DynamicImage::ImageLuma8(
118 GrayImage::from_raw(width, height, pixels.to_vec()).expect("checked above"),
119 ),
120 PixelLayout::Gray4 => {
121 let mut expanded = Vec::with_capacity(pixels.len() * 2);
122
123 for byte in pixels {
124 expanded.push((byte >> 4) * 17);
125 expanded.push((byte & 0x0F) * 17);
126 }
127
128 expanded.truncate((width as usize) * (height as usize));
129
130 DynamicImage::ImageLuma8(
131 GrayImage::from_raw(width, height, expanded).expect("checked above"),
132 )
133 }
134 };
135
136 Ok(Self {
137 width,
138 height,
139 image,
140 })
141 }
142
143 pub fn width(&self) -> u32 {
144 self.width
145 }
146
147 pub fn height(&self) -> u32 {
148 self.height
149 }
150
151 pub(crate) fn buffer(&self) -> &DynamicImage {
152 &self.image
153 }
154
155 pub fn with_raster(&self, raster: Raster) -> Result<Self, RenderError> {
156 let too_large = || RenderError::RasterTooLarge {
157 width: self.width,
158 height: self.height,
159 pixel: raster.pixel,
160 gap: raster.gap,
161 };
162
163 let width = raster.extent(self.width).ok_or_else(too_large)?;
164 let height = raster.extent(self.height).ok_or_else(too_large)?;
165
166 let step = raster.pixel + raster.gap;
167
168 let image = match &self.image {
169 DynamicImage::ImageLuma8(source) => {
170 let mut target = GrayImage::new(width, height);
171 paint(source, &mut target, step, raster.pixel);
172 DynamicImage::ImageLuma8(target)
173 }
174 source => {
175 let source = source.to_rgb8();
176 let mut target = RgbImage::new(width, height);
177 paint(&source, &mut target, step, raster.pixel);
178 DynamicImage::ImageRgb8(target)
179 }
180 };
181
182 Ok(Self {
183 width,
184 height,
185 image,
186 })
187 }
188}
189
190fn paint<P: image::Pixel<Subpixel = u8>>(
191 source: &image::ImageBuffer<P, Vec<u8>>,
192 target: &mut image::ImageBuffer<P, Vec<u8>>,
193 step: u32,
194 size: u32,
195) {
196 for (x, y, pixel) in source.enumerate_pixels() {
197 for offset_y in 0..size {
198 for offset_x in 0..size {
199 target.put_pixel(x * step + offset_x, y * step + offset_y, *pixel);
200 }
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn reads_rgb_in_the_order_it_is_given() {
211 let image = RawImage::new(1, 1, PixelLayout::Rgb888, &[0x11, 0x22, 0x33]).unwrap();
212
213 assert_eq!(
214 image.buffer().as_rgb8().unwrap().get_pixel(0, 0).0,
215 [0x11, 0x22, 0x33]
216 );
217 }
218
219 #[test]
220 fn swaps_the_channels_of_a_bgr_buffer() {
221 let image = RawImage::new(1, 1, PixelLayout::Bgr888, &[0x11, 0x22, 0x33]).unwrap();
222
223 assert_eq!(
224 image.buffer().as_rgb8().unwrap().get_pixel(0, 0).0,
225 [0x33, 0x22, 0x11]
226 );
227 }
228
229 #[test]
230 fn expands_four_bit_grayscale_to_eight() {
231 let image = RawImage::new(2, 1, PixelLayout::Gray4, &[0xf0]).unwrap();
232 let luma = image.buffer().as_luma8().unwrap();
233
234 assert_eq!(luma.get_pixel(0, 0).0, [0xff]);
235 assert_eq!(luma.get_pixel(1, 0).0, [0x00]);
236 }
237
238 #[test]
239 fn rejects_a_buffer_which_does_not_match_the_geometry() {
240 let error = RawImage::new(72, 16, PixelLayout::Rgb888, &[0; 16]).unwrap_err();
241
242 assert_eq!(
243 error.to_string(),
244 "72x16 in rgb888 needs 3456 bytes, but 16 were given"
245 );
246 }
247
248 #[test]
249 fn a_raster_spaces_the_pixels_out_and_leaves_black_between_them() {
250 let image = RawImage::new(
251 2,
252 2,
253 PixelLayout::Rgb888,
254 &[
255 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
256 ],
257 )
258 .unwrap();
259
260 let raster = Raster::new(3, 1).unwrap();
261 let rastered = image.with_raster(raster).unwrap();
262
263 assert_eq!((rastered.width(), rastered.height()), (7, 7));
264
265 let pixels = rastered.buffer().to_rgb8();
266
267 assert_eq!(pixels.get_pixel(0, 0).0, [0xff, 0x00, 0x00]);
268 assert_eq!(pixels.get_pixel(2, 2).0, [0xff, 0x00, 0x00]);
269 assert_eq!(pixels.get_pixel(3, 0).0, [0x00, 0x00, 0x00]);
270 assert_eq!(pixels.get_pixel(0, 3).0, [0x00, 0x00, 0x00]);
271 assert_eq!(pixels.get_pixel(4, 0).0, [0x00, 0xff, 0x00]);
272 assert_eq!(pixels.get_pixel(4, 4).0, [0xff, 0xff, 0xff]);
273 }
274
275 #[test]
276 fn a_raster_keeps_a_grayscale_image_grayscale() {
277 let image = RawImage::new(2, 1, PixelLayout::Gray8, &[0xff, 0x40]).unwrap();
278 let rastered = image.with_raster(Raster::default()).unwrap();
279
280 assert_eq!((rastered.width(), rastered.height()), (7, 3));
281
282 let pixels = rastered.buffer().as_luma8().expect("stays grayscale");
283
284 assert_eq!(pixels.get_pixel(0, 0).0, [0xff]);
285 assert_eq!(pixels.get_pixel(3, 0).0, [0x00]);
286 assert_eq!(pixels.get_pixel(4, 0).0, [0x40]);
287 }
288
289 #[test]
290 fn a_raster_needs_a_pixel_size() {
291 assert!(Raster::new(0, 1).is_none());
292 assert!(Raster::new(1, 0).is_some());
293 }
294}