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