use alloc::vec;
use alloc::vec::Vec;
use super::{hri, Layout, RenderOptions, Renderer};
use crate::error::{Error, Result};
use crate::symbology::Symbol;
const INCHES_PER_METRE: f64 = 39.370_078_740_157_48;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Png;
impl Renderer for Png {
type Output = Vec<u8>;
fn render(&self, symbol: &Symbol, options: &RenderOptions) -> Result<Vec<u8>> {
let layout = options.layout(symbol)?;
let pixels = rasterize(symbol, options, &layout);
encode(&pixels, &layout, options)
}
}
fn rasterize(symbol: &Symbol, options: &RenderOptions, layout: &Layout) -> Vec<u8> {
let width = layout.width_px as usize;
let height = layout.height_px as usize;
let fg = options.foreground();
let bg = options.background();
let mut buf = vec![0u8; width * height * 4];
for px in buf.chunks_exact_mut(4) {
px.copy_from_slice(&[bg.r, bg.g, bg.b, bg.a]);
}
let put = |x: u32, y: u32, buf: &mut Vec<u8>| {
if x >= layout.width_px || y >= layout.height_px {
return;
}
let i = ((y as usize) * width + (x as usize)) * 4;
buf[i..i + 4].copy_from_slice(&[fg.r, fg.g, fg.b, fg.a]);
};
let modules = symbol.modules();
for my in 0..modules.height() {
let (y0, y1) = if symbol.is_linear() {
(layout.symbol_y_px, layout.symbol_y_px + layout.symbol_h_px)
} else {
let top = layout.symbol_y_px + my * layout.module_px;
(top, top + layout.module_px)
};
for mx in 0..modules.width() {
if !modules.get(mx, my) {
continue;
}
let x0 = layout.symbol_x_px + mx * layout.module_px;
for y in y0..y1 {
for x in x0..x0 + layout.module_px {
put(x, y, &mut buf);
}
}
}
}
if layout.hri_scale > 0 {
let scale = layout.hri_scale;
for (index, ch) in symbol.payload().chars().enumerate() {
let glyph = hri::glyph(ch);
let origin_x = layout.hri_x_px + (index as u32) * hri::ADVANCE * scale;
for gy in 0..hri::GLYPH_H {
for gx in 0..hri::GLYPH_W {
if !hri::pixel(glyph, gx, gy) {
continue;
}
let x0 = origin_x + gx * scale;
let y0 = layout.hri_y_px + gy * scale;
for y in y0..y0 + scale {
for x in x0..x0 + scale {
put(x, y, &mut buf);
}
}
}
}
}
}
buf
}
fn encode(pixels: &[u8], layout: &Layout, options: &RenderOptions) -> Result<Vec<u8>> {
let mut out = Vec::new();
{
let mut encoder = ::png::Encoder::new(&mut out, layout.width_px, layout.height_px);
encoder.set_color(::png::ColorType::Rgba);
encoder.set_depth(::png::BitDepth::Eight);
let ppu = (f64::from(options.dpi()) * INCHES_PER_METRE).round() as u32;
encoder.set_pixel_dims(Some(::png::PixelDimensions {
xppu: ppu,
yppu: ppu,
unit: ::png::Unit::Meter,
}));
let mut writer = encoder
.write_header()
.map_err(|e| Error::Render(alloc::format!("png header: {e}")))?;
writer
.write_image_data(pixels)
.map_err(|e| Error::Render(alloc::format!("png image data: {e}")))?;
writer
.finish()
.map_err(|e| Error::Render(alloc::format!("png finish: {e}")))?;
}
Ok(out)
}
#[cfg(all(test, feature = "code128"))]
mod tests {
use super::*;
use crate::render::{Color, Length, QuietZone};
use crate::symbology::{Code128, Symbology};
const PNG_MAGIC: &[u8] = b"\x89PNG\r\n\x1a\n";
fn symbol() -> Symbol {
Code128.encode("PKG-9ED9285C").unwrap()
}
fn decode(bytes: &[u8]) -> (u32, u32, Vec<u8>) {
let decoder = ::png::Decoder::new(std::io::Cursor::new(bytes));
let mut reader = decoder.read_info().expect("valid png");
let mut buf = vec![0; reader.output_buffer_size().unwrap()];
let info = reader.next_frame(&mut buf).expect("valid frame");
buf.truncate(info.buffer_size());
(info.width, info.height, buf)
}
fn pixel_at(w: u32, buf: &[u8], x: u32, y: u32) -> [u8; 4] {
let i = ((y as usize) * (w as usize) + (x as usize)) * 4;
[buf[i], buf[i + 1], buf[i + 2], buf[i + 3]]
}
#[test]
fn produces_a_valid_png_of_the_expected_size() {
let s = symbol();
let opts = RenderOptions::default();
let layout = opts.layout(&s).unwrap();
let bytes = Png.render(&s, &opts).unwrap();
assert_eq!(&bytes[..8], PNG_MAGIC);
let (w, h, _) = decode(&bytes);
assert_eq!((w, h), (layout.width_px, layout.height_px));
}
#[test]
fn quiet_zone_is_actually_blank() {
let s = symbol();
let opts = RenderOptions::default();
let layout = opts.layout(&s).unwrap();
let (w, _, buf) = decode(&Png.render(&s, &opts).unwrap());
let white = [255, 255, 255, 255];
for x in 0..layout.quiet_x_px {
assert_eq!(pixel_at(w, &buf, x, 0), white, "left quiet zone not blank");
let right = layout.width_px - 1 - x;
assert_eq!(
pixel_at(w, &buf, right, 0),
white,
"right quiet zone not blank"
);
}
}
#[test]
fn first_and_last_module_are_dark() {
let s = symbol();
let opts = RenderOptions::default();
let layout = opts.layout(&s).unwrap();
let (w, _, buf) = decode(&Png.render(&s, &opts).unwrap());
let black = [0, 0, 0, 255];
assert_eq!(pixel_at(w, &buf, layout.symbol_x_px, 0), black);
let last = layout.symbol_x_px + layout.symbol_w_px - 1;
assert_eq!(pixel_at(w, &buf, last, 0), black);
}
#[test]
fn every_module_column_is_uniform() {
let s = symbol();
let opts = RenderOptions::builder()
.human_readable(false)
.build()
.unwrap();
let layout = opts.layout(&s).unwrap();
let (w, _, buf) = decode(&Png.render(&s, &opts).unwrap());
for mx in 0..s.modules().width() {
let expected = if s.modules().get(mx, 0) {
[0, 0, 0, 255]
} else {
[255, 255, 255, 255]
};
for i in 0..layout.module_px {
let x = layout.symbol_x_px + mx * layout.module_px + i;
assert_eq!(
pixel_at(w, &buf, x, 0),
expected,
"column {x} (module {mx})"
);
}
}
}
#[test]
fn custom_colors_are_applied() {
let s = symbol();
let opts = RenderOptions::builder()
.colors(Color::rgb(10, 20, 30), Color::rgb(200, 210, 220))
.build()
.unwrap();
let layout = opts.layout(&s).unwrap();
let (w, _, buf) = decode(&Png.render(&s, &opts).unwrap());
assert_eq!(pixel_at(w, &buf, 0, 0), [200, 210, 220, 255]);
assert_eq!(pixel_at(w, &buf, layout.symbol_x_px, 0), [10, 20, 30, 255]);
}
#[test]
fn transparent_background_is_preserved() {
let s = symbol();
let opts = RenderOptions::builder()
.colors(Color::BLACK, Color::TRANSPARENT)
.build()
.unwrap();
let (w, _, buf) = decode(&Png.render(&s, &opts).unwrap());
assert_eq!(
pixel_at(w, &buf, 0, 0)[3],
0,
"background should be transparent"
);
}
#[test]
fn physical_resolution_is_recorded() {
let s = symbol();
let opts = RenderOptions::builder().dpi(300).build().unwrap();
let bytes = Png.render(&s, &opts).unwrap();
let decoder = ::png::Decoder::new(std::io::Cursor::new(&bytes[..]));
let reader = decoder.read_info().unwrap();
let dims = reader
.info()
.pixel_dims
.expect("pHYs chunk should be present");
assert!(matches!(dims.unit, ::png::Unit::Meter));
assert_eq!(dims.xppu, 11811);
assert_eq!(dims.yppu, dims.xppu);
}
#[test]
fn output_is_byte_for_byte_reproducible() {
let s = symbol();
let opts = RenderOptions::default();
assert_eq!(
Png.render(&s, &opts).unwrap(),
Png.render(&s, &opts).unwrap()
);
}
#[test]
fn hri_draws_dark_pixels_below_the_bars() {
let s = symbol();
let opts = RenderOptions::default();
let layout = opts.layout(&s).unwrap();
let (w, _, buf) = decode(&Png.render(&s, &opts).unwrap());
let dark = (layout.hri_y_px..layout.height_px)
.flat_map(|y| (0..layout.width_px).map(move |x| (x, y)))
.filter(|(x, y)| pixel_at(w, &buf, *x, *y) == [0, 0, 0, 255])
.count();
assert!(dark > 0, "no HRI pixels were drawn");
}
#[test]
fn no_quiet_zone_yields_a_symbol_width_image() {
let s = symbol();
let opts = RenderOptions::builder()
.quiet_zone(QuietZone::None)
.human_readable(false)
.build()
.unwrap();
let (w, h, _) = decode(&Png.render(&s, &opts).unwrap());
let layout = opts.layout(&s).unwrap();
assert_eq!(w, layout.symbol_w_px);
assert_eq!(h, layout.symbol_h_px);
}
#[test]
fn a_one_pixel_module_still_renders() {
let s = symbol();
let opts = RenderOptions::builder()
.module_width(Length::Px(1.0))
.height(Length::Px(20.0))
.build()
.unwrap();
let (w, _, _) = decode(&Png.render(&s, &opts).unwrap());
assert_eq!(w, s.modules().width() + 20);
}
}