Browse Source

More work on the gif decoder/encoder

Former-commit-id: 780d7b2478cbafd42d2230fc245d8ab5320dc245
Former-commit-id: 16c380a0287b7b1822b617eae826554c4109c4d8
Former-commit-id: 81a821fdd7bf09f715fb956c4486468ddce2e92e
pull/17/head
James Jackson-South 11 years ago
parent
commit
586f3be179
  1. 15
      src/ImageProcessor/Common/Extensions/ByteExtensions.cs
  2. 71
      src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
  3. 34
      src/ImageProcessor/Formats/Gif/GifEncoder.cs
  4. 2
      src/ImageProcessor/Formats/Gif/LzwDecoder.cs
  5. 31
      src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
  6. 4
      src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
  7. 6
      src/ImageProcessor/Image.cs
  8. 2
      src/ImageProcessor/ImageBase.cs
  9. 2
      tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs

15
src/ImageProcessor/Common/Extensions/ByteExtensions.cs

@ -1,7 +1,20 @@
namespace ImageProcessor // --------------------------------------------------------------------------------------------------------------------
// <copyright file="ByteExtensions.cs" company="James South">
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Extension methods for the <see cref="byte" /> struct.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor
{ {
using System; using System;
/// <summary>
/// Extension methods for the <see cref="byte"/> struct.
/// </summary>
internal static class ByteExtensions internal static class ByteExtensions
{ {
/// <summary> /// <summary>

71
src/ImageProcessor/Formats/Gif/GifDecoderCore.cs

@ -25,7 +25,7 @@
private byte[] globalColorTable; private byte[] globalColorTable;
private byte[] _currentFrame; private byte[] _currentFrame;
private GifLogicalScreenDescriptor logicalScreenDescriptor; private GifLogicalScreenDescriptor logicalScreenDescriptor;
private GifGraphicsControlExtension _graphicsControl; private GifGraphicsControlExtension graphicsControlExtension;
public void Decode(Image image, Stream stream) public void Decode(Image image, Stream stream)
{ {
@ -64,6 +64,7 @@
this.ReadComments(); this.ReadComments();
break; break;
case ApplicationExtensionLabel: case ApplicationExtensionLabel:
// TODO: Read Application extension
this.Skip(12); this.Skip(12);
break; break;
case PlainTextLabel: case PlainTextLabel:
@ -88,7 +89,7 @@
byte packed = buffer[1]; byte packed = buffer[1];
_graphicsControl = new GifGraphicsControlExtension this.graphicsControlExtension = new GifGraphicsControlExtension
{ {
DelayTime = BitConverter.ToInt16(buffer, 2), DelayTime = BitConverter.ToInt16(buffer, 2),
TransparencyIndex = buffer[4], TransparencyIndex = buffer[4],
@ -97,6 +98,12 @@
}; };
} }
private void ReadApplicationBlockExtension()
{
// TODO: Implement
throw new NotImplementedException();
}
private GifImageDescriptor ReadImageDescriptor() private GifImageDescriptor ReadImageDescriptor()
{ {
byte[] buffer = new byte[9]; byte[] buffer = new byte[9];
@ -105,14 +112,16 @@
byte packed = buffer[8]; byte packed = buffer[8];
GifImageDescriptor imageDescriptor = new GifImageDescriptor(); GifImageDescriptor imageDescriptor = new GifImageDescriptor
imageDescriptor.Left = BitConverter.ToInt16(buffer, 0); {
imageDescriptor.Top = BitConverter.ToInt16(buffer, 2); Left = BitConverter.ToInt16(buffer, 0),
imageDescriptor.Width = BitConverter.ToInt16(buffer, 4); Top = BitConverter.ToInt16(buffer, 2),
imageDescriptor.Height = BitConverter.ToInt16(buffer, 6); Width = BitConverter.ToInt16(buffer, 4),
imageDescriptor.LocalColorTableFlag = ((packed & 0x80) >> 7) == 1; Height = BitConverter.ToInt16(buffer, 6),
imageDescriptor.LocalColorTableSize = 2 << (packed & 0x07); LocalColorTableFlag = ((packed & 0x80) >> 7) == 1,
imageDescriptor.InterlaceFlag = ((packed & 0x40) >> 6) == 1; LocalColorTableSize = 2 << (packed & 0x07),
InterlaceFlag = ((packed & 0x40) >> 6) == 1
};
return imageDescriptor; return imageDescriptor;
} }
@ -137,18 +146,14 @@
if (this.logicalScreenDescriptor.GlobalColorTableSize > 255 * 4) 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) if (this.logicalScreenDescriptor.Width > ImageBase.MaxWidth || this.logicalScreenDescriptor.Height > ImageBase.MaxHeight)
{ {
throw new ArgumentOutOfRangeException( throw new ArgumentOutOfRangeException(
string.Format( $"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 '{0}x{1}' is bigger then the max allowed size '{2}x{3}'",
this.logicalScreenDescriptor.Width,
this.logicalScreenDescriptor.Height,
ImageBase.MaxWidth,
ImageBase.MaxHeight));
} }
} }
@ -156,7 +161,7 @@
{ {
this.currentStream.Seek(length, SeekOrigin.Current); this.currentStream.Seek(length, SeekOrigin.Current);
int flag = 0; int flag;
while ((flag = this.currentStream.ReadByte()) != 0) while ((flag = this.currentStream.ReadByte()) != 0)
{ {
@ -166,13 +171,13 @@
private void ReadComments() private void ReadComments()
{ {
int flag = 0; int flag;
while ((flag = this.currentStream.ReadByte()) != 0) while ((flag = this.currentStream.ReadByte()) != 0)
{ {
if (flag > MaxCommentLength) 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]; byte[] buffer = new byte[flag];
@ -230,19 +235,19 @@
int imageWidth = this.logicalScreenDescriptor.Width; int imageWidth = this.logicalScreenDescriptor.Width;
int imageHeight = this.logicalScreenDescriptor.Height; 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; byte[] lastFrame = null;
if (_graphicsControl != null && if (this.graphicsControlExtension != null &&
_graphicsControl.DisposalMethod == DisposalMethod.RestoreToPrevious) this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
{ {
lastFrame = new byte[imageWidth * imageHeight * 4]; 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; int offset = 0, i = 0, index = -1;
@ -294,9 +299,9 @@
index = indices[i]; index = indices[i];
if (_graphicsControl == null || if (this.graphicsControlExtension == null ||
_graphicsControl.TransparencyFlag == false || this.graphicsControlExtension.TransparencyFlag == false ||
_graphicsControl.TransparencyIndex != index) this.graphicsControlExtension.TransparencyIndex != index)
{ {
_currentFrame[offset * 4 + 0] = colorTable[index * 3 + 2]; _currentFrame[offset * 4 + 0] = colorTable[index * 3 + 2];
_currentFrame[offset * 4 + 1] = colorTable[index * 3 + 1]; _currentFrame[offset * 4 + 1] = colorTable[index * 3 + 1];
@ -319,9 +324,9 @@
currentImage = this.image; currentImage = this.image;
currentImage.SetPixels(imageWidth, imageHeight, pixels); 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 else
@ -334,9 +339,9 @@
this.image.Frames.Add(frame); 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++) 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; _currentFrame = lastFrame;
} }

34
src/ImageProcessor/Formats/Gif/GifEncoder.cs

@ -11,6 +11,8 @@ namespace ImageProcessor.Formats
/// </summary> /// </summary>
private int quality = 256; private int quality = 256;
private ImageBase image;
/// <summary> /// <summary>
/// Gets or sets the quality of output for images. /// Gets or sets the quality of output for images.
/// </summary> /// </summary>
@ -59,6 +61,8 @@ namespace ImageProcessor.Formats
Guard.NotNull(image, "image"); Guard.NotNull(image, "image");
Guard.NotNull(stream, "stream"); Guard.NotNull(stream, "stream");
this.image = image;
// Write the header. // Write the header.
// File Header signature and version. // File Header signature and version.
this.WriteString(stream, "GIF"); this.WriteString(stream, "GIF");
@ -95,12 +99,38 @@ namespace ImageProcessor.Formats
this.WriteByte(stream, descriptor.PixelAspectRatio); // Pixel aspect ratio this.WriteByte(stream, descriptor.PixelAspectRatio); // Pixel aspect ratio
// Write the global color table. // 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();
} }
/// <summary> /// <summary>

2
src/ImageProcessor/Formats/Gif/LzwDecoder.cs

@ -56,7 +56,7 @@ namespace ImageProcessor.Formats
/// <returns>The decoded and uncompressed array.</returns> /// <returns>The decoded and uncompressed array.</returns>
public byte[] DecodePixels(int width, int height, int dataSize) 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. // The resulting index table.
byte[] pixels = new byte[width * height]; byte[] pixels = new byte[width * height];

31
src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs

@ -42,11 +42,16 @@ namespace ImageProcessor.Formats
/// </summary> /// </summary>
public QuantizedImage(int width, int height, Bgra[] palette, byte[] pixels) public QuantizedImage(int width, int height, Bgra[] palette, byte[] pixels)
{ {
if (width <= 0) throw new ArgumentOutOfRangeException(nameof(width)); Guard.GreaterThan(width, 0, nameof(width));
if (height <= 0) throw new ArgumentOutOfRangeException(nameof(height)); Guard.GreaterThan(height, 0, nameof(height));
if (palette == null) throw new ArgumentNullException(nameof(palette)); Guard.NotNull(palette, nameof(palette));
if (pixels == null) throw new ArgumentNullException(nameof(pixels)); Guard.NotNull(pixels, nameof(pixels));
if (pixels.Length != width * height) throw new ArgumentException("Pixel array size must be width * height", 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.Width = width;
this.Height = height; this.Height = height;
@ -61,20 +66,20 @@ namespace ImageProcessor.Formats
public Image ToImage() public Image ToImage()
{ {
Image image = new Image(); Image image = new Image();
int pixelCount = Pixels.Length; int pixelCount = this.Pixels.Length;
byte[] bgraPixels = new byte[pixelCount * 4]; byte[] bgraPixels = new byte[pixelCount * 4];
for (int i = 0; i < pixelCount; i++) for (int i = 0; i < pixelCount; i++)
{ {
int j = i * 4; int offset = i * 4;
Bgra color = Palette[Pixels[i]]; Bgra color = this.Palette[this.Pixels[i]];
bgraPixels[j + 0] = color.B; bgraPixels[offset + 0] = color.B;
bgraPixels[j + 1] = color.G; bgraPixels[offset + 1] = color.G;
bgraPixels[j + 2] = color.R; bgraPixels[offset + 2] = color.R;
bgraPixels[j + 3] = color.A; bgraPixels[offset + 3] = color.A;
} }
image.SetPixels(Width, Height, bgraPixels); image.SetPixels(this.Width, this.Height, bgraPixels);
return image; return image;
} }
} }

4
src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs

@ -61,11 +61,9 @@ namespace ImageProcessor.Formats
byte[] quantizedPixels = new byte[width * height]; byte[] quantizedPixels = new byte[width * height];
List<Bgra> palette = GetPalette();
this.SecondPass(imageBase, quantizedPixels, width, height); this.SecondPass(imageBase, quantizedPixels, width, height);
return new QuantizedImage(width, height, palette.ToArray(), quantizedPixels); return new QuantizedImage(width, height, this.GetPalette().ToArray(), quantizedPixels);
} }
/// <summary> /// <summary>

6
src/ImageProcessor/Image.cs

@ -222,6 +222,12 @@ namespace ImageProcessor
/// </value> /// </value>
public bool IsAnimated => this.Frames.Count > 0; public bool IsAnimated => this.Frames.Count > 0;
/// <summary>
/// Gets or sets the number of times any animation is repeated.
/// <remarks>0 means to repeat indefinitely.</remarks>
/// </summary>
public ushort RepeatCount { get; set; }
/// <summary> /// <summary>
/// Gets the other frames for the animation. /// Gets the other frames for the animation.
/// </summary> /// </summary>

2
src/ImageProcessor/ImageBase.cs

@ -60,7 +60,7 @@ namespace ImageProcessor
/// </exception> /// </exception>
protected ImageBase(ImageBase other) 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; byte[] pixels = other.Pixels;

2
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] [Theory]

Loading…
Cancel
Save