jnordberg / gif.js

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

Incorrect transparency due to unstable index #75

Open zbryikt opened 7 years ago

zbryikt commented 7 years ago

NeuQuant generates colormap with the same RGB values for different indexes. That's ok, but after that, inxsearch in NeuQuant ( which is called by findClosestRGB ) might then return different indexes for different calls with the same RGB value.

This means that some pixels with the same color might be mapped to different indexes, which leads to incorrect result of transparency.

here is an example of gif generated with this kind of issue ( wait for the frame of blinking pink ) google 3

A quick workaround is to skip this if-block at GIFEncoder.js, L382 but I'm not sure if there will be any side effect by doing this.

jnordberg commented 7 years ago

Hmm, not sure what's going on there. Setting globalPalette to true might be a workaround

zbryikt commented 7 years ago

I actually have tried globalPalette before that, but it didn't help. Instead the workaround of removing line 382 works well, so I'm using this approach now.

Tup3x commented 7 years ago

I'm not sure if I have similar issue or something a bit different. When I set transparent colour (i.e. transparent: "0x0f0000"), canvas image with at least little bit transparency is fine (I've converted transparent pixels to match 0x0f0000 colour first) but when there's no transparent pixels things go crazy and it seems to pick random colour as transparent. If has only one colour the whole image will be transparent even though that doesn't match with the previously set transparent colour.

So, for that reason I have to use weird workaround like this:

     let gif = new GIF({
        repeat: -1,
        workers: 2,
        quality: 1,
        width: tempcanvas.width,
        height: tempcanvas.height,
        workerScript: "libs/gif.worker.js"
      });

    if (hasTransparency == true) {
        gif = new GIF({
        repeat: -1,
        workers: 2,
        quality: 1,
        width: tempcanvas.width,
        height: tempcanvas.height,
        workerScript: "libs/gif.worker.js",
        transparent: palette[currentpalette].transparentColour.hex
      });

  } 

I'm only adding single frame so I'm not sure what would happen with multiple frames.

zbryikt commented 7 years ago

if transparent is not null, analyzePixels always finds an index of color that is closest to transparent color no matter how far it is to the value we specified. Say, if you have only one color - 0xff0000 - in your frame, then even if you set transparent to 0x0000ff, 0xff0000 will still be the closest color to 0x0000ff...

I think maybe we can add a threshold/max disatance when matching closest color to alleviate this kind of situation

ghost commented 5 years ago

I am experiencing this issue as well, with only one frame rendering incorrectly without any transparency. If I input a slight offset of ctx.drawImage(img, -1, -1, 128, 128); instead of ctx.drawImage(img, 0, 0, 128, 128); for only the first and last frames, it works fine.