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.
- Host: GitHub
- URL: https://github.com/smyrgeorge/pyeffects
- Owner: smyrgeorge
- License: mit
- Created: 2026-06-28T17:47:21.000Z (21 days ago)
- Default Branch: main
- Last Pushed: 2026-06-28T19:08:33.000Z (21 days ago)
- Last Synced: 2026-06-28T21:07:09.674Z (21 days ago)
- Topics: animation, chromatic-aberration, creative-coding, ffmpeg, glitch, glitch-art, glitch-effect, image-effects, image-processing, numpy, photo-editor, python, video-rendering
- Language: Python
- Homepage:
- Size: 2.66 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
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 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 Height** — radial 3D extrusion bursting from a focal point:

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

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

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

### 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%:
> 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)