v0.4.0

Examples

24 looks built from layeris operations, each applied to a real photo. Drag the divider to compare. Every snippet below is the exact code that rendered its image, and it runs as-is: it assumes from layeris import LayerImage, import numpy as np and the sample photos in a photos/ folder. Finish with image.save("result.jpg").

Filter by method

Everyday Fixes

Small corrections that make a photo look the way it felt.

Fishing harbor on Korea's east coast on a hazy day
Before After

Rescue a Flat Photo

A hazy, overcast shot has no true blacks or whites. Curve points computed with NumPy emulate a Levels adjustment that stretches the tones to the full range; saturation and a warm overlay bring the colors back.

Uses
image = LayerImage.from_file("photos/fishing-harbor.jpg")
# Levels: map input 0.16 to black and 0.82 to white
x = np.linspace(0, 1, 26)
levels = np.clip((x - 0.16) / 0.66, 0, 1)

(image.curve("rgb", levels.tolist())
      .saturation(0.35)
      .overlay("#ffd8a8", opacity=0.25))
Overhead view of bibimbap in a stone bowl with banchan side dishes
Before After

Make Food Pop

Food photos benefit from rich color and a touch of warmth. A gentle S-curve adds contrast without crushing the shadows.

Uses
image = LayerImage.from_file("photos/bibimbap.jpg")
(image.saturation(0.4)
      .curve("rgb", [0, 0.2, 0.52, 0.8, 1])
      .overlay("#ffc670", opacity=0.3))
Older man in a white hanbok and black gat hat by a paper window
Before After

Warm Up Skin Tones

Per-channel curves act as a white-balance slider: raise the red midtones and lower the blue ones to warm up a cool, grey-lit portrait.

Uses
image = LayerImage.from_file("photos/hanbok-portrait.jpg")
(image.curve("r", [0, 0.58, 1])
      .curve("b", [0, 0.43, 0.95])
      .saturation(0.1))

Color Grading

Shift the mood of a scene with curves and color layers.

Jeju beach with black volcanic rocks in late afternoon light
Before After

Teal & Orange

The blockbuster grade: an S-curve on red and an inverted S-curve on blue push the shadows toward teal and the highlights toward orange.

Uses
image = LayerImage.from_file("photos/jeju-coast.jpg")
(image.curve("r", [0, 0.1, 0.5, 0.9, 1])
      .curve("b", [0.18, 0.38, 0.5, 0.6, 0.8])
      .contrast(1.1)
      .saturation(0.2))
Seoul alley at night with neon and lanterns reflected on wet pavement
Before After

Neon Nights

Lift the blue in the shadows, wash the scene with magenta using Soft Light, and turn up saturation for a cyberpunk feel.

Uses
image = LayerImage.from_file("photos/seoul-night.jpg")
(image.curve("b", [0.12, 0.45, 0.75, 1])
      .soft_light("#ff2fb3", opacity=0.6)
      .saturation(0.3)
      .contrast(1.1))
Hanok house with a snow-covered tiled roof
Before After

Winter Blues

A blue Soft Light layer and a curve that lifts the blue shadows cool the whole scene; a small lightness lift keeps it airy.

Uses
image = LayerImage.from_file("photos/snowy-hanok.jpg")
(image.soft_light("#1f5fd1", opacity=0.8)
      .curve("b", [0.1, 0.55, 1])
      .lightness(0.05))
Misty bamboo forest with sun rays
Before After

Moody Forest

Muted greens and a deep teal cast: desaturate, crush the shadows with a curve, then multiply with a dark teal at low opacity.

Uses
image = LayerImage.from_file("photos/bamboo-forest.jpg")
(image.saturation(-0.35)
      .curve("rgb", [0.03, 0.18, 0.45, 0.78, 0.97])
      .multiply("#2f6f6a", opacity=0.35)
      .contrast(1.15))
Gyeonghoeru pavilion reflected in its pond at sunrise
Before After

Golden Glow

Exaggerate the sunrise: Screen a dark orange to lift and tint the shadows, Overlay a brighter one to warm the midtones, then restore contrast with an S-curve.

Uses
image = LayerImage.from_file("photos/palace-pond.jpg")
(image.screen("#3a1800", opacity=0.7)
      .overlay("#ff9d3b", opacity=0.25)
      .curve("rgb", [0, 0.18, 0.5, 0.82, 1]))

Film & Vintage

Lifted blacks, warm casts and soft contrast for an analog look.

Celadon tea set on a low table in a hanok room
Before After

Faded Film

