A Recursive QR Code


A QR code zooming in on itself.

I've been thinking about fun little artistic things to do with QR codes. What if each individual pixel were a QR code?

There's two fundamental problems with that idea. Firstly, a QR code needs whitespace around it in order to be scanned properly.

So I focussed on the top left positional marker. There's plenty of whitespace there.

Secondly, because QR codes contain a lot of white pixels inside them, scaling down the code usually results in a grey square - which is unlikely to be recognised as a black pixel when scanning.

So I cheated! I made the smaller code transparent and gradually increased its opacity as it grows larger.

I took a Version 2 QR code - which is 25px wide. With a 2px whitespace border around it, that makes 29px * 29px.

Blow it up to 2900px * 2900px. That will be the base image.

Take the original 25px code and blow it up to the size of the new marker, 300px * 300px. Place it on a new transparent canvas the size of the base image, and place it where the marker is - 400px from the top and left.

Next step is creating the image sequence for zooming in. The aim is to move in to the target area, then directly zoom in.

The whole code, if you want to build one yourself, is:

 BASH#!/bin/bash

#   Input file
input="25.png"

#   Add a whitespace border
convert "$input" -bordercolor white -border 2 29.png

#   Upscaled image size
upscaled_size=2900

#   Scale it up for the base
convert 29.png -scale "${upscaled_size}x${upscaled_size}"\! base.png

#   Create the overlay
convert -size "${upscaled_size}x${upscaled_size}" xc:none canvas.png
convert "$input" -scale 300x300\! 300.png
convert canvas.png 300.png -geometry +400+400 -composite overlay.png

#   Start crop size (full image) and end crop size (target region)
start_crop=$upscaled_size
end_crop=350

#   Zoom-in target position (top-left corner)
target_x=375
target_y=375

#   Start with a completely opaque image
original_opacity=0

#   Number of intermediate images
steps=100

for i in $(seq 0 $((steps - 1))); do
    #   Calculate current crop size
    crop_size=$(echo "$start_crop - ($start_crop - $end_crop) * $i / ($steps - 1)" | bc)
    crop_size=$(printf "%.0f" "$crop_size")  # Round to nearest integer

    #   Keep zoom centered on the target
    crop_x_offset=$(echo "$target_x - ($crop_size - $end_crop) / 2" | bc)
    crop_y_offset=$(echo "$target_y - ($crop_size - $end_crop) / 2" | bc)

    #   Once centred, zoom in normally
    if (( crop_x_offset < 0 )); then crop_x_offset=0; fi
    if (( crop_y_offset < 0 )); then crop_y_offset=0; fi

    #   Generate output filenames
    background_file=$(printf "%s_%03d.png" "background" "$i")
    overlay_file=$(printf "%s_%03d.png" "overlay" "$i")
    combined_file=$(printf "%s_%03d.png" "combined" "$i")

    #   Crop and resize the base
    convert "base.png" -crop "${crop_size}x${crop_size}+${crop_x_offset}+${crop_y_offset}" \
            -resize "${upscaled_size}x${upscaled_size}" \
            "$background_file"

    #   Transparancy for the overlay
    opacity=$(echo "$original_opacity + 0.01 * $i" | bc)

    # Crop and resize the overlay
    convert "overlay.png" -alpha on -channel A -evaluate multiply "$opacity" \
            -crop "${crop_size}x${crop_size}+${crop_x_offset}+${crop_y_offset}" \
            -resize "${upscaled_size}x${upscaled_size}" \
            "$overlay_file"

    #   Combine the two files
    convert "$background_file" "$overlay_file" -composite "$combined_file"
done

#   Create a 25fps video, scaled to 1024px
ffmpeg -framerate 25 -i combined_%03d.png -vf "scale=1024:1024" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p recursive.mp4

Share this post on…

What are your reckons?

All comments are moderated and may not be published immediately. Your email address will not be published.

Allowed HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <p> <pre> <br> <img src="" alt="" title="" srcset="">