44using System ;
55using System . Buffers . Binary ;
66using System . IO ;
7- using System . Linq ;
87using System . Runtime . CompilerServices ;
98using System . Runtime . InteropServices ;
109using System . Text ;
11- using SixLabors . ImageSharp . IO ;
1210using SixLabors . ImageSharp . Memory ;
1311using SixLabors . ImageSharp . MetaData ;
1412using SixLabors . ImageSharp . PixelFormats ;
1513using 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.
2015namespace 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