Browse Source

Better transparency detection + reduce allocations

pull/56/head
James Jackson-South 10 years ago
parent
commit
e56789cafb
  1. 90
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs

90
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -9,7 +9,6 @@ namespace ImageSharp.Formats
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics;
using IO; using IO;
using Quantizers; using Quantizers;
@ -22,7 +21,7 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// The pixel buffer, used to reduce allocations. /// The pixel buffer, used to reduce allocations.
/// </summary> /// </summary>
private readonly byte[] pixelBuffer = new byte[3]; private readonly byte[] buffer = new byte[16];
/// <summary> /// <summary>
/// The number of bits requires to store the image palette. /// The number of bits requires to store the image palette.
@ -77,7 +76,7 @@ namespace ImageSharp.Formats
// Quantize the image returning a palette. // Quantize the image returning a palette.
QuantizedImage<TColor, TPacked> quantized = ((IQuantizer<TColor, TPacked>)this.Quantizer).Quantize(image, this.Quality); QuantizedImage<TColor, TPacked> quantized = ((IQuantizer<TColor, TPacked>)this.Quantizer).Quantize(image, this.Quality);
int index = GetTransparentIndex(quantized); int index = this.GetTransparentIndex(quantized);
// Write the header. // Write the header.
this.WriteHeader(writer); this.WriteHeader(writer);
@ -95,11 +94,14 @@ namespace ImageSharp.Formats
if (image.Frames.Any()) if (image.Frames.Any())
{ {
this.WriteApplicationExtension(writer, image.RepeatCount, image.Frames.Count); this.WriteApplicationExtension(writer, image.RepeatCount, image.Frames.Count);
foreach (ImageFrame<TColor, TPacked> frame in image.Frames)
// ReSharper disable once ForCanBeConvertedToForeach
for (int i = 0; i < image.Frames.Count; i++)
{ {
ImageFrame<TColor, TPacked> frame = image.Frames[i];
QuantizedImage<TColor, TPacked> quantizedFrame = ((IQuantizer<TColor, TPacked>)this.Quantizer).Quantize(frame, this.Quality); QuantizedImage<TColor, TPacked> quantizedFrame = ((IQuantizer<TColor, TPacked>)this.Quantizer).Quantize(frame, this.Quality);
this.WriteGraphicalControlExtension(frame, writer, GetTransparentIndex(quantizedFrame)); this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame));
this.WriteImageDescriptor(frame, writer); this.WriteImageDescriptor(frame, writer);
this.WriteColorTable(quantizedFrame, writer); this.WriteColorTable(quantizedFrame, writer);
this.WriteImageData(quantizedFrame, writer); this.WriteImageData(quantizedFrame, writer);
@ -121,25 +123,35 @@ namespace ImageSharp.Formats
/// <returns> /// <returns>
/// The <see cref="int"/>. /// The <see cref="int"/>.
/// </returns> /// </returns>
private static int GetTransparentIndex<TColor, TPacked>(QuantizedImage<TColor, TPacked> quantized) private int GetTransparentIndex<TColor, TPacked>(QuantizedImage<TColor, TPacked> quantized)
where TColor : struct, IPackedPixel<TPacked> where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct, IEquatable<TPacked> where TPacked : struct, IEquatable<TPacked>
{ {
// Find the lowest alpha value and make it the transparent index. // Find the lowest alpha value and make it the transparent index.
int index = -1; int index = 255;
float alpha = 1; 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++) for (int i = 0; i < quantized.Palette.Length; i++)
{ {
Vector4 vector = quantized.Palette[i].ToVector4(); quantized.Palette[i].ToBytes(this.buffer, 0, ComponentOrder.XYZW);
if (vector == Vector4.Zero)
{
return i;
}
if (vector.W < alpha) if (!hasEmpty)
{ {
alpha = vector.W; if (this.buffer[0] == 0 && this.buffer[1] == 0 && this.buffer[2] == 0 && this.buffer[3] == 0)
index = i; {
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, Height = (short)image.Height,
GlobalColorTableFlag = false, // Always false for now. GlobalColorTableFlag = false, // Always false for now.
GlobalColorTableSize = this.bitDepth - 1, GlobalColorTableSize = this.bitDepth - 1,
BackgroundColorIndex = (byte)(tranparencyIndex > -1 ? tranparencyIndex : 255) BackgroundColorIndex = (byte)tranparencyIndex
}; };
writer.Write((ushort)descriptor.Width); 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) field.SetBits(5, 3, descriptor.GlobalColorTableSize); // 6-8 : GCT size. 2^(N+1)
// Reduce the number of writes // Reduce the number of writes
byte[] arr = this.buffer[0] = field.Byte;
{ this.buffer[1] = descriptor.BackgroundColorIndex; // Background Color Index
field.Byte, descriptor.BackgroundColorIndex, // Background Color Index this.buffer[2] = descriptor.PixelAspectRatio; // Pixel aspect ratio. Assume 1:1
descriptor.PixelAspectRatio // Pixel aspect ratio. Assume 1:1
};
writer.Write(arr); writer.Write(this.buffer, 0, 3);
} }
/// <summary> /// <summary>
@ -206,13 +216,11 @@ namespace ImageSharp.Formats
// Application Extension Header // Application Extension Header
if (repeatCount != 1 && frames > 0) if (repeatCount != 1 && frames > 0)
{ {
byte[] ext = this.buffer[0] = GifConstants.ExtensionIntroducer;
{ this.buffer[1] = GifConstants.ApplicationExtensionLabel;
GifConstants.ExtensionIntroducer, GifConstants.ApplicationExtensionLabel, this.buffer[2] = GifConstants.ApplicationBlockSize;
GifConstants.ApplicationBlockSize
};
writer.Write(ext); writer.Write(this.buffer, 0, 3);
writer.Write(GifConstants.ApplicationIdentification.ToCharArray()); // NETSCAPE2.0 writer.Write(GifConstants.ApplicationIdentification.ToCharArray()); // NETSCAPE2.0
writer.Write((byte)3); // Application block length writer.Write((byte)3); // Application block length
@ -243,7 +251,7 @@ namespace ImageSharp.Formats
where TPacked : struct, IEquatable<TPacked> where TPacked : struct, IEquatable<TPacked>
{ {
// TODO: Check transparency logic. // TODO: Check transparency logic.
bool hasTransparent = transparencyIndex > -1; bool hasTransparent = transparencyIndex < 255;
DisposalMethod disposalMethod = hasTransparent DisposalMethod disposalMethod = hasTransparent
? DisposalMethod.RestoreToBackground ? DisposalMethod.RestoreToBackground
: DisposalMethod.Unspecified; : DisposalMethod.Unspecified;
@ -256,13 +264,11 @@ namespace ImageSharp.Formats
DelayTime = image.FrameDelay DelayTime = image.FrameDelay
}; };
// Reduce the number of writes. // Write the intro.
byte[] intro = this.buffer[0] = GifConstants.ExtensionIntroducer;
{ this.buffer[1] = GifConstants.GraphicControlLabel;
GifConstants.ExtensionIntroducer, GifConstants.GraphicControlLabel, 4 // Size this.buffer[2] = 4;
}; writer.Write(this.buffer, 0, 3);
writer.Write(intro);
PackedField field = default(PackedField); PackedField field = default(PackedField);
field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal 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(field.Byte);
writer.Write((ushort)extension.DelayTime); writer.Write((ushort)extension.DelayTime);
writer.Write((byte)(extension.TransparencyIndex == -1 ? 255 : extension.TransparencyIndex)); writer.Write((byte)extension.TransparencyIndex);
writer.Write(GifConstants.Terminator); writer.Write(GifConstants.Terminator);
} }
@ -327,10 +333,10 @@ namespace ImageSharp.Formats
for (int i = 0; i < pixelCount; i++) for (int i = 0; i < pixelCount; i++)
{ {
int offset = i * 3; int offset = i * 3;
image.Palette[i].ToBytes(this.pixelBuffer, 0, ComponentOrder.XYZ); image.Palette[i].ToBytes(this.buffer, 0, ComponentOrder.XYZ);
colorTable[offset] = this.pixelBuffer[0]; colorTable[offset] = this.buffer[0];
colorTable[offset + 1] = this.pixelBuffer[1]; colorTable[offset + 1] = this.buffer[1];
colorTable[offset + 2] = this.pixelBuffer[2]; colorTable[offset + 2] = this.buffer[2];
} }
writer.Write(colorTable, 0, colorTableLength); writer.Write(colorTable, 0, colorTableLength);

Loading…
Cancel
Save