Phase 1 Atomic Ops — differenceMap + orientationField
Visualisiert die beiden Atomic Ops, die die Hertzmann-Painterly- Pipeline (und jeden anderen struktur-gesteuerten Effekt) tragen. Vier Panels: 1. **original** — Eingabe. 2. **gaussianBlur(σ)** — geglättete Referenz. 3. **differenceMap(blur)** — pixelweise L2-RGB-Distanz als Grau- stufen-Heatmap. Hell = verändert, schwarz = identisch. Das "wo weicht die Leinwand von der Referenz ab"-Target, das in Hertzmann die Strichplatzierung steuert. 4. **orientationField(σ)** — geglätteter Struktur-Tensor auf Luminanz, HSV-codiert. Farbton = lokale Kantenrichtung, Sättigung = Kohärenz (wie stark gerichtet). Flache Bereiche erscheinen als helles Grau, gerichtete Bereiche leuchten farbig. Das Farbrad zeigt, wohin später die Pinselstriche fliessen werden. Beide Ops sind weit über Painterly hinaus nützlich: `differenceMap` für Vorher/Nachher-Diffs, Bewegungs-Gates, Qualitäts-Metriken; `orientationField` für jeden flow-basierten Effekt, kanten-bewusste Transformationen oder analytische Visualisierungen der Bildstruktur. Mit `SMOOTH` siehst du, wie das Feld bei höheren Sigmas blockiger und kohärenter wird (gut für globale Strichrichtung), aber feine Details verliert; niedrige Sigmas verfolgen Kanten präziser, wirken aber rauschiger.
// Phase 1 of the Hertzmann painterly pipeline — visualises the two
// new atomic ops that drive structure-aware effects.
// demo_orientation_field.js
//!INPUT: INPUT
//!OUTPUT: OUTPUT
//!PARAM: SMOOTH:number=2.0,min=0.5,max=8.0
//!PARAM: DIFF_REF_BLUR:number=2.5,min=0.5,max=10.0
//!PARAM: MAX_W:integer=900,min=400,max=1800
//!PARAM: LABEL_SIZE:number=22,min=10,max=64
// Two new building blocks, both visualised side-by-side with the
// reference image:
//
// ┌──────────────────────┬──────────────────────┐
// │ original │ gaussianBlur(σ) │
// ├──────────────────────┼──────────────────────┤
// │ differenceMap(blur) │ orientationField(σ) │
// │ (where the blur │ (HSV: hue=angle, │
// │ changed pixels) │ sat=coherence) │
// └──────────────────────┴──────────────────────┘
//
// `differenceMap` is the per-pixel L2 RGB distance between two images,
// rendered as a grayscale heatmap — bright = changed, black =
// identical. Useful well beyond Hertzmann: before/after diffs,
// motion gates, quality heatmaps.
//
// `orientationField` is the smoothed structure tensor on luminance,
// HSV-encoded: hue = local edge direction, saturation = coherence
// (how strongly directional the structure is at that pixel). Flat
// regions read as bright grayscale; oriented regions burst into
// colour. The colour wheel reveals which way the painterly brush
// strokes will eventually flow when Phase 2 lands.
//
// Tweak `SMOOTH` to see how the orientation field becomes blockier
// and more coherent at higher sigmas, but loses fine detail.
const SRC0 = Engine.loadImage(INPUT);
{
const long = Math.max(SRC0.width, SRC0.height);
if (long > MAX_W) {
const s = MAX_W / long;
SRC0.resize(Math.round(SRC0.width * s),
Math.round(SRC0.height * s),
Interp.Bilinear);
}
}
const W = SRC0.width;
const H = SRC0.height;
const bandH = LABEL_SIZE + 8;
const cellW = W;
const cellH = bandH + H;
const out = Engine.createImage(1, 1);
out.setPixel(px(0, 0), new Pixel(0.05, 0.05, 0.07, 1.0));
out.resize(cellW * 2, cellH * 2);
function panel(label, image, col, row) {
const x0 = col * cellW;
const y0 = row * cellH;
out.drawText(label, x0 + 16, y0 + LABEL_SIZE - 2, {
size: LABEL_SIZE, color: NamedColor.white,
});
out.blendAt(image, px(x0, y0 + bandH), 1.0, BlendMode.Over);
}
// Panel 0,0 — original
panel('1) original', SRC0.clone(), 0, 0);
// Panel 1,0 — blurred reference (the comparison target)
const blurred = SRC0.clone().gaussianBlur(DIFF_REF_BLUR);
panel(`2) gaussianBlur(σ=${DIFF_REF_BLUR})`, blurred.clone(), 1, 0);
// Panel 0,1 — differenceMap(original, blur)
{
const diff = SRC0.clone().differenceMap(blurred);
panel('3) differenceMap → grayscale', diff, 0, 1);
}
// Panel 1,1 — orientationField on the original
{
const flow = SRC0.clone().orientationField(SMOOTH);
panel(`4) orientationField(σ=${SMOOTH})`, flow, 1, 1);
}
SRC0.free();
blurred.free();
out.save(OUTPUT);
out.free();
// © 2026 Michael Lechner · mlc OpticScript · https://mlcgo.eu · Elastic License 2.0