sharp icon indicating copy to clipboard operation
sharp copied to clipboard

How does sharp infer the height if the width is provided?

Open watjurk opened this issue 11 months ago • 7 comments

Question about an existing feature

What are you trying to achieve?

I am writing a webpack loader to optimize images, some of that work includes resizing. For dev purposes I need to know up-front what will be the image dimensions. As the user can specify the width only I need to infer the height. After looking at sharp source code I found these lines: https://github.com/lovell/sharp/blob/7c631c0787915416e20a567a039516e99c81c42d/src/pipeline.cc#L176-L184

I thought that this must be it so I reimplemented this logic in my loader:

// https://github.com/lovell/sharp/blob/7c631c0787915416e20a567a039516e99c81c42d/src/pipeline.cc#L176-L184
function inferHeight(currentWidth, currentHeight, newWidth) {
	const xFactor = currentWidth / newWidth;
	const newHeightNotRounded = currentHeight / xFactor;
	const newHeight = Math.round(newHeightNotRounded);
	return newHeight;
}

Unfortunately there must be more to it. For the target width of 496 and current height of 2731 and current width of 4096 this code inferred 331. While sharp resized the image to 330. It's not the rounding semantics and it's not the numerical precision, I check these already.

So the question stands, how does sharp auto-scale the images?

Best, Wiktor

Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this question

I've created a smal example with the image that behaves in this way (other images work with the inference code). Running index.js in this repo, will run sharp and this inference function: The link to a reproduction repo

Please provide sample image(s) that help explain this question

The sample image

watjurk avatar Mar 24 '25 00:03 watjurk

Did you see the fastShrinkOnLoad option of resize?

lovell avatar Mar 24 '25 11:03 lovell

No I did not, I will take a look. But I don't understand how that would help me.

watjurk avatar Mar 24 '25 16:03 watjurk

Param Type Default Description
[options.fastShrinkOnLoad] Boolean true Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension.

When you use fastShrinkOnLoad with JPEG input, some of the resizing might be carried out via mozjpeg/libjpeg-turbo DCT scaling feature, which rounds down. If you want predictable calculations, set fastShinkOnLoad to false to always use sharp/libvips for all of the resizing, which rounds to nearest.

lovell avatar Mar 26 '25 14:03 lovell

I set fastShrinkOnLoad to false in the reproduction repo. Unfortunately the result is the same. The height inferred by JS is 331, while sharp resizes to 330.

watjurk avatar Mar 28 '25 13:03 watjurk

Here's what's happening:

$ vipsheader image.jpeg
image.jpeg: 4096x2731 uchar, 3 bands, srgb, jpegload
$ vips jpegload image.jpeg x.v --shrink 4
$ vipsheader x.v
x.v: 1024x682 uchar, 3 bands, srgb, jpegload
$ vipsthumbnail x.v -s 496x
$ vipsheader tn_x.jpg
tn_x.jpg: 496x330 uchar, 3 bands, srgb, jpegload

So, the image is initially shrunk by a factor of 4 during the shrink-on-load stage:

\frac{2731}{4} = 682.75 \approx 682

(this rounding happens because JPEG always strictly rounds down)

The resized height (682) is then used in the next calculation:

682 \times {\frac{496}{1024}} = 330.34375 \approx 330

This would result in a thumbnail of 496x330 pixels.

I'm not sure if there's an easy fix for this. I suppose it could also confuse users who expect these commands to produce the same output dimensions.

$ vipsheader image.jpeg
image.jpeg: 4096x2731 uchar, 3 bands, srgb, jpegload
$ vips copy image.jpeg x.v
$ vipsthumbnail x.v -s 496x
$ vipsheader tn_x.jpg
tn_x.jpg: 496x331 uchar, 3 bands, srgb, jpegload
$ vipsthumbnail image.jpeg -s 496x
$ vipsheader tn_image.jpg
tn_image.jpg: 496x330 uchar, 3 bands, srgb, jpegload

kleisauke avatar Mar 28 '25 15:03 kleisauke

When in the JPEG format the height is always rounded down (floored), would stretching the image by using an explicit height and with mode: 'fill' then degrade the image quality of the resulting, displayed JPEG image (if even so slightly)?

strarsis avatar Apr 20 '25 20:04 strarsis