From fab729c6be045ec13e4d390a4abe56b52aeccabd Mon Sep 17 00:00:00 2001 From: Maximiliano Sandoval Date: Wed, 1 Jan 2025 17:09:42 +0100 Subject: [PATCH] qrcode_paintable: Use an upscaled texture This is more efficient and draws without gaps in betweek black squares. --- src/widgets/accounts/qrcode_paintable.rs | 78 ++++++++++++------------ 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/src/widgets/accounts/qrcode_paintable.rs b/src/widgets/accounts/qrcode_paintable.rs index aac167d..4a86b69 100644 --- a/src/widgets/accounts/qrcode_paintable.rs +++ b/src/widgets/accounts/qrcode_paintable.rs @@ -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>, + pub size: i32, + pub items: Vec, } impl> From for QRCodeData { fn from(data: B) -> Self { let code = qrencode::QrCode::new(data).unwrap(); let items = code - .render::() - .quiet_zone(false) - .module_dimensions(1, 1) - .build() - .split('\n') - .map(|line| { - line.chars() - .map(|c| !c.is_whitespace()) - .collect::>() - }) - .collect::>>(); + .to_colors() + .iter() + .map(|color| matches!(color, qrencode::types::Color::Dark)) + .collect::>(); - 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 = 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() +}