Browse Source

More work on the gif decoder/encoder

Former-commit-id: 780d7b2478cbafd42d2230fc245d8ab5320dc245
Former-commit-id: 16c380a0287b7b1822b617eae826554c4109c4d8
Former-commit-id: 81a821fdd7bf09f715fb956c4486468ddce2e92e
af/merge-core
James Jackson-South 11 years ago
parent
commit
f5a5cac667
  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;
/// <summary>
/// Extension methods for the <see cref="byte"/> struct.
/// </summary>
internal static class ByteExtensions
{
/// <summary>

71
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;
}

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

@ -11,6 +11,8 @@ namespace ImageProcessor.Formats
/// </summary>
private int quality = 256;
private ImageBase image;
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
@ -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();
}
/// <summary>

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

@ -56,7 +56,7 @@ namespace ImageProcessor.Formats
/// <returns>The decoded and uncompressed array.</returns>
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];

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

@ -42,11 +42,16 @@ namespace ImageProcessor.Formats
/// </summary>
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;
}
}

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

@ -61,11 +61,9 @@ namespace ImageProcessor.Formats
byte[] quantizedPixels = new byte[width * height];
List<Bgra> 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);
}
/// <summary>

6
src/ImageProcessor/Image.cs

@ -222,6 +222,12 @@ namespace ImageProcessor
/// </value>
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>
/// Gets the other frames for the animation.
/// </summary>

2
src/ImageProcessor/ImageBase.cs

@ -60,7 +60,7 @@ namespace ImageProcessor
/// </exception>
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;

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]

Loading…
Cancel
Save