Skip to content

Unexpected artefacts when quantizing images #2469

Description

@BrentCoxEscentual

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

Activity

  1. JimBobSquarePants commented on May 31, 2023

    @JimBobSquarePants
    Member

    What happens when you quantize without dithering?

    Why are you quantizing then resaving at 32bit?

  2. BrentCoxEscentual commented on May 31, 2023

    @BrentCoxEscentual
    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

  3. JimBobSquarePants commented on May 31, 2023

    @JimBobSquarePants
    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.

  4. BrentCoxEscentual commented on May 31, 2023

    @BrentCoxEscentual
    Author

    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

  5. JimBobSquarePants commented on Jun 12, 2023

    @JimBobSquarePants
    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

  6. added a commit that references this issue on Jun 12, 2023
    ae3900d
  7. JimBobSquarePants commented on Jun 12, 2023

    @JimBobSquarePants
    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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions