Browse Source

Merge pull request #205 from JimBobSquarePants/bug-fix-gif

Bug fix gif
af/merge-core
James Jackson-South 9 years ago
committed by GitHub
parent
commit
6c6ee24a3c
  1. 20
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  2. 32
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  3. 2476
      src/ImageSharp/Formats/Gif/spec-gif89a.txt
  4. 10
      src/ImageSharp/Image/ImageFrame{TPixel}.cs
  5. 3
      src/ImageSharp/Image/Image{TPixel}.cs
  6. 11
      src/ImageSharp/MetaData/IMetaData.cs
  7. 13
      src/ImageSharp/MetaData/ImageFrameMetaData.cs
  8. 20
      src/ImageSharp/MetaData/ImageMetaData.cs
  9. 15
      src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs
  10. 3
      src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
  11. 2
      src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
  12. 4
      src/ImageSharp/Quantizers/Quantizer{TPixel}.cs
  13. 3
      src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs
  14. 3
      tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs
  15. 3
      tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs

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

@ -8,6 +8,7 @@ namespace ImageSharp.Formats
using System; using System;
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -332,6 +333,7 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param> /// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param>
/// <param name="indices">The pixel array to write to.</param> /// <param name="indices">The pixel array to write to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices) private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices)
{ {
int dataSize = this.currentStream.ReadByte(); int dataSize = this.currentStream.ReadByte();
@ -366,7 +368,7 @@ namespace ImageSharp.Formats
// This initializes the image to become fully transparent because the alpha channel is zero. // This initializes the image to become fully transparent because the alpha channel is zero.
this.image = Image.Create<TPixel>(imageWidth, imageHeight, this.metaData, this.configuration); this.image = Image.Create<TPixel>(imageWidth, imageHeight, this.metaData, this.configuration);
this.SetFrameDelay(this.metaData); this.SetFrameMetaData(this.metaData);
image = this.image; image = this.image;
} }
@ -380,7 +382,7 @@ namespace ImageSharp.Formats
currentFrame = this.previousFrame.Clone(); currentFrame = this.previousFrame.Clone();
this.SetFrameDelay(currentFrame.MetaData); this.SetFrameMetaData(currentFrame.MetaData);
image = currentFrame; image = currentFrame;
@ -506,14 +508,20 @@ namespace ImageSharp.Formats
} }
/// <summary> /// <summary>
/// Sets the frame delay in the metadata. /// Sets the frames metadata.
/// </summary> /// </summary>
/// <param name="metaData">The meta data.</param> /// <param name="metaData">The meta data.</param>
private void SetFrameDelay(IMetaData metaData) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetFrameMetaData(IMetaData metaData)
{ {
if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) if (this.graphicsControlExtension != null)
{ {
metaData.FrameDelay = this.graphicsControlExtension.DelayTime; if (this.graphicsControlExtension.DelayTime > 0)
{
metaData.FrameDelay = this.graphicsControlExtension.DelayTime;
}
metaData.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
} }
} }
} }

