Image API — `ImageHandle` · `Engine`

Diese Seite ist derzeit nur englisch — die JSDoc-Kommentare in der Engine sind englisch verfasst. Eine deutsche Auto-Generierung folgt, sobald die Methoden mit @apidoc-de-Tags ausgestattet sind. — Zur Übersicht

All pixel-processing operations live on ImageHandle (the value you get back from Engine.loadImage() / Engine.createImage()). The Engine global is the entry point for loading, creating, and saving images, plus the canvas / path / animation / tool / timer factories.

← Zurück zur API-Referenz


Classes

ImageHandle

A handle to an image stored in the Rust image registry.

Most methods return this for fluent chaining:

const img = Engine.loadImage("input.png");
img.brightness(1.2).gaussianBlur(1.5).save("out.png");

Size-changing operations (crop, resize, pad, rotateExpand, warpPolar, warpPerspective) update width and height automatically.

Accessors

height
Get Signature
get height(): number;

Current image height in pixels. Updated automatically by size-changing ops.

Returns

number

Set Signature
set height(v): void;
Parameters
Parameter Type
v number
Returns

void

id
Get Signature
get id(): number;

Internal handle id (read-only). Needed for cross-image ops like blend/applyMask.

Returns

number

width
Get Signature
get width(): number;

Current image width in pixels. Updated automatically by size-changing ops.

Returns

number

Set Signature
set width(v): void;
Parameters
Parameter Type
v number
Returns

void

Methods

_setHeightAndWidthUnsafe()
_setHeightAndWidthUnsafe(h, w): void;
Parameters
Parameter Type
h number
w number
Returns

void

addNoise()
addNoise(opts?, region?): this;

Add synthetic sensor noise — the core degradation primitive for building model-training data (Real-ESRGAN / GFPGAN style: take a clean image, degrade it, train the network to invert it).

Unlike filmGrain (uniform mono grain only), this exposes the distribution shapes a degradation pipeline actually needs:

  • "gaussian" — normal noise, constant standard deviation sigma. The classic additive read-noise.
  • "uniform" — flat noise in ±sigma (same as filmGrain).
  • "poisson" — shot noise whose strength scales with √pixel: bright regions get noisier, shadows stay clean. Modelled as the standard Gaussian approximation of Poisson statistics; sigma is the overall gain.
Parameters
Parameter Type Description
opts? { color?: boolean; seed?: number; sigma?: number; type?: "gaussian" | "uniform" | "poisson"; } -
opts.color? boolean false (default) shares one sample across RGB (luminance grain); true perturbs each channel independently (chroma noise). Alpha is never touched.
opts.seed? number Integer ≥ 0 makes the noise reproducible: the same call on the same image gives the same result, bit for bit. Omit it for fresh noise on every run. Set it whenever the output goes into a test, a diff or a published example.
opts.sigma? number Noise strength (≈ [0..1]). Default 0.05.
opts.type? "gaussian" | "uniform" | "poisson" Distribution. Default "gaussian".
region? Rect | RegionOpts Optional region to limit the effect to.
Returns

this

this

Note

Without seed the result varies between runs.

Example
// Real-ESRGAN-style degradation step
img.gaussianBlur(1.5)
   .resize(img.width / 2, img.height / 2)
   .addNoise({ type: "poisson", sigma: 0.08, color: true });
adjustAlpha()
adjustAlpha(
   value, 
   mode?, 
   region?): this;

Adjust the alpha (transparency) channel.

Parameters
Parameter Type Description
value number Target value; meaning depends on mode.
mode AlphaMode AlphaMode.Set = set directly; Mul = multiply; Add = add (clamped). Default: Set.
region? Rect | RegionOpts -
Returns

this

this

anisotropicBlur()
anisotropicBlur(sigmaX, sigmaY): this;

Anisotropic Gaussian blur — independent sigmas on the X and Y axes. Useful for lens-style streak effects, fake shallow depth of field, or just a "stretched lights" look. When sigmaX === sigmaY this matches gaussianBlur(sigmaX). Either sigma ≤ 0 disables that axis (so a horizontal-only or vertical-only smear is just anisotropicBlur(8, 0) / anisotropicBlur(0, 8)).

Parameters
Parameter Type Description
sigmaX number Horizontal Gaussian sigma in pixels.
sigmaY number Vertical Gaussian sigma in pixels.
Returns

this

this

Example
// Horizontal motion-streak hint — vertical detail intact.
img.anisotropicBlur(12, 1);
// Camera-shake-style stretch in both axes, more horizontal.
img.anisotropicBlur(8, 3);
anisotropicKuwahara()
anisotropicKuwahara(radius, region?): this;

Anisotropic Kuwahara filter (Kyprianidis 2009) — the state-of-the-art painterly stylisation. Where kuwahara uses four axis-aligned quadrants, this version drives the kernel geometry from the local image structure: brush strokes follow the dominant edge direction (hair runs along hair, ridges along ridges) and stretch into long, thin shapes in highly-directional regions. Result: noticeably closer to a real painting than the classical version, especially around faces, fabric and natural texture.

Cost is ~3-5× the classical Kuwahara at the same radius — we additionally compute a structure tensor (smoothed gradient outer product) before the per-pixel sector accumulation. radius 6-12 is typical; larger looks more "broad-stroke".

Parameters
Parameter Type Description
radius number Maximum kernel half-extent in pixels (≥ 1).
region? Rect | RegionOpts -
Returns

this

this

Example
img.anisotropicKuwahara(8);          // painterly portrait
img.bilateralBlur(5, 0.25, 8)
   .anisotropicKuwahara(10)
   .saturation(1.10);                // pre-smooth for cleaner strokes
applyMask()
applyMask(mask): this;

Apply a luminance mask — the mask image's greyscale value sets this image's alpha.

Parameters
Parameter Type Description
mask ImageHandle Mask image (must have the same dimensions).
Returns

this

this

artisticBold()
artisticBold(size?): this;

Ink-drawing effect — black-on-white outline silhouette built from canny(0.1, 0.2) → dilate(size) → invert. Kept as a convenience alias.

Parameters
Parameter Type Default value Description
size number 3 Dilation radius. Larger = thicker ink lines. Default 3.
Returns

this

this

Deprecated

Convenience alias. New code should compose the three primitives explicitly so the intent is visible: img.canny(0.1, 0.2).dilate(size).invert(). Or for a softer / shaded sketch look, use img.pencilSketch(strength).

autocrop()
autocrop(opts?): this;

Trim a uniform-colour or transparent border. Finds the smallest rectangle containing every non-background pixel and crops to it. Updates width and height. When the whole image is background, returns this unchanged.

Default behaviour (no opts): trim transparent margins (α ≤ 0.001).

Parameters
Parameter Type Description
opts? { color?: Color; padding?: number; tolerance?: number; } -
opts.color? Color If set, trim that solid colour instead of transparency (Pixel or hex string). α=0 pixels are also treated as background so mixed transparent + flat-colour borders work without a second pass.
opts.padding? number Pixels of margin to keep around the detected box. Default 0. Clamped to image bounds.
opts.tolerance? number How close to the background a pixel can be before it counts as content. In [0, 1]. Default 0.001 (alpha) / 0.05 (color).
Returns

this

this

Example
// Drop transparent edges
Engine.loadImage("logo.png").autocrop().save("logo_tight.png");

// Drop a white frame around a scan
Engine.loadImage("scan.jpg")
      .autocrop({ color: "#ffffff", tolerance: 0.04, padding: 8 })
      .save("trimmed.jpg");
