use imageproc::drawing::draw_text_mut;
use image;
use image::{imageops, imageops::FilterType, DynamicImage, Luma, Rgba};
use qrcode::render::svg;
use qrcode::QrCode;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{env, fs};
use ab_glyph::{FontArc, PxScale};
pub fn qrcode_create(text: &str, mode: &str) -> Result<String, String> {
let temp_dir = env::temp_dir();
let filename = format!("{}.{mode}", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
let filepath = temp_dir.join(filename);
let code = match QrCode::new(text.as_bytes()) {
Ok(e) => e,
Err(e) => return Err(format!("Error QrCode file: {}", e)),
};
match mode {
"svg" => {
let string = code.render().min_dimensions(200, 200).dark_color(svg::Color("#000000")).light_color(svg::Color("#ffffff")).build();
match fs::write(filepath.clone(), string) {
Ok(_) => Ok(filepath.to_str().unwrap().to_string()),
Err(e) => Err(format!("Error writing svg file: {}", e)),
}
}
"png" => {
let image = code.render::<Luma<u8>>().build();
match image.save(filepath.clone()) {
Ok(_) => Ok(filepath.to_str().unwrap().to_string()),
Err(e) => Err(format!("Error writing png file: {}", e)),
}
}
_ => {
let image = code.render::<Luma<u8>>().min_dimensions(200, 200).build();
match image.save(filepath.clone()) {
Ok(_) => Ok(filepath.to_str().unwrap().to_string()),
Err(e) => Err(format!("Error writing file: {}", e)),
}
}
}
}
pub fn qrcode_read(filename: &str) -> Result<Vec<String>, String> {
let mut qrcode = vec![];
let img = image::open(filename);
let img_gray = match img {
Ok(e) => e.into_luma8(),
Err(e) => return Err(format!("Error reading image {}", e))
};
let mut decoder = quircs::Quirc::default();
let codes = decoder.identify(img_gray.width() as usize, img_gray.height() as usize, &img_gray);
for code in codes {
let code = match code {
Ok(e) => e,
Err(e) => return Err(format!("Error reading image {}", e))
};
let decoded = match code.decode() {
Ok(e) => e,
Err(e) => return Err(format!("Error reading image {}", e))
};
qrcode.push(std::str::from_utf8(&decoded.payload.clone()).unwrap().to_string());
}
Ok(qrcode)
}
pub fn create_qrcode(code: &str) -> String {
let code = QrCode::new(code).unwrap();
let string = code.render().min_dimensions(200, 200).dark_color(svg::Color("#000000")).light_color(svg::Color("#ffffff")).build();
string.replace("width=\"203\"", "width=\"100%\"").replace("height=\"203\"", "height=\"100%\"")
}
pub fn compose_qrcode(
bg_data: &[u8],
qrcode_data: &[u8],
pos_x: i64,
pos_y: i64,
size: u32,
) -> Result<String, String> {
let mut bg_img = image::load_from_memory(bg_data).map_err(|e| format!("无法加载背景图: {}", e))?.to_rgba8();
let mut qr_img = image::load_from_memory(qrcode_data).map_err(|e| format!("无法加载二维码: {}", e))?.to_rgba8();
for pixel in qr_img.pixels_mut() {
if pixel[0] > 240 && pixel[1] > 240 && pixel[2] > 240 {
pixel[3] = 0;
}
}
let qr_resized = DynamicImage::ImageRgba8(qr_img).resize_exact(size, size, FilterType::Lanczos3);
imageops::overlay(&mut bg_img, &qr_resized, pos_x, pos_y);
let temp_dir = env::temp_dir();
let filename = format!("{}.png", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
let filepath = temp_dir.join(filename);
bg_img.save(&filepath).map_err(|e| format!("保存二维码合成图片失败: {}", e))?;
Ok(filepath.to_str().unwrap().to_string())
}
pub fn compose_text(
bg_data: &[u8],
font_data: &[u8],
text: &str,
pos_x: i32,
pos_y: i32,
font_size: f32,
) -> Result<String, String> {
let mut bg_img = image::load_from_memory(bg_data).map_err(|e| format!("无法加载背景图: {}", e))?.to_rgba8();
let font = FontArc::try_from_vec(font_data.to_vec()).map_err(|_| "加载字体失败")?;
let scale = PxScale::from(font_size);
draw_text_mut(
&mut bg_img,
Rgba([0, 0, 0, 255]),
pos_x,
pos_y,
scale,
&font,
text,
);
let temp_dir = env::temp_dir();
let filename = format!("{}.png", SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs());
let filepath = temp_dir.join(filename);
bg_img.save(&filepath).map_err(|e| format!("保存文字合成图片失败: {}", e))?;
Ok(filepath.to_str().unwrap().to_string())
}
#[cfg(test)]
mod tests {
use std::fs;
#[test]
fn qrcode_create() {
let i = match crate::qrcode::qrcode_create("otpauth://totp/br:t?secret=VIDKKSUNCJGXELPWK6MG2BWZOTQJVTQINRLYUSNV6YQNRRJMVZHA&issuer=br", "png") {
Ok(e) => e,
Err(_) => {
return;
}
};
let res = fs::read(i).unwrap();
fs::write("./examples/444.png", res).unwrap();
}
#[test]
fn qrcode_read() {
let i = match crate::qrcode::qrcode_read("./examples/444.png") {
Ok(e) => e,
Err(_) => {
return;
}
};
for item in i {
assert_eq!(item, "otpauth://totp/br:t?secret=VIDKKSUNCJGXELPWK6MG2BWZOTQJVTQINRLYUSNV6YQNRRJMVZHA&issuer=br");
}
}
}