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
|
way, and so is an IPv6 address without brackets, which needed a signature
|
||||||
before. Underscores are kept because `allowlist_hosts` accepts host names
|
before. Underscores are kept because `allowlist_hosts` accepts host names
|
||||||
holding them.
|
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
|
- 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
|
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
|
source path after `%3F`. pixa now reads the image URL's path decoded once, so
|
||||||
|
|||||||
@@ -475,8 +475,8 @@ func (p *ImageProcessor) resize(
|
|||||||
) error {
|
) error {
|
||||||
switch fit {
|
switch fit {
|
||||||
case FitCover, "":
|
case FitCover, "":
|
||||||
// Resize and crop to fill exact dimensions (default)
|
// Crop the centre and resize to fill exact dimensions (default)
|
||||||
return img.Thumbnail(width, height, vips.InterestingCentre)
|
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
|
||||||
@@ -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
|
const defaultQuality = 85
|
||||||
|
|
||||||
// encode encodes an image to the specified format. govips gives pixa no way
|
// encode encodes an image to the specified format. govips gives pixa no way
|
||||||
|
|||||||
Reference in New Issue
Block a user