notnullnotvoid / msf_gif

A single-header animated GIF exporter
206 stars 7 forks source link

Display issue about dithering or number of colors #8

Open adamwangxx opened 2 years ago

adamwangxx commented 2 years ago

Preview

image

Question:

Hi, I export a gif by msf_gif library. But the image doesn't look right, I don't know if it's because of dithering or the number of colors isn't right. And I checked debug info on https://onlinegiftools.com/analyze-gif, the number of colors is 128, not 255.

Here's the GIF file:

msf_gif

notnullnotvoid commented 2 years ago

When you say "doesn't look right", what do you mean exactly? What do you expect the gif to look like? If there is a high-quality source for the image, having that for comparison would help. Also, can you post the code that you used to generate this gif?

adamwangxx commented 2 years ago

Sorry for my poor description. Here's a demo that I expected, a "smooth pixel" version(seem like 1x1 grid). The photo I posted above seems to have a bigger pixel point. (seems like 2x2 or 4x4?) pic

Here's my code to generate gif:

setup:

m_gifState = {};
msf_gif_alpha_threshold = 0;
if (msf_gif_begin(&m_gifState, 400, 400) == 0) {
    return err;
};

encode:

if (msf_gif_frame(&m_gifState, data, centisecondsPerFrame, 8, 400 * 4) == 0) {
    return err;
}

finish:

MsfGifResult result = msf_gif_end(&m_gifState);
if (!result.data || !result.dataSize) {
    return err;
}

FILE *fp = fopen(m_path.c_str(), "wb");
if (!fp) {
    return err;
}
fwrite(result.data, result.dataSize, 1, fp);
fclose(fp);
msf_gif_free(result);
notnullnotvoid commented 2 years ago

There is a lot of dithering because you pass a value of 8 for maxBitDepth. If you pass a higher value, it will be less noticeable. I recommend using 16, which is the maximum value.

The maxBitDepth parameter doesn't refer to how many bits are in each color channel, it refers to how many bits will be used for an entire pixel when quantizing colors inside the library. I'll probably change the name of the parameter to something like "quality" in the next version to avoid confusion.

RustyMoyher commented 2 years ago

I'm seeing similar results even with the maxBitDepth set to 16. It's a consistent checkerboard pattern. Is it possible to disabled the dithering? I understand that msf_gif is faster because of the constant-quality algorithm. Does that require dithering?

adamwangxx commented 2 years ago

Same result as @RustyMoyher even with the maxBitDepth set to 16. Is there any way that I can turn off dithering?

notnullnotvoid commented 2 years ago

The short answer is no, you cannot in general avoid dithering with any gif exporter, including this one, because the gif format is fundamentally limited to 256 or fewer colors per frame.

The long answer is that there are ways to try to work around or accomodate this limitation for specific limited cases, but they all come with major downsides.

You can see what flat quantization looks like by replacing this array:

const static int ditherKernel[16] = {
     0 << 12,  8 << 12,  2 << 12, 10 << 12,
    12 << 12,  4 << 12, 14 << 12,  6 << 12,
     3 << 12, 11 << 12,  1 << 12,  9 << 12,
    15 << 12,  7 << 12, 13 << 12,  5 << 12,
};

with all zeroes, but I doubt the result will be what you want.

RustyMoyher commented 2 years ago

I should say, the way it exports currently is quite good and surprisingly fast. Think I'm trying to improve the quality for my specific case: exporting GIFs of pixel art gameplay.

Yea, it’s the repeating nature of the dithering that I’m trying to overcome. I’d be interested in a noise-based approach, even if it meant larger files sizes. I’m also interested in an adaptive color algorithm, even if it’s slower. Losing some speed to improve the image quality would be worth it for me. I also may be able to export the GIFs on a background thread.

Setting the ditherKernel to all zeros kind of worked! The repeating nature of the dither pattern is gone. But now some of the colors change and flash as the animation plays. I’m ok if some of the colors are “wrong”, but they shouldn’t keep changing throughout playback. Is there perhaps a way to prevent this? (Hmm, perhaps if I use the same color palette for each image?)

notnullnotvoid commented 2 years ago

The flashing is a result of quantization bit depth changing from one frame to the next, as it tries to use the highest possible value for each frame. The quick fix would be to lower the maxBitDepth argument - a value of 8 is guaranteed to never flash, while a value of 9 will probably never flash (I have never seen the bit depth go lower than 9 in any non-synthetic test cases). For pixel art with a limited palette, maybe it can be higher.

I'll experiment with random dithering and see how it affects things, but for pixel art where details at the scale of individual pixels are important, dithering will always be problematic to some extent.

RustyMoyher commented 2 years ago

Bingo! Lowering the maxBitDepth fixed this issue. No flicker seen with 8 or 9 yet, but certainly with 10. Setting it to 9 does improve the quality, so I'll keep testing with that for now.