Keep a side computed for a size with one side 0 at 1 or more (closes #283)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
The side was rounded down, so for a thin source it came out at 0, and libvips made the size square: an 8192x1 source at 100x0 came out at 100x100, at 1300x0 with fit=cover libvips asked for about 39 GB, and an 8192x2 source at 100x0 answered 500. fit=contain and fit=inside work both sides out again from the smaller scale and keep each at 1 or more as well, as a 1x8192 source at 0x100 answered 500 there. Model: opus-5-5
This commit is contained in:
@@ -26,13 +26,21 @@ resolved. The disk cache is now size-bounded with LRU eviction
|
|||||||
|
|
||||||
# Next Step
|
# Next Step
|
||||||
|
|
||||||
P1: security: a size with one side `0` whose side computed from the source's
|
P2: security: per-origin rate limiting
|
||||||
proportions rounds down to 0 comes out square, can make libvips ask for about 39
|
|
||||||
GB (an 8192x1 source at `1300x0`), or answers 500
|
|
||||||
(https://git.eeqj.de/sneak/pixa/issues/283)
|
|
||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 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
|
||||||
@@ -1000,7 +1008,6 @@ GB (an 8192x1 source at `1300x0`), or answers 500
|
|||||||
|
|
||||||
# Future Steps
|
# Future Steps
|
||||||
|
|
||||||
- P2: security: per-origin rate limiting
|
|
||||||
- P2: HTTP response handling
|
- P2: HTTP response handling
|
||||||
- Last-Modified headers
|
- Last-Modified headers
|
||||||
- P2: configuration
|
- P2: configuration
|
||||||
|
|||||||
@@ -311,8 +311,10 @@ func (p *ImageProcessor) Process(
|
|||||||
|
|
||||||
// targetDimensions calculates the output dimensions for a requested size,
|
// targetDimensions calculates the output dimensions for a requested size,
|
||||||
// scaling proportionally when only one dimension is given and keeping the
|
// scaling proportionally when only one dimension is given and keeping the
|
||||||
// original dimensions when both are zero. A side scaled proportionally can
|
// original dimensions when both are zero. A side scaled proportionally is
|
||||||
// come out above MaxOutputDimension, which is ErrOutputTooLarge.
|
// rounded down but kept at 1 or more, as for a thin image it would round
|
||||||
|
// down to 0, a side of no pixels. It can come out above MaxOutputDimension,
|
||||||
|
// which is ErrOutputTooLarge.
|
||||||
func targetDimensions(size Size, origWidth, origHeight int) (int, int, error) {
|
func targetDimensions(size Size, origWidth, origHeight int) (int, int, error) {
|
||||||
width, height := size.Width, size.Height
|
width, height := size.Width, size.Height
|
||||||
|
|
||||||
@@ -322,10 +324,10 @@ func targetDimensions(size Size, origWidth, origHeight int) (int, int, error) {
|
|||||||
return origWidth, origHeight, nil
|
return origWidth, origHeight, nil
|
||||||
case width == 0:
|
case width == 0:
|
||||||
// Only height specified: calculate width proportionally
|
// Only height specified: calculate width proportionally
|
||||||
width = origWidth * height / origHeight
|
width = max(1, origWidth*height/origHeight)
|
||||||
case height == 0:
|
case height == 0:
|
||||||
// Only width specified: calculate height proportionally
|
// Only width specified: calculate height proportionally
|
||||||
height = origHeight * width / origWidth
|
height = max(1, origHeight*width/origWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
if width > MaxOutputDimension || height > MaxOutputDimension {
|
if width > MaxOutputDimension || height > MaxOutputDimension {
|
||||||
@@ -479,13 +481,16 @@ func (p *ImageProcessor) resize(
|
|||||||
return cropCentreAndResize(img, width, height)
|
return cropCentreAndResize(img, width, height)
|
||||||
|
|
||||||
case FitContain:
|
case FitContain:
|
||||||
// Resize to fit within dimensions, maintaining aspect ratio
|
// Resize to fit within dimensions, maintaining aspect ratio. Scaled
|
||||||
|
// by the smaller scale, the short side of a thin image can 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 := min(scaleW, scaleH)
|
scale := min(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))
|
||||||
|
|
||||||
return img.Thumbnail(newW, newH, vips.InterestingNone)
|
return img.Thumbnail(newW, newH, vips.InterestingNone)
|
||||||
|
|
||||||
@@ -503,8 +508,8 @@ func (p *ImageProcessor) resize(
|
|||||||
scaleW := float64(width) / float64(imgW)
|
scaleW := float64(width) / float64(imgW)
|
||||||
scaleH := float64(height) / float64(imgH)
|
scaleH := float64(height) / float64(imgH)
|
||||||
scale := min(scaleW, scaleH)
|
scale := min(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))
|
||||||
|
|
||||||
return img.Thumbnail(newW, newH, vips.InterestingNone)
|
return img.Thumbnail(newW, newH, vips.InterestingNone)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user