gifuct-js icon indicating copy to clipboard operation
gifuct-js copied to clipboard

How do I prevent pixelation of the GIF from 2nd frame onwards when serving it from Node Server?

Open Tribevote opened this issue 3 years ago • 4 comments

The first frame seems to load fine, from 2nd frame onwards, there are black pixels. Any idea on how to get rid of them?

Pixelated Screen reference

Here is my code in NodeJS

var GIFEncoder = require('gifencoder');    
const { createCanvas, loadImage, giflib, ImageData } = require('canvas')
const canvas = createCanvas(600, 400)
const ctx = canvas.getContext('2d')
const { parseGIF, decompressFrames } = require('gifuct-js')

app.get('/gif2', async function(req, res) {

 const gifURL = 'https://www.hubspot.com/hubfs/Smiling%20Leo%20Perfect%20GIF.gif'

 const gif = await fetch(gifURL)
 .then(resp => resp.arrayBuffer())
 .then(buff => parseGIF(buff));

 const frames = await decompressFrames(gif,true);

 const encoder = new GIFEncoder(600, 400);
 // stream the results as they are available into myanimated.gif
 encoder.createReadStream().pipe(fs.createWriteStream('myanimated.gif'));
 encoder.start()
 encoder.setDelay(200)

 for (let frameIndex=0; frameIndex < frames.length; frameIndex++) {

     try {

         const frame = frames[frameIndex]

         const imageData = new ImageData(
             frame.patch,
             frame.dims.width,
             frame.dims.height
         );
 
         ctx.putImageData(imageData, 0, 0);
 
         //  Add Text on Canvas     
         let label = req.query.name
         ctx.font = '30px Impact'
         ctx.fillStyle = "white";
         wrapText(ctx, label, 50, 50, 200, 35)

         encoder.addFrame(ctx);
         
     } catch (error) {
         
         console.log("Something went wrong>>>>", error);
     }
    
     
 }

 encoder.finish();

 console.log("Encoding Done>>>>>");

 setTimeout(function() { 

     fs.readFile('myanimated.gif', function(err, data) {

         console.log("Serve it Waiter>>>>>");
 
         res.writeHead(200, {'content-type':'image/gif'});
         res.end(data);
 
     })

 }, 300);


})

Tribevote avatar Jul 20 '21 06:07 Tribevote

I'm also having the same problem. Has anyone found a fix yet?

xenodirt avatar Jan 13 '22 10:01 xenodirt

Playing the GIF you supplied in the gifuct sample player seems to playback fine without artifacts. Could it be something to do with the encoder?

matt-way avatar Apr 12 '22 02:04 matt-way

@flyskywhy/gifuct-js add imageData besides patch, here imageData of every frame is full size. Or you can find out how small size patch (from 2nd frame) be converted into full size imageData in the source code, and use them in your APP code with official gifuct-js.

Also you may ref to pixel-gif and it's react-native version react-native-pixel-gif

flyskywhy avatar Feb 08 '23 05:02 flyskywhy

@Tribevote @xenodirt I faced this issue too and figured out the solution (at least in my case) to rendering the frames from this old issue:

  1. Keep track of last frame's disposalType.
  2. Before rendering a frame, clearRect if frameIndex is 0 or last frame's disposalType is 2.
if (i === 0 || prevDisposalType === 2) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
}

Here's a link to my full code (I had to use a temp canvas for some reason to get it working):

fetch(gifUrl)
    .then(response => response.arrayBuffer())
    .then(buffer => {

          let gif = parseGIF(buffer);
          let decompressedFrames = decompressFrames(gif, true);

          const canvas = document.createElement('canvas');
          const ctx = canvas.getContext('2d');
          canvas.width = gif.lsd.width;
          canvas.height = gif.lsd.height;

          let prevDisposalType;

          const frameDataURLs = decompressedFrames.map((frame, i) => {

              const tempCanvas = document.createElement('canvas');
              const tempCtx = tempCanvas.getContext('2d');

              tempCanvas.width = frame.dims.width;
              tempCanvas.height = frame.dims.height;

              const frameImageData = tempCtx.createImageData(frame.dims.width, frame.dims.height);
              frameImageData.data.set(frame.patch);
              tempCtx.putImageData(frameImageData, 0, 0);

              if (i === 0 || prevDisposalType === 2) {
                  ctx.clearRect(0, 0, canvas.width, canvas.height);
              }
              ctx.drawImage(tempCanvas, frame.dims.left, frame.dims.top);

              prevDisposalType = frame.disposalType;

              return canvas.toDataURL('image/png');
          });
     });

I don't think this will be useful for OP, although here's how I rendered it if it helps anyone else:

{frameDataURLs.map((frameDataURL, index) => (
    <img
        key={index}
        src={frameDataURL}
        alt={`Frame ${index}`}
        style={{
            display: index === currentIdx ? 'block' : 'none',
        }}
    />
))}

eric-yates avatar Apr 26 '23 04:04 eric-yates