zudo-image-tweaker
GitHub repository

Type to search...

to open search from anywhere

composite

Corner-anchored overlay compositing, single-shot or batched over a cartesian product of bases and overlays.

What it does

@takazudo/zudo-image-tweaker/composite places a square overlay (a watermark, badge, or logo) in a corner of a base image, sized as a percentage of the base's shorter side, plus a batch helper that composites every base against every overlay with bounded concurrency.

Exports

function compositeOverlay(
  base: ImageInput,
  overlay: ImageInput,
  options: CompositeOverlayOptions,
): Promise<CompositeOverlayResult>;
function compositeBatch(
  bases: CompositeBatchItem[],
  overlays: CompositeBatchItem[],
  options: CompositeBatchOptions,
): Promise<CompositeBatchResult[]>;

type ImageInput = SharpInput;
type CompositePosition = 'bottom-right';

SharpInput is sharp's own accepted-input type (string | Buffer | ArrayBuffer | Uint8Array | Uint8ClampedArray).

  • compositeOverlay — a single base/overlay composite. The overlay is always resized to a 1:1 square ('cover' fit) at widthPercent of the base's shorter side (min(width, height)), then anchored to a corner with a paddingPercent gap. The result buffer is always re-encoded as PNG, regardless of the base image's original format.

  • compositeBatch — runs compositeOverlay over the cartesian product of a bases list and an overlays list (every base × every overlay), processed with a bounded-concurrency worker pool.

Options

CompositeOverlayOptions

NameTypeDefaultEffect
widthPercentnumberrequiredOverlay width as a percentage (0-100) of the base image's shorter side (min(width, height)). The overlay is always forced to a 1:1 square at this size.
paddingPercentnumberrequiredGap from the base image's edges, as a percentage of the base's shorter side — used for both the horizontal and vertical offset, even against a non-square base.
positionCompositePosition'bottom-right'Corner to anchor to. 'bottom-right' is the only value that ships in v1.

CompositeOverlayResult: { buffer, width, height }.

CompositeBatchOptions

Extends CompositeOverlayOptions with:

NameTypeDefaultEffect
concurrencynumber4Max composites running at once. Must be a finite number >= 1.

CompositeBatchItem: { ref: string, image: ImageInput }ref is a caller-chosen identifier echoed back in the result (e.g. a filename or slug). CompositeBatchResult: { baseRef, overlayRef, result: CompositeOverlayResult }.

Example

import { compositeBatch } from '@takazudo/zudo-image-tweaker/composite';

const results = await compositeBatch(
  [
    { ref: 'photo-1', image: './photo-1.jpg' },
    { ref: 'photo-2', image: './photo-2.jpg' },
  ],
  [{ ref: 'watermark', image: './watermark.png' }],
  { widthPercent: 15, paddingPercent: 4 },
);

for (const { baseRef, overlayRef, result } of results) {
  console.log(`${baseRef} + ${overlayRef}: ${result.width}x${result.height}`);
}

Caveats

  • EXIF orientation is baked into the base before compositing (via sharp's .rotate()), and width/height in the result reflect the displayed dimensions. sharp's own metadata() always reports the as-stored (pre-rotation) dimensions regardless, so orientation values 5-8 (which involve a 90deg turn) need a manual width/height swap internally to match what .rotate() actually produces — compositeOverlay handles this for you.

  • Overlay size and padding are both derived from the base's shorter side (min(width, height)) on both axes, even for a non-square base — this keeps the badge from overflowing the base's narrow axis on a strongly landscape or portrait image.

  • A widthPercent that rounds to a non-positive pixel size throws, rather than silently producing a 0px (or negative) overlay.

  • An overlay/padding combination that doesn't fit within the base throws a descriptive error instead of producing a cryptic sharp composite failure or a badge positioned outside the canvas.

  • Only 'bottom-right' ships in v1. Passing any other position value throws at runtime (the implementation is an exhaustive switch, ready for more corners later).

  • compositeBatch validates concurrency eagerly. A non-finite or < 1 value throws immediately instead of silently defaulting or hanging.

  • Output is always PNG. compositeOverlay re-encodes the composited result as PNG regardless of the base image's input format (JPEG, WebP, etc.) — CompositeOverlayResult.buffer is a PNG buffer.