diff --git a/README.md b/README.md index b84f4a1..39870ea 100644 --- a/README.md +++ b/README.md @@ -512,8 +512,13 @@ Where: - `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 `/a` with query `b:` are signed differently -- `width` — requested width in pixels, `0` for original -- `height` — requested height in pixels, `0` for original +- `width` — requested width in pixels, or `0`: the width is then worked out from + 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` 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 @@ -526,7 +531,10 @@ Where: with 400 - `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 - 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 RFC 4648) with the trailing `=` padding kept, 44 characters in all. pixa diff --git a/TODO.md b/TODO.md index 36878a6..1372f3b 100644 --- a/TODO.md +++ b/TODO.md @@ -36,11 +36,14 @@ P2: security: per-origin rate limiting 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. + 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-15 a thin source requested with `fit=cover` no longer makes libvips 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 diff --git a/internal/imageprocessor/imageprocessor.go b/internal/imageprocessor/imageprocessor.go index 0c315ea..7b83a41 100644 --- a/internal/imageprocessor/imageprocessor.go +++ b/internal/imageprocessor/imageprocessor.go @@ -514,13 +514,16 @@ func (p *ImageProcessor) resize( return img.Thumbnail(newW, newH, vips.InterestingNone) 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() scaleW := float64(width) / float64(imgW) scaleH := float64(height) / float64(imgH) scale := max(scaleW, scaleH) - newW := int(float64(imgW) * scale) - newH := int(float64(imgH) * scale) + newW := max(1, int(float64(imgW)*scale)) + newH := max(1, int(float64(imgH)*scale)) // The dimension that exceeds the target is held to the maximum too if newW > MaxOutputDimension || newH > MaxOutputDimension {