Metadata-Version: 2.4
Name: pyimagebend
Version: 0.1.0
Summary: Composable image manipulation effects, as a Python library and a chainable CLI
Project-URL: Homepage, https://github.com/pomegranar/pyimagebend
Project-URL: Source, https://github.com/pomegranar/pyimagebend
Project-URL: Issues, https://github.com/pomegranar/pyimagebend/issues
Author-email: Anar Nyambayar <anar.nyambayar@duke.edu>
License-Expression: MIT
License-File: LICENSE
Keywords: dither,effects,generative-art,glitch,image,numpy,pillow,pipeline
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Artistic Software
Classifier: Topic :: Multimedia :: Graphics :: Editors
Requires-Python: >=3.10
Requires-Dist: numpy>=1.22
Requires-Dist: pillow>=9.0
Description-Content-Type: text/markdown

# 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](assets/gallery.jpg)

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

## Install

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

```bash
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.

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

## From Python

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

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

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

```python
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
```

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

```toml
[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](https://marimo.io) 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.

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

## Development

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

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

## License

MIT — see [LICENSE](LICENSE). The demo photograph is by Daniel Sessler on
[Unsplash](https://unsplash.com/photos/IyhdFcaRYqE), under the Unsplash
License.
