Crop the centre before resizing for fit=cover (closes #282) #284
@@ -46,6 +46,16 @@ P2: security: per-origin rate limiting
|
||||
way, and so is an IPv6 address without brackets, which needed a signature
|
||||
before. Underscores are kept because `allowlist_hosts` accepts host names
|
||||
holding them.
|
||||
- 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 the README says how a source URL with a query is written into an
|
||||
image URL, and the image route accepts it (closes #255): the query follows the
|
||||
source path after `%3F`. pixa now reads the image URL's path decoded once, so
|
||||
|
||||
@@ -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