feat: Logo 嵌入 — QR 码中央叠加自定义图片
- PNG 渲染:RgbaImage 上使用 imageops::overlay 叠加 logo - SVG 渲染:base64 编码 logo 嵌入 <image> 标签 - QrCode::to_png_bytes / to_svg 新增 Option<logo_bytes> 参数 - Logo 默认占 QR 区域 25%,建议配合 H 级纠错使用
This commit is contained in:
+39
-3
@@ -1,6 +1,6 @@
|
||||
use crate::qr::QrCode;
|
||||
|
||||
pub fn render_svg(qr: &QrCode) -> String {
|
||||
pub fn render_svg(qr: &QrCode, logo: Option<&[u8]>) -> String {
|
||||
let matrix_size = qr.size() as u32;
|
||||
let margin = qr.margin as u32;
|
||||
let total = matrix_size + 2 * margin;
|
||||
@@ -20,9 +20,9 @@ pub fn render_svg(qr: &QrCode) -> String {
|
||||
.flat_map(|row| row.iter())
|
||||
.filter(|&&m| m)
|
||||
.count();
|
||||
let mut svg = String::with_capacity(200 + dark_count * 50);
|
||||
let mut svg = String::with_capacity(300 + dark_count * 50);
|
||||
svg.push_str(&format!(
|
||||
r#"<svg xmlns="http://www.w3.org/2000/svg" width="{total}" height="{total}" viewBox="0 0 {total} {total}">"#
|
||||
r#"<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{total}" height="{total}" viewBox="0 0 {total} {total}">"#
|
||||
));
|
||||
svg.push_str(&format!(
|
||||
r#"<rect width="{total}" height="{total}" fill="{bg}"/>"#
|
||||
@@ -40,6 +40,42 @@ pub fn render_svg(qr: &QrCode) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Logo 嵌入:base64 PNG data URL
|
||||
if let Some(logo_bytes) = logo {
|
||||
let b64 = base64_encode(logo_bytes);
|
||||
let logo_size = total as f32 * 0.25;
|
||||
let logo_x = (total as f32 - logo_size) / 2.0;
|
||||
let logo_y = (total as f32 - logo_size) / 2.0;
|
||||
svg.push_str(&format!(
|
||||
r#"<image x="{logo_x}" y="{logo_y}" width="{logo_size}" height="{logo_size}" xlink:href="data:image/png;base64,{b64}"/>"#
|
||||
));
|
||||
}
|
||||
|
||||
svg.push_str("</svg>");
|
||||
svg
|
||||
}
|
||||
|
||||
/// 简易 base64 编码(无外部依赖)
|
||||
fn base64_encode(data: &[u8]) -> String {
|
||||
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
let mut result = String::with_capacity((data.len() + 2) / 3 * 4);
|
||||
for chunk in data.chunks(3) {
|
||||
let b0 = chunk[0] as u32;
|
||||
let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
|
||||
let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
|
||||
let triple = (b0 << 16) | (b1 << 8) | b2;
|
||||
result.push(CHARS[((triple >> 18) & 0x3F) as usize] as char);
|
||||
result.push(CHARS[((triple >> 12) & 0x3F) as usize] as char);
|
||||
if chunk.len() > 1 {
|
||||
result.push(CHARS[((triple >> 6) & 0x3F) as usize] as char);
|
||||
} else {
|
||||
result.push('=');
|
||||
}
|
||||
if chunk.len() > 2 {
|
||||
result.push(CHARS[(triple & 0x3F) as usize] as char);
|
||||
} else {
|
||||
result.push('=');
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user