Metadata-Version: 2.4
Name: QRtsy
Version: 1.0.0
Summary: Encoder-neutral artistic QR rendering with adaptive image texture, semantic function patterns, presets, and QR-aware compensation
License-Expression: MIT
License-File: LICENSE
Author: Ricardo Newbery
Author-email: ric@digitalmarbles.com
Requires-Python: >=3.11, <4.0
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: segno
Provides-Extra: server
Requires-Dist: attrs (>=26.1.0,<27.0.0)
Requires-Dist: cattrs (>=26.1.0,<27.0.0)
Requires-Dist: litestar[standard] (>=2.24,<3) ; extra == "server"
Requires-Dist: pillow (>=12.3.0,<13.0.0)
Requires-Dist: pyyaml (>=6.0.3,<7.0.0) ; extra == "server"
Requires-Dist: segno (>=1.6.6,<2) ; extra == "segno"
Requires-Dist: segno (>=1.6.6,<2) ; extra == "server"
Project-URL: Homepage, https://qrtsy.app/
Project-URL: Repository, https://codeberg.org/newbery/qrtsy
Description-Content-Type: text/markdown


# The QRtsy Project

<img align="left" width="110" height="110"
 src="https://codeberg.org/newbery/qrtsy/raw/branch/master/src/qrtsy/server/assets/qrtsy-icon.png">

**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 as a reusable, encoder-neutral Python library which includes
integration with the [Segno](https://segno.readthedocs.io/) QR code encoder library,
a command-line interface, and a local browser application for interactively trying
different settings.

> **Artistic QR codes may be less robust than conventional QR codes.** 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:

```console
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](https://python-poetry.org/) or
> [UV](https://docs.astral.sh/uv/). Pick your favorite tooling.

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

```console
pip install qrtsy
```

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

```console
pip install 'qrtsy[segno]'
```

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

```console
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:

```console
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; QR-optimized and image-optimized mask selection;
all-eight-mask comparison; sampling-core sizing; adaptive module tinting;
decorative canvas margins; 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`](https://codeberg.org/newbery/qrtsy/src/branch/master/docs/what.md).


## Quickstart: Python

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

```python
from qrtsy import RenderOptions
from qrtsy.integrations.segno import make_and_save_qr

make_and_save_qr(
    "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:

```python
import segno

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

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

When QRtsy and Segno are installed together, QRtsy also registers a Segno
converter named `to_qrtsy` with the Segno library:

```python
segno_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`](https://codeberg.org/newbery/qrtsy/src/branch/master/examples/matrix.py).


## Quickstart: Command line

The `qrtsy` command uses the optional Segno integration:

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

The CLI defaults to QR version 6 and automatic mask selection. Use `--version auto`
to let Segno choose the smallest version that fits the payload, or `--mask 0` through
`--mask 7` to select a mask explicitly. After rendering, the CLI reports the resolved
version and mask and identifies values that Segno selected automatically.

For all available options:

```console
qrtsy --help
```

## Highlights

QRtsy currently supports:

- full-color, posterized, and monochrome image backgrounds;
- cover, contain, and stretch image fitting to either the QR region or the full
  decorative canvas;
- configurable module size, quiet zone, and central QR sampling-core size;
- optional decorative canvas margins populated with deterministic synthetic
  modules outside the QR quiet zone;
- adaptive module tinting that lets image-exposing QR cores and texture borrow
  local background color while preserving dark/light polarity;
- semantic treatment of data and QR function-pattern modules;
- independently optional shrinking of finder, separator, alignment, and timing
  patterns to expose more of the image;
- constrained Floyd-Steinberg dithering at either raster-pixel or sampling-core
  cell scale, including two-pass forced-core error diffusion;
- configurable dither intensity source, gamma, contrast, brightness, clipping,
  threshold, and traversal direction;
- optional free-pixel texture using ordered dither, seeded noise, randomized
  error diffusion, or directional flow diffusion;
- optional adaptive texture masking to concentrate texture in smooth, neutral,
  near-white image regions;
- QR-aware local luminance compensation;
- QR-penalty-optimized and image-difference-optimized mask selection in the
  browser UI, plus side-by-side comparison of all eight QR masks;
- 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-exposing 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.

Adaptive module tinting can move those required dark/light core colors toward
the local source-image color while keeping dark modules dark and light modules
light. Python callers can also supply a custom `ModuleStyle` to change the core
geometry; the same style is used for image-exposing QR modules and synthetic
canvas modules.

An optional **canvas margin** can add decorative synthetic modules outside the
quiet zone. The source image may remain confined to the QR region or extend
through this outer canvas; either way, the quiet zone itself remains protected.
Synthetic modules can participate in the same styling, tinting, texture, and
monochrome-dither treatments as image-exposing QR modules.

In monochrome mode, dithering can operate either on individual raster pixels or
on square cells the same size as the sampling core. Core-sized dithering can use
Andrew Taylor's two-pass technique: first diffuse the error introduced by forced
QR cores into their neighbors, then Floyd-Steinberg-dither only the remaining free
cells.

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`](https://codeberg.org/newbery/qrtsy/src/branch/master/docs/what.md).


## Presets

QRtsy currently ships with four 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;
- `andrew-taylor` - reproduces Andrew Taylor's 3×3 monochrome dither treatment
  with core-sized two-pass diffusion, green-channel/gamma preprocessing, full
  finder/timing/alignment patterns, and independently shrunk separators.

For example:

```python
from qrtsy import get_builtin_preset
from qrtsy.integrations.segno import make_and_save_qr

options = get_builtin_preset("scan-priority").options
make_and_save_qr("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;
- keep the default four-module quiet zone unless you have a reason to reduce it;
- decorative canvas margins are added outside the quiet zone and do not replace
  it;
- the image-difference score used for image-optimized mask selection measures
  visual similarity to the source image, not scan reliability;
- 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](https://codeberg.org/newbery/qrtsy/src/branch/master/docs/what.md#scannability)
for more detail.


## Development

The project uses Poetry and Poe the Poet:

```console
poetry install --all-extras
poetry run poe check
```

Useful development tasks include:

```console
poetry run poe lint
poetry run poe format
poetry run poe format-check
poetry run poe typecheck
poetry run poe test
poetry run poe coverage
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 currently has the following 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.

See the changelog for compatibility notes and changes to APIs or rendering
behavior between releases.


## Acknowledgements

QRtsy owes its biggest debt to Andrew Taylor's
[Dithered QR Code Generator](https://www.andrewt.net/dithered-qr-codes/) and his
excellent explanation of
[how the technique works](https://www.andrewt.net/dithered-qr-codes/wtf/).
His use of small forced QR samples and error diffusion was the starting point for
much of this experimentation. The built-in `andrew-taylor` preset now reproduces
that core 3×3/two-pass rendering treatment.

QRtsy does not try to reinvent QR encoding itself. The first supplied integration
uses [Segno](https://segno.readthedocs.io/), 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](https://thenounproject.com/browse/icons/term/painter-artist/)
(licensed under
[CC BY 3.0](https://creativecommons.org/licenses/by/3.0/deed.en))
as the background image.


## License

QRtsy is released under the
[MIT License](https://codeberg.org/newbery/qrtsy/src/branch/master/LICENSE).

"QR Code" is a registered trademark of DENSO WAVE INCORPORATED.

