sharp icon indicating copy to clipboard operation
sharp copied to clipboard

withoutReduction: true - does not stop from downscaling images

Open sugoidesune opened this issue 2 years ago • 4 comments

What are the steps to reproduce?

An image of size 500x500 will get resized to 400 despite the withoutReduction: true flag.

sharp('large.png')
.resize(400, 400, {
    withoutReduction: true
  }).toFile('small.png');

What is the expected behaviour?

Only smaller images should be resized (upscaled) to 400px Trying to resize a larger image than 400x400 should due to withoutReduction: true should result in an image with with the dimensions of the original image, effectively not performing any resizing.

version:

"sharp": "^0.31.3",

sugoidesune avatar Mar 18 '23 20:03 sugoidesune

In this example, the image is not resized. The default fit is cover and the target dimensions are 400x400 so the image is cropped to cover these. Perhaps you meant to specify a fit of contain?

https://sharp.pixelplumbing.com/api-resize

cover: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.

contain: Preserving aspect ratio, contain within both provided dimensions

I guess the documentation for withoutEnlargement and withoutReduction could be updated to clarify that a crop might still occur even when resizing does not.

lovell avatar Mar 19 '23 10:03 lovell

Well lets compare it to withoutEnlargement: true Source Image size: 500x500

sharp('500x500.png').resize(1000, 1000, {
    withoutEnlargement: true
  }).toFile('small.png');

Resizing skipped as we do not want to enlarge - output 500x500 ✅

The dimensions of the output are NOT 1000x1000 they are the size of the original image dimensions - as we did not want to enlarge.

sharp('500x500.png').resize(200, 200, {
    withoutReduction: true
  }).toFile('small.png');

Resizing not skipped despite not wanting to reduce - (expected 500x500) output 200x200 ❌

The dimensions of the output ARE 200x200 instead of the original image dimensions - even though we did not want to Reduce.

I would expect withoutReduction and withoutEnlargement to to be a way to do conditional resizing. The docs also mention outputBuffer contains JPEG image data of at least 200 pixels wide and 200 pixels high while maintaining aspect ratio After resizing to 200px with withoutReduction: true, indicating that large images will have more pixels.

If the current behavior is desired it should be done by cropping, not resizing.

✅I just tried v0.30.0 and the behaviour is as expected, so it must have been a change after.

sugoidesune avatar Mar 19 '23 12:03 sugoidesune

The words "reduction" and "enlargement" refer to scaling and not cropping.

The idea of fit=cover is to ensure there is "enough" image to cover the target dimensions. If both required to and allowed to it will scale an image; if required to it will crop an image. It will not embed or pad an image with a provided background colour.

In the first example you've provided, the use of withoutEnlargement: true prevents it from scaling up the image. When it comes to the cropping decision, the image is already smaller than both target dimensions, so nothing else occurs.

In the second example, the use of withoutReduction: true prevents it from scaling down the image. When it comes to the cropping decision, the image is larger than the target dimensions so a crop occurs. This image with this combination of options is almost equivalent to the CSS object-fit: none; directive.

Given the examples provided, I suspect a fit of either outside or contain might be what you're looking for.

lovell avatar Mar 24 '23 14:03 lovell

fit: outside works. It returns an unmodifined file in both (upscale, downscale) cases. fit: contain doesn't work. It returns the bottom right quarter of the image, in the top left corner with the rest black, when downscaling.

Therefore the default fit setting should be 'outside' for resizing. As it provides the same behaviour for upscaling as it does for downscaling while keeping the conditional resizing function of withoutReduction and withoutEnlargement. This also keeps the regular down and upscaling feature. So far I see no reason why it shouldn't be the default setting.

I understand that implementation wise the concepts of cropping and resizing combined with different fit settings are blurry. But from a user perspective - extending the canvas and cutting a part of the image off, are cropping functions. While resizing is strictly about the image dimensions.

And the current behaviour of downsampling with withoutReduction: true is a crop. which is not at all expected.

sugoidesune avatar Mar 24 '23 18:03 sugoidesune