Keep each side at 1 or more with fit=outside too
check / check (push) Canceled after 0s

fit=outside works both sides out again from the larger scale, and a side
scaled to 1 could come out just under 1 and round down to 0, which
libvips refuses: a 49x8192 source at 0x100 and a 49x49 source at 1x1
answered 500. README.md now says what fit=outside does, with the 8192x1
source at 100x0 that comes out at 8192x1, and how a 0 side is worked
out from the source's proportions.

Model: opus-5-5
This commit is contained in:
2026-10-16 01:11:20 +00:00
parent c590d0f8ca
commit ab35342671
3 changed files with 25 additions and 11 deletions
+11 -3
View File
@@ -512,8 +512,13 @@ Where:
- `query` — source query string, empty string if none, with `%` and `:` written - `query` — source query string, empty string if none, with `%` and `:` written
as in `path`; neither then holds a `:`, so path `/a:b` with no query and path as in `path`; neither then holds a `:`, so path `/a:b` with no query and path
`/a` with query `b:` are signed differently `/a` with query `b:` are signed differently
- `width` — requested width in pixels, `0` for original - `width` — requested width in pixels, or `0`: the width is then worked out from
- `height` — requested height in pixels, `0` for original the source's proportions and the requested height, rounded down but at least
1, or is the source's own width when the height is `0` too (the `orig` size)
- `height` — requested height in pixels, or `0`: the height is then worked out
from the source's proportions and the requested width, rounded down but at
least 1, or is the source's own height when the width is `0` too (the `orig`
size)
- `format` — output format, one of those listed under Routes, with `original` - `format` — output format, one of those listed under Routes, with `original`
signed as `orig`, `jpg` as `jpeg`, and no format as `jxl`, so a URL with no signed as `orig`, `jpg` as `jpeg`, and no format as `jxl`, so a URL with no
format has the signature of the same URL ending in `.jxl`; `auto` is signed as format has the signature of the same URL ending in `.jxl`; `auto` is signed as
@@ -526,7 +531,10 @@ Where:
with 400 with 400
- `fit` — the URL's `fit` query parameter (cover, contain, fill, inside, - `fit` — the URL's `fit` query parameter (cover, contain, fill, inside,
outside), or `cover` when the URL has no `fit`; a request whose `fit` is outside), or `cover` when the URL has no `fit`; a request whose `fit` is
anything else, an empty `fit=` included, is refused with 400 anything else, an empty `fit=` included, is refused with 400. `outside` scales
the image to cover the requested size, keeping its proportions, so it can come
out larger than that size on one side: an 8192x1 source at `100x0`, which is
100x1 by its proportions, comes out at 8192x1
The URL's `sig` is the HMAC-SHA256 result in base64url (the URL-safe alphabet of The URL's `sig` is the HMAC-SHA256 result in base64url (the URL-safe alphabet of
RFC 4648) with the trailing `=` padding kept, 44 characters in all. pixa RFC 4648) with the trailing `=` padding kept, 44 characters in all. pixa
+8 -5
View File
@@ -36,11 +36,14 @@ P2: security: per-origin rate limiting
source at `100x0` came out at 100x100, and at `1300x0` with `fit=cover` made source at `100x0` came out at 100x100, and at `1300x0` with `fit=cover` made
libvips ask for about 39 GB at once; an 8192x2 source at `100x0` answered 500. libvips ask for about 39 GB at once; an 8192x2 source at `100x0` answered 500.
`fit=contain` and `fit=inside` work both sides out again from the smaller of `fit=contain` and `fit=inside` work both sides out again from the smaller of
the two scales, and keep each at 1 or more too, so a 1x8192 source at `0x100` the two scales and `fit=outside` from the larger, and each keeps both sides at
no longer answers 500 there. An 8192x1 source at `1300x0` now comes out at 1 or more too, so a 1x8192 source at `0x100` with `fit=contain`, and a 49x8192
1300x1, and the process running it alone peaked at about 34 MiB resident. With source at `0x100` or a 49x49 source at `1x1` with `fit=outside`, no longer
`fit=outside` the image is scaled to cover the size, so that source at `100x0` answer 500. An 8192x1 source at `1300x0` now comes out at 1300x1, and the
comes out at 8192x1. process running it alone peaked at about 34 MiB resident. With `fit=outside`
the image is scaled to cover the size, so that source at `100x0` comes out at
8192x1. `README.md` now says this under Signature Specification, and says how
a `0` side is worked out where it said `0` meant the original.
- 2026-10-15 a thin source requested with `fit=cover` no longer makes libvips - 2026-10-15 a thin source requested with `fit=cover` no longer makes libvips
build a huge image (closes #282): libvips' thumbnail scaled the whole source build a huge image (closes #282): libvips' thumbnail scaled the whole source
to cover the requested size and copied that into memory before it cut out the to cover the requested size and copied that into memory before it cut out the
+6 -3
View File
@@ -514,13 +514,16 @@ func (p *ImageProcessor) resize(
return img.Thumbnail(newW, newH, vips.InterestingNone) return img.Thumbnail(newW, newH, vips.InterestingNone)
case FitOutside: case FitOutside:
// Resize so smallest dimension fits, may exceed target on other dimension // Resize so smallest dimension fits, may exceed target on other
// dimension. A side scaled to 1 can come out just under 1 in
// floating point, as 49 times 1/49 does, and round down to 0, which
// libvips refuses as a width, so each side is at least 1.
imgW, imgH := img.Width(), img.Height() imgW, imgH := img.Width(), img.Height()
scaleW := float64(width) / float64(imgW) scaleW := float64(width) / float64(imgW)
scaleH := float64(height) / float64(imgH) scaleH := float64(height) / float64(imgH)
scale := max(scaleW, scaleH) scale := max(scaleW, scaleH)
newW := int(float64(imgW) * scale) newW := max(1, int(float64(imgW)*scale))
newH := int(float64(imgH) * scale) newH := max(1, int(float64(imgH)*scale))
// The dimension that exceeds the target is held to the maximum too // The dimension that exceeds the target is held to the maximum too
if newW > MaxOutputDimension || newH > MaxOutputDimension { if newW > MaxOutputDimension || newH > MaxOutputDimension {