Three blur flavours — Gaussian, anisotropic, motion
Same image through three different blur kernels stacked vertically so you can compare them at a glance: - **Gaussian** — the classic isotropic soft-focus blur, same sigma both axes. - **Anisotropic** — independent X/Y sigmas. With X much larger than Y you get a horizontal streak that keeps vertical detail crisp; lens-style "stretched lights" effect. - **Motion** — directional linear blur along an angle and length. Simulates camera shake or subject motion in a specific direction. All three are pure-pixel ops in the engine — no extensions needed. Tweak the param sliders to see how each one differs from naive Gaussian smoothing.
INPUT
Three blurs
JavaScript
// Three blur flavours side-by-side — gaussian / anisotropic / motion
// demo_blur_variants.js
//!INPUT: INPUT
//!OUTPUT: OUTPUT
//!PARAM: GAUSSIAN_SIGMA:number=4,min=0,max=40
//!PARAM: ANISO_SIGMA_X:number=12,min=0,max=40
//!PARAM: ANISO_SIGMA_Y:number=2,min=0,max=40
//!PARAM: MOTION_ANGLE:number=30,min=-180,max=180
//!PARAM: MOTION_LENGTH:integer=24,min=0,max=80
//!PARAM: LABEL_SIZE:number=22,min=10,max=64
// Renders the same input through three different blur kernels in
// one composite, so the visual difference is immediately obvious:
//
// 1. Isotropic Gaussian — the everyday "soft focus" blur. Equal
// sigma in both axes, separable two-pass.
// 2. Anisotropic Gaussian — independent sigmas. Stretching one
// axis gives lens-style streaks (sigma_x ≫ sigma_y) without
// losing detail in the other axis.
// 3. Motion blur — a directional linear average along an angle,
// not a Gaussian. Simulates camera shake / subject motion.
// Bilinear-sampled along the trail for smooth gradient.
//
// All three are pure pixel ops with no Rust extensions needed; the
// engine ships them in `bridge/src/ops/filter.rs`.
const src = Engine.loadImage(INPUT);
const SRC_W = src.width;
const SRC_H = src.height;
// One panel = the source size. Three panels stacked vertically with
// a small label band on top of each so the comparison is clear.
const BAND_H = LABEL_SIZE + 8;
const PANEL_H = SRC_H;
const TOTAL_H = (BAND_H + PANEL_H) * 3;
const canvas = Engine.createImage(1, 1);
canvas.setPixel(px(0, 0), new Pixel(0.05, 0.05, 0.07, 1.0));
canvas.resize(SRC_W, TOTAL_H);
// Helper: draw a label on the canvas at the start of a band.
function label(text, y) {
canvas.drawText(text, 16, y + LABEL_SIZE - 2, {
size: LABEL_SIZE,
color: NamedColor.white,
});
}
// ── Panel 1: isotropic Gaussian ──────────────────────────────────
{
const tile = src.clone();
tile.gaussianBlur(GAUSSIAN_SIGMA);
label(`gaussianBlur(${GAUSSIAN_SIGMA})`, 0);
canvas.blendAt(tile, px(0, BAND_H), 1.0, BlendMode.Over);
tile.free();
}
// ── Panel 2: anisotropic Gaussian ─────────────────────────────────
{
const tile = src.clone();
tile.anisotropicBlur(ANISO_SIGMA_X, ANISO_SIGMA_Y);
const y = (BAND_H + PANEL_H);
label(`anisotropicBlur(${ANISO_SIGMA_X}, ${ANISO_SIGMA_Y})`, y);
canvas.blendAt(tile, px(0, y + BAND_H), 1.0, BlendMode.Over);
tile.free();
}
// ── Panel 3: motion blur ──────────────────────────────────────────
{
const tile = src.clone();
tile.motionBlur(MOTION_ANGLE, MOTION_LENGTH);
const y = 2 * (BAND_H + PANEL_H);
label(`motionBlur(${MOTION_ANGLE}°, ${MOTION_LENGTH}px)`, y);
canvas.blendAt(tile, px(0, y + BAND_H), 1.0, BlendMode.Over);
tile.free();
}
src.free();
canvas.save(OUTPUT);
canvas.free();
// © 2026 Michael Lechner · mlc OpticScript · https://mlcgo.eu · Elastic License 2.0