From 3b97c0db914fedcbdbdcb2e80eca91eaf1717ad7 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 13 Apr 2017 13:13:27 +1000 Subject: [PATCH] Octree Quantizer now only supports 1 alpha value ... As nature intended. My Existing hack was incorrect and led to strange results. --- .../Quantizers/Octree/OctreeQuantizer.cs | 70 +++++------------- .../Quantizers/Options/Quantization.cs | 5 +- tests/ImageSharp.Tests/FileTestBase.cs | 3 +- tests/ImageSharp.Tests/TestImages.cs | 1 + .../TestImages/Formats/Gif/trans.gif | Bin 0 -> 13707 bytes 5 files changed, 25 insertions(+), 54 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif diff --git a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs index 2590f297e..f95ae5fce 100644 --- a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs @@ -58,18 +58,12 @@ namespace ImageSharp.Quantizers public override QuantizedImage Quantize(ImageBase image, int maxColors) { this.colors = maxColors.Clamp(1, 255); - this.octree = new Octree(this.GetBitsNeededForColorDepth(maxColors)); + this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors)); - return base.Quantize(image, maxColors); + return base.Quantize(image, this.colors); } - /// - /// Execute a second pass through the bitmap - /// - /// The source image. - /// The output pixel array - /// The width in pixels of the image - /// The height in pixels of the image + /// protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second @@ -115,36 +109,17 @@ namespace ImageSharp.Quantizers } } - /// - /// Process the pixel in the first pass of the algorithm - /// - /// - /// The pixel to quantize - /// - /// - /// This function need only be overridden if your quantize algorithm needs two passes, - /// such as an Octree quantizer. - /// + /// protected override void InitialQuantizePixel(TColor pixel) { // Add the color to the Octree this.octree.AddColor(pixel, this.pixelBuffer); } - /// - /// Retrieve the palette for the quantized image. - /// - /// - /// The new color palette - /// + /// protected override TColor[] GetPalette() { - if (this.palette == null) - { - this.palette = this.octree.Palletize(Math.Max(this.colors, 1)); - } - - return this.palette; + return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, 1))); } /// @@ -175,6 +150,7 @@ namespace ImageSharp.Quantizers /// /// The /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int GetBitsNeededForColorDepth(int colorCount) { return (int)Math.Ceiling(Math.Log(colorCount, 2)); @@ -189,7 +165,7 @@ namespace ImageSharp.Quantizers /// Mask used when getting the appropriate pixels for a given node /// // ReSharper disable once StaticMemberInGenericType - private static readonly int[] Mask = { 0x100, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; /// /// The root of the Octree @@ -379,11 +355,6 @@ namespace ImageSharp.Quantizers /// private int blue; - /// - /// Alpha component - /// - private int alpha; - /// /// The index of this node in the palette /// @@ -406,7 +377,7 @@ namespace ImageSharp.Quantizers // Construct the new node this.leaf = level == colorBits; - this.red = this.green = this.blue = this.alpha = 0; + this.red = this.green = this.blue = 0; this.pixelCount = 0; // If a leaf, increment the leaf count @@ -454,10 +425,9 @@ namespace ImageSharp.Quantizers int shift = 7 - level; pixel.ToXyzwBytes(buffer, 0); - int index = ((buffer[3] & Mask[0]) >> (shift - 3)) | - ((buffer[2] & Mask[level + 1]) >> (shift - 2)) | - ((buffer[1] & Mask[level + 1]) >> (shift - 1)) | - ((buffer[0] & Mask[level + 1]) >> shift); + int index = ((buffer[2] & Mask[level]) >> (shift - 2)) | + ((buffer[1] & Mask[level]) >> (shift - 1)) | + ((buffer[0] & Mask[level]) >> shift); OctreeNode child = this.children[index]; @@ -479,7 +449,7 @@ namespace ImageSharp.Quantizers /// The number of leaves removed public int Reduce() { - this.red = this.green = this.blue = this.alpha = 0; + this.red = this.green = this.blue = 0; int childNodes = 0; // Loop through all children and add their information to this node @@ -490,7 +460,6 @@ namespace ImageSharp.Quantizers this.red += this.children[index].red; this.green += this.children[index].green; this.blue += this.children[index].blue; - this.alpha += this.children[index].alpha; this.pixelCount += this.children[index].pixelCount; ++childNodes; this.children[index] = null; @@ -517,11 +486,10 @@ namespace ImageSharp.Quantizers byte r = (this.red / this.pixelCount).ToByte(); byte g = (this.green / this.pixelCount).ToByte(); byte b = (this.blue / this.pixelCount).ToByte(); - byte a = (this.alpha / this.pixelCount).ToByte(); // And set the color of the palette entry TColor pixel = default(TColor); - pixel.PackFromBytes(r, g, b, a); + pixel.PackFromBytes(r, g, b, 255); palette[index] = pixel; // Consume the next palette index @@ -558,10 +526,9 @@ namespace ImageSharp.Quantizers int shift = 7 - level; pixel.ToXyzwBytes(buffer, 0); - int pixelIndex = ((buffer[3] & Mask[0]) >> (shift - 3)) | - ((buffer[2] & Mask[level + 1]) >> (shift - 2)) | - ((buffer[1] & Mask[level + 1]) >> (shift - 1)) | - ((buffer[0] & Mask[level + 1]) >> shift); + int pixelIndex = ((buffer[2] & Mask[level]) >> (shift - 2)) | + ((buffer[1] & Mask[level]) >> (shift - 1)) | + ((buffer[0] & Mask[level]) >> shift); if (this.children[pixelIndex] != null) { @@ -588,9 +555,8 @@ namespace ImageSharp.Quantizers this.red += buffer[0]; this.green += buffer[1]; this.blue += buffer[2]; - this.alpha += buffer[3]; } } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Quantizers/Options/Quantization.cs b/src/ImageSharp/Quantizers/Options/Quantization.cs index 428c8e801..039404384 100644 --- a/src/ImageSharp/Quantizers/Options/Quantization.cs +++ b/src/ImageSharp/Quantizers/Options/Quantization.cs @@ -12,17 +12,20 @@ namespace ImageSharp { /// /// An adaptive Octree quantizer. Fast with good quality. + /// The quantizer only supports a single alpha value. /// Octree, /// /// Xiaolin Wu's Color Quantizer which generates high quality output. + /// The quantizer supports multiple alpha values. /// Wu, /// /// Palette based, Uses the collection of web-safe colors by default. + /// The quantizer supports multiple alpha values. /// Palette } -} +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 765ff3a42..1fa26063d 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -28,7 +28,7 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only // TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only TestFile.Create(TestImages.Bmp.Car), - // TestFile.Create(TestImages.Bmp.Neg_height), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only TestFile.Create(TestImages.Png.Splash), // TestFile.Create(TestImages.Png.ChunkLength1), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.ChunkLength2), // Perf: Enable for local testing only @@ -46,6 +46,7 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Png.P1), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Pd), // Perf: Enable for local testing only TestFile.Create(TestImages.Gif.Rings), + // TestFile.Create(TestImages.Gif.Trans), // Perf: Enable for local testing only // TestFile.Create(TestImages.Gif.Cheers), // Perf: Enable for local testing only // TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only }; diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e9d658887..5be1240ef 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -103,6 +103,7 @@ namespace ImageSharp.Tests public const string Rings = "Gif/rings.gif"; public const string Giphy = "Gif/giphy.gif"; public const string Cheers = "Gif/cheers.gif"; + public const string Trans = "Gif/trans.gif"; } } } diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif b/tests/ImageSharp.Tests/TestImages/Formats/Gif/trans.gif new file mode 100644 index 0000000000000000000000000000000000000000..6ae92e97c95c70a750fe744f89cfd72df1db3b54 GIT binary patch literal 13707 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

F;1U;xy#Y4mFZRgp&+ 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 M_P<`@K>^494`T1gbpQYW literal 0 HcmV?d00001