I had a look through the docs/wiki and couldn't see any examples of what I'm trying to do, which is save multiple sizes of the same image after cropping:
sharp( imgBuffer )
.resize(800, 800)
.crop(sharp.gravity.centre)
.toFile( imgLarge )
.resize(300, 300)
.toFile( imgMedium )
.resize(100, 100)
.toFile( imgSmall );
Would something like that work?
Hi Jonathan,
An undocumented feature is that if the input is a filename, you can do exactly what you're trying to achieve.
You're using a Buffer, which behaves slightly differently to work around a nuance of the V8 garbage collector.
An input Buffer containing compressed image data is copied and unreferenced as soon as the first call to toFile (or toBuffer or pipe) is made. This is to avoid the situation where, should the memory compaction stage of the V8 garbage collector run in the time between a resize task being queued and being processed, the Buffer could move without us knowing.
In your example this means the internal reference to imgBuffer held by the instance of sharp doesn't exist by the second call to toFile.
To provide the API you're expecting, we'd need to change this module to hold two copies of the input Buffer in memory at the same time, one in JavaScript land and one in C++ land. The newer version of V8 and the changes to Buffer allocation in Node.js 0.11 unstable (and io.js) might improve the situation, so I'd be happy to revisit this when they stabilise.
As an aside, the default behaviour is to crop with centre gravity so you can leave the .crop(sharp.gravity.centre) bit out.
Thanks for such a comprehensive answer and the cropping shortcut, it's a tad more complicated than I imagined.
I wonder if you can suggest the most efficient way of what I'm trying to do. I'm using the multer middleware to handle user image uploads, which you can either write straight to disk, or keep as a buffer. At the moment I'm keeping it as a buffer, then looping over it to make the three thumbnails and writing them to disk.
It sounds like I could write it to disk, read it back with sharp and create the three thumbnails in a single chain like my example above.
I imagine the former is more memory/CPU intensive, whereas the latter is more IO.
Untrusted user input? I recommend you write to disk rather than take the risk of filling memory. You'll benefit from OS-level file caching, which could well be faster than the memory hand-off between each V8/Node/sharp/libvips/libjpeg layer (I like to think of memory as being IO-bound).
You might want to look at limitInputPixels for added safety too, although it is based on trusting the metadata of input files.
Thanks, I've refactored my code to write to disk, however it's throwing an error as .toFile returns a promise, where I think .resize is expecting an image stream.
sharp( img.path )
.resize( 800 )
.toFile( imgLarge )
.resize( 300 )
.toFile( imgMedium )
.resize( 100 )
.toFile( imgSmall );
TypeError: Object [object Promise] has no method 'resize'
If you're not using Promises, you'll need to provide toFile with a callback as the 2nd parameter to handle any error.
@nmec Were you able to get this working?
Not in a single chain like I outlined above, instead I keep the image buffer in memory and loop over it for each size I need:
var sizes = [{
path: 'large',
xy: 800
},{
path: 'medium',
xy: 300
},{
path: 'small',
xy: 100
}];
Promise.map(sizes, function(size) {
return sharp( img.buffer )
.resize( size.xy, size.xy )
.toFile( size.path );
});
Hello @lovell , new user of sharp here. I want to do the same thing that @nmec did with resize, only using extract.
Since this is a quite old issue I was wandering if this is still the standard way to do things with sharp.
Thanks!
P.s. I landed here from https://stackoverflow.com/questions/43704465/resize-one-image-multiple-times
@BiancoA I'd probably recommend the Promise-based approach. Feel free to open an new issue if you can't get it working.
Most helpful comment
Hi Jonathan,
An undocumented feature is that if the input is a filename, you can do exactly what you're trying to achieve.
You're using a Buffer, which behaves slightly differently to work around a nuance of the V8 garbage collector.
An input Buffer containing compressed image data is copied and unreferenced as soon as the first call to
toFile(ortoBufferorpipe) is made. This is to avoid the situation where, should the memory compaction stage of the V8 garbage collector run in the time between a resize task being queued and being processed, the Buffer could move without us knowing.In your example this means the internal reference to
imgBufferheld by the instance ofsharpdoesn't exist by the second call totoFile.To provide the API you're expecting, we'd need to change this module to hold two copies of the input Buffer in memory at the same time, one in JavaScript land and one in C++ land. The newer version of V8 and the changes to Buffer allocation in Node.js 0.11 unstable (and io.js) might improve the situation, so I'd be happy to revisit this when they stabilise.
As an aside, the default behaviour is to crop with centre gravity so you can leave the
.crop(sharp.gravity.centre)bit out.