bilateralBlur()
bilateralBlur(
   d, 
   sigmaColor, 
   sigmaSpace, 
   region?): this;

Bilateral blur — edge-preserving smoothing.

Parameters
Parameter Type Description
d number Diameter of pixel neighbourhood.
sigmaColor number Color sigma (larger = smoother colors).
sigmaSpace number Space sigma (larger = wider area).
region? Rect | RegionOpts -
Returns

this

this

blend()
blend(
   other, 
   factor, 
   mode?): this;

Blend another image onto this one. Both images must have the same dimensions.

Parameters
Parameter Type Description
other ImageHandle The image to blend in.
factor number Blend weight ∈ [0..1]: 0 = only self, 1 = only other.
mode? BlendMode Blend mode (default: Normal = 0).
Returns

this

this

blendAt()
blendAt(
   other, 
   at, 
   factor, 
   mode?): this;

Blend another image onto this one at a specific pixel position. The other image may be smaller — it is composited at at.

Parameters
Parameter Type Description
other ImageHandle The image to composite.
at Px Pixel-space offset, e.g. px(100, 50) or px(0, 0).
factor number Blend weight ∈ [0..1].
mode? BlendMode Blend mode (default: Normal = 0).
Returns

this

this

boxBlur()
boxBlur(size, region?): this;

Box blur (average filter).

Parameters
Parameter Type Description
size number Kernel half-size in pixels.
region? Rect | RegionOpts -
Returns

this

this

brightness()
brightness(factor, region?): this;

Adjust brightness.

Parameters
Parameter Type Description
factor number Multiplier: 1.0 = unchanged, > 1.0 = brighter, < 1.0 = darker.
region? Rect | RegionOpts -
Returns

this

this

canny()
canny(
   low, 
   high, 
   region?): this;

Canny edge detection (two-threshold).

Parameters
Parameter Type Description
low number Low hysteresis threshold ∈ [0..1].
high number High hysteresis threshold ∈ [0..1].
region? Rect | RegionOpts -
Returns

this

this

clone()
clone(): ImageHandle;

Create a deep copy of this handle. The clone is independent — operations on it do not affect the original.

Returns

ImageHandle

A new ImageHandle with its own pixel data.

Throws

If the clone fails.

colorJitter()
colorJitter(
   brightness, 
   contrast, 
   saturation, 
   hue, 
   region?): this;

Randomized color jitter for data augmentation. Each parameter applies random variation in the given range.

Parameters
Parameter Type Description
brightness number Brightness jitter magnitude.
contrast number Contrast jitter magnitude.
saturation number Saturation jitter magnitude.
hue number Hue jitter magnitude (degrees).
region? Rect | RegionOpts -
Returns

this

this

Note

Non-deterministic — result varies between runs.

colorMatch()
colorMatch(reference): this;

Reinhard per-channel mean/std colour transfer — shifts this image's tone to match reference. Per channel:

new = (old - mean_self) * std_ref / std_self + mean_ref

Alpha-weighted, so transparent borders (e.g. from warpPerspective outside-source areas) don't skew the source mean toward zero. Useful for blending AI-restored faces (GFPGAN, CodeFormer) back into a photo's skin-tone neighbourhood: pass the original aligned face crop before warping the restored version back. Dimensions must match.

Parameters
Parameter Type
reference ImageHandle
Returns

this

Example
const aligned  = orig.clone().warpPerspective(512, 512, fwd);
const restored = Engine.tool('gfpgan-restore').apply(aligned);
restored.colorMatch(aligned);  // match orig face's tone
aligned.free();
colorTransfer()
colorTransfer(reference): this;

Reinhard color transfer — match this image's per-channel mean and standard deviation in log-LMS space to a reference image's. Useful for harmonising photos shot under different lighting, matching screenshot tones across OS themes, or warming/cooling a target to fit a hero shot.

Pixels with α = 0 are skipped (left untouched).

If you transfer the same reference onto many targets, prefer Engine.colorStats(ref) once → target.colorTransfer(stats) per target, so the reference stats aren't recomputed every time.

Parameters
Parameter Type Description
reference LmsStats | ImageHandle Either an ImageHandle (stats computed on the spot) or a precomputed LmsStats object from Engine.colorStats().
Returns

this

this

Example
const warm = Engine.loadImage("hero_warm.jpg");
Engine.loadImage("cold_screenshot.png")
      .colorTransfer(warm)
      .save("harmonised.png");
compare()
compare(reference, opts?): object;

Compare this image against a reference and get the numbers back.

differenceMap() shows where two images disagree; this says how much. Sizes must match — a mismatch throws rather than quietly returning something meaningless.

Returned values, all computed on the [0..1] range the engine works in:

  • mse — mean squared error. 0 means identical.
  • psnr — peak signal-to-noise ratio in dB, 10·log₁₀(1/mse). Higher is closer; identical images give Infinity. Roughly: 50 dB is invisible, 40 dB is hard to spot, below 30 dB you can see it.
  • mae — mean absolute error, less dominated by outliers.
  • maxDiff — the single worst channel deviation.
  • differingPixels / differingFraction — how many pixels exceed tolerance in any channel.
Parameters
Parameter Type Description
reference ImageHandle Image to compare against. Must be the same size.
opts? { alpha?: boolean; tolerance?: number; } -
opts.alpha? boolean Count the alpha channel too. Default true: for a regression check you want it, since two images can share every colour and differ only in transparency.
opts.tolerance? number Per-channel distance in [0..1] above which a pixel counts as differing. Default 0, so any difference at all counts. Only affects differingPixels / differingFraction.
Returns

object

Metrics object. Does not modify the image.

Name Type
differingFraction number
differingPixels number
mae number
maxDiff number
mse number
psnr number
Examples
// Regression check against a reference rendering
const shot = Engine.loadImage(INPUT);
const m = shot.compare(Engine.loadImage("reference.png"));
if (m.psnr < 40) {
    throw new Error(`drifted: ${m.psnr.toFixed(2)} dB, ` +
                    `${m.differingPixels} pixels`);
}
// Pick the smallest quality that still looks right.
//
// The probe has to be written AND read back: `save()` encodes to
// a file but leaves the handle in memory untouched, so comparing
// against it would measure nothing at all.
const src = Engine.loadImage(INPUT);
for (const q of [90, 80, 70, 60, 50]) {
    src.clone().save("/tmp/probe.webp", { format: "webp", quality: q });
    const probe = Engine.loadImage("/tmp/probe.webp");
    const psnr = src.compare(probe).psnr;
    probe.free();
    if (psnr < 42) { console.log(`stop at ${q}`); break; }
}
contrast()
contrast(factor, region?): this;

Adjust contrast.

Parameters
Parameter Type Description
factor number 1.0 = unchanged, > 1.0 = higher contrast, 0.0 = flat grey.
region? Rect | RegionOpts -
Returns

this

this

convolve()
convolve(
   kernel, 
   kw?, 
   kh?): this;

Apply a custom 2-D convolution kernel.

The kernel is a flat row-major array applied to the RGB channels. Alpha is preserved. Output is clamped to [0..1].

If kw and kh are omitted the kernel is assumed to be square (sqrt(kernel.length) × sqrt(kernel.length)).

Parameters
Parameter Type Description
kernel number[] Flat row-major kernel values.
kw? number Kernel width (default: Math.sqrt(kernel.length)).
kh? number Kernel height (default: same as kw).
Returns

this

this

