I’ve been dealing with QR codes recently, as one of my side projects generates them, and I got curious about how much data you can really fit in a QR code. Specifically, I wondered how much of my favorite book I could fit on one QR code.

QR Code versions

QR Codes have different versions, but they’re not versions as you’d generally think about them, version in this case just means a particular size. Version 1 has 21 rows of “modules”, or dots, and 21 columns of modules. Version 2 is 25x25, version 3 is 29x29, etc. The highest version in the spec is version 40, which is composed of 177x177 modules. So an upper bound for the bytes we have to work with is 3916 ( 177 * 177 / 8 ), but there is some overhead to each QR code. Some modules are used for alignment, some are used for position (the large squares in the corners), and some are used for metadata like version and format information. The biggest overhead takes the form of error correction.

Error correction

Every QR Code has a couple modules dedicated to communicating the level of error correction. Supported values are L, M, Q, and H. L can correct for roughly a 7% loss of data (ex. if a module has been scraped off such that it can’t be read). M, Q, and H can recover 15%, 25%, and 30% loss of data, respectively. Since each additional level of error correction requires more overhead, in order to store the maximum data possible I’m going to go with the L level.

Plain-text

If we encode The Hitchhiker’s Guide as plain text, in a 177x177 QR Code, with minimal error correction, it turns out we can fit in 2,953 bytes. Here’s that QR code,

This gets us to about the time Arthur remembers being drunk at the pub, but not far enough to make the connection with the bulldozer outside his house.

Compression

Since QR Codes can store arbitrary data, we’re not limited to storing our ASCII text as ASCII text. We can try to compress our data using a library like LZMA, for even better results. I made a simple program in Rust to find the maximum number of bytes I can read from The Hitchhiker’s Guide, while staying underneath the 2,953-byte limit. Here is the program to determine that limit:

use image::Luma;
use qrcode::QrCode;

fn main() {
    let bytes_limit = 2953;
    let mut bytes = 1000;
    let mut step = 100;
    let hitchikers_text = std::fs::read_to_string("hitchhikers.txt").unwrap();
    loop {
        let head_bytes = hitchikers_text
            .as_bytes()
            .iter()
            .take(bytes)
            .cloned()
            .collect::<Vec<u8>>();
        let compressed = lzma::compress(&head_bytes, 9).unwrap();
        let compressed_length = compressed.len();
        if compressed_length == bytes_limit {
            break;
        } else if compressed_length < bytes_limit && step == 1 {
            break;
        } else if compressed_length > bytes_limit {
            step -= 1;
            bytes -= step;
        } else {
            bytes += step;
        }
    }
    println!("The right number of bytes to take is {}", bytes);
}

It’s not the prettiest code I’ve written, but it gets the job done. Turns out, LZMA can compress 6,565 bytes into that 2,953 byte limit, for a compression ratio of 2.22. That’s not a great compression ratio for human text, because the input is pretty small, but still helps quite a bit. Here’s the final QR code, with LZMA-compressed data:

This gets us all the way to the conversation with the Genghis Khan-descended Mr. Prosser. All in all, I’m pretty amazed that so much can fit on that one QR Code.

Here’s the full text encoded by the above QR Code:

The house stood on a slight rise just on the edge of the village. It stood on its own and looked over a broad spread of West Country farmland. Not a remarkable house by any means - it was about thirty years old, squattish, squarish, made of brick, and had four windows set in the front of a size and proportion which more or less exactly failed to please the eye.

The only person for whom the house was in any way special was Arthur Dent, and that was only because it happened to be the one he lived in. He had lived in it for about three years, ever since he had moved out of London because it made him nervous and irritable. He was about thirty as well, dark haired and never quite at ease with himself. The thing that used to worry him most was the fact that people always used to ask him what he was looking so worried about. He worked in local radio which he always used to tell his friends was a lot more interesting than they probably thought. It was, too - most of his friends worked in advertising.

It hadn’t properly registered with Arthur that the council wanted to knock down his house and build an bypass instead.

At eight o’clock on Thursday morning Arthur didn’t feel very good. He woke up blearily, got up, wandered blearily round his room, opened a window, saw a bulldozer, found his slippers, and stomped off to the bathroom to wash.

Toothpaste on the brush - so. Scrub.

