br_code/
qrcode.rs

1use imageproc::drawing::draw_text_mut;
2use image;
3use image::{imageops, imageops::FilterType, DynamicImage, Luma, Rgba};
4use qrcode::render::svg;
5use qrcode::QrCode;
6use std::time::{SystemTime, UNIX_EPOCH};
7use std::{env, fs};
8use ab_glyph::{FontArc, PxScale};
9
10/// 生成二维码
11pub fn qrcode_create(text: &str, mode: &str) -> Result<String, String> {
12    let temp_dir = env::temp_dir();
13    let filename = format!("{}.{mode}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
14    let filepath = temp_dir.join(filename);
15
16    let code = match QrCode::new(text.as_bytes()) {
17        Ok(e) => e,
18        Err(e) => return Err(format!("Error QrCode file: {}", e)),
19    };
20    match mode {
21        "svg" => {
22            let string = code.render().min_dimensions(200, 200).dark_color(svg::Color("#000000")).light_color(svg::Color("#ffffff")).build();
23            match fs::write(filepath.clone(), string) {
24                Ok(_) => Ok(filepath.to_str().unwrap().to_string()),
25                Err(e) => Err(format!("Error writing svg file: {}", e)),
26            }
27        }
28        "png" => {
29            let image = code.render::<Luma<u8>>().build();
30            match image.save(filepath.clone()) {
31                Ok(_) => Ok(filepath.to_str().unwrap().to_string()),
32                Err(e) => Err(format!("Error writing png file: {}", e)),
33            }
34        }
35        _ => {
36            let image = code.render::<Luma<u8>>().min_dimensions(200, 200).build();
37            match image.save(filepath.clone()) {
38                Ok(_) => Ok(filepath.to_str().unwrap().to_string()),
39                Err(e) => Err(format!("Error writing file: {}", e)),
40            }
41        }
42    }
43}
44
45pub fn qrcode_read(filename: &str) -> Vec<String> {
46    let mut qrcode = vec![];
47
48    let img = image::open(filename);
49    let img_gray = match img {
50        Ok(e) => e.into_luma8(),
51        Err(_) => {
52            return qrcode;
53        }
54    };
55
56    let mut decoder = quircs::Quirc::default();
57    let codes = decoder.identify(img_gray.width() as usize, img_gray.height() as usize, &img_gray);
58    for code in codes {
59        let code = match code {
60            Ok(e) => e,
61            Err(_) => {
62                return qrcode;
63            }
64        };
65        let decoded = match code.decode() {
66            Ok(e) => e,
67            Err(_) => {
68                return qrcode;
69            }
70        };
71        qrcode.push(std::str::from_utf8(&decoded.payload.clone()).unwrap().to_string());
72    }
73    qrcode
74}
75
76/// 生成 svg 二维码
77pub fn create_qrcode(code: &str) -> String {
78    let code = QrCode::new(code).unwrap();
79    let string = code.render().min_dimensions(200, 200).dark_color(svg::Color("#000000")).light_color(svg::Color("#ffffff")).build();
80    string.replace("width=\"203\"", "width=\"100%\"").replace("height=\"203\"", "height=\"100%\"")
81}
82
83/// 合成二维码 + 图片
84pub fn compose_qrcode(
85    bg_data: &[u8],
86    qrcode_data: &[u8],
87    pos_x: i64,
88    pos_y: i64,
89    size: u32,
90) -> Result<String, String> {
91    let mut bg_img = image::load_from_memory(bg_data)
92        .map_err(|e| format!("无法加载背景图: {}", e))?
93        .to_rgba8();
94
95    let mut qr_img = image::load_from_memory(qrcode_data)
96        .map_err(|e| format!("无法加载二维码: {}", e))?
97        .to_rgba8();
98
99    // 白色透明化
100    for pixel in qr_img.pixels_mut() {
101        if pixel[0] > 240 && pixel[1] > 240 && pixel[2] > 240 {
102            pixel[3] = 0;
103        }
104    }
105
106    let qr_resized = DynamicImage::ImageRgba8(qr_img)
107        .resize_exact(size, size, FilterType::Lanczos3);
108
109    imageops::overlay(&mut bg_img, &qr_resized, pos_x, pos_y);
110
111    // 保存到临时文件
112    let temp_dir = env::temp_dir();
113    let filename = format!("{}.png", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
114    let filepath = temp_dir.join(filename);
115    bg_img.save(&filepath)
116        .map_err(|e| format!("保存二维码合成图片失败: {}", e))?;
117
118    Ok(filepath.to_str().unwrap().to_string())
119}
120
121/// 合成文字 + 图片
122pub fn compose_text(
123    bg_data: &[u8],
124    font_data: &[u8],
125    text: &str,
126    pos_x: i32,
127    pos_y: i32,
128    font_size: f32,
129) -> Result<String, String> {
130    // 加载背景图
131    let mut bg_img = image::load_from_memory(bg_data)
132        .map_err(|e| format!("无法加载背景图: {}", e))?
133        .to_rgba8();
134
135    // 用 ab_glyph 加载字体
136    let font = FontArc::try_from_vec(font_data.to_vec())
137        .map_err(|_| "加载字体失败")?;
138
139    // 字体大小
140    let scale = PxScale::from(font_size);
141
142    // 绘制文字
143    draw_text_mut(
144        &mut bg_img,
145        Rgba([0, 0, 0, 255]),
146        pos_x,
147        pos_y,
148        scale,
149        &font,
150        text,
151    );
152
153    // 保存临时文件
154    let temp_dir = env::temp_dir();
155    let filename = format!("{}.png", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
156    let filepath = temp_dir.join(filename);
157
158    bg_img.save(&filepath)
159        .map_err(|e| format!("保存文字合成图片失败: {}", e))?;
160
161    Ok(filepath.to_str().unwrap().to_string())
162}