Browse Source

Merge remote-tracking branch 'refs/remotes/origin/Fix-gifs'

pull/56/head
James Jackson-South 10 years ago
parent
commit
d4ca605ff6
  1. 437
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  2. 92
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  3. 17
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  4. 2
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  5. 1
      src/ImageSharp/Quantizers/Octree/Quantizer.cs
  6. 375
      src/ImageSharp/Quantizers/Wu/WuQuantizer.cs
  7. 1
      tests/ImageSharp.Tests/FileTestBase.cs
  8. 2
      tests/ImageSharp.Tests/TestImages.cs
  9. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Gif/cheers.gif

437
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
using System.Buffers;
using System.IO; using System.IO;
/// <summary> /// <summary>
@ -17,6 +18,11 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked> where TPacked : struct, IEquatable<TPacked>
{ {
/// <summary>
/// The temp buffer used to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[16];
/// <summary> /// <summary>
/// The image to decode the information to. /// The image to decode the information to.
/// </summary> /// </summary>
@ -33,14 +39,14 @@ namespace ImageSharp.Formats
private byte[] globalColorTable; private byte[] globalColorTable;
/// <summary> /// <summary>
/// The next frame. /// The global color table length
/// </summary> /// </summary>
private ImageFrame<TColor, TPacked> nextFrame; private int globalColorTableLength;
/// <summary> /// <summary>
/// The area to restore. /// The current frame.
/// </summary> /// </summary>
private Rectangle? restoreArea; private TColor[] currentFrame;
/// <summary> /// <summary>
/// The logical screen descriptor. /// The logical screen descriptor.
@ -59,55 +65,65 @@ namespace ImageSharp.Formats
/// <param name="stream">The stream containing image data. </param> /// <param name="stream">The stream containing image data. </param>
public void Decode(Image<TColor, TPacked> image, Stream stream) public void Decode(Image<TColor, TPacked> image, Stream stream)
{ {
this.decodedImage = image; try
this.currentStream = stream;
// Skip the identifier
this.currentStream.Skip(6);
this.ReadLogicalScreenDescriptor();
if (this.logicalScreenDescriptor.GlobalColorTableFlag)
{ {
this.globalColorTable = new byte[this.logicalScreenDescriptor.GlobalColorTableSize * 3]; this.decodedImage = image;
this.currentStream = stream;
// Read the global color table from the stream // Skip the identifier
stream.Read(this.globalColorTable, 0, this.globalColorTable.Length); this.currentStream.Skip(6);
} this.ReadLogicalScreenDescriptor();
// Loop though the respective gif parts and read the data. if (this.logicalScreenDescriptor.GlobalColorTableFlag)
int nextFlag = stream.ReadByte();
while (nextFlag != GifConstants.Terminator)
{
if (nextFlag == GifConstants.ImageLabel)
{ {
this.ReadFrame(); this.globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
this.globalColorTable = ArrayPool<byte>.Shared.Rent(this.globalColorTableLength);
// Read the global color table from the stream
stream.Read(this.globalColorTable, 0, this.globalColorTableLength);
} }
else if (nextFlag == GifConstants.ExtensionIntroducer)
// Loop though the respective gif parts and read the data.
int nextFlag = stream.ReadByte();
while (nextFlag != GifConstants.Terminator)
{ {
int label = stream.ReadByte(); if (nextFlag == GifConstants.ImageLabel)
switch (label)
{ {
case GifConstants.GraphicControlLabel: this.ReadFrame();
this.ReadGraphicalControlExtension(); }
break; else if (nextFlag == GifConstants.ExtensionIntroducer)
case GifConstants.CommentLabel: {
this.ReadComments(); int label = stream.ReadByte();
break; switch (label)
case GifConstants.ApplicationExtensionLabel: {
this.Skip(12); // No need to read. case GifConstants.GraphicControlLabel:
break; this.ReadGraphicalControlExtension();
case GifConstants.PlainTextLabel: break;
this.Skip(13); // Not supported by any known decoder. case GifConstants.CommentLabel:
break; this.ReadComments();
break;
case GifConstants.ApplicationExtensionLabel:
this.Skip(12); // No need to read.
break;
case GifConstants.PlainTextLabel:
this.Skip(13); // Not supported by any known decoder.
break;
}
} }
else if (nextFlag == GifConstants.EndIntroducer)
{
break;
}
nextFlag = stream.ReadByte();
} }
else if (nextFlag == GifConstants.EndIntroducer) }
finally
{
if (this.globalColorTable != null)
{ {
break; ArrayPool<byte>.Shared.Return(this.globalColorTable);
} }
nextFlag = stream.ReadByte();
} }
} }
@ -116,16 +132,14 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private void ReadGraphicalControlExtension() private void ReadGraphicalControlExtension()
{ {
byte[] buffer = new byte[6]; this.currentStream.Read(this.buffer, 0, 6);
this.currentStream.Read(buffer, 0, buffer.Length);
byte packed = buffer[1]; byte packed = this.buffer[1];
this.graphicsControlExtension = new GifGraphicsControlExtension this.graphicsControlExtension = new GifGraphicsControlExtension
{ {
DelayTime = BitConverter.ToInt16(buffer, 2), DelayTime = BitConverter.ToInt16(this.buffer, 2),
TransparencyIndex = buffer[4], TransparencyIndex = this.buffer[4],
TransparencyFlag = (packed & 0x01) == 1, TransparencyFlag = (packed & 0x01) == 1,
DisposalMethod = (DisposalMethod)((packed & 0x1C) >> 2) DisposalMethod = (DisposalMethod)((packed & 0x1C) >> 2)
}; };
@ -137,18 +151,16 @@ namespace ImageSharp.Formats
/// <returns><see cref="GifImageDescriptor"/></returns> /// <returns><see cref="GifImageDescriptor"/></returns>
private GifImageDescriptor ReadImageDescriptor() private GifImageDescriptor ReadImageDescriptor()
{ {
byte[] buffer = new byte[9]; this.currentStream.Read(this.buffer, 0, 9);
this.currentStream.Read(buffer, 0, buffer.Length); byte packed = this.buffer[8];
byte packed = buffer[8];
GifImageDescriptor imageDescriptor = new GifImageDescriptor GifImageDescriptor imageDescriptor = new GifImageDescriptor
{ {
Left = BitConverter.ToInt16(buffer, 0), Left = BitConverter.ToInt16(this.buffer, 0),
Top = BitConverter.ToInt16(buffer, 2), Top = BitConverter.ToInt16(this.buffer, 2),
Width = BitConverter.ToInt16(buffer, 4), Width = BitConverter.ToInt16(this.buffer, 4),
Height = BitConverter.ToInt16(buffer, 6), Height = BitConverter.ToInt16(this.buffer, 6),
LocalColorTableFlag = ((packed & 0x80) >> 7) == 1, LocalColorTableFlag = ((packed & 0x80) >> 7) == 1,
LocalColorTableSize = 2 << (packed & 0x07), LocalColorTableSize = 2 << (packed & 0x07),
InterlaceFlag = ((packed & 0x40) >> 6) == 1 InterlaceFlag = ((packed & 0x40) >> 6) == 1
@ -162,26 +174,23 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private void ReadLogicalScreenDescriptor() private void ReadLogicalScreenDescriptor()
{ {
byte[] buffer = new byte[7]; this.currentStream.Read(this.buffer, 0, 7);
this.currentStream.Read(buffer, 0, buffer.Length);
byte packed = buffer[4]; byte packed = this.buffer[4];
this.logicalScreenDescriptor = new GifLogicalScreenDescriptor this.logicalScreenDescriptor = new GifLogicalScreenDescriptor
{ {
Width = BitConverter.ToInt16(buffer, 0), Width = BitConverter.ToInt16(this.buffer, 0),
Height = BitConverter.ToInt16(buffer, 2), Height = BitConverter.ToInt16(this.buffer, 2),
BackgroundColorIndex = buffer[5], BackgroundColorIndex = this.buffer[5],
PixelAspectRatio = buffer[6], PixelAspectRatio = this.buffer[6],
GlobalColorTableFlag = ((packed & 0x80) >> 7) == 1, GlobalColorTableFlag = ((packed & 0x80) >> 7) == 1,
GlobalColorTableSize = 2 << (packed & 0x07) GlobalColorTableSize = 2 << (packed & 0x07)
}; };
if (this.logicalScreenDescriptor.GlobalColorTableSize > 255 * 4) if (this.logicalScreenDescriptor.GlobalColorTableSize > 255 * 4)
{ {
throw new ImageFormatException( throw new ImageFormatException($"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'");
$"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'");
} }
if (this.logicalScreenDescriptor.Width > this.decodedImage.MaxWidth || this.logicalScreenDescriptor.Height > this.decodedImage.MaxHeight) if (this.logicalScreenDescriptor.Width > this.decodedImage.MaxWidth || this.logicalScreenDescriptor.Height > this.decodedImage.MaxHeight)
@ -221,11 +230,17 @@ namespace ImageSharp.Formats
throw new ImageFormatException($"Gif comment length '{flag}' exceeds max '{GifConstants.MaxCommentLength}'"); throw new ImageFormatException($"Gif comment length '{flag}' exceeds max '{GifConstants.MaxCommentLength}'");
} }
byte[] buffer = new byte[flag]; byte[] flagBuffer = ArrayPool<byte>.Shared.Rent(flag);
this.currentStream.Read(buffer, 0, flag);
this.decodedImage.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(buffer))); try
{
this.currentStream.Read(flagBuffer, 0, flag);
this.decodedImage.Properties.Add(new ImageProperty("Comments", BitConverter.ToString(flagBuffer, 0, flag)));
}
finally
{
ArrayPool<byte>.Shared.Return(flagBuffer);
}
} }
} }
@ -236,54 +251,50 @@ namespace ImageSharp.Formats
{ {
GifImageDescriptor imageDescriptor = this.ReadImageDescriptor(); GifImageDescriptor imageDescriptor = this.ReadImageDescriptor();
byte[] localColorTable = this.ReadFrameLocalColorTable(imageDescriptor); byte[] localColorTable = null;
byte[] indices = null;
try
{
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
int length = this.globalColorTableLength;
if (imageDescriptor.LocalColorTableFlag)
{
length = imageDescriptor.LocalColorTableSize * 3;
localColorTable = ArrayPool<byte>.Shared.Rent(length);
this.currentStream.Read(localColorTable, 0, length);
}
byte[] indices = this.ReadFrameIndices(imageDescriptor); indices = ArrayPool<byte>.Shared.Rent(imageDescriptor.Width * imageDescriptor.Height);
// Determine the color table for this frame. If there is a local one, use it this.ReadFrameIndices(imageDescriptor, indices);
// otherwise use the global color table. this.ReadFrameColors(indices, localColorTable ?? this.globalColorTable, length, imageDescriptor);
byte[] colorTable = localColorTable ?? this.globalColorTable;
this.ReadFrameColors(indices, colorTable, imageDescriptor); // Skip any remaining blocks
this.Skip(0);
}
finally
{
if (localColorTable != null)
{
ArrayPool<byte>.Shared.Return(localColorTable);
}
// Skip any remaining blocks ArrayPool<byte>.Shared.Return(indices);
this.Skip(0); }
} }
/// <summary> /// <summary>
/// Reads the frame indices marking the color to use for each pixel. /// Reads the frame indices marking the color to use for each pixel.
/// </summary> /// </summary>
/// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param> /// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <param name="indices">The pixel array to write to.</param>
private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor) private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices)
{ {
int dataSize = this.currentStream.ReadByte(); int dataSize = this.currentStream.ReadByte();
byte[] indices;
using (LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream)) using (LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream))
{ {
indices = lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize); lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices);
} }
return indices;
}
/// <summary>
/// Reads the local color table from the current frame.
/// </summary>
/// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
private byte[] ReadFrameLocalColorTable(GifImageDescriptor imageDescriptor)
{
byte[] localColorTable = null;
if (imageDescriptor.LocalColorTableFlag)
{
localColorTable = new byte[imageDescriptor.LocalColorTableSize * 3];
this.currentStream.Read(localColorTable, 0, localColorTable.Length);
}
return localColorTable;
} }
/// <summary> /// <summary>
@ -291,47 +302,30 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="indices">The indexed pixels.</param> /// <param name="indices">The indexed pixels.</param>
/// <param name="colorTable">The color table containing the available colors.</param> /// <param name="colorTable">The color table containing the available colors.</param>
/// <param name="colorTableLength">The color table length.</param>
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param> /// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
private unsafe void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor) private void ReadFrameColors(byte[] indices, byte[] colorTable, int colorTableLength, GifImageDescriptor descriptor)
{ {
int imageWidth = this.logicalScreenDescriptor.Width; int imageWidth = this.logicalScreenDescriptor.Width;
int imageHeight = this.logicalScreenDescriptor.Height; int imageHeight = this.logicalScreenDescriptor.Height;
ImageFrame<TColor, TPacked> previousFrame = null; if (this.currentFrame == null)
ImageFrame<TColor, TPacked> currentFrame = null;
ImageBase<TColor, TPacked> image;
if (this.nextFrame == null)
{ {
image = this.decodedImage; this.currentFrame = new TColor[imageWidth * imageHeight];
}
image.Quality = colorTable.Length / 3; TColor[] lastFrame = null;
// This initializes the image to become fully transparent because the alpha channel is zero. if (this.graphicsControlExtension != null
image.InitPixels(imageWidth, imageHeight); && this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
}
else
{ {
if (this.graphicsControlExtension != null && lastFrame = new TColor[imageWidth * imageHeight];
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
using (PixelAccessor<TColor, TPacked> lastPixels = lastFrame.Lock<TColor, TPacked>(imageWidth, imageHeight))
using (PixelAccessor<TColor, TPacked> currentPixels = this.currentFrame.Lock<TColor, TPacked>(imageWidth, imageHeight))
{ {
previousFrame = this.nextFrame; currentPixels.CopyImage(lastPixels);
} }
currentFrame = this.nextFrame.Clone();
image = currentFrame;
this.RestoreToBackground(image);
this.decodedImage.Frames.Add(currentFrame);
}
if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0)
{
image.FrameDelay = this.graphicsControlExtension.DelayTime;
} }
int i = 0; int i = 0;
@ -339,124 +333,119 @@ namespace ImageSharp.Formats
int interlaceIncrement = 8; // The interlacing line increment int interlaceIncrement = 8; // The interlacing line increment
int interlaceY = 0; // The current interlaced line int interlaceY = 0; // The current interlaced line
using (PixelAccessor<TColor, TPacked> pixelAccessor = image.Lock()) // Lock the current image pixels for fast acces.
using (PixelAccessor<TColor, TPacked> currentPixels = this.currentFrame.Lock<TColor, TPacked>(imageWidth, imageHeight))
{ {
using (PixelArea<TColor, TPacked> pixelRow = new PixelArea<TColor, TPacked>(imageWidth, ComponentOrder.XYZW)) for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++)
{ {
for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) // Check if this image is interlaced.
int writeY; // the target y offset to write to
if (descriptor.InterlaceFlag)
{ {
// Check if this image is interlaced. // If so then we read lines at predetermined offsets.
int writeY; // the target y offset to write to // When an entire image height worth of offset lines has been read we consider this a pass.
if (descriptor.InterlaceFlag) // With each pass the number of offset lines changes and the starting line changes.
if (interlaceY >= descriptor.Height)
{ {
// If so then we read lines at predetermined offsets. interlacePass++;
// When an entire image height worth of offset lines has been read we consider this a pass. switch (interlacePass)
// With each pass the number of offset lines changes and the starting line changes.
if (interlaceY >= descriptor.Height)
{ {
interlacePass++; case 1:
switch (interlacePass) interlaceY = 4;
{ break;
case 1: case 2:
interlaceY = 4; interlaceY = 2;
break; interlaceIncrement = 4;
case 2: break;
interlaceY = 2; case 3:
interlaceIncrement = 4; interlaceY = 1;
break; interlaceIncrement = 2;
case 3: break;
interlaceY = 1;
interlaceIncrement = 2;
break;
}
} }
writeY = interlaceY + descriptor.Top;
interlaceY += interlaceIncrement;
}
else
{
writeY = y;
} }
pixelRow.Reset(); writeY = interlaceY + descriptor.Top;
byte* pixelBase = pixelRow.PixelBase; interlaceY += interlaceIncrement;
for (int x = 0; x < descriptor.Width; x++) }
{ else
int index = indices[i]; {
writeY = y;
}
if (this.graphicsControlExtension == null || for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++)
this.graphicsControlExtension.TransparencyFlag == false || {
this.graphicsControlExtension.TransparencyIndex != index) int index = indices[i];
{
int indexOffset = index * 3;
*(pixelBase + 0) = colorTable[indexOffset];
*(pixelBase + 1) = colorTable[indexOffset + 1];
*(pixelBase + 2) = colorTable[indexOffset + 2];
*(pixelBase + 3) = 255;
}
i++; if (this.graphicsControlExtension == null
pixelBase += 4; || this.graphicsControlExtension.TransparencyFlag == false
|| this.graphicsControlExtension.TransparencyIndex != index)
{
// Stored in r-> g-> b-> a order.
int indexOffset = index * 3;
TColor pixel = default(TColor);
pixel.PackFromBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255);
currentPixels[x, writeY] = pixel;
} }
pixelAccessor.CopyFrom(pixelRow, writeY, descriptor.Left); i++;
} }
} }
}
if (previousFrame != null) TColor[] pixels = new TColor[imageWidth * imageHeight];
{
this.nextFrame = previousFrame;
return;
}
this.nextFrame = currentFrame == null ? this.decodedImage.ToFrame() : currentFrame.Clone(); using (PixelAccessor<TColor, TPacked> newPixels = pixels.Lock<TColor, TPacked>(imageWidth, imageHeight))
{
currentPixels.CopyImage(newPixels);
}
if (this.graphicsControlExtension != null && ImageBase<TColor, TPacked> currentImage;
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
{
this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height);
}
}
/// <summary> if (this.decodedImage.Pixels == null)
/// Restores the current frame background. {
/// </summary> currentImage = this.decodedImage;
/// <param name="frame">The frame.</param> currentImage.SetPixels(imageWidth, imageHeight, pixels);
private void RestoreToBackground(ImageBase<TColor, TPacked> frame) currentImage.Quality = colorTableLength / 3;
{
if (this.restoreArea == null)
{
return;
}
// Optimization for when the size of the frame is the same as the image size. if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0)
if (this.restoreArea.Value.Width == this.decodedImage.Width && {
this.restoreArea.Value.Height == this.decodedImage.Height) this.decodedImage.FrameDelay = this.graphicsControlExtension.DelayTime;
{ }
using (PixelAccessor<TColor, TPacked> pixelAccessor = frame.Lock()) }
else
{ {
pixelAccessor.Reset(); ImageFrame<TColor, TPacked> frame = new ImageFrame<TColor, TPacked>();
currentImage = frame;
currentImage.SetPixels(imageWidth, imageHeight, pixels);
currentImage.Quality = colorTableLength / 3;
if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0)
{
currentImage.FrameDelay = this.graphicsControlExtension.DelayTime;
}
this.decodedImage.Frames.Add(frame);
} }
}
else if (this.graphicsControlExtension != null)
{
using (PixelArea<TColor, TPacked> emptyRow = new PixelArea<TColor, TPacked>(this.restoreArea.Value.Width, ComponentOrder.XYZW))
{ {
using (PixelAccessor<TColor, TPacked> pixelAccessor = frame.Lock()) if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
{ {
for (int y = this.restoreArea.Value.Top; y < this.restoreArea.Value.Top + this.restoreArea.Value.Height; y++) for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++)
{ {
pixelAccessor.CopyFrom(emptyRow, y, this.restoreArea.Value.Left); for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++)
{
currentPixels[x, y] = default(TColor);
}
} }
} }
else if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
{
this.currentFrame = lastFrame;
}
} }
} } // End lock
this.restoreArea = null;
} }
} }
} }

