diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index 746e27ff7..4eb5629e5 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -6,11 +6,13 @@
namespace ImageSharp.Formats
{
using System;
+ using System.Buffers;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using IO;
+
using Quantizers;
///
@@ -18,6 +20,11 @@ namespace ImageSharp.Formats
///
internal sealed class GifEncoderCore
{
+ ///
+ /// The pixel buffer, used to reduce allocations.
+ ///
+ private readonly byte[] pixelBuffer = new byte[3];
+
///
/// The number of bits requires to store the image palette.
///
@@ -47,8 +54,7 @@ namespace ImageSharp.Formats
/// The to encode from.
/// The to encode the image data to.
public void Encode(Image image, Stream stream)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ where TColor : struct, IPackedPixel where TPacked : struct
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
@@ -116,11 +122,10 @@ namespace ImageSharp.Formats
/// The .
///
private static int GetTransparentIndex(QuantizedImage quantized)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ where TColor : struct, IPackedPixel where TPacked : struct
{
// Find the lowest alpha value and make it the transparent index.
- int index = 255;
+ int index = -1;
float alpha = 1;
for (int i = 0; i < quantized.Palette.Length; i++)
{
@@ -152,9 +157,10 @@ namespace ImageSharp.Formats
/// The image to encode.
/// The writer to write to the stream with.
/// The transparency index to set the default background index to.
- private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int tranparencyIndex)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ private void WriteLogicalScreenDescriptor(
+ Image image,
+ EndianBinaryWriter writer,
+ int tranparencyIndex) where TColor : struct, IPackedPixel where TPacked : struct
{
GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor
{
@@ -169,18 +175,17 @@ namespace ImageSharp.Formats
writer.Write((ushort)descriptor.Height);
PackedField field = default(PackedField);
- field.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used)
+ field.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used)
field.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution
field.SetBit(4, false); // 5 : GCT sort flag = 0
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
- };
+ {
+ field.Byte, descriptor.BackgroundColorIndex, // Background Color Index
+ descriptor.PixelAspectRatio // Pixel aspect ratio. Assume 1:1
+ };
writer.Write(arr);
}
@@ -197,11 +202,10 @@ namespace ImageSharp.Formats
if (repeatCount != 1 && frames > 0)
{
byte[] ext =
- {
- GifConstants.ExtensionIntroducer,
- GifConstants.ApplicationExtensionLabel,
- GifConstants.ApplicationBlockSize
- };
+ {
+ GifConstants.ExtensionIntroducer, GifConstants.ApplicationExtensionLabel,
+ GifConstants.ApplicationBlockSize
+ };
writer.Write(ext);
@@ -226,15 +230,16 @@ namespace ImageSharp.Formats
/// The to encode.
/// The stream to write to.
/// The index of the color in the color palette to make transparent.
- private void WriteGraphicalControlExtension(ImageBase image, EndianBinaryWriter writer, int transparencyIndex)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ private void WriteGraphicalControlExtension(
+ ImageBase image,
+ EndianBinaryWriter writer,
+ int transparencyIndex) where TColor : struct, IPackedPixel where TPacked : struct
{
// TODO: Check transparency logic.
bool hasTransparent = transparencyIndex > -1;
DisposalMethod disposalMethod = hasTransparent
- ? DisposalMethod.RestoreToBackground
- : DisposalMethod.Unspecified;
+ ? DisposalMethod.RestoreToBackground
+ : DisposalMethod.Unspecified;
GifGraphicsControlExtension extension = new GifGraphicsControlExtension()
{
@@ -247,10 +252,8 @@ namespace ImageSharp.Formats
// Reduce the number of writes.
byte[] intro =
{
- GifConstants.ExtensionIntroducer,
- GifConstants.GraphicControlLabel,
- 4 // Size
- };
+ GifConstants.ExtensionIntroducer, GifConstants.GraphicControlLabel, 4 // Size
+ };
writer.Write(intro);
@@ -275,8 +278,7 @@ namespace ImageSharp.Formats
/// The to be encoded.
/// The stream to write to.
private void WriteImageDescriptor(ImageBase image, EndianBinaryWriter writer)
- where TColor : struct, IPackedPixel
- where TPacked : struct
+ where TColor : struct, IPackedPixel where TPacked : struct
{
writer.Write(GifConstants.ImageDescriptorLabel); // 2c
// TODO: Can we capture this?
@@ -306,27 +308,29 @@ namespace ImageSharp.Formats
where TPacked : struct
{
// Grab the palette and write it to the stream.
- TColor[] palette = image.Palette;
- int pixelCount = palette.Length;
+ int pixelCount = image.Palette.Length;
// Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3;
- byte[] colorTable = new byte[colorTableLength];
+ byte[] colorTable = ArrayPool.Shared.Rent(colorTableLength);
- Parallel.For(
- 0,
- pixelCount,
- i =>
- {
+ try
+ {
+ for (int i = 0; i < pixelCount; i++)
+ {
int offset = i * 3;
- Color color = new Color(palette[i].ToVector4());
-
- colorTable[offset] = color.R;
- colorTable[offset + 1] = color.G;
- colorTable[offset + 2] = color.B;
- });
+ 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];
+ }
- writer.Write(colorTable, 0, colorTableLength);
+ writer.Write(colorTable, 0, colorTableLength);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(colorTable);
+ }
}
///
@@ -340,10 +344,10 @@ namespace ImageSharp.Formats
where TColor : struct, IPackedPixel
where TPacked : struct
{
- byte[] indexedPixels = image.Pixels;
-
- LzwEncoder encoder = new LzwEncoder(indexedPixels, (byte)this.bitDepth);
- encoder.Encode(writer.BaseStream);
+ using (LzwEncoder encoder = new LzwEncoder(image.Pixels, (byte)this.bitDepth))
+ {
+ encoder.Encode(writer.BaseStream);
+ }
}
}
}
diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs
index 003cfa834..fcfadfbba 100644
--- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs
+++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs
@@ -6,6 +6,7 @@
namespace ImageSharp.Formats
{
using System;
+ using System.Buffers;
using System.IO;
///
@@ -31,33 +32,70 @@ namespace ImageSharp.Formats
/// Joe Orost (decvax!vax135!petsd!joe)
///
///
- internal sealed class LzwEncoder
+ internal sealed class LzwEncoder : IDisposable
{
+ ///
+ /// The end-of-file marker
+ ///
private const int Eof = -1;
+ ///
+ /// The maximum number of bits.
+ ///
private const int Bits = 12;
- private const int HashSize = 5003; // 80% occupancy
+ ///
+ /// 80% occupancy
+ ///
+ private const int HashSize = 5003;
+ ///
+ /// Mask used when shifting pixel values
+ ///
+ private static readonly int[] Masks =
+ {
+ 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,
+ 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
+ };
+
+ ///
+ /// The working pixel array
+ ///
private readonly byte[] pixelArray;
+ ///
+ /// The initial code size.
+ ///
private readonly int initialCodeSize;
- private readonly int[] hashTable = new int[HashSize];
-
- private readonly int[] codeTable = new int[HashSize];
+ ///
+ /// The hash table.
+ ///
+ private readonly int[] hashTable;
- private readonly int[] masks =
- {
- 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,
- 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
- };
+ ///
+ /// The code table.
+ ///
+ private readonly int[] codeTable;
///
/// Define the storage for the packet accumulator.
///
private readonly byte[] accumulators = new byte[256];
+ ///
+ /// A value indicating whether this instance of the given entity has been disposed.
+ ///
+ /// if this instance has been disposed; otherwise, .
+ ///
+ /// If the entity is disposed, it must not be disposed a second
+ /// time. The isDisposed field is set the first time the entity
+ /// is disposed. If the isDisposed field is true, then the Dispose()
+ /// method will not dispose again. This help not to prolong the entity's
+ /// life in the Garbage Collector.
+ ///
+ private bool isDisposed = false;
+
///
/// The current pixel
///
@@ -99,39 +137,50 @@ namespace ImageSharp.Formats
///
private bool clearFlag;
- // Algorithm: use open addressing double hashing (no chaining) on the
- // prefix code / next character combination. We do a variant of Knuth's
- // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
- // secondary probe. Here, the modular division first probe is gives way
- // to a faster exclusive-or manipulation. Also do block compression with
- // an adaptive reset, whereby the code table is cleared when the compression
- // ratio decreases, but after the table fills. The variable-length output
- // codes are re-sized at this point, and a special CLEAR code is generated
- // for the decompressor. Late addition: construct the table according to
- // file size for noticeable speed improvement on small files. Please direct
- // questions about this implementation to ames!jaw.
+ ///
+ /// Algorithm: use open addressing double hashing (no chaining) on the
+ /// prefix code / next character combination. We do a variant of Knuth's
+ /// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
+ /// secondary probe. Here, the modular division first probe is gives way
+ /// to a faster exclusive-or manipulation. Also do block compression with
+ /// an adaptive reset, whereby the code table is cleared when the compression
+ /// ratio decreases, but after the table fills. The variable-length output
+ /// codes are re-sized at this point, and a special CLEAR code is generated
+ /// for the decompressor. Late addition: construct the table according to
+ /// file size for noticeable speed improvement on small files. Please direct
+ /// questions about this implementation to ames!jaw.
+ ///
private int globalInitialBits;
+ ///
+ /// The clear code.
+ ///
private int clearCode;
+ ///
+ /// The end-of-file code.
+ ///
private int eofCode;
- // output
- //
- // Output the given code.
- // Inputs:
- // code: A bitCount-bit integer. If == -1, then EOF. This assumes
- // that bitCount =< wordsize - 1.
- // Outputs:
- // Outputs code to the file.
- // Assumptions:
- // Chars are 8 bits long.
- // Algorithm:
- // Maintain a BITS character long buffer (so that 8 codes will
- // fit in it exactly). Use the VAX insv instruction to insert each
- // code in turn. When the buffer fills up empty it and start over.
+ ///
+ /// Output the given code.
+ /// Inputs:
+ /// code: A bitCount-bit integer. If == -1, then EOF. This assumes
+ /// that bitCount =< wordsize - 1.
+ /// Outputs:
+ /// Outputs code to the file.
+ /// Assumptions:
+ /// Chars are 8 bits long.
+ /// Algorithm:
+ /// Maintain a BITS character long buffer (so that 8 codes will
+ /// fit in it exactly). Use the VAX insv instruction to insert each
+ /// code in turn. When the buffer fills up empty it and start over.
+ ///
private int currentAccumulator;
+ ///
+ /// The current bits.
+ ///
private int currentBits;
///
@@ -148,6 +197,9 @@ namespace ImageSharp.Formats
{
this.pixelArray = indexedPixels;
this.initialCodeSize = Math.Max(2, colorDepth);
+
+ this.hashTable = ArrayPool.Shared.Rent(HashSize);
+ this.codeTable = ArrayPool.Shared.Rent(HashSize);
}
///
@@ -168,6 +220,13 @@ namespace ImageSharp.Formats
stream.WriteByte(GifConstants.Terminator);
}
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ this.Dispose(true);
+ }
+
///
/// Gets the maximum code value
///
@@ -348,11 +407,6 @@ namespace ImageSharp.Formats
return Eof;
}
- if (this.currentPixel == this.pixelArray.Length)
- {
- return Eof;
- }
-
this.currentPixel++;
return this.pixelArray[this.currentPixel - 1] & 0xff;
}
@@ -364,7 +418,7 @@ namespace ImageSharp.Formats
/// The stream to write to.
private void Output(int code, Stream outs)
{
- this.currentAccumulator &= this.masks[this.currentBits];
+ this.currentAccumulator &= Masks[this.currentBits];
if (this.currentBits > 0)
{
@@ -415,5 +469,25 @@ namespace ImageSharp.Formats
this.FlushPacket(outs);
}
}
+
+ ///
+ /// Disposes the object and frees resources for the Garbage Collector.
+ ///
+ /// If true, the object gets disposed.
+ private void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ ArrayPool.Shared.Return(this.hashTable);
+ ArrayPool.Shared.Return(this.codeTable);
+ }
+
+ this.isDisposed = true;
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs
index d43edc353..2d640298c 100644
--- a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs
+++ b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs
@@ -7,7 +7,6 @@ namespace ImageSharp.Quantizers
{
using System;
using System.Collections.Generic;
- using System.Numerics;
///
/// Encapsulates methods to calculate the color palette if an image using an Octree pattern.
@@ -183,7 +182,8 @@ namespace ImageSharp.Quantizers
TPacked packed = pixel.PackedValue;
// Check if this request is for the same color as the last
- if (this.previousColor.Equals(packed))
+ // TODO: We should change our TPacked signature to ensure we can compare values without boxing allocations.
+ if (this.previousColor.Equals((IEquatable)packed))
{
// If so, check if I have a previous node setup. This will only occur if the first color in the image
// happens to be black, with an alpha component of zero.
@@ -282,11 +282,6 @@ namespace ImageSharp.Quantizers
///
protected class OctreeNode
{
- ///
- /// Vector representing the maximum number of bytes
- ///
- private static readonly Vector4 MaxBytes = new Vector4(255);
-
///
/// Pointers to any child nodes
///
@@ -481,7 +476,7 @@ namespace ImageSharp.Quantizers
///
/// Return the palette index for the passed color
///
- /// The representing the pixel.
+ /// The pixel data.
/// The level.
/// The buffer array.
///
diff --git a/src/ImageSharp/Quantizers/QuantizedImage.cs b/src/ImageSharp/Quantizers/QuantizedImage.cs
index 956557c62..a204f6c2d 100644
--- a/src/ImageSharp/Quantizers/QuantizedImage.cs
+++ b/src/ImageSharp/Quantizers/QuantizedImage.cs
@@ -33,8 +33,7 @@ namespace ImageSharp.Quantizers
if (pixels.Length != width * height)
{
- throw new ArgumentException(
- $"Pixel array size must be {nameof(width)} * {nameof(height)}", nameof(pixels));
+ throw new ArgumentException($"Pixel array size must be {nameof(width)} * {nameof(height)}", nameof(pixels));
}
this.Width = width;