Skip to main content

qr_code_styling/rendering/
raster_renderer.rs

1//! Raster (PNG/JPEG/WebP) renderer for QR codes using resvg.
2
3use crate::error::{QRError, Result};
4use crate::types::OutputFormat;
5use image::{DynamicImage, ImageFormat, RgbaImage};
6use resvg::tiny_skia::Pixmap;
7use resvg::usvg::{Options, Transform, Tree};
8use std::io::Cursor;
9
10/// Raster renderer for converting SVG to raster formats.
11pub struct RasterRenderer;
12
13impl RasterRenderer {
14    /// Convert SVG string to raster image bytes.
15    pub fn render(svg: &str, width: u32, height: u32, format: OutputFormat) -> Result<Vec<u8>> {
16        // Parse the SVG with resvg/usvg
17        let image = Self::svg_to_image(svg, width, height)?;
18
19        // Encode to target format
20        Self::encode_image(&image, format)
21    }
22
23    /// Parse SVG and render to image buffer using resvg.
24    fn svg_to_image(svg: &str, width: u32, height: u32) -> Result<DynamicImage> {
25        // Parse SVG using usvg
26        let tree = Tree::from_str(svg, &Options::default())
27            .map_err(|e| QRError::SvgError(e.to_string()))?;
28
29        // Get the SVG's original size
30        let svg_size = tree.size();
31
32        // Create a pixmap with the target dimensions
33        let mut pixmap = Pixmap::new(width, height)
34            .ok_or_else(|| QRError::SvgError("Failed to create pixmap".to_string()))?;
35
36        // Fill with white background (since QR codes typically have white background)
37        pixmap.fill(resvg::tiny_skia::Color::WHITE);
38
39        // Calculate scale to fit the SVG into the target dimensions
40        let scale_x = width as f32 / svg_size.width();
41        let scale_y = height as f32 / svg_size.height();
42        let scale = scale_x.min(scale_y);
43
44        // Calculate offset to center the SVG
45        let offset_x = (width as f32 - svg_size.width() * scale) / 2.0;
46        let offset_y = (height as f32 - svg_size.height() * scale) / 2.0;
47
48        // Create transform
49        let transform = Transform::from_scale(scale, scale).post_translate(offset_x, offset_y);
50
51        // Render the SVG
52        resvg::render(&tree, transform, &mut pixmap.as_mut());
53
54        // Convert pixmap to image::RgbaImage
55        let img = RgbaImage::from_raw(width, height, pixmap.data().to_vec())
56            .ok_or_else(|| QRError::SvgError("Failed to create image from pixmap".to_string()))?;
57
58        Ok(DynamicImage::ImageRgba8(img))
59    }
60
61    /// Encode image to the specified format.
62    fn encode_image(image: &DynamicImage, format: OutputFormat) -> Result<Vec<u8>> {
63        let mut buffer = Cursor::new(Vec::new());
64
65        let image_format = match format {
66            OutputFormat::Png => ImageFormat::Png,
67            OutputFormat::Jpeg => ImageFormat::Jpeg,
68            OutputFormat::WebP => ImageFormat::WebP,
69            OutputFormat::Svg => {
70                return Err(QRError::ImageEncodeError(
71                    "SVG format should use SVG renderer".to_string(),
72                ));
73            }
74            OutputFormat::Pdf => {
75                return Err(QRError::ImageEncodeError(
76                    "PDF format should use PDF renderer".to_string(),
77                ));
78            }
79        };
80
81        image
82            .write_to(&mut buffer, image_format)
83            .map_err(|e| QRError::ImageEncodeError(e.to_string()))?;
84
85        Ok(buffer.into_inner())
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_encode_png() {
95        let img = DynamicImage::ImageRgba8(RgbaImage::new(100, 100));
96        let result = RasterRenderer::encode_image(&img, OutputFormat::Png);
97        assert!(result.is_ok());
98        let bytes = result.unwrap();
99        assert!(!bytes.is_empty());
100        // PNG magic bytes
101        assert_eq!(&bytes[0..4], &[0x89, 0x50, 0x4E, 0x47]);
102    }
103
104    #[test]
105    fn test_encode_jpeg() {
106        let img = DynamicImage::ImageRgba8(RgbaImage::new(100, 100));
107        let result = RasterRenderer::encode_image(&img, OutputFormat::Jpeg);
108        assert!(result.is_ok());
109        let bytes = result.unwrap();
110        assert!(!bytes.is_empty());
111        // JPEG magic bytes
112        assert_eq!(&bytes[0..2], &[0xFF, 0xD8]);
113    }
114
115    #[test]
116    fn test_svg_to_image() {
117        // Simple SVG with a black square
118        let svg = r#"<?xml version="1.0" encoding="UTF-8"?>
119            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
120                <rect x="0" y="0" width="100" height="100" fill="white"/>
121                <rect x="25" y="25" width="50" height="50" fill="black"/>
122            </svg>"#;
123
124        let result = RasterRenderer::svg_to_image(svg, 100, 100);
125        assert!(result.is_ok());
126    }
127
128    #[test]
129    fn test_full_render() {
130        let svg = r#"<?xml version="1.0" encoding="UTF-8"?>
131            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
132                <rect x="0" y="0" width="100" height="100" fill="white"/>
133                <rect x="25" y="25" width="50" height="50" fill="black"/>
134            </svg>"#;
135
136        let result = RasterRenderer::render(svg, 100, 100, OutputFormat::Png);
137        assert!(result.is_ok());
138        let bytes = result.unwrap();
139        // PNG magic bytes
140        assert_eq!(&bytes[0..4], &[0x89, 0x50, 0x4E, 0x47]);
141    }
142}