92
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -9,7 +9,6 @@ namespace ImageSharp.Formats
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics;
using IO; using IO;
using Quantizers; using Quantizers;
@ -20,9 +19,9 @@ namespace ImageSharp.Formats
internal sealed class GifEncoderCore internal sealed class GifEncoderCore
{ {
/// <summary> /// <summary>
/// The pixel buffer, used to reduce allocations. /// The temp buffer used to reduce allocations.
/// </summary> /// </summary>
private readonly byte[] pixelBuffer = new byte[3]; private readonly byte[] buffer = new byte[16];
/// <summary> /// <summary>
/// The number of bits requires to store the image palette. /// The number of bits requires to store the image palette.
@ -77,7 +76,7 @@ namespace ImageSharp.Formats
// Quantize the image returning a palette. // Quantize the image returning a palette.
QuantizedImage<TColor, TPacked> quantized = ((IQuantizer<TColor, TPacked>)this.Quantizer).Quantize(image, this.Quality); QuantizedImage<TColor, TPacked> quantized = ((IQuantizer<TColor, TPacked>)this.Quantizer).Quantize(image, this.Quality);
int index = GetTransparentIndex(quantized); int index = this.GetTransparentIndex(quantized);
// Write the header. // Write the header.
this.WriteHeader(writer); this.WriteHeader(writer);
@ -95,11 +94,14 @@ namespace ImageSharp.Formats
if (image.Frames.Any()) if (image.Frames.Any())
{ {
this.WriteApplicationExtension(writer, image.RepeatCount, image.Frames.Count); this.WriteApplicationExtension(writer, image.RepeatCount, image.Frames.Count);
foreach (ImageFrame<TColor, TPacked> frame in image.Frames)
// ReSharper disable once ForCanBeConvertedToForeach
for (int i = 0; i < image.Frames.Count; i++)
{ {
ImageFrame<TColor, TPacked> frame = image.Frames[i];
QuantizedImage<TColor, TPacked> quantizedFrame = ((IQuantizer<TColor, TPacked>)this.Quantizer).Quantize(frame, this.Quality); QuantizedImage<TColor, TPacked> quantizedFrame = ((IQuantizer<TColor, TPacked>)this.Quantizer).Quantize(frame, this.Quality);
this.WriteGraphicalControlExtension(frame, writer, GetTransparentIndex(quantizedFrame)); this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame));
this.WriteImageDescriptor(frame, writer); this.WriteImageDescriptor(frame, writer);
this.WriteColorTable(quantizedFrame, writer); this.WriteColorTable(quantizedFrame, writer);
this.WriteImageData(quantizedFrame, writer); this.WriteImageData(quantizedFrame, writer);
@ -121,25 +123,35 @@ namespace ImageSharp.Formats
/// <returns> /// <returns>
/// The <see cref="int"/>. /// The <see cref="int"/>.
/// </returns> /// </returns>
private static int GetTransparentIndex<TColor, TPacked>(QuantizedImage<TColor, TPacked> quantized) private int GetTransparentIndex<TColor, TPacked>(QuantizedImage<TColor, TPacked> quantized)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked> where TPacked : struct, IEquatable<TPacked>
{ {
// Find the lowest alpha value and make it the transparent index. // Find the lowest alpha value and make it the transparent index.
int index = -1; int index = 255;
float alpha = 1; byte alpha = 255;
bool hasEmpty = false;
// Some images may have more than one quantized pixel returned with an alpha value of zero
// (No idea why?!) so we should always ignore if we have empty pixels present.
for (int i = 0; i < quantized.Palette.Length; i++) for (int i = 0; i < quantized.Palette.Length; i++)
{ {
Vector4 vector = quantized.Palette[i].ToVector4(); quantized.Palette[i].ToBytes(this.buffer, 0, ComponentOrder.XYZW);
if (vector == Vector4.Zero)
{
return i;
}
if (vector.W < alpha) if (!hasEmpty)
{ {
alpha = vector.W; if (this.buffer[0] == 0 && this.buffer[1] == 0 && this.buffer[2] == 0 && this.buffer[3] == 0)
index = i; {
alpha = this.buffer[3];
index = i;
hasEmpty = true;
}
if (this.buffer[3] < alpha)
{
alpha = this.buffer[3];
index = i;
}
} }
} }
@ -173,7 +185,7 @@ namespace ImageSharp.Formats
Height = (short)image.Height, Height = (short)image.Height,
GlobalColorTableFlag = false, // Always false for now. GlobalColorTableFlag = false, // Always false for now.
GlobalColorTableSize = this.bitDepth - 1, GlobalColorTableSize = this.bitDepth - 1,
BackgroundColorIndex = (byte)(tranparencyIndex > -1 ? tranparencyIndex : 255) BackgroundColorIndex = (byte)tranparencyIndex
}; };
writer.Write((ushort)descriptor.Width); writer.Write((ushort)descriptor.Width);
@ -186,13 +198,11 @@ namespace ImageSharp.Formats
field.SetBits(5, 3, descriptor.GlobalColorTableSize); // 6-8 : GCT size. 2^(N+1) field.SetBits(5, 3, descriptor.GlobalColorTableSize); // 6-8 : GCT size. 2^(N+1)
// Reduce the number of writes // Reduce the number of writes
byte[] arr = this.buffer[0] = field.Byte;
{ this.buffer[1] = descriptor.BackgroundColorIndex; // Background Color Index
field.Byte, descriptor.BackgroundColorIndex, // Background Color Index this.buffer[2] = descriptor.PixelAspectRatio; // Pixel aspect ratio. Assume 1:1
descriptor.PixelAspectRatio // Pixel aspect ratio. Assume 1:1
};
writer.Write(arr); writer.Write(this.buffer, 0, 3);
} }
/// <summary> /// <summary>
@ -206,13 +216,11 @@ namespace ImageSharp.Formats
// Application Extension Header // Application Extension Header
if (repeatCount != 1 && frames > 0) if (repeatCount != 1 && frames > 0)
{ {
byte[] ext = this.buffer[0] = GifConstants.ExtensionIntroducer;
{ this.buffer[1] = GifConstants.ApplicationExtensionLabel;
GifConstants.ExtensionIntroducer, GifConstants.ApplicationExtensionLabel, this.buffer[2] = GifConstants.ApplicationBlockSize;
GifConstants.ApplicationBlockSize
};
writer.Write(ext); writer.Write(this.buffer, 0, 3);
writer.Write(GifConstants.ApplicationIdentification.ToCharArray()); // NETSCAPE2.0 writer.Write(GifConstants.ApplicationIdentification.ToCharArray()); // NETSCAPE2.0
writer.Write((byte)3); // Application block length writer.Write((byte)3); // Application block length
@ -243,7 +251,7 @@ namespace ImageSharp.Formats
where TPacked : struct, IEquatable<TPacked> where TPacked : struct, IEquatable<TPacked>
{ {
// TODO: Check transparency logic. // TODO: Check transparency logic.
bool hasTransparent = transparencyIndex > -1; bool hasTransparent = transparencyIndex < 255;
DisposalMethod disposalMethod = hasTransparent DisposalMethod disposalMethod = hasTransparent
? DisposalMethod.RestoreToBackground ? DisposalMethod.RestoreToBackground
: DisposalMethod.Unspecified; : DisposalMethod.Unspecified;
@ -256,13 +264,11 @@ namespace ImageSharp.Formats
DelayTime = image.FrameDelay DelayTime = image.FrameDelay
}; };
// Reduce the number of writes. // Write the intro.
byte[] intro = this.buffer[0] = GifConstants.ExtensionIntroducer;
{ this.buffer[1] = GifConstants.GraphicControlLabel;
GifConstants.ExtensionIntroducer, GifConstants.GraphicControlLabel, 4 // Size this.buffer[2] = 4;
}; writer.Write(this.buffer, 0, 3);
writer.Write(intro);
PackedField field = default(PackedField); PackedField field = default(PackedField);
field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal
@ -273,7 +279,7 @@ namespace ImageSharp.Formats
writer.Write(field.Byte); writer.Write(field.Byte);
writer.Write((ushort)extension.DelayTime); writer.Write((ushort)extension.DelayTime);
writer.Write((byte)(extension.TransparencyIndex == -1 ? 255 : extension.TransparencyIndex)); writer.Write((byte)extension.TransparencyIndex);
writer.Write(GifConstants.Terminator); writer.Write(GifConstants.Terminator);
} }
@ -327,10 +333,10 @@ namespace ImageSharp.Formats
for (int i = 0; i < pixelCount; i++) for (int i = 0; i < pixelCount; i++)
{ {
int offset = i * 3; int offset = i * 3;
image.Palette[i].ToBytes(this.pixelBuffer, 0, ComponentOrder.XYZ); image.Palette[i].ToBytes(this.buffer, 0, ComponentOrder.XYZ);
colorTable[offset] = this.pixelBuffer[0]; colorTable[offset] = this.buffer[0];
colorTable[offset + 1] = this.pixelBuffer[1]; colorTable[offset + 1] = this.buffer[1];
colorTable[offset + 2] = this.pixelBuffer[2]; colorTable[offset + 2] = this.buffer[2];
} }
writer.Write(colorTable, 0, colorTableLength); writer.Write(colorTable, 0, colorTableLength);

17
src/ImageSharp/Formats/Gif/LzwDecoder.cs

@ -2,6 +2,7 @@
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System; using System;
@ -71,6 +72,10 @@ namespace ImageSharp.Formats
this.prefix = ArrayPool<int>.Shared.Rent(MaxStackSize); this.prefix = ArrayPool<int>.Shared.Rent(MaxStackSize);
this.suffix = ArrayPool<int>.Shared.Rent(MaxStackSize); this.suffix = ArrayPool<int>.Shared.Rent(MaxStackSize);
this.pixelStack = ArrayPool<int>.Shared.Rent(MaxStackSize + 1); this.pixelStack = ArrayPool<int>.Shared.Rent(MaxStackSize + 1);
Array.Clear(this.prefix, 0, MaxStackSize);
Array.Clear(this.suffix, 0, MaxStackSize);
Array.Clear(this.pixelStack, 0, MaxStackSize + 1);
} }
/// <summary> /// <summary>
@ -79,13 +84,13 @@ namespace ImageSharp.Formats
/// <param name="width">The width of the pixel index array.</param> /// <param name="width">The width of the pixel index array.</param>
/// <param name="height">The height of the pixel index array.</param> /// <param name="height">The height of the pixel index array.</param>
/// <param name="dataSize">Size of the data.</param> /// <param name="dataSize">Size of the data.</param>
/// <returns>The decoded and uncompressed array.</returns> /// <param name="pixels">The pixel array to decode to.</param>
public byte[] DecodePixels(int width, int height, int dataSize) public void DecodePixels(int width, int height, int dataSize, byte[] pixels)
{ {
Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize)); Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize));
// The resulting index table. // The resulting index table length.
byte[] pixels = new byte[width * height]; int length = width * height;
// Calculate the clear code. The value of the clear code is 2 ^ dataSize // Calculate the clear code. The value of the clear code is 2 ^ dataSize
int clearCode = 1 << dataSize; int clearCode = 1 << dataSize;
@ -120,7 +125,7 @@ namespace ImageSharp.Formats
} }
byte[] buffer = new byte[255]; byte[] buffer = new byte[255];
while (xyz < pixels.Length) while (xyz < length)
{ {
if (top == 0) if (top == 0)
{ {
@ -217,8 +222,6 @@ namespace ImageSharp.Formats
// Clear missing pixels // Clear missing pixels
pixels[xyz++] = (byte)this.pixelStack[top]; pixels[xyz++] = (byte)this.pixelStack[top];
} }
return pixels;
} }
/// <inheritdoc /> /// <inheritdoc />

