Skip to main content

The QRtsy Project

QRtsy (pronounced “cue-artsy”) is an experimental Python toolkit for making pretty QR codes.

QRtsy combines a normal QR matrix with a source image, preserves the pixels that a scanner most needs to sample, and leaves the remaining pixels available for the image and optional texture effects. The result is a playground for exploring the tradeoff between visual appearance and scan reliability.

QRtsy is designed primarily as a reusable, encoder-neutral Python library. It also includes an optional Segno integration, a command-line interface, and a local browser application for interactively trying different settings.

QRtsy is alpha software. Artistic QR codes deliberately spend some of the robustness of a conventional QR symbol. Always test generated codes with the actual phones, cameras, print sizes, display sizes, distances, angles, and lighting conditions in which you expect them to be used.

Installation

QRtsy requires Python 3.11 or newer.

It is a good idea to install QRtsy into a dedicated Python virtual environment rather than into your system Python. From the directory where you want to work:

python -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip

The rest of the installation instructions below use the standard Python pip command installed within this activated virtual environment. If it's not activated, you'll probably install the library somewhere unexpected and that will just be confusing and maybe even break things. Remember to activate.

Instead of using pip directly, most Python code jockeys these days instead tend to use various third-party project tools to manage project virtual environments like Poetry or UV. Pick your favorite tooling.

To install just the core QRtsy library with the encoder-neutral Pillow renderer:

pip install qrtsy

To install QRtsy with Segno support for the convenience API, command-line interface, and Segno converter plugin:

pip install 'qrtsy[segno]'

Or to install QRtsy with the local browser application, which also includes Segno:

pip install 'qrtsy[server]'

Quickstart: Browser UI

After QRtsy has been installed, the easiest way to explore its features is with the local web application:

qrtsy-server

Then open http://127.0.0.1:8000/ in your web browser.

Upload an image, enter the text or URL to encode, and experiment with the controls while QRtsy updates the preview.

The UI includes full-color, posterized, and monochrome image modes; QR version, mask, and error-correction controls; sampling-core sizing; function-pattern shrinking; several free-pixel texture algorithms; optional luminance compensation; built-in and custom presets; per-setting undo; and PNG download.

The How does this work? link opens the built-in manual at /what. The source for that guide is also readable directly in the repository at docs/what.md.

The server binds to 127.0.0.1 by default. Uploaded and generated images are processed in memory rather than written to the server filesystem.

Quickstart: Python

For most applications, the Segno convenience API is the simplest place to start:

from qrtsy import RenderOptions
from qrtsy.integrations.segno import save

save(
    "https://example.com/",
    "portrait.jpg",
    "qrcode.png",
    RenderOptions(
        module_size=8,
        core_size=3,
        border=4,
    ),
    segno_options={"error": "H", "version": 6},
)

Segno is optional because QRtsy itself does not encode payloads. The core renderer consumes an encoder-neutral semantic ModuleMatrix, so other QR encoders can be adapted without coupling the renderer to Segno.

If you already have a Segno QR code, QRtsy can render that directly:

import segno

from qrtsy import RenderOptions
from qrtsy.integrations.segno import save_qr

qr = segno.make_qr("https://example.com/", error="H")
save_qr(
    qr,
    "portrait.jpg",
    "qrcode.png",
    RenderOptions(module_size=8, core_size=3, border=4),
)

When QRtsy and Segno are installed together, QRtsy also registers a Segno converter named qrtsy:

qr.to_qrtsy(
    "qrcode.png",
    image="portrait.jpg",
    module_size=8,
    core_size=3,
    border=4,
)

For a direct example of the encoder-neutral ModuleMatrix API, see examples/matrix.py.

Quickstart: Command line

The qrtsy command uses the optional Segno integration:

qrtsy \
  'https://example.com/' \
  portrait.jpg \
  qrcode.png \
  --error H \
  --version 6 \
  --module-size 8 \
  --core-size 3 \
  --border 4

The CLI defaults to QR version 6. Use --version auto to let Segno choose the smallest version that fits the payload.

For all available options:

qrtsy --help

Highlights

QRtsy currently supports:

  • full-color, posterized, and monochrome image backgrounds;
  • cover, contain, and stretch image fitting;
  • configurable module size, quiet zone, and central QR sampling-core size;
  • semantic treatment of data and QR function-pattern modules;
  • optional shrinking of finder/separator, alignment, and timing patterns to expose more of the image;
  • constrained Floyd–Steinberg dithering for monochrome rendering;
  • optional free-pixel texture using ordered dither, seeded noise, randomized error diffusion, or directional flow diffusion;
  • adaptive texture fade to keep texture concentrated in visually suitable image regions;
  • QR-aware local luminance compensation;
  • immutable built-in presets plus browser-managed custom presets;
  • JSON preset import/export;
  • an optional Segno adapter and Segno converter plugin;
  • a CLI and a Litestar/Uvicorn local experimentation server.

Most of these controls exist because there is no single “best” artistic QR code. A setting that looks great for one photograph, payload, output size, and scanner may perform badly for another.

How it works

QRtsy separates QR encoding from QR rendering.

An encoder adapter converts a QR symbol into a semantic matrix whose modules are classified as data, finder, separator, timing, alignment, format, version, fixed dark, and so on. This is more information than a simple dark/light matrix and lets the renderer treat different parts of the QR symbol differently.

