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 ofcover()— 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::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::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.