32
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -35,6 +35,11 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private int bitDepth; private int bitDepth;
/// <summary>
/// Whether the current image has multiple frames.
/// </summary>
private bool hasFrames;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class. /// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary> /// </summary>
@ -74,7 +79,13 @@ namespace ImageSharp.Formats
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality); this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality);
// Quantize the image returning a palette. // Quantize the image returning a palette.
QuantizedImage<TPixel> quantized = ((IQuantizer<TPixel>)this.Quantizer).Quantize(image, quality); this.hasFrames = image.Frames.Any();
// Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames.
IQuantizer<TPixel> ditheredQuantizer = (IQuantizer<TPixel>)this.Quantizer;
ditheredQuantizer.Dither = !this.hasFrames;
QuantizedImage<TPixel> quantized = ditheredQuantizer.Quantize(image, quality);
int index = this.GetTransparentIndex(quantized); int index = this.GetTransparentIndex(quantized);
@ -92,7 +103,7 @@ namespace ImageSharp.Formats
this.WriteImageData(quantized, writer); this.WriteImageData(quantized, writer);
// Write additional frames. // Write additional frames.
if (image.Frames.Any()) if (this.hasFrames)
{ {
this.WriteApplicationExtension(writer, image.MetaData.RepeatCount, image.Frames.Count); this.WriteApplicationExtension(writer, image.MetaData.RepeatCount, image.Frames.Count);
@ -100,7 +111,7 @@ namespace ImageSharp.Formats
for (int i = 0; i < image.Frames.Count; i++) for (int i = 0; i < image.Frames.Count; i++)
{ {
ImageFrame<TPixel> frame = image.Frames[i]; ImageFrame<TPixel> frame = image.Frames[i];
QuantizedImage<TPixel> quantizedFrame = ((IQuantizer<TPixel>)this.Quantizer).Quantize(frame, quality); QuantizedImage<TPixel> quantizedFrame = ditheredQuantizer.Quantize(frame, quality);
this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame)); this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame));
this.WriteImageDescriptor(frame, writer); this.WriteImageDescriptor(frame, writer);
@ -180,7 +191,7 @@ namespace ImageSharp.Formats
{ {
Width = (short)image.Width, Width = (short)image.Width,
Height = (short)image.Height, Height = (short)image.Height,
GlobalColorTableFlag = false, // Always false for now. GlobalColorTableFlag = false, // TODO: Always false for now.
GlobalColorTableSize = this.bitDepth - 1, GlobalColorTableSize = this.bitDepth - 1,
BackgroundColorIndex = (byte)tranparencyIndex BackgroundColorIndex = (byte)tranparencyIndex
}; };
@ -224,8 +235,7 @@ namespace ImageSharp.Formats
writer.Write((byte)1); // Data sub-block index (always 1) writer.Write((byte)1); // Data sub-block index (always 1)
// 0 means loop indefinitely. Count is set as play n + 1 times. // 0 means loop indefinitely. Count is set as play n + 1 times.
repeatCount = (ushort)(Math.Max((ushort)0, repeatCount) - 1); repeatCount = (ushort)Math.Max(0, repeatCount - 1);
writer.Write(repeatCount); // Repeat count for images. writer.Write(repeatCount); // Repeat count for images.
writer.Write(GifConstants.Terminator); // Terminator writer.Write(GifConstants.Terminator); // Terminator
@ -302,16 +312,10 @@ namespace ImageSharp.Formats
private void WriteGraphicalControlExtension<TPixel>(ImageBase<TPixel> image, IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex) private void WriteGraphicalControlExtension<TPixel>(ImageBase<TPixel> image, IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// TODO: Check transparency logic.
bool hasTransparent = transparencyIndex < 255;
DisposalMethod disposalMethod = hasTransparent
? DisposalMethod.RestoreToBackground
: DisposalMethod.Unspecified;
GifGraphicsControlExtension extension = new GifGraphicsControlExtension() GifGraphicsControlExtension extension = new GifGraphicsControlExtension()
{ {
DisposalMethod = disposalMethod, DisposalMethod = metaData.DisposalMethod,
TransparencyFlag = hasTransparent, TransparencyFlag = transparencyIndex < 255,
TransparencyIndex = transparencyIndex, TransparencyIndex = transparencyIndex,
DelayTime = metaData.FrameDelay DelayTime = metaData.FrameDelay
}; };

2476
src/ImageSharp/Formats/Gif/spec-gif89a.txt

File diff suppressed because it is too large

10
src/ImageSharp/Image/ImageFrame{TPixel}.cs

@ -30,6 +30,16 @@ namespace ImageSharp
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}"/> class.
/// </summary>
/// <param name="image">The image to create the frame from.</param>
public ImageFrame(ImageFrame<TPixel> image)
: base(image)
{
this.CopyProperties(image);
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}"/> class. /// Initializes a new instance of the <see cref="ImageFrame{TPixel}"/> class.
/// </summary> /// </summary>

3
src/ImageSharp/Image/Image{TPixel}.cs

@ -79,7 +79,6 @@ namespace ImageSharp
public Image(ImageBase<TPixel> other) public Image(ImageBase<TPixel> other)
: base(other) : base(other)
{ {
this.MetaData = new ImageMetaData();
} }
/// <summary> /// <summary>
@ -107,7 +106,7 @@ namespace ImageSharp
/// <summary> /// <summary>
/// Gets the meta data of the image. /// Gets the meta data of the image.
/// </summary> /// </summary>
public ImageMetaData MetaData { get; private set; } public ImageMetaData MetaData { get; private set; } = new ImageMetaData();
/// <summary> /// <summary>
/// Gets the width of the image in inches. It is calculated as the width of the image /// Gets the width of the image in inches. It is calculated as the width of the image

11
src/ImageSharp/MetaData/IMetaData.cs

