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

Unexpected artefacts when quantizing images #2469

Closed
4 tasks done
BrentCoxEscentual opened this issue May 31, 2023 · 6 comments · Fixed by #2473
Closed
4 tasks done

Unexpected artefacts when quantizing images #2469

BrentCoxEscentual opened this issue May 31, 2023 · 6 comments · Fixed by #2473

Comments

@BrentCoxEscentual
Copy link

Prerequisites

  • I have written a descriptive issue title
  • I have verified that I am running the latest version of ImageSharp
  • I have verified if the problem exist in both DEBUG and RELEASE mode
  • I have searched open and closed issues to ensure it has not already been reported

ImageSharp version

3.0.1

Other ImageSharp packages and versions

NA

Environment (Operating system, version and so on)

Windows, Linux

.NET Framework version

6

Description

We are currently experiencing strange artifacts when quantizing images.

Steps to Reproduce

Load image.
Quantize the image using KnownQuantizers.Wu
The saved output will have unexpected artifacts.

Below is a test program I created to replicate the problem. It does not happen on all images only on some. I have attached a image that it happens on. If you compare the original to the quantized version you will note a grey triangular artifact from right of centre to the right middle of the image.

using SixLabors.ImageSharp.Formats.Png;

using (var i = Image.Load("Orig.png"))
{
    i.Mutate(x => x.Quantize(KnownQuantizers.Wu));
    await i.SaveAsPngAsync("Resized.png", new PngEncoder()
    {
        CompressionLevel = PngCompressionLevel.BestCompression,
    });
}

Original Image
Orig
Quantized Image
Resized

Images

Orig
Resized

@JimBobSquarePants
Copy link
Member

What happens when you quantize without dithering?

Why are you quantizing then resaving at 32bit?

@BrentCoxEscentual
Copy link
Author

Hi there,

I created the the demo program with the absolute minimum code to illustrate the problem that I came across while working with the library and thought it best to raise it.

As to saving as 32 bit, it was not a conscious decision.

I am happy to try with out dithering with you could point me in the right direction how I can do that.

Thanks for your time with this

@JimBobSquarePants
Copy link
Member

I am happy to try with out dithering with you could point me in the right direction how I can do that.

Instead of using KnownQuantizers.Wu new up an instance of the WuQuantizer and passing QuantizerOptions.Dither as null.

It's likely the dither as the pattern stems down and left from the widening as it does which is the direction error diffusion goes.

You can set the strength of the dither (0-1) in those same options.

@BrentCoxEscentual
Copy link
Author

BrentCoxEscentual commented May 31, 2023

I am happy to try with out dithering with you could point me in the right direction how I can do that.

Instead of using KnownQuantizers.Wu new up an instance of the WuQuantizer and passing QuantizerOptions.Dither as null.

It's likely the dither as the pattern stems down and left from the widening as it does which is the direction error diffusion goes.

You can set the strength of the dither (0-1) in those same options.

Hi there

I have made the change as suggested.

using (var i = Image.Load("Orig.png"))
{
    i.Mutate(x => x.Quantize(new WuQuantizer(new QuantizerOptions()
    {
        Dither = null
    })));

    await i.SaveAsPngAsync("test.png", new PngEncoder()
    {
        CompressionLevel = PngCompressionLevel.BestCompression
    });
}

The artifact has now disappeared and the image is as expect.

I have tested all the Dithering types and the following types create the artifacts.

Burks
FloydSteinberg
Sierra2
Sierra3
SierraLite
Stucki

The rest are fine and don't product any issues that I can see.

Purely for my education and better understanding, why do you think the dithering would cause this issue?

Thanks again for you time and help.

Regards

@JimBobSquarePants
Copy link
Member

The dithering algorithms work by pushing error (this difference between a quantized pixel and the original) to its surrounding pixels.

For example, Floyd Steinburg propagates the error to the neighboring pixels as follows:

  • Distribute 7/16 of the error to the pixel on the right.
  • Distribute 3/16 of the error to the pixel on the bottom-left.
  • Distribute 5/16 of the error to the pixel directly below.
  • Distribute 1/16 of the error to the pixel on the bottom-right.

We use a lookup table that stores up to 5 bits of pixel data per component to match the result of a quantized pixel against it's undithered equivalent. We use 5 bits as that keeps memory low (6 bits would be preferred but that uses over 30 MB compared to 2 1/4 MB). Sometimes we end up with a collision which leads to the artifacts you are seeing.

This, in the case of your image pushes the artifact caused by that shadow down and to the right. This is why I suggested adjusting the strength of the dithering. Using .5F as a parameter for example would reduce the error by half.

Here's the output from doing that.

Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469

@JimBobSquarePants
Copy link
Member

I've actually opened up #2473 which allocates more memory towards alpha component comparisons in our lookup table which fixes the issue using the default encoder settings.

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

Successfully merging a pull request may close this issue.

2 participants