An open API service indexing awesome lists of open source software.

https://github.com/smyrgeorge/pyeffects

A small desktop app (and CLI) for applying creative effects to your photos — with a built-in video renderer that animates an effect over time.
https://github.com/smyrgeorge/pyeffects

animation chromatic-aberration creative-coding ffmpeg glitch glitch-art glitch-effect image-effects image-processing numpy photo-editor python video-rendering

Last synced: 5 days ago
JSON representation

A small desktop app (and CLI) for applying creative effects to your photos — with a built-in video renderer that animates an effect over time.

Awesome Lists containing this project

README

          

# PyEffects

A curated collection of creative photo effects — preview them live, run them from the CLI, or animate them into video.

## About

![PyEffects desktop app](docs/img/screenshot.png)

PyEffects is a lightweight Python toolkit for applying image effects. It ships with a reusable GUI that shows a
before/after slider and builds its settings controls **dynamically** from whichever effect is selected. Adding a new
effect only means implementing a small interface and registering it — the UI and command-line parser adapt
automatically, and `python src/utils/gen_previews.py` renders its before/after preview for this README.

It also **renders videos**: pick an effect, choose a *from → to* range for any of its variables (or several at once),
and PyEffects interpolates those values across the frames and encodes a smooth MP4 — animating, for example, the
*Height* effect ramping from 0 to 100% strength. Frames render in parallel across your CPU cores and ffmpeg smooths
the motion.

Five effects ship in the box:

- **Glitch** — RGB shift, slice displacement, noise, and scanlines.
- **Glitch Height** — a radial 3D-extrusion effect: the image bursts outward from a focal point into strong,
pointed, feathery spikes that grow toward the edges, with an optional circular frame.
- **Night** — a neon "night" stylisation: the background is crushed to black while the in-focus subject glows in a
cool teal/amber palette, with posterised banding, a vignette, and optional chunky shard shredding.
- **Pixelate** — a blocky mosaic / pixel-art look, with optional retro color banding and soft blocks.
- **Pop Art** — a Warhol-style silkscreen: a 2×2 grid where each quadrant is recoloured through a different duotone
palette, finished with a halftone dot screen and paper texture.

> [!NOTE]
> More effects are on the way — this is an ongoing project, and the collection keeps growing.

### Examples

Each effect shown in the app's before/after view.

**Glitch** — RGB shift, slice displacement, and scanlines:

![Glitch effect before/after](docs/img/glitch.png)

**Glitch Height** — radial 3D extrusion bursting from a focal point:

![Glitch Height effect before/after](docs/img/glitch-height.png)

**Night** — a neon "night" stylisation with a cool teal/amber palette:

![Night effect before/after](docs/img/night.png)

**Pixelate** — a blocky mosaic / pixel-art look:

![Pixelate effect before/after](docs/img/pixelate.png)

**Pop Art** — a Warhol-style four-up silkscreen with a halftone dot screen:

![Pop Art effect before/after](docs/img/pop-art.png)

### Video

Any effect can be animated over time. You choose a *from → to* range for one or more of its parameters; PyEffects then
renders a frame at each step along that range (in parallel across your CPU cores), and **ffmpeg** interpolates the
in-between frames and encodes them into a smooth MP4. Below, the *Glitch Height* effect ramps its **strength** from 0 to
100%:


Glitch Height video — strength ramped 0 → 100%