Examples
img.convolve([0, -1, 0,  -1, 5, -1,  0, -1, 0]);
img.emboss();
img.convolve(new Array(9).fill(1/9), 9, 1);
crop()
crop(
   x, 
   y, 
   w, 
   h): this;

Crop a rectangular region. Updates width and height.

Parameters
Parameter Type Description
x number Left edge in pixels.
y number Top edge in pixels.
w number Crop width in pixels.
h number Crop height in pixels.
Returns

this

this

differenceMap()
differenceMap(reference): this;

Per-pixel L2 RGB difference between this image and reference — replaces this image with a grayscale heatmap of the distance. Output: R = G = B = min(1, sqrt(dr² + dg² + db²) / sqrt(3)), A = 1.0 (the /√3 normalisation maps black-vs-white to 1.0).

Both images must have the same dimensions; mismatched sizes leave the image untouched.

Phase 1a of the Hertzmann painterly pipeline (the "where does the canvas disagree with the reference" target for stroke placement). Independently useful for:

  • Before/after diff visualisations.
  • Quality-metric heatmaps (where MSE concentrates).
  • Motion-detection gates ("did anything change?").
Parameters
Parameter Type Description
reference ImageHandle Image to compare against.
Returns

this

this

Example
const before = Engine.loadImage(INPUT);
const after  = before.clone().bilateralBlur(9, 0.1, 8);
const diff   = before.clone().differenceMap(after);
diff.save(OUTPUT);  // bright = the blur changed something
dilate()
dilate(size, region?): this;

Morphological dilation — expands bright regions.

Parameters
Parameter Type Description
size number Structuring element radius in pixels.
region? Rect | RegionOpts -
Returns

this

this

dither()
dither(region?): this;

Floyd–Steinberg error-diffusion dithering (reduces to 1-bit per channel).

Parameters
Parameter Type
region? Rect | RegionOpts
Returns

this

this

dominantColors()
dominantColors(count?): object[];

The most common colours, counted over a coarse 16-step grid.

Each entry carries the average of the real colours in its cell, not the cell centre — otherwise you would be shown colours the image does not contain. Mostly transparent pixels are skipped, so a cut-out subject is not described by the colour of the background it no longer has.

Parameters
Parameter Type Default value Description
count number 5 How many colours to return. Default 5.
Returns

object[]

drawText()
drawText(
   text, 
   x, 
   y, 
   opts?): this;

Draw a single line of text onto the image at pixel (x, y). The y-coordinate is the text baseline (SVG default), not the top of the type. Two open-source variable fonts are embedded with the engine and always available:

  • "Inter" (also responds to "sans-serif")
  • "JetBrains Mono" (also "monospace") Custom fonts can be loaded with Engine.loadFont(path).
Parameters
Parameter Type Description
text string The string to draw. < > & are auto-escaped.
x number X coordinate in pixels.
y number Y baseline coordinate in pixels.
opts? | MlcFont | { anchor?: "start" | "middle" | "end"; color?: Color; font?: string; lineHeight?: number; maxWidth?: number; size?: number; textAlign?: TextAlign; } -
Returns

this

this

dropShadow()
dropShadow(
   dx?, 
   dy?, 
   blur?, 
   color?, 
   strength?): this;

Drop shadow — a coloured, blurred, offset silhouette behind the image. Composes tint → blur → blendAt at offset → blendAt sharp on top in one call.

Note: the image stays the same size — pixels of the shadow that fall outside the original frame are clipped. If you need a wider canvas, pad() first then call dropShadow.

Parameters
Parameter Type Default value Description
dx number 5 Horizontal offset in pixels (positive → shadow to the right). Default 5.
dy number 5 Vertical offset in pixels (positive → down). Default 5.
blur number 5 Gaussian sigma of the shadow softness. Default 5.
color Color '#000000' Shadow colour. Hex / Pixel / tuple. Default "#000000".
strength number 1.0 Shadow opacity, ∈ [0..1]. Default 1.0.
Returns

this

this

Example
img.dropShadow(4, 4, 6, "#000000", 0.6);
img.pad(20, 20, 20, 20).dropShadow(8, 12, 10, "#000", 0.5);
emboss()
emboss(): this;

Emboss effect — classic relief kernel that makes the image look carved.

Applies the kernel [-2,-1,0, -1,1,1, 0,1,2]. Its weights sum to 1, so the photo keeps its colours and gains a relief along directional edges — it is not the grey "stone" look. For that, take the colour out first: img.grayscale().emboss().

Returns

this

this

erode()
erode(size, region?): this;

Morphological erosion — shrinks bright regions.

Parameters
Parameter Type Description
size number Structuring element radius in pixels.
region? Rect | RegionOpts -
Returns

this

this

fillRectangle()
fillRectangle(
   x, 
   y, 
   w, 
   h, 
   color): this;

Fill a rectangular region with a single colour, in-place. The rect is clipped to the image bounds; a fully off-screen rect is a silent no-op.

Accepts the same Color shapes as Engine.createColoredImage: Pixel, hex string, or [r,g,b,(a)] array (0..1 floats).

Parameters
Parameter Type
x number
y number
w number
h number
color Color
Returns

this

Example
img.fillRectangle(10, 10, 100, 80, [1, 0, 0, 1]);   // red box
img.fillRectangle(0, 0, img.width, img.height, "#ffffff"); // whiteout
filmGrain()
filmGrain(amount, region?): this;

Film grain — adds random noise for analog look.

Parameters
Parameter Type Description
amount number Noise intensity ∈ [0..1].
region? Rect | RegionOpts -
Returns

this

this

Note

Non-deterministic — result varies between runs.

fisheye()
fisheye(
   center, 
   radius, 
   amount): this;

Fisheye lens distortion.

Parameters
Parameter Type Description
center Px Center of the distortion in pixel space, e.g. px(img.width/2, img.height/2).
radius number Effect radius in pixels.
amount number Distortion strength (positive = barrel, negative = pincushion).
Returns

this

this

flipH()
flipH(): this;

Flip horizontally (mirror left↔right).

Returns

this

this

flipV()
flipV(): this;

Flip vertically (mirror top↔bottom).

Returns

this

this

forEachPixel()
forEachPixel(fn): this;

Iterate every pixel, calling fn(pixel, x, y). If fn returns a Pixel, that value is written back. ⚠ Very slow for large images — intended for algorithmic / creative use.