For image-bearing modules, QRtsy can replace only a centered sampling core with the required black or white QR value instead of painting the entire module. The surrounding pixels remain available to show the source image. QR function patterns can remain fully rendered, or selected patterns can be shrunk explicitly for more aggressive experiments.

Optional texture passes can make the regular sampling-core grid less visually obvious. Optional local compensation can then nudge free pixels within each module to recover some of the luminance changed by forced QR pixels and texture.

For a walkthrough of the rendering pipeline, every UI setting, QR anatomy, texture modes, compensation, and scannability tradeoffs, see docs/what.md.

Presets

QRtsy currently ships with three built-in renderer presets:

  • default — the normal RenderOptions defaults;
  • scan-priority — a more conservative starting point with a four-module quiet zone, large sampling cores, protected timing patterns, and no optional texture or compensation;
  • small-core-textured — a more aggressive experimental style using one-pixel cores and randomized error-diffusion texture.

For example:

from qrtsy import get_builtin_preset
from qrtsy.integrations.segno import save

options = get_builtin_preset("scan-priority").options
save("https://example.com/", "portrait.jpg", "qrcode.png", options)

Built-in presets are immutable package data. Custom presets are application-owned state: the browser UI stores them in that browser's local storage and validates or migrates them through QRtsy before saving them. They can also be exported to or imported from JSON.

Scannability

QR error correction helps recover damaged codewords, but it does not make arbitrary artistic changes safe. QRtsy intentionally exposes controls that can make a symbol less robust.

A few practical rules of thumb:

  • larger sampling cores are generally more scannable than smaller ones;
  • leaving finder, alignment, and timing structures intact is generally more scannable than shrinking them;
  • try at least a four-module quiet zone since that is the QR standard;
  • texture and compensation are aesthetic tools; they can't increase scanning robustness;
  • test the final physical or displayed result, not just a large desktop preview.

The built-in scan-priority preset is a useful conservative starting point, but it is still not a guarantee. See the scannability discussion for more detail.

Development

The project uses Poetry and Poe the Poet:

poetry install --all-extras
poetry run poe check

Useful development tasks include:

poetry run poe lint
poetry run poe format
poetry run poe typecheck
poetry run poe test
poetry run poe coverage
poetry run poe docs
poetry run poe build
poetry run poe server

poe check runs project validation, linting, format checking, type checking, tests, and a documentation-generation consistency check.

The server manual is generated from docs/what.md by docs/build_docs.py.

Current limitations

QRtsy is still deliberately experimental. Among the current limitations:

  • output is raster/Pillow only;
  • the QRtsy-supplied Segno adapter does not support Micro QR;
  • local compensation can be slow;
  • the renderer does not calculate or enforce a Reed–Solomon damage budget;
  • Segno-specific module classification remains isolated behind the adapter because Segno documents that interface as experimental.

Expect APIs and rendering behavior to change while the project is in alpha.

Acknowledgements

QRtsy owes its biggest debt to Andrew Taylor's Dithered QR Code Generator and his excellent explanation of how the technique works. His use of small forced QR samples and error diffusion was the starting point for much of this experimentation.

QRtsy does not try to reinvent QR encoding itself. The first supplied integration uses Segno, whose encoder, semantic module output, and plugin architecture make it a particularly useful match for the project.

The built-in manual contains additional links to QR standards, tutorials, research papers, and other aesthetic QR-code projects.

The QRtsy project icon was generated by the QRtsy server app using Painter Artist by Gan Khoon Lay from Noun Project (licensed under CC BY 3.0) as the background image.

License

QRtsy is released under the MIT License.

“QR Code” is a registered trademark of DENSO WAVE INCORPORATED.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qrtsy-0.17.0.tar.gz (86.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qrtsy-0.17.0-py3-none-any.whl (89.6 kB view details)

Uploaded Python 3

File details

Details for the file qrtsy-0.17.0.tar.gz.

File metadata

  • Download URL: qrtsy-0.17.0.tar.gz
  • Upload date:
  • Size: 86.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.1 CPython/3.14.5 Darwin/24.6.0

File hashes

Hashes for qrtsy-0.17.0.tar.gz
Algorithm Hash digest
SHA256 e044397fe419f2f643c0fe68550ffbd9fcddbf381cb6f859fb53a3a2d4c0f3e0
MD5 1fcbe5f7b6b26ef3a91eceb38ca0ba87
BLAKE2b-256 859db5f0a9c3247f8ff64b4d4d3f902100f3a2bfd0a86709553e3984be19bb75

See more details on using hashes here.

File details

Details for the file qrtsy-0.17.0-py3-none-any.whl.

File metadata

  • Download URL: qrtsy-0.17.0-py3-none-any.whl
  • Upload date:
  • Size: 89.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.4.1 CPython/3.14.5 Darwin/24.6.0

File hashes

Hashes for qrtsy-0.17.0-py3-none-any.whl
Algorithm Hash digest
SHA256 98078f0d7490ac34f5a2635d68aea74b0869718595b8b8f97da6f6d30e2d0829
MD5 785d7f665bc9726d9e0b605e0766859a
BLAKE2b-256 1a2f052cd31cb6a7d60ce6c36e355ff01313e172525c5b2d1c1a37ce4a6c2ae4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page