libvips' thumbnail with a centre crop scales the whole source to cover the requested size and copies that into memory before it crops: 8192x64 at 8192x8192 made it allocate about 24 GiB, and 8192x1 failed with 500. resize now cuts the centre of the source to the requested size's proportions first and resizes that, so no image larger than the source or the output is made. The crop is the same centre as before. TODO.md says so. Model: opus-5-5
This commit is contained in:
@@ -30,6 +30,16 @@ P2: security: per-origin rate limiting
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-15 a thin source requested with `fit=cover` no longer makes libvips
|
||||
build a huge image (closes #282): libvips scaled the whole source to cover the
|
||||
requested size and copied that into memory before it cut out the centre, so an
|
||||
8192x64 source at 8192x8192 made it allocate about 24 GiB, and an 8192x1
|
||||
source answered 500. The image processor now cuts the centre of the source to
|
||||
the requested size's proportions first and resizes that, so no image larger
|
||||
than the source or the output is made; the crop is the same centre as before.
|
||||
With the runtime image's libvips and JPEG output at 8192x8192, resident memory
|
||||
peaked at about 44 MiB for the 8192x64 source, 40 MiB for the 8192x1 one, and
|
||||
117 MiB for a 4000x3000 one, which took 373 MiB before.
|
||||
- 2026-10-15 `max_concurrent_processing` defaults to half the CPUs pixa sees,
|
||||
rounded down, and at least 1 (closes #242): it defaulted to one image per CPU,
|
||||
and a container with no CPU limit sees every CPU of the host, so on a 48-CPU
|
||||
|
||||
@@ -475,8 +475,8 @@ func (p *ImageProcessor) resize(
|
||||
) error {
|
||||
switch fit {
|
||||
case FitCover, "":
|
||||
// Resize and crop to fill exact dimensions (default)
|
||||
return img.Thumbnail(width, height, vips.InterestingCentre)
|
||||
// Crop the centre and resize to fill exact dimensions (default)
|
||||
return cropCentreAndResize(img, width, height)
|
||||
|
||||
case FitContain:
|
||||
// Resize to fit within dimensions, maintaining aspect ratio
|
||||
@@ -530,6 +530,37 @@ func (p *ImageProcessor) resize(
|
||||
}
|
||||
}
|
||||
|
||||
// cropCentreAndResize cuts img to its centre, the largest area it holds with
|
||||
// the proportions of width x height, and resizes that to width x height.
|
||||
// This is the crop libvips' thumbnail makes with InterestingCentre, but
|
||||
// libvips scales the whole image to cover the size and copies the result
|
||||
// into memory before it crops, and for a thin image that is far larger than
|
||||
// the image or the output: 8192x64 at 8192x8192 is 1048576x8192, about 24
|
||||
// GiB. Cropping first makes no image larger than img or the output.
|
||||
func cropCentreAndResize(img *vips.ImageRef, width, height int) error {
|
||||
imgW, imgH := img.Width(), img.Height()
|
||||
|
||||
// An image wider than the requested proportions keeps its height and
|
||||
// loses width, any other keeps its width and loses height; the side
|
||||
// shortened is rounded to the nearest pixel and is at least 1
|
||||
cropW, cropH := imgW, imgH
|
||||
if imgW*height > imgH*width {
|
||||
cropW = max(1, (imgH*width+height/2)/height)
|
||||
} else {
|
||||
cropH = max(1, (imgW*height+width/2)/width)
|
||||
}
|
||||
|
||||
// Half of what is cut comes off each side
|
||||
left, top := (imgW-cropW)/2, (imgH-cropH)/2 //nolint:mnd // half the cut
|
||||
|
||||
err := img.ExtractArea(left, top, cropW, cropH)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return img.ThumbnailWithSize(width, height, vips.InterestingNone, vips.SizeForce)
|
||||
}
|
||||
|
||||
const defaultQuality = 85
|
||||
|
||||
// encode encodes an image to the specified format. govips gives pixa no way
|
||||
|
||||
Reference in New Issue
Block a user