//! QR 码图像渲染(支持 PNG/BMP/JPEG/WebP) //! //! 使用 `image` crate 将 QR 模块矩阵渲染为像素缓冲区,可选叠加 Logo。 use crate::qr::QrCode; use image::{imageops, ImageBuffer, Rgba, RgbaImage}; /// 输出图像格式 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OutputFormat { Png, Bmp, Jpeg, WebP, } impl OutputFormat { /// 转为 `image` crate 的格式枚举 fn to_image_format(self) -> image::ImageFormat { match self { Self::Png => image::ImageFormat::Png, Self::Bmp => image::ImageFormat::Bmp, Self::Jpeg => image::ImageFormat::Jpeg, Self::WebP => image::ImageFormat::WebP, } } /// 文件扩展名(不含点) pub fn extension(self) -> &'static str { match self { Self::Png => "png", Self::Bmp => "bmp", Self::Jpeg => "jpeg", Self::WebP => "webp", } } /// MIME 类型 pub fn mime(self) -> &'static str { match self { Self::Png => "image/png", Self::Bmp => "image/bmp", Self::Jpeg => "image/jpeg", Self::WebP => "image/webp", } } /// 从扩展名解析 pub fn from_ext(ext: &str) -> Option { match ext.to_lowercase().as_str() { "png" => Some(Self::Png), "bmp" => Some(Self::Bmp), "jpeg" | "jpg" => Some(Self::Jpeg), "webp" => Some(Self::WebP), _ => None, } } } fn fill_module( img: &mut RgbaImage, x: u32, y: u32, module_size: u32, is_dark: bool, fg: &[u8; 3], bg: &[u8; 3], ) { let color = if is_dark { Rgba([fg[0], fg[1], fg[2], 255]) } else { Rgba([bg[0], bg[1], bg[2], 255]) }; let x0 = x * module_size; let y0 = y * module_size; for dy in 0..module_size { for dx in 0..module_size { img.put_pixel(x0 + dx, y0 + dy, color); } } } fn overlay_logo(img: &mut RgbaImage, logo_bytes: &[u8], logo_size_pct: f32) -> Result<(), String> { let logo = image::load_from_memory(logo_bytes).map_err(|e| format!("Logo 加载失败: {e}"))?; let logo = logo.to_rgba8(); let img_w = img.width(); let img_h = img.height(); let logo_size = (img_w.min(img_h) as f32 * logo_size_pct) as u32; if logo_size < 4 { return Ok(()); } let resized = imageops::resize(&logo, logo_size, logo_size, imageops::FilterType::Lanczos3); let x = (img_w - logo_size) / 2; let y = (img_h - logo_size) / 2; imageops::overlay(img, &resized, x as i64, y as i64); Ok(()) } /// 渲染 QR 码到图像字节(支持 PNG/BMP/JPEG/WebP) pub fn render_image( qr: &QrCode, module_size: u8, format: OutputFormat, logo: Option<&[u8]>, ) -> Result, image::ImageError> { let matrix_size = qr.size() as u32; let margin = qr.margin as u32; let total_size = matrix_size + 2 * margin; let img_size = total_size * module_size as u32; let mut img = ImageBuffer::new(img_size, img_size); for y in 0..total_size { for x in 0..total_size { let is_dark = if x >= margin && x < margin + matrix_size && y >= margin && y < margin + matrix_size { let mx = (x - margin) as usize; let my = (y - margin) as usize; qr.modules()[my][mx] } else { false }; fill_module( &mut img, x, y, module_size as u32, is_dark, &qr.fg_color, &qr.bg_color, ); } } if let Some(logo_data) = logo { let _ = overlay_logo(&mut img, logo_data, 0.25); } let mut buf = Vec::new(); img.write_to( &mut std::io::Cursor::new(&mut buf), format.to_image_format(), )?; Ok(buf) }