From e56789cafb6d346a52ac427d7b513ae296c732d5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 21 Dec 2016 14:42:37 +1100 Subject: [PATCH] Better transparency detection + reduce allocations --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 90 +++++++++++--------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index b729f30e90..d5b0fccd6c 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; @@ -22,7 +21,7 @@ namespace ImageSharp.Formats /// /// The pixel 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);