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.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c02a19708765f3c6a7222dad5bcd4af8320333aa96461576d4c6eb876854064e
|
|
| MD5 |
404d363414e4e98ea34343c219334d57
|
|
| BLAKE2b-256 |
cc0dfd048ab262f0e28ee1c0c20109dba8e8f00316b3c8d81c33fc8792be81b9
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c536961731e08e2ea76c9ba7dbc2b579e446d417bc1a1ede745f7bd6b7a078b
|
|
| MD5 |
64aab97cfeafa50ed4dd43d1fcdc4ffb
|
|
| BLAKE2b-256 |
ae8a12a5e34f41cea35721a8828d4751d1d1d86a4d7a572c354fdd2cd76b735a
|