use egui::{Context, Image, ImageSource, load::Bytes};
use image::{ImageBuffer, ImageFormat, Luma};
use qrcodegen_no_heap::*;
use std::sync::Arc;
#[cfg(feature = "secure-types")]
use secure_types::Zeroize;
#[derive(Debug, Clone)]
pub enum QrError {
EncodingFailed(String),
ImageError(String),
Other(String),
}
impl QrError {
pub fn to_string(&self) -> String {
match self {
QrError::EncodingFailed(message) => format!("Encoding failed: {}", message),
QrError::ImageError(message) => format!("Image failed: {}", message),
QrError::Other(message) => message.to_string(),
}
}
}
impl std::fmt::Display for QrError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
QrError::EncodingFailed(message) => write!(f, "Encoding failed: {}", message),
QrError::ImageError(message) => write!(f, "Image failed: {}", message),
QrError::Other(message) => write!(f, "{}", message),
}
}
}
impl std::error::Error for QrError {}
#[cfg(feature = "secure-types")]
fn zeroize_image_data(data: &mut [u8]) -> bool {
data.zeroize();
true
}
#[cfg(not(feature = "secure-types"))]
fn zeroize_image_data(_data: &mut [u8]) -> bool {
false
}
pub struct QrImage {
image_data: Arc<[u8]>,
uri: String,
error: Option<QrError>,
}
impl QrImage {
pub fn new(data: &str, uri: String) -> Self {
let res = data_to_qr(data);
let (image_data, error) = match res {
Ok(image_data) => (image_data, None),
Err(e) => (Vec::new(), Some(e)),
};
Self {
image_data: image_data.into(),
uri,
error,
}
}
pub fn empty_with_error(error: String) -> Self {
Self {
image_data: Vec::new().into(),
uri: String::new(),
error: Some(QrError::Other(error)),
}
}
pub fn has_error(&self) -> bool {
self.error.is_some()
}
pub fn error(&self) -> Option<&QrError> {
self.error.as_ref()
}
pub fn is_cleared(&self) -> bool {
self.image_data.is_empty()
}
pub fn image(&self) -> Image<'static> {
let data = self.image_data.clone();
let image = Image::new(ImageSource::Bytes {
uri: self.uri.clone().into(),
bytes: Bytes::Shared(data),
});
image
}
pub fn clear(&mut self, ctx: &Context) -> bool {
ctx.forget_image(&self.uri);
self.error = None;
let success = match Arc::get_mut(&mut self.image_data) {
Some(data) => {
let did_zeroize = zeroize_image_data(data);
self.image_data = Arc::new([0u8; 0]);
did_zeroize
}
None => false,
};
success
}
}
impl Drop for QrImage {
fn drop(&mut self) {
if let Some(data) = Arc::get_mut(&mut self.image_data) {
zeroize_image_data(data);
}
}
}
fn data_to_qr(data: &str) -> Result<Vec<u8>, QrError> {
let mut tempbuffer = vec![0u8; Version::MAX.buffer_len()];
let mut outbuffer = vec![0u8; Version::MAX.buffer_len()];
let qr = QrCode::encode_text(
data,
&mut tempbuffer,
&mut outbuffer,
QrCodeEcc::High,
Version::MIN,
Version::MAX,
None,
true,
)
.map_err(|e| QrError::EncodingFailed(e.to_string()))?;
let size = qr.size() as u32;
let module_px = ((512f32 / size as f32).ceil() as u32).max(1);
let img_size = size * module_px;
let mut img: ImageBuffer<Luma<u8>, Vec<u8>> = ImageBuffer::new(img_size, img_size);
for pixel in img.pixels_mut() {
*pixel = Luma([255u8]); }
for y in 0..size {
for x in 0..size {
if qr.get_module(x as i32, y as i32) {
let px = Luma([0u8]); for dy in 0..module_px {
for dx in 0..module_px {
*img.get_pixel_mut(x * module_px + dx, y * module_px + dy) = px;
}
}
}
}
}
let mut png_bytes = Vec::new();
img.write_to(
&mut std::io::Cursor::new(&mut png_bytes),
ImageFormat::Png,
)
.map_err(|e| QrError::ImageError(e.to_string()))?;
#[cfg(feature = "secure-types")]
{
let mut img_buf = img.into_vec();
img_buf.zeroize();
tempbuffer.zeroize();
outbuffer.zeroize();
}
Ok(png_bytes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qr_image() {
let mut qr = QrImage::new("Hello, world!", "bytes://test".to_string());
{
let _image = qr.image();
}
assert!(!qr.is_cleared());
assert!(!qr.has_error());
qr.clear(&Context::default());
assert!(qr.is_cleared());
}
}