2
src/ImageSharp/Formats/Gif/LzwEncoder.cs

@ -200,6 +200,8 @@ namespace ImageSharp.Formats
this.hashTable = ArrayPool<int>.Shared.Rent(HashSize); this.hashTable = ArrayPool<int>.Shared.Rent(HashSize);
this.codeTable = ArrayPool<int>.Shared.Rent(HashSize); this.codeTable = ArrayPool<int>.Shared.Rent(HashSize);
Array.Clear(this.hashTable, 0, HashSize);
Array.Clear(this.codeTable, 0, HashSize);
} }
/// <summary> /// <summary>

1
src/ImageSharp/Quantizers/Octree/Quantizer.cs

@ -6,7 +6,6 @@
namespace ImageSharp.Quantizers namespace ImageSharp.Quantizers
{ {
using System; using System;
using System.Threading.Tasks;
/// <summary> /// <summary>
/// Encapsulates methods to calculate the color palette of an image. /// Encapsulates methods to calculate the color palette of an image.

375
src/ImageSharp/Quantizers/Wu/WuQuantizer.cs

@ -65,6 +65,21 @@ namespace ImageSharp.Quantizers
/// </summary> /// </summary>
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
/// <summary>
/// The long array pool.
/// </summary>
private static readonly ArrayPool<long> LongPool = ArrayPool<long>.Create(TableLength, 25);
/// <summary>
/// The double array pool.
/// </summary>
private static readonly ArrayPool<double> DoublePool = ArrayPool<double>.Create(TableLength, 5);
/// <summary>
/// The byte array pool.
/// </summary>
private static readonly ArrayPool<byte> BytePool = ArrayPool<byte>.Create(TableLength, 5);
/// <summary> /// <summary>
/// Moment of <c>P(c)</c>. /// Moment of <c>P(c)</c>.
/// </summary> /// </summary>
@ -100,18 +115,24 @@ namespace ImageSharp.Quantizers
/// </summary> /// </summary>
private readonly byte[] tag; private readonly byte[] tag;
/// <summary>
/// A buffer for storing pixels
/// </summary>
private readonly byte[] rgbaBuffer = new byte[4];
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WuQuantizer{TColor,TPacked}"/> class. /// Initializes a new instance of the <see cref="WuQuantizer{TColor,TPacked}"/> class.
/// </summary> /// </summary>
public WuQuantizer() public WuQuantizer()
{ {
this.vwt = new long[TableLength]; // TODO: We might have to use a custom pool here. The default doesn't appear to save memory
this.vmr = new long[TableLength]; this.vwt = LongPool.Rent(TableLength);
this.vmg = new long[TableLength]; this.vmr = LongPool.Rent(TableLength);
this.vmb = new long[TableLength]; this.vmg = LongPool.Rent(TableLength);
this.vma = new long[TableLength]; this.vmb = LongPool.Rent(TableLength);
this.m2 = new double[TableLength]; this.vma = LongPool.Rent(TableLength);
this.tag = new byte[TableLength]; this.m2 = DoublePool.Rent(TableLength);
this.tag = BytePool.Rent(TableLength);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -145,14 +166,9 @@ namespace ImageSharp.Quantizers
/// <returns>The index.</returns> /// <returns>The index.</returns>
private static int GetPaletteIndex(int r, int g, int b, int a) private static int GetPaletteIndex(int r, int g, int b, int a)
{ {
return (r << ((IndexBits * 2) + IndexAlphaBits)) return (r << ((IndexBits * 2) + IndexAlphaBits)) + (r << (IndexBits + IndexAlphaBits + 1))
+ (r << (IndexBits + IndexAlphaBits + 1)) + (g << (IndexBits + IndexAlphaBits)) + (r << (IndexBits * 2)) + (r << (IndexBits + 1))
+ (g << (IndexBits + IndexAlphaBits)) + (g << IndexBits) + ((r + g + b) << IndexAlphaBits) + r + g + b + a;
+ (r << (IndexBits * 2))
+ (r << (IndexBits + 1))
+ (g << IndexBits)
+ ((r + g + b) << IndexAlphaBits)
+ r + g + b + a;
} }
/// <summary> /// <summary>
@ -164,21 +180,21 @@ namespace ImageSharp.Quantizers
private static double Volume(Box cube, long[] moment) private static double Volume(Box cube, long[] moment)
{ {
return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)] return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A1)]
- moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
- moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)]
+ moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)]
- moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)]
+ moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)]
+ moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)]
- moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
- moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)]
+ moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)]
+ moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)]
- moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
+ moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)]
- moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
- moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
} }
/// <summary> /// <summary>
@ -195,46 +211,46 @@ namespace ImageSharp.Quantizers
// Red // Red
case 0: case 0:
return -moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)] return -moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A1)]
+ moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)]
+ moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)]
- moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
+ moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)]
- moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
- moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
// Green // Green
case 1: case 1:
return -moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)] return -moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A1)]
+ moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)]
+ moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)]
- moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
+ moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)] + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A1)]
- moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
- moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
// Blue // Blue
case 2: case 2:
return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)] return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A1)]
+ moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)]
+ moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)] + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A1)]
- moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
+ moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)] + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A1)]
- moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
- moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)] - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A1)]
+ moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
// Alpha // Alpha
case 3: case 3:
return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)] return -moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, cube.A0)]
+ moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)] + moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, cube.A0)]
+ moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)] + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, cube.A0)]
- moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)] - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, cube.A0)]
+ moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)] + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, cube.A0)]
- moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)] - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, cube.A0)]
- moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)] - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, cube.A0)]
+ moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)]; + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, cube.A0)];
default: default:
throw new ArgumentOutOfRangeException(nameof(direction)); throw new ArgumentOutOfRangeException(nameof(direction));
@ -256,46 +272,46 @@ namespace ImageSharp.Quantizers
// Red // Red
case 0: case 0:
return moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A1)] return moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A1)]
- moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A0)] - moment[GetPaletteIndex(position, cube.G1, cube.B1, cube.A0)]
- moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A1)] - moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A1)]
+ moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A0)] + moment[GetPaletteIndex(position, cube.G1, cube.B0, cube.A0)]
- moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A1)] - moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A1)]
+ moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A0)] + moment[GetPaletteIndex(position, cube.G0, cube.B1, cube.A0)]
+ moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A1)] + moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A1)]
- moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A0)]; - moment[GetPaletteIndex(position, cube.G0, cube.B0, cube.A0)];
// Green // Green
case 1: case 1:
return moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A1)] return moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A1)]
- moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A0)] - moment[GetPaletteIndex(cube.R1, position, cube.B1, cube.A0)]
- moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A1)] - moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A1)]
+ moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A0)] + moment[GetPaletteIndex(cube.R1, position, cube.B0, cube.A0)]
- moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A1)] - moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A1)]
+ moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A0)] + moment[GetPaletteIndex(cube.R0, position, cube.B1, cube.A0)]
+ moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A1)] + moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A1)]
- moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A0)]; - moment[GetPaletteIndex(cube.R0, position, cube.B0, cube.A0)];
// Blue // Blue
case 2: case 2:
return moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A1)] return moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A1)]
- moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A0)] - moment[GetPaletteIndex(cube.R1, cube.G1, position, cube.A0)]
- moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A1)] - moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A1)]
+ moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A0)] + moment[GetPaletteIndex(cube.R1, cube.G0, position, cube.A0)]
- moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A1)] - moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A1)]
+ moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A0)] + moment[GetPaletteIndex(cube.R0, cube.G1, position, cube.A0)]
+ moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A1)] + moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A1)]
- moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A0)]; - moment[GetPaletteIndex(cube.R0, cube.G0, position, cube.A0)];
// Alpha // Alpha
case 3: case 3:
return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, position)] return moment[GetPaletteIndex(cube.R1, cube.G1, cube.B1, position)]
- moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, position)] - moment[GetPaletteIndex(cube.R1, cube.G1, cube.B0, position)]
- moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, position)] - moment[GetPaletteIndex(cube.R1, cube.G0, cube.B1, position)]
+ moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, position)] + moment[GetPaletteIndex(cube.R1, cube.G0, cube.B0, position)]
- moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, position)] - moment[GetPaletteIndex(cube.R0, cube.G1, cube.B1, position)]
+ moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, position)] + moment[GetPaletteIndex(cube.R0, cube.G1, cube.B0, position)]
+ moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, position)] + moment[GetPaletteIndex(cube.R0, cube.G0, cube.B1, position)]
- moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, position)]; - moment[GetPaletteIndex(cube.R0, cube.G0, cube.B0, position)];
default: default:
throw new ArgumentOutOfRangeException(nameof(direction)); throw new ArgumentOutOfRangeException(nameof(direction));
@ -313,7 +329,6 @@ namespace ImageSharp.Quantizers
Array.Clear(this.vmb, 0, TableLength); Array.Clear(this.vmb, 0, TableLength);
Array.Clear(this.vma, 0, TableLength); Array.Clear(this.vma, 0, TableLength);
Array.Clear(this.m2, 0, TableLength); Array.Clear(this.m2, 0, TableLength);
Array.Clear(this.tag, 0, TableLength); Array.Clear(this.tag, 0, TableLength);
} }
@ -323,18 +338,17 @@ namespace ImageSharp.Quantizers
/// <param name="pixels">The pixel accessor.</param> /// <param name="pixels">The pixel accessor.</param>
private void Build3DHistogram(PixelAccessor<TColor, TPacked> pixels) private void Build3DHistogram(PixelAccessor<TColor, TPacked> pixels)
{ {
byte[] rgba = new byte[4];
for (int y = 0; y < pixels.Height; y++) for (int y = 0; y < pixels.Height; y++)
{ {
for (int x = 0; x < pixels.Width; x++) for (int x = 0; x < pixels.Width; x++)
{ {
// Colors are expected in r->g->b->a format // Colors are expected in r->g->b->a format
pixels[x, y].ToBytes(rgba, 0, ComponentOrder.XYZW); pixels[x, y].ToBytes(this.rgbaBuffer, 0, ComponentOrder.XYZW);
byte r = rgba[0]; byte r = this.rgbaBuffer[0];
byte g = rgba[1]; byte g = this.rgbaBuffer[1];
byte b = rgba[2]; byte b = this.rgbaBuffer[2];
byte a = rgba[3]; byte a = this.rgbaBuffer[3];
int inr = r >> (8 - IndexBits); int inr = r >> (8 - IndexBits);
int ing = g >> (8 - IndexBits); int ing = g >> (8 - IndexBits);
@ -354,91 +368,109 @@ namespace ImageSharp.Quantizers
} }
/// <summary> /// <summary>
/// Converts the histogram into moments so that we can rapidly calculate /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box.
/// the sums of the above quantities over any desired box.
/// </summary> /// </summary>
private void Get3DMoments() private void Get3DMoments()
{ {
long[] volume = new long[IndexCount * IndexAlphaCount]; long[] volume = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount);
long[] volumeR = new long[IndexCount * IndexAlphaCount]; long[] volumeR = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount);
long[] volumeG = new long[IndexCount * IndexAlphaCount]; long[] volumeG = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount);
long[] volumeB = new long[IndexCount * IndexAlphaCount]; long[] volumeB = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount);
long[] volumeA = new long[IndexCount * IndexAlphaCount]; long[] volumeA = ArrayPool<long>.Shared.Rent(IndexCount * IndexAlphaCount);
double[] volume2 = new double[IndexCount * IndexAlphaCount]; double[] volume2 = ArrayPool<double>.Shared.Rent(IndexCount * IndexAlphaCount);
long[] area = new long[IndexAlphaCount]; long[] area = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
long[] areaR = new long[IndexAlphaCount]; long[] areaR = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
long[] areaG = new long[IndexAlphaCount]; long[] areaG = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
long[] areaB = new long[IndexAlphaCount]; long[] areaB = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
long[] areaA = new long[IndexAlphaCount]; long[] areaA = ArrayPool<long>.Shared.Rent(IndexAlphaCount);
double[] area2 = new double[IndexAlphaCount]; double[] area2 = ArrayPool<double>.Shared.Rent(IndexAlphaCount);
for (int r = 1; r < IndexCount; r++) try
{ {
Array.Clear(volume, 0, IndexCount * IndexAlphaCount); for (int r = 1; r < IndexCount; r++)
Array.Clear(volumeR, 0, IndexCount * IndexAlphaCount);
Array.Clear(volumeG, 0, IndexCount * IndexAlphaCount);
Array.Clear(volumeB, 0, IndexCount * IndexAlphaCount);
Array.Clear(volumeA, 0, IndexCount * IndexAlphaCount);
Array.Clear(volume2, 0, IndexCount * IndexAlphaCount);
for (int g = 1; g < IndexCount; g++)
{ {
Array.Clear(area, 0, IndexAlphaCount); Array.Clear(volume, 0, IndexCount * IndexAlphaCount);
Array.Clear(areaR, 0, IndexAlphaCount); Array.Clear(volumeR, 0, IndexCount * IndexAlphaCount);
Array.Clear(areaG, 0, IndexAlphaCount); Array.Clear(volumeG, 0, IndexCount * IndexAlphaCount);
Array.Clear(areaB, 0, IndexAlphaCount); Array.Clear(volumeB, 0, IndexCount * IndexAlphaCount);
Array.Clear(areaA, 0, IndexAlphaCount); Array.Clear(volumeA, 0, IndexCount * IndexAlphaCount);
Array.Clear(area2, 0, IndexAlphaCount); Array.Clear(volume2, 0, IndexCount * IndexAlphaCount);
for (int b = 1; b < IndexCount; b++) for (int g = 1; g < IndexCount; g++)
{ {
long line = 0; Array.Clear(area, 0, IndexAlphaCount);
long lineR = 0; Array.Clear(areaR, 0, IndexAlphaCount);
long lineG = 0; Array.Clear(areaG, 0, IndexAlphaCount);
long lineB = 0; Array.Clear(areaB, 0, IndexAlphaCount);
long lineA = 0; Array.Clear(areaA, 0, IndexAlphaCount);
double line2 = 0; Array.Clear(area2, 0, IndexAlphaCount);
for (int a = 1; a < IndexAlphaCount; a++) for (int b = 1; b < IndexCount; b++)
{ {
int ind1 = GetPaletteIndex(r, g, b, a); long line = 0;
long lineR = 0;
line += this.vwt[ind1]; long lineG = 0;
lineR += this.vmr[ind1]; long lineB = 0;
lineG += this.vmg[ind1]; long lineA = 0;
lineB += this.vmb[ind1]; double line2 = 0;
lineA += this.vma[ind1];
line2 += this.m2[ind1]; for (int a = 1; a < IndexAlphaCount; a++)
{
area[a] += line; int ind1 = GetPaletteIndex(r, g, b, a);
areaR[a] += lineR;
areaG[a] += lineG; line += this.vwt[ind1];
areaB[a] += lineB; lineR += this.vmr[ind1];
areaA[a] += lineA; lineG += this.vmg[ind1];
area2[a] += line2; lineB += this.vmb[ind1];
lineA += this.vma[ind1];
int inv = (b * IndexAlphaCount) + a; line2 += this.m2[ind1];
volume[inv] += area[a]; area[a] += line;
volumeR[inv] += areaR[a]; areaR[a] += lineR;
volumeG[inv] += areaG[a]; areaG[a] += lineG;
volumeB[inv] += areaB[a]; areaB[a] += lineB;
volumeA[inv] += areaA[a]; areaA[a] += lineA;
volume2[inv] += area2[a]; area2[a] += line2;
int ind2 = ind1 - GetPaletteIndex(1, 0, 0, 0); int inv = (b * IndexAlphaCount) + a;
this.vwt[ind1] = this.vwt[ind2] + volume[inv]; volume[inv] += area[a];
this.vmr[ind1] = this.vmr[ind2] + volumeR[inv]; volumeR[inv] += areaR[a];
this.vmg[ind1] = this.vmg[ind2] + volumeG[inv]; volumeG[inv] += areaG[a];
this.vmb[ind1] = this.vmb[ind2] + volumeB[inv]; volumeB[inv] += areaB[a];
this.vma[ind1] = this.vma[ind2] + volumeA[inv]; volumeA[inv] += areaA[a];
this.m2[ind1] = this.m2[ind2] + volume2[inv]; volume2[inv] += area2[a];
int ind2 = ind1 - GetPaletteIndex(1, 0, 0, 0);
this.vwt[ind1] = this.vwt[ind2] + volume[inv];
this.vmr[ind1] = this.vmr[ind2] + volumeR[inv];
this.vmg[ind1] = this.vmg[ind2] + volumeG[inv];
this.vmb[ind1] = this.vmb[ind2] + volumeB[inv];
this.vma[ind1] = this.vma[ind2] + volumeA[inv];
this.m2[ind1] = this.m2[ind2] + volume2[inv];
}
} }
} }
} }
} }
finally
{
ArrayPool<long>.Shared.Return(volume);
ArrayPool<long>.Shared.Return(volumeR);
ArrayPool<long>.Shared.Return(volumeG);
ArrayPool<long>.Shared.Return(volumeB);
ArrayPool<long>.Shared.Return(volumeA);
ArrayPool<double>.Shared.Return(volume2);
ArrayPool<long>.Shared.Return(area);
ArrayPool<long>.Shared.Return(areaR);
ArrayPool<long>.Shared.Return(areaG);
ArrayPool<long>.Shared.Return(areaB);
ArrayPool<long>.Shared.Return(areaA);
ArrayPool<double>.Shared.Return(area2);
}
} }
/// <summary> /// <summary>
@ -770,6 +802,15 @@ namespace ImageSharp.Quantizers
ArrayPool<byte>.Shared.Return(rgba); ArrayPool<byte>.Shared.Return(rgba);
}); });
// Cleanup
LongPool.Return(this.vwt);
LongPool.Return(this.vmr);
LongPool.Return(this.vmg);
LongPool.Return(this.vmb);
LongPool.Return(this.vma);
DoublePool.Return(this.m2);
BytePool.Return(this.tag);
return new QuantizedImage<TColor, TPacked>(width, height, pallette, pixels); return new QuantizedImage<TColor, TPacked>(width, height, pallette, pixels);
} }
} }

