Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display issue about dithering or number of colors #8

Open
adamwangxx opened this issue Feb 25, 2022 · 9 comments
Open

Display issue about dithering or number of colors #8

adamwangxx opened this issue Feb 25, 2022 · 9 comments

Comments

@adamwangxx
Copy link

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
Copy link
Owner

notnullnotvoid commented Feb 25, 2022

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
Copy link
Author

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
Copy link
Owner

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
Copy link

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
Copy link
Author

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

@notnullnotvoid
Copy link
Owner

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 could use the image block feature of the gif format to bypass the 256-color limit entirely and get more colors in a frame like gifski does, but the file sizes will be absolutely massive because the LZW compression used in the gif format is not suitable for large palettes, and many programs will not handle these kinds of gifs correctly since they are very rare (for good reason). Note that even gifski still uses some dithering to fight against the file size problem.
  • For images that naturally have only a few colors, like flat shaded drawings or greyscale images, you can avoid dithering using either flat quantization (which loses some color accuracy) or an adaptive color counting algorithm (which will be slow). Neither approach scales up to images with more color variety, so they are not suitable for a library which is trying to be general-purpose.
  • If it's specifically the repeating nature of the dither pattern that bothers you, you could use a larger pseudo-random dither kernel with jitter, but files will get much bigger because random noise doesn't compress well and the jitter eliminates most opportunities for inter-frame compression. It may be possible to add a lossy LZW encoding step to win back some or all of that extra file size, but that introduces some extra noise, and I don't know how fast it can be made to be - I'd have to look into it. But this option may be worth considering for the library.

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
Copy link

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
Copy link
Owner

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
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants