Browse Source

Merge pull request #365 from SixLabors/js/fix-255

Enable decoding  only single frame from animated gif + fix quantization issue
af/merge-core
James Jackson-South 9 years ago
committed by GitHub
parent
commit
162b199015
  1. 21
      src/ImageSharp/Formats/Gif/FrameDecodingMode.cs
  2. 5
      src/ImageSharp/Formats/Gif/GifDecoder.cs
  3. 61
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  4. 10
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  5. 9
      src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
  6. 122
      src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
  7. 2
      src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs
  8. 37
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  9. 95
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs

21
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
{
/// <summary>
/// Enumerated frame process modes to apply to multi-frame images.
/// </summary>
public enum FrameDecodingMode
{
/// <summary>
/// Decodes all the frames of a multi-frame image.
/// </summary>
All,
/// <summary>
/// Decodes only the first frame of a multi-frame image.
/// </summary>
First
}
}

5
src/ImageSharp/Formats/Gif/GifDecoder.cs

@ -24,6 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding; public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <summary>
/// Gets or sets the decoding mode for multi-frame images
/// </summary>
public FrameDecodingMode DecodingMode { get; set; } = FrameDecodingMode.All;
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream) public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>

61
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -84,6 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
this.TextEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; this.TextEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding;
this.IgnoreMetadata = options.IgnoreMetadata; this.IgnoreMetadata = options.IgnoreMetadata;
this.DecodingMode = options.DecodingMode;
this.configuration = configuration ?? Configuration.Default; this.configuration = configuration ?? Configuration.Default;
} }
@ -95,7 +96,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// Gets the text encoding /// Gets the text encoding
/// </summary> /// </summary>
public Encoding TextEncoding { get; private set; } public Encoding TextEncoding { get; }
/// <summary>
/// Gets the decoding mode for multi-frame images
/// </summary>
public FrameDecodingMode DecodingMode { get; }
/// <summary> /// <summary>
/// Decodes the stream to the image. /// Decodes the stream to the image.
@ -129,6 +135,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
if (nextFlag == GifConstants.ImageLabel) if (nextFlag == GifConstants.ImageLabel)
{ {
if (this.previousFrame != null && this.DecodingMode == FrameDecodingMode.First)
{
break;
}
this.ReadFrame(); this.ReadFrame();
} }
else if (nextFlag == GifConstants.ExtensionIntroducer) else if (nextFlag == GifConstants.ExtensionIntroducer)
@ -238,14 +249,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
throw new ImageFormatException($"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'"); 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<TPixel>.MaxWidth || this.logicalScreenDescriptor.Height > Image<TPixel>.MaxHeight)
{
throw new ArgumentOutOfRangeException(
$"The input gif '{this.logicalScreenDescriptor.Width}x{this.logicalScreenDescriptor.Height}' is bigger then the max allowed size '{Image<TPixel>.MaxWidth}x{Image<TPixel>.MaxHeight}'");
}
*/
} }
/// <summary> /// <summary>
@ -311,10 +314,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
try try
{ {
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table. // 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) if (imageDescriptor.LocalColorTableFlag)
{ {
length = imageDescriptor.LocalColorTableSize * 3; int length = imageDescriptor.LocalColorTableSize * 3;
localColorTable = ArrayPool<byte>.Shared.Rent(length); localColorTable = ArrayPool<byte>.Shared.Rent(length);
this.currentStream.Read(localColorTable, 0, length); this.currentStream.Read(localColorTable, 0, length);
} }
@ -322,7 +324,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
indices = ArrayPool<byte>.Shared.Rent(imageDescriptor.Width * imageDescriptor.Height); indices = ArrayPool<byte>.Shared.Rent(imageDescriptor.Width * imageDescriptor.Height);
this.ReadFrameIndices(imageDescriptor, indices); this.ReadFrameIndices(imageDescriptor, indices);
this.ReadFrameColors(indices, localColorTable ?? this.globalColorTable, length, imageDescriptor); this.ReadFrameColors(indices, localColorTable ?? this.globalColorTable, imageDescriptor);
// Skip any remaining blocks // Skip any remaining blocks
this.Skip(0); this.Skip(0);
@ -358,18 +360,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
/// <param name="indices">The indexed pixels.</param> /// <param name="indices">The indexed pixels.</param>
/// <param name="colorTable">The color table containing the available colors.</param> /// <param name="colorTable">The color table containing the available colors.</param>
/// <param name="colorTableLength">The color table length.</param>
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param> /// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
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 imageWidth = this.logicalScreenDescriptor.Width;
int imageHeight = this.logicalScreenDescriptor.Height; int imageHeight = this.logicalScreenDescriptor.Height;
ImageFrame<TPixel> previousFrame = null; ImageFrame<TPixel> prevFrame = null;
ImageFrame<TPixel> currentFrame = null; ImageFrame<TPixel> currentFrame = null;
ImageFrame<TPixel> image; ImageFrame<TPixel> imageFrame;
if (this.previousFrame == null) if (this.previousFrame == null)
{ {
@ -378,23 +379,23 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.SetFrameMetaData(this.image.Frames.RootFrame.MetaData); this.SetFrameMetaData(this.image.Frames.RootFrame.MetaData);
image = this.image.Frames.RootFrame; imageFrame = this.image.Frames.RootFrame;
} }
else else
{ {
if (this.graphicsControlExtension != null && if (this.graphicsControlExtension != null &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) 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); this.SetFrameMetaData(currentFrame.MetaData);
image = currentFrame; imageFrame = currentFrame;
this.RestoreToBackground(image); this.RestoreToBackground(imageFrame);
} }
int i = 0; int i = 0;
@ -439,9 +440,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
writeY = y; writeY = y;
} }
Span<TPixel> rowSpan = image.GetPixelRowSpan(writeY); Span<TPixel> 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++) 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; return;
} }
this.previousFrame = currentFrame == null ? this.image.Frames.RootFrame : currentFrame; this.previousFrame = currentFrame ?? this.image.Frames.RootFrame;
if (this.graphicsControlExtension != null && if (this.graphicsControlExtension != null &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground) this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
@ -518,18 +519,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// Sets the frames metadata. /// Sets the frames metadata.
/// </summary> /// </summary>
/// <param name="metaData">The meta data.</param> /// <param name="meta">The meta data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetFrameMetaData(ImageFrameMetaData metaData) private void SetFrameMetaData(ImageFrameMetaData meta)
{ {
if (this.graphicsControlExtension != null) if (this.graphicsControlExtension != null)
{ {
if (this.graphicsControlExtension.DelayTime > 0) 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;
} }
} }
} }

