Stamp images at perspective quads (stampAt)

Demonstrates the stampAt helper by placing multiple copies of an image onto a background using 4-point perspective quadrilaterals, allowing for skew and rotation.

INPUT
INPUT — Stamp images at perspective quads (stampAt)
OUTPUT
OUTPUT — Stamp images at perspective quads (stampAt)
JavaScript
// Stamp images at perspective quads (stampAt)
// stamp_at.js
//!INPUT: INPUT
//!OUTPUT: OUTPUT
//!PARAM: WIDTH:integer=400,min=200,max=2000
//!PARAM: HEIGHT:integer=300,min=150,max=2000
//!PARAM: INSET:integer=10,min=0,max=80
//!PARAM: TILT:number=20,min=0,max=80
//!PARAM: ALPHA:number=0.85,min=0,max=1

// Smoke-test for ImageHandle.stampAt(). Loads one image, creates a
// white WIDTH×HEIGHT background, then stamps the image twice — once
// in each half — at perspective quads computed from the canvas size.
//
// The four corners of each stamp are derived from WIDTH/HEIGHT,
// INSET (margin from canvas + center seam) and TILT (how strongly
// the top edge skews against the bottom). Tweak the params live in
// the Playground to see how stampAt handles different perspective
// envelopes.

const src = Engine.loadImage(INPUT);

// White background.
const bg = Engine.createImage(1, 1);
bg.setPixel(px(0, 0), new Pixel(1, 1, 1, 1));
bg.resize(WIDTH, HEIGHT);

// Each half spans from INSET to (mid - INSET/2) horizontally,
// INSET to (HEIGHT - INSET) vertically. TILT skews the top edge
// outward (left half: top tilts right; right half: mirror).
const mid    = WIDTH / 2;
const halfW  = mid - INSET - INSET / 2;
const top    = INSET;
const bottom = HEIGHT - INSET;

// Stamp 1 — left half, top edge tilts right.
{
  const x0 = INSET;
  const x1 = INSET + halfW;
  bg.stampAt(src, [
    px(x0,        top + TILT),    // TL  (drops a bit so the top tilts right)
    px(x1,        top),           // TR
    px(x1,        bottom),        // BR
    px(x0,        bottom - TILT), // BL  (mirror of TL)
  ]);
}

// Stamp 2 — right half, top edge tilts left (mirror), with ALPHA
// opacity + Porter-Duff Over so it sits on the white background.
{
  const x0 = mid + INSET / 2;
  const x1 = WIDTH - INSET;
  bg.stampAt(src, [
    px(x0,        top),           // TL
    px(x1,        top + TILT),    // TR  (drops a bit, top tilts left)
    px(x1,        bottom - TILT), // BR  (mirror of TR)
    px(x0,        bottom),        // BL
  ], ALPHA, 10 /* Porter-Duff Over */);
}

Engine.saveImage(bg, OUTPUT);

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