QR Code creation in Rust


#1

Has anyone successfully generated QR code images using Rust? I have been playing with the qrcode crate example to generate the raw data, and then feeding this into lodepng to generate a PNG image, but what I end up with doesn’t seem to be valid.

extern crate lodepng;
use std::path::Path;

extern crate qrcode;

fn main() {

    let data = "http://www.rust-lang.org";

    let code = qrcode::QrCode::new(data.as_bytes()).unwrap();

    // println!("{}", code.to_debug_str('#', '.') );

    let mut imgdata = vec![];

    let mut qr_modules = 0;

    for y in 0 .. code.width() {
        for x in 0 .. code.width() {
            if code[(x, y)] {
                imgdata.push(255u8);
                imgdata.push(255u8);
                imgdata.push(255u8);
            } else {
                imgdata.push(0u8);
                imgdata.push(0u8);
                imgdata.push(0u8);
            };
        }
        qr_modules += 1;
    };


    println!("Total QR Code Modules: {}", qr_modules);

    let path = &Path::new("write_test.png");

    let image = &imgdata[..];

    // Use number of QR modules to specify the image dimensions.
    if let Err(e) = lodepng::encode_file(path, &image, qr_modules, qr_modules, lodepng::LCT_RGB, 8) {
        panic!("failed to write png: {:?}", e);
    }

    println!("Written to {}", path.display());
}

#2

After much banging of head, my problem was that I had inverted the push of black/white pixels into the vector! :blush: So now:

for y in 0 .. code.width() {
    for x in 0 .. code.width() {
        if code[(x, y)] {
            imgdata.push(0u8);
            imgdata.push(0u8);
            imgdata.push(0u8);
        } else {
            imgdata.push(255u8);
            imgdata.push(255u8);
            imgdata.push(255u8);
        };
    }
    qr_modules += 1;
};

is working and the iPhone QR code reader app now loads the URL as expected!

I’d still be interested to hear of more elegant QR image generation approaches in Rust, as my example above is currently limited to only generating a pixel-to-module resolution.


#3

I gave it a shot using Iterators. Also note that you can get rid of qr_modules as it’s just equal to code.width():

extern crate lodepng;
use std::path::Path;

extern crate qrcode;

fn main() {
  let data = "http://www.rust-lang.org";

  let code = qrcode::QrCode::new(data.as_bytes()).unwrap();
  let output_size = code.width();
  
  let imgdata: Vec<u8> = code.into_vec()
    .into_iter()
    .flat_map(|b| {
      if b { vec![0u8; 3] } else { vec![255u8; 3] }
    })
    .collect();

  println!("Total QR Code Modules: {}", output_size);

  let path = &Path::new("write_test.png");

  // Use number of QR modules to specify the image dimensions.
  if let Err(e) = lodepng::encode_file(path, &imgdata, output_size, output_size, lodepng::LCT_RGB, 8) {
    panic!("failed to write png: {:?}", e);
  }

  println!("Written to {}", path.display());
}

#4

Thanks, will give that a go!


#5

I imagine this line would be a little slow:

.flat_map(|b| {
      if b { vec![0u8; 3] } else { vec![255u8; 3] }
})

Micro-allocations like this are generally not very performant, even with a well optimized allocator.

I would do something like:

use std::iter;

// ...

.flat_map(|b| {
      iter::repeat(if b { 0u8 } else { 255u8 }).take(3)
})

This should inline very cleanly. In fact, the latter version is about an order of magnitude faster in this basic test: https://is.gd/WhPXp3


#6

This is quite interesting. Is there any documentation I can read into on why repeat is favorable?


#7

The primary reason the vec![] version is slower is because it’s making a lot of small allocations and then freeing them almost immediately. You don’t want to be doing that in a tight loop like this, because it’s putting an immense pressure on the allocator.

The other issue is that the compiler can only make limited assumptions about the contents of Vec, and more importantly, it can’t elide the allocation because that would be implicitly removing side-effects which isn’t always desirable.

On the other hand, iter::repeat().take() is trivially optimizable. LLVM can inline and unroll it into the .collect() loop and work in 3-byte chunks on the output vector:

let code = code.into_vec();
let out_vec = Vec::with_capacity(code.len() * 3);
unsafe { out_vec.set_len(code.len() * 3); }

for i in (0 .. code.len) {
    // With some bit-twiddling, this branch can be turned into a few sequential operations.
    let val = if code[i] { 0u8 } else { 255u8 };
    out_vec[i * 3] = val;
    out_vec[i * 3 + 1] = val;
    out_vec[i * 3 + 2] = val;
}