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
29.33k stars 1.3k forks source link

Trouble composing multiple files #805

Closed shwill closed 7 years ago

shwill commented 7 years ago

Hello everybody,

first of all thank you for this fine piece of software. It even runs on my raspberry pi (arm8), great job. Secondly, I want to apologize, because this might be regarded as a very novice question and it (might) not be an issue at all.

I am trying to combine several images into one, but I have trouble doing so. E.g. I want to put images A, B and C on a blank alpha, my result is only C, but not A and B appearing. I tried to put together a precise example of showing what is happening:

var sharp = require('sharp');

var images = [
    __dirname + '/test.jpg', // blue square, 200x200
    __dirname + '/test.jpg',
    __dirname + '/test.jpg'
];

// create 1800x1200 transparent canvas
var canvas = sharp(null, {
    create: {
        width: 1800, 
        height: 1200, 
        channels: 4, 
        background: '#ffffff00'
    }
});

// load images
var buffers = [];
for (i = 0; i < images.length; i++) {
    // load the image into sharp
    console.log("Loading image");
    buffers.push(sharp(images[i]).resize(50, 50).toBuffer());
}

// wait for buffers to be ready
Promise
    .all(buffers)
    .then(function (buffers) {

        // overlay the canvas with the buffers, spread them out a bit
        for (i = 0; i < buffers.length; i++) {
            sharp(buffers[i]).toFile('buffer-'+ i + '.png'); // 50x50 blue squares
            canvas = canvas.overlayWith(buffers[i], {left: i*100, top: i*100});
        }
        canvas.toFile('canvas.png'); // has only one blue square, not three on it
    }).catch(error => console.log(error));

The results

Please note that I did not manage to include the images directly. Basically, I expected that there will be three (3) blue squares on the canvas, but the resulting canvas.png has only one on it. From the position I would assume that the last canvas.overlayWith(..) was written to the canvas, overwriting the existing ones.

test.jpg - input http://abload.de/image.php?img=buffer-0lky4n.png

Output (canvas.png) http://abload.de/image.php?img=canvastzzrd.png

Expected output (but transparent, this was a mistake of mine): http://abload.de/image.php?img=expected-canvasrwxf1.png

What did I do wrong here, or is just one .overlayWith supported? Thank you for your time to read up on all this.

Sebastian

lovell commented 7 years ago

Hello, did you see #728 ?

shwill commented 7 years ago

I have seen it know, and will be investigating into this direction. I got the code sample working, that was provided in #405, but I have to admit: For me, that seems like a really cumbersome way to do it. I found a suggestion while digging through the issues that a

sharp(..)
  .overlayWith(..)
  .overlayWith(..)

pipeline would be a bit more intuitive. But this is coming from someone without your javascript background, so I might be totally wrong and that solution indeed is elegant and the way to do it.

Thank you for the pointer into the right direction. Perhaps it could be mentioned in the API docs that multiple overlays are not pipe-able right now?

shwill commented 7 years ago

Just wanting to update my question (thanks for labeling it as one): I am trying to accomplish something according to the example of #405, but I cannot manage it. This is just a tad too much on javascript and the library here.

If it's not too much to ask, could you give me a pointer on how to do:

-----------
+    1    +
-----------
+    2    +
-----------
+    3    +
-----------
+    4    +
-----------

I understood the example you were giving in #405, but I didn't manage to adapt additional processing steps (resizing and positioning) while doing so.

You may close this question if you consider this as non-related to your project, of course (which it is), I would understand that.

matAtWork commented 7 years ago

I have the same (noob) question. So any help would be much appreciated!

lovell commented 7 years ago

If you're able to wait for #728 then that will make this task significantly easier :)

matAtWork commented 7 years ago

I actually got around this as described in https://github.com/lovell/sharp/issues/573#issuecomment-302109919, but thanks for the heads-up

shwill commented 7 years ago

Thanks for the headsup, I spent my time building other code and I am coming back right now at the image processing part, so if I manage to pull something off, I will post it here, too, as an additional ressource for other users.

shwill commented 7 years ago

Here's at least part of the promised result. I am capturing images with Canon EOS digital camera, and I have plenty of time between captures and composing. Since I am running it on a Raspberry Pi, memory is quite limited, so I gave up on the idea to store each captured and resized image in a buffer (raw buffers are huge, I quickly ran out of memory), and build some kind of queue system.

So, in pseudo-code, I have something like this:

add(filename) {
   // options for the whole image
   const defaultOpts = {raw: {height: 200, width: 200, channels: 4}};

   // load background canvas as the first buffer
   if (this.buffer === null) {
     this.buffer = sharp(background).raw().toBuffer();
   }

   // I am getting width, height, left and top as meta information, just assume that they are "there"
   var newImage = sharp(filename)
   .resize(width, height)
   .rotate(180) // since my camera is upside-down, I need to do this. Space limitation in the photobooth box
   .raw()
   .toBuffer();

   // contains top and left for translation of the resized image, too
   const newImageOpts = { raw: {height: height, width: width, channels: 4}, top: top, left: left};

   Promise.all([this.buffer, newImage])
   .then( function (buffers) {
      this.buffer = sharp(buffers[0], defaultOpts).overlayWith(buffers[1], newImageOpts).raw().toBuffer();
   })
   .catch (function(reason) {
      throw reason;
   });
}

Perhaps it might help someone else getting on track.