twolfson / gif-encoder

Streaming GIF encoder
The Unlicense
86 stars 10 forks source link

Create a promise with pixels as entry, gif as resolve #16

Closed arnaudambro closed 5 years ago

arnaudambro commented 5 years ago

Hi there,

I want to thank you for your lib, but I can't yet because I couldn't make it work :)

I am trying to create a gif on client-side, starting with the DOM. I use for that the lib dom-to-image which, thanks to it's method toPixelData, return some pixels. Then I want these pixels to give me a gif. I thought about using a Promise for that because the last step is to put this gif in a ZIP file created by JSZip.

Anyway, so here is my Promise, and it's not returning what I want, could you help please ?

const pixelsToGIF = (pixels, fileName, width, height) =>
  new Promise((resolve, reject) => {
    const gif = new GifEncoder(width, height);
    const gifFile = fs.createWriteStream(fileName);
    gif.pipe(gifFile);
    gif.writeHeader();
    gif.addFrame(pixels);
    gif.finish();
    gif.on('end', () => resolve(gif))
    gif.on('error', reject)
    // gif.on('data', console.log)
  })
twolfson commented 5 years ago

There seem to be 2 conflicting questions here. One is about how to use a GIF with fs (as that's what the code example is using) and another about calling back with the contents from a promise. I'll address them independently:

Using the GIF

Our library outputs the GIF as data, not as an DOM element or similar. To aggregate this content, you can collect the stream into a buffer or pass it along to its target

https://nodejs.org/api/stream.html

The former has the benefit of being able to re-encode the data in a desired format (e.g. converting to a base64 image URL to reuse in a DOM element):

// Using `concat-stream` to simplify collecting content -- could be done with `gif.read()` and `gif.on('readable')`
var gif = new GifEncoder(width, height);
var gifFile = fs.createWriteStream(fileName);
gif.pipe(concatStream(function (gifBuff) {
  // gifBuff is raw image data in Buffer format
  // Convert our image to a data URI
  //   https://css-tricks.com/data-uris/
  var img = new Image();
  img.src = 'data:image/gif;base64' + gifBuff.toString('base64');

  // Add image to page
  document.body.appendChild(img);
});
gif.writeHeader();
gif.addFrame(pixels);
gif.finish();

The latter has the benefit of being not as memory intensive so it might better suit usage with zipping. In that case, we'd use the gif.pipe from your earlier code sample

Calling back with contents in a promise

I believe that a promise expects all the contents to be collected in a single buffer. If that's not the case (e.g. it can return a stream), then simply resolve(gif) immediately (or on process.nextTick to avoid unexpected synchronous behavior for an async action)

Here's how to do it with calling back the buffer:

const pixelsToGIF = (pixels, fileName, width, height) =>
  new Promise((resolve, reject) => {
    const gif = new GifEncoder(width, height);
    gif.pipe(concatStream(resolve)); // Calls back to `resolve` with the `gifBuff` buffer
    gif.writeHeader();
    gif.addFrame(pixels);
    gif.finish();
    gif.on('error', reject);
  })
arnaudambro commented 5 years ago

Great explanation, thanks !

So to make it clear (because I didn't get the first time your comment // Using 'concat-stream' to simplify collecting content), to make it work we use the lib concat-stream and the final code is the following :

import GifEncoder from 'gif-encoder';
import concat from 'concat-stream';

const pixelsToGIF = (pixels, fileName, width, height) =>
  new Promise((resolve, reject) => {
    const gif = new GifEncoder(width, height);
    gif.pipe(concat(resolve));
    gif.writeHeader();
    gif.addFrame(pixels);
    gif.finish();
    gif.on('error', reject);
  });

thanks a lot for your help and great explanation, I spent a few hours on it yesterday, and I wouldn't have make it by myself !

arnaudambro commented 5 years ago

May I ask you another question : when I want to export a DOM Node with some elements overflowing, the export is "corrupted", or let say really really distorted. I guess it is because the dom-to-image lib is exporting pixel data, and there are too many pixels to fit within the width and height we also give as an input. But as we use those width and height parameters both with dom-to-image and gif-encoder libs, I don't know where the distorsion happens, and where (and how) it should be corrected : I guess we would have to filter the pixel data removing all the overflowing pixels just before it goes to gif-encoder, do you agree ?

twolfson commented 5 years ago

Yea, I'd say that sanitizing the output of dom-to-image seems right. Otherwise this and all other image generating libraries would be doing a lot more work =P

For what it's worth, it might be easier if you use a library like save-pixels thought that introduces more overhead of libraries to bundle

On Mon, Oct 22, 2018, 4:19 AM Arnaud Ambroselli notifications@github.com wrote:

May I ask you another question : when I want to export a DOM Node with some elements overflowing, the export is "corrupted", or let say really really distorted. I guess it is because the dom-to-image lib is exporting pixel data, and there are too many pixels to fit within the width and height we also give as an input. But as we use those width and height parameters both with dom-to-image and gif-encoder libs, I don't know where the distorsion happens, and where (and how) it should be corrected : I guess we would have to filter the pixel data removing all the overflowing pixels just before it goes to gif-encoder, do you agree ?

— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/twolfson/gif-encoder/issues/16#issuecomment-431801996, or mute the thread https://github.com/notifications/unsubscribe-auth/AA3FWMEHuRinNHH1aQtI6n1679jcOqiZks5unamqgaJpZM4XtF4N .