diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index d5e8067160..d77ce9900b 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -6,6 +6,7 @@
namespace ImageSharp.Formats
{
using System;
+ using System.Buffers;
using System.IO;
///
@@ -17,6 +18,11 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel
where TPacked : struct, IEquatable
{
+ ///
+ /// The temp buffer used to reduce allocations.
+ ///
+ private readonly byte[] buffer = new byte[16];
+
///
/// The image to decode the information to.
///
@@ -33,14 +39,14 @@ namespace ImageSharp.Formats
private byte[] globalColorTable;
///
- /// The next frame.
+ /// The global color table length
///
- private ImageFrame nextFrame;
+ private int globalColorTableLength;
///
- /// The area to restore.
+ /// The current frame.
///
- private Rectangle? restoreArea;
+ private TColor[] currentFrame;
///
/// The logical screen descriptor.
@@ -59,55 +65,65 @@ namespace ImageSharp.Formats
/// The stream containing image data.
public void Decode(Image 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.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.Shared.Return(this.globalColorTable);
}
-
- nextFlag = stream.ReadByte();
}
}
@@ -116,16 +132,14 @@ namespace ImageSharp.Formats
///
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
///
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
///
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.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.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.Shared.Rent(length);
+ this.currentStream.Read(localColorTable, 0, length);
+ }
- byte[] indices = this.ReadFrameIndices(imageDescriptor);
+ indices = ArrayPool.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.Shared.Return(localColorTable);
+ }
- // Skip any remaining blocks
- this.Skip(0);
+ ArrayPool.Shared.Return(indices);
+ }
}
///
/// Reads the frame indices marking the color to use for each pixel.
///
/// The .
- /// The
- private byte[] ReadFrameIndices(GifImageDescriptor imageDescriptor)
+ /// The pixel array to write to.
+ 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;
- }
-
- ///
- /// Reads the local color table from the current frame.
- ///
- /// The .
- /// The
- 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;
}
///
@@ -291,47 +302,30 @@ namespace ImageSharp.Formats
///
/// The indexed pixels.
/// The color table containing the available colors.
+ /// The color table length.
/// The
- 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 previousFrame = null;
-
- ImageFrame currentFrame = null;
-
- ImageBase 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 lastPixels = lastFrame.Lock(imageWidth, imageHeight))
+ using (PixelAccessor currentPixels = this.currentFrame.Lock(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 pixelAccessor = image.Lock())
+ // Lock the current image pixels for fast acces.
+ using (PixelAccessor currentPixels = this.currentFrame.Lock(imageWidth, imageHeight))
{
- using (PixelArea pixelRow = new PixelArea(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 newPixels = pixels.Lock(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 currentImage;
- ///
- /// Restores the current frame background.
- ///
- /// The frame.
- private void RestoreToBackground(ImageBase 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 pixelAccessor = frame.Lock())
+ if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0)
+ {
+ this.decodedImage.FrameDelay = this.graphicsControlExtension.DelayTime;
+ }
+ }
+ else
{
- pixelAccessor.Reset();
+ ImageFrame frame = new ImageFrame();
+
+ 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 emptyRow = new PixelArea(this.restoreArea.Value.Width, ComponentOrder.XYZW))
+
+ if (this.graphicsControlExtension != null)
{
- using (PixelAccessor 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
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index b729f30e90..b968256b75 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/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
{
///
- /// The pixel buffer, used to reduce allocations.
+ /// The temp buffer used to reduce allocations.
///
- private readonly byte[] pixelBuffer = new byte[3];
+ private readonly byte[] buffer = new byte[16];
///
/// The number of bits requires to store the image palette.
@@ -77,7 +76,7 @@ namespace ImageSharp.Formats
// Quantize the image returning a palette.
QuantizedImage quantized = ((IQuantizer)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 frame in image.Frames)
+
+ // ReSharper disable once ForCanBeConvertedToForeach
+ for (int i = 0; i < image.Frames.Count; i++)
{
+ ImageFrame frame = image.Frames[i];
QuantizedImage quantizedFrame = ((IQuantizer)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
///
/// The .
///
- private static int GetTransparentIndex(QuantizedImage quantized)
+ private int GetTransparentIndex(QuantizedImage quantized)
where TColor : struct, IPackedPixel
where TPacked : struct, IEquatable
{
// 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);
}
///
@@ -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
{
// 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);
diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs
index 001f775edc..bc0e9717c6 100644
--- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs
+++ b/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.
//
+
namespace ImageSharp.Formats
{
using System;
@@ -71,6 +72,10 @@ namespace ImageSharp.Formats
this.prefix = ArrayPool.Shared.Rent(MaxStackSize);
this.suffix = ArrayPool.Shared.Rent(MaxStackSize);
this.pixelStack = ArrayPool.Shared.Rent(MaxStackSize + 1);
+
+ Array.Clear(this.prefix, 0, MaxStackSize);
+ Array.Clear(this.suffix, 0, MaxStackSize);
+ Array.Clear(this.pixelStack, 0, MaxStackSize + 1);
}
///
@@ -79,13 +84,13 @@ namespace ImageSharp.Formats
/// The width of the pixel index array.
/// The height of the pixel index array.
/// Size of the data.
- /// The decoded and uncompressed array.
- public byte[] DecodePixels(int width, int height, int dataSize)
+ /// The pixel array to decode to.
+ 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;
}
///
diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs
index 4cc125e2f4..69419444ff 100644
--- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs
+++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs
@@ -200,6 +200,8 @@ namespace ImageSharp.Formats
this.hashTable = ArrayPool.Shared.Rent(HashSize);
this.codeTable = ArrayPool.Shared.Rent(HashSize);
+ Array.Clear(this.hashTable, 0, HashSize);
+ Array.Clear(this.codeTable, 0, HashSize);
}
///
diff --git a/src/ImageSharp/Quantizers/Octree/Quantizer.cs b/src/ImageSharp/Quantizers/Octree/Quantizer.cs
index 9c43d98f78..c1301740a4 100644
--- a/src/ImageSharp/Quantizers/Octree/Quantizer.cs
+++ b/src/ImageSharp/Quantizers/Octree/Quantizer.cs
@@ -6,7 +6,6 @@
namespace ImageSharp.Quantizers
{
using System;
- using System.Threading.Tasks;
///
/// Encapsulates methods to calculate the color palette of an image.
diff --git a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs b/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs
index 6eef9633c4..47e41e84aa 100644
--- a/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs
+++ b/src/ImageSharp/Quantizers/Wu/WuQuantizer.cs
@@ -65,6 +65,21 @@ namespace ImageSharp.Quantizers
///
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
+ ///
+ /// The long array pool.
+ ///
+ private static readonly ArrayPool LongPool = ArrayPool.Create(TableLength, 25);
+
+ ///
+ /// The double array pool.
+ ///
+ private static readonly ArrayPool DoublePool = ArrayPool.Create(TableLength, 5);
+
+ ///
+ /// The byte array pool.
+ ///
+ private static readonly ArrayPool BytePool = ArrayPool.Create(TableLength, 5);
+
///
/// Moment of P(c).
///
@@ -100,18 +115,24 @@ namespace ImageSharp.Quantizers
///
private readonly byte[] tag;
+ ///
+ /// A buffer for storing pixels
+ ///
+ private readonly byte[] rgbaBuffer = new byte[4];
+
///
/// Initializes a new instance of the class.
///
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);
}
///
@@ -145,14 +166,9 @@ namespace ImageSharp.Quantizers
/// The index.
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;
}
///
@@ -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)];
}
///
@@ -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
/// The pixel accessor.
private void Build3DHistogram(PixelAccessor 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
}
///
- /// 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.
///
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.Shared.Rent(IndexCount * IndexAlphaCount);
+ long[] volumeR = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
+ long[] volumeG = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
+ long[] volumeB = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
+ long[] volumeA = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
+ double[] volume2 = ArrayPool.Shared.Rent(IndexCount * IndexAlphaCount);
+
+ long[] area = ArrayPool.Shared.Rent(IndexAlphaCount);
+ long[] areaR = ArrayPool.Shared.Rent(IndexAlphaCount);
+ long[] areaG = ArrayPool.Shared.Rent(IndexAlphaCount);
+ long[] areaB = ArrayPool.Shared.Rent(IndexAlphaCount);
+ long[] areaA = ArrayPool.Shared.Rent(IndexAlphaCount);
+ double[] area2 = ArrayPool.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.Shared.Return(volume);
+ ArrayPool.Shared.Return(volumeR);
+ ArrayPool.Shared.Return(volumeG);
+ ArrayPool.Shared.Return(volumeB);
+ ArrayPool.Shared.Return(volumeA);
+ ArrayPool.Shared.Return(volume2);
+
+ ArrayPool.Shared.Return(area);
+ ArrayPool.Shared.Return(areaR);
+ ArrayPool.Shared.Return(areaG);
+ ArrayPool.Shared.Return(areaB);
+ ArrayPool.Shared.Return(areaA);
+ ArrayPool.Shared.Return(area2);
+ }
}
///
@@ -770,6 +802,15 @@ namespace ImageSharp.Quantizers
ArrayPool.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(width, height, pallette, pixels);
}
}
diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs
index 984100bf10..1aa70989bc 100644
--- a/tests/ImageSharp.Tests/FileTestBase.cs
+++ b/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
};
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index bd745f7e1c..671d0c1dd1 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/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");
}
}
}
diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Gif/cheers.gif b/tests/ImageSharp.Tests/TestImages/Formats/Gif/cheers.gif
new file mode 100644
index 0000000000..237342069e
Binary files /dev/null and b/tests/ImageSharp.Tests/TestImages/Formats/Gif/cheers.gif differ