> See [**Video (animated parameters)**](#video-animated-parameters) below for the full list of rendering options.

## Setup

### Initialize Python Environment

Create and activate a virtual environment:

```bash
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
```

### Install Requirements

```bash
pip install -r requirements.txt
```

> [!NOTE]
> The GUI uses [PySide6](https://doc.qt.io/qtforpython/) (Qt). On macOS the bundled Homebrew Python may not include
> Tkinter, which is why PySide6 is used instead — it installs as a self-contained wheel.

## Usage

### Desktop app (GUI)

```bash
python src/ui/app.py
```

1. Click **Open…** and choose an image.
2. Drag the **vertical divider** over the image to wipe between the original (left, *BEFORE*) and the processed result
(right, *AFTER*).
3. Adjust the effect settings on the right — the preview updates live.
4. Below the effect settings, click **Save image…** to export the result at full resolution.
5. In the **Render video** panel below it, set the duration, frame rate, frame count, resolution (native input size by
default — capped to 4K so the clip plays back smoothly — or untick to set a smaller cap), and smoothing, and use the
**Transitions** rows to set a *from → to* range for any effect variable (by default the strength ramps 0 → 100%).
Each panel has a **Reset**. Click **Render video…** to export the animation: a progress bar tracks the frames, then a
live **ffmpeg log** shows the encode, with a **Cancel** button. Frames render in parallel across your CPU cores.

The preview is computed on a downscaled copy for responsiveness; exporting always re-renders at full resolution.

### Command line

Each effect is also runnable headless. Its options are generated from the same settings the GUI uses:

```bash
python src/effects/glitch.py path/to/image.jpg
python src/effects/glitch.py workspace/photo.jpg --rgb-shift 0.6 --slices 30 --noise 0.1 --seed 7
```

By default the result is written next to the original as `_glitch.` (use `-o` to override). Run with
`--help` to see every option.

#### Glitch settings

| Setting | CLI flag | Description | Default |
|-------------|-----------------|-----------------------------------------------------------|---------|
| RGB shift | `--rgb-shift` | Chromatic aberration: how far the red/blue channels slide | `0.5` |
| Slices | `--slices` | Number of displaced horizontal bands | `20` |
| Slice shift | `--slice-shift` | Max horizontal displacement per band (fraction of width) | `0.10` |
| Noise | `--noise` | Amount of random color speckle | `0.0` |
| Scanlines | `--scanlines` | Strength of the darkened CRT-style scanlines | `0.15` |
| Seed | `--seed` | Random seed (same seed → same glitch) | `42` |

#### Glitch Height settings

```bash
python src/effects/glitch_height.py path/to/image.jpg
python src/effects/glitch_height.py workspace/photo.jpg --strength 0.6 --center-y 0.55
```

| Setting | CLI flag | Description | Default |
|----------------|---------------|---------------------------------------------------|---------|
| Strength | `--strength` | Length of the radial extrusion spikes | `0.25` |
| Center X | `--center-x` | Horizontal focal point (0 = left, 1 = right) | `0.5` |
| Center Y | `--center-y` | Vertical focal point (0 = top, 1 = bottom) | `0.5` |
| Detail | `--detail` | Number of extrusion steps (higher = smoother) | `100` |
| Circular frame | `--circle` | Mask the result to a circle, fading corners black | off |
| Scanlines | `--scanlines` | Strength of the darkened CRT-style scanlines | `0.0` |

#### Night settings

```bash
python src/effects/night.py path/to/image.jpg
python src/effects/night.py workspace/photo.jpg --streaks 0.85 --levels 6 --vignette 0.8
```

The background is crushed to black while the in-focus subject glows in a cool teal/amber palette — the colour follows
the subject's own hue, so warm features (eyes, nose, ears) turn amber while neutral fur and whiskers read cool. It is
finished with posterised banding, a vignette, and a fine grain. Turn up `--streaks` for a chunky, shredded "shard" look.

| Setting | CLI flag | Description | Default |
|--------------|--------------|-----------------------------------------------------------------|---------|
| Strength | `--strength` | Blend between the original and the full night stylisation | `1.0` |
| Detail glow | `--detail` | How strongly textured/in-focus areas light up vs. fade to black | `0.5` |
| Streaks | `--streaks` | Boldness of the hard-edged streak/block shredding (0 = smooth) | `0.0` |
| Color levels | `--levels` | Posterise each channel to this many levels (banding) | `8` |
| Grain | `--grain` | Amount of fine film grain | `0.035` |
| Vignette | `--vignette` | Darkening of the corners toward black | `0.7` |

#### Pixelate settings

```bash
python src/effects/pixelate.py path/to/image.jpg
python src/effects/pixelate.py workspace/photo.jpg --pixel-size 0.06 --levels 8 --smooth
```

| Setting | CLI flag | Description | Default |
|---------------|----------------|------------------------------------------------------------------|---------|
| Pixel size | `--pixel-size` | Block size as a fraction of the longest edge (bigger = chunkier) | `0.03` |
| Color levels | `--levels` | Quantise each channel to N levels (256 = full color) | `256` |
| Smooth blocks | `--smooth` | Bilinear upscaling for soft blocks instead of hard pixels | off |

#### Pop Art settings

```bash
python src/effects/popart.py path/to/image.jpg
python src/effects/popart.py workspace/photo.jpg --contrast 0.7 --dots 0.6 --dot-size 0.012
```

The image is laid out as a 2×2 grid and each quadrant is recoloured through a different two/three-tone gradient map
(maroon/olive, magenta/periwinkle, blue/yellow, green/pink), then overlaid with a halftone dot screen and a subtle
paper texture — the classic Warhol silkscreen.

| Setting | CLI flag | Description | Default |
|---------------|--------------|---------------------------------------------------------|---------|
| Strength | `--strength` | Blend between the original and the full pop-art result | `1.0` |
| Contrast | `--contrast` | Tonal punch before the colour mapping (higher = bolder) | `0.5` |
| Halftone | `--dots` | Strength of the halftone dot screen | `0.5` |
| Dot size | `--dot-size` | Halftone cell size as a fraction of the quadrant width | `0.01` |
| Paper texture | `--texture` | Amount of canvas/paper grain over the print | `0.3` |

### Video (animated parameters)

Render an MP4 that animates an effect over time. Give any of its parameters a *from → to* range and PyEffects
interpolates them across the frames while holding every other parameter constant — animate a single variable or
several at once. By default it ramps the **strength** from 0 to 100%:

```bash
python src/render/video.py workspace/photo.jpg # height effect, 10s @ 30fps
python src/render/video.py workspace/photo.jpg -e height -d 15 --frames 200 --smooth motion
python src/render/video.py workspace/photo.jpg --transition strength:0:1 --transition center_y:0.4:0.6
```

| Option | CLI flag | Description | Default |
|------------|-----------------|----------------------------------------------------|----------------|
| Effect | `-e/--effect` | Effect id to animate (`glitch`, `height`, …) | `height` |
| Output | `-o/--output` | Output `.mp4` path | `.mp4` |
| Parameter | `-p/--param` | Parameter to animate (when `--transition` not set) | `strength` |
| Transition | `--transition` | `NAME:FROM:TO`, repeatable — animate several vars | strength 0→max |
| Duration | `-d/--duration` | Video length in seconds | `10` |
| Frame rate | `--fps` | Output frames per second | `30` |
| Frames | `--frames` | Distinct frames rendered across the animation | `100` |
| Max size | `--max-size` | Longest edge of the video in px (capped at 3840) | `1024` |
| Smoothing | `--smooth` | In-between frames: `blend`, `motion`, or `none` | `blend` |
| Workers | `--workers` | Frames rendered in parallel | CPU count |

The frames are rendered into a folder named `_` next to the image, then encoded to `.mp4`.
Because each frame of an effect like *Height* is expensive, only `--frames` distinct frames are rendered (one per
step) — in parallel across `--workers` threads. **ffmpeg** then fills the gap up to `--fps` either by interpolating
smooth in-between frames (`--smooth blend`/`motion`) or by duplicating frames (`--smooth none`), so the video runs the
full `--duration` and stays smooth. Raise `--frames` for crisper motion (slower), lower it for a quicker render.
Encoding uses a bundled ffmpeg (`imageio-ffmpeg`) or a system `ffmpeg` if present.

## License

[MIT](LICENSE)