diff --git a/src/ImageProcessor/Formats/Gif/GifConstants.cs b/src/ImageProcessor/Formats/Gif/GifConstants.cs
new file mode 100644
index 0000000000..70f84f42ad
--- /dev/null
+++ b/src/ImageProcessor/Formats/Gif/GifConstants.cs
@@ -0,0 +1,68 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright © James South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+//
+// Constants that define specific points within a gif.
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace ImageProcessor.Formats
+{
+ ///
+ /// Constants that define specific points within a gif.
+ ///
+ internal sealed class GifConstants
+ {
+ ///
+ /// The maximum comment length.
+ ///
+ public const int MaxCommentLength = 1024 * 8;
+
+ ///
+ /// The extension block introducer !.
+ ///
+ public const byte ExtensionIntroducer = 0x21;
+
+ ///
+ /// The terminator.
+ ///
+ public const byte Terminator = 0;
+
+ ///
+ /// The image label introducer ,.
+ ///
+ public const byte ImageLabel = 0x2C;
+
+ ///
+ /// The end introducer trailer ;.
+ ///
+ public const byte EndIntroducer = 0x3B;
+
+ ///
+ /// The application extension label.
+ ///
+ public const byte ApplicationExtensionLabel = 0xFF;
+
+ ///
+ /// The comment label.
+ ///
+ public const byte CommentLabel = 0xFE;
+
+ ///
+ /// The image descriptor label ,.
+ ///
+ public const byte ImageDescriptorLabel = 0x2C;
+
+ ///
+ /// The plain text label.
+ ///
+ public const byte PlainTextLabel = 0x01;
+
+ ///
+ /// The graphic control label.
+ ///
+ public const byte GraphicControlLabel = 0xF9;
+ }
+}
diff --git a/src/ImageProcessor/Formats/Gif/GifDecoder.cs b/src/ImageProcessor/Formats/Gif/GifDecoder.cs
index 00c59db849..d4c33fd2bc 100644
--- a/src/ImageProcessor/Formats/Gif/GifDecoder.cs
+++ b/src/ImageProcessor/Formats/Gif/GifDecoder.cs
@@ -24,6 +24,9 @@ namespace ImageProcessor.Formats
/// The size of the header.
public int HeaderSize => 6;
+ internal GifDecoderCore CoreDecoder { get; private set; }
+
+
///
/// Returns a value indicating whether the supports the specified
/// file header.
@@ -34,7 +37,7 @@ namespace ImageProcessor.Formats
///
public bool IsSupportedFileExtension(string extension)
{
- Guard.NotNullOrEmpty(extension, "extension");
+ Guard.NotNullOrEmpty(extension, nameof(extension));
extension = extension.StartsWith(".") ? extension.Substring(1) : extension;
return extension.Equals("GIF", StringComparison.OrdinalIgnoreCase);
@@ -66,7 +69,8 @@ namespace ImageProcessor.Formats
/// The containing image data.
public void Decode(Image image, Stream stream)
{
- new GifDecoderCore().Decode(image, stream);
+ this.CoreDecoder = new GifDecoderCore();
+ this.CoreDecoder.Decode(image, stream);
}
}
}
diff --git a/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs b/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
index 28f55b0471..92382d8bc7 100644
--- a/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
@@ -24,8 +24,9 @@
private Stream currentStream;
private byte[] globalColorTable;
private byte[] currentFrame;
- private GifLogicalScreenDescriptor logicalScreenDescriptor;
- private GifGraphicsControlExtension graphicsControlExtension;
+
+ internal GifLogicalScreenDescriptor LogicalScreenDescriptor { get; set; }
+ internal GifGraphicsControlExtension GraphicsControlExtension { get; set; }
public void Decode(Image image, Stream stream)
{
@@ -37,16 +38,16 @@
this.currentStream.Seek(6, SeekOrigin.Current);
this.ReadLogicalScreenDescriptor();
- if (this.logicalScreenDescriptor.GlobalColorTableFlag)
+ if (this.LogicalScreenDescriptor.GlobalColorTableFlag)
{
- this.globalColorTable = new byte[this.logicalScreenDescriptor.GlobalColorTableSize * 3];
+ this.globalColorTable = new byte[this.LogicalScreenDescriptor.GlobalColorTableSize * 3];
// Read the global color table from the stream
stream.Read(this.globalColorTable, 0, this.globalColorTable.Length);
}
int nextFlag = stream.ReadByte();
- while (nextFlag != 0)
+ while (nextFlag != Terminator)
{
if (nextFlag == ImageLabel)
{
@@ -89,7 +90,7 @@
byte packed = buffer[1];
- this.graphicsControlExtension = new GifGraphicsControlExtension
+ this.GraphicsControlExtension = new GifGraphicsControlExtension
{
DelayTime = BitConverter.ToInt16(buffer, 2),
TransparencyIndex = buffer[4],
@@ -134,7 +135,7 @@
byte packed = buffer[4];
- this.logicalScreenDescriptor = new GifLogicalScreenDescriptor
+ this.LogicalScreenDescriptor = new GifLogicalScreenDescriptor
{
Width = BitConverter.ToInt16(buffer, 0),
Height = BitConverter.ToInt16(buffer, 2),
@@ -144,16 +145,16 @@
GlobalColorTableSize = 2 << (packed & 0x07)
};
- if (this.logicalScreenDescriptor.GlobalColorTableSize > 255 * 4)
+ if (this.LogicalScreenDescriptor.GlobalColorTableSize > 255 * 4)
{
throw new ImageFormatException(
- $"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'");
+ $"Invalid gif colormap size '{this.LogicalScreenDescriptor.GlobalColorTableSize}'");
}
- if (this.logicalScreenDescriptor.Width > ImageBase.MaxWidth || this.logicalScreenDescriptor.Height > ImageBase.MaxHeight)
+ if (this.LogicalScreenDescriptor.Width > ImageBase.MaxWidth || this.LogicalScreenDescriptor.Height > ImageBase.MaxHeight)
{
throw new ArgumentOutOfRangeException(
- $"The input gif '{this.logicalScreenDescriptor.Width}x{this.logicalScreenDescriptor.Height}' is bigger then the max allowed size '{ImageBase.MaxWidth}x{ImageBase.MaxHeight}'");
+ $"The input gif '{this.LogicalScreenDescriptor.Width}x{this.LogicalScreenDescriptor.Height}' is bigger then the max allowed size '{ImageBase.MaxWidth}x{ImageBase.MaxHeight}'");
}
}
@@ -232,8 +233,8 @@
private void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor)
{
- int imageWidth = this.logicalScreenDescriptor.Width;
- int imageHeight = this.logicalScreenDescriptor.Height;
+ int imageWidth = this.LogicalScreenDescriptor.Width;
+ int imageHeight = this.LogicalScreenDescriptor.Height;
if (this.currentFrame == null)
{
@@ -242,8 +243,8 @@
byte[] lastFrame = null;
- if (this.graphicsControlExtension != null &&
- this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
+ if (this.GraphicsControlExtension != null &&
+ this.GraphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
{
lastFrame = new byte[imageWidth * imageHeight * 4];
@@ -299,9 +300,9 @@
index = indices[i];
- if (this.graphicsControlExtension == null ||
- this.graphicsControlExtension.TransparencyFlag == false ||
- this.graphicsControlExtension.TransparencyIndex != index)
+ if (this.GraphicsControlExtension == null ||
+ this.GraphicsControlExtension.TransparencyFlag == false ||
+ this.GraphicsControlExtension.TransparencyIndex != index)
{
this.currentFrame[offset * 4 + 0] = colorTable[index * 3 + 2];
this.currentFrame[offset * 4 + 1] = colorTable[index * 3 + 1];
@@ -324,9 +325,9 @@
currentImage = this.image;
currentImage.SetPixels(imageWidth, imageHeight, pixels);
- if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0)
+ if (this.GraphicsControlExtension != null && this.GraphicsControlExtension.DelayTime > 0)
{
- this.image.FrameDelay = this.graphicsControlExtension.DelayTime;
+ this.image.FrameDelay = this.GraphicsControlExtension.DelayTime;
}
}
else
@@ -339,9 +340,9 @@
this.image.Frames.Add(frame);
}
- if (this.graphicsControlExtension != null)
+ if (this.GraphicsControlExtension != null)
{
- if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
+ if (this.GraphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
{
for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++)
{
@@ -356,7 +357,7 @@
}
}
}
- else if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
+ else if (this.GraphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
{
this.currentFrame = lastFrame;
}
diff --git a/src/ImageProcessor/Formats/Gif/GifEncoder.cs b/src/ImageProcessor/Formats/Gif/GifEncoder.cs
index e903d9ac32..dcce4ad1aa 100644
--- a/src/ImageProcessor/Formats/Gif/GifEncoder.cs
+++ b/src/ImageProcessor/Formats/Gif/GifEncoder.cs
@@ -11,7 +11,7 @@ namespace ImageProcessor.Formats
///
private int quality = 256;
- private ImageBase image;
+ private Image image;
///
/// Gets or sets the quality of output for images.
@@ -58,48 +58,65 @@ namespace ImageProcessor.Formats
/// The to encode the image data to.
public void Encode(ImageBase image, Stream stream)
{
- Guard.NotNull(image, "image");
- Guard.NotNull(stream, "stream");
+ Guard.NotNull(image, nameof(image));
+ Guard.NotNull(stream, nameof(stream));
- this.image = image;
+ this.image = (Image)image;
// Write the header.
// File Header signature and version.
this.WriteString(stream, "GIF");
this.WriteString(stream, "89a");
- GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor
- {
- Width = (short)image.Width,
- Height = (short)image.Height,
- GlobalColorTableFlag = true,
- GlobalColorTableSize = this.Quality
- };
-
- this.WriteGlobalLogicalScreenDescriptor(stream, descriptor);
-
+ int bitdepth = this.GetBitsNeededForColorDepth(this.Quality) - 1;
+ this.WriteGlobalLogicalScreenDescriptor(stream, bitdepth);
+
+ foreach (ImageFrame frame in this.image.Frames)
+ {
+ this.WriteColorTable(stream, bitdepth);
+ this.WriteGraphicalControlExtension(stream);
+ }
throw new System.NotImplementedException();
}
- private void WriteGlobalLogicalScreenDescriptor(Stream stream, GifLogicalScreenDescriptor descriptor)
+ private void WriteGlobalLogicalScreenDescriptor(Stream stream, int bitDepth)
{
+ IImageDecoder decoder = ((Image)this.image).Decoder;
+ GifLogicalScreenDescriptor descriptor;
+
+ // Try and grab an existing descriptor.
+ if (decoder.GetType() == typeof(GifDecoder))
+ {
+ // Ensure the dimensions etc are up to date.
+ descriptor = ((GifDecoder)decoder).CoreDecoder.LogicalScreenDescriptor;
+ descriptor.Width = (short)this.image.Width;
+ descriptor.Height = (short)this.image.Height;
+ descriptor.GlobalColorTableSize = this.Quality;
+ }
+ else
+ {
+ descriptor = new GifLogicalScreenDescriptor
+ {
+ Width = (short)this.image.Width,
+ Height = (short)this.image.Height,
+ GlobalColorTableFlag = true,
+ GlobalColorTableSize = this.Quality
+ };
+ }
+
this.WriteShort(stream, descriptor.Width);
this.WriteShort(stream, descriptor.Width);
- int size = descriptor.GlobalColorTableSize;
- int bitdepth = this.GetBitsNeededForColorDepth(size) - 1;
+
int packed = 0x80 | // 1 : Global color table flag = 1 (GCT used)
0x70 | // 2-4 : color resolution
0x00 | // 5 : GCT sort flag = 0
- bitdepth; // 6-8 : GCT size assume 1:1
+ bitDepth; // 6-8 : GCT size assume 1:1
this.WriteByte(stream, packed);
this.WriteByte(stream, descriptor.BackgroundColorIndex); // Background Color Index
this.WriteByte(stream, descriptor.PixelAspectRatio); // Pixel aspect ratio
-
- // Write the global color table.
- this.WriteColorTable(stream, bitdepth);
}
private void WriteColorTable(Stream stream, int bitDepth)
@@ -127,6 +144,54 @@ namespace ImageProcessor.Formats
stream.Write(colorTable, 0, colorTableLength);
}
+ private void WriteGraphicalControlExtension(Stream stream)
+ {
+ Image i = ((Image)this.image);
+ IImageDecoder decoder = i.Decoder;
+ GifGraphicsControlExtension extension;
+
+ // Try and grab an existing descriptor.
+ // TODO: Check whether we need to.
+ if (decoder.GetType() == typeof(GifDecoder))
+ {
+ // Ensure the dimensions etc are up to date.
+ extension = ((GifDecoder)decoder).CoreDecoder.GraphicsControlExtension;
+ extension.TransparencyFlag = this.Quality > 1;
+ extension.TransparencyIndex = this.Quality - 1;
+ extension.DelayTime = i.FrameDelay;
+ }
+ else
+ {
+ bool hasTransparent = this.Quality > 1;
+ DisposalMethod disposalMethod = hasTransparent
+ ? DisposalMethod.RestoreToBackground
+ : DisposalMethod.Unspecified;
+
+ extension = new GifGraphicsControlExtension()
+ {
+ DisposalMethod = disposalMethod,
+ TransparencyFlag = hasTransparent,
+ TransparencyIndex = this.Quality - 1, // Quantizer set last as transparent.
+ DelayTime = i.FrameDelay
+ };
+ }
+
+ this.WriteByte(stream, GifConstants.ExtensionIntroducer);
+ this.WriteByte(stream, GifConstants.GraphicControlLabel);
+ this.WriteByte(stream, 4); // Size
+
+ int packed = 0 | // 1-3 : Reserved
+ (int)extension.DisposalMethod | // 4-6 : Disposal
+ 0 | // 7 : User input - 0 = none
+ extension.TransparencyIndex;
+
+ this.WriteByte(stream, packed);
+ this.WriteShort(stream, extension.DelayTime);
+ this.WriteByte(stream, GifConstants.Terminator);
+ }
+
+
+
private void WriteApplicationExtension(Stream stream)
{
// TODO: Implement
@@ -146,7 +211,7 @@ namespace ImageProcessor.Formats
}
///
- /// Writes a short to the given stream.
+ /// Writes a byte to the given stream.
///
/// The containing image data.
/// The value to write.
diff --git a/src/ImageProcessor/Formats/Gif/GifGraphicsControlExtension.cs b/src/ImageProcessor/Formats/Gif/GifGraphicsControlExtension.cs
index 0004347163..7b7bfc153b 100644
--- a/src/ImageProcessor/Formats/Gif/GifGraphicsControlExtension.cs
+++ b/src/ImageProcessor/Formats/Gif/GifGraphicsControlExtension.cs
@@ -42,7 +42,6 @@ namespace ImageProcessor.Formats
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
- /// This field may be used in conjunction with the User Input Flag field.
///
public int DelayTime { get; set; }
}
diff --git a/src/ImageProcessor/Formats/Gif/LzwEncoder.cs b/src/ImageProcessor/Formats/Gif/LzwEncoder.cs
index b6af73e5d0..b9244f6493 100644
--- a/src/ImageProcessor/Formats/Gif/LzwEncoder.cs
+++ b/src/ImageProcessor/Formats/Gif/LzwEncoder.cs
@@ -63,7 +63,7 @@
// General DEFINEs
- private const int BITS = 12;
+ private const int Bits = 12;
private const int HSIZE = 5003; // 80% occupancy
@@ -79,12 +79,12 @@
// Joe Orost (decvax!vax135!petsd!joe)
private int numberOfBits; // number of bits/code
- private int maxbits = BITS; // user settable max # bits/code
+ private int maxbits = Bits; // user settable max # bits/code
private int maxcode; // maximum code, given n_bits
- private int maxmaxcode = 1 << BITS; // should NEVER generate this code
+ private int maxmaxcode = 1 << Bits; // should NEVER generate this code
- private int[] htab = new int[HSIZE];
- private int[] codetab = new int[HSIZE];
+ private readonly int[] hashTable = new int[HSIZE];
+ private readonly int[] codeTable = new int[HSIZE];
private int hsize = HSIZE; // for dynamic table sizing
@@ -168,7 +168,7 @@
{
for (int i = 0; i < hsize; ++i)
{
- this.htab[i] = -1;
+ this.hashTable[i] = -1;
}
}
@@ -210,21 +210,21 @@
this.Output(this.ClearCode, outs);
- // TODO: Refactor this. Goto is baaaaaaad!
- outer_loop:
+ // TODO: Refactor this. Goto is baaaaaaad!
+ // outer_loop:
while ((c = this.NextPixel()) != EOF)
{
fcode = (c << this.maxbits) + ent;
int i = c << hshift ^ ent;
- if (this.htab[i] == fcode)
+ if (this.hashTable[i] == fcode)
{
- ent = this.codetab[i];
+ ent = this.codeTable[i];
continue;
}
// non-empty slot
- if (this.htab[i] >= 0)
+ if (this.hashTable[i] >= 0)
{
disp = hsize_reg - i; // secondary hash (after G. Knott)
if (i == 0)
@@ -239,13 +239,14 @@
i += hsize_reg;
}
- if (this.htab[i] == fcode)
+ if (this.hashTable[i] == fcode)
{
- ent = this.codetab[i];
- goto outer_loop;
+ ent = this.codeTable[i];
+ // goto outer_loop;
+ break;
}
}
- while (this.htab[i] >= 0);
+ while (this.hashTable[i] >= 0);
}
this.Output(ent, outs);
@@ -253,8 +254,8 @@
if (this.freeEntry < this.maxmaxcode)
{
- this.codetab[i] = this.freeEntry++; // code -> hashtable
- this.htab[i] = fcode;
+ this.codeTable[i] = this.freeEntry++; // code -> hashtable
+ this.hashTable[i] = fcode;
}
else
{
diff --git a/src/ImageProcessor/Image.cs b/src/ImageProcessor/Image.cs
index f6e6a0d3dc..d27c77833d 100644
--- a/src/ImageProcessor/Image.cs
+++ b/src/ImageProcessor/Image.cs
@@ -98,7 +98,7 @@ namespace ImageProcessor
public Image(Image other)
: base(other)
{
- Guard.NotNull(other, "other", "Other image cannot be null.");
+ Guard.NotNull(other, nameof(other), "Other image cannot be null.");
foreach (ImageFrame frame in other.Frames)
{
@@ -120,7 +120,7 @@ namespace ImageProcessor
///
public Image(Stream stream)
{
- Guard.NotNull(stream, "stream");
+ Guard.NotNull(stream, nameof(stream));
this.Load(stream, Decoders);
}
@@ -154,9 +154,8 @@ namespace ImageProcessor
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
- /// This field may be used in conjunction with the User Input Flag field.
///
- public int? FrameDelay { get; set; }
+ public int FrameDelay { get; set; }
///
/// Gets or sets the resolution of the image in x- direction. It is defined as
@@ -240,6 +239,8 @@ namespace ImageProcessor
/// A list of image properties.
public IList Properties { get; } = new List();
+ internal IImageDecoder Decoder { get; set; }
+
///
/// Loads the image from the given stream.
///
@@ -279,7 +280,8 @@ namespace ImageProcessor
IImageDecoder decoder = decoders.FirstOrDefault(x => x.IsSupportedFileFormat(header));
if (decoder != null)
{
- decoder.Decode(this, stream);
+ this.Decoder = decoder;
+ this.Decoder.Decode(this, stream);
return;
}
}
diff --git a/src/ImageProcessor/ImageBase.cs b/src/ImageProcessor/ImageBase.cs
index 0773d63e85..a8116b2d2b 100644
--- a/src/ImageProcessor/ImageBase.cs
+++ b/src/ImageProcessor/ImageBase.cs
@@ -40,8 +40,8 @@ namespace ImageProcessor
///
protected ImageBase(int width, int height)
{
- Guard.MustBeGreaterThan(width, 0, "width");
- Guard.MustBeGreaterThan(height, 0, "height");
+ Guard.MustBeGreaterThan(width, 0, nameof(width));
+ Guard.MustBeGreaterThan(height, 0, nameof(height));
this.Width = width;
this.Height = height;
diff --git a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
index c175ba0d0a..a5d584cc88 100644
--- a/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
+++ b/tests/ImageProcessor.Tests/Colors/ColorConversionTests.cs
@@ -88,7 +88,7 @@ namespace ImageProcessor.Tests
}
///
- /// Tests the implicit conversion from to .
+ /// Tests the implicit conversion from to .
///
[Fact]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
@@ -128,6 +128,9 @@ namespace ImageProcessor.Tests
Assert.Equal(80, hsv3.V, 1);
}
+ ///
+ /// Tests the implicit conversion from to .
+ ///
[Fact]
public void HsvToBgr()
{