1use alloc::vec;
8use alloc::vec::Vec;
9
10use super::{hri, Layout, RenderOptions, Renderer};
11use crate::error::{Error, Result};
12use crate::symbology::Symbol;
13
14const INCHES_PER_METRE: f64 = 39.370_078_740_157_48;
16
17#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
35pub struct Png;
36
37impl Renderer for Png {
38 type Output = Vec<u8>;
39
40 fn render(&self, symbol: &Symbol, options: &RenderOptions) -> Result<Vec<u8>> {
41 let layout = options.layout(symbol)?;
42 let pixels = rasterize(symbol, options, &layout);
43 encode(&pixels, &layout, options)
44 }
45}
46
47fn rasterize(symbol: &Symbol, options: &RenderOptions, layout: &Layout) -> Vec<u8> {
49 let width = layout.width_px as usize;
50 let height = layout.height_px as usize;
51 let fg = options.foreground();
52 let bg = options.background();
53
54 let mut buf = vec![0u8; width * height * 4];
55 for px in buf.chunks_exact_mut(4) {
56 px.copy_from_slice(&[bg.r, bg.g, bg.b, bg.a]);
57 }
58
59 let put = |x: u32, y: u32, buf: &mut Vec<u8>| {
60 if x >= layout.width_px || y >= layout.height_px {
61 return;
62 }
63 let i = ((y as usize) * width + (x as usize)) * 4;
64 buf[i..i + 4].copy_from_slice(&[fg.r, fg.g, fg.b, fg.a]);
65 };
66
67 let modules = symbol.modules();
69 for my in 0..modules.height() {
70 let (y0, y1) = if symbol.is_linear() {
73 (layout.symbol_y_px, layout.symbol_y_px + layout.symbol_h_px)
74 } else {
75 let top = layout.symbol_y_px + my * layout.module_px;
76 (top, top + layout.module_px)
77 };
78
79 for mx in 0..modules.width() {
80 if !modules.get(mx, my) {
81 continue;
82 }
83 let x0 = layout.symbol_x_px + mx * layout.module_px;
84 for y in y0..y1 {
85 for x in x0..x0 + layout.module_px {
86 put(x, y, &mut buf);
87 }
88 }
89 }
90 }
91
92 if layout.hri_scale > 0 {
94 let scale = layout.hri_scale;
95 for (index, ch) in symbol.payload().chars().enumerate() {
96 let glyph = hri::glyph(ch);
97 let origin_x = layout.hri_x_px + (index as u32) * hri::ADVANCE * scale;
98 for gy in 0..hri::GLYPH_H {
99 for gx in 0..hri::GLYPH_W {
100 if !hri::pixel(glyph, gx, gy) {
101 continue;
102 }
103 let x0 = origin_x + gx * scale;
104 let y0 = layout.hri_y_px + gy * scale;
105 for y in y0..y0 + scale {
106 for x in x0..x0 + scale {
107 put(x, y, &mut buf);
108 }
109 }
110 }
111 }
112 }
113 }
114
115 buf
116}
117
118fn encode(pixels: &[u8], layout: &Layout, options: &RenderOptions) -> Result<Vec<u8>> {
120 let mut out = Vec::new();
121 {
122 let mut encoder = ::png::Encoder::new(&mut out, layout.width_px, layout.height_px);
123 encoder.set_color(::png::ColorType::Rgba);
124 encoder.set_depth(::png::BitDepth::Eight);
125
126 let ppu = (f64::from(options.dpi()) * INCHES_PER_METRE).round() as u32;
127 encoder.set_pixel_dims(Some(::png::PixelDimensions {
128 xppu: ppu,
129 yppu: ppu,
130 unit: ::png::Unit::Meter,
131 }));
132
133 let mut writer = encoder
134 .write_header()
135 .map_err(|e| Error::Render(alloc::format!("png header: {e}")))?;
136 writer
137 .write_image_data(pixels)
138 .map_err(|e| Error::Render(alloc::format!("png image data: {e}")))?;
139 writer
140 .finish()
141 .map_err(|e| Error::Render(alloc::format!("png finish: {e}")))?;
142 }
143 Ok(out)
144}
145
146#[cfg(all(test, feature = "code128"))]
147mod tests {
148 use super::*;
149 use crate::render::{Color, Length, QuietZone};
150 use crate::symbology::{Code128, Symbology};
151
152 const PNG_MAGIC: &[u8] = b"\x89PNG\r\n\x1a\n";
153
154 fn symbol() -> Symbol {
155 Code128.encode("PKG-9ED9285C").unwrap()
156 }
157
158 fn decode(bytes: &[u8]) -> (u32, u32, Vec<u8>) {
160 let decoder = ::png::Decoder::new(std::io::Cursor::new(bytes));
161 let mut reader = decoder.read_info().expect("valid png");
162 let mut buf = vec![0; reader.output_buffer_size().unwrap()];
163 let info = reader.next_frame(&mut buf).expect("valid frame");
164 buf.truncate(info.buffer_size());
165 (info.width, info.height, buf)
166 }
167
168 fn pixel_at(w: u32, buf: &[u8], x: u32, y: u32) -> [u8; 4] {
169 let i = ((y as usize) * (w as usize) + (x as usize)) * 4;
170 [buf[i], buf[i + 1], buf[i + 2], buf[i + 3]]
171 }
172
173 #[test]
174 fn produces_a_valid_png_of_the_expected_size() {
175 let s = symbol();
176 let opts = RenderOptions::default();
177 let layout = opts.layout(&s).unwrap();
178 let bytes = Png.render(&s, &opts).unwrap();
179
180 assert_eq!(&bytes[..8], PNG_MAGIC);
181 let (w, h, _) = decode(&bytes);
182 assert_eq!((w, h), (layout.width_px, layout.height_px));
183 }
184
185 #[test]
186 fn quiet_zone_is_actually_blank() {
187 let s = symbol();
188 let opts = RenderOptions::default();
189 let layout = opts.layout(&s).unwrap();
190 let (w, _, buf) = decode(&Png.render(&s, &opts).unwrap());
191
192 let white = [255, 255, 255, 255];
193 for x in 0..layout.quiet_x_px {
194 assert_eq!(pixel_at(w, &buf, x, 0), white, "left quiet zone not blank");
195 let right = layout.width_px - 1 - x;
196 assert_eq!(
197 pixel_at(w, &buf, right, 0),
198 white,
199 "right quiet zone not blank"
200 );
201 }
202 }
203
204 #[test]
205 fn first_and_last_module_are_dark() {
206 let s = symbol();
209 let opts = RenderOptions::default();
210 let layout = opts.layout(&s).unwrap();
211 let (w, _, buf) = decode(&Png.render(&s, &opts).unwrap());
212
213 let black = [0, 0, 0, 255];
214 assert_eq!(pixel_at(w, &buf, layout.symbol_x_px, 0), black);
215 let last = layout.symbol_x_px + layout.symbol_w_px - 1;
216 assert_eq!(pixel_at(w, &buf, last, 0), black);
217 }
218
219 #[test]
220 fn every_module_column_is_uniform() {
221 let s = symbol();
224 let opts = RenderOptions::builder()
225 .human_readable(false)
226 .build()
227 .unwrap();
228 let layout = opts.layout(&s).unwrap();
229 let (w, _, buf) = decode(&Png.render(&s, &opts).unwrap());
230
231 for mx in 0..s.modules().width() {
232 let expected = if s.modules().get(mx, 0) {
233 [0, 0, 0, 255]
234 } else {
235 [255, 255, 255, 255]
236 };
237 for i in 0..layout.module_px {
238 let x = layout.symbol_x_px + mx * layout.module_px + i;
239 assert_eq!(
240 pixel_at(w, &buf, x, 0),
241 expected,
242 "column {x} (module {mx})"
243 );
244 }
245 }
246 }
247
248 #[test]
249 fn custom_colors_are_applied() {
250 let s = symbol();
251 let opts = RenderOptions::builder()
252 .colors(Color::rgb(10, 20, 30), Color::rgb(200, 210, 220))
253 .build()
254 .unwrap();
255 let layout = opts.layout(&s).unwrap();
256 let (w, _, buf) = decode(&Png.render(&s, &opts).unwrap());
257
258 assert_eq!(pixel_at(w, &buf, 0, 0), [200, 210, 220, 255]);
259 assert_eq!(pixel_at(w, &buf, layout.symbol_x_px, 0), [10, 20, 30, 255]);
260 }
261
262 #[test]
263 fn transparent_background_is_preserved() {
264 let s = symbol();
265 let opts = RenderOptions::builder()
266 .colors(Color::BLACK, Color::TRANSPARENT)
267 .build()
268 .unwrap();
269 let (w, _, buf) = decode(&Png.render(&s, &opts).unwrap());
270 assert_eq!(
271 pixel_at(w, &buf, 0, 0)[3],
272 0,
273 "background should be transparent"
274 );
275 }
276
277 #[test]
278 fn physical_resolution_is_recorded() {
279 let s = symbol();
280 let opts = RenderOptions::builder().dpi(300).build().unwrap();
281 let bytes = Png.render(&s, &opts).unwrap();
282
283 let decoder = ::png::Decoder::new(std::io::Cursor::new(&bytes[..]));
284 let reader = decoder.read_info().unwrap();
285 let dims = reader
286 .info()
287 .pixel_dims
288 .expect("pHYs chunk should be present");
289 assert!(matches!(dims.unit, ::png::Unit::Meter));
290 assert_eq!(dims.xppu, 11811);
292 assert_eq!(dims.yppu, dims.xppu);
293 }
294
295 #[test]
296 fn output_is_byte_for_byte_reproducible() {
297 let s = symbol();
298 let opts = RenderOptions::default();
299 assert_eq!(
300 Png.render(&s, &opts).unwrap(),
301 Png.render(&s, &opts).unwrap()
302 );
303 }
304
305 #[test]
306 fn hri_draws_dark_pixels_below_the_bars() {
307 let s = symbol();
308 let opts = RenderOptions::default();
309 let layout = opts.layout(&s).unwrap();
310 let (w, _, buf) = decode(&Png.render(&s, &opts).unwrap());
311
312 let dark = (layout.hri_y_px..layout.height_px)
313 .flat_map(|y| (0..layout.width_px).map(move |x| (x, y)))
314 .filter(|(x, y)| pixel_at(w, &buf, *x, *y) == [0, 0, 0, 255])
315 .count();
316 assert!(dark > 0, "no HRI pixels were drawn");
317 }
318
319 #[test]
320 fn no_quiet_zone_yields_a_symbol_width_image() {
321 let s = symbol();
322 let opts = RenderOptions::builder()
323 .quiet_zone(QuietZone::None)
324 .human_readable(false)
325 .build()
326 .unwrap();
327 let (w, h, _) = decode(&Png.render(&s, &opts).unwrap());
328 let layout = opts.layout(&s).unwrap();
329 assert_eq!(w, layout.symbol_w_px);
330 assert_eq!(h, layout.symbol_h_px);
331 }
332
333 #[test]
334 fn a_one_pixel_module_still_renders() {
335 let s = symbol();
336 let opts = RenderOptions::builder()
337 .module_width(Length::Px(1.0))
338 .height(Length::Px(20.0))
339 .build()
340 .unwrap();
341 let (w, _, _) = decode(&Png.render(&s, &opts).unwrap());
342 assert_eq!(w, s.modules().width() + 20);
343 }
344}