Shaving mirror - pointing at the ceiling. He adjusted it. For a moment it reflected a second bulldozer through the bathroom window. Properly adjusted, it reflected Arthur Dent’s bristles. He shaved them off, washed, dried, and stomped off to the kitchen to find something pleasant to put in his mouth.

Kettle, plug, fridge, milk, coffee. Yawn.

The word bulldozer wandered through his mind for a moment in search of something to connect with. The bulldozer outside the kitchen window was quite a big one.

He stared at it.

“Yellow,” he thought and stomped off back to his bedroom to get dressed.

Passing the bathroom he stopped to drink a large glass of water, and another. He began to suspect that he was hung over. Why was he hung over? Had he been drinking the night before? He supposed that he must have been. He caught a glint in the shaving mirror. “Yellow,” he thought and stomped on to the bedroom.

He stood and thought. The pub, he thought. Oh dear, the pub. He vaguely remembered being angry, angry about something that seemed important. He’d been telling people about it, telling people about it at great length, he rather suspected: his clearest visual recollection was of glazed looks on other people’s faces. Something about a new bypass he had just found out about. It had been in the pipeline for months only no one seemed to have known about it. Ridiculous. He took a swig of water. It would sort itself out, he’d decided, no one wanted a bypass, the council didn’t have a leg to stand on. It would sort itself out.

God what a terrible hangover it had earned him though. He looked at himself in the wardrobe mirror. He stuck out his tongue. “Yellow,” he thought. The word yellow wandered through his mind in search of something to connect with.

Fifteen seconds later he was out of the house and lying in front of a big yellow bulldozer that was advancing up his garden path.

Mr. L Prosser was, as they say, only human. In other words he was a carbon-based life form descended from an ape. More specifically he was forty, fat and shabby and worked for the local council. Curiously enough, though he didn’t know it, he was also a direct male-line descendant of Genghis Khan, though intervening generations and racial mixing had so juggled his genes that he had no discernible Mongoloid characteristics, and the only vestiges left in Mr. L Prosser of his mighty ancestry were a pronounced stoutness about the turn and a predilection for little fur hats.

He was by no means a great warrior: in fact he was a nervous worried man. Today he was particularly nervous and worried because something had gone seriously wrong with his job - which was to see that Arthur Dent’s house got cleared out of the way before the day was out.

“Come off it, Mr. Dent,”, he said, “you can’t win you know. You can’t lie in front of the bulldozer indefinitely.” He tried to make his eyes blaze fiercely but they just wouldn’t do it.

Arthur lay in the mud and squelched at him. “I’m game,” he said, “we’ll see who rusts first.”

“I’m afraid you’re going to have to accept it,” said Mr. Prosser gripping his fur hat and rolling it round the top of his head, “this bypass has got to be built and it’s going to be built!”

“First I’ve heard of it,” said Arthur, “why’s it going to be built?”

Mr. Prosser shook his finger at him for a bit, then stopped and put it away again.

“What do you mean, why’s it got to be built?” he said. “It’s a bypass. You’ve got to build bypasses.”

Bypasses are devices which allow some people to drive from point A to point B very fast whilst other people dash from point B to point A very fast. People living at point C, being a point directly in between, are often given to wonder what’s so great about point A that so many people of point B are so keen to get there, and what’s so great about point B that so many people of point A are so keen to get there. They often wish that people would just once and for all work out where the hell they wanted to be.

Mr. Prosser wanted to be at point D. Point D wasn’t anywhere in particular, it was just any convenient point a very long way from points A, B and C. He would have a nice little cottage at point D, with axes over the door, and spend a pleasant amount of time at point E, which would be the nearest pub to point D. His wife of course wanted climbing roses, but he wanted axes. He didn’t know why - he just liked axes. He flushed hotly under the derisive grins of the bulldozer drivers.

He shifted his weight from foot to foot, but it was equally uncomfortable on each. Obviously somebody had been appallingly incompetent and he hoped to God it wasn’t him.

Mr. Prosser said: “You were quite entitled to make any suggestions or protests at the appropriate time you know.”

“Appropriate time?” hooted Arthur. “Appropriate time? The first I knew about it was when a workman arrived at my home yesterday. I asked him if he’d come to clean the windows and he said no he’d come to demolish the house. He didn’t tell me straight away of course. Oh no. First he wiped a couple of windows and charged me a fiver. Then he told me.”

“But Mr. Dent, the plan