diff --git a/src/ImageSharp/Formats/Gif/FrameDecodingMode.cs b/src/ImageSharp/Formats/Gif/FrameDecodingMode.cs
new file mode 100644
index 000000000..05791c92e
--- /dev/null
+++ b/src/ImageSharp/Formats/Gif/FrameDecodingMode.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Gif
+{
+ ///
+ /// Enumerated frame process modes to apply to multi-frame images.
+ ///
+ public enum FrameDecodingMode
+ {
+ ///
+ /// Decodes all the frames of a multi-frame image.
+ ///
+ All,
+
+ ///
+ /// Decodes only the first frame of a multi-frame image.
+ ///
+ First
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs
index 5ded251e2..11b5b57fa 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs
@@ -24,6 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
+ ///
+ /// Gets or sets the decoding mode for multi-frame images
+ ///
+ public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
+
///
public Image Decode(Configuration configuration, Stream stream)
where TPixel : struct, IPixel
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index bfd441259..453197b0c 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -84,6 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
this.TextEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding;
this.IgnoreMetadata = options.IgnoreMetadata;
+ this.DecodingMode = options.DecodingMode;
this.configuration = configuration ?? Configuration.Default;
}
@@ -95,7 +96,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// Gets the text encoding
///
- public Encoding TextEncoding { get; private set; }
+ public Encoding TextEncoding { get; }
+
+ ///
+ /// Gets the decoding mode for multi-frame images
+ ///
+ public FrameDecodingMode DecodingMode { get; }
///
/// Decodes the stream to the image.
@@ -129,6 +135,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
if (nextFlag == GifConstants.ImageLabel)
{
+ if (this.previousFrame != null && this.DecodingMode == FrameDecodingMode.First)
+ {
+ break;
+ }
+
this.ReadFrame();
}
else if (nextFlag == GifConstants.ExtensionIntroducer)
@@ -238,14 +249,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
throw new ImageFormatException($"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'");
}
-
- /* // No point doing this as the max width/height is always int.Max and that always bigger than the max size of a gif which is stored in a short.
- if (this.logicalScreenDescriptor.Width > Image.MaxWidth || this.logicalScreenDescriptor.Height > Image.MaxHeight)
- {
- throw new ArgumentOutOfRangeException(
- $"The input gif '{this.logicalScreenDescriptor.Width}x{this.logicalScreenDescriptor.Height}' is bigger then the max allowed size '{Image.MaxWidth}x{Image.MaxHeight}'");
- }
- */
}
///
@@ -311,10 +314,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
try
{
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
- int length = this.globalColorTableLength;
if (imageDescriptor.LocalColorTableFlag)
{
- length = imageDescriptor.LocalColorTableSize * 3;
+ int length = imageDescriptor.LocalColorTableSize * 3;
localColorTable = ArrayPool.Shared.Rent(length);
this.currentStream.Read(localColorTable, 0, length);
}
@@ -322,7 +324,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
indices = ArrayPool.Shared.Rent(imageDescriptor.Width * imageDescriptor.Height);
this.ReadFrameIndices(imageDescriptor, indices);
- this.ReadFrameColors(indices, localColorTable ?? this.globalColorTable, length, imageDescriptor);
+ this.ReadFrameColors(indices, localColorTable ?? this.globalColorTable, imageDescriptor);
// Skip any remaining blocks
this.Skip(0);
@@ -358,18 +360,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// The indexed pixels.
/// The color table containing the available colors.
- /// The color table length.
/// The
- private unsafe void ReadFrameColors(byte[] indices, byte[] colorTable, int colorTableLength, GifImageDescriptor descriptor)
+ private void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor)
{
int imageWidth = this.logicalScreenDescriptor.Width;
int imageHeight = this.logicalScreenDescriptor.Height;
- ImageFrame previousFrame = null;
+ ImageFrame prevFrame = null;
ImageFrame currentFrame = null;
- ImageFrame image;
+ ImageFrame imageFrame;
if (this.previousFrame == null)
{
@@ -378,23 +379,23 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.SetFrameMetaData(this.image.Frames.RootFrame.MetaData);
- image = this.image.Frames.RootFrame;
+ imageFrame = this.image.Frames.RootFrame;
}
else
{
if (this.graphicsControlExtension != null &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
{
- previousFrame = this.previousFrame;
+ prevFrame = this.previousFrame;
}
- currentFrame = this.image.Frames.AddFrame(this.previousFrame); // this clones the frame and adds it the collection
+ currentFrame = this.image.Frames.AddFrame(this.previousFrame); // This clones the frame and adds it the collection
this.SetFrameMetaData(currentFrame.MetaData);
- image = currentFrame;
+ imageFrame = currentFrame;
- this.RestoreToBackground(image);
+ this.RestoreToBackground(imageFrame);
}
int i = 0;
@@ -439,9 +440,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
writeY = y;
}
- Span rowSpan = image.GetPixelRowSpan(writeY);
+ Span rowSpan = imageFrame.GetPixelRowSpan(writeY);
- Rgba32 rgba = new Rgba32(0, 0, 0, 255);
+ var rgba = new Rgba32(0, 0, 0, 255);
for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++)
{
@@ -463,13 +464,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
}
- if (previousFrame != null)
+ if (prevFrame != null)
{
- this.previousFrame = previousFrame;
+ this.previousFrame = prevFrame;
return;
}
- this.previousFrame = currentFrame == null ? this.image.Frames.RootFrame : currentFrame;
+ this.previousFrame = currentFrame ?? this.image.Frames.RootFrame;
if (this.graphicsControlExtension != null &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
@@ -518,18 +519,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// Sets the frames metadata.
///
- /// The meta data.
+ /// The meta data.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void SetFrameMetaData(ImageFrameMetaData metaData)
+ private void SetFrameMetaData(ImageFrameMetaData meta)
{
if (this.graphicsControlExtension != null)
{
if (this.graphicsControlExtension.DelayTime > 0)
{
- metaData.FrameDelay = this.graphicsControlExtension.DelayTime;
+ meta.FrameDelay = this.graphicsControlExtension.DelayTime;
}
- metaData.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
+ meta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
}
}
}
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index b10f8a2e0..1b145a79e 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/Formats/Gif/IGifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
index 60c39f936..a2288f30a 100644
--- a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
+++ b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
@@ -1,11 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using System;
-using System.Collections.Generic;
-using System.IO;
using System.Text;
-using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Gif
{
@@ -23,5 +19,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Gets the encoding that should be used when reading comments.
///
Encoding TextEncoding { get; }
+
+ ///
+ /// Gets the decoding mode for multi-frame images
+ ///
+ FrameDecodingMode DecodingMode { get; }
}
}
diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
index 8766f1042..d646a680e 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 20ba2e637..d57865c97 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/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
index d04c49a98..cd78add75 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System.Text;
-using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@@ -45,12 +44,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Decode_IgnoreMetadataIsFalse_CommentsAreRead()
{
- GifDecoder options = new GifDecoder()
+ var options = new GifDecoder
{
IgnoreMetadata = false
};
- TestFile testFile = TestFile.Create(TestImages.Gif.Rings);
+ var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image image = testFile.CreateImage(options))
{
@@ -63,12 +62,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored()
{
- GifDecoder options = new GifDecoder()
+ var options = new GifDecoder
{
IgnoreMetadata = true
};
- TestFile testFile = TestFile.Create(TestImages.Gif.Rings);
+ var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image image = testFile.CreateImage(options))
{
@@ -79,12 +78,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding()
{
- GifDecoder options = new GifDecoder()
+ var options = new GifDecoder
{
TextEncoding = Encoding.Unicode
};
- TestFile testFile = TestFile.Create(TestImages.Gif.Rings);
+ var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image image = testFile.CreateImage(options))
{
@@ -92,5 +91,27 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal("浉条卥慨灲", image.MetaData.Properties[0].Value);
}
}
+
+ [Theory]
+ [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
+ public void CanDecodeJustOneFrame(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.First }))
+ {
+ Assert.Equal(1, image.Frames.Count);
+ }
+ }
+
+ [Theory]
+ [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
+ public void CanDecodeAllFrames(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.All }))
+ {
+ Assert.True(image.Frames.Count > 1);
+ }
+ }
}
-}
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
new file mode 100644
index 000000000..a0b14b09b
--- /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