10
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 // Transparent pixels are much more likely to be found at the end of a palette
int index = -1; int index = -1;
var trans = default(Rgba32);
for (int i = quantized.Palette.Length - 1; i >= 0; i--) 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) if (trans.Equals(default(Rgba32)))
{
continue;
}
else
{ {
index = i; index = i;
break;
} }
} }

9
src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs

@ -1,11 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text; using System.Text;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Gif namespace SixLabors.ImageSharp.Formats.Gif
{ {
@ -23,5 +19,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Gets the encoding that should be used when reading comments. /// Gets the encoding that should be used when reading comments.
/// </summary> /// </summary>
Encoding TextEncoding { get; } Encoding TextEncoding { get; }
/// <summary>
/// Gets the decoding mode for multi-frame images
/// </summary>
FrameDecodingMode DecodingMode { get; }
} }
} }

122
src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs

@ -23,11 +23,6 @@ namespace SixLabors.ImageSharp.Quantizers
/// </summary> /// </summary>
private readonly Dictionary<TPixel, byte> colorMap = new Dictionary<TPixel, byte>(); private readonly Dictionary<TPixel, byte> colorMap = new Dictionary<TPixel, byte>();
/// <summary>
/// The pixel buffer, used to reduce allocations.
/// </summary>
private readonly byte[] pixelBuffer = new byte[4];
/// <summary> /// <summary>
/// Stores the tree /// Stores the tree
/// </summary> /// </summary>
@ -43,6 +38,11 @@ namespace SixLabors.ImageSharp.Quantizers
/// </summary> /// </summary>
private TPixel[] palette; private TPixel[] palette;
/// <summary>
/// The transparent index
/// </summary>
private byte transparentIndex;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer{TPixel}"/> class. /// Initializes a new instance of the <see cref="OctreeQuantizer{TPixel}"/> class.
/// </summary> /// </summary>
@ -73,7 +73,8 @@ namespace SixLabors.ImageSharp.Quantizers
// pass of the algorithm by avoiding transforming rows of identical color. // pass of the algorithm by avoiding transforming rows of identical color.
TPixel sourcePixel = source[0, 0]; TPixel sourcePixel = source[0, 0];
TPixel previousPixel = sourcePixel; TPixel previousPixel = sourcePixel;
byte pixelValue = this.QuantizePixel(sourcePixel); var rgba = default(Rgba32);
byte pixelValue = this.QuantizePixel(sourcePixel, ref rgba);
TPixel[] colorPalette = this.GetPalette(); TPixel[] colorPalette = this.GetPalette();
TPixel transformedPixel = colorPalette[pixelValue]; TPixel transformedPixel = colorPalette[pixelValue];
@ -92,7 +93,7 @@ namespace SixLabors.ImageSharp.Quantizers
if (!previousPixel.Equals(sourcePixel)) if (!previousPixel.Equals(sourcePixel))
{ {
// Quantize the pixel // Quantize the pixel
pixelValue = this.QuantizePixel(sourcePixel); pixelValue = this.QuantizePixel(sourcePixel, ref rgba);
// And setup the previous pointer // And setup the previous pointer
previousPixel = sourcePixel; previousPixel = sourcePixel;
@ -118,24 +119,57 @@ namespace SixLabors.ImageSharp.Quantizers
protected override void InitialQuantizePixel(TPixel pixel) protected override void InitialQuantizePixel(TPixel pixel)
{ {
// Add the color to the Octree // Add the color to the Octree
this.octree.AddColor(pixel, this.pixelBuffer); var rgba = default(Rgba32);
this.octree.AddColor(pixel, ref rgba);
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override TPixel[] GetPalette() 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;
}
/// <summary>
/// Returns the index of the first instance of the transparent color in the palette.
/// </summary>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
[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;
} }
/// <summary> /// <summary>
/// Process the pixel in the second pass of the algorithm /// Process the pixel in the second pass of the algorithm
/// </summary> /// </summary>
/// <param name="pixel">The pixel to quantize</param> /// <param name="pixel">The pixel to quantize</param>
/// <param name="rgba">The color to compare against</param>
/// <returns> /// <returns>
/// The quantized value /// The quantized value
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte QuantizePixel(TPixel pixel) private byte QuantizePixel(TPixel pixel, ref Rgba32 rgba)
{ {
if (this.Dither) if (this.Dither)
{ {
@ -144,13 +178,13 @@ namespace SixLabors.ImageSharp.Quantizers
return this.GetClosestPixel(pixel, this.palette, this.colorMap); return this.GetClosestPixel(pixel, this.palette, this.colorMap);
} }
pixel.ToXyzwBytes(this.pixelBuffer, 0); pixel.ToRgba32(ref rgba);
if (this.pixelBuffer[3] == 0) 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);
} }
/// <summary> /// <summary>
@ -233,8 +267,8 @@ namespace SixLabors.ImageSharp.Quantizers
/// Add a given color value to the Octree /// Add a given color value to the Octree
/// </summary> /// </summary>
/// <param name="pixel">The pixel data.</param> /// <param name="pixel">The pixel data.</param>
/// <param name="buffer">The buffer array.</param> /// <param name="rgba">The color.</param>
public void AddColor(TPixel pixel, byte[] buffer) public void AddColor(TPixel pixel, ref Rgba32 rgba)
{ {
// Check if this request is for the same color as the last // Check if this request is for the same color as the last
if (this.previousColor.Equals(pixel)) if (this.previousColor.Equals(pixel))
@ -244,18 +278,18 @@ namespace SixLabors.ImageSharp.Quantizers
if (this.previousNode == null) if (this.previousNode == null)
{ {
this.previousColor = pixel; this.previousColor = pixel;
this.root.AddColor(pixel, this.maxColorBits, 0, this, buffer); this.root.AddColor(pixel, this.maxColorBits, 0, this, ref rgba);
} }
else else
{ {
// Just update the previous node // Just update the previous node
this.previousNode.Increment(pixel, buffer); this.previousNode.Increment(pixel, ref rgba);
} }
} }
else else
{ {
this.previousColor = pixel; 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 /// Get the palette index for the passed color
/// </summary> /// </summary>
/// <param name="pixel">The pixel data.</param> /// <param name="pixel">The pixel data.</param>
/// <param name="buffer">The buffer array.</param> /// <param name="rgba">The color to map to.</param>
/// <returns> /// <returns>
/// The <see cref="int"/>. /// The <see cref="int"/>.
/// </returns> /// </returns>
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);
} }
/// <summary> /// <summary>
@ -415,17 +449,17 @@ namespace SixLabors.ImageSharp.Quantizers
/// <summary> /// <summary>
/// Add a color into the tree /// Add a color into the tree
/// </summary> /// </summary>
/// <param name="pixel">The color</param> /// <param name="pixel">The pixel color</param>
/// <param name="colorBits">The number of significant color bits</param> /// <param name="colorBits">The number of significant color bits</param>
/// <param name="level">The level in the tree</param> /// <param name="level">The level in the tree</param>
/// <param name="octree">The tree to which this node belongs</param> /// <param name="octree">The tree to which this node belongs</param>
/// <param name="buffer">The buffer array.</param> /// <param name="rgba">The color to map to.</param>
public void AddColor(TPixel pixel, int colorBits, int level, Octree octree, byte[] buffer) public void AddColor(TPixel pixel, int colorBits, int level, Octree octree, ref Rgba32 rgba)
{ {
// Update the color information if this is a leaf // Update the color information if this is a leaf
if (this.leaf) if (this.leaf)
{ {
this.Increment(pixel, buffer); this.Increment(pixel, ref rgba);
// Setup the previous node // Setup the previous node
octree.TrackPrevious(this); octree.TrackPrevious(this);
@ -434,11 +468,11 @@ namespace SixLabors.ImageSharp.Quantizers
{ {
// Go to the next level down in the tree // Go to the next level down in the tree
int shift = 7 - level; int shift = 7 - level;
pixel.ToXyzwBytes(buffer, 0); pixel.ToRgba32(ref rgba);
int index = ((buffer[2] & Mask[level]) >> (shift - 2)) | int index = ((rgba.B & Mask[level]) >> (shift - 2)) |
((buffer[1] & Mask[level]) >> (shift - 1)) | ((rgba.G & Mask[level]) >> (shift - 1)) |
((buffer[0] & Mask[level]) >> shift); ((rgba.R & Mask[level]) >> shift);
OctreeNode child = this.children[index]; OctreeNode child = this.children[index];
@ -450,7 +484,7 @@ namespace SixLabors.ImageSharp.Quantizers
} }
// Add the color to the child node // 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
/// </summary> /// </summary>
/// <param name="pixel">The pixel data.</param> /// <param name="pixel">The pixel data.</param>
/// <param name="level">The level.</param> /// <param name="level">The level.</param>
/// <param name="buffer">The buffer array.</param> /// <param name="rgba">The color to map to.</param>
/// <returns> /// <returns>
/// The <see cref="int"/> representing the index of the pixel in the palette. /// The <see cref="int"/> representing the index of the pixel in the palette.
/// </returns> /// </returns>
public int GetPaletteIndex(TPixel pixel, int level, byte[] buffer) public int GetPaletteIndex(TPixel pixel, int level, ref Rgba32 rgba)
{ {
int index = this.paletteIndex; int index = this.paletteIndex;
if (!this.leaf) if (!this.leaf)
{ {
int shift = 7 - level; int shift = 7 - level;
pixel.ToXyzwBytes(buffer, 0); pixel.ToRgba32(ref rgba);
int pixelIndex = ((buffer[2] & Mask[level]) >> (shift - 2)) | int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2)) |
((buffer[1] & Mask[level]) >> (shift - 1)) | ((rgba.G & Mask[level]) >> (shift - 1)) |
((buffer[0] & Mask[level]) >> shift); ((rgba.R & Mask[level]) >> shift);
if (this.children[pixelIndex] != null) 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 else
{ {
@ -558,14 +592,14 @@ namespace SixLabors.ImageSharp.Quantizers
/// Increment the pixel count and add to the color information /// Increment the pixel count and add to the color information
/// </summary> /// </summary>
/// <param name="pixel">The pixel to add.</param> /// <param name="pixel">The pixel to add.</param>
/// <param name="buffer">The buffer array.</param> /// <param name="rgba">The color to map to.</param>
public void Increment(TPixel pixel, byte[] buffer) public void Increment(TPixel pixel, ref Rgba32 rgba)
{ {
pixel.ToXyzwBytes(buffer, 0); pixel.ToRgba32(ref rgba);
this.pixelCount++; this.pixelCount++;
this.red += buffer[0]; this.red += rgba.R;
this.green += buffer[1]; this.green += rgba.G;
this.blue += buffer[2]; this.blue += rgba.B;
} }
} }
} }

2
src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs

@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Quantizers.Base
} }
/// <inheritdoc /> /// <inheritdoc />
public bool Dither { get; set; } = true; public bool Dither { get; set; } = false;
/// <inheritdoc /> /// <inheritdoc />
public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser(); public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser();

37
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Text; using System.Text;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -45,12 +44,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() public void Decode_IgnoreMetadataIsFalse_CommentsAreRead()
{ {
GifDecoder options = new GifDecoder() var options = new GifDecoder
{ {
IgnoreMetadata = false IgnoreMetadata = false
}; };
TestFile testFile = TestFile.Create(TestImages.Gif.Rings); var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image<Rgba32> image = testFile.CreateImage(options)) using (Image<Rgba32> image = testFile.CreateImage(options))
{ {
@ -63,12 +62,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored()
{ {
GifDecoder options = new GifDecoder() var options = new GifDecoder
{ {
IgnoreMetadata = true IgnoreMetadata = true
}; };
TestFile testFile = TestFile.Create(TestImages.Gif.Rings); var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image<Rgba32> image = testFile.CreateImage(options)) using (Image<Rgba32> image = testFile.CreateImage(options))
{ {
@ -79,12 +78,12 @@ namespace SixLabors.ImageSharp.Tests
[Fact] [Fact]
public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding()
{ {
GifDecoder options = new GifDecoder() var options = new GifDecoder
{ {
TextEncoding = Encoding.Unicode TextEncoding = Encoding.Unicode
}; };
TestFile testFile = TestFile.Create(TestImages.Gif.Rings); var testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image<Rgba32> image = testFile.CreateImage(options)) using (Image<Rgba32> image = testFile.CreateImage(options))
{ {
@ -92,5 +91,27 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal("浉条卥慨灲", image.MetaData.Properties[0].Value); Assert.Equal("浉条卥慨灲", image.MetaData.Properties[0].Value);
} }
} }
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
public void CanDecodeJustOneFrame<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.First }))
{
Assert.Equal(1, image.Frames.Count);
}
}
[Theory]
[WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)]
public void CanDecodeAllFrames<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.All }))
{
Assert.True(image.Frames.Count > 1);
}
}
} }
} }

