jnordberg / gif.js

JavaScript GIF encoding library
http://jnordberg.github.io/gif.js/
MIT License
4.74k stars 668 forks source link

GIF.js flashing and has black background #156

Closed limwengni closed 3 weeks ago

limwengni commented 3 weeks ago

The gif that I try resizing ends up with a black background despite the original version has a transparent background, and the gif keeps flashing and how to make the animation speed same as the original one, because it seems to be faster than the original version.

Original version: run

Resized version: run_112x112

const processGif = async (file, size) => {
      const picaInstance = pica();

      try {
        // Step 1: Read the GIF file as an ArrayBuffer
        const arrayBuffer = await new Promise((resolve, reject) => {
          const reader = new FileReader();
          reader.onload = () => resolve(reader.result);
          reader.onerror = (error) => reject(error);
          reader.readAsArrayBuffer(file);
        });

        // Step 2: Parse the GIF into frames
        const gif = parseGIF(arrayBuffer);
        const frames = decompressFrames(gif, true);

        // Step 3: Resize each frame asynchronously
        const resizedFrames = await Promise.all(
          frames.map(async (frame) => {
            // Create a temporary canvas for the current frame
            const tempCanvas = document.createElement('canvas');
            const tempCtx = tempCanvas.getContext('2d');
            tempCanvas.width = frame.dims.width;
            tempCanvas.height = frame.dims.height;

            // Put the frame's pixel data onto the temporary canvas
            const imageData = new ImageData(new Uint8ClampedArray(frame.patch), frame.dims.width, frame.dims.height);
            tempCtx.putImageData(imageData, 0, 0);

            // Create a target canvas for resized frame
            const canvas = document.createElement('canvas');
            canvas.width = size;
            canvas.height = size;
            const ctx = canvas.getContext('2d');

            // Ensure the canvas is clear and fully transparent
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // Resize using pica library
            await picaInstance.resize(tempCanvas, canvas, {
              unsharpAmount: 80,
              unsharpThreshold: 10,
              alpha: true,
            });

            // Convert resized canvas to ImageData to preserve transparency
            const resizedImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

            // Create a new canvas to draw the resized ImageData
            const finalCanvas = document.createElement('canvas');
            finalCanvas.width = size;
            finalCanvas.height = size;
            const finalCtx = finalCanvas.getContext('2d');
            finalCtx.putImageData(resizedImageData, 0, 0);

            // Convert final canvas to blob
            return new Promise((resolve, reject) => {
              finalCanvas.toBlob((blob) => {
                if (blob) {
                  // Create an Image object from the blob
                  const img = new Image();
                  img.onload = () => {
                    resolve({ img, delay: frame.delay, disposal: frame.disposalType });
                  };
                  img.src = URL.createObjectURL(blob);
                } else {
                  reject(new Error('Failed to create blob.'));
                }
              }, 'image/png');
            });
          })
        );

        // Step 4: Create a new GIF encoder with worker script path
        const gifEncoder = new GIF({
          workers: 2,
          quality: 10,
          transparent: 0x00FF00,  // Ensure transparency color
          workerScript: process.env.PUBLIC_URL + '/gif.worker.js'
        });

        // Add resized frames to the GIF encoder with correct delays and disposal methods
        resizedFrames.forEach(({ img, delay, disposal }) => {
          gifEncoder.addFrame(img, { delay, dispose: disposal });
        });

        // Step 5: Generate the resized GIF blob and return as a File object
        return new Promise((resolve) => {
          gifEncoder.on('finished', (blob) => {
            const resizedFileName = `${file.name.split('.')[0]}_${size}x${size}.gif`;
            const resizedFile = new File([blob], resizedFileName, { type: 'image/gif' });
            resolve(resizedFile);
          });
          gifEncoder.render();
        });
      } catch (error) {
        console.error('Error processing GIF:', error);
        throw error;
      }
    };