Filmkorn auf allen Fotos

- Hero: feine, dezente Körnung (Variante A)
- Team-Porträts, Galerie und Menü-Fotos: Film-Look ISO 800 mit leicht
  farbigem Korn, etwas angehobenem Schwarz und leicht entsättigt (Variante B)
- Unbearbeitete Originale in design/original/, scripts/grain.py wendet den
  Effekt reproduzierbar an; Vorschaubild und README aktualisiert

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KBHsCnWNXAB2Le3gAEsivF
This commit is contained in:
Claude committed 2026-09-29 15:09:01 +00:00
1 parent 9e22ce616d
commit 734c2775b5
47 files changed
+75

No files matched your search

+5
View File
@@ -57,6 +57,11 @@ Preis-Thumbnails). Neue Porträts im selben Format ablegen und in `src/data/salo
laufen beim Scrollen seitlich durch, auf dem Handy wird gewischt. Neues Bild: als WebP (900 px breit) in laufen beim Scrollen seitlich durch, auf dem Handy wird gewischt. Neues Bild: als WebP (900 px breit) in
`src/assets/gallery/` ablegen und in `moments` in `Gallery.tsx` eintragen (Titel + Seitenverhältnis). `src/assets/gallery/` ablegen und in `moments` in `Gallery.tsx` eintragen (Titel + Seitenverhältnis).
**Körnung:** Alle Fotos haben einen Filmkorn-Look – Hero fein (Variante A), alle übrigen Fotos wie Film
ISO 800 (Variante B). Die unbearbeiteten Originale liegen in `design/original/`. Neue oder geänderte Fotos
dort ablegen (gleicher Pfad wie in `src/assets/`) und `python3 scripts/grain.py` ausführen – das Skript
schreibt die gekörnten Versionen nach `src/assets/`.
**Direktbuchung:** Jede Leistung trägt ihre Setmore-ID (`id`). Wird bei Setmore eine Leistung **Direktbuchung:** Jede Leistung trägt ihre Setmore-ID (`id`). Wird bei Setmore eine Leistung
neu angelegt, bekommt sie eine neue ID. Sie steht im Buchungslink der Leistung auf der neu angelegt, bekommt sie eine neue ID. Sie steht im Buchungslink der Leistung auf der
Setmore-Seite hinter `products=`. Setmore-Seite hinter `products=`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 65 KiB

+70
View File
@@ -0,0 +1,70 @@
"""Filmkorn auf die Website-Fotos rechnen.
Liest die unbearbeiteten Bilder aus design/original/ und schreibt die gekörnten Versionen
nach src/assets/. Hero: Variante A (fein), alle anderen Fotos: Variante B (Film, ISO 800).
pip install numpy pillow
python3 scripts/grain.py
"""
from pathlib import Path
import numpy as np
from PIL import Image, ImageFilter
ROOT = Path(__file__).resolve().parent.parent
SRC = ROOT / 'design' / 'original'
DST = ROOT / 'src' / 'assets'
VARIANTS = {
# fein & dezent
'A': dict(amount=0.016, size=1.2),
# Film, ISO 800: sichtbares, leicht farbiges Korn, etwas angehobenes Schwarz, leicht entsättigt
'B': dict(amount=0.028, size=1.5, color=0.15, lift=0.03, contrast=1.05, sat=0.92),
}
HERO = {'photos/hero.webp', 'photos/hero-1000.webp', 'photos/hero-mobile.webp'}
LUMA = np.array([0.2126, 0.7152, 0.0722], np.float32)
def noise(rng, h, w, size, color):
"""Gauß-Rauschen, auf Korngröße weichgezeichnet und wieder auf Standardabweichung 1 normiert."""
def one():
n = rng.normal(0, 1, (h, w)).astype(np.float32)
if size > 1:
im = Image.fromarray((n * 40 + 128).clip(0, 255).astype(np.uint8))
n = (np.asarray(im.filter(ImageFilter.GaussianBlur(size * 0.5)), np.float32) - 128) / 40
n /= n.std() + 1e-6
return n
mono = one()
if not color:
return np.stack([mono] * 3, -1)
return np.stack([mono + color * one() for _ in range(3)], -1)
def grain(img, rng, amount, size, color=0.0, lift=0.0, contrast=1.0, sat=1.0):
a = np.asarray(img.convert('RGB'), np.float32) / 255
h, w, _ = a.shape
a = (a - 0.5) * contrast + 0.5
lum = a @ LUMA
a = lum[..., None] + (a - lum[..., None]) * sat
a = lift + a * (1 - lift)
# Korn vor allem in den Mitteltönen, wie bei Film
lum = np.clip(a @ LUMA, 0, 1)
weight = 4 * lum * (1 - lum) + 0.35
a = a + noise(rng, h, w, size, color) * amount * weight[..., None]
return Image.fromarray((np.clip(a, 0, 1) * 255).astype(np.uint8))
def main():
for i, src in enumerate(sorted(SRC.rglob('*.webp'))):
rel = src.relative_to(SRC).as_posix()
variant = 'A' if rel in HERO else 'B'
out = grain(Image.open(src), np.random.default_rng(i + 1), **VARIANTS[variant])
dst = DST / rel
out.save(dst, 'WEBP', quality=82, method=6)
print(f'{variant} {rel:28} {src.stat().st_size // 1024:4} KB -> {dst.stat().st_size // 1024:4} KB')
if __name__ == '__main__':
main()
Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 166 KiB