@ -5,6 +5,8 @@
namespace ImageSharp namespace ImageSharp
{ {
using ImageSharp.Formats;
/// <summary> /// <summary>
/// Encapsulates the metadata of an image frame. /// Encapsulates the metadata of an image frame.
/// </summary> /// </summary>
@ -12,10 +14,17 @@ namespace ImageSharp
{ {
/// <summary> /// <summary>
/// Gets or sets the frame delay for animated images. /// Gets or sets the frame delay for animated images.
/// If not 0, this field specifies the number of hundredths (1/100) of a second to /// If not 0, when utilized in Gif animation, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream. /// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered. /// The clock starts ticking immediately after the graphic is rendered.
/// </summary> /// </summary>
int FrameDelay { get; set; } int FrameDelay { get; set; }
/// <summary>
/// Gets or sets the disposal method for animated images.
/// Primarily used in Gif animation, this field indicates the way in which the graphic is to
/// be treated after being displayed.
/// </summary>
DisposalMethod DisposalMethod { get; set; }
} }
} }

13
src/ImageSharp/MetaData/ImageFrameMetaData.cs

@ -5,6 +5,8 @@
namespace ImageSharp namespace ImageSharp
{ {
using ImageSharp.Formats;
/// <summary> /// <summary>
/// Encapsulates the metadata of an image frame. /// Encapsulates the metadata of an image frame.
/// </summary> /// </summary>
@ -29,14 +31,13 @@ namespace ImageSharp
DebugGuard.NotNull(other, nameof(other)); DebugGuard.NotNull(other, nameof(other));
this.FrameDelay = other.FrameDelay; this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
} }
/// <summary> /// <inheritdoc/>
/// Gets or sets the frame delay for animated images.
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
/// </summary>
public int FrameDelay { get; set; } public int FrameDelay { get; set; }
/// <inheritdoc/>
public DisposalMethod DisposalMethod { get; set; }
} }
} }

20
src/ImageSharp/MetaData/ImageMetaData.cs

@ -7,6 +7,7 @@ namespace ImageSharp
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ImageSharp.Formats;
/// <summary> /// <summary>
/// Encapsulates the metadata of an image. /// Encapsulates the metadata of an image.
@ -52,6 +53,7 @@ namespace ImageSharp
this.VerticalResolution = other.VerticalResolution; this.VerticalResolution = other.VerticalResolution;
this.Quality = other.Quality; this.Quality = other.Quality;
this.FrameDelay = other.FrameDelay; this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
this.RepeatCount = other.RepeatCount; this.RepeatCount = other.RepeatCount;
foreach (ImageProperty property in other.Properties) foreach (ImageProperty property in other.Properties)
@ -83,10 +85,10 @@ namespace ImageSharp
set set
{ {
if (value > 0) if (value > 0)
{ {
this.horizontalResolution = value; this.horizontalResolution = value;
} }
} }
} }
@ -116,14 +118,12 @@ namespace ImageSharp
/// </summary> /// </summary>
public ExifProfile ExifProfile { get; set; } public ExifProfile ExifProfile { get; set; }
/// <summary> /// <inheritdoc/>
/// Gets or sets the frame delay for animated images.
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
/// </summary>
public int FrameDelay { get; set; } public int FrameDelay { get; set; }
/// <inheritdoc/>
public DisposalMethod DisposalMethod { get; set; }
/// <summary> /// <summary>
/// Gets the list of properties for storing meta information about this image. /// Gets the list of properties for storing meta information about this image.
/// </summary> /// </summary>

15
src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs

@ -9,7 +9,7 @@ namespace ImageSharp.Quantizers
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
/// <summary> /// <summary>
/// Provides methods for allowing quantization of images pixels. /// Provides methods for for allowing quantization of images pixels with configurable dithering.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IQuantizer<TPixel> : IQuantizer public interface IQuantizer<TPixel> : IQuantizer
@ -27,11 +27,9 @@ namespace ImageSharp.Quantizers
} }
/// <summary> /// <summary>
/// Provides methods for allowing dithering of quantized image pixels. /// Provides methods for allowing quantization of images pixels with configurable dithering.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> public interface IQuantizer
public interface IDitheredQuantizer<TPixel> : IQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to apply dithering to the output image. /// Gets or sets a value indicating whether to apply dithering to the output image.
@ -43,11 +41,4 @@ namespace ImageSharp.Quantizers
/// </summary> /// </summary>
IErrorDiffuser DitherType { get; set; } IErrorDiffuser DitherType { get; set; }
} }
/// <summary>
/// Provides methods for allowing quantization of images pixels.
/// </summary>
public interface IQuantizer
{
}
} }

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

@ -60,6 +60,7 @@ namespace ImageSharp.Quantizers
{ {
this.colors = maxColors.Clamp(1, 255); this.colors = maxColors.Clamp(1, 255);
this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors)); this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors));
this.palette = null;
return base.Quantize(image, this.colors); return base.Quantize(image, this.colors);
} }
@ -137,7 +138,7 @@ namespace ImageSharp.Quantizers
{ {
// The colors have changed so we need to use Euclidean distance caclulation to find the closest value. // The colors have changed so we need to use Euclidean distance caclulation to find the closest value.
// This palette can never be null here. // This palette can never be null here.
return this.GetClosesTPixel(pixel, this.palette, this.colorMap); return this.GetClosestPixel(pixel, this.palette, this.colorMap);
} }
return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer);

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

