Skip to content

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

Open

Description

@watjurk

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:

sharp/src/pipeline.cc

Lines 176 to 184 in 7c631c0

} else if (baton->width > 0) {
// Fixed width
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
if (baton->canvas == Canvas::IGNORE_ASPECT) {
targetResizeHeight = baton->height = inputHeight;
} else {
// Auto height
yfactor = xfactor;
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / yfactor));

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

Activity

  1. lovell commented on Mar 24, 2025

    @lovell
    Owner

    Did you see the fastShrinkOnLoad option of resize?

  2. watjurk commented on Mar 24, 2025

    @watjurk
    Author

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

  3. lovell commented on Mar 26, 2025

    @lovell
    Owner
    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.

  4. watjurk commented on Mar 28, 2025

    @watjurk
    Author

    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.

  5. kleisauke commented on Mar 28, 2025

    @kleisauke
    Contributor

    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
  6. strarsis commented on Apr 20, 2025

    @strarsis

    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)?

  7. Repository owner deleted a comment from on Apr 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions