Skip to content

Commit 4b7538f

Browse files
Merge pull request #536 from carbon/gif-cleanup
Improve Gif CQ, Part 2
2 parents eada0ab + 093245c commit 4b7538f

29 files changed

+439
-1333
lines changed

src/ImageSharp/Formats/Gif/DisposalMethod.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,26 @@ namespace SixLabors.ImageSharp.Formats.Gif
1111
public enum DisposalMethod
1212
{
1313
/// <summary>
14-
/// No disposal specified. The decoder is not required to take any action.
14+
/// No disposal specified.
15+
/// The decoder is not required to take any action.
1516
/// </summary>
1617
Unspecified = 0,
1718

1819
/// <summary>
19-
/// Do not dispose. The graphic is to be left in place.
20+
/// Do not dispose.
21+
/// The graphic is to be left in place.
2022
/// </summary>
2123
NotDispose = 1,
2224

2325
/// <summary>
24-
/// Restore to background color. The area used by the graphic must be restored to
25-
/// the background color.
26+
/// Restore to background color.
27+
/// The area used by the graphic must be restored to the background color.
2628
/// </summary>
2729
RestoreToBackground = 2,
2830

2931
/// <summary>
30-
/// Restore to previous. The decoder is required to restore the area overwritten by the
32+
/// Restore to previous.
33+
/// The decoder is required to restore the area overwritten by the
3134
/// graphic with what was there prior to rendering the graphic.
3235
/// </summary>
3336
RestoreToPrevious = 3

src/ImageSharp/Formats/Gif/GifConstants.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ internal static class GifConstants
5454
/// <summary>
5555
/// The application block size.
5656
/// </summary>
57-
public const byte ApplicationBlockSize = 0x0b;
57+
public const byte ApplicationBlockSize = 11;
5858

5959
/// <summary>
6060
/// The comment label.

src/ImageSharp/Formats/Gif/GifDecoderCore.cs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ internal sealed class GifDecoderCore
3232
/// <summary>
3333
/// The currently loaded stream.
3434
/// </summary>
35-
private Stream currentStream;
35+
private Stream stream;
3636

3737
/// <summary>
3838
/// The global color table.
@@ -57,7 +57,7 @@ internal sealed class GifDecoderCore
5757
/// <summary>
5858
/// The graphics control extension.
5959
/// </summary>
60-
private GifGraphicsControlExtension graphicsControlExtension;
60+
private GifGraphicControlExtension graphicsControlExtension;
6161

6262
/// <summary>
6363
/// The metadata
@@ -236,9 +236,9 @@ public IImageInfo Identify(Stream stream)
236236
/// </summary>
237237
private void ReadGraphicalControlExtension()
238238
{
239-
this.currentStream.Read(this.buffer, 0, 6);
239+
this.stream.Read(this.buffer, 0, 6);
240240

241-
this.graphicsControlExtension = GifGraphicsControlExtension.Parse(this.buffer);
241+
this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer);
242242
}
243243

244244
/// <summary>
@@ -247,7 +247,7 @@ private void ReadGraphicalControlExtension()
247247
/// <returns><see cref="GifImageDescriptor"/></returns>
248248
private GifImageDescriptor ReadImageDescriptor()
249249
{
250-
this.currentStream.Read(this.buffer, 0, 9);
250+
this.stream.Read(this.buffer, 0, 9);
251251

252252
return GifImageDescriptor.Parse(this.buffer);
253253
}
@@ -257,7 +257,7 @@ private GifImageDescriptor ReadImageDescriptor()
257257
/// </summary>
258258
private void ReadLogicalScreenDescriptor()
259259
{
260-
this.currentStream.Read(this.buffer, 0, 7);
260+
this.stream.Read(this.buffer, 0, 7);
261261

262262
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer);
263263
}
@@ -268,13 +268,13 @@ private void ReadLogicalScreenDescriptor()
268268
/// <param name="length">The number of bytes to skip.</param>
269269
private void Skip(int length)
270270
{
271-
this.currentStream.Skip(length);
271+
this.stream.Skip(length);
272272

273273
int flag;
274274

275-
while ((flag = this.currentStream.ReadByte()) != 0)
275+
while ((flag = this.stream.ReadByte()) != 0)
276276
{
277-
this.currentStream.Skip(flag);
277+
this.stream.Skip(flag);
278278
}
279279
}
280280

