Keep each side at 1 or more with fit=outside too

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 02:05:57 +00:00
parent 52c77ec4ba
commit 778d3b24bd
3 changed files with 31 additions and 17 deletions
+11 -3
View File
@@ -536,8 +536,13 @@ Where:
- `query` — source query string as under Routes, empty string if none, with `%` - `query` — source query string as under Routes, empty string if none, with `%`
and `:` written as in `path`; neither then holds a `:`, so path `/a:b` with no and `:` written as in `path`; neither then holds a `:`, so path `/a:b` with no
query and path `/a` with query `b:` are signed differently query and path `/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
@@ -550,7 +555,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
+14 -11
View File
@@ -35,6 +35,20 @@ P2: security: per-origin rate limiting
(closes #293). A host written in upper and in lower case, or a source URL with (closes #293). A host written in upper and in lower case, or a source URL with
no path and with the path `/`, made two cache keys and two upstream requests no path and with the path `/`, made two cache keys and two upstream requests
for one image. for one image.
- 2026-10-16 a size with one side `0` keeps the side computed from the source's
proportions at 1 or more (closes #283): it was rounded down, so for a thin
source it came out at 0, and libvips then made the size square. An 8192x1
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.
`fit=contain` and `fit=inside` work both sides out again from the smaller of
the two scales and `fit=outside` from the larger, and each keeps both sides at
1 or more too, so a 1x8192 source at `0x100` with `fit=contain`, and a 49x8192
source at `0x100` or a 49x49 source at `1x1` with `fit=outside`, no longer
answer 500. An 8192x1 source at `1300x0` now comes out at 1300x1, and the
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-16 the source URL a request fetches is built with `net/url` from the - 2026-10-16 the source URL a request fetches is built with `net/url` from the
source host, path and query, not joined as text (closes #288). The source source host, path and query, not joined as text (closes #288). The source
path, decoded, is fetched percent-encoded again, so `100%25.png` fetches path, decoded, is fetched percent-encoded again, so `100%25.png` fetches
@@ -48,17 +62,6 @@ P2: security: per-origin rate limiting
another way, such as with `%2F` in the path, whatever else the path holds, or another way, such as with `%2F` in the path, whatever else the path holds, or
with a user name and password. It also answers 400 for a source path holding a with a user name and password. It also answers 400 for a source path holding a
`..` segment, as `/v1/image/` refuses one. `..` segment, as `/v1/image/` refuses one.
- 2026-10-16 a size with one side `0` keeps the side computed from the source's
proportions at 1 or more (closes #283): it was rounded down, so for a thin
source it came out at 0, and libvips then made the size square. An 8192x1
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.
`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`
no longer answers 500 there. An 8192x1 source at `1300x0` now comes out at
1300x1, and the 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.
- 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 {