95
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<TPixel>(TestImageProvider<TPixel> provider, bool dither)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Assert.True(image[0, 0].Equals(default(TPixel)));
IQuantizer<TPixel> quantizer = new PaletteQuantizer<TPixel> { Dither = dither };
foreach (ImageFrame<TPixel> frame in image.Frames)
{
QuantizedImage<TPixel> 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<TPixel>(TestImageProvider<TPixel> provider, bool dither)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Assert.True(image[0, 0].Equals(default(TPixel)));
IQuantizer<TPixel> quantizer = new OctreeQuantizer<TPixel> { Dither = dither };
foreach (ImageFrame<TPixel> frame in image.Frames)
{
QuantizedImage<TPixel> 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<TPixel>(TestImageProvider<TPixel> provider, bool dither)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
Assert.True(image[0, 0].Equals(default(TPixel)));
IQuantizer<TPixel> quantizer = new WuQuantizer<TPixel> { Dither = dither };
foreach (ImageFrame<TPixel> frame in image.Frames)
{
QuantizedImage<TPixel> quantized = quantizer.Quantize(frame, 256);
int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.Pixels[0]);
}
}
}
private int GetTransparentIndex<TPixel>(QuantizedImage<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
{
// 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;
}
}
}
Loading…
Cancel
Save