diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index da10061fb..0ae8bf28d 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. /// @@ -32,6 +38,11 @@ namespace ImageSharp.Formats /// private byte[] globalColorTable; + /// + /// The global color table length + /// + private int globalColorTableLength; + /// /// The current frame. /// @@ -54,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(); } } @@ -111,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) }; @@ -132,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 @@ -157,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) @@ -216,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); + } } } @@ -231,18 +251,32 @@ namespace ImageSharp.Formats { GifImageDescriptor imageDescriptor = this.ReadImageDescriptor(); - byte[] localColorTable = this.ReadFrameLocalColorTable(imageDescriptor); - - byte[] indices = this.ReadFrameIndices(imageDescriptor); + byte[] localColorTable = null; - // 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; + 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); + } - this.ReadFrameColors(indices, colorTable, imageDescriptor); + byte[] indices = this.ReadFrameIndices(imageDescriptor); + this.ReadFrameColors(indices, localColorTable ?? this.globalColorTable, length, imageDescriptor); - // Skip any remaining blocks - this.Skip(0); + // Skip any remaining blocks + this.Skip(0); + } + finally + { + if (localColorTable != null) + { + ArrayPool.Shared.Return(localColorTable); + } + } } /// @@ -259,32 +293,14 @@ namespace ImageSharp.Formats } } - /// - /// 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; - } - /// /// Reads the frames colors, mapping indices to colors. /// /// The indexed pixels. /// The color table containing the available colors. + /// The color table length. /// The - private 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; @@ -301,7 +317,12 @@ namespace ImageSharp.Formats { lastFrame = new TColor[imageWidth * imageHeight]; - Array.Copy(this.currentFrame, lastFrame, lastFrame.Length); + // Array.Copy(this.currentFrame, lastFrame, lastFrame.Length); + using (PixelAccessor lastPixels = lastFrame.Lock(imageWidth, imageHeight)) + using (PixelAccessor currentPixels = this.currentFrame.Lock(imageWidth, imageHeight)) + { + currentPixels.CopyImage(lastPixels); + } } int offset, i = 0; @@ -368,7 +389,12 @@ namespace ImageSharp.Formats TColor[] pixels = new TColor[imageWidth * imageHeight]; - Array.Copy(this.currentFrame, pixels, pixels.Length); + // Array.Copy(this.currentFrame, pixels, pixels.Length); + using (PixelAccessor newPixels = pixels.Lock(imageWidth, imageHeight)) + using (PixelAccessor currentPixels = this.currentFrame.Lock(imageWidth, imageHeight)) + { + currentPixels.CopyImage(newPixels); + } ImageBase currentImage; @@ -376,7 +402,7 @@ namespace ImageSharp.Formats { currentImage = this.decodedImage; currentImage.SetPixels(imageWidth, imageHeight, pixels); - currentImage.Quality = colorTable.Length / 3; + currentImage.Quality = colorTableLength / 3; if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) { @@ -389,7 +415,7 @@ namespace ImageSharp.Formats currentImage = frame; currentImage.SetPixels(imageWidth, imageHeight, pixels); - currentImage.Quality = colorTable.Length / 3; + currentImage.Quality = colorTableLength / 3; if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) { @@ -421,4 +447,4 @@ namespace ImageSharp.Formats } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index d5b0fccd6..b968256b7 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -19,7 +19,7 @@ namespace ImageSharp.Formats internal sealed class GifEncoderCore { /// - /// The pixel buffer, used to reduce allocations. + /// The temp buffer used to reduce allocations. /// private readonly byte[] buffer = new byte[16];