qrcodegen_image/lib.rs
1//! Utility functions for drawing QR codes generated using `qrcodegen`
2//! to a canvas provided by the `image` crate.
3use image::Luma;
4
5pub use image;
6pub use qrcodegen;
7
8/// Draw a QR code to an image buffer.
9pub fn draw_canvas(qr: qrcodegen::QrCode) -> image::ImageBuffer<Luma<u8>, Vec<u8>> {
10 let size = qr.size() as u32;
11 // "+ 8 * 8" is here to add padding (the white border around the QRCode)
12 // As some QRCode readers don't work without padding
13 let image_size = size * 8 + 8 * 8;
14 let mut canvas = image::GrayImage::from_pixel(image_size, image_size, Luma([255]));
15
16 let raw = canvas.as_mut();
17
18 // The QR inside the white border
19 for x_qr in 0..size {
20 for y_qr in 0..size {
21 if !qr.get_module(x_qr as i32, y_qr as i32) {
22 continue;
23 }
24
25 // Multiply coordinates by width of pixels
26 // And take into account the 8*4 padding on top and left side
27 let x_start = x_qr * 8 + 8 * 4;
28 let y_start = y_qr * 8 + 8 * 4;
29
30 // Draw a 8-pixels-wide square
31 for y_img in y_start..y_start + 8 {
32 let start = (x_start + y_img * image_size) as usize;
33 raw[start..start + 8].copy_from_slice(&[0; 8]);
34 }
35 }
36 }
37 canvas
38}
39
40/// Draw text to a PNG QR code.
41///
42/// # Errors
43///
44/// This will return an error in case the URL gets too long to encode into a QR code.
45/// This would require the get_url method to generate an url bigger than 2000 characters,
46/// Which would be too long for some browsers anyway.
47///
48/// It will also return an error in case it can't encode the qr into a png. This shouldn't happen unless either the qrcode library returns malformed data, or the image library doesn't encode the data correctly.
49pub fn draw_png(text: &str) -> Result<Vec<u8>, String> {
50 use image::ImageEncoder;
51
52 let mut vec = Vec::new();
53
54 let qr: Result<qrcodegen::QrCode, String> =
55 match qrcodegen::QrCode::encode_text(text, qrcodegen::QrCodeEcc::Medium) {
56 Ok(qr) => Ok(qr),
57 Err(err) => Err(err.to_string()),
58 };
59
60 if qr.is_err() {
61 return Err(qr.err().unwrap());
62 }
63
64 let code = qr?;
65
66 // "+ 8 * 8" is here to add padding (the white border around the QRCode)
67 // As some QRCode readers don't work without padding
68 let image_size = (code.size() as u32) * 8 + 8 * 8;
69
70 let canvas = draw_canvas(code);
71
72 // Encode the canvas into a PNG
73 let encoder = image::codecs::png::PngEncoder::new(&mut vec);
74 match encoder.write_image(
75 &canvas.into_raw(),
76 image_size,
77 image_size,
78 image::ExtendedColorType::L8,
79 ) {
80 Ok(_) => Ok(vec),
81 Err(err) => Err(err.to_string()),
82 }
83}
84
85/// Draw text to a Base64-encoded PNG QR code.
86///
87/// # Errors
88///
89/// This will return an error in case the URL gets too long to encode into a QR code.
90/// This would require the get_url method to generate an url bigger than 2000 characters,
91/// Which would be too long for some browsers anyway.
92///
93/// It will also return an error in case it can't encode the qr into a png. This shouldn't happen unless either the qrcode library returns malformed data, or the image library doesn't encode the data correctly.
94#[cfg(feature = "base64")]
95pub fn draw_base64(text: &str) -> Result<String, String> {
96 use base64::{engine::general_purpose, Engine as _};
97 draw_png(text).map(|vec| general_purpose::STANDARD.encode(vec))
98}