sharp icon indicating copy to clipboard operation
sharp copied to clipboard

Provide pre-pipeline function to modify options based on input image metadata

Open lovell opened this issue 9 years ago • 16 comments

This could seriously increase the power of Stream-based image processing.

Here's an example of how halving an image's dimensions could work:

// PROPOSED API NOT YET AVAILABLE
var halver = sharp().before(function(metadata) {
  this.resize(metadata.width / 2, metadata.height / 2);
});
readableStream.pipe(halver).pipe(writableStream);
// PROPOSED API NOT YET AVAILABLE

The existing metadata logic can also be improved to require only the first few hundred bytes of a Stream.

In addition, adding a "playbook" of example uses for this feature to the docs would be great too. There's a variable/percentage extract/crop under discussion at #205 that this should allow for.

lovell avatar Jun 23 '15 12:06 lovell

Is the before function not been supported yet?

Lanfei avatar Jan 21 '16 03:01 Lanfei

@Lanfei Not yet, sorry, but please do subscribe to this issue for updates.

lovell avatar Jan 21 '16 09:01 lovell

Okay.

Lanfei avatar Jan 21 '16 12:01 Lanfei

HI @lovell, I think this Issue addresses what I'm trying to solve currently as well – I've subscribed for notifications for when .before is implemented.

In the meantime, might you have a suggestion for how I can verify that a stream contains valid image data before piping it into a transform? I'm using request to pipe data from various URLs that may or may not point at valid images.

I tried piping into a sharp().metadata() instance, handling any errors, and then continue to pipe the stream from inside the success callback (which would seem to imply that it successfully read metadata from a valid image), but I get the error: You cannot pipe after data has been emitted from the response. I understand why this is happening but haven't come up with a solution to pause the stream and then resume from inside the callback, for instance.

jaredscheib avatar Mar 04 '16 22:03 jaredscheib

@jaredscheib This feature (possibly with #298) should allow you to achieve what you need, yes. In the meantime, the least complex (and race-condition free) method of doing so is probably to store the streamed data in a Buffer.

lovell avatar Mar 05 '16 08:03 lovell

What is the status of this feature?

elliotfleming avatar Sep 08 '16 03:09 elliotfleming

@elliotfleming This is yet to be implemented. As always I'm happy to provide guidance to anyone interested in tackling it.

lovell avatar Sep 08 '16 12:09 lovell

It'd be really great to have this - I'd like to check image dimensions and throw error if they don't meet criteria before transforming my stream.

niftylettuce avatar Oct 04 '16 11:10 niftylettuce

It would be very nice to have either this or be able to change image scale, specifically some method that would work like: .scale(2) to double the image dimensions.

max-degterev avatar May 31 '17 13:05 max-degterev

Any news for this feature ?

gwen1230 avatar Apr 30 '18 15:04 gwen1230

Does the sharp.metadata() function read the entire stream into memory or will a followup resize operation currently result in a re-read of the entire stream?

I am currently using something like:

const image = stream.pipe(sharp())
const { width, height } = await image.metadata()
upload(image.resize(...))

EDIT: I see its waiting for the entire file to be read into memory before calculating the metadata... which should not even be allowed for streams tbh.

Could this be solved by having a second function called .dimensions() which only returns the dimensions instead of all of the metadata?

justinmchase avatar Nov 07 '19 17:11 justinmchase

It will be nice to have this feature but I'm using image-size-stream as an alternative for now.

Example:

const fileStream = fs.createReadStream(path)
const sizeStream = new ImageDimensionsStream()
const bufferStream = new PassThrough().pause()

let stream = fileStream.pipe(sizeStream).pipe(bufferStream)

sizeStream.on('dimensions', ({ width, height }) => {
    // take decision based on image size
    if (W !== 0 && H !== 0 && (X !== 0 || Y !== 0 || width !== W || height !== H)) {
        const extractStream = sharp()
            .extract({ left: X, top: Y, width: W, height: H })
        stream = stream.pipe(extractStream)
    }
    bufferStream.resume()
    res.send(stream)
})

Using a paused PassThrough stream as buffer while ImageDimensionsStream returns the image dimensions works out nicely.

teoxoy avatar Dec 13 '19 01:12 teoxoy

+1, would really like this feature.

omarish avatar Dec 02 '21 05:12 omarish

any changes on 2022?

xkguq007 avatar Feb 17 '22 09:02 xkguq007

Any progress on this? I am looking to do the same but than for when an image is being contained and replace the black bars (if there will appear any) with a blurred and stretched image to create a customer requested effect - but as there's no before and I am using Streams because I am working with S3 (it eases it a lot) - something like before could be really handy for this (haven't found a solution yet to get the metadata and process the image)

ThaDaVos avatar Aug 03 '22 11:08 ThaDaVos

I'm not sure I understand the semantics for the proposed before method. Before what? Also its reliance on this would make it impossible or difficult to use the fat arrow syntax.

I'd expect just being able to return the usual operation parameter via a function that gets called with the metadata, considering that (almost?) all operations have a signature that takes only a single parameter.

sharp('image.png')
  .resize(({ width }) => width / 2)

It would also be backward compatible, since no operator currently accepts a function.

lazarljubenovic avatar Jul 02 '23 06:07 lazarljubenovic