diff --git a/src/ImageProcessor/Common/Extensions/ByteExtensions.cs b/src/ImageProcessor/Common/Extensions/ByteExtensions.cs
index 0c0bc6b1f..958326f17 100644
--- a/src/ImageProcessor/Common/Extensions/ByteExtensions.cs
+++ b/src/ImageProcessor/Common/Extensions/ByteExtensions.cs
@@ -1,7 +1,20 @@
-namespace ImageProcessor
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Extension methods for the struct.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor
{
using System;
+ ///
+ /// Extension methods for the struct.
+ ///
internal static class ByteExtensions
{
///
diff --git a/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs b/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
index e8df9337d..d3c7d7155 100644
--- a/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
@@ -25,7 +25,7 @@
private byte[] globalColorTable;
private byte[] _currentFrame;
private GifLogicalScreenDescriptor logicalScreenDescriptor;
- private GifGraphicsControlExtension _graphicsControl;
+ private GifGraphicsControlExtension graphicsControlExtension;
public void Decode(Image image, Stream stream)
{
@@ -64,6 +64,7 @@
this.ReadComments();
break;
case ApplicationExtensionLabel:
+ // TODO: Read Application extension
this.Skip(12);
break;
case PlainTextLabel:
@@ -88,7 +89,7 @@
byte packed = buffer[1];
- _graphicsControl = new GifGraphicsControlExtension
+ this.graphicsControlExtension = new GifGraphicsControlExtension
{
DelayTime = BitConverter.ToInt16(buffer, 2),
TransparencyIndex = buffer[4],
@@ -97,6 +98,12 @@
};
}
+ private void ReadApplicationBlockExtension()
+ {
+ // TODO: Implement
+ throw new NotImplementedException();
+ }
+
private GifImageDescriptor ReadImageDescriptor()
{
byte[] buffer = new byte[9];
@@ -105,14 +112,16 @@
byte packed = buffer[8];
- GifImageDescriptor imageDescriptor = new GifImageDescriptor();
- imageDescriptor.Left = BitConverter.ToInt16(buffer, 0);
- imageDescriptor.Top = BitConverter.ToInt16(buffer, 2);
- imageDescriptor.Width = BitConverter.ToInt16(buffer, 4);
- imageDescriptor.Height = BitConverter.ToInt16(buffer, 6);
- imageDescriptor.LocalColorTableFlag = ((packed & 0x80) >> 7) == 1;
- imageDescriptor.LocalColorTableSize = 2 << (packed & 0x07);
- imageDescriptor.InterlaceFlag = ((packed & 0x40) >> 6) == 1;
+ GifImageDescriptor imageDescriptor = new GifImageDescriptor
+ {
+ Left = BitConverter.ToInt16(buffer, 0),
+ Top = BitConverter.ToInt16(buffer, 2),
+ Width = BitConverter.ToInt16(buffer, 4),
+ Height = BitConverter.ToInt16(buffer, 6),
+ LocalColorTableFlag = ((packed & 0x80) >> 7) == 1,
+ LocalColorTableSize = 2 << (packed & 0x07),
+ InterlaceFlag = ((packed & 0x40) >> 6) == 1
+ };
return imageDescriptor;
}
@@ -137,18 +146,14 @@
if (this.logicalScreenDescriptor.GlobalColorTableSize > 255 * 4)
{
- throw new ImageFormatException(string.Format("Invalid gif colormap size '{0}'", this.logicalScreenDescriptor.GlobalColorTableSize));
+ throw new ImageFormatException(
+ $"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'");
}
if (this.logicalScreenDescriptor.Width > ImageBase.MaxWidth || this.logicalScreenDescriptor.Height > ImageBase.MaxHeight)
{
throw new ArgumentOutOfRangeException(
- string.Format(
- "The input gif '{0}x{1}' is bigger then the max allowed size '{2}x{3}'",
- this.logicalScreenDescriptor.Width,
- this.logicalScreenDescriptor.Height,
- ImageBase.MaxWidth,
- ImageBase.MaxHeight));
+ $"The input gif '{this.logicalScreenDescriptor.Width}x{this.logicalScreenDescriptor.Height}' is bigger then the max allowed size '{ImageBase.MaxWidth}x{ImageBase.MaxHeight}'");
}
}
@@ -156,7 +161,7 @@
{
this.currentStream.Seek(length, SeekOrigin.Current);
- int flag = 0;
+ int flag;
while ((flag = this.currentStream.ReadByte()) != 0)
{
@@ -166,13 +171,13 @@
private void ReadComments()
{
- int flag = 0;
+ int flag;
while ((flag = this.currentStream.ReadByte()) != 0)
{
if (flag > MaxCommentLength)
{
- throw new ImageFormatException(string.Format("Gif comment length '{0}' exceeds max '{1}'", flag, MaxCommentLength));
+ throw new ImageFormatException($"Gif comment length '{flag}' exceeds max '{MaxCommentLength}'");
}
byte[] buffer = new byte[flag];
@@ -230,19 +235,19 @@
int imageWidth = this.logicalScreenDescriptor.Width;
int imageHeight = this.logicalScreenDescriptor.Height;
- if (_currentFrame == null)
+ if (this._currentFrame == null)
{
- _currentFrame = new byte[imageWidth * imageHeight * 4];
+ this._currentFrame = new byte[imageWidth * imageHeight * 4];
}
byte[] lastFrame = null;
- if (_graphicsControl != null &&
- _graphicsControl.DisposalMethod == DisposalMethod.RestoreToPrevious)
+ if (this.graphicsControlExtension != null &&
+ this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
{
lastFrame = new byte[imageWidth * imageHeight * 4];
- Array.Copy(_currentFrame, lastFrame, lastFrame.Length);
+ Array.Copy(this._currentFrame, lastFrame, lastFrame.Length);
}
int offset = 0, i = 0, index = -1;
@@ -294,9 +299,9 @@
index = indices[i];
- if (_graphicsControl == null ||
- _graphicsControl.TransparencyFlag == false ||
- _graphicsControl.TransparencyIndex != index)
+ if (this.graphicsControlExtension == null ||
+ this.graphicsControlExtension.TransparencyFlag == false ||
+ this.graphicsControlExtension.TransparencyIndex != index)
{
_currentFrame[offset * 4 + 0] = colorTable[index * 3 + 2];
_currentFrame[offset * 4 + 1] = colorTable[index * 3 + 1];
@@ -319,9 +324,9 @@
currentImage = this.image;
currentImage.SetPixels(imageWidth, imageHeight, pixels);
- if (_graphicsControl != null && _graphicsControl.DelayTime > 0)
+ if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0)
{
- this.image.FrameDelay = _graphicsControl.DelayTime;
+ this.image.FrameDelay = this.graphicsControlExtension.DelayTime;
}
}
else
@@ -334,9 +339,9 @@
this.image.Frames.Add(frame);
}
- if (_graphicsControl != null)
+ if (this.graphicsControlExtension != null)
{
- if (_graphicsControl.DisposalMethod == DisposalMethod.RestoreToBackground)
+ if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
{
for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++)
{
@@ -351,7 +356,7 @@
}
}
}
- else if (_graphicsControl.DisposalMethod == DisposalMethod.RestoreToPrevious)
+ else if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
{
_currentFrame = lastFrame;
}
diff --git a/src/ImageProcessor/Formats/Gif/GifEncoder.cs b/src/ImageProcessor/Formats/Gif/GifEncoder.cs
index 8d630f808..e903d9ac3 100644
--- a/src/ImageProcessor/Formats/Gif/GifEncoder.cs
+++ b/src/ImageProcessor/Formats/Gif/GifEncoder.cs
@@ -11,6 +11,8 @@ namespace ImageProcessor.Formats
///
private int quality = 256;
+ private ImageBase image;
+
///
/// Gets or sets the quality of output for images.
///
@@ -59,6 +61,8 @@ namespace ImageProcessor.Formats
Guard.NotNull(image, "image");
Guard.NotNull(stream, "stream");
+ this.image = image;
+
// Write the header.
// File Header signature and version.
this.WriteString(stream, "GIF");
@@ -95,12 +99,38 @@ namespace ImageProcessor.Formats
this.WriteByte(stream, descriptor.PixelAspectRatio); // Pixel aspect ratio
// Write the global color table.
- this.WriteColorTable(stream, size);
+ this.WriteColorTable(stream, bitdepth);
}
- private void WriteColorTable(Stream stream, int size)
+ private void WriteColorTable(Stream stream, int bitDepth)
{
+ // Quantize the image returning a pallete.
+ IQuantizer quantizer = new OctreeQuantizer(Math.Max(1, this.quality - 1), bitDepth);
+ QuantizedImage quantizedImage = quantizer.Quantize(this.image);
+ this.image = quantizedImage.ToImage();
+
+ // Grab the pallete and write it to the stream.
+ Bgra[] pallete = quantizedImage.Palette;
+ int pixelCount = pallete.Length;
+ int colorTableLength = pixelCount * 3;
+ byte[] colorTable = new byte[colorTableLength];
+
+ for (int i = 0; i < pixelCount; i++)
+ {
+ int offset = i * 4;
+ Bgra color = pallete[i];
+ colorTable[offset + 0] = color.B;
+ colorTable[offset + 1] = color.G;
+ colorTable[offset + 2] = color.R;
+ }
+ stream.Write(colorTable, 0, colorTableLength);
+ }
+
+ private void WriteApplicationExtension(Stream stream)
+ {
+ // TODO: Implement
+ throw new NotImplementedException();
}
///
diff --git a/src/ImageProcessor/Formats/Gif/LzwDecoder.cs b/src/ImageProcessor/Formats/Gif/LzwDecoder.cs
index 8fa2e7880..1c3d5d28b 100644
--- a/src/ImageProcessor/Formats/Gif/LzwDecoder.cs
+++ b/src/ImageProcessor/Formats/Gif/LzwDecoder.cs
@@ -56,7 +56,7 @@ namespace ImageProcessor.Formats
/// The decoded and uncompressed array.
public byte[] DecodePixels(int width, int height, int dataSize)
{
- Guard.LessThan(dataSize, int.MaxValue, "dataSize");
+ Guard.LessThan(dataSize, int.MaxValue, nameof(dataSize));
// The resulting index table.
byte[] pixels = new byte[width * height];
diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs b/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
index 1d4d3b3f7..627eee2ac 100644
--- a/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
+++ b/src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
@@ -42,11 +42,16 @@ namespace ImageProcessor.Formats
///
public QuantizedImage(int width, int height, Bgra[] palette, byte[] pixels)
{
- if (width <= 0) throw new ArgumentOutOfRangeException(nameof(width));
- if (height <= 0) throw new ArgumentOutOfRangeException(nameof(height));
- if (palette == null) throw new ArgumentNullException(nameof(palette));
- if (pixels == null) throw new ArgumentNullException(nameof(pixels));
- if (pixels.Length != width * height) throw new ArgumentException("Pixel array size must be width * height", nameof(pixels));
+ Guard.GreaterThan(width, 0, nameof(width));
+ Guard.GreaterThan(height, 0, nameof(height));
+ Guard.NotNull(palette, nameof(palette));
+ Guard.NotNull(pixels, nameof(pixels));
+
+ if (pixels.Length != width * height)
+ {
+ throw new ArgumentException(
+ $"Pixel array size must be {nameof(width)} * {nameof(height)}", nameof(pixels));
+ }
this.Width = width;
this.Height = height;
@@ -61,20 +66,20 @@ namespace ImageProcessor.Formats
public Image ToImage()
{
Image image = new Image();
- int pixelCount = Pixels.Length;
+ int pixelCount = this.Pixels.Length;
byte[] bgraPixels = new byte[pixelCount * 4];
for (int i = 0; i < pixelCount; i++)
{
- int j = i * 4;
- Bgra color = Palette[Pixels[i]];
- bgraPixels[j + 0] = color.B;
- bgraPixels[j + 1] = color.G;
- bgraPixels[j + 2] = color.R;
- bgraPixels[j + 3] = color.A;
+ int offset = i * 4;
+ Bgra color = this.Palette[this.Pixels[i]];
+ bgraPixels[offset + 0] = color.B;
+ bgraPixels[offset + 1] = color.G;
+ bgraPixels[offset + 2] = color.R;
+ bgraPixels[offset + 3] = color.A;
}
- image.SetPixels(Width, Height, bgraPixels);
+ image.SetPixels(this.Width, this.Height, bgraPixels);
return image;
}
}
diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
index bcb8e79fb..ec00bf508 100644
--- a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
+++ b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
@@ -61,11 +61,9 @@ namespace ImageProcessor.Formats
byte[] quantizedPixels = new byte[width * height];
- List palette = GetPalette();
-
this.SecondPass(imageBase, quantizedPixels, width, height);
- return new QuantizedImage(width, height, palette.ToArray(), quantizedPixels);
+ return new QuantizedImage(width, height, this.GetPalette().ToArray(), quantizedPixels);
}
///
diff --git a/src/ImageProcessor/Image.cs b/src/ImageProcessor/Image.cs
index 0e8113167..f6e6a0d3d 100644
--- a/src/ImageProcessor/Image.cs
+++ b/src/ImageProcessor/Image.cs
@@ -222,6 +222,12 @@ namespace ImageProcessor
///
public bool IsAnimated => this.Frames.Count > 0;
+ ///
+ /// Gets or sets the number of times any animation is repeated.
+ /// 0 means to repeat indefinitely.
+ ///
+ public ushort RepeatCount { get; set; }
+
///
/// Gets the other frames for the animation.
///
diff --git a/src/ImageProcessor/ImageBase.cs b/src/ImageProcessor/ImageBase.cs
index 19c1fd809..517ac184f 100644
--- a/src/ImageProcessor/ImageBase.cs
+++ b/src/ImageProcessor/ImageBase.cs
@@ -60,7 +60,7 @@ namespace ImageProcessor
///
protected ImageBase(ImageBase other)
{
- Guard.NotNull(other, "other", "Other image cannot be null.");
+ Guard.NotNull(other, nameof(other), "Other image cannot be null.");
byte[] pixels = other.Pixels;
diff --git a/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs b/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
index 5c0c5bf0c..b6162a840 100644
--- a/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
+++ b/tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
@@ -55,7 +55,7 @@
// }
//}
- Trace.WriteLine(string.Format("{0} : {1}ms", filename, watch.ElapsedMilliseconds));
+ Trace.WriteLine($"{filename} : {watch.ElapsedMilliseconds}ms");
}
[Theory]