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;