qrcode_paintable: Use an upscaled texture

This is more efficient and draws without gaps in betweek black squares.
This commit is contained in:
Maximiliano Sandoval 2025-01-01 17:09:42 +01:00 committed by Bilal Elmoussaoui
parent 7fd6186886
commit fab729c6be

View file

@ -1,35 +1,22 @@
use gtk::{gdk, glib, graphene, prelude::*, subclass::prelude::*};
use gtk::{gdk, glib, graphene, gsk, prelude::*, subclass::prelude::*};
#[allow(clippy::upper_case_acronyms)]
pub struct QRCodeData {
pub width: i32,
pub height: i32,
pub items: Vec<Vec<bool>>,
pub size: i32,
pub items: Vec<bool>,
}
impl<B: AsRef<[u8]>> From<B> for QRCodeData {
fn from(data: B) -> Self {
let code = qrencode::QrCode::new(data).unwrap();
let items = code
.render::<char>()
.quiet_zone(false)
.module_dimensions(1, 1)
.build()
.split('\n')
.map(|line| {
line.chars()
.map(|c| !c.is_whitespace())
.collect::<Vec<bool>>()
})
.collect::<Vec<Vec<bool>>>();
.to_colors()
.iter()
.map(|color| matches!(color, qrencode::types::Color::Dark))
.collect::<Vec<bool>>();
let width = items.first().unwrap().len() as i32;
let height = items.len() as i32;
Self {
width,
height,
items,
}
let size = code.width() as i32;
Self { size, items }
}
}
@ -55,30 +42,23 @@ mod imp {
impl PaintableImpl for QRCodePaintable {
fn snapshot(&self, snapshot: &gdk::Snapshot, width: f64, height: f64) {
if let Some(ref qrcode) = *self.qrcode.borrow() {
let padding_squares = 3.max(qrcode.height / 10);
let square_height = height as f32 / (qrcode.height + 2 * padding_squares) as f32;
let square_width = width as f32 / (qrcode.width + 2 * padding_squares) as f32;
let padding_squares = 3.max(qrcode.size / 10);
let square_height = height as f32 / (qrcode.size + 2 * padding_squares) as f32;
let square_width = width as f32 / (qrcode.size + 2 * padding_squares) as f32;
let padding = square_height * padding_squares as f32;
let rect = graphene::Rect::new(0.0, 0.0, width as f32, height as f32);
snapshot.append_color(&gdk::RGBA::WHITE, &rect);
qrcode.items.iter().enumerate().for_each(|(y, line)| {
line.iter().enumerate().for_each(|(x, is_dark)| {
if *is_dark {
let mut black = gdk::RGBA::BLACK;
black.set_alpha(0.85);
let position = graphene::Rect::new(
(x as f32) * square_width + padding,
(y as f32) * square_height + padding,
square_width,
square_height,
);
let inner_rect = graphene::Rect::new(
padding,
padding,
square_width * qrcode.size as f32,
square_height * qrcode.size as f32,
);
snapshot.append_color(&black, &position);
};
});
});
let texture = texture(qrcode);
snapshot.append_scaled_texture(&texture, gsk::ScalingFilter::Nearest, &inner_rect);
}
}
}
@ -101,3 +81,21 @@ impl Default for QRCodePaintable {
glib::Object::new()
}
}
fn texture(qrcode: &QRCodeData) -> gdk::Texture {
let size = qrcode.size;
const G8_SIZE: usize = 1;
const WHITE: u8 = 0xff; // #ffffff
const BLACK: u8 = 0x24; // #242424
let bytes: Vec<u8> = qrcode
.items
.iter()
.map(|is_black| if *is_black { BLACK } else { WHITE })
.collect();
let bytes = glib::Bytes::from_owned(bytes);
let stride = G8_SIZE * size as usize;
gdk::MemoryTexture::new(size, size, gdk::MemoryFormat::G8, &bytes, stride).upcast()
}