Browse Source

Implemented the IEncoderOptions inside the gif encoder.

af/merge-core
Dirk Lemstra 9 years ago
committed by Dirk Lemstra
parent
commit
2161916564
  1. 14
      src/ImageSharp.Formats.Gif/GifConstants.cs
  2. 2
      src/ImageSharp.Formats.Gif/GifDecoderCore.cs
  3. 4
      src/ImageSharp.Formats.Gif/GifDecoderOptions.cs
  4. 37
      src/ImageSharp.Formats.Gif/GifEncoder.cs
  5. 68
      src/ImageSharp.Formats.Gif/GifEncoderCore.cs
  6. 71
      src/ImageSharp.Formats.Gif/GifEncoderOptions.cs
  7. 38
      src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs
  8. 11
      src/ImageSharp.Formats.Gif/ImageExtensions.cs
  9. 67
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs

14
src/ImageSharp.Formats.Gif/GifConstants.cs

@ -5,10 +5,12 @@
namespace ImageSharp.Formats
{
using System.Text;
/// <summary>
/// Constants that define specific points within a gif.
/// </summary>
internal sealed class GifConstants
internal static class GifConstants
{
/// <summary>
/// The file type.
@ -50,6 +52,11 @@ namespace ImageSharp.Formats
/// </summary>
public const byte CommentLabel = 0xFE;
/// <summary>
/// The name of the property inside the image properties for the comments.
/// </summary>
public const string Comments = "Comments";
/// <summary>
/// The maximum comment length.
/// </summary>
@ -79,5 +86,10 @@ namespace ImageSharp.Formats
/// The end introducer trailer <value>;</value>.
/// </summary>
public const byte EndIntroducer = 0x3B;
/// <summary>
/// Gets the default encoding to use when reading comments.
/// </summary>
public static Encoding DefaultEncoding { get; } = Encoding.GetEncoding("ASCII");
}
}

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

@ -261,7 +261,7 @@ namespace ImageSharp.Formats
{
this.currentStream.Read(commentsBuffer, 0, length);
string comments = this.options.TextEncoding.GetString(commentsBuffer, 0, length);
this.decodedImage.MetaData.Properties.Add(new ImageProperty("Comments", comments));
this.decodedImage.MetaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments));
}
finally
{

4
src/ImageSharp.Formats.Gif/GifDecoderOptions.cs

@ -12,8 +12,6 @@ namespace ImageSharp.Formats
/// </summary>
public sealed class GifDecoderOptions : DecoderOptions, IGifDecoderOptions
{
private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII");
/// <summary>
/// Initializes a new instance of the <see cref="GifDecoderOptions"/> class.
/// </summary>
@ -33,7 +31,7 @@ namespace ImageSharp.Formats
/// <summary>
/// Gets or sets the encoding that should be used when reading comments.
/// </summary>
public Encoding TextEncoding { get; set; } = DefaultEncoding;
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <summary>
/// Converts the options to a <see cref="IGifDecoderOptions"/> instance with a cast

37
src/ImageSharp.Formats.Gif/GifEncoder.cs

@ -8,40 +8,31 @@ namespace ImageSharp.Formats
using System;
using System.IO;
using ImageSharp.Quantizers;
/// <summary>
/// Image encoder for writing image data to a stream in gif format.
/// </summary>
public class GifEncoder : IImageEncoder
{
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
public int Quality { get; set; }
/// <inheritdoc/>
public void Encode<TColor>(Image<TColor> image, Stream stream, IEncoderOptions options)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options);
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 128;
this.Encode(image, stream, gifOptions);
}
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// Encodes the image to the specified stream from the <see cref="Image{TColor}"/>.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <inheritdoc/>
public void Encode<TColor>(Image<TColor> image, Stream stream, IEncoderOptions options)
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TColor}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param>
public void Encode<TColor>(Image<TColor> image, Stream stream, IGifEncoderOptions options)
where TColor : struct, IPixel<TColor>
{
GifEncoderCore encoder = new GifEncoderCore
{
Quality = this.Quality,
Quantizer = this.Quantizer,
Threshold = this.Threshold
};
GifEncoderCore encoder = new GifEncoderCore(options);
encoder.Encode(image, stream);
}
}

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

