lovell / sharp

High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images. Uses the libvips library.
https://sharp.pixelplumbing.com
Apache License 2.0
28.58k stars 1.28k forks source link

Upscaling with nearest neighbor produces blurry image rather than scaled pixels #4158

Closed clubside closed 3 weeks ago

clubside commented 3 weeks ago

Possible bug

Is this a possible bug in a feature of sharp, unrelated to installation?

If you cannot confirm both of these, please open an installation issue instead.

Are you using the latest version of sharp?

What is the output of running npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp?

  System:
    OS: Windows 10 10.0.19045
    CPU: (8) x64 Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
    Memory: 15.48 GB / 31.85 GB
  Binaries:
    Node: 22.3.0 - G:\nodejs\node.EXE
    Yarn: 1.22.22 - G:\nodejs\yarn.CMD
    npm: 10.8.1 - G:\nodejs\npm.CMD
    pnpm: 9.5.0 - G:\nodejs\pnpm.CMD
  npmPackages:
    sharp: ^0.33.4 => 0.33.4

Does this problem relate to file caching?

The default behaviour of libvips is to cache input files, which can lead to EBUSY or EPERM errors on Windows. Use sharp.cache(false) to switch this feature off.

Does this problem relate to images appearing to have been rotated by 90 degrees?

Images that contain EXIF Orientation metadata are not auto-oriented. By default, EXIF metadata is removed.

What are the steps to reproduce?

const avatar = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAAXNSR0IArs4c6QAAAA5lWElmTU0AKgAAAAgAAAAAAAAA0lOTAAAATklEQVQIHWN00ZdlwAaYsAmCxHBKsAAlq3wd0fS1bd7PBBS1qp+78HoORA7IAHKBgkxyihJAoeOX44AMIAIygFwgg/HO3DI0cyBcnJYDAE6jE2C2ld0zAAAAAElFTkSuQmCC'
const dataUri = avatar.match(/^data:.+\/(.+);base64,(.*)$/)
const dataBuffer = Buffer.from(dataUri[2], 'base64')
const avatarSharp = sharp(dataBuffer)
avatarSharp.resize(256, { kernel: sharp.kernel.nearest })
avatarSharp.extract({ left: 0, top: 0, width: 256, height: 256 })
avatarSharp.png()
await avatarSharp.toFile('avatar.png')

What is the expected behaviour?

That each pixel is just scaled with no interpolation, especially in this case where 8x8 to 256x256 is proportional.

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

The code is above.

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

Here is the source image: Original Image Here is the sharp upscale with nearest neighbor: Sharp Upscale Here is the Photoshop upscale with nearest neighbor: Sharp Upscale

clubside commented 3 weeks ago

Not providing a height was causing an error I didn't spot. If I force 256x256 using avatarSharp.resize(256, 256, { kernel: sharp.kernel.nearest }) everything works great. How do I set the options to proportional height while still passing the kernel option?

clubside commented 3 weeks ago

Sorry, I see in the docs I can set the height to null which solves everything. Sorry for the trouble and thanks again for a wonderful library!

lovell commented 3 weeks ago

Alternatively, perhaps move the width into the options to make it less ambiguous:

- avatarSharp.resize(256, { kernel: sharp.kernel.nearest })
+ avatarSharp.resize({ width: 256, kernel: sharp.kernel.nearest })