aaharu / gifken

JavaScript library that can reverse and split animated GIFs
MIT License
25 stars 3 forks source link

Invalid LZW code #22

Open EmilienLeroy opened 4 years ago

EmilienLeroy commented 4 years ago

Trouble

I'm tried to split some gifs using the readme example and i get this error Uncaught Error: Invalid LZW code . When i set false into the split() params it work but some frame isn't complete.

Example

For example here each two frame, the floor is missing : image

Code

import gifken from 'gifken';

var xhr = new XMLHttpRequest();
xhr.open("GET", './assets/200.gif', true);
xhr.responseType = "arraybuffer";
xhr.onload = function (e) {
    var arrayBuffer = e.target["response"];
    var gif = gifken.Gif.parse(arrayBuffer);

    gif.split(false).forEach(function (i) {
        var img = new Image();
        var blob = gifken.GifPresenter.writeToBlob(i.writeToArrayBuffer());
        img.src = URL.createObjectURL(blob);
        document.body.appendChild(img);
    });
};
xhr.send();

I'm using the 2.1.1 version of gifken.

aaharu commented 4 years ago

Thank you for your report.

split(true) is a very heavy method, and it takes time to investigate.

I think the reason the floor isn't being drawn is because the animation uses a transparent GIF and doesn't have a second image over the first.

EmilienLeroy commented 4 years ago

I have made some others tests and i always get the Uncaught Error: Invalid LZW code when i use the split(true).

And when i use the split(false) lot of frames are incomplete :

Test

image

Original

tenor

if I have a little time, I'll try to find out what the problem is.

aaharu commented 4 years ago

split(false) is completed.

In order to reduce the size of GIF animation, if the first and second frames of the animation are the same color, it is often used to make the second frame transparent and display the first frame's color. Therefore, the result contains a lot of transparent colors when you use split(false).

To solve this problem, split(true) repeats the process of combining the image of the previous frame with the image of the next frame. I think there's an error somewhere in this process.

原文 GIFアニメーションは容量削減のため、アニメーションの1フレーム目と2フレーム目が同じ色の場合、2フレーム目を透過色にして1フレーム目の色を表示させる手法がよくつかわれています。 そのため、単純にフレームを分割すると透過色が多く含まれる画像になります。 `split(false)`は、この問題に対応するため、前フレームの画像に次フレームの画像を合成することを繰り返して画像を生成しています。 この処理のどこかでエラーになっていると思われます
aaharu commented 4 years ago

Sorry, split(true) has a lot of bugs.. I have been tempted to rewrite my library.

There is a solution by using canvas.

  xhr.onload = function (e) {
    var arrayBuffer = e.target["response"];
    var gif = gifken.Gif.parse(arrayBuffer);

    var canvas = document.createElement("canvas");
    canvas.width = gif.width;
    canvas.height = gif.height;
    var ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, gif.width, gif.height);

    gif.split().forEach((splited) => {
      var img = new Image();
      img.src = gifken.GifPresenter.writeToDataUrl(splited.writeToArrayBuffer());
      ctx.drawImage(img, 0, 0);
      var img2 = new Image();
      img2.src = canvas.toDataURL("image/gif");
      document.body.appendChild(img2);
    });
  };

image

EmilienLeroy commented 4 years ago

I tried your solution, but it seem not work for me. I only get empty images. image I don't know what is wrong, the code used (copy/paste from your solution) :

import gifken from 'gifken';

var xhr = new XMLHttpRequest();
xhr.open("GET", './assets/tenor.gif', true);
xhr.responseType = "arraybuffer";
xhr.onload = function (e) {
    var arrayBuffer = e.target["response"];
    var gif = gifken.Gif.parse(arrayBuffer);

    var canvas = document.createElement("canvas");
    canvas.width = gif.width;
    canvas.height = gif.height;
    var ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, gif.width, gif.height);

    gif.split().forEach((splited) => {
      var img = new Image();

      img.src = gifken.GifPresenter.writeToDataUrl(splited.writeToArrayBuffer());
      ctx.drawImage(img, 0, 0);
      var img2 = new Image();
      img2.src = canvas.toDataURL("image/gif");
      document.body.appendChild(img2);
    });
  };
xhr.send();

I also use webpack to bundle and serve my files during the developement. You can find the repository of the code here

aaharu commented 4 years ago

The onload was needed.

import gifken from 'gifken';

const xhr = new XMLHttpRequest();
xhr.open("GET", './assets/tenor.gif', true);
xhr.responseType = "arraybuffer";
xhr.onload = (e) => {
  const arrayBuffer = e.target["response"];
  const gif = gifken.Gif.parse(arrayBuffer);

  const canvas = document.createElement("canvas");
  canvas.width = gif.width;
  canvas.height = gif.height;
  const ctx = canvas.getContext("2d");
  ctx.clearRect(0, 0, gif.width, gif.height);

  Promise.all(gif.split().map((splited) => {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => resolve(img);
      img.onerror = (e) => reject(e);
      img.src = gifken.GifPresenter.writeToDataUrl(splited.writeToArrayBuffer());
    })
  })).then((values) => {
    values.forEach((img) => {
      ctx.drawImage(img, 0, 0);
      const newImg = new Image();
      newImg.src = canvas.toDataURL("image/gif");
      document.body.appendChild(newImg);
    });
  });
};
xhr.send();
EmilienLeroy commented 4 years ago

It work ! Thanks a lot 👍