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