@ -133,7 +133,7 @@ namespace ImageSharp.Quantizers
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte QuantizePixel(TPixel pixel) private byte QuantizePixel(TPixel pixel)
{ {
return this.GetClosesTPixel(pixel, this.GetPalette(), this.colorMap); return this.GetClosestPixel(pixel, this.GetPalette(), this.colorMap);
} }
} }
} }

4
src/ImageSharp/Quantizers/Quantizer{TPixel}.cs

@ -15,7 +15,7 @@ namespace ImageSharp.Quantizers
/// Encapsulates methods to calculate the color palette of an image. /// Encapsulates methods to calculate the color palette of an image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
public abstract class Quantizer<TPixel> : IDitheredQuantizer<TPixel> public abstract class Quantizer<TPixel> : IQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
@ -144,7 +144,7 @@ namespace ImageSharp.Quantizers
/// <param name="cache">The cache to store the result in.</param> /// <param name="cache">The cache to store the result in.</param>
/// <returns>The <see cref="byte"/></returns> /// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
protected byte GetClosesTPixel(TPixel pixel, TPixel[] colorPalette, Dictionary<TPixel, byte> cache) protected byte GetClosestPixel(TPixel pixel, TPixel[] colorPalette, Dictionary<TPixel, byte> cache)
{ {
// Check if the color is in the lookup table // Check if the color is in the lookup table
if (cache.ContainsKey(pixel)) if (cache.ContainsKey(pixel))

3
src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs

@ -138,6 +138,7 @@ namespace ImageSharp.Quantizers
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
this.colors = maxColors.Clamp(1, 255); this.colors = maxColors.Clamp(1, 255);
this.palette = null;
try try
{ {
@ -832,7 +833,7 @@ namespace ImageSharp.Quantizers
{ {
// The colors have changed so we need to use Euclidean distance caclulation to find the closest value. // The colors have changed so we need to use Euclidean distance caclulation to find the closest value.
// This palette can never be null here. // This palette can never be null here.
return this.GetClosesTPixel(pixel, this.palette, this.colorMap); return this.GetClosestPixel(pixel, this.palette, this.colorMap);
} }
// Expected order r->g->b->a // Expected order r->g->b->a

3
tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs

@ -5,6 +5,7 @@
namespace ImageSharp.Tests namespace ImageSharp.Tests
{ {
using ImageSharp.Formats;
using Xunit; using Xunit;
/// <summary> /// <summary>
@ -17,10 +18,12 @@ namespace ImageSharp.Tests
{ {
ImageFrameMetaData metaData = new ImageFrameMetaData(); ImageFrameMetaData metaData = new ImageFrameMetaData();
metaData.FrameDelay = 42; metaData.FrameDelay = 42;
metaData.DisposalMethod = DisposalMethod.RestoreToBackground;
ImageFrameMetaData clone = new ImageFrameMetaData(metaData); ImageFrameMetaData clone = new ImageFrameMetaData(metaData);
Assert.Equal(42, clone.FrameDelay); Assert.Equal(42, clone.FrameDelay);
Assert.Equal(DisposalMethod.RestoreToBackground, clone.DisposalMethod);
} }
} }
} }

3
tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs

@ -5,6 +5,7 @@
namespace ImageSharp.Tests namespace ImageSharp.Tests
{ {
using ImageSharp.Formats;
using Xunit; using Xunit;
/// <summary> /// <summary>
@ -27,6 +28,7 @@ namespace ImageSharp.Tests
metaData.Properties.Add(imageProperty); metaData.Properties.Add(imageProperty);
metaData.Quality = 24; metaData.Quality = 24;
metaData.RepeatCount = 1; metaData.RepeatCount = 1;
metaData.DisposalMethod = DisposalMethod.RestoreToBackground;
ImageMetaData clone = new ImageMetaData(metaData); ImageMetaData clone = new ImageMetaData(metaData);
@ -37,6 +39,7 @@ namespace ImageSharp.Tests
Assert.Equal(imageProperty, clone.Properties[0]); Assert.Equal(imageProperty, clone.Properties[0]);
Assert.Equal(24, clone.Quality); Assert.Equal(24, clone.Quality);
Assert.Equal(1, clone.RepeatCount); Assert.Equal(1, clone.RepeatCount);
Assert.Equal(DisposalMethod.RestoreToBackground, clone.DisposalMethod);
} }
[Fact] [Fact]

Loading…
Cancel
Save