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.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using ImageSharp.PixelFormats;
@ -332,6 +333,7 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param>
/// <param name="indices">The pixel array to write to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices)
{
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.image = Image.Create<TPixel>(imageWidth, imageHeight, this.metaData, this.configuration);
this.SetFrameDelay(this.metaData);
this.SetFrameMetaData(this.metaData);
image = this.image;
}
@ -380,7 +382,7 @@ namespace ImageSharp.Formats
currentFrame = this.previousFrame.Clone();
this.SetFrameDelay(currentFrame.MetaData);
this.SetFrameMetaData(currentFrame.MetaData);
image = currentFrame;
@ -506,14 +508,20 @@ namespace ImageSharp.Formats
}
/// <summary>
/// Sets the frame delay in the metadata.
/// Sets the frames metadata.
/// </summary>
/// <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>
private int bitDepth;
/// <summary>
/// Whether the current image has multiple frames.
/// </summary>
private bool hasFrames;
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary>
@ -74,7 +79,13 @@ namespace ImageSharp.Formats
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality);
// 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);
@ -92,7 +103,7 @@ namespace ImageSharp.Formats
this.WriteImageData(quantized, writer);
// Write additional frames.
if (image.Frames.Any())
if (this.hasFrames)
{
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++)
{
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.WriteImageDescriptor(frame, writer);
@ -180,7 +191,7 @@ namespace ImageSharp.Formats
{
Width = (short)image.Width,
Height = (short)image.Height,
GlobalColorTableFlag = false, // Always false for now.
GlobalColorTableFlag = false, // TODO: Always false for now.
GlobalColorTableSize = this.bitDepth - 1,
BackgroundColorIndex = (byte)tranparencyIndex
};
@ -224,8 +235,7 @@ namespace ImageSharp.Formats
writer.Write((byte)1); // Data sub-block index (always 1)
// 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(GifConstants.Terminator); // Terminator
@ -302,16 +312,10 @@ namespace ImageSharp.Formats
private void WriteGraphicalControlExtension<TPixel>(ImageBase<TPixel> image, IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex)
where TPixel : struct, IPixel<TPixel>
{
// TODO: Check transparency logic.
bool hasTransparent = transparencyIndex < 255;
DisposalMethod disposalMethod = hasTransparent
? DisposalMethod.RestoreToBackground
: DisposalMethod.Unspecified;
GifGraphicsControlExtension extension = new GifGraphicsControlExtension()
{
DisposalMethod = disposalMethod,
TransparencyFlag = hasTransparent,
DisposalMethod = metaData.DisposalMethod,
TransparencyFlag = transparencyIndex < 255,
TransparencyIndex = transparencyIndex,
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>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}"/> class.
/// </summary>

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

@ -79,7 +79,6 @@ namespace ImageSharp
public Image(ImageBase<TPixel> other)
: base(other)
{
this.MetaData = new ImageMetaData();
}
/// <summary>
@ -107,7 +106,7 @@ namespace ImageSharp
/// <summary>
/// Gets the meta data of the image.
/// </summary>
public ImageMetaData MetaData { get; private set; }
public ImageMetaData MetaData { get; private set; } = new ImageMetaData();
/// <summary>
/// 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
{
using ImageSharp.Formats;
/// <summary>
/// Encapsulates the metadata of an image frame.
/// </summary>
@ -12,10 +14,17 @@ namespace ImageSharp
{
/// <summary>
/// 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.
/// The clock starts ticking immediately after the graphic is rendered.
/// </summary>
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
{
using ImageSharp.Formats;
/// <summary>
/// Encapsulates the metadata of an image frame.
/// </summary>
@ -29,14 +31,13 @@ namespace ImageSharp
DebugGuard.NotNull(other, nameof(other));
this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
}
/// <summary>
/// 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>
/// <inheritdoc/>
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.Collections.Generic;
using ImageSharp.Formats;
/// <summary>
/// Encapsulates the metadata of an image.
@ -52,6 +53,7 @@ namespace ImageSharp
this.VerticalResolution = other.VerticalResolution;
this.Quality = other.Quality;
this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
this.RepeatCount = other.RepeatCount;
foreach (ImageProperty property in other.Properties)
@ -83,10 +85,10 @@ namespace ImageSharp
set
{
if (value > 0)
{
this.horizontalResolution = value;
}
if (value > 0)
{
this.horizontalResolution = value;
}
}
}
@ -116,14 +118,12 @@ namespace ImageSharp
/// </summary>
public ExifProfile ExifProfile { get; set; }
/// <summary>
/// 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>
/// <inheritdoc/>
public int FrameDelay { get; set; }
/// <inheritdoc/>
public DisposalMethod DisposalMethod { get; set; }
/// <summary>
/// Gets the list of properties for storing meta information about this image.
/// </summary>

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

@ -9,7 +9,7 @@ namespace ImageSharp.Quantizers
using ImageSharp.PixelFormats;
/// <summary>
/// Provides methods for allowing quantization of images pixels.
/// Provides methods for for allowing quantization of images pixels with configurable dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IQuantizer<TPixel> : IQuantizer
@ -27,11 +27,9 @@ namespace ImageSharp.Quantizers
}
/// <summary>
/// Provides methods for allowing dithering of quantized image pixels.
/// Provides methods for allowing quantization of images pixels with configurable dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IDitheredQuantizer<TPixel> : IQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
public interface IQuantizer
{
/// <summary>
/// Gets or sets a value indicating whether to apply dithering to the output image.
@ -43,11 +41,4 @@ namespace ImageSharp.Quantizers
/// </summary>
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.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors));
this.palette = null;
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.
// 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);

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

@ -133,7 +133,7 @@ namespace ImageSharp.Quantizers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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.
/// </summary>
/// <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>
{
/// <summary>
@ -144,7 +144,7 @@ namespace ImageSharp.Quantizers
/// <param name="cache">The cache to store the result in.</param>
/// <returns>The <see cref="byte"/></returns>
[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
if (cache.ContainsKey(pixel))

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

@ -138,6 +138,7 @@ namespace ImageSharp.Quantizers
Guard.NotNull(image, nameof(image));
this.colors = maxColors.Clamp(1, 255);
this.palette = null;
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.
// 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

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

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

Loading…
Cancel
Save