-
-
Notifications
You must be signed in to change notification settings - Fork 8
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
Already quantized frames are processed again and not dithered on save #8
Comments
What is the pixel format of the preview images? Are they indexed images with some palette (eg. 8 bpp)? If so, then the encoder will preserve their original content unless you set the The provided code sample doesn't contain how
If the input images are not indexed ones, then the default quantizer tries to optimize the palette for each frames individually, which has a significant impact on the performance. And considering that the default value of |
Please note that >8 bpp quantizers will not be alright for GIF images (case 13..16). From the docs of the
This image illustrates what happens in such case (captured from this tool) - note the tooltip that warns you about the undesired result: It happens because a 32-bit quantizer was selected (which effectively just removes transparency below Now, as the target pixel format is 8bpp, the result is even worse (as the screenshot illustrates). It's because the 32-bit quantizer returns a How to create high-color GIF animations then (if this was your intention with high-color quantizers): GIF format does not allow more than 256 colors per frame. High color animations use a clever trick: new frames do not clear the previous content and use a local palette to add up to 256 new colors compared to the previous frame. By reserving one palette entry for transparency it is possible to mask out the unchanged content. The
Of course, when delta images are allowed, your preview for the non-first frame can only be a hint. The first frame will have 256 colors in all cases, and the further ones may have more. Actually, in the next release I plan to do some improvements when generating the delta images. When can you pass the prequantized and dithered preview images to the encoder:
|
@koszeggy Thank you for the detailed explanation. I updated my styles to match your recommendations above: set
{
_sindex = value;
switch (value)
{
case 0: // graphix
quantizer = PredefinedColorsQuantizer.BlackAndWhite();
pixFormat = PixelFormat.Format1bppIndexed;
ditherer = OrderedDitherer.BlueNoise.ConfigureStrength(0.9f);
break;
case 1: // lightness
quantizer = PredefinedColorsQuantizer.BlackAndWhite(System.Drawing.Color.Black, 92);
pixFormat = PixelFormat.Format1bppIndexed;
ditherer = ErrorDiffusionDitherer.Atkinson.ConfigureErrorDiffusionMode(true);
break;
case 2: // Polka dot
quantizer = PredefinedColorsQuantizer.BlackAndWhite(System.Drawing.Color.Black, 112);
pixFormat = PixelFormat.Format1bppIndexed;
ditherer = OrderedDitherer.DottedHalftone.ConfigureStrength(.9f);
break;
case 3: // medium dot
quantizer = PredefinedColorsQuantizer.BlackAndWhite();
pixFormat = PixelFormat.Format1bppIndexed;
ditherer = ErrorDiffusionDitherer.StevensonArce;
break;
case 4: // small dot
quantizer = PredefinedColorsQuantizer.BlackAndWhite(System.Drawing.Color.Black, 24);
pixFormat = PixelFormat.Format1bppIndexed;
ditherer = ErrorDiffusionDitherer.Burkes;
break;
case 5: // vintage
quantizer = OptimizedPaletteQuantizer.Octree(2);
pixFormat = PixelFormat.Format1bppIndexed;
ditherer = OrderedDitherer.BlueNoise.ConfigureStrength(.66f);
OptimisedQuantizer = true;
break;
case 6: // mono pop
quantizer = PredefinedColorsQuantizer.Grayscale4(System.Drawing.Color.Black, true);
pixFormat = PixelFormat.Format4bppIndexed;
ditherer = OrderedDitherer.Bayer8x8.ConfigureStrength(.7f);
break;
case 7: // blues
Palette blues = new Palette(new Color32[] {
new Color32(System.Drawing.Color.AliceBlue),
new Color32(System.Drawing.Color.SteelBlue),
new Color32(System.Drawing.Color.LightGoldenrodYellow),
new Color32(System.Drawing.Color.White),
new Color32(System.Drawing.Color.Black)});
quantizer = PredefinedColorsQuantizer.FromCustomPalette(blues);
pixFormat = PixelFormat.Format4bppIndexed;
ditherer = ErrorDiffusionDitherer.Burkes;
break;
case 8: // mono
quantizer = PredefinedColorsQuantizer.Grayscale16();
pixFormat = PixelFormat.Format4bppIndexed;
ditherer = ErrorDiffusionDitherer.FloydSteinberg;
break;
case 9: // Gray
quantizer = PredefinedColorsQuantizer.Grayscale();
pixFormat = PixelFormat.Format8bppIndexed;
ditherer = ErrorDiffusionDitherer.FloydSteinberg;
break;
case 10: // film 200
quantizer = PredefinedColorsQuantizer.Grayscale();
pixFormat = PixelFormat.Format8bppIndexed;
ditherer = new RandomNoiseDitherer(0.25f);
break;
case 11: // filmic
quantizer = OptimizedPaletteQuantizer.Octree(32);
pixFormat = PixelFormat.Format8bppIndexed;
ditherer = OrderedDitherer.BlueNoise.ConfigureStrength(.88f);
OptimisedQuantizer = true;
break;
case 12: // index
quantizer = OptimizedPaletteQuantizer.Octree();
pixFormat = PixelFormat.Format8bppIndexed;
ditherer = OrderedDitherer.BlueNoise.ConfigureStrength(.9f);
OptimisedQuantizer = true;
break;
case 13: // 332
quantizer = PredefinedColorsQuantizer.Rgb332(System.Drawing.Color.Black, true);
pixFormat = PixelFormat.Format8bppIndexed;
ditherer = ErrorDiffusionDitherer.FloydSteinberg;
break;
case 14: // High fidelity
HighQuality = true;
quantizer = OptimizedPaletteQuantizer.Wu();
pixFormat = PixelFormat.Format8bppIndexed;
ditherer = ErrorDiffusionDitherer.FloydSteinberg;
break;
case 15: // system default
break;
default:
quantizer = PredefinedColorsQuantizer.Rgb332();
pixFormat = PixelFormat.Format8bppIndexed;
ditherer = ErrorDiffusionDitherer.FloydSteinberg;
break;
} Btw the speed did not improve compared to this native encoder part of BumpKit. And I think I have figured out something. For most of the cases the ditherer is passed trough from the settings and generated images I provide but if I use setting 9 Grayscale the save becomes faster and in the end there is no ditherer for some reason except on the first frame? So I believe that there might be a case where the ditherer is reapplied and that's why the saving time gets much longer for the already generated preview images. Here's an example gif: |
I cloned the repo but the |
I will try to figure it out but there is nothing in the Output window :D @koszeggy I think its ok now and you can check the latest commit |
Sorry for late reply but I was trying to implement at least the colour for this release. I'm an architect and just want the fastest possible solution to prepare graphic material. I think the threshold should wait for next releases as the software I use generates the images and the animations without transparency better. I would definitely add the option in the future but for the moment, given the regular input files, I thinks its not that necessary. More important is performance with high res files as regularly I render at more than 2000px. There are cases of course where I export images for web and then only the artistic qualities are judged rather than absolute accuracy. I think I'm after smooth gradients and also distinct shadows. I checked your code but could not figure out why there is delay as it is doing similar thing to the BumpKit. It should be super fast as it already has the formatted images and just have to write them to disk. Is there some compression happening like LZW on the way? But actually overall performance now is comparable to Photoshop gif editor with similar resolution images. Here is an artistic result: I also attach an archive with sample files (very few because there is size limit on github) I would use without scaling down. The transparent option does not render properly as in software for some reason. I checked what you suggested but in the end I set again 8x8 ditherer because otherwise I get the ditherer from the previous style I was using for some reason. You can check the latest commit. |
Yes. Actually I tried to optimize it as much as possible, for example, instead of the default To improve performance with the current version you can try to instantiate the |
I will try to do that next year and will start working on it soon. Thanks so much for the help you are giving me. Actually I have a question? Why did you decide to make this library open source? I wonder how to license my program. I have no problem listing it as open source but fist I want to know what are my options regarding future growth and support. Does it mean that other people get to make money out of the free software you write? And finally almost forgot. Happy New Year! Edit: |
The possible accidental re-quantizing issue has been fixed by this commit. But it's not in 6.2.0 so it will be released later.
Enthusiasm :)
They are allowed to do so. I didn't set up any donation yet so I have no idea whether anyone would be grateful for it.
The system palette is a bit redundant with its first 16 colors, and also has 24 transparent entries. RGB332 does not have a transparent color though.
It's because by using an optimized quantizer all frames will have their own palette, whereas the RGB332 palette is stored only once as a global one. Happy 2022 to you, too! |
I reopened the issue and improved LZW compression by using a custom hash table as I mentioned above. The performance tests look promising: depending on how detailed the image is, the improvement is between 1.5x-5x
|
It's not released yet. I'm just maintaining the change log. The actual version can be even 6.3.0 if there will be more changes.
Not necessarily. It can be true whenever the palette supports transparency, even for system default palette. It's just that with a fix palette there will never be more than 256 colors on a frame, but masking out the unchanged parts may help to reduce the file size.
Now that's interesting. It says it cannot find KGySoft.CoreLibraries 6.0.1. By any chance, do you have maybe an explicit reference to KGySoft.CoreLibraries with a specific version? Maybe an auto-generated assembly redirect in app.config, versioned reference in .csproj that didn't change when you upgraded the KGySoft.Drawing version, etc? |
Got it.. fixed that. There is something more interesting happening though. When I use optimized palette images that support transparency and save already quantized frames the resulting file has lower resolution than frames I'm passing to the SaveAnimation method: compared to result. It seems only the information about frame size is wrong but the actual saved images are the desired resolution because when I open the gif in my program it shows correct size for all frames: |
Feel free to build it by yourself, it's on the Development branch. It's not always stable and I'm just working on a larger refactoring right now.
The file Properties/Details tab is a bit misleading. GIF specification allows you to add smaller frames than the actual image size (or logical screen size as it is called in the specs). If you instantiate the And it happens because if the
To "fix" the reported resolution in file details you can set the |
I knew I was doing something wrong :D Okey it makes sense to remove them but when preview them they seemed smaller scale than with the borders which should not be the case but I guess that's how the windows photo app works. Using GifEncoder is maybe not necessary at the moment but the EncodeAnimation instead. I also tried to build it but it seems I'm lacking System.Drawing.Common v6 on NET 5 and don't know how to get it. So I'm grateful there is NuGet package as well. |
The fix has not been released yet because in the end I will introduce more changes in the next release. That's why the issue is still open. Feel free to download the |
Alright, I will try. I was sleepy yesterday and pulled the wrong branch. There is something that I don't understand thought. Why the final app size have increased double when I used the manually referenced assemblies vs the NuGet ones? It was 4MB and now is 8MB. |
Just a guess: you mentioned you build a single-file monolith build. Maybe NeGet references are not directly included (is there a *.deps.json in the output dir?), but when directly referenced, all will be included in the final .exe file. However, I'm not sure about this as I haven't played too much with this build option yet. |
Yeah maybe that is what is happening but as I'm not too experienced and 8MB is not too big its fine. I just use this option because I dont have an installer yet. P.S. case 9: // Gray
quantizer = PredefinedColorsQuantizer.Grayscale(Background);
pixFormat = PixelFormat.Format8bppIndexed;
ditherer = OrderedDitherer.Bayer8x8; // because preview does not work otherwise and is using the previous detherer
break;
case 10: // film 200
quantizer = PredefinedColorsQuantizer.Grayscale(Background);
pixFormat = PixelFormat.Format8bppIndexed;
ditherer = new RandomNoiseDitherer(0.25f);
break;
case 11: // rought c
Color[] colour9 = new Color[] {
System.Drawing.Color.Black,
System.Drawing.Color.White,
System.Drawing.Color.Transparent,
System.Drawing.Color.Red,
System.Drawing.Color.Lime,
System.Drawing.Color.Blue,
System.Drawing.Color.Cyan,
System.Drawing.Color.Yellow,
System.Drawing.Color.Magenta
};
quantizer = PredefinedColorsQuantizer.FromCustomPalette(colour9, Background, AlphaThold);
pixFormat = PixelFormat.Format4bppIndexed;
ditherer = ErrorDiffusionDitherer.Stucki;
// not optimised but use this for setting delta frames to true as we have transparency
OptimisedQuantizer = true;
break;
case 12: // old colour
quantizer = OptimizedPaletteQuantizer.MedianCut(16, Background, AlphaThold);
pixFormat = PixelFormat.Format4bppIndexed;
ditherer = OrderedDitherer.BlueNoise;
OptimisedQuantizer = true;
break;
case 13: // filmic
quantizer = OptimizedPaletteQuantizer.Octree(32, Background, AlphaThold);
pixFormat = PixelFormat.Format8bppIndexed;
ditherer = OrderedDitherer.BlueNoise.ConfigureStrength(.88f);
OptimisedQuantizer = true;
break;
case 14: // full index
quantizer = OptimizedPaletteQuantizer.Octree(256, Background, AlphaThold);
pixFormat = PixelFormat.Format8bppIndexed;
ditherer = OrderedDitherer.BlueNoise;
OptimisedQuantizer = true;
break;
EDIT: |
If you push the recent changes I will have a look at it. |
I updated it just now. Thanks |
Are you sure you built the latest Btw, this kind of banding should not occur anymore even when requantizing with default I attached the .NET 5 release build of my current Development branch. Please let me know if it works for you. And please note that it cannot be considered as a stable version (I would like release the next version in a week or so): KGySoft.Drawing.zip |
Sorry for late reply but had lots of things to do. How did you see that I have old assembly? Is that in github site or in VS? Okey got the concept about the solid colour and will be making a style without dithering. In the future I can make style editor and there users can be able to select more settings. Thanks for explaining! |
In your |
Ah that is just for the Imaging tools app. I reference directly from the release folders, which is not the smartest solution but for now its ok. |
The fix has been released in v6.3.0 |
Great! Thank you :) |
Thank you so much for providing this wonderful tool for free! I'm making a small program to export frames to animation. I already generated preview of the bitmaps and want to export that result directly bit it seems it is being qunatized and not dithered even tho the original preview frames are already processed? What I'm doing wrong? If I use the default windows encoder saving is two times faster so there is something happening under the hood here...
EDIT:
Actually I figured half of the story. If I change the (i => BitmapExtensions.GetReadableBitmapData((Bitmap)i)); to i => BitmapExtensions.GetReadWriteBitmapData((Bitmap)i.Clone())); the indexed format is preserved to the encoder but then the progress report starts at 50% and finishes 100 but getting exponentially slower. Overall time seems comparable or a hair faster.
The text was updated successfully, but these errors were encountered: