Resize & Fit

The Chainsaw API for resize, crop, fit, and aspect-ratio operations. For the conceptual catalogue (vendor-neutral, with comparison tables), see Resize Modes.

crop() is a Spatie-style alias of cover() — same signature, same behaviour. Pick whichever name reads better at the call site.

width(int $width)

Baseline Supported on all 12 providers.
BUCFCNGUIMIMIMIXIPIVTHWS

Scale to a specific width, preserving aspect ratio.

$image->width(400);

height(int $height)

Baseline Supported on all 12 providers.
BUCFCNGUIMIMIMIXIPIVTHWS

Scale to a specific height, preserving aspect ratio.

$image->height(300);

scale(float $factor)

Limited availability Only supported on Cloudinary, Imagine and Intervention.
BUCFCNGUIMIMIMIXIPIVTHWS

Multiply both dimensions by the same factor. 0.5 halves the image; 2 doubles it.

$image->scale(0.5);
$image->scale(2.0);

Only Cloudinary exposes a native factor primitive among the URL providers. On the others a pre-dispatch DimensionNormalizer re-expresses scale() as a width() when the source dimensions are known (a MetadataResolver is configured on the factory). width() re-derives the height from the source aspect ratio, so the emitted size can differ from a raw two-axis multiply by a pixel of rounding. Without resolved dimensions it throws UnsupportedManipulator.

contain(int $width, int $height, bool $noUpscale = false)

Baseline Supported on all 12 providers.
BUCFCNGUIMIMIMIXIPIVTHWS

Scale to fit inside a box, preserving aspect ratio. Output is ≤ box on each axis. noUpscale: true caps the output at source dimensions (the old scaleDown() behaviour).

$image->contain(400, 400);
$image->contain(400, 400, noUpscale: true);

pad(int $width, int $height, string $background = '#ffffff', bool $noUpscale = false)

Widely available Unsupported on Bunny.
BUCFCNGUIMIMIMIXIPIVTHWS

Resize to fit inside a box (preserving aspect), then fill the empty area with a background colour. Output dimensions equal the box exactly.

$image->pad(400, 400, '#2a2a2a');
$image->pad(400, 400, '#fff', noUpscale: true);

stretch(int $width, int $height)

Widely available Unsupported on Bunny.
BUCFCNGUIMIMIMIXIPIVTHWS

Force exact dimensions, ignoring aspect ratio. Distorts the source.

$image->stretch(300, 300);

cover(int $width, int $height, CropPosition|Anchor|null $anchor = null, bool $noUpscale = false)

Baseline Supported on all 12 providers.
BUCFCNGUIMIMIMIXIPIVTHWS
Anchor variants
Anchor::compass() BUCFCNGUIMIMIMIXIPIVTHWS 12/12
Anchor::focal() BUCFCNGUIMIMIMIXIPIVTHWS 8/12
Anchor::smart() BUCFCNGUIMIMIMIXIPIVTHWS 9/12
Anchor::face() BUCFCNGUIMIMIMIXIPIVTHWS 6/12
Imgproxy
Pro mode only.
Anchor::entropy() BUCFCNGUIMIMIMIXIPIVTHWS 5/12
Anchor::attention() BUCFCNGUIMIMIMIXIPIVTHWS 3/12
Anchor::object() BUCFCNGUIMIMIMIXIPIVTHWS 3/12
Cloudinary
Renders as `g_auto:<class>`.
Imgproxy
Pro mode only (`obj:<class>`).

Scale the source so it covers the entire box, preserving aspect, then crop the overflow on the long axis. Output dimensions equal the box exactly. The crop position is configurable via the anchor argument: a CropPosition enum value (the 9 compass points) or any Anchor (focal point, smart, face, entropy, attention, object class).

use Timber\Chainsaw\Anchor;
use Timber\Chainsaw\Enum\CropPosition;

$image->cover(800, 600);                                   // centred (default)
$image->cover(800, 600, CropPosition::TopLeft);            // compass anchor
$image->cover(800, 600, Anchor::focal(0.7, 0.4));          // focal point
$image->cover(800, 600, Anchor::focal(0.7, 0.4, zoom: 1.5));// focal + zoom
$image->cover(800, 600, Anchor::smart());                  // saliency / auto
$image->cover(800, 600, Anchor::entropy());                // entropy crop
$image->cover(800, 600, Anchor::attention());              // attention crop
$image->cover(800, 600, Anchor::face());                   // face detection
$image->cover(800, 600, Anchor::object('dog'));            // object-class detection
$image->cover(800, 600, noUpscale: true);                  // never enlarge

Anchor::smart() enables content-aware smart cropping — supported by most cloud providers. Providers without smart-crop support throw UnsupportedManipulator.

Anchor::object() is supported by Cloudinary (g_auto:<class>) and imgproxy Pro (obj:<class>); other providers throw.

crop() — alias of cover()

For PHP-ecosystem familiarity (Spatie-style), crop() is a one-liner alias. Identical signature, identical behaviour.

$image->crop(800, 600);                                    // same as cover(800, 600)
$image->crop(800, 600, CropPosition::Top);
$image->crop(800, 600, Anchor::face());

manualCrop(int $width, int $height, int $x, int $y)

Baseline Supported on all 12 providers.
BUCFCNGUIMIMIMIXIPIVTHWS

Crop at absolute pixel coordinates. Specify the crop window’s size and top-left offset.

$image->manualCrop(400, 300, 100, 50);  // 400×300 box at (100, 50)

cropToRatio(string|Ratio $ratio, CropPosition|Anchor|null $anchor = null)

