Skip to main content

pyimagebend

Composable image manipulation, as a Python library and a chainable CLI.

An image is a float array in [0, 1]. An effect is a function that takes one and returns one. Every effect in here is a building block; the interesting results come from stacking them.

three panels: the original photo, chunkswap texture, and a dithered duotone

pyimagebend photo.jpg contrast:1.1 chunkswap:strength=0.6,grain=3,radius=30 --seed 7

Install

pip install pyimagebend            # or: uv add pyimagebend

NumPy and Pillow are the only dependencies.

The command line

Each argument after the input is one step, written name:key=value,key=value. The first value may be positional, and a bare name runs on defaults:

pyimagebend photo.jpg invert                          # writes photo_bent.png
pyimagebend photo.jpg contrast:1.4 dither:levels=3 -o poster.png
pyimagebend photo.jpg blur:6 blend:other=photo.jpg,mode=difference,amount=1
pyimagebend photo.jpg noise shift chunkswap --seed 12 # reproducible
pyimagebend photo.jpg pixelsort:0.6 -n               # print the chain, run nothing

--seed N fills in the seed of every step that takes one and has none set, so a whole chain replays exactly. Parameters annotated as images — guide, weights, mask, other — take a file path.

pyimagebend --list              # every effect, by category
pyimagebend --explain chunkswap # its parameters and what they do

From Python

import pyimagebend as bend

image = bend.load("photo.jpg")
result = bend.bend(image, "contrast:1.2", "chunkswap:strength=0.4", seed=7)
bend.save(result, "photo_bent.png")

Effects are ordinary functions, so reach for them directly when that reads better:

from pyimagebend import blur, chunkswap, gradient

weights = gradient(blur(image, radius=6))          # let big shapes drive it
textured = chunkswap(image, strength=0.8, grain=4, weights=weights, seed=1)

A Pipeline is a reusable chain. It is immutable, and composes with |:

from pyimagebend import Pipeline

grit = Pipeline("noise:0.04", "chunkswap:strength=0.3")
look = Pipeline("contrast:1.2") | grit | "rgbshift:amount=3"

for path in paths:
    bend.save(look.with_seed(3)(bend.load(path)), path.with_suffix(".bent.png"))

Writing an effect

Any function taking an image and returning one is already usable as a step. Decorating it publishes it — to the pipeline syntax, --list, --explain, and the CLI — with its parameters read off the signature:

import numpy as np
import pyimagebend as bend

@bend.effect
def bloom(image, threshold: float = 0.7, radius: int = 12, amount: float = 0.6):
    """Let the highlights spill over their edges."""
    highlights = np.where(image > threshold, image, 0.0)
    return image + bend.blur(highlights, radius=radius) * amount
pyimagebend photo.jpg bloom:amount=1.2   # once the module is imported

Annotations decide how command-line values are read: int, float, bool, str, and bend.Image for "load this path as an image". Another package can ship effects by pointing an entry point at the module that registers them:

[project.entry-points."pyimagebend.effects"]
myplugin = "myplugin.effects"

The representation

float32, shaped (height, width, channels) with 1 or 3 channels, values in [0, 1]. That is the whole contract — no wrapper class to learn, and NumPy does the work. Values may leave [0, 1] mid-chain; save clips at the end. Alpha is dropped on load, so effects never have to think about it.

load, save, to_pil, as_image, luminance, and new cover getting in and out. Anything array-like works as input — PIL images and uint8 arrays are converted on the way in.

The effects

category effects
color brightness contrast duotone gamma grayscale hue invert posterize saturation solarize threshold tint
filter blur edges emboss gradient sharpen
dither dither diffuse
geometry flip mirror resize rotate wave
glitch chunkswap noise pixelate pixelsort rgbshift scanlines shift
compose blend mask

chunkswap is the one this package grew out of: it divides the image into a grid of small squares and exchanges pairs of them, more often where the image changes fastest, so edges dissolve into grain while flat areas stay clean. Pixels are only moved, never blended — the output holds exactly the same values as the input.

Demo

demo.py is a marimo notebook: every effect with live controls built from its own signature, a pipeline you can type into, and an effect defined in the notebook itself. marimo is declared in the notebook's own script metadata, so it never becomes a dependency of this package.

uvx marimo edit --sandbox demo.py   # from the repository root

Development

uv sync --group dev
uv run pytest

To cut a release, bump __version__ in src/pyimagebend/__init__.py — the build reads the version from there — then:

uv build
uvx twine check dist/*
uvx twine upload dist/*

License

MIT — see LICENSE. The demo photograph is by Daniel Sessler on Unsplash, under the Unsplash License.

Download files

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

Source Distribution

pyimagebend-0.1.0.tar.gz (29.0 kB view details)

Uploaded Source

Built Distribution

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

pyimagebend-0.1.0-py3-none-any.whl (28.6 kB view details)

Uploaded Python 3

File details

Details for the file pyimagebend-0.1.0.tar.gz.

File metadata

  • Download URL: pyimagebend-0.1.0.tar.gz
  • Upload date:
  • Size: 29.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pyimagebend-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c02a19708765f3c6a7222dad5bcd4af8320333aa96461576d4c6eb876854064e
MD5 404d363414e4e98ea34343c219334d57
BLAKE2b-256 cc0dfd048ab262f0e28ee1c0c20109dba8e8f00316b3c8d81c33fc8792be81b9

See more details on using hashes here.

File details

Details for the file pyimagebend-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pyimagebend-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 28.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Fedora Linux","version":"44","id":"","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pyimagebend-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4c536961731e08e2ea76c9ba7dbc2b579e446d417bc1a1ede745f7bd6b7a078b
MD5 64aab97cfeafa50ed4dd43d1fcdc4ffb
BLAKE2b-256 ae8a12a5e34f41cea35721a8828d4751d1d1d86a4d7a572c354fdd2cd76b735a

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