Ölbild-Look mit Kuwahara — ein einziger Aufruf

`img.kuwahara(radius)` ist der klassische kanten-erhaltende Ölbild-Filter — die Umgebung jedes Pixels wird in vier überlappende Quadranten geteilt, der homogenste gewinnt und seine Mittelfarbe wird ausgegeben. Glatte Regionen bleiben glatt, Kanten rasten ein, das Bild bekommt einen echten malerischen „Pinselzonen"-Look in einem einzigen Aufruf. Eine kleine Sättigungs-/Schärfungs-Korrektur erhält die Lebendigkeit. `RADIUS` regelt den Look: 4 = skizzenhaft, 8-10 = klar gemalt, 16-24 = breite Striche. Zum Vergleich: das ältere `demo_oilpaint` baut eine Annäherung aus bestehenden Primitiven (Bilateral-Stapel + Posterize + Sättigung + Schärfen) — als „ohne neuen Op"-Demo brauchbar, aber das Ergebnis hat die Kubismus-artige Farbverflachung, die Posterize einbringt. Kuwahara vermeidet das vollständig.

INPUT
INPUT — Ölbild-Look mit Kuwahara — ein einziger Aufruf
Kuwahara paint
Kuwahara paint — Ölbild-Look mit Kuwahara — ein einziger Aufruf
JavaScript
// Painterly look via classical Kuwahara — single-call oil paint
// demo_kuwahara_paint.js
//!INPUT: INPUT
//!OUTPUT: OUTPUT
//!PARAM: RADIUS:number=10,min=2,max=30
//!PARAM: SATURATION_BOOST:number=1.10,min=0.5,max=2.0
//!PARAM: SHARPEN_AMOUNT:number=0.25,min=0,max=2

// `img.kuwahara(radius)` is the canonical edge-preserving oil-paint
// filter: each pixel's surrounding (2·radius+1)² window is split
// into four overlapping quadrants, the one with the lowest
// luminance variance wins, and its mean RGB becomes the output.
// Result: flat regions stay flat, edges snap, the image gets a
// real painterly "brush-zone" look in one call — no bilateral
// stack, no posterize, no manual tuning chain.
//
// This demo runs the filter, gives a small saturation/sharpen
// touch-up to keep the result lively (raw Kuwahara can read a
// little flat), and saves. Tweak guide:
//
//   RADIUS = 4      → sketchy hint
//   RADIUS = 8-10   → clear painterly look (default)
//   RADIUS = 16-24  → broad strokes / poster style
//
// Cost is O(W·H·radius²); a 1500×1000 image at radius=10 runs in
// roughly 1-2 s on a modern CPU.

const img = Engine.loadImage(INPUT);
img.kuwahara(RADIUS);
if (SATURATION_BOOST !== 1.0) {
  img.saturation(SATURATION_BOOST);
}
if (SHARPEN_AMOUNT > 0) {
  img.sharpen(SHARPEN_AMOUNT);
}
img.save(OUTPUT);
img.free();

// © 2026 Michael Lechner · mlc OpticScript · https://mlcgo.eu · Elastic License 2.0