diff --git a/src/ImageSharp.Formats.Gif/GifConstants.cs b/src/ImageSharp.Formats.Gif/GifConstants.cs
index 5334bcba3..4af291c2b 100644
--- a/src/ImageSharp.Formats.Gif/GifConstants.cs
+++ b/src/ImageSharp.Formats.Gif/GifConstants.cs
@@ -5,10 +5,12 @@
namespace ImageSharp.Formats
{
+ using System.Text;
+
///
/// Constants that define specific points within a gif.
///
- internal sealed class GifConstants
+ internal static class GifConstants
{
///
/// The file type.
@@ -50,6 +52,11 @@ namespace ImageSharp.Formats
///
public const byte CommentLabel = 0xFE;
+ ///
+ /// The name of the property inside the image properties for the comments.
+ ///
+ public const string Comments = "Comments";
+
///
/// The maximum comment length.
///
@@ -79,5 +86,10 @@ namespace ImageSharp.Formats
/// The end introducer trailer ;.
///
public const byte EndIntroducer = 0x3B;
+
+ ///
+ /// Gets the default encoding to use when reading comments.
+ ///
+ public static Encoding DefaultEncoding { get; } = Encoding.GetEncoding("ASCII");
}
}
diff --git a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs b/src/ImageSharp.Formats.Gif/GifDecoderCore.cs
index 3ab0e6ca8..ab1edc2c7 100644
--- a/src/ImageSharp.Formats.Gif/GifDecoderCore.cs
+++ b/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
{
diff --git a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs
index 5932425e5..8722c5fe8 100644
--- a/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs
+++ b/src/ImageSharp.Formats.Gif/GifDecoderOptions.cs
@@ -12,8 +12,6 @@ namespace ImageSharp.Formats
///
public sealed class GifDecoderOptions : DecoderOptions, IGifDecoderOptions
{
- private static readonly Encoding DefaultEncoding = Encoding.GetEncoding("ASCII");
-
///
/// Initializes a new instance of the class.
///
@@ -33,7 +31,7 @@ namespace ImageSharp.Formats
///
/// Gets or sets the encoding that should be used when reading comments.
///
- public Encoding TextEncoding { get; set; } = DefaultEncoding;
+ public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
///
/// Converts the options to a instance with a cast
diff --git a/src/ImageSharp.Formats.Gif/GifEncoder.cs b/src/ImageSharp.Formats.Gif/GifEncoder.cs
index 402d2d09a..34027ee0c 100644
--- a/src/ImageSharp.Formats.Gif/GifEncoder.cs
+++ b/src/ImageSharp.Formats.Gif/GifEncoder.cs
@@ -8,40 +8,31 @@ namespace ImageSharp.Formats
using System;
using System.IO;
- using ImageSharp.Quantizers;
-
///
/// Image encoder for writing image data to a stream in gif format.
///
public class GifEncoder : IImageEncoder
{
- ///
- /// Gets or sets the quality of output for images.
- ///
- /// For gifs the value ranges from 1 to 256.
- public int Quality { get; set; }
+ ///
+ public void Encode(Image image, Stream stream, IEncoderOptions options)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ IGifEncoderOptions gifOptions = GifEncoderOptions.Create(options);
- ///
- /// Gets or sets the transparency threshold.
- ///
- public byte Threshold { get; set; } = 128;
+ this.Encode(image, stream, gifOptions);
+ }
///
- /// Gets or sets the quantizer for reducing the color count.
+ /// Encodes the image to the specified stream from the .
///
- public IQuantizer Quantizer { get; set; }
-
- ///
- public void Encode(Image image, Stream stream, IEncoderOptions options)
+ /// The pixel format.
+ /// The to encode from.
+ /// The to encode the image data to.
+ /// The options for the encoder.
+ public void Encode(Image image, Stream stream, IGifEncoderOptions options)
where TColor : struct, IPixel
{
- GifEncoderCore encoder = new GifEncoderCore
- {
- Quality = this.Quality,
- Quantizer = this.Quantizer,
- Threshold = this.Threshold
- };
-
+ GifEncoderCore encoder = new GifEncoderCore(options);
encoder.Encode(image, stream);
}
}
diff --git a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs
index 36cf614d9..2c9a0c8b0 100644
--- a/src/ImageSharp.Formats.Gif/GifEncoderCore.cs
+++ b/src/ImageSharp.Formats.Gif/GifEncoderCore.cs
@@ -24,20 +24,23 @@ namespace ImageSharp.Formats
private readonly byte[] buffer = new byte[16];
///
- /// The number of bits requires to store the image palette.
+ /// The options for the encoder.
///
- private int bitDepth;
+ private readonly IGifEncoderOptions options;
///
- /// Gets or sets the quality of output for images.
+ /// The number of bits requires to store the image palette.
///
- /// For gifs the value ranges from 1 to 256.
- public int Quality { get; set; }
+ private int bitDepth;
///
- /// Gets or sets the transparency threshold.
+ /// Initializes a new instance of the class.
///
- public byte Threshold { get; set; } = 128;
+ /// The options for the encoder.
+ public GifEncoderCore(IGifEncoderOptions options)
+ {
+ this.options = options ?? new GifEncoderOptions();
+ }
///
/// 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();
- }
+ this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer();
// 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 quantized = ((IQuantizer)this.Quantizer).Quantize(image, this.Quality);
+ QuantizedImage quantized = ((IQuantizer)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 frame = image.Frames[i];
- QuantizedImage quantizedFrame = ((IQuantizer)this.Quantizer).Quantize(frame, this.Quality);
+ QuantizedImage quantizedFrame = ((IQuantizer)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
}
}
+ ///
+ /// Writes the image comments to the stream.
+ ///
+ /// The pixel format.
+ /// The to be encoded.
+ /// The stream to write to.
+ private void WriteComments(Image image, EndianBinaryWriter writer)
+ where TColor : struct, IPackedPixel, IEquatable
+ {
+ 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);
+ }
+
///
/// Writes the graphics control extension to the stream.
///
diff --git a/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs b/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs
new file mode 100644
index 000000000..94cad9603
--- /dev/null
+++ b/src/ImageSharp.Formats.Gif/GifEncoderOptions.cs
@@ -0,0 +1,71 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats
+{
+ using System.Text;
+
+ using Quantizers;
+
+ ///
+ /// Encapsulates the options for the .
+ ///
+ public sealed class GifEncoderOptions : EncoderOptions, IGifEncoderOptions
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GifEncoderOptions()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The options for the encoder.
+ private GifEncoderOptions(IEncoderOptions options)
+ : base(options)
+ {
+ }
+
+ ///
+ /// Gets or sets the encoding that should be used when writing comments.
+ ///
+ public Encoding TextEncoding { get; set; } = GifConstants.DefaultEncoding;
+
+ ///
+ /// Gets or sets the quality of output for images.
+ ///
+ /// For gifs the value ranges from 1 to 256.
+ public int Quality { get; set; }
+
+ ///
+ /// Gets or sets the transparency threshold.
+ ///
+ public byte Threshold { get; set; } = 128;
+
+ ///
+ /// Gets or sets the quantizer for reducing the color count.
+ ///
+ public IQuantizer Quantizer { get; set; }
+
+ ///
+ /// Converts the options to a instance with a
+ /// cast or by creating a new instance with the specfied options.
+ ///
+ /// The options for the encoder.
+ /// The options for the .
+ internal static IGifEncoderOptions Create(IEncoderOptions options)
+ {
+ IGifEncoderOptions gifOptions = options as IGifEncoderOptions;
+ if (gifOptions != null)
+ {
+ return gifOptions;
+ }
+
+ return new GifEncoderOptions(options);
+ }
+ }
+}
diff --git a/src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs b/src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs
new file mode 100644
index 000000000..c1d6b7ad8
--- /dev/null
+++ b/src/ImageSharp.Formats.Gif/IGifEncoderOptions.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Formats
+{
+ using System.Text;
+
+ using Quantizers;
+
+ ///
+ /// Encapsulates the options for the .
+ ///
+ public interface IGifEncoderOptions : IEncoderOptions
+ {
+ ///
+ /// Gets the encoding that should be used when writing comments.
+ ///
+ Encoding TextEncoding { get; }
+
+ ///
+ /// Gets the quality of output for images.
+ ///
+ /// For gifs the value ranges from 1 to 256.
+ int Quality { get; }
+
+ ///
+ /// Gets the transparency threshold.
+ ///
+ byte Threshold { get; }
+
+ ///
+ /// Gets the quantizer for reducing the color count.
+ ///
+ IQuantizer Quantizer { get; }
+ }
+}
diff --git a/src/ImageSharp.Formats.Gif/ImageExtensions.cs b/src/ImageSharp.Formats.Gif/ImageExtensions.cs
index e0ec2e8c8..dcf4ab29a 100644
--- a/src/ImageSharp.Formats.Gif/ImageExtensions.cs
+++ b/src/ImageSharp.Formats.Gif/ImageExtensions.cs
@@ -21,13 +21,18 @@ namespace ImageSharp
/// The pixel format.
/// The image this method extends.
/// The stream to save the image to.
- /// The quality to save the image to representing the number of colors. Between 1 and 256.
+ /// The options for the encoder.
/// Thrown if the stream is null.
///
/// The .
///
- public static Image SaveAsGif(this Image source, Stream stream, int quality = 256)
+ public static Image SaveAsGif(this Image source, Stream stream, IGifEncoderOptions options = null)
where TColor : struct, IPixel
- => source.Save(stream, new GifEncoder { Quality = quality });
+ {
+ GifEncoder encoder = new GifEncoder();
+ encoder.Encode(source, stream, options);
+
+ return source;
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs
new file mode 100644
index 000000000..6f163bc6e
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderCoreTests.cs
@@ -0,0 +1,67 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+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);
+ }
+ }
+ }
+ }
+ }
+}