diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index b10f8a2e02..1b145a79eb 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -154,18 +154,14 @@ namespace SixLabors.ImageSharp.Formats.Gif { // Transparent pixels are much more likely to be found at the end of a palette int index = -1; + var trans = default(Rgba32); for (int i = quantized.Palette.Length - 1; i >= 0; i--) { - quantized.Palette[i].ToXyzwBytes(this.buffer, 0); + quantized.Palette[i].ToRgba32(ref trans); - if (this.buffer[3] > 0) - { - continue; - } - else + if (trans.Equals(default(Rgba32))) { index = i; - break; } } diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs index 8766f10420..d646a680ea 100644 --- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs @@ -23,11 +23,6 @@ namespace SixLabors.ImageSharp.Quantizers /// private readonly Dictionary colorMap = new Dictionary(); - /// - /// The pixel buffer, used to reduce allocations. - /// - private readonly byte[] pixelBuffer = new byte[4]; - /// /// Stores the tree /// @@ -43,6 +38,11 @@ namespace SixLabors.ImageSharp.Quantizers /// private TPixel[] palette; + /// + /// The transparent index + /// + private byte transparentIndex; + /// /// Initializes a new instance of the class. /// @@ -73,7 +73,8 @@ namespace SixLabors.ImageSharp.Quantizers // pass of the algorithm by avoiding transforming rows of identical color. TPixel sourcePixel = source[0, 0]; TPixel previousPixel = sourcePixel; - byte pixelValue = this.QuantizePixel(sourcePixel); + var rgba = default(Rgba32); + byte pixelValue = this.QuantizePixel(sourcePixel, ref rgba); TPixel[] colorPalette = this.GetPalette(); TPixel transformedPixel = colorPalette[pixelValue]; @@ -92,7 +93,7 @@ namespace SixLabors.ImageSharp.Quantizers if (!previousPixel.Equals(sourcePixel)) { // Quantize the pixel - pixelValue = this.QuantizePixel(sourcePixel); + pixelValue = this.QuantizePixel(sourcePixel, ref rgba); // And setup the previous pointer previousPixel = sourcePixel; @@ -118,24 +119,57 @@ namespace SixLabors.ImageSharp.Quantizers protected override void InitialQuantizePixel(TPixel pixel) { // Add the color to the Octree - this.octree.AddColor(pixel, this.pixelBuffer); + var rgba = default(Rgba32); + this.octree.AddColor(pixel, ref rgba); } /// protected override TPixel[] GetPalette() { - return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, (byte)1))); + if (this.palette == null) + { + this.palette = this.octree.Palletize(Math.Max(this.colors, (byte)1)); + this.transparentIndex = this.GetTransparentIndex(); + } + + return this.palette; + } + + /// + /// Returns the index of the first instance of the transparent color in the palette. + /// + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte GetTransparentIndex() + { + // Transparent pixels are much more likely to be found at the end of a palette + int index = this.colors; + var trans = default(Rgba32); + for (int i = this.palette.Length - 1; i >= 0; i--) + { + this.palette[i].ToRgba32(ref trans); + + if (trans.Equals(default(Rgba32))) + { + index = i; + } + } + + return (byte)index; } /// /// Process the pixel in the second pass of the algorithm /// /// The pixel to quantize + /// The color to compare against /// /// The quantized value /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte QuantizePixel(TPixel pixel) + private byte QuantizePixel(TPixel pixel, ref Rgba32 rgba) { if (this.Dither) { @@ -144,13 +178,13 @@ namespace SixLabors.ImageSharp.Quantizers return this.GetClosestPixel(pixel, this.palette, this.colorMap); } - pixel.ToXyzwBytes(this.pixelBuffer, 0); - if (this.pixelBuffer[3] == 0) + pixel.ToRgba32(ref rgba); + if (rgba.Equals(default(Rgba32))) { - return this.colors; + return this.transparentIndex; } - return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); + return (byte)this.octree.GetPaletteIndex(pixel, ref rgba); } /// @@ -233,8 +267,8 @@ namespace SixLabors.ImageSharp.Quantizers /// Add a given color value to the Octree /// /// The pixel data. - /// The buffer array. - public void AddColor(TPixel pixel, byte[] buffer) + /// The color. + public void AddColor(TPixel pixel, ref Rgba32 rgba) { // Check if this request is for the same color as the last if (this.previousColor.Equals(pixel)) @@ -244,18 +278,18 @@ namespace SixLabors.ImageSharp.Quantizers if (this.previousNode == null) { this.previousColor = pixel; - this.root.AddColor(pixel, this.maxColorBits, 0, this, buffer); + this.root.AddColor(pixel, this.maxColorBits, 0, this, ref rgba); } else { // Just update the previous node - this.previousNode.Increment(pixel, buffer); + this.previousNode.Increment(pixel, ref rgba); } } else { this.previousColor = pixel; - this.root.AddColor(pixel, this.maxColorBits, 0, this, buffer); + this.root.AddColor(pixel, this.maxColorBits, 0, this, ref rgba); } } @@ -287,13 +321,13 @@ namespace SixLabors.ImageSharp.Quantizers /// Get the palette index for the passed color /// /// The pixel data. - /// The buffer array. + /// The color to map to. /// /// The . /// - public int GetPaletteIndex(TPixel pixel, byte[] buffer) + public int GetPaletteIndex(TPixel pixel, ref Rgba32 rgba) { - return this.root.GetPaletteIndex(pixel, 0, buffer); + return this.root.GetPaletteIndex(pixel, 0, ref rgba); } /// @@ -415,17 +449,17 @@ namespace SixLabors.ImageSharp.Quantizers /// /// Add a color into the tree /// - /// The color + /// The pixel color /// The number of significant color bits /// The level in the tree /// The tree to which this node belongs - /// The buffer array. - public void AddColor(TPixel pixel, int colorBits, int level, Octree octree, byte[] buffer) + /// The color to map to. + public void AddColor(TPixel pixel, int colorBits, int level, Octree octree, ref Rgba32 rgba) { // Update the color information if this is a leaf if (this.leaf) { - this.Increment(pixel, buffer); + this.Increment(pixel, ref rgba); // Setup the previous node octree.TrackPrevious(this); @@ -434,11 +468,11 @@ namespace SixLabors.ImageSharp.Quantizers { // Go to the next level down in the tree int shift = 7 - level; - pixel.ToXyzwBytes(buffer, 0); + pixel.ToRgba32(ref rgba); - int index = ((buffer[2] & Mask[level]) >> (shift - 2)) | - ((buffer[1] & Mask[level]) >> (shift - 1)) | - ((buffer[0] & Mask[level]) >> shift); + int index = ((rgba.B & Mask[level]) >> (shift - 2)) | + ((rgba.G & Mask[level]) >> (shift - 1)) | + ((rgba.R & Mask[level]) >> shift); OctreeNode child = this.children[index]; @@ -450,7 +484,7 @@ namespace SixLabors.ImageSharp.Quantizers } // Add the color to the child node - child.AddColor(pixel, colorBits, level + 1, octree, buffer); + child.AddColor(pixel, colorBits, level + 1, octree, ref rgba); } } @@ -524,26 +558,26 @@ namespace SixLabors.ImageSharp.Quantizers /// /// The pixel data. /// The level. - /// The buffer array. + /// The color to map to. /// /// The representing the index of the pixel in the palette. /// - public int GetPaletteIndex(TPixel pixel, int level, byte[] buffer) + public int GetPaletteIndex(TPixel pixel, int level, ref Rgba32 rgba) { int index = this.paletteIndex; if (!this.leaf) { int shift = 7 - level; - pixel.ToXyzwBytes(buffer, 0); + pixel.ToRgba32(ref rgba); - int pixelIndex = ((buffer[2] & Mask[level]) >> (shift - 2)) | - ((buffer[1] & Mask[level]) >> (shift - 1)) | - ((buffer[0] & Mask[level]) >> shift); + int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2)) | + ((rgba.G & Mask[level]) >> (shift - 1)) | + ((rgba.R & Mask[level]) >> shift); if (this.children[pixelIndex] != null) { - index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1, buffer); + index = this.children[pixelIndex].GetPaletteIndex(pixel, level + 1, ref rgba); } else { @@ -558,14 +592,14 @@ namespace SixLabors.ImageSharp.Quantizers /// Increment the pixel count and add to the color information /// /// The pixel to add. - /// The buffer array. - public void Increment(TPixel pixel, byte[] buffer) + /// The color to map to. + public void Increment(TPixel pixel, ref Rgba32 rgba) { - pixel.ToXyzwBytes(buffer, 0); + pixel.ToRgba32(ref rgba); this.pixelCount++; - this.red += buffer[0]; - this.green += buffer[1]; - this.blue += buffer[2]; + this.red += rgba.R; + this.green += rgba.G; + this.blue += rgba.B; } } } diff --git a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs index 20ba2e637e..d57865c973 100644 --- a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs +++ b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Quantizers.Base } /// - public bool Dither { get; set; } = true; + public bool Dither { get; set; } = false; /// public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser(); diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs new file mode 100644 index 0000000000..a0b14b09ba --- /dev/null +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -0,0 +1,95 @@ +namespace SixLabors.ImageSharp.Tests +{ + using SixLabors.ImageSharp.PixelFormats; + using SixLabors.ImageSharp.Quantizers; + + using Xunit; + + public class QuantizedImageTests + { + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] + public void PaletteQuantizerYieldsCorrectTransparentPixel(TestImageProvider provider, bool dither) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + Assert.True(image[0, 0].Equals(default(TPixel))); + + IQuantizer quantizer = new PaletteQuantizer { Dither = dither }; + + foreach (ImageFrame frame in image.Frames) + { + QuantizedImage quantized = quantizer.Quantize(frame, 256); + + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.Pixels[0]); + } + } + } + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] + public void OctreeQuantizerYieldsCorrectTransparentPixel(TestImageProvider provider, bool dither) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + Assert.True(image[0, 0].Equals(default(TPixel))); + + IQuantizer quantizer = new OctreeQuantizer { Dither = dither }; + + foreach (ImageFrame frame in image.Frames) + { + QuantizedImage quantized = quantizer.Quantize(frame, 256); + + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.Pixels[0]); + } + } + } + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] + public void WuQuantizerYieldsCorrectTransparentPixel(TestImageProvider provider, bool dither) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + Assert.True(image[0, 0].Equals(default(TPixel))); + + IQuantizer quantizer = new WuQuantizer { Dither = dither }; + + foreach (ImageFrame frame in image.Frames) + { + QuantizedImage quantized = quantizer.Quantize(frame, 256); + + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.Pixels[0]); + } + } + } + + private int GetTransparentIndex(QuantizedImage quantized) + where TPixel : struct, IPixel + { + // Transparent pixels are much more likely to be found at the end of a palette + int index = -1; + var trans = default(Rgba32); + for (int i = quantized.Palette.Length - 1; i >= 0; i--) + { + quantized.Palette[i].ToRgba32(ref trans); + + if (trans.Equals(default(Rgba32))) + { + index = i; + } + } + + return index; + } + } +} \ No newline at end of file