Lifted blacks and softened whites give the matte look of expired film; a cream Soft Light layer warms the midtones.

Uses
image = LayerImage.from_file("photos/hanok-tea.jpg")
(image.curve("rgb", [0.15, 0.32, 0.55, 0.76, 0.9])
      .saturation(-0.25)
      .soft_light("#f0cf9c", opacity=0.7))
Pastel houses on a hillside in Gamcheon Culture Village, Busan
Before After

Cross-Processed

Developing slide film in negative chemistry gave punchy, yellow-green highlights and blue shadows. Opposing curves on each channel recreate it.

Uses
image = LayerImage.from_file("photos/gamcheon-village.jpg")
(image.curve("r", [0, 0.15, 0.55, 0.9, 1])
      .curve("g", [0, 0.2, 0.55, 0.85, 1])
      .curve("b", [0.22, 0.35, 0.48, 0.6, 0.72]))
Jeju beach with black volcanic rocks in late afternoon light
Before After

Instant Film

Lighten with a deep navy so no shadow goes below it, warm the image with Overlay and pull back saturation for a Polaroid feel.

Uses
image = LayerImage.from_file("photos/jeju-coast.jpg")
(image.lighten("#2b2d4a")
      .overlay("#ffb870", opacity=0.5)
      .saturation(-0.25)
      .curve("rgb", [0, 0.27, 0.55, 0.8, 0.95]))
Older man in a white hanbok and black gat hat by a paper window
Before After

Sepia Print

Convert to grayscale, then use per-channel curves as a gradient map: black becomes dark brown and white becomes warm cream.

Uses
image = LayerImage.from_file("photos/hanbok-portrait.jpg")
(image.grayscale()
      .curve("r", [0.16, 0.62, 1.0])
      .curve("g", [0.1, 0.5, 0.92])
      .curve("b", [0.06, 0.36, 0.78]))
Rows of colorful lotus lanterns over a temple courtyard
Before After

Lomo

Toy-camera look: high contrast, oversaturated color and dark corners. The vignette is a NumPy array blended with Multiply, where white in the center leaves pixels untouched.

Uses
image = LayerImage.from_file("photos/lotus-lanterns.jpg")
h, w, _ = image.get_image_as_array().shape
y, x = np.ogrid[-1:1:h * 1j, -1:1:w * 1j]
vignette = np.clip(1.2 - 0.6 * (x**2 + y**2), 0, 1)[..., np.newaxis]

(image.contrast(1.3)
      .saturation(0.4)
      .multiply(vignette))

Black & White

Monochrome conversions, from classic to toned prints.

Older man in a white hanbok and black gat hat by a paper window
Before After

Classic Black & White

Grayscale alone is often flat. An S-curve restores depth and brings out the texture of the beard and the woven gat hat.

Uses
image = LayerImage.from_file("photos/hanbok-portrait.jpg")
(image.grayscale()
      .curve("rgb", [0, 0.15, 0.48, 0.85, 1]))
Jeju beach with black volcanic rocks in late afternoon light
Before After

B&W with a Red Filter

Film photographers used a red lens filter to turn blue skies almost black. Multiplying by red before converting to grayscale does the same thing.

Uses
image = LayerImage.from_file("photos/jeju-coast.jpg")
(image.multiply("#ff5a36")
      .grayscale()
      .curve("rgb", [0, 0.3, 0.85, 1])
      .contrast(1.2))
Seoul alley at night with neon and lanterns reflected on wet pavement
Before After

Noir

Crushed blacks and bright highlights for a film-noir street. The curve's low second point drives most of the frame into shadow.

Uses
image = LayerImage.from_file("photos/seoul-night.jpg")
(image.grayscale()
      .curve("rgb", [0, 0.04, 0.3, 0.75, 1])
      .contrast(1.2))
Misty bamboo forest with sun rays
Before After

Cyanotype

The 19th-century blueprint process, recreated with a gradient map from deep Prussian blue to pale cyan.

Uses
image = LayerImage.from_file("photos/bamboo-forest.jpg")
(image.grayscale()
      .curve("rgb", [0, 0.1, 0.55, 0.9, 1])
      .curve("r", [0.02, 0.85])
      .curve("g", [0.15, 0.95])
      .curve("b", [0.35, 1.0]))
Hanok house with a snow-covered tiled roof
Before After

High-Key Monochrome

Bright, airy black and white: lighten toward white, then add contrast so the darkest details still read.

Uses
image = LayerImage.from_file("photos/snowy-hanok.jpg")
(image.grayscale()
      .lightness(0.3)
      .contrast(1.25))

