Six film-grade presets — Moonlight, Hereditary, The Green Knight, The Lighthouse, Eighth Grade, Midsommar — built from split toning, film grain, halation, chromatic aberration and simulated lighting. FastAPI backend, React frontend, one docker-compose up.
One source image through all six presets. Generated by running this repo's own processor — reproduce it with the script in Reproducing the grid.
Upload a photo, pick a film, get it back graded. Each preset is a bundle of parameters driving a five-stage pipeline — not a filter slapped on top.
flowchart LR
A["📤 Upload<br/>JPG · PNG · BMP · TIFF"] --> B["🎨 Colour grading<br/>LUT · split tone<br/>desaturate · lift shadows"]
B --> C["💡 Lighting<br/>directional window light<br/>shadow enhancement"]
C --> D["🎞️ Film texture<br/>grain · halation<br/>chromatic aberration"]
D --> E["🖼️ Composition<br/>aspect ratio · reframe"]
E --> F["📥 Download"]
| Style | Look | Grain | Temperature | Aspect |
|---|---|---|---|---|
| Moonlight | Cyan-magenta contrasts, dreamy coastal atmosphere | 35mm | Cool | Cinematic |
| Hereditary | Warm interiors with unsettling green shadows | 16mm | Warm | Standard |
| The Green Knight | Medieval earthiness, candlelit warmth | 35mm | Warm | Cinematic |
| The Lighthouse | Black and white, crushing claustrophobia | 16mm | Cool | Academy |
| Eighth Grade | Natural digital look, subtle warmth | Digital | Neutral | Standard |
| Midsommar | Bright daylight horror, saturated pastels | 35mm | Warm | Standard |
Every parameter is overridable per request — desaturation, shadow lift, grain intensity, halation strength, chromatic aberration, lighting direction and intensity. Presets live in backend/app/processors/style_presets.py.
git clone https://github.com/moeezalam/A24Colour.git
cd A24Colour
docker-compose up --buildFrontend on http://localhost:3000, API on http://localhost:8000, interactive API docs on http://localhost:8000/docs.
cd backend
python -m venv venv && venv\Scripts\activate # macOS/Linux: source venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000cd frontend
npm install
npm startOr run both halves in one go with setup.sh (macOS/Linux) or setup.bat (Windows) — they check for Python and Node, create the venv, and install both dependency sets.
Base path /api/v1.
| Method | Route | Purpose |
|---|---|---|
GET |
/styles/ |
List styles with their full preset parameters |
GET |
/styles/{style_name}/ |
One preset's details |
POST |
/process-image/ |
Multipart upload → graded image bytes back |
POST |
/process-image-async/ |
Queue a job, poll for the result |
GET |
/job-status/{job_id} |
Async job state |
GET |
/health/ |
Health check |
curl -X POST http://localhost:8000/api/v1/process-image/ \
-F "image=@photo.jpg" \
-F "style=hereditary" \
-F 'custom_settings={"grain_intensity":0.6,"desaturation":0.4}' \
--output graded.jpgLimits, from backend/app/config.py: 10 MB max upload, images over 2048 px are downscaled first, JPEG output at quality 95.
The figure at the top is real output, not a mockup. It was produced by importing EnhancedA24StyleProcessor directly and running one synthetic scene through all six presets:
import cv2, sys
sys.path.insert(0, "backend")
from app.processors.enhanced_a24_processor import EnhancedA24StyleProcessor
proc = EnhancedA24StyleProcessor("backend/assets")
src = cv2.imread("photo.jpg") # BGR in
out = proc.process_image_array(src, "hereditary") # RGB out
cv2.imwrite("out.jpg", cv2.cvtColor(out, cv2.COLOR_RGB2BGR))Note the colour-space convention: the processor takes BGR (what cv2.imread gives you) and returns RGB. The API handles the flip back in prepare_image_response; if you call the processor directly, you must.
Measured on the sample scene above, so you know what you're getting:
- The Lighthouse preset crushes the image. Mean brightness drops from 84 to 12 out of 255 — an 86% loss. Saturation falls to 7, so the black-and-white intent lands, but almost all detail is gone. The exposure compensation for that preset needs work.
- Aspect-ratio control doesn't run on the API path. The
aspect_ratiofield is defined on every preset andAspectRatioProcessoris implemented, butEnhancedA24StyleProcessor— the one the endpoints actually use — never calls it. Only the olderA24StyleProcessorapplies composition. - The LUTs are procedural. They approximate each film's palette; they are not the actual LUTs used in production.
- Redis and Celery are declared but barely wired. Async jobs currently live in an in-process dict, so they don't survive a restart or scale past one worker.
- CPU only. 2–5 seconds per image is typical.
Good entry points, roughly in order of impact:
- Fix the Lighthouse exposure — the most visible defect in the whole project
- Call the composition stage from the enhanced processor so aspect ratios apply
- Real LUT support — load
.cubefiles instead of generating curves procedurally - A before/after slider in the frontend instead of two static panels
- Batch mode — a folder in, a graded folder out, no UI
- More presets — Ex Machina, Under the Skin, Everything Everywhere All at Once
- Finish the Celery path so async jobs survive a restart
MIT — see LICENSE.
Not affiliated with, endorsed by, or connected to A24 Films LLC. "A24" and the film titles referenced are trademarks of their respective owners; they're used here only to describe the visual style each preset approximates.
If your holiday photos now look like a horror film, star the repo.
