Files
galaxy/app/src/drive/sharing/qr_code.rs
T

75 lines
2.2 KiB
Rust

use std::io::Cursor;
use image::{ImageBuffer, ImageFormat, Luma};
use qrcode::{Color, QrCode};
pub const QUIET_ZONE_MODULES: usize = 4;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QrMatrix {
width: usize,
modules: Vec<bool>,
}
#[derive(Debug, thiserror::Error)]
pub enum QrCodeError {
#[error("Unable to encode QR code")]
Encode,
#[error("Unable to encode QR code image")]
Image(#[from] image::ImageError),
}
impl QrMatrix {
pub fn width(&self) -> usize {
self.width
}
pub fn is_dark(&self, x: usize, y: usize) -> bool {
self.modules
.get(y.saturating_mul(self.width).saturating_add(x))
.copied()
.unwrap_or(false)
}
}
pub fn qr_matrix_for_url(url: &str) -> Result<QrMatrix, QrCodeError> {
let code = QrCode::new(url.as_bytes()).map_err(|_| QrCodeError::Encode)?;
let width = code.width();
let modules = code
.to_colors()
.into_iter()
.map(|color| matches!(color, Color::Dark))
.collect();
Ok(QrMatrix { width, modules })
}
pub fn qr_png_for_url(url: &str, pixel_size: u32) -> Result<Vec<u8>, QrCodeError> {
let matrix = qr_matrix_for_url(url)?;
let modules_with_quiet_zone = matrix.width().saturating_add(QUIET_ZONE_MODULES * 2);
let module_size = (pixel_size as usize / modules_with_quiet_zone).max(1);
let image_size = modules_with_quiet_zone.saturating_mul(module_size) as u32;
let mut image = ImageBuffer::from_pixel(image_size, image_size, Luma([255u8]));
for y in 0..matrix.width() {
for x in 0..matrix.width() {
if matrix.is_dark(x, y) {
let start_x = (x + QUIET_ZONE_MODULES) * module_size;
let start_y = (y + QUIET_ZONE_MODULES) * module_size;
for pixel_y in start_y..start_y + module_size {
for pixel_x in start_x..start_x + module_size {
image.put_pixel(pixel_x as u32, pixel_y as u32, Luma([0u8]));
}
}
}
}
}
let mut png = Cursor::new(Vec::new());
image.write_to(&mut png, ImageFormat::Png)?;
Ok(png.into_inner())
}
#[cfg(test)]
#[path = "qr_code_tests.rs"]
mod tests;