Creative Effects

Gradients, vignettes, double exposures and false color. Most of them use NumPy arrays as blend layers.

Painted palace eaves against a deep blue sky
Before After

Duotone Poster

Map shadows to one color and highlights to another. A two-point curve per channel acts as a gradient map.

Uses
image = LayerImage.from_file("photos/palace-eaves.jpg")
shadow, highlight = (0.12, 0.08, 0.4), (1.0, 0.45, 0.42)  # indigo to coral

image.grayscale().contrast(1.2)
for channel, lo, hi in zip("rgb", shadow, highlight):
    image.curve(channel, [lo, hi])
Profile silhouette of a woman in hanbok against a white sky
Before After

Double Exposure

Blend modes accept another photo's pixels. Screen keeps the white sky white and fills the dark silhouette with the bamboo; a pale green Multiply tints the result.

Uses
image = LayerImage.from_file("photos/hanbok-silhouette.jpg")
bamboo = LayerImage.from_file("photos/bamboo-forest.jpg").get_image_as_array()

(image.screen(bamboo)
      .grayscale()
      .curve("rgb", [0, 0.12, 0.5, 0.88, 1])
      .multiply("#cfe3dc"))
Gyeonghoeru pavilion reflected in its pond at sunrise
Before After

Graduated Sky Filter

A vertical gradient from warm tint to white, blended with Multiply, deepens the sky while leaving the pond untouched, like a graduated filter on a lens.

Uses
image = LayerImage.from_file("photos/palace-pond.jpg")
h, w, _ = image.get_image_as_array().shape
t = np.clip(np.linspace(0, 2.2, h), 0, 1)[:, np.newaxis, np.newaxis]
gradient = (1 - t) * np.array([0.95, 0.55, 0.4]) + t  # tint at the top fading to white

image.multiply(gradient)
Pastel houses on a hillside in Gamcheon Culture Village, Busan
Before After

Channel Swap

LayerImage works on plain NumPy arrays, so anything NumPy can do is fair game. Swapping the red and green channels turns the turquoise houses violet and the pink ones lime.

Uses
image = LayerImage.from_file("photos/gamcheon-village.jpg")
pixels = image.get_image_as_array()
image = LayerImage.from_array(pixels[..., [1, 0, 2]].copy())  # swap R and G

image.contrast(1.1)
Rows of colorful lotus lanterns over a temple courtyard
Before After

Colorize

hue() sets every pixel to a single hue while keeping saturation and value, so every lantern becomes a shade of magenta.

Uses
image = LayerImage.from_file("photos/lotus-lanterns.jpg")
image.hue(0.88).contrast(1.1)
Pastel houses on a hillside in Gamcheon Culture Village, Busan
Before After

Pastel Dream

Soft and airy: lighten toward white, mute the colors and Screen a pale pink over everything.

Uses
image = LayerImage.from_file("photos/gamcheon-village.jpg")
(image.lightness(0.18)
      .saturation(-0.2)
      .screen("#ffc4d6", opacity=0.35))

One Preset, Many Photos

Operations can also be described as data. Save a look as JSON and apply it to a whole folder with apply_from_dict() (or apply_from_json()). Here is a warm, matte preset on four different photos.

warm-matte.json
{
  "operations": [
    {"type": "curve", "channels": "rgb", "curve_points": [0.08, 0.3, 0.55, 0.78, 0.94]},
    {"type": "saturation", "factor": -0.15},
    {"type": "soft_light", "hex": "#ffb36b", "opacity": 0.65},
    {"type": "lighten", "hex": "#1e1b2e"}
  ]
}
batch.py
import json
from pathlib import Path

from layeris import LayerImage

preset = json.loads(Path("warm-matte.json").read_text())
Path("out").mkdir(exist_ok=True)
for path in Path("photos").glob("*.jpg"):
    LayerImage.from_file(path).apply_from_dict(preset).save(f"out/{path.name}")
Gyeonghoeru pavilion reflected in its pond at sunrise
palace-pond.jpg
Overhead view of bibimbap in a stone bowl with banchan side dishes
bibimbap.jpg
Fishing harbor on Korea's east coast on a hazy day
fishing-harbor.jpg
Celadon tea set on a low table in a hanok room
hanok-tea.jpg

Sample Photos

Download the originals (1536 x 1024) to run the snippets yourself. They were generated for these docs with OpenAI's gpt-image-2.5 model; the prompts are in scripts/generate_photos.py.