diff --git a/src/ImageProcessor/Formats/Gif/GifEncoder.cs b/src/ImageProcessor/Formats/Gif/GifEncoder.cs
index 549333ac4..46bb8dd31 100644
--- a/src/ImageProcessor/Formats/Gif/GifEncoder.cs
+++ b/src/ImageProcessor/Formats/Gif/GifEncoder.cs
@@ -7,22 +7,13 @@ namespace ImageProcessor.Formats
{
using System;
using System.IO;
+ using System.Linq;
///
/// The Gif encoder
///
public class GifEncoder : IImageEncoder
{
- ///
- /// The gif decoder if any used to decode the original image.
- ///
- private GifDecoder gifDecoder;
-
- ///
- /// The currently processed image.
- ///
- private ImageBase currentImage;
-
///
/// Gets or sets the quality of output for images.
///
@@ -62,13 +53,6 @@ namespace ImageProcessor.Formats
Image image = (Image)imageBase;
- // Try to grab and assign an image decoder.
- IImageDecoder decoder = image.CurrentDecoder;
- if (decoder.GetType() == typeof(GifDecoder))
- {
- this.gifDecoder = (GifDecoder)decoder;
- }
-
// Write the header.
// File Header signature and version.
this.WriteString(stream, GifConstants.FileType);
@@ -84,63 +68,41 @@ namespace ImageProcessor.Formats
// Write the LSD and check to see if we need a global color table.
// Always true just now.
bool globalColor = this.WriteGlobalLogicalScreenDescriptor(image, stream, bitdepth);
-
- QuantizedImage quantized;
-
- // Should always be true.
- if (globalColor)
- {
- quantized = this.WriteColorTable(imageBase, stream, quality, bitdepth);
- }
- else
- {
- // Quantize the image returning a pallete.
- IQuantizer quantizer = new OctreeQuantizer(quality.Clamp(1, 255), bitdepth + 1);
- quantized = quantizer.Quantize(image);
- }
+ QuantizedImage quantized = this.WriteColorTable(imageBase, stream, quality, bitdepth);
this.WriteGraphicalControlExtension(imageBase, stream);
+ this.WriteImageDescriptor(quantized, quality, stream);
- // TODO: Write Comments
- this.WriteApplicationExtension(stream, image.RepeatCount, image.Frames.Count);
- this.WriteImageDescriptor(quantized.ToImage(), quality, stream, true);
-
- // TODO: Write Image Info
-
- foreach (ImageFrame frame in image.Frames)
+ if (image.Frames.Any())
{
- this.WriteGraphicalControlExtension(frame, stream);
- this.WriteImageDescriptor(imageBase, quality, stream, false);
+ this.WriteApplicationExtension(stream, image.RepeatCount, image.Frames.Count);
+ foreach (ImageFrame frame in image.Frames)
+ {
+ this.WriteGraphicalControlExtension(frame, stream);
+ this.WriteFrameImageDescriptor(frame, stream);
+ }
}
- // Cleanup
- this.gifDecoder = null;
+ // TODO: Write Comments extension etc
+ this.WriteByte(stream, GifConstants.EndIntroducer);
}
+ ///
+ /// Writes the logical screen descriptor to the stream.
+ ///
+ /// The image to encode.
+ /// The stream to write to.
+ /// The bit depth.
+ /// The
private bool WriteGlobalLogicalScreenDescriptor(Image image, Stream stream, int bitDepth)
{
- GifLogicalScreenDescriptor descriptor;
-
- // Try and grab an existing descriptor.
- if (this.gifDecoder != null)
+ GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor
{
- // Ensure the dimensions etc are up to date.
- descriptor = this.gifDecoder.CoreDecoder.LogicalScreenDescriptor;
- descriptor.Width = (short)image.Width;
- descriptor.Height = (short)image.Height;
- descriptor.GlobalColorTableFlag = true;
- descriptor.GlobalColorTableSize = bitDepth;
- }
- else
- {
- descriptor = new GifLogicalScreenDescriptor
- {
- Width = (short)image.Width,
- Height = (short)image.Height,
- GlobalColorTableFlag = true,
- GlobalColorTableSize = bitDepth
- };
- }
+ Width = (short)image.Width,
+ Height = (short)image.Height,
+ GlobalColorTableFlag = true,
+ GlobalColorTableSize = bitDepth
+ };
this.WriteShort(stream, descriptor.Width);
this.WriteShort(stream, descriptor.Height);
@@ -157,6 +119,14 @@ namespace ImageProcessor.Formats
return descriptor.GlobalColorTableFlag;
}
+ ///
+ /// Writes the color table to the stream.
+ ///
+ /// The to encode.
+ /// The stream to write to.
+ /// The quality (number of colors) to encode the image to.
+ /// The bit depth.
+ /// The
private QuantizedImage WriteColorTable(ImageBase image, Stream stream, int quality, int bitDepth)
{
// Quantize the image returning a pallete.
@@ -166,7 +136,9 @@ namespace ImageProcessor.Formats
// Grab the pallete and write it to the stream.
Bgra[] pallete = quantizedImage.Palette;
int pixelCount = pallete.Length;
- int colorTableLength = pixelCount * 3;
+
+ // Get max colors for bit depth.
+ int colorTableLength = (int)Math.Pow(2, bitDepth) * 3;
byte[] colorTable = new byte[colorTableLength];
for (int i = 0; i < pixelCount; i++)
@@ -183,40 +155,30 @@ namespace ImageProcessor.Formats
return quantizedImage;
}
+ ///
+ /// Writes the graphics control extension to the stream.
+ ///
+ /// The to encode.
+ /// The stream to write to.
private void WriteGraphicalControlExtension(ImageBase image, Stream stream)
{
- GifGraphicsControlExtension extension;
-
// Calculate the quality.
int quality = this.Quality > 0 ? this.Quality : image.Quality;
quality = quality > 0 ? quality.Clamp(1, 256) : 256;
- // Try and grab an existing descriptor.
- // TODO: Check whether we need to.
- if (this.gifDecoder != null)
- {
- // Ensure the dimensions etc are up to date.
- extension = this.gifDecoder.CoreDecoder.GraphicsControlExtension;
- extension.TransparencyFlag = quality > 1;
- extension.TransparencyIndex = quality - 1; // Quantizer set last as transparent.
- extension.DelayTime = image.FrameDelay;
- }
- else
- {
- // TODO: Check transparency logic.
- bool hasTransparent = quality > 1;
- DisposalMethod disposalMethod = hasTransparent
- ? DisposalMethod.RestoreToBackground
- : DisposalMethod.Unspecified;
+ // TODO: Check transparency logic.
+ bool hasTransparent = quality > 1;
+ DisposalMethod disposalMethod = hasTransparent
+ ? DisposalMethod.RestoreToBackground
+ : DisposalMethod.Unspecified;
- extension = new GifGraphicsControlExtension()
- {
- DisposalMethod = disposalMethod,
- TransparencyFlag = hasTransparent,
- TransparencyIndex = quality - 1,
- DelayTime = image.FrameDelay
- };
- }
+ GifGraphicsControlExtension extension = new GifGraphicsControlExtension()
+ {
+ DisposalMethod = disposalMethod,
+ TransparencyFlag = hasTransparent,
+ TransparencyIndex = quality - 1, // Quantizer sets last index as transparent.
+ DelayTime = image.FrameDelay
+ };
this.WriteByte(stream, GifConstants.ExtensionIntroducer);
this.WriteByte(stream, GifConstants.GraphicControlLabel);
@@ -233,6 +195,12 @@ namespace ImageProcessor.Formats
this.WriteByte(stream, GifConstants.Terminator);
}
+ ///
+ /// Writes the application exstension to the stream.
+ ///
+ /// The stream to write to.
+ /// The animated image repeat count.
+ /// Th number of image frames.
private void WriteApplicationExtension(Stream stream, ushort repeatCount, int frames)
{
// Application Extension Header
@@ -254,7 +222,13 @@ namespace ImageProcessor.Formats
}
}
- private void WriteImageDescriptor(ImageBase image, int quality, Stream stream, bool first)
+ ///
+ /// Writes the image descriptor to the stream.
+ ///
+ /// The containing indexed pixels.
+ /// The quality (number of colors) to encode the image to.
+ /// The stream to write to.
+ private void WriteImageDescriptor(QuantizedImage image, int quality, Stream stream)
{
this.WriteByte(stream, GifConstants.ImageDescriptorLabel); // 2c
// TODO: Can we capture this?
@@ -263,43 +237,59 @@ namespace ImageProcessor.Formats
this.WriteShort(stream, image.Width);
this.WriteShort(stream, image.Height);
- if (first)
- {
- // Calculate the quality.
- int bitdepth = this.GetBitsNeededForColorDepth(quality);
+ // Calculate the quality.
+ int bitdepth = this.GetBitsNeededForColorDepth(quality);
- // No LCT use GCT.
- this.WriteByte(stream, 0);
+ // No LCT use GCT.
+ this.WriteByte(stream, 0);
- // Write the image data. Pixels have already been quantized.
- this.WriteImageData(image, stream, bitdepth);
- }
- else
- {
- // Calculate the quality.
- quality = this.Quality > 0 ? this.Quality : image.Quality;
- quality = quality > 0 ? quality.Clamp(1, 256) : 256;
- int bitdepth = this.GetBitsNeededForColorDepth(quality);
-
- int packed = 0x80 | // 1: Local color table flag = 1 (LCT used)
- 0x00 | // 2: Interlace flag 0
- 0x00 | // 3: Sort flag 0
- 0 | // 4-5: Reserved
- bitdepth - 1;
-
- this.WriteByte(stream, packed);
-
- // Now immediately follow with the color table.
- QuantizedImage quantized = this.WriteColorTable(image, stream, quality, bitdepth);
- this.WriteImageData(quantized.ToImage(), stream, bitdepth);
- }
+ // Write the image data..
+ this.WriteImageData(image, stream, bitdepth);
+ }
- this.WriteByte(stream, GifConstants.EndIntroducer);
+ ///
+ /// Writes the image descriptor to the stream.
+ ///
+ /// The to be encoded.
+ /// The stream to write to.
+ private void WriteFrameImageDescriptor(ImageBase image, Stream stream)
+ {
+ this.WriteByte(stream, GifConstants.ImageDescriptorLabel); // 2c
+ // TODO: Can we capture this?
+ this.WriteShort(stream, 0); // Left position
+ this.WriteShort(stream, 0); // Top position
+ this.WriteShort(stream, image.Width);
+ this.WriteShort(stream, image.Height);
+
+ // Calculate the quality.
+ int quality = this.Quality > 0 ? this.Quality : image.Quality;
+ quality = quality > 0 ? quality.Clamp(1, 256) : 256;
+ int bitdepth = this.GetBitsNeededForColorDepth(quality);
+
+ int packed = 0x80 | // 1: Local color table flag = 1 (LCT used)
+ 0x00 | // 2: Interlace flag 0
+ 0x00 | // 3: Sort flag 0
+ 0 | // 4-5: Reserved
+ bitdepth - 1;
+
+ this.WriteByte(stream, packed);
+
+ // Now immediately follow with the color table.
+ QuantizedImage quantized = this.WriteColorTable(image, stream, quality, bitdepth);
+ this.WriteImageData(quantized, stream, bitdepth);
}
- private void WriteImageData(ImageBase image, Stream stream, int bitDepth)
+ ///
+ /// Writes the image pixel data to the stream.
+ ///
+ /// The containing indexed pixels.
+ /// The stream to write to.
+ /// The bit depth of the image.
+ private void WriteImageData(QuantizedImage image, Stream stream, int bitDepth)
{
- LzwEncoder encoder = new LzwEncoder(image.Pixels, (byte)bitDepth);
+ byte[] indexedPixels = image.Pixels;
+
+ LzwEncoder encoder = new LzwEncoder(indexedPixels, (byte)bitDepth);
encoder.Encode(stream);
this.WriteByte(stream, GifConstants.Terminator);
diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs
index a7ebe8ec3..1cf697589 100644
--- a/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs
+++ b/src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs
@@ -338,7 +338,7 @@ namespace ImageProcessor.Formats
private int paletteIndex;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
///
/// The level in the tree = 0 - 7
@@ -415,7 +415,7 @@ namespace ImageProcessor.Formats
if (null == child)
{
- // Create a new child node & store in the array
+ // Create a new child node and store it in the array
child = new OctreeNode(level + 1, colorBits, octree);
this.children[index] = child;
}
@@ -511,8 +511,8 @@ namespace ImageProcessor.Formats
{
int shift = 7 - level;
int pixelIndex = ((pixel.R & Mask[level]) >> (shift - 2)) |
- ((pixel.G & Mask[level]) >> (shift - 1)) |
- ((pixel.B & Mask[level]) >> shift);
+ ((pixel.G & Mask[level]) >> (shift - 1)) |
+ ((pixel.B & Mask[level]) >> shift);
if (null != this.children[pixelIndex])
{
diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
index ec00bf508..9efe62ec3 100644
--- a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
+++ b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
@@ -61,9 +61,11 @@ namespace ImageProcessor.Formats
byte[] quantizedPixels = new byte[width * height];
+ List palette = this.GetPalette();
+
this.SecondPass(imageBase, quantizedPixels, width, height);
- return new QuantizedImage(width, height, this.GetPalette().ToArray(), quantizedPixels);
+ return new QuantizedImage(width, height, palette.ToArray(), quantizedPixels);
}
///
@@ -158,4 +160,4 @@ namespace ImageProcessor.Formats
///
protected abstract List GetPalette();
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageProcessor/Image.cs b/src/ImageProcessor/Image.cs
index ded47cc3f..8a4adb1f2 100644
--- a/src/ImageProcessor/Image.cs
+++ b/src/ImageProcessor/Image.cs
@@ -232,11 +232,6 @@ namespace ImageProcessor
/// A list of image properties.
public IList Properties { get; } = new List();
- ///
- /// The current decoder
- ///
- internal IImageDecoder CurrentDecoder { get; set; }
-
///
/// Loads the image from the given stream.
///
@@ -276,8 +271,7 @@ namespace ImageProcessor
IImageDecoder decoder = decoders.FirstOrDefault(x => x.IsSupportedFileFormat(header));
if (decoder != null)
{
- this.CurrentDecoder = decoder;
- this.CurrentDecoder.Decode(this, stream);
+ decoder.Decode(this, stream);
return;
}
}
diff --git a/src/ImageProcessor/ImageBase.cs b/src/ImageProcessor/ImageBase.cs
index 22362f0e5..c90516861 100644
--- a/src/ImageProcessor/ImageBase.cs
+++ b/src/ImageProcessor/ImageBase.cs
@@ -145,7 +145,7 @@ namespace ImageProcessor
throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width.");
}
- if ((y < 0) || (y >= this.Width))
+ if ((y < 0) || (y >= this.Height))
{
throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height.");
}
@@ -163,7 +163,7 @@ namespace ImageProcessor
throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width.");
}
- if ((y < 0) || (y >= this.Width))
+ if ((y < 0) || (y >= this.Height))
{
throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height.");
}
diff --git a/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs b/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
index 86732b1e9..dae8f8763 100644
--- a/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
+++ b/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
@@ -11,14 +11,14 @@
public class EncoderDecoderTests
{
[Theory]
- //[InlineData("TestImages/Car.bmp")]
- //[InlineData("TestImages/Portrait.png")]
- //[InlineData("../../TestImages/Formats/Jpg/Backdrop.jpg")]
+ [InlineData("../../TestImages/Formats/Jpg/Backdrop.jpg")]
[InlineData("../../TestImages/Formats/Gif/a.gif")]
- //[InlineData("../../TestImages/Formats/Gif/leaf.gif")]
- //[InlineData("TestImages/Windmill.gif")]
- //[InlineData("../../TestImages/Formats/Bmp/Car.bmp")]
- //[InlineData("../../TestImages/Formats/Png/cmyk.png")]
+ [InlineData("../../TestImages/Formats/Gif/leaf.gif")]
+ [InlineData("../../TestImages/Formats/Gif/ani.gif")]
+ [InlineData("../../TestImages/Formats/Gif/ani2.gif")]
+ [InlineData("../../TestImages/Formats/Gif/giphy.gif")]
+ [InlineData("../../TestImages/Formats/Bmp/Car.bmp")]
+ [InlineData("../../TestImages/Formats/Png/cmyk.png")]
public void DecodeThenEncodeImageFromStreamShouldSucceed(string filename)
{
if (!Directory.Exists("Encoded"))
@@ -29,7 +29,7 @@
FileStream stream = File.OpenRead(filename);
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
-
+
string encodedFilename = "Encoded/" + Path.GetFileName(filename);
//if (!image.IsAnimated)
@@ -62,8 +62,8 @@
[Theory]
[InlineData("../../TestImages/Formats/Jpg/Backdrop.jpg")]
- [InlineData("../../TestImages/Formats/Bmp/Car.bmp")]
- [InlineData("../../TestImages/Formats/Png/cmyk.png")]
+ //[InlineData("../../TestImages/Formats/Bmp/Car.bmp")]
+ //[InlineData("../../TestImages/Formats/Png/cmyk.png")]
public void QuantizedImageShouldPreserveMaximumColorPrecision(string filename)
{
if (!Directory.Exists("Quantized"))
@@ -74,7 +74,7 @@
Image image = new Image(File.OpenRead(filename));
IQuantizer quantizer = new OctreeQuantizer();
QuantizedImage quantizedImage = quantizer.Quantize(image);
-
+ var pixel = quantizedImage.Pixels;
using (FileStream output = File.OpenWrite($"Quantized/{ Path.GetFileName(filename) }"))
{
IImageEncoder encoder = Image.Encoders.First(e => e.IsSupportedFileExtension(Path.GetExtension(filename)));
diff --git a/tests/ImageProcessor.Tests/TestImages/Formats/Gif/ani.gif.REMOVED.git-id b/tests/ImageProcessor.Tests/TestImages/Formats/Gif/ani.gif.REMOVED.git-id
new file mode 100644
index 000000000..9b07936a0
--- /dev/null
+++ b/tests/ImageProcessor.Tests/TestImages/Formats/Gif/ani.gif.REMOVED.git-id
@@ -0,0 +1 @@
+a0cc93222effb5feec0d1a1dc45efd0c5af77450
\ No newline at end of file
diff --git a/tests/ImageProcessor.Tests/TestImages/Formats/Gif/ani2.gif.REMOVED.git-id b/tests/ImageProcessor.Tests/TestImages/Formats/Gif/ani2.gif.REMOVED.git-id
new file mode 100644
index 000000000..c8fe9de52
--- /dev/null
+++ b/tests/ImageProcessor.Tests/TestImages/Formats/Gif/ani2.gif.REMOVED.git-id
@@ -0,0 +1 @@
+c2c7a5fcc0f00cdef39dacd7df0816137b3d63a3
\ No newline at end of file
diff --git a/tests/ImageProcessor.Tests/TestImages/Formats/Gif/giphy.gif b/tests/ImageProcessor.Tests/TestImages/Formats/Gif/giphy.gif
new file mode 100644
index 000000000..1f2618fba
Binary files /dev/null and b/tests/ImageProcessor.Tests/TestImages/Formats/Gif/giphy.gif differ