Limited availability Only supported on Bunny, Cloudinary, Gumlet, Imagekit, Imagine, Imgix and Intervention.
BUCFCNGUIMIMIMIXIPIVTHWS
Anchor variants
Anchor::compass() BUCFCNGUIMIMIMIXIPIVTHWS 12/12
Anchor::focal() BUCFCNGUIMIMIMIXIPIVTHWS 8/12
Anchor::smart() BUCFCNGUIMIMIMIXIPIVTHWS 9/12
Anchor::face() BUCFCNGUIMIMIMIXIPIVTHWS 6/12
Imgproxy
Pro mode only.
Anchor::entropy() BUCFCNGUIMIMIMIXIPIVTHWS 4/12
Anchor::attention() BUCFCNGUIMIMIMIXIPIVTHWS 2/12
Anchor::object() BUCFCNGUIMIMIMIXIPIVTHWS 3/12
Cloudinary
Renders as `g_auto:<class>`.
Imgproxy
Pro mode only (`obj:<class>`).

Crop to a target aspect ratio. Output dimensions are derived from the source — the largest rectangle of the target ratio that fits inside the source is cut.

use Timber\Chainsaw\Anchor;
use Timber\Chainsaw\Geometry\Ratio;

$image->cropToRatio('16:9');                              // string form
$image->cropToRatio(new Ratio(16, 9));                    // typed form
$image->cropToRatio('16:9', Anchor::smart());             // smart-anchored

Cloudinary, ImageKit and imgix express this natively. On Cloudflare, imgproxy, Imagor, Thumbor and wsrv.nl a pre-dispatch DimensionNormalizer re-expresses it as a cover() once the source dimensions are known (a MetadataResolver is configured on the factory); without them it throws UnsupportedManipulator.

padToRatio(string|Ratio $ratio, ?string $background = null)

Limited availability Only supported on Cloudinary, Imagine and Intervention.
BUCFCNGUIMIMIMIXIPIVTHWS

Extend the canvas to a target aspect ratio with a background colour (white when omitted). Source pixels are preserved at their original size; only the surrounding canvas grows. When it follows a pad() and no colour is given, the pad’s background carries over instead of resetting to white; same for cropToRatio() after a cover() — an omitted anchor inherits the cover’s.

$image->padToRatio('16:9');
$image->padToRatio('16:9', '#000000');

Cloudinary and imgix express this natively. Everywhere else a pre-dispatch DimensionNormalizer re-expresses it as a pad() once the source dimensions are known (a MetadataResolver is configured on the factory); without them it throws UnsupportedManipulator. ImageKit is included here on purpose: its aspect-ratio parameter takes only one axis while its pad mode needs both, so an ar-only pad silently crops instead of padding — resolving to an explicit pad() box is the only correct emission.

trim(int $tolerance = 10, ?string $color = null)

Widely available Unsupported on Bunny and Imagine.
BUCFCNGUIMIMIMIXIPIVTHWS

Remove border pixels that match a reference colour. By default the reference colour is auto-detected from the image corners; pass $color to specify it explicitly.

$image->trim();                    // auto-detect border colour
$image->trim(30);                  // higher tolerance (0..100)
$image->trim(10, '#ffffff');       // trim white borders explicitly

Tolerance is 0 (exact match only) to 100 (aggressive). Chainsaw maps the value to each provider’s native range.

Explicit colour is supported by Cloudflare, Cloudinary, imgix, imgproxy, and wsrv.nl. Thumbor, Imagor, and Intervention ignore it and auto-detect.

dpr(float $ratio)

Limited availability Only supported on Cloudflare, Gumlet and Imgix.
BUCFCNGUIMIMIMIXIPIVTHWS
Cloudflare / Imgix
Native `dpr=` URL parameter.
Cloudinary
Native `dpr_` URL parameter.
Imagekit
Native `dpr-` URL parameter.
Imagor / Imgproxy / Intervention / Thumbor / Wserv
Computed: dimensions multiplied at URL-build time. User-visible bytes are identical, but CDN-cached URLs differ.

Apply a device-pixel-ratio multiplier to output dimensions. Typically used with responsive srcset at 1x, 2x, 3x.

$image->width(800)->dpr(2); // requests a 1600px-wide variant

DPR multiplies output-space pixel values: the size manipulators (width, height, cover, pad, contain, stretch, scale) and pixel-unit overlays — a 4px border, a pixelate block size, a watermark’s pixel padding/size all scale under dpr(), so the composition looks identical at every density. Combine with a size anchor to take effect.

Two things deliberately do not scale. manualCrop coordinates live in source pixel space — the source gains no pixels at dpr(2), so the selected region is identical at every density. Effect intensities (blur, sharpen) are the caller’s judgment call and pass through untouched; use percent units for decorations that must stay relative to the canvas. Aspect-ratio manipulators (padToRatio, cropToRatio) resolve to concrete boxes first and scale as boxes.

The multiplier is emitted natively where the CDN’s own parameter matches those semantics (Cloudflare, Gumlet, and Imgix dpr=). Everywhere else — Cloudinary, ImageKit, Imgproxy, Imagor, Thumbor, Wserv, and the local providers (Intervention, Imagine) — a pre-dispatch DprNormalizer rewrites the chain: the user-visible output is identical; only the encoded URL differs. Cloudinary and ImageKit are rewritten on purpose: Cloudinary’s dpr_ rescales the whole chain including the crop window and blur, and ImageKit’s dpr- composes incoherently with its own chained transformations (both measured live).

For browser-side density selection across multiple resolutions, build a srcset with width descriptors via widths() and let the browser pick — no Client Hints headers required, works on every backend. See Responsive Images.