square
Five square-crop and square-pad transforms for turning any aspect ratio into a square.
What it does
@takazudo/ is a small toolbox of five ways to turn an arbitrary-aspect-ratio image into a square, from a plain crop through to content-aware trim-then-pad. Every function bakes EXIF orientation into the pixels first, so cropping/padding always operates on the displayed dimensions.
Exports
function cropToSquare(input: Buffer, opts?: CropToSquareOptions): Promise<SquareResult>;
function padToSquare(input: Buffer, opts?: PadToSquareOptions): Promise<SquareResult>;
function padToSquareCentered(
input: Buffer,
opts?: PadToSquareCenteredOptions,
): Promise<SquareResult>;
function insetOnSquare(input: Buffer, opts?: InsetOnSquareOptions): Promise<SquareResult>;
function trimPadSquare(input: Buffer, opts?: TrimPadSquareOptions): Promise<SquareResult>;
interface SquareResult {
buffer: Buffer;
width: number;
height: number;
}cropToSquare— crops to the shorter side;anchorpicks which edge of the longer axis to keep.padToSquare— extends canvas to square, padding only the narrow axis's leading edge (content becomes right/bottom-aligned). A square input is a no-op re-encode.padToSquareCentered— extends canvas to square, padding both edges of the narrow axis symmetrically (content stays centered, no crop).insetOnSquare— shrinks a (typically already-square) image's content and centers it on a same-size canvas, leaving a proportional border. Insets the whole input, so a pre-existing border stacks with the new one.trimPadSquare— trims the near-background border down to the content bounding box first, then pads a square canvas leaving an exact margin — the fix forinsetOnSquare's border-stacking behavior on pre-framed images.
Options
| Name | Type | Default | Effect | Used by |
|---|---|---|---|---|
anchor | 'top' | 'center' | 'bottom' | 'center' | Which edge of the crop axis to keep. See Anchor semantics below. | cropToSquare |
background | Color (from sharp) | { r: 255, g: 255, b: 255, alpha: 1 } | Fill color for padded area / canvas. | padToSquare, padToSquareCentered, insetOnSquare, trimPadSquare |
margin | number (0 ≤ margin < 0.5) | 0.1 | Fractional border to leave around the content. | insetOnSquare, trimPadSquare |
threshold | number (0-255, integer) | 245 | A pixel counts as background when all of R, G, B are >= this value. | trimPadSquare |
Example
import { readFile } from 'node:fs/promises';
import { trimPadSquare } from '@takazudo/zudo-image-tweaker/square';
const source = await readFile('./product.png');
const { buffer, width, height } = await trimPadSquare(source, {
margin: 0.08,
background: { r: 255, g: 255, b: 255, alpha: 1 },
});
console.log(`${width}x${height}, ${buffer.length} bytes`);Caveats
Anchor semantics
cropToSquare's anchor names the edge of the crop axis, not an absolute screen position — and the crop axis itself flips with orientation:
Portrait (
h > w, crop is vertical):topkeeps the top rows,bottomkeeps the bottom rows.Landscape (
w > h, crop is horizontal):topkeeps the left columns,bottomkeeps the right columns.
Other caveats
A translucent
backgroundrequires an alpha channel on the source, whichpadToSquare/padToSquareCenteredensure internally (ensureAlpha()) before extending — otherwise sharp would silently flatten a partial-alphabackgroundto opaque.insetOnSquarestacks with any existing border; it insets the whole input every time. A 10% inset of an image already framed at 10% yields roughly an 18% border, not 10%. UsetrimPadSquarewhen the final margin must be exact regardless of input framing.trimPadSquaretreats an all-background input as blank — it emits abackground-colored square (sized to the larger original side) rather than throwing, since no content bounding box could be found.Only
insetOnSquareresamples content; the other four don't (beyond the initial orientation bake).cropToSquare/padToSquare/padToSquareCenteredcrop or pad 1:1 with no resizing of the kept pixels;insetOnSquarealways resizes content into its inset box.trimPadSquarealso composites the trimmed content 1:1 — no resampling — but the canvas size it pads to is derived from the computed margin, not the original dimensions.