Sharp: Resize one image multiple times

Created on 1 May 2017  路  4Comments  路  Source: lovell/sharp

Already asked on SO, but I think the chance of getting an answer is bigger here.


I am resizing a given image (saved on disk) to differenz sizes:

var image = 'photos/pic';
var sizes = [1440, 1080, 720, 480];

for (var i = 0; i < sizes.length; i++) {
    sharp(image + '.jpg')
    .resize(sizes[i], sizes[i])
    .toFile(image + '-' + sizes[i] + '.jpg');       
}

This works as expected, but I guess there is room for improvements.

  • Will the for-loop lead to any problems? If yes, is there a better way to solve that?
  • Would it be faster to wait for the generated picture to be resized and use this for the next resizing process? Let's say the original picture is 2000x2000. What's the speed improvement from resizing 720x720 to 480x480 instead of 2000x2000 to 480x480, if there is any? Considering I have to read the 720x720 file first and wait for the resizing to be finished.
  • Should I do those resizes on the "main" node thread or fork a child process? They are running asynchronously anyways, correct?
question

Most helpful comment

Hello, these tasks will run asynchronously and concurrently via the code sample you've provide.

Using the same input image will provide the best results, both in terms of quality and performance.

You may want to pass a callback as the second parameter to toFile, or, if you want notification when all tasks are completed, perhaps try a Promise-based approach, something like (untested):

const image = 'photos/pic';

const resize = size => sharp(`${image}.jpg`)
  .resize(size, size)
  .toFile(`${image}-${size}.jpg`);

Promise
  .all([1440, 1080, 720, 480].map(resize))
  .then(() => {
    console.log('complete');
  });

All 4 comments

Hello, these tasks will run asynchronously and concurrently via the code sample you've provide.

Using the same input image will provide the best results, both in terms of quality and performance.

You may want to pass a callback as the second parameter to toFile, or, if you want notification when all tasks are completed, perhaps try a Promise-based approach, something like (untested):

const image = 'photos/pic';

const resize = size => sharp(`${image}.jpg`)
  .resize(size, size)
  .toFile(`${image}-${size}.jpg`);

Promise
  .all([1440, 1080, 720, 480].map(resize))
  .then(() => {
    console.log('complete');
  });

As @lovell says, your code should give the best quality and speed.

I think a factor you might not be considering is jpeg shrink-on-load. libjpeg (which libvips uses for jpeg decode) supports shrink-on-load: when opening a file, you can ask for full resolution data, or 1/2 scale, or 1/4 or 1/8th. libjpeg is able to generate these lower resolution versions extremely quickly. The 1/8th scale image is especially fast --- it just reads the uncompressed DC component from each 8x8 DCT block.

sharp exploits these fast load paths, so, depending on the size of your source image, there might well not be a computation to reuse. Additionally, as you say, although reusing results will decrease overall CPU usage, it will also decrease parallelism (since you must wait for the previous result) and thereby increase latency.

This has just come up on the libvips tracker. There's a tiny program and some benchmarks here:

https://github.com/jcupitt/libvips/issues/656#issuecomment-301237482

tldr: slightly slower for JPG sources, faster for PNG sources, at least with that test program.

@ChristopherWalz Hope we've been able to help, please re-open if more info required.

Was this page helpful?
0 / 5 - 0 ratings