Parameters
Parameter Type Description
fn (pixel, x, y) => void | Pixel `(pixel: Pixel, x: number, y: number) => Pixel
Returns

this

this

free()
free(): void;

Release the underlying Rust image buffer. After calling free(), do not use this handle.

Calling free() explicitly is the deterministic option (memory released immediately). When omitted, the FinalizationRegistry in handle-registry.ts releases the buffer on the next GC pass — fine for one-shot scripts, but for tight loops call .free() (or Engine.gc() once per iteration) to keep peak RSS bounded.

Returns

void

gaussianBlur()
gaussianBlur(sigma, region?): this;

Gaussian blur.

Parameters
Parameter Type Description
sigma number Standard deviation in pixels. Higher = more blur.
region? Rect | RegionOpts -
Returns

this

this

getMeta()
getMeta(key): unknown;

Read a single top-level metadata key. Returns the parsed value (any JSON type) or null if the key is unset.

Parameters
Parameter Type
key string
Returns

unknown

Example
img.getMeta("opticscript").width;     // sub-access via JS chaining
img.getMeta("dataset");                // user-set free-form value
getPixel()
getPixel(at): Pixel;

Read a single pixel. Returns a Pixel with r/g/b/a in [0..1].

Parameters
Parameter Type Description
at Px Pixel position, e.g. px(100, 50).
Returns

Pixel

glow()
glow(
   blur?, 
   color?, 
   strength?): this;

Outer glow — soft luminous halo BEHIND the silhouette, extending past the alpha edges. Builds a tinted, blurred, alpha-faded copy of this image and stacks the sharp original on top so the body keeps its original colours and only the region outside the silhouette is coloured.

Implemented as a JS composition of existing primitives: tint → blur → adjustAlpha(strength, Mul) → blendAt halo behind → blendAt sharp on top. Best on inputs with alpha edges (post-rmbg, isolated subjects, rendered SVG, text). On a fully opaque source the sharp top layer covers the halo entirely — the call is effectively a no-op.

Parameters
Parameter Type Default value Description
blur number 15 Gaussian sigma of the halo in pixels. Default 15. Larger = softer / wider halo.
color Color '#ffffff' Halo tint colour. Hex string, Pixel, or [r,g,b,a] tuple. Default "#ffffff".
strength number 1.0 Halo opacity, ∈ [0..1]. Default 1.0.
Returns

this

this

Example
img.glow(15, "#ffffff");          // classic white halo
img.glow(8, "#ff80ff", 0.6);      // soft magenta border
grayscale()
grayscale(region?): this;

Convert to greyscale using luminance weights.

Parameters
Parameter Type
region? Rect | RegionOpts
Returns

this

this

histogram()
histogram(bins?): object;

Per-channel histogram with a chosen number of bins.

Deliberately coarse by default. 256 bins are meant for an eye looking at a curve; 16 or 32 are what a script — or a language model — can actually summarise into a sentence about exposure and contrast.

const h = img.histogram(16);
const dark = (h.r[0] + h.r[1]) / (img.width * img.height);
if (dark > 0.4) console.log("this image is mostly shadow");
Parameters
Parameter Type Default value Description
bins number 16 Number of buckets per channel, 1..256. Default 16.
Returns

object

Name Type
a number[]
b number[]
bins number
g number[]
r number[]
hue()
hue(delta, region?): this;

Rotate hue by delta degrees.

Parameters
Parameter Type Description
delta number Degrees to shift hue, e.g. 180 inverts hue.
region? Rect | RegionOpts -
Returns

this

this

invert()
invert(region?): this;

Invert all color channels (negative effect). Alpha is preserved.

Parameters
Parameter Type
region? Rect | RegionOpts
Returns

this

this

kaleidoscope()
kaleidoscope(
   center, 
   segments, 
   rotation): this;

Kaleidoscope — mirror-slice symmetry.

Parameters
Parameter Type Description
center Px Center of symmetry in pixel space, e.g. px(img.width/2, img.height/2).
segments number Number of mirror segments.
rotation number Initial rotation offset in degrees.
Returns

this

this

kuwahara()
kuwahara(radius, region?): this;

Classical Kuwahara filter — the modern "oil-painting" / painterly effect. For each pixel the surrounding (2·radius+1)² window is split into four overlapping quadrants; the quadrant with the lowest luminance variance wins and its mean RGB becomes the output. Result: flat regions stay flat, edges snap, and the image gets that characteristic "brush-zone" look without the cubist colour-flattening that the older oilPaint (histogram- based) introduces.

Recommended for new uses. radius 4–8 is a soft sketch, 10–20 a clear painting. Cost is O(W·H·radius²).

Parameters
Parameter Type Description
radius number Window half-extent in pixels (≥ 1).
region? Rect | RegionOpts -
Returns

this

this

Example
img.kuwahara(8);                  // soft painterly look
img.bilateralBlur(7, 0.3, 12)
   .kuwahara(12)
   .saturation(1.15);             // pre-smooth then paint
mark()
mark(id, opts?): this;

Mark a photo as yours — an invisible watermark that survives what happens to photos on the web: resizing, cropping and JPEG re-encoding. Requires OpticScript Pro; reading marks with readMark is free.

The id is repeated in tiles across the whole image (a quarter of the short side each), with a sync pattern that lets readMark find scale and crop again, and error correction plus a checksum so it never returns a wrong id.

Measured on 30.09.2026 on 24 photos (512–1600 px), default strength, about 43 dB:

after read
JPEG q90 / q75 / q50 24/24
scaled to 95 %, 84 %, long side 800 px 24/24
scaled to 50 % 23/24
border cropped 1–50 px 23–24/24
centre square, 1080 px (Instagram) 24/24
70 % crop, then scaled to 70 % 21/24
60 % crop 20/24
rotated 90° / 180°, mirrored (with resize and JPEG) 24/24
sharpen, median, blur 1–2 px 23–24/24
addNoise() default (σ 0.05) · σ 0.10 20/24 · 11/24
strong denoise (nonLocalMeans(7, 3, 0.08)) 11/24

Limits: a slight tilt (a few degrees) is not recognised — turns by 90° steps and mirroring are; the checked image should keep at least about 40 % of the short side and 300 px. It is evidence, not copy protection — someone who knows the method can weaken it. Mark last, after any editing.

Parameters
Parameter Type Description
id string 16 hex digits (64 bit) or a text of 1–8 bytes.
opts? { strength?: number; } -
opts.strength? number Default 0.5. Higher survives harsher treatment and is easier to see.
Returns

this

this

Throws

Without a Pro licence, or when the short side is under 256 px.

Example
const photo = Engine.loadImage(INPUT);
photo.resize(2048, 1365);          // edit first
photo.mark("a3f29c810badf00d");    // then mark
photo.save(OUTPUT, { quality: 90 });
medianBlur()
medianBlur(radius): this;

Median filter — replaces each pixel's R/G/B channels with the median of the channel values in a (2·radius+1)² window. The classic salt-and-pepper denoiser: a single rogue bright/dark pixel is gone after one pass because the median ignores outliers that the mean would smear into neighbours. For Gaussian-style sensor noise prefer bilateralBlur or nonLocalMeans — those preserve fine texture better.

radius 1-2 typical for impulse cleanup; larger values lose detail quickly. Cost O(W·H·radius²·log(radius)). Alpha preserved.

Parameters
Parameter Type Description
radius number Window half-extent in pixels (≥ 1).
Returns

this

this

mergeMeta()
mergeMeta(key, partial): this;

Shallow-merge a partial object into an existing metadata key. If the key was unset or held a non-object, partial becomes the new value. Convenience for the common setMeta(k, { ...getMeta(k), ...partial }) pattern.

Parameters
Parameter Type
key string
partial Record<string, unknown>
Returns

this

this

Example
img.mergeMeta("exif", { iso: 400, aperture: 2.8 });
meta()
meta(): Record<string, unknown>;

Return the full metadata blob attached to this image as a fresh JS object. Auto-populated entries live under the opticscript.* namespace (fileSize, mime, format, width, height, sourcePath, …); EXIF / DICOM / TIFF parsers add their own siblings; user-set data lives wherever you put it via setMeta.

The returned object is a snapshot — mutating it does not affect storage. Use setMeta(key, value) to write back.

Returns

Record<string, unknown>

Example
const img = Engine.loadImage("photo.jpg");
const m = img.meta();
console.log(m.opticscript.mime);           // "image/jpeg"
console.log(m.opticscript.fileSize);       // 845231
montageH()
montageH(...others): ImageHandle;

Stack this image and one or more others side-by-side horizontally. The result height equals the tallest image; shorter images are top-aligned.

Parameters
Parameter Type Description
...others ImageHandle[] One or more ImageHandles to place to the right.
Returns

ImageHandle

New ImageHandle containing all images concatenated horizontally.

montageV()
montageV(...others): ImageHandle;

Stack this image and one or more others vertically. The result width equals the widest image; narrower images are left-aligned.

Parameters
Parameter Type Description
...others ImageHandle[] One or more ImageHandles to place below.
Returns

ImageHandle

New ImageHandle containing all images concatenated vertically.

motionBlur()
motionBlur(angleDeg, length): this;

Motion blur — directional linear blur along angleDeg for length pixels. Simulates camera or subject motion. Single pass; cost scales with the pixel length of the trail.

angleDeg is measured counter-clockwise from the +x axis: 0 blurs horizontally, 90 vertically, 45 along the diagonal.

Parameters
Parameter Type Description
angleDeg number Direction of motion in degrees.
length number Trail length in pixels (0 = no-op).
Returns

this

this

Example
// 30° camera shake, 24 px trail.
img.motionBlur(30, 24);
// Pure horizontal motion (race car).
img.motionBlur(0, 40);
nonLocalMeans()
nonLocalMeans(
   searchRadius, 
   patchRadius, 
   h): this;

Non-Local Means denoiser — the gold-standard for natural-photo Gaussian noise. For each pixel the filter searches a window of nearby pixels and averages them, weighting each neighbour by patch similarity (small patch around centre vs. small patch around candidate). Similar patches occur naturally throughout a clean image but noise differs sample-to-sample, so the weighted average preserves real structure (edges, texture) while cancelling the noise. Markedly cleaner output than bilateralBlur for sensor noise.

Integral-image accelerated — cost is O(W·H·search²) regardless of patch size; ~2-5 s for a 1500×1000 image at default params (search=7 / patch=3 / h=0.08).

Parameters
Parameter Type Description
searchRadius number Outer search-window half-extent (typical 5-10).
patchRadius number Patch half-extent for similarity (typical 2-3).
h number Filtering parameter; larger = more aggressive smoothing (typical 0.05-0.15 for [0..1] data).
Returns

this

this

Example
img.nonLocalMeans(7, 3, 0.08);          // strong denoise
img.nonLocalMeans(5, 2, 0.05);          // gentle, fast
oilPaint()
oilPaint(
   radius, 
   levels, 
   region?): this;

Oil paint effect (legacy histogram-based) — quantizes to dominant colors in local windows. Kept for backward compatibility; new code should prefer kuwahara(radius) which produces a softer, more painterly look without the cubist colour-flattening this method introduces.

Parameters
Parameter Type Description
radius number Local window radius in pixels.
levels number Number of intensity levels (e.g. 8).
region? Rect | RegionOpts -
Returns

this

this

Deprecated

Prefer kuwahara(radius) for new code.

orientationField()
orientationField(smoothSigma?): this;

Replace this image with an HSV-encoded visualisation of its local edge orientation, derived from the smoothed structure tensor on luminance:

  • Hue = edge direction φ (mapped from [0, π] to a full colour wheel — same physical edge in either direction maps to the same hue).
  • Saturation = coherence ((λ₁ − λ₂) / (λ₁ + λ₂)) — high in clearly oriented regions, low in flat or noisy regions.
  • Value = 1.0 throughout, so flat regions read as bright grayscale and oriented regions burst into colour.

Phase 1b of the Hertzmann painterly pipeline. Same tensor math as anisotropicKuwahara but exposed as a standalone op for any flow-based effect. Visualisation is intentionally lossy — a raw-vector variant will follow when stroke generation needs precise vectors.

Parameters
Parameter Type Default value Description
smoothSigma number 2.0 Gaussian sigma for tensor smoothing (typical 1.0–4.0). Larger = blockier, more coherent fields. Default 2.0.
Returns

this

this

Example
const flow = Engine.loadImage(INPUT).orientationField(2.0);
flow.save(OUTPUT);  // colour wheel = local edge direction
pad()
pad(
   top, 
   bottom, 
   left, 
   right, 
   color?, 
   border?): this;

Pad the image with a border. Updates width and height.

Parameters
Parameter Type Description
top number Top padding in pixels.
bottom number Bottom padding in pixels.
left number Left padding in pixels.
right number Right padding in pixels.
color? PadColor Border color: a Pixel, a CSS hex string ("#rgb" / "#rgba" / "#rrggbb" / "#rrggbbaa"), a CSS colour name ("tomato", "transparent"), or a BorderMode number for non-constant modes (Reflect/Wrap/Replicate). Omit for transparent padding (default). An unreadable colour throws — it never falls back to a fill you did not ask for.
border? BorderMode BorderMode override when color is a Pixel or string (default: Constant).
Returns

this

this

padBottom()
padBottom(size, color?): this;

Pad bottom only. Shorthand for pad(0, size, 0, 0, color). Updates height.

Parameters
Parameter Type
size number
color? PadColor
Returns

this

padLeft()
padLeft(size, color?): this;

Pad left side only. Shorthand for pad(0, 0, size, 0, color). Updates width.

Parameters
Parameter Type
size number
color? PadColor
Returns

this

padRight()
padRight(size, color?): this;

Pad right side only. Shorthand for pad(0, 0, 0, size, color). Updates width.

Parameters
Parameter Type
size number
color? PadColor
Returns

this

padTop()
padTop(size, color?): this;

Pad top only. Shorthand for pad(size, 0, 0, 0, color). Updates height.

Parameters
Parameter Type
size number
color? PadColor
Returns

this

pencilSketch()
pencilSketch(strength): this;

Pencil-sketch effect — the classic "colour dodge" trick from Photoshop / GIMP tutorials: convert to greyscale, invert, blur, then divide the original grey by 1 - blurred_inverted. Soft natural pencil strokes with the smooth tonal shading graphite gives, no edge-detection harshness. Output is greyscale; alpha preserved.

strength is the Gaussian sigma applied to the inverted copy:

  • 5–10 → tight, fine pencil hatching feel
  • 15–30 → broader graphite shading
  • < 1 → almost identical to plain greyscale
Parameters
Parameter Type Description
strength number Gaussian sigma for the inverted-blur step.
Returns

this

this

Example
img.pencilSketch(20);                    // soft graphite
img.gaussianBlur(0.5)
   .pencilSketch(15)
   .brightness(1.05);                    // pre-soften then sketch
perceptualHash()
perceptualHash(): string;

64-bit perceptual hash (dHash) as 16 hex characters.

Returned as a string, not a number: 64 bits do not survive a JavaScript number, and a silently rounded hash is worse than none. Compare two of them with Engine.hashDistance().

Survives rescaling, moderate sharpening and a different JPEG quality. It does not survive fine repeating patterns — dither, halftone screens, tiny text — where downscaling to 9x8 aliases; see docs/sampling_and_rotation.md for why that class of image is hard.

Returns

string

pixelate()
pixelate(size, region?): this;

Pixelate — mosaic effect.

Parameters
Parameter Type Description
size number Block size in pixels.
region? Rect | RegionOpts -
Returns

this

this

posterize()
posterize(levels, region?): this;

Posterize — reduce to N discrete color levels per channel.

Parameters
Parameter Type Description
levels number Number of levels (e.g. 4 = retro poster look).
region? Rect | RegionOpts -
Returns

this

this

premultiplyAlpha()
premultiplyAlpha(region?): this;

Pre-multiply RGB by alpha in-place. Rewrites (R, G, B, A) as (R*A, G*A, B*A, A) so fully-transparent pixels carry RGB=0 and partially-transparent pixels are scaled correspondingly.

Use after rmbg/autocrop and before any geometry op that uses bilinear interpolation (warpPerspective, stampAt, resize, …) to stop background-colour bleed at silhouette edges. The original RGB on transparent pixels (where rmbg only flipped α to 0) would otherwise leak in at every soft edge.

Parameters
Parameter Type
region? Rect | RegionOpts
Returns

this

this

Example
Engine.loadImage(INPUT)
  .rmbg()
  .premultiplyAlpha()
  .stampAt(bg, quad, 0.85);
protect()
protect(opts?): object;

Protect a photo as yours: sign an entry in your register and mark the photo with its id — in one step. Requires OpticScript Pro and a key set up once (Engine.provenance.init("Your Name"), or mlcos-protect init --name "Your Name").

The entry holds your name, the time, the title, the photo's perceptual hash and size, and is signed with your key. The id in the mark is the fingerprint of that signed entry — so anyone with the photo and the entry can check, without any secret, that this id belongs to exactly this entry (see verify).

Not available on a shared server (the register belongs to a person, not to the server account).

Parameters
Parameter Type Description
opts? { strength?: number; title?: string; } -
opts.strength? number Mark strength, default 0.5 (see mark).
opts.title? string Shown in the register; usually the file name.
Returns

object

{ id, owner, fingerprint, title, created, psnr }

Name Type
created string
fingerprint string
id string
owner string
psnr number
title string
Example
const photo = Engine.loadImage(INPUT);
const r = photo.protect({ title: "lake.jpg" });
photo.save(OUTPUT, { quality: 92 });
console.log(`registered as ${r.id}`);
readMark()
readMark(): object;

Look for a mark written by mark. Free in every edition.

found is only true when the sync pattern and the checksum agree — an unmarked image returns found: false, never a made-up id. sync is the strength of the sync peak (unmarked photos stay below about 6; found needs 7 and a matching checksum). orientation says whether the image was turned or mirrored since it was marked ("none", "rotated 90 cw", "rotated 180", "rotated 90 ccw", "mirrored horizontally", "mirrored vertically", "transposed", "anti-transposed"). cell is the tile cell size found, in pixels of this image — compared with the cell at marking time (short side / 128) it shows how much the image was scaled.

Returns

object

{ found, id, text, sync, cell, orientation } — id as 16 hex digits, text when the id was a short text.

Name Type
cell number
found boolean
id string | null
orientation string
sync number
text string | null
Example
const m = Engine.loadImage(INPUT).readMark();
console.log(m.found ? `marked: ${m.text ?? m.id}` : "no mark found");
readWatermark()
readWatermark(): object;

Read a watermark back out.

confidence says how unanimous the blocks were: 1.0 means every block agreed, 0.5 means the read was a coin flip. An unmarked image still returns a payload — noise decoded as if it were data — so always check confidence rather than trusting the string. Above roughly 0.9 the mark is really there; below 0.75 it is not.

Returns

object

{ payload, confidence }. Does not modify the image.

Name Type
confidence number
payload string
Example
const { payload, confidence } = Engine.loadImage(INPUT).readWatermark();
if (confidence > 0.9) console.log(`came from build ${payload}`);
else                  console.log("no watermark found");
rectFromCenter()
rectFromCenter(
   at, 
   w, 
   h): Rect;

Build a normalized rect centered at a pixel position.

Parameters
Parameter Type Description
at Px Center position in pixel space.
w number Width in pixels.
h number Height in pixels.
Returns

Rect

[x, y, w, h] all ∈ [0..1]

rectFromPixels()
rectFromPixels(
   at, 
   w, 
   h): Rect;

Build a normalized [x, y, w, h] rect from a pixel origin and pixel dimensions.

Parameters
Parameter Type Description
at Px Top-left corner in pixel space.
w number Width in pixels.
h number Height in pixels.
Returns

Rect

[x, y, w, h] all ∈ [0..1]

rectFull()
rectFull(): Rect;

Return a normalized rect covering the full image: [0, 0, 1, 1].

Returns

Rect

removeWhiteBackground()
removeWhiteBackground(opts?): this;

Knock out the bright background of a sketch / lineart by driving alpha from luminance: pixels brighter than threshold become transparent, darker pixels keep their colour and alpha. RGB is preserved — only alpha changes.

Use this for black-on-white sketches where U²-Net (rmbg tool) struggles. Deterministic, no model — just luminance maths.

Parameters
Parameter Type Description
opts? { feather?: number; threshold?: number; } -
opts.feather? number Width of the soft edge in luminance units. 0 = hard binary cut, 0.05 = ~13/255 ramp. Default 0.05.
opts.threshold? number Luminance cut-off ∈ [0..1]. Default 0.95 (≈ 242/255). Anything brighter goes transparent.
Returns

this

this

resize()
resize(
   w, 
   h, 
   interp?): this;

Resize to exact dimensions. Updates width and height.

Shrinking averages rather than point-samples: the filter widens with the reduction factor, so a pattern finer than the target grid turns into its average instead of moiré. Enlarging reduces to ordinary bilinear interpolation. Both run over premultiplied alpha, so a soft transparent edge keeps its colour.

Sampling is half-pixel-centred, the same convention PIL, OpenCV and ffmpeg use. Before 2.3.0 it was corner-aligned, which shifted the image by half a pixel and stretched it slightly — output from 2.3.0 on will not be byte-identical to older runs.

Parameters
Parameter Type Description
w number Target width in pixels.
h number Target height in pixels.
interp? Interp Interp.Nearest keeps hard pixel edges (no filtering — the right choice for pixel art and for nearest-neighbour upscaling). Anything else uses the filtered path described above; default is Interp.Bilinear.
Returns

this

this

Example
// Thumbnail without moiré on fine detail
Engine.loadImage("screenshot.png").resize(320, 180).save("thumb.png");

// Pixel art, scaled up without smoothing
Engine.loadImage("sprite.png").resize(256, 256, Interp.Nearest).save("big.png");
rotate()
rotate(angle): this;

Rotate in-place. Canvas stays the same size — corners are clipped. Use rotateExpand() to expand the canvas instead.

Parameters
Parameter Type Description
angle number Angle in degrees (counter-clockwise).
Returns

this

this

rotateExpand()
rotateExpand(angle): this;

Rotate and expand the canvas so the full image fits without clipping. Updates width and height.

Parameters
Parameter Type Description
angle number Rotation angle in degrees (counter-clockwise).
Returns

this

this

saturation()
saturation(factor, region?): this;

Adjust color saturation.

Parameters
Parameter Type Description
factor number 0.0 = greyscale, 1.0 = unchanged, > 1.0 = vivid.
region? Rect | RegionOpts -
Returns

this

this

save()
save(path, opts?): this;

Save the image to a file on disk (or to a registered output slot).

Parameters
Parameter Type Description
path string Absolute / relative filesystem path (e.g. "out.png", "result.jpg") OR a //!OUTPUT: slot name in CLI mode OR mlcartifact://name.png to store it as an artifact (the new id goes to the log — use Engine.writeArtifact when the script needs it). On a server, file paths are refused.
opts? { format?: "png" | "jpeg" | "webp" | "avif"; lossy?: boolean; quality?: number; } Optional encoder hints. Quality is in [0..100] and applies to lossy encoders (JPEG, lossy WebP, AVIF). lossy flips WebP between lossless (default) and lossy. format ("png"
opts.format? "png" | "jpeg" | "webp" | "avif" -
opts.lossy? boolean -
opts.quality? number -
Returns

this

this for chaining.

Throws

If the write fails.

img.save("out.png");                                       // PNG, lossless
img.save("out.webp");                                      // WebP, lossless (default)
img.save("out.webp", { quality: 82 });                     // WebP, lossy q=82
img.save("out.webp", { lossy: true, quality: 90 });
img.save("out.jpg",  { quality: 95 });                     // JPEG q=95
img.save(OUTPUT, { format: "webp", quality: 82 });         // playground: override UI select
saveTIFF()
saveTIFF(keyOrPath): this;

Save this image as a one-page TIFF. Distinct from save() because TIFF goes through a separate code path (the tiff crate, not the image crate's TIFF feature) so we can share the encoder with AnimationHandle.saveTIFF() and offer multi-page TIFF later from the same call shape.

RGBA8, uncompressed — preserves alpha exactly, no quality loss. Use this when you need a single-page TIFF for a workflow that expects them (scientific imaging, archival, certain printers). For animation-style multi-page TIFFs see AnimationHandle.saveTIFF.

Parameters
Parameter Type Description
keyOrPath string Output slot or filesystem path.
Returns

this

sepia()
sepia(region?): this;

Apply sepia tone (warm brownish tint).

Parameters
Parameter Type
region? Rect | RegionOpts
Returns

this

this

setMeta()
setMeta(key, value): this;

Set a top-level metadata key. Value can be any JSON-serialisable type: primitive, array, or nested object. Overwrites whatever was there before.

To update only some fields of a nested object, use mergeMeta or do the JS spread yourself: img.setMeta("exif", { ...img.getMeta("exif"), iso: 200 }).

On save, exif.* lands as EXIF in PNG, JPEG, WebP and AVIF. Every other top-level key goes into an XMP packet — in PNG, JPEG and WebP only: AVIF carries the EXIF part but drops free-form keys. Save as PNG/JPEG/WebP if they must survive.

Parameters
Parameter Type
key string
value unknown
Returns

this

this

Example
img.setMeta("dataset", "ct-batch-1");
img.setMeta("anon", { patientId: "OPAQUE-7F3A" });
setPixel()
setPixel(at, pixel): this;

Write a single pixel. Values are clamped to [0..1].

Parameters
Parameter Type Description
at Px Pixel position, e.g. px(100, 50).
pixel Pixel A Pixel object.
Returns

this

this for chaining.

shadowsHighlights()
shadowsHighlights(opts, region?): this;

Shadow / highlight recovery — tonal adjustment for photo editing.

Separates the image into dark and bright regions using luminance weighting and applies independent lift/suppression to each tonal range:

  • shadows > 0 — lifts dark areas (opens under-exposed shadows).
  • shadows < 0 — crushes shadows (deepens blacks).
  • highlights < 0 — recovers blown highlights (pulls back bright areas).
  • highlights > 0 — brightens already-bright areas.

Both values are additive lifts ∈ [−1..1] weighted by (1 − L)² and L² respectively, so the midtones are barely touched.

Parameters
Parameter Type Description
opts { highlights?: number; shadows?: number; } -
opts.highlights? number Highlight lift ∈ [−1..1]. Default 0 (no change).
opts.shadows? number Shadow lift ∈ [−1..1]. Default 0 (no change).
region? Rect | RegionOpts -
Returns

this

this

Example
img.shadowsHighlights({ shadows: 0.25, highlights: -0.15 });
sharpen()
sharpen(amount, region?): this;

Unsharp-mask sharpening.

Parameters
Parameter Type Description
amount number Sharpening strength. 1.0 = moderate, > 2.0 = strong.
region? Rect | RegionOpts -
Returns

this

this

sobel()
sobel(region?): this;

Sobel edge detection. Outputs edge magnitudes as greyscale.

Parameters
Parameter Type
region? Rect | RegionOpts
Returns

this

this

solarize()
solarize(threshold, region?): this;

Solarize — invert pixels above threshold (darkroom effect).

Parameters
Parameter Type Description
threshold number Inversion threshold ∈ [0..1].
region? Rect | RegionOpts -
Returns

this

this

stampAt()
stampAt(
   src, 
   dstPoints, 
   factor?, 
   mode?, 
   interp?, 
   border?): this;

Perspective-place src onto this image at four destination corners.

Edge anti-aliasing is automatic. The warp samples src per destination pixel; with the default WarpBorder.Transparent each out-of-bounds bilinear tap contributes transparent, so a tilted quad's silhouette interpolates opaque↔transparent and comes out smooth. Pass border = WarpBorder.Clamp for hard edges (edge- replicate); in that case pre-pad src with a 1-px transparent border (src.pad(1,1,1,1)) if you still want a soft rim.

Parameters
Parameter Type Description
src ImageHandle The image to stamp.
dstPoints [Px, Px, Px, Px] Four canvas-space corners [TL, TR, BR, BL] as Px values.
factor? number Blend weight ∈ [0..1] (default 1.0).
mode? BlendMode Blend mode (default BlendMode.Over).
interp? Interp Interpolation mode (default Bilinear).
border? WarpBorder Out-of-bounds mode (default WarpBorder.Transparent).
Returns

this

this

stampGrid()
stampGrid(
   content, 
   grid, 
   interp?, 
   factor?): this;

Forward complement of warpGrid: project content onto this image through an R×C forward mesh.

Whereas warpGrid treats the nodes as the source UV at each uniform destination grid point (inverse warp — useful for cylindrical/squeeze effects on a single image), stampGrid treats them as the destination position of each uniform source grid point — the intuitive interpretation when you place a grid on a target surface in the editor and want a second image to land inside it. Areas of the base canvas not covered by any mesh cell keep their original pixels.

Internally, each of the (rows-1)×(cols-1) cells is rasterised as a bilinear patch (inverse-bilinear per pixel inside the cell's bbox). Adjacent cells share boundary nodes so they abut without gaps.

Parameters
Parameter Type Default value Description
content ImageHandle undefined Image to stamp onto this canvas.
grid { cols: number; nodes: Record<string, number[]>; rows: number; } undefined WarpGrid object. nodes are normalised to this image's size (x = nodes[r][c*2] * this.width).
grid.cols number undefined -
grid.nodes Record<string, number[]> undefined -
grid.rows number undefined -
interp number 1 0 = nearest-neighbour, 1 = bilinear (default).
factor number 1.0 Global opacity multiplier ∈ [0..1] (default 1).
Returns

this

this

Example
// Project a poster onto a curved monitor in `scene`:
const scene = Engine.loadImage(REFERENCE);
const poster = Engine.loadImage(CONTENT);
scene.stampGrid(poster, { rows: ROWS, cols: COLS, nodes: NODES });
scene.save(OUTPUT);
stats()
stats(): object;

Summary numbers for this image, without comparing it to anything.

mean, min, max and stddev are per channel, in R G B A order, on the [0..1] range.

sharpness is the variance of the Laplacian on luminance — the standard blur measure from autofocus. It has no absolute scale: use it to compare images, not as a threshold on its own. A flat fill gives 0; fine detail gives a large number.

Returns

object

Stats object. Does not modify the image.

Name Type
clippedHigh number[]
clippedLow number[]
max number[]
mean number[]
min number[]
sharpness number
stddev number[]
transparentFraction number
Example
const s = Engine.loadImage(INPUT).stats();
if (s.stddev[0] < 0.01) console.log("this screenshot is basically blank");
if (s.mean[3] < 1)      console.log("has transparency");
swirl()
swirl(
   center, 
   radius, 
   angle): this;

Swirl / twirl distortion — pixels rotate around a center point.

Parameters
Parameter Type Description
center Px Center of the swirl in pixel space, e.g. px(img.width/2, img.height/2).
radius number Swirl radius in pixels. Pixels beyond this are unaffected.
angle number Maximum rotation angle in degrees at the center.
Returns

this

this

threshold()
threshold(value, region?): this;

Binary threshold — pixels above value become white, below become black.

Parameters
Parameter Type Description
value number Threshold ∈ [0..1].
region? Rect | RegionOpts -
Returns

this

this

tint()
tint(
   color, 
   strength, 
   region?): this;

Tint the image with a color.

Parameters
Parameter Type Description
color Color -
strength number Blend strength ∈ [0..1].
region? Rect | RegionOpts -
Returns

this

this

toSVG()
toSVG(optsOrKey?): string;

Trace the image to SVG using vtracer.

Parameters
Parameter Type Description
optsOrKey string | SVGOptions Either an SVGOptions config object, or a string output key (writes the SVG to that output slot and also returns it). All options are optional — omitted fields use the defaults from SVGOptions.
Returns

string

unsetMeta()
unsetMeta(key): this;

Delete a metadata key, nested ones by dotted path ("exif.Artist"). No-op if the key was already absent. Useful for stripping individual fields before export: img.unsetMeta("exif.GPSInfo") drops the capture location — every GPS field, however the reader stored them. (Before 2.11.1 dotted paths did nothing and the location stayed in the file.)

Parameters
Parameter Type
key string
Returns

this

this

verify()
verify(opts?): object;

Check a photo: read the mark and look its id up in your register — and in any exported registers you pass. Free in every edition.

verdict sums it up in a sentence. With an entry found, signatureValid says whether the entry is untouched, and hashDistance how far the photo is from the registered original (0–10: the same picture; more: a crop or an edit).

Parameters
Parameter Type Description
opts? { registers?: string[]; } -
opts.registers? string[] Paths of exported registers (another photographer's), .json list or one entry per line.
Returns

object

Name Type
created? string
fingerprint? string
found boolean
hashDistance? number
id? string
owner? string
registered? boolean
signatureValid? boolean
sync number
text? string | null
title? string
verdict string
Example
const v = Engine.loadImage(INPUT).verify();
console.log(v.verdict);
if (v.registered) console.log(`${v.owner}, ${v.created}, key ${v.fingerprint}`);
vignette()
vignette(strength, region?): this;

Apply vignette (edge darkening).

Parameters
Parameter Type Description
strength number 0.0 = none, 1.0 = strong dark border.
region? Rect | RegionOpts -
Returns

this

this

warpGrid()
warpGrid(grid, interp?): this;

Apply a Catmull-Rom mesh warp defined by a WarpGrid object.

The grid's sparse nodes map is expanded to a dense flat array before being passed to the Rust engine. Rows absent from nodes are filled with their identity (evenly-spaced) UV positions.

Parameters
Parameter Type Default value Description
grid { border?: WarpBorder; cols: number; nodes: Record<string, number[]>; rows: number; } undefined WarpGrid object (e.g. the WARPGRID global injected from the @warpgrid script block).
grid.border? WarpBorder undefined -
grid.cols number undefined -
grid.nodes Record<string, number[]> undefined -
grid.rows number undefined -
interp number 1 0 = nearest-neighbour, 1 = bilinear (default).
Returns

this

this

warpPerspective()
warpPerspective(
   dw, 
   dh, 
   matrix, 
   interp?, 
   border?): this;

Perspective (homography) warp. Updates width and height.

The transform is src→dst (forward): for each source pixel it says where that pixel lands in the destination grid.

matrix accepts either:

  • a Mat3 instance — the recommended path. Build one with Matrix.Mat3.estimateSimilarity/Affine/Homography(...), or any gl-matrix construction. It is column-major; this wrapper transposes it to the engine's row-major layout for you.
  • a raw 9-element number[] — treated as row-major [a,b,c, d,e,f, g,h,i] and passed straight through.

Out-of-bounds edges are anti-aliased by default: WarpBorder.Transparent lets each bilinear tap that lands outside the source contribute transparent, so a tilted quad's silhouette interpolates opaque↔transparent instead of staircasing. Pass WarpBorder.Clamp to replicate the edge texel instead (hard edges).

Parameters
Parameter Type Description
dw number Output width in pixels.
dh number Output height in pixels.
matrix number[] | Mat3 Mat3 or row-major number[9], src→dst forward.
interp? Interp Interpolation method (default: Bilinear = 1).
border? WarpBorder Out-of-bounds mode (default: WarpBorder.Transparent).
Returns

this

this

warpPolar()
warpPolar(innerR?, outSize?): this;

Polar coordinate warp — maps the image onto a ring/donut shape. The image's X axis maps to angle (0°→360°) and Y axis maps to radius. Creates a "film roll" or "planet" effect. Updates width and height.

Parameters
Parameter Type Description
innerR? number Inner ring radius as fraction of outer radius ∈ [0..1]. Default: 0.3.
outSize? number Output image side length in pixels. 0 = auto (max of input dims).
Returns

this

this

watermark()
watermark(payload, opts?): this;

Embed an invisible watermark into the pixels.

Unlike EXIF or a C2PA seal, this survives what platforms do to images: it lives in the pixel data, so stripping metadata does not remove it. Useful for tracing where a screenshot came from — a build number, a tenant id, a licence key.

The payload is carried by the ratio of two mid-frequency DCT coefficients per 8×8 block, repeated across the whole image, and read back by majority vote. Capacity is 8 characters — this holds an identifier, not a message. Longer input is truncated.

Measured on 09.09.2026 against a real photograph at 1280×720. Strength trades visibility against survival, and the default sits in the middle:

strength invisibility survives JPEG down to
0.3 46.5 dB q=90
0.5 (default) 45.3 dB q=70
0.8 43.4 dB q=50
1.0 42.3 dB q=40

Most platforms re-encode somewhere around q=75–85, which the default clears.

It does not survive scaling or cropping. Resizing shifts the 8×8 grid and the payload is lost. Watermark last, after any resize. For photos published on the web, use mark — it survives resizing, cropping and re-encoding.

It is also not a tamper seal: a watermark cannot prove an image is unmodified, and anyone who knows the scheme can write one.

Parameters
Parameter Type Description
payload string Up to 8 characters. Longer is truncated.
opts? { strength?: number; } -
opts.strength? number 0..1, default 0.5. Higher survives harsher compression and is easier to see.
Returns

this

this

Throws

When the image is smaller than 64 blocks (roughly 64×64).

Example
// Stamp every screenshot with the build it came from
const shot = Engine.loadImage(INPUT);
shot.resize(1280, 720);   // resize FIRST
shot.watermark("b1421");  // then mark
shot.save(OUTPUT);
wave()
wave(
   amplitudeX, 
   amplitudeY, 
   freqX, 
   freqY): this;

Sinusoidal wave distortion.

Parameters
Parameter Type Description
amplitudeX number Horizontal wave amplitude in pixels.
amplitudeY number Vertical wave amplitude in pixels.
freqX number Horizontal wave frequency (cycles per image width).
freqY number Vertical wave frequency (cycles per image height).
Returns

this

this