Nerdy creations with numbers, words, sounds, and pixels

Art, Experimental, Science

QRfypx

Introduction

QR codes are weird, efficient, and ingenious. But the same is said of the Japanese, who invented them. With more humble intentions, I started this project with the idea of rearranging the dark and bright regions of an image to encode information. You can see one of the first attempts below. In this case, the message is converted into individual bits, which form the dark and bright cells.

Bitwise encoding of text in cell brightness

However, a common problem with most of my ciphers is that you need special tools to decode them, many of which I never released to the public. Hence, I decided to use QR codes this time because they are standardized, and all you need is the camera app on your phone. The goal was then to transform arbitrary input images into QR codes that lead to arbitrary websites.

Naturally, the most basic move was to break down the input image into the same number of squares that are in a standard QR code, i.e., 29 by 29. If the image is not square, the margins are simply cut off. Each square, let’s call it a cell, is then sorted into one of two lists: dark and bright. These lists are then used to fill the black and white squares of the QR code randomly. Since the number of dark and bright cells is unlikely to coincide with the black and white squares of the QR code, one of the two lists needs to be wrapped around. You can see the result in the image below.

Random cell pattern with QR code

Patterns

I almost posted this topic under “Cryptology” instead of “Generative Art” because it was able to transfer information and bore similarities to steganography. But on top of that, I was interested in finding patterns for the output images that were easier on the eyes and resembled the input images more closely. Below, you can see five simple geometric approaches:

  • Index mode: inserting cells based on their index in the input image.
  • Reverse mode: similar to index mode but backward.
  • Vortex mode: inserting cells into a spiral pattern.
  • Horizontal mode: inserting cells with a horizontal “bleed” effect.
  • Vertical mode: similar to horizontal mode but transposed.

Admittedly, the five output images are difficult to distinguish from a distance, but you can already see that some resemble the input image more closely than others.

Different geometrical cell patterns

Taking it up a notch, I explored two more daring approaches:

  • Maintain mode: pre-filling the output image with the cells from the input image where permitted by the QR code. Remaining cells are then filled similarly to the index mode.
  • Nearest mode: similar to maintain mode, but the remaining cells are filled with the nearest valid neighbor to their positions.

Needless to say, both variants require more power but end up closer to the input images, making especially eyes and faces easy to recognize. I am certain that gaping at images like these triggers some primal instincts in us that helped our predecessors recognize predators hiding in the bushes. For example, a creepy Kandinsky baboon like the one shown below.

Advanced patterns computing nearest valid cells

Tool

At this point, performance was still decent enough to turn my messy script into a small tool. It runs on Python but features a GUI so that the user—i.e., you—does not have to fiddle around in the code. In terms of robustness and looks, it might not be perfect, but it gets the job done. Deciding to parallelize the program and use 50% of CPU cores brought additional issues—for example, I cannot remember when I last crashed my computer this hard while developing art. It looked like winning Solitaire on Windows 98! Apparently, the program was opened over and over again on every CPU thread. Luckily, I am a “hyperanxious quicksaver,” and no progress was lost.

User interface of QRfypx tool

The tool also lets you choose additional parameters: the image can be equalized to spread the distribution of gray values. This helps with images that contain many more bright than dark cells, or vice versa, but heavily distorts the colors. I owe you that information: the entire math is performed in grayscale, only the output images are re-colorized in RGB. Therefore, it was no feat to implement an (optional) grayscale output mode.

In case the input image is not equalized, there are other methods to restore the balance between dark and bright cells. From my engineering studies, I knew how adaptive algorithms that binarize images—essentially what we are doing here—may compute the arithmetic mean of all gray values to determine an ideal global threshold. Computation costs aside, the median may be used instead; it corresponds to that one cell out of 841 which is brighter than 50% of its peers and darker than the other 50%. Of course, to have absolute control, you may also set a custom gray value threshold using a slider. However, the image below demonstrates how too low values make the dark cells more repetitive and even darker, while too high values make the bright cells behave analogously.

Different methods to average gray value thresholds for image binarization

Another functionality I implemented early on was a “trench” value to better separate dark gray values from bright ones, which should make it easier for the cameras to recognize the QR code. Another interpretation of the trench is an increased contrast, ignoring that I implemented an actual contrast slider. The trench determines a range of prohibited gray values around the global threshold, and all cell brightness values that fall within this range are remapped to darker or brighter values. The effect is similar to the custom threshold demonstrated above but more symmetrical and also useful with the mean and median threshold modes—in fact, the most useful parameter if you want to increase the machine legibility of your QR code. Maybe try and scan each of the three images below. If your phone camera can recognize the one on the right, it is at least as good as the camera in iOS 18. Recognition happens despite the text being partially in the way because the smart Japanese ingrained some redundancy in the QR code.

Symmetrical increase of the gap between dark and bright cells, remapping gray values

Conclusion

QRfypx, pronounced “qurify pix,” is a Python tool freely available to everyone on GitHub. If you want to run it on Windows, you can simply download the release version. In case of Mac or Linux, or if you want to take a closer look at the code, you can download the Python script but may have to install additional modules.

The tool accepts an input image and a URL, combines them, and lets you mess around with different patterns and brightness settings to find the ideal balance between aesthetics and machine interpretability. Output images are regenerated whenever the UI detects a value change, running reasonably fast on a high-end CPU despite using Python. However, machine interpretability of the output images depends very much on the camera device, and sometimes stepping away from the code makes it easier to be read. If maximum compatibility and clear communication are key, do not generate your QR codes with QRfypx. But if you want to append them to your Instagram posts and take part in a bit of steganography, go ahead.

Lastly, here are three of my favorite output images so far:

The Garden of Earthly Delights, Hieronymus Bosch

Farewell to the Fairground

Elegant Baboon

← Return to “Generative”

← Return to “Art”

Leave a Reply