Browse Source

Fix pixel tearing with animated gifs.

pull/205/head
James Jackson-South 9 years ago
parent
commit
9b1f7c4bdb
  1. 17
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  2. 2476
      src/ImageSharp/Formats/Gif/spec-gif89a.txt
  3. 17
      src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs
  4. 3
      src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
  5. 2
      src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
  6. 4
      src/ImageSharp/Quantizers/Quantizer{TPixel}.cs
  7. 3
      src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs

17
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 hasMultipleFrames;
/// <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.hasMultipleFrames = 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.hasMultipleFrames;
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.hasMultipleFrames)
{
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);

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

File diff suppressed because it is too large

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

@ -1,4 +1,4 @@
// <copyright file="IQuantizer{TPixel}.cs" company="James Jackson-South">
// <copyright file="IDitheredQuantizer{TPixel}.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -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

Loading…
Cancel
Save