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
{
using System;
using System.Buffers;
using System.IO;
/// <summary>
@ -17,6 +18,11 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked>
{
/// <summary>
/// The temp buffer used to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[16];
/// <summary>
/// The image to decode the information to.
/// </summary>
@ -33,14 +39,14 @@ namespace ImageSharp.Formats
private byte[] globalColorTable;
/// <summary>
/// The next frame.
/// The global color table length
/// </summary>
private ImageFrame<TColor, TPacked> nextFrame;
private int globalColorTableLength;
/// <summary>
/// The area to restore.
/// The current frame.
/// </summary>
private Rectangle? restoreArea;
private TColor[] currentFrame;
/// <summary>
/// The logical screen descriptor.
@ -59,55 +65,65 @@ namespace ImageSharp.Formats
/// <param name="stream">The stream containing image data. </param>
public void Decode(Image<TColor, TPacked> image, Stream stream)
{
this.decodedImage = image;
this.currentStream = stream;
// Skip the identifier
this.currentStream.Skip(6);
this.ReadLogicalScreenDescriptor();
if (this.logicalScreenDescriptor.GlobalColorTableFlag)
try
{
this.globalColorTable = new byte[this.logicalScreenDescriptor.GlobalColorTableSize * 3];
this.decodedImage = image;
this.currentStream = stream;
// Read the global color table from the stream
stream.Read(this.globalColorTable, 0, this.globalColorTable.Length);
}
// Skip the identifier
this.currentStream.Skip(6);
this.ReadLogicalScreenDescriptor();
// Loop though the respective gif parts and read the data.
int nextFlag = stream.ReadByte();
while (nextFlag != GifConstants.Terminator)
{
if (nextFlag == GifConstants.ImageLabel)
if (this.logicalScreenDescriptor.GlobalColorTableFlag)
{
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();
switch (label)
if (nextFlag == GifConstants.ImageLabel)
{
case GifConstants.GraphicControlLabel:
this.ReadGraphicalControlExtension();
break;
case GifConstants.CommentLabel:
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;
this.ReadFrame();
}
else if (nextFlag == GifConstants.ExtensionIntroducer)
{
int label = stream.ReadByte();
switch (label)
{
case GifConstants.GraphicControlLabel:
this.ReadGraphicalControlExtension();
break;
case GifConstants.CommentLabel:
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>
private void ReadGraphicalControlExtension()
{
byte[] buffer = new byte[6];
this.currentStream.Read(buffer, 0, buffer.Length);
this.currentStream.Read(this.buffer, 0, 6);
byte packed = buffer[1];
byte packed = this.buffer[1];
this.graphicsControlExtension = new GifGraphicsControlExtension
{
DelayTime = BitConverter.ToInt16(buffer, 2),
TransparencyIndex = buffer[4],
DelayTime = BitConverter.ToInt16(this.buffer, 2),
TransparencyIndex = this.buffer[4],
TransparencyFlag = (packed & 0x01) == 1,
DisposalMethod = (DisposalMethod)((packed & 0x1C) >> 2)
};
@ -137,18 +151,16 @@ namespace ImageSharp.Formats
/// <returns><see cref="GifImageDescriptor"/></returns>
private GifImageDescriptor ReadImageDescriptor()
{
byte[] buffer = new byte[9];
this.currentStream.Read(this.buffer, 0, 9);
this.currentStream.Read(buffer, 0, buffer.Length);
byte packed = buffer[8];
byte packed = this.buffer[8];
GifImageDescriptor imageDescriptor = new GifImageDescriptor
{
Left = BitConverter.ToInt16(buffer, 0),
Top = BitConverter.ToInt16(buffer, 2),
Width = BitConverter.ToInt16(buffer, 4),
Height = BitConverter.ToInt16(buffer, 6),
Left = BitConverter.ToInt16(this.buffer, 0),
Top = BitConverter.ToInt16(this.buffer, 2),
Width = BitConverter.ToInt16(this.buffer, 4),
Height = BitConverter.ToInt16(this.buffer, 6),
LocalColorTableFlag = ((packed & 0x80) >> 7) == 1,
LocalColorTableSize = 2 << (packed & 0x07),
InterlaceFlag = ((packed & 0x40) >> 6) == 1
@ -162,26 +174,23 @@ namespace ImageSharp.Formats
/// </summary>
private void ReadLogicalScreenDescriptor()
{
byte[] buffer = new byte[7];
this.currentStream.Read(buffer, 0, buffer.Length);
this.currentStream.Read(this.buffer, 0, 7);
byte packed = buffer[4];
byte packed = this.buffer[4];
this.logicalScreenDescriptor = new GifLogicalScreenDescriptor
{
Width = BitConverter.ToInt16(buffer, 0),
Height = BitConverter.ToInt16(buffer, 2),
BackgroundColorIndex = buffer[5],
PixelAspectRatio = buffer[6],
Width = BitConverter.ToInt16(this.buffer, 0),
Height = BitConverter.ToInt16(this.buffer, 2),
BackgroundColorIndex = this.buffer[5],
PixelAspectRatio = this.buffer[6],
GlobalColorTableFlag = ((packed & 0x80) >> 7) == 1,
GlobalColorTableSize = 2 << (packed & 0x07)
};
if (this.logicalScreenDescriptor.GlobalColorTableSize > 255 * 4)
{
throw new ImageFormatException(
$"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'");
throw new ImageFormatException($"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'");
}
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}'");
}
byte[] buffer = new byte[flag];
this.currentStream.Read(buffer, 0, flag);
byte[] flagBuffer = ArrayPool<byte>.Shared.Rent(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();
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
// otherwise use the global color table.
byte[] colorTable = localColorTable ?? this.globalColorTable;
this.ReadFrameIndices(imageDescriptor, indices);
this.ReadFrameColors(indices, localColorTable ?? this.globalColorTable, length, imageDescriptor);
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
this.Skip(0);
ArrayPool<byte>.Shared.Return(indices);
}
}
/// <summary>
/// Reads the frame indices marking the color to use for each pixel.
/// </summary>
/// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor)
/// <param name="indices">The pixel array to write to.</param>
private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices)
{
int dataSize = this.currentStream.ReadByte();
byte[] indices;
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>
@ -291,47 +302,30 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="indices">The indexed pixels.</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>
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 imageHeight = this.logicalScreenDescriptor.Height;
ImageFrame<TColor, TPacked> previousFrame = null;
ImageFrame<TColor, TPacked> currentFrame = null;
ImageBase<TColor, TPacked> image;
if (this.nextFrame == null)
if (this.currentFrame == 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.
image.InitPixels(imageWidth, imageHeight);
}
else
if (this.graphicsControlExtension != null
&& this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
{
if (this.graphicsControlExtension != null &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
lastFrame = new TColor[imageWidth * imageHeight];
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;
@ -339,124 +333,119 @@ namespace ImageSharp.Formats
int interlaceIncrement = 8; // The interlacing line increment
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.
int writeY; // the target y offset to write to
if (descriptor.InterlaceFlag)
// If so then we read lines at predetermined offsets.
// When an entire image height worth of offset lines has been read we consider this a pass.
// 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.
// When an entire image height worth of offset lines has been read we consider this a pass.
// With each pass the number of offset lines changes and the starting line changes.
if (interlaceY >= descriptor.Height)
interlacePass++;
switch (interlacePass)
{
interlacePass++;
switch (interlacePass)
{
case 1:
interlaceY = 4;
break;
case 2:
interlaceY = 2;
interlaceIncrement = 4;
break;
case 3:
interlaceY = 1;
interlaceIncrement = 2;
break;
}
case 1:
interlaceY = 4;
break;
case 2:
interlaceY = 2;
interlaceIncrement = 4;
break;
case 3:
interlaceY = 1;
interlaceIncrement = 2;
break;
}
writeY = interlaceY + descriptor.Top;
interlaceY += interlaceIncrement;
}
else
{
writeY = y;
}
pixelRow.Reset();
writeY = interlaceY + descriptor.Top;
byte* pixelBase = pixelRow.PixelBase;
for (int x = 0; x < descriptor.Width; x++)
{
int index = indices[i];
interlaceY += interlaceIncrement;
}
else
{
writeY = y;
}
if (this.graphicsControlExtension == null ||
this.graphicsControlExtension.TransparencyFlag == false ||
this.graphicsControlExtension.TransparencyIndex != index)
{
int indexOffset = index * 3;
*(pixelBase + 0) = colorTable[indexOffset];
*(pixelBase + 1) = colorTable[indexOffset + 1];
*(pixelBase + 2) = colorTable[indexOffset + 2];
*(pixelBase + 3) = 255;
}
for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++)
{
int index = indices[i];
i++;
pixelBase += 4;
if (this.graphicsControlExtension == null
|| 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)
{
this.nextFrame = previousFrame;
return;
}
TColor[] pixels = new TColor[imageWidth * imageHeight];
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 &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
{
this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height);
}
}
ImageBase<TColor, TPacked> currentImage;
/// <summary>
/// Restores the current frame background.
/// </summary>
/// <param name="frame">The frame.</param>
private void RestoreToBackground(ImageBase<TColor, TPacked> frame)
{
if (this.restoreArea == null)
{
return;
}
if (this.decodedImage.Pixels == null)
{
currentImage = this.decodedImage;
currentImage.SetPixels(imageWidth, imageHeight, pixels);
currentImage.Quality = colorTableLength / 3;
// Optimization for when the size of the frame is the same as the image size.
if (this.restoreArea.Value.Width == this.decodedImage.Width &&
this.restoreArea.Value.Height == this.decodedImage.Height)
{
using (PixelAccessor<TColor, TPacked> pixelAccessor = frame.Lock())
if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0)
{
this.decodedImage.FrameDelay = this.graphicsControlExtension.DelayTime;
}
}
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
{
using (PixelArea<TColor, TPacked> emptyRow = new PixelArea<TColor, TPacked>(this.restoreArea.Value.Width, ComponentOrder.XYZW))
if (this.graphicsControlExtension != null)
{
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;
}
}
}
this.restoreArea = null;
} // End lock
}
}
}
}

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

@ -9,7 +9,6 @@ namespace ImageSharp.Formats
using System.Buffers;
using System.IO;
using System.Linq;
using System.Numerics;
using IO;
using Quantizers;
@ -20,9 +19,9 @@ namespace ImageSharp.Formats
internal sealed class GifEncoderCore
{
/// <summary>
/// The pixel buffer, used to reduce allocations.
/// The temp buffer used to reduce allocations.
/// </summary>
private readonly byte[] pixelBuffer = new byte[3];
private readonly byte[] buffer = new byte[16];
/// <summary>
/// The number of bits requires to store the image palette.
@ -77,7 +76,7 @@ namespace ImageSharp.Formats
// Quantize the image returning a palette.
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.
this.WriteHeader(writer);
@ -95,11 +94,14 @@ namespace ImageSharp.Formats
if (image.Frames.Any())
{
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);
this.WriteGraphicalControlExtension(frame, writer, GetTransparentIndex(quantizedFrame));
this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame));
this.WriteImageDescriptor(frame, writer);
this.WriteColorTable(quantizedFrame, writer);
this.WriteImageData(quantizedFrame, writer);
@ -121,25 +123,35 @@ namespace ImageSharp.Formats
/// <returns>
/// The <see cref="int"/>.
/// </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 TPacked : struct, IEquatable<TPacked>
{
// Find the lowest alpha value and make it the transparent index.
int index = -1;
float alpha = 1;
int index = 255;
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++)
{
Vector4 vector = quantized.Palette[i].ToVector4();
if (vector == Vector4.Zero)
{
return i;
}
quantized.Palette[i].ToBytes(this.buffer, 0, ComponentOrder.XYZW);
if (vector.W < alpha)
if (!hasEmpty)
{
alpha = vector.W;
index = i;
if (this.buffer[0] == 0 && this.buffer[1] == 0 && this.buffer[2] == 0 && this.buffer[3] == 0)
{
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,
GlobalColorTableFlag = false, // Always false for now.
GlobalColorTableSize = this.bitDepth - 1,
BackgroundColorIndex = (byte)(tranparencyIndex > -1 ? tranparencyIndex : 255)
BackgroundColorIndex = (byte)tranparencyIndex
};
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)
// Reduce the number of writes
byte[] arr =
{
field.Byte, descriptor.BackgroundColorIndex, // Background Color Index
descriptor.PixelAspectRatio // Pixel aspect ratio. Assume 1:1
};
this.buffer[0] = field.Byte;
this.buffer[1] = descriptor.BackgroundColorIndex; // Background Color Index
this.buffer[2] = descriptor.PixelAspectRatio; // Pixel aspect ratio. Assume 1:1
writer.Write(arr);
writer.Write(this.buffer, 0, 3);
}
/// <summary>
@ -206,13 +216,11 @@ namespace ImageSharp.Formats
// Application Extension Header
if (repeatCount != 1 && frames > 0)
{
byte[] ext =
{
GifConstants.ExtensionIntroducer, GifConstants.ApplicationExtensionLabel,
GifConstants.ApplicationBlockSize
};
this.buffer[0] = GifConstants.ExtensionIntroducer;
this.buffer[1] = GifConstants.ApplicationExtensionLabel;
this.buffer[2] = GifConstants.ApplicationBlockSize;
writer.Write(ext);
writer.Write(this.buffer, 0, 3);
writer.Write(GifConstants.ApplicationIdentification.ToCharArray()); // NETSCAPE2.0
writer.Write((byte)3); // Application block length
@ -243,7 +251,7 @@ namespace ImageSharp.Formats
where TPacked : struct, IEquatable<TPacked>
{
// TODO: Check transparency logic.
bool hasTransparent = transparencyIndex > -1;
bool hasTransparent = transparencyIndex < 255;
DisposalMethod disposalMethod = hasTransparent
? DisposalMethod.RestoreToBackground
: DisposalMethod.Unspecified;
@ -256,13 +264,11 @@ namespace ImageSharp.Formats
DelayTime = image.FrameDelay
};
// Reduce the number of writes.
byte[] intro =
{
GifConstants.ExtensionIntroducer, GifConstants.GraphicControlLabel, 4 // Size
};
writer.Write(intro);
// Write the intro.
this.buffer[0] = GifConstants.ExtensionIntroducer;
this.buffer[1] = GifConstants.GraphicControlLabel;
this.buffer[2] = 4;
writer.Write(this.buffer, 0, 3);
PackedField field = default(PackedField);
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((ushort)extension.DelayTime);
writer.Write((byte)(extension.TransparencyIndex == -1 ? 255 : extension.TransparencyIndex));
writer.Write((byte)extension.TransparencyIndex);
writer.Write(GifConstants.Terminator);
}
@ -327,10 +333,10 @@ namespace ImageSharp.Formats
for (int i = 0; i < pixelCount; i++)
{
int offset = i * 3;
image.Palette[i].ToBytes(this.pixelBuffer, 0, ComponentOrder.XYZ);
colorTable[offset] = this.pixelBuffer[0];
colorTable[offset + 1] = this.pixelBuffer[1];
colorTable[offset + 2] = this.pixelBuffer[2];
image.Palette[i].ToBytes(this.buffer, 0, ComponentOrder.XYZ);
colorTable[offset] = this.buffer[0];
colorTable[offset + 1] = this.buffer[1];
colorTable[offset + 2] = this.buffer[2];
}
writer.Write(colorTable, 0, colorTableLength);

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

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

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

@ -200,6 +200,8 @@ namespace ImageSharp.Formats
this.hashTable = 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>

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

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

1
tests/ImageSharp.Tests/FileTestBase.cs

@ -42,6 +42,7 @@ namespace ImageSharp.Tests
// TestImages.Png.Filter4, // Perf: Enable for local testing only
// TestImages.Png.FilterVar, // Perf: Enable for local testing only
TestImages.Gif.Rings,
TestImages.Gif.Cheers,
// 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 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