1
tests/ImageSharp.Tests/FileTestBase.cs

@ -42,6 +42,7 @@ namespace ImageSharp.Tests
// TestImages.Png.Filter4, // Perf: Enable for local testing only // TestImages.Png.Filter4, // Perf: Enable for local testing only
// TestImages.Png.FilterVar, // Perf: Enable for local testing only // TestImages.Png.FilterVar, // Perf: Enable for local testing only
TestImages.Gif.Rings, TestImages.Gif.Rings,
TestImages.Gif.Cheers,
// TestImages.Gif.Giphy // Perf: Enable for local testing only // TestImages.Gif.Giphy // Perf: Enable for local testing only
}; };

2
tests/ImageSharp.Tests/TestImages.cs

@ -101,6 +101,8 @@ namespace ImageSharp.Tests
public static TestFile Rings => new TestFile(folder + "rings.gif"); public static TestFile Rings => new TestFile(folder + "rings.gif");
public static TestFile Giphy => new TestFile(folder + "giphy.gif"); public static TestFile Giphy => new TestFile(folder + "giphy.gif");
public static TestFile Cheers => new TestFile(folder + "cheers.gif");
} }
} }
} }

BIN
tests/ImageSharp.Tests/TestImages/Formats/Gif/cheers.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Loading…
Cancel
Save