@@ -285,7 +285,7 @@ private void ReadComments()
285285
{
286286
int length;
287287

288-
while ((length = this.currentStream.ReadByte()) != 0)
288+
while ((length = this.stream.ReadByte()) != 0)
289289
{
290290
if (length > GifConstants.MaxCommentLength)
291291
{
@@ -294,13 +294,13 @@ private void ReadComments()
294294

295295
if (this.IgnoreMetadata)
296296
{
297-
this.currentStream.Seek(length, SeekOrigin.Current);
297+
this.stream.Seek(length, SeekOrigin.Current);
298298
continue;
299299
}
300300

301301
using (IManagedByteBuffer commentsBuffer = this.MemoryManager.AllocateManagedByteBuffer(length))
302302
{
303-
this.currentStream.Read(commentsBuffer.Array, 0, length);
303+
this.stream.Read(commentsBuffer.Array, 0, length);
304304
string comments = this.TextEncoding.GetString(commentsBuffer.Array, 0, length);
305305
this.metaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments));
306306
}
@@ -327,7 +327,7 @@ private void ReadFrame<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> p
327327
{
328328
int length = imageDescriptor.LocalColorTableSize * 3;
329329
localColorTable = this.configuration.MemoryManager.AllocateManagedByteBuffer(length, true);
330-
this.currentStream.Read(localColorTable.Array, 0, length);
330+
this.stream.Read(localColorTable.Array, 0, length);
331331
}
332332

333333
indices = this.configuration.MemoryManager.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, true);
@@ -354,8 +354,8 @@ private void ReadFrame<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> p
354354
[MethodImpl(MethodImplOptions.AggressiveInlining)]
355355
private void ReadFrameIndices(in GifImageDescriptor imageDescriptor, Span<byte> indices)
356356
{
357-
int dataSize = this.currentStream.ReadByte();
358-
using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryManager, this.currentStream))
357+
int dataSize = this.stream.ReadByte();
358+
using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryManager, this.stream))
359359
{
360360
lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices);
361361
}
@@ -526,10 +526,10 @@ private void ReadLogicalScreenDescriptorAndGlobalColorTable(Stream stream)
526526
{
527527
this.metaData = new ImageMetaData();
528528

529-
this.currentStream = stream;
529+
this.stream = stream;
530530

531531
// Skip the identifier
532-
this.currentStream.Skip(6);
532+
this.stream.Skip(6);
533533
this.ReadLogicalScreenDescriptor();
534534

535535
if (this.logicalScreenDescriptor.GlobalColorTableFlag)

src/ImageSharp/Formats/Gif/GifEncoderCore.cs

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,45 @@
44
using System;
55
using System.Buffers.Binary;
66
using System.IO;
7-
using System.Linq;
87
using System.Runtime.CompilerServices;
98
using System.Runtime.InteropServices;
109
using System.Text;
11-
using SixLabors.ImageSharp.IO;
1210
using SixLabors.ImageSharp.Memory;
1311
using SixLabors.ImageSharp.MetaData;
1412
using SixLabors.ImageSharp.PixelFormats;
1513
using SixLabors.ImageSharp.Processing.Quantization;
1614

17-
// TODO: This is causing more GC collections than I'm happy with.
18-
// This is likely due to the number of short writes to the stream we are doing.
19-
// We should investigate reducing them since we know the length of the byte array we require for multiple parts.
2015
namespace SixLabors.ImageSharp.Formats.Gif
2116
{
2217
/// <summary>
23-
/// Performs the gif encoding operation.
18+
/// Implements the GIF encoding protocol.
2419
/// </summary>
2520
internal sealed class GifEncoderCore
2621
{
2722
private readonly MemoryManager memoryManager;
2823

2924
/// <summary>
30-
/// The temp buffer used to reduce allocations.
25+
/// A reusable buffer used to reduce allocations.
3126
/// </summary>
32-
private readonly byte[] buffer = new byte[16];
27+
private readonly byte[] buffer = new byte[20];
3328

3429
/// <summary>
35-
/// Gets the TextEncoding
30+
/// Gets the text encoding used to write comments.
3631
/// </summary>
3732
private readonly Encoding textEncoding;
3833

3934
/// <summary>
40-
/// Gets or sets the quantizer for reducing the color count.
35+
/// Gets or sets the quantizer used to generate the color palette.
4136
/// </summary>
4237
private readonly IQuantizer quantizer;
4338

4439
/// <summary>
45-
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
40+
/// A flag indicating whether to ingore the metadata when writing the image.
4641
/// </summary>
4742
private readonly bool ignoreMetadata;
4843

4944
/// <summary>
50-
/// The number of bits requires to store the image palette.
45+
/// The number of bits requires to store the color palette.
5146
/// </summary>
5247
private int bitDepth;
5348

@@ -91,7 +86,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
9186
this.WriteLogicalScreenDescriptor(image, stream, index);
9287

9388
// Write the first frame.
94-
this.WriteComments(image, stream);
89+
this.WriteComments(image.MetaData, stream);
9590

9691
// Write additional frames.
9792
if (image.Frames.Count > 1)
@@ -133,7 +128,8 @@ private int GetTransparentIndex<TPixel>(QuantizedFrame<TPixel> quantized)
133128
{
134129
// Transparent pixels are much more likely to be found at the end of a palette
135130
int index = -1;
136-
var trans = default(Rgba32);
131+
Rgba32 trans = default;
132+
137133
ref TPixel paletteRef = ref MemoryMarshal.GetReference(quantized.Palette.AsSpan());
138134
for (int i = quantized.Palette.Length - 1; i >= 0; i--)
139135
{
@@ -168,13 +164,12 @@ private void WriteHeader(Stream stream)
168164
private void WriteLogicalScreenDescriptor<TPixel>(Image<TPixel> image, Stream stream, int transparencyIndex)
169165
where TPixel : struct, IPixel<TPixel>
170166
{
167+
byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(false, this.bitDepth - 1, false, this.bitDepth - 1);
168+
171169
var descriptor = new GifLogicalScreenDescriptor(
172170
width: (ushort)image.Width,
173171
height: (ushort)image.Height,
174-
bitsPerPixel: 0,
175-
pixelAspectRatio: 0,
176-
globalColorTableFlag: false, // TODO: Always false for now.
177-
globalColorTableSize: this.bitDepth - 1,
172+
packed: packedValue,
178173
backgroundColorIndex: unchecked((byte)transparencyIndex));
179174

180175
descriptor.WriteTo(this.buffer);
@@ -196,40 +191,37 @@ private void WriteApplicationExtension(Stream stream, ushort repeatCount)
196191
this.buffer[1] = GifConstants.ApplicationExtensionLabel;
197192
this.buffer[2] = GifConstants.ApplicationBlockSize;
198193

199-
stream.Write(this.buffer, 0, 3);
200-
201-
stream.Write(GifConstants.ApplicationIdentificationBytes, 0, 11); // NETSCAPE2.0
194+
// Write NETSCAPE2.0
195+
GifConstants.ApplicationIdentificationBytes.AsSpan().CopyTo(this.buffer.AsSpan(3, 11));
202196

203-
this.buffer[0] = 3; // Application block length
204-
this.buffer[1] = 1; // Data sub-block index (always 1)
197+
// Application Data ----
198+
this.buffer[14] = 3; // Application block length
199+
this.buffer[15] = 1; // Data sub-block index (always 1)
205200

206201
// 0 means loop indefinitely. Count is set as play n + 1 times.
207202
repeatCount = (ushort)Math.Max(0, repeatCount - 1);
208203

209-
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer.AsSpan(2, 2), repeatCount); // Repeat count for images.
204+
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer.AsSpan(16, 2), repeatCount); // Repeat count for images.
210205

211-
this.buffer[4] = GifConstants.Terminator; // Terminator
206+
this.buffer[18] = GifConstants.Terminator; // Terminator
212207

213-
stream.Write(this.buffer, 0, 5);
208+
stream.Write(this.buffer, 0, 19);
214209
}
215210
}
216211

217212
/// <summary>
218213
/// Writes the image comments to the stream.
219214
/// </summary>
220-
/// <typeparam name="TPixel">The pixel format.</typeparam>
221-
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to be encoded.</param>
215+
/// <param name="metadata">The metadata to be extract the comment data.</param>
222216
/// <param name="stream">The stream to write to.</param>
223-
private void WriteComments<TPixel>(Image<TPixel> image, Stream stream)
224-
where TPixel : struct, IPixel<TPixel>
217+
private void WriteComments(ImageMetaData metadata, Stream stream)
225218
{
226219
if (this.ignoreMetadata)
227220
{
228221
return;
229222
}
230223

231-
ImageProperty property = image.MetaData.Properties.FirstOrDefault(p => p.Name == GifConstants.Comments);
232-
if (property == null || string.IsNullOrEmpty(property.Value))
224+
if (!metadata.TryGetProperty(GifConstants.Comments, out ImageProperty property) || string.IsNullOrEmpty(property.Value))
233225
{
234226
return;
235227
}
@@ -255,15 +247,33 @@ private void WriteComments<TPixel>(Image<TPixel> image, Stream stream)
255247
/// <param name="transparencyIndex">The index of the color in the color palette to make transparent.</param>
256248
private void WriteGraphicalControlExtension(ImageFrameMetaData metaData, Stream stream, int transparencyIndex)
257249
{
258-
var extension = new GifGraphicsControlExtension(
250+
byte packedValue = GifGraphicControlExtension.GetPackedValue(
259251
disposalMethod: metaData.DisposalMethod,
260-
transparencyFlag: transparencyIndex > -1,
252+
transparencyFlag: transparencyIndex > -1);
253+
254+
var extension = new GifGraphicControlExtension(
255+
packed: packedValue,
261256
transparencyIndex: unchecked((byte)transparencyIndex),
262257
delayTime: (ushort)metaData.FrameDelay);
263258

264-
extension.WriteTo(this.buffer);
259+
this.WriteExtension(extension, stream);
260+
}
261+
262+
/// <summary>
263+
/// Writes the provided extension to the stream.
264+
/// </summary>
265+
/// <param name="extension">The extension to write to the stream.</param>
266+
/// <param name="stream">The stream to write to.</param>
267+
public void WriteExtension(IGifExtension extension, Stream stream)
268+
{
269+
this.buffer[0] = GifConstants.ExtensionIntroducer;
270+
this.buffer[1] = extension.Label;
265271

266-
stream.Write(this.buffer, 0, GifGraphicsControlExtension.Size);
272+
int extensionSize = extension.WriteTo(this.buffer.AsSpan(2));
273+
274+
this.buffer[extensionSize + 2] = GifConstants.Terminator;
275+
276+
stream.Write(this.buffer, 0, extensionSize + 3);
267277
}
268278

269279
/// <summary>
@@ -279,7 +289,7 @@ private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, Stream strea
279289
localColorTableFlag: true,
280290
interfaceFlag: false,
281291
sortFlag: false,
282-
localColorTableSize: this.bitDepth); // Note: we subtract 1 from the colorTableSize writing
292+
localColorTableSize: (byte)this.bitDepth); // Note: we subtract 1 from the colorTableSize writing
283293

284294
var descriptor = new GifImageDescriptor(
285295
left: 0,
@@ -302,24 +312,23 @@ private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, Stream strea
302312
private void WriteColorTable<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
303313
where TPixel : struct, IPixel<TPixel>
304314
{
305-
// Grab the palette and write it to the stream.
306315
int pixelCount = image.Palette.Length;
307316

308-
// Get max colors for bit depth.
309-
int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3;
310-
var rgb = default(Rgb24);
317+
int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; // The maximium number of colors for the bit depth
318+
Rgb24 rgb = default;
319+
311320
using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength))
312321
{
313322
ref TPixel paletteRef = ref MemoryMarshal.GetReference(image.Palette.AsSpan());
314323
ref Rgb24 rgb24Ref = ref Unsafe.As<byte, Rgb24>(ref MemoryMarshal.GetReference(colorTable.Span));
315-
316324
for (int i = 0; i < pixelCount; i++)
317325
{
318326
ref TPixel entry = ref Unsafe.Add(ref paletteRef, i);
319327
entry.ToRgb24(ref rgb);
320328
Unsafe.Add(ref rgb24Ref, i) = rgb;
321329
}
322330

331+
// Write the palette to the stream
323332
stream.Write(colorTable.Array, 0, colorTableLength);
324333
}
325334
}

0 commit comments

Comments
 (0)