@ -24,20 +24,23 @@ namespace ImageSharp.Formats
private readonly byte[] buffer = new byte[16];
/// <summary>
/// The number of bits requires to store the image palette.
/// The options for the encoder.
/// </summary>
private int bitDepth;
private readonly IGifEncoderOptions options;
/// <summary>
/// Gets or sets the quality of output for images.
/// The number of bits requires to store the image palette.
/// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
public int Quality { get; set; }
private int bitDepth;
/// <summary>
/// Gets or sets the transparency threshold.
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary>
public byte Threshold { get; set; } = 128;
/// <param name="options">The options for the encoder.</param>
public GifEncoderCore(IGifEncoderOptions options)
{
this.options = options ?? new GifEncoderOptions();
}
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
@ -56,23 +59,20 @@ namespace ImageSharp.Formats
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
if (this.Quantizer == null)
{
this.Quantizer = new OctreeQuantizer<TColor>();
}
this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer<TColor>();
// Do not use IDisposable pattern here as we want to preserve the stream.
EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
// Ensure that quality can be set but has a fallback.
int quality = this.Quality > 0 ? this.Quality : image.MetaData.Quality;
this.Quality = quality > 0 ? quality.Clamp(1, 256) : 256;
int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
quality = quality > 0 ? quality.Clamp(1, 256) : 256;
// Get the number of bits.
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(this.Quality);
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality);
// Quantize the image returning a palette.
QuantizedImage<TColor> quantized = ((IQuantizer<TColor>)this.Quantizer).Quantize(image, this.Quality);
QuantizedImage<TColor> quantized = ((IQuantizer<TColor>)this.Quantizer).Quantize(image, quality);
int index = this.GetTransparentIndex(quantized);
@ -84,6 +84,7 @@ namespace ImageSharp.Formats
// Write the first frame.
this.WriteGraphicalControlExtension(image, writer, index);
this.WriteComments(image, writer);
this.WriteImageDescriptor(image, writer);
this.WriteColorTable(quantized, writer);
this.WriteImageData(quantized, writer);
@ -97,7 +98,7 @@ namespace ImageSharp.Formats
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<TColor> frame = image.Frames[i];
QuantizedImage<TColor> quantizedFrame = ((IQuantizer<TColor>)this.Quantizer).Quantize(frame, this.Quality);
QuantizedImage<TColor> quantizedFrame = ((IQuantizer<TColor>)this.Quantizer).Quantize(frame, quality);
this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame));
this.WriteImageDescriptor(frame, writer);
@ -106,7 +107,7 @@ namespace ImageSharp.Formats
}
}
// TODO: Write Comments extension etc
// TODO: Write extension etc
writer.Write(GifConstants.EndIntroducer);
}
@ -229,6 +230,39 @@ namespace ImageSharp.Formats
}
}
/// <summary>
/// Writes the image comments to the stream.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageBase{TColor}"/> to be encoded.</param>
/// <param name="writer">The stream to write to.</param>
private void WriteComments<TColor>(Image<TColor> image, EndianBinaryWriter writer)
where TColor : struct, IPackedPixel, IEquatable<TColor>
{
if (this.options.IgnoreMetadata == true)
{
return;
}
ImageProperty property = image.MetaData.Properties.FirstOrDefault(p => p.Name == GifConstants.Comments);
if (property == null || string.IsNullOrEmpty(property.Value))
{
return;
}
byte[] comments = this.options.TextEncoding.GetBytes(property.Value);
int count = Math.Min(comments.Length, 255);
this.buffer[0] = GifConstants.ExtensionIntroducer;
this.buffer[1] = GifConstants.CommentLabel;
this.buffer[2] = (byte)count;
writer.Write(this.buffer, 0, 3);
writer.Write(comments, 0, count);
writer.Write(GifConstants.Terminator);
}
/// <summary>
/// Writes the graphics control extension to the stream.
/// </summary>

