use crate::error::{QRError, Result};
use crate::types::OutputFormat;
use image::{DynamicImage, ImageFormat, RgbaImage};
use resvg::tiny_skia::Pixmap;
use resvg::usvg::{Options, Transform, Tree};
use std::io::Cursor;
pub struct RasterRenderer;
impl RasterRenderer {
pub fn render(svg: &str, width: u32, height: u32, format: OutputFormat) -> Result<Vec<u8>> {
let image = Self::svg_to_image(svg, width, height)?;
Self::encode_image(&image, format)
}
fn svg_to_image(svg: &str, width: u32, height: u32) -> Result<DynamicImage> {
let tree = Tree::from_str(svg, &Options::default())
.map_err(|e| QRError::SvgError(e.to_string()))?;
let svg_size = tree.size();
let mut pixmap = Pixmap::new(width, height)
.ok_or_else(|| QRError::SvgError("Failed to create pixmap".to_string()))?;
pixmap.fill(resvg::tiny_skia::Color::WHITE);
let scale_x = width as f32 / svg_size.width();
let scale_y = height as f32 / svg_size.height();
let scale = scale_x.min(scale_y);
let offset_x = (width as f32 - svg_size.width() * scale) / 2.0;
let offset_y = (height as f32 - svg_size.height() * scale) / 2.0;
let transform = Transform::from_scale(scale, scale).post_translate(offset_x, offset_y);
resvg::render(&tree, transform, &mut pixmap.as_mut());
let img = RgbaImage::from_raw(width, height, pixmap.data().to_vec())
.ok_or_else(|| QRError::SvgError("Failed to create image from pixmap".to_string()))?;
Ok(DynamicImage::ImageRgba8(img))
}
fn encode_image(image: &DynamicImage, format: OutputFormat) -> Result<Vec<u8>> {
let mut buffer = Cursor::new(Vec::new());
let image_format = match format {
OutputFormat::Png => ImageFormat::Png,
OutputFormat::Jpeg => ImageFormat::Jpeg,
OutputFormat::WebP => ImageFormat::WebP,
OutputFormat::Svg => {
return Err(QRError::ImageEncodeError(
"SVG format should use SVG renderer".to_string(),
));
}
OutputFormat::Pdf => {
return Err(QRError::ImageEncodeError(
"PDF format should use PDF renderer".to_string(),
));
}
};
image
.write_to(&mut buffer, image_format)
.map_err(|e| QRError::ImageEncodeError(e.to_string()))?;
Ok(buffer.into_inner())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_png() {
let img = DynamicImage::ImageRgba8(RgbaImage::new(100, 100));
let result = RasterRenderer::encode_image(&img, OutputFormat::Png);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(!bytes.is_empty());
assert_eq!(&bytes[0..4], &[0x89, 0x50, 0x4E, 0x47]);
}
#[test]
fn test_encode_jpeg() {
let img = DynamicImage::ImageRgba8(RgbaImage::new(100, 100));
let result = RasterRenderer::encode_image(&img, OutputFormat::Jpeg);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(!bytes.is_empty());
assert_eq!(&bytes[0..2], &[0xFF, 0xD8]);
}
#[test]
fn test_svg_to_image() {
let svg = r#"<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
<rect x="0" y="0" width="100" height="100" fill="white"/>
<rect x="25" y="25" width="50" height="50" fill="black"/>
</svg>"#;
let result = RasterRenderer::svg_to_image(svg, 100, 100);
assert!(result.is_ok());
}
#[test]
fn test_full_render() {
let svg = r#"<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
<rect x="0" y="0" width="100" height="100" fill="white"/>
<rect x="25" y="25" width="50" height="50" fill="black"/>
</svg>"#;
let result = RasterRenderer::render(svg, 100, 100, OutputFormat::Png);
assert!(result.is_ok());
let bytes = result.unwrap();
assert_eq!(&bytes[0..4], &[0x89, 0x50, 0x4E, 0x47]);
}
}