71
src/ImageSharp.Formats.Gif/GifEncoderOptions.cs

@ -0,0 +1,71 @@
// <copyright file="GifEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.Text;
using Quantizers;
/// <summary>
/// Encapsulates the options for the <see cref="GifEncoder"/>.
/// </summary>
public sealed class GifEncoderOptions : EncoderOptions, IGifEncoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderOptions"/> class.
/// </summary>
public GifEncoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
private GifEncoderOptions(IEncoderOptions options)
: base(options)
{
}
/// <summary>
/// Gets or sets the encoding that should be used when writing comments.
/// </summary>
public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
public int Quality { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 128;
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <summary>
/// Converts the options to a <see cref="IGifEncoderOptions"/> instance with a
/// cast or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <returns>The options for the <see cref="GifEncoder"/>.</returns>
internal static IGifEncoderOptions Create(IEncoderOptions options)
{
IGifEncoderOptions gifOptions = options as IGifEncoderOptions;
if (gifOptions != null)
{
return gifOptions;
}
return new GifEncoderOptions(options);
}
}
}

38
src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs

@ -0,0 +1,38 @@
// <copyright file="IGifEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System.Text;
using Quantizers;
/// <summary>
/// Encapsulates the options for the <see cref="GifEncoder"/>.
/// </summary>
public interface IGifEncoderOptions : IEncoderOptions
{
/// <summary>
/// Gets the encoding that should be used when writing comments.
/// </summary>
Encoding TextEncoding { get; }
/// <summary>
/// Gets the quality of output for images.
/// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks>
int Quality { get; }
/// <summary>
/// Gets the transparency threshold.
/// </summary>
byte Threshold { get; }
/// <summary>
/// Gets the quantizer for reducing the color count.
/// </summary>
IQuantizer Quantizer { get; }
}
}

11
src/ImageSharp.Formats.Gif/ImageExtensions.cs

@ -21,13 +21,18 @@ namespace ImageSharp
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="quality">The quality to save the image to representing the number of colors. Between 1 and 256.</param>
/// <param name="options">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>
/// The <see cref="Image{TColor}"/>.
/// </returns>
public static Image<TColor> SaveAsGif<TColor>(this Image<TColor> source, Stream stream, int quality = 256)
public static Image<TColor> SaveAsGif<TColor>(this Image<TColor> source, Stream stream, IGifEncoderOptions options = null)
where TColor : struct, IPixel<TColor>
=> source.Save(stream, new GifEncoder { Quality = quality });
{
GifEncoder encoder = new GifEncoder();
encoder.Encode(source, stream, options);
return source;
}
}
}

67
tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs

@ -0,0 +1,67 @@
// <copyright file="GifEncoderCoreTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests
{
using System.IO;
using Xunit;
using ImageSharp.Formats;
public class GifEncoderCoreTests
{
[Fact]
public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten()
{
var options = new EncoderOptions()
{
IgnoreMetadata = false
};
TestFile testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image input = testFile.CreateImage())
{
using (MemoryStream memStream = new MemoryStream())
{
input.Save(memStream, new GifFormat(), options);
memStream.Position = 0;
using (Image output = new Image(memStream))
{
Assert.Equal(1, output.MetaData.Properties.Count);
Assert.Equal("Comments", output.MetaData.Properties[0].Name);
Assert.Equal("ImageSharp", output.MetaData.Properties[0].Value);
}
}
}
}
[Fact]
public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten()
{
var options = new GifEncoderOptions()
{
IgnoreMetadata = true
};
TestFile testFile = TestFile.Create(TestImages.Gif.Rings);
using (Image input = testFile.CreateImage())
{
using (MemoryStream memStream = new MemoryStream())
{
input.SaveAsGif(memStream, options);
memStream.Position = 0;
using (Image output = new Image(memStream))
{
Assert.Equal(0, output.MetaData.Properties.Count);
}
}
}
}
}
}
Loading…
Cancel
Save