@@ -79,14 +79,14 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
7979 bool useGlobalTable = this . colorTableMode == GifColorTableMode . Global ;
8080
8181 // Quantize the image returning a palette.
82- QuantizedFrame < TPixel > quantized ;
82+ IndexedImageFrame < TPixel > quantized ;
8383 using ( IFrameQuantizer < TPixel > frameQuantizer = this . quantizer . CreateFrameQuantizer < TPixel > ( this . configuration ) )
8484 {
8585 quantized = frameQuantizer . QuantizeFrame ( image . Frames . RootFrame , image . Bounds ( ) ) ;
8686 }
8787
8888 // Get the number of bits.
89- this . bitDepth = ImageMaths . GetBitsNeededForColorDepth ( quantized . Palette . Length ) . Clamp ( 1 , 8 ) ;
89+ this . bitDepth = ImageMaths . GetBitsNeededForColorDepth ( quantized . Palette . Length ) ;
9090
9191 // Write the header.
9292 this . WriteHeader ( stream ) ;
@@ -119,15 +119,20 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
119119 }
120120
121121 // Clean up.
122- quantized ? . Dispose ( ) ;
122+ quantized . Dispose ( ) ;
123123
124124 // TODO: Write extension etc
125125 stream . WriteByte ( GifConstants . EndIntroducer ) ;
126126 }
127127
128- private void EncodeGlobal < TPixel > ( Image < TPixel > image , QuantizedFrame < TPixel > quantized , int transparencyIndex , Stream stream )
128+ private void EncodeGlobal < TPixel > ( Image < TPixel > image , IndexedImageFrame < TPixel > quantized , int transparencyIndex , Stream stream )
129129 where TPixel : unmanaged, IPixel < TPixel >
130130 {
131+ // The palette quantizer can reuse the same pixel map across multiple frames
132+ // since the palette is unchanging. This allows a reduction of memory usage across
133+ // multi frame gifs using a global palette.
134+ EuclideanPixelMap < TPixel > pixelMap = default ;
135+ bool pixelMapSet = false ;
131136 for ( int i = 0 ; i < image . Frames . Count ; i ++ )
132137 {
133138 ImageFrame < TPixel > frame = image . Frames [ i ] ;
@@ -142,22 +147,27 @@ private void EncodeGlobal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> qu
142147 }
143148 else
144149 {
145- using ( var paletteFrameQuantizer = new PaletteFrameQuantizer < TPixel > ( this . configuration , this . quantizer . Options , quantized . Palette ) )
146- using ( QuantizedFrame < TPixel > paletteQuantized = paletteFrameQuantizer . QuantizeFrame ( frame , frame . Bounds ( ) ) )
150+ if ( ! pixelMapSet )
147151 {
148- this . WriteImageData ( paletteQuantized , stream ) ;
152+ pixelMapSet = true ;
153+ pixelMap = new EuclideanPixelMap < TPixel > ( this . configuration , quantized . Palette ) ;
149154 }
155+
156+ using var paletteFrameQuantizer = new PaletteFrameQuantizer < TPixel > ( this . configuration , this . quantizer . Options , pixelMap ) ;
157+ using IndexedImageFrame < TPixel > paletteQuantized = paletteFrameQuantizer . QuantizeFrame ( frame , frame . Bounds ( ) ) ;
158+ this . WriteImageData ( paletteQuantized , stream ) ;
150159 }
151160 }
152161 }
153162
154- private void EncodeLocal < TPixel > ( Image < TPixel > image , QuantizedFrame < TPixel > quantized , Stream stream )
163+ private void EncodeLocal < TPixel > ( Image < TPixel > image , IndexedImageFrame < TPixel > quantized , Stream stream )
155164 where TPixel : unmanaged, IPixel < TPixel >
156165 {
157166 ImageFrame < TPixel > previousFrame = null ;
158167 GifFrameMetadata previousMeta = null ;
159- foreach ( ImageFrame < TPixel > frame in image . Frames )
168+ for ( int i = 0 ; i < image . Frames . Count ; i ++ )
160169 {
170+ ImageFrame < TPixel > frame = image . Frames [ i ] ;
161171 ImageFrameMetadata metadata = frame . Metadata ;
162172 GifFrameMetadata frameMetadata = metadata . GetGifMetadata ( ) ;
163173 if ( quantized is null )
@@ -173,27 +183,23 @@ private void EncodeLocal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> qua
173183 MaxColors = frameMetadata . ColorTableLength
174184 } ;
175185
176- using ( IFrameQuantizer < TPixel > frameQuantizer = this . quantizer . CreateFrameQuantizer < TPixel > ( this . configuration , options ) )
177- {
178- quantized = frameQuantizer . QuantizeFrame ( frame , frame . Bounds ( ) ) ;
179- }
186+ using IFrameQuantizer < TPixel > frameQuantizer = this . quantizer . CreateFrameQuantizer < TPixel > ( this . configuration , options ) ;
187+ quantized = frameQuantizer . QuantizeFrame ( frame , frame . Bounds ( ) ) ;
180188 }
181189 else
182190 {
183- using ( IFrameQuantizer < TPixel > frameQuantizer = this . quantizer . CreateFrameQuantizer < TPixel > ( this . configuration ) )
184- {
185- quantized = frameQuantizer . QuantizeFrame ( frame , frame . Bounds ( ) ) ;
186- }
191+ using IFrameQuantizer < TPixel > frameQuantizer = this . quantizer . CreateFrameQuantizer < TPixel > ( this . configuration ) ;
192+ quantized = frameQuantizer . QuantizeFrame ( frame , frame . Bounds ( ) ) ;
187193 }
188194 }
189195
190- this . bitDepth = ImageMaths . GetBitsNeededForColorDepth ( quantized . Palette . Length ) . Clamp ( 1 , 8 ) ;
196+ this . bitDepth = ImageMaths . GetBitsNeededForColorDepth ( quantized . Palette . Length ) ;
191197 this . WriteGraphicalControlExtension ( frameMetadata , this . GetTransparentIndex ( quantized ) , stream ) ;
192198 this . WriteImageDescriptor ( frame , true , stream ) ;
193199 this . WriteColorTable ( quantized , stream ) ;
194200 this . WriteImageData ( quantized , stream ) ;
195201
196- quantized ? . Dispose ( ) ;
202+ quantized . Dispose ( ) ;
197203 quantized = null ; // So next frame can regenerate it
198204 previousFrame = frame ;
199205 previousMeta = frameMetadata ;
@@ -208,25 +214,23 @@ private void EncodeLocal<TPixel>(Image<TPixel> image, QuantizedFrame<TPixel> qua
208214 /// <returns>
209215 /// The <see cref="int"/>.
210216 /// </returns>
211- private int GetTransparentIndex < TPixel > ( QuantizedFrame < TPixel > quantized )
217+ private int GetTransparentIndex < TPixel > ( IndexedImageFrame < TPixel > quantized )
212218 where TPixel : unmanaged, IPixel < TPixel >
213219 {
214- // Transparent pixels are much more likely to be found at the end of a palette
220+ // Transparent pixels are much more likely to be found at the end of a palette.
215221 int index = - 1 ;
216- int length = quantized . Palette . Length ;
222+ ReadOnlySpan < TPixel > paletteSpan = quantized . Palette . Span ;
217223
218- using ( IMemoryOwner < Rgba32 > rgbaBuffer = this . memoryAllocator . Allocate < Rgba32 > ( length ) )
219- {
220- Span < Rgba32 > rgbaSpan = rgbaBuffer . GetSpan ( ) ;
221- ref Rgba32 paletteRef = ref MemoryMarshal . GetReference ( rgbaSpan ) ;
222- PixelOperations < TPixel > . Instance . ToRgba32 ( this . configuration , quantized . Palette . Span , rgbaSpan ) ;
224+ using IMemoryOwner < Rgba32 > rgbaOwner = quantized . Configuration . MemoryAllocator . Allocate < Rgba32 > ( paletteSpan . Length ) ;
225+ Span < Rgba32 > rgbaSpan = rgbaOwner . GetSpan ( ) ;
226+ PixelOperations < TPixel > . Instance . ToRgba32 ( quantized . Configuration , paletteSpan , rgbaSpan ) ;
227+ ref Rgba32 rgbaSpanRef = ref MemoryMarshal . GetReference ( rgbaSpan ) ;
223228
224- for ( int i = quantized . Palette . Length - 1 ; i >= 0 ; i -- )
229+ for ( int i = rgbaSpan . Length - 1 ; i >= 0 ; i -- )
230+ {
231+ if ( Unsafe . Add ( ref rgbaSpanRef , i ) . Equals ( default ) )
225232 {
226- if ( Unsafe . Add ( ref paletteRef , i ) . Equals ( default ) )
227- {
228- index = i ;
229- }
233+ index = i ;
230234 }
231235 }
232236
@@ -326,16 +330,19 @@ private void WriteComments(GifMetadata metadata, Stream stream)
326330 return ;
327331 }
328332
329- foreach ( string comment in metadata . Comments )
333+ for ( var i = 0 ; i < metadata . Comments . Count ; i ++ )
330334 {
335+ string comment = metadata . Comments [ i ] ;
331336 this . buffer [ 0 ] = GifConstants . ExtensionIntroducer ;
332337 this . buffer [ 1 ] = GifConstants . CommentLabel ;
333338 stream . Write ( this . buffer , 0 , 2 ) ;
334339
335340 // Comment will be stored in chunks of 255 bytes, if it exceeds this size.
336341 ReadOnlySpan < char > commentSpan = comment . AsSpan ( ) ;
337342 int idx = 0 ;
338- for ( ; idx <= comment . Length - GifConstants . MaxCommentSubBlockLength ; idx += GifConstants . MaxCommentSubBlockLength )
343+ for ( ;
344+ idx <= comment . Length - GifConstants . MaxCommentSubBlockLength ;
345+ idx += GifConstants . MaxCommentSubBlockLength )
339346 {
340347 WriteCommentSubBlock ( stream , commentSpan , idx , GifConstants . MaxCommentSubBlockLength ) ;
341348 }
@@ -391,7 +398,8 @@ private void WriteGraphicalControlExtension(GifFrameMetadata metadata, int trans
391398 /// </summary>
392399 /// <param name="extension">The extension to write to the stream.</param>
393400 /// <param name="stream">The stream to write to.</param>
394- public void WriteExtension ( IGifExtension extension , Stream stream )
401+ private void WriteExtension < TGifExtension > ( TGifExtension extension , Stream stream )
402+ where TGifExtension : struct , IGifExtension
395403 {
396404 this . buffer [ 0 ] = GifConstants . ExtensionIntroducer ;
397405 this . buffer [ 1 ] = extension . Label ;
@@ -437,37 +445,33 @@ private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, bool hasColo
437445 /// <typeparam name="TPixel">The pixel format.</typeparam>
438446 /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode.</param>
439447 /// <param name="stream">The stream to write to.</param>
440- private void WriteColorTable < TPixel > ( QuantizedFrame < TPixel > image , Stream stream )
448+ private void WriteColorTable < TPixel > ( IndexedImageFrame < TPixel > image , Stream stream )
441449 where TPixel : unmanaged, IPixel < TPixel >
442450 {
443451 // The maximum number of colors for the bit depth
444- int colorTableLength = ImageMaths . GetColorCountForBitDepth ( this . bitDepth ) * 3 ;
445- int pixelCount = image . Palette . Length ;
452+ int colorTableLength = ImageMaths . GetColorCountForBitDepth ( this . bitDepth ) * Unsafe . SizeOf < Rgb24 > ( ) ;
446453
447- using ( IManagedByteBuffer colorTable = this . memoryAllocator . AllocateManagedByteBuffer ( colorTableLength ) )
448- {
449- PixelOperations < TPixel > . Instance . ToRgb24Bytes (
450- this . configuration ,
451- image . Palette . Span ,
452- colorTable . GetSpan ( ) ,
453- pixelCount ) ;
454- stream . Write ( colorTable . Array , 0 , colorTableLength ) ;
455- }
454+ using IManagedByteBuffer colorTable = this . memoryAllocator . AllocateManagedByteBuffer ( colorTableLength , AllocationOptions . Clean ) ;
455+ PixelOperations < TPixel > . Instance . ToRgb24Bytes (
456+ this . configuration ,
457+ image . Palette . Span ,
458+ colorTable . GetSpan ( ) ,
459+ image . Palette . Length ) ;
460+
461+ stream . Write ( colorTable . Array , 0 , colorTableLength ) ;
456462 }
457463
458464 /// <summary>
459465 /// Writes the image pixel data to the stream.
460466 /// </summary>
461467 /// <typeparam name="TPixel">The pixel format.</typeparam>
462- /// <param name="image">The <see cref="QuantizedFrame {TPixel}"/> containing indexed pixels.</param>
468+ /// <param name="image">The <see cref="IndexedImageFrame {TPixel}"/> containing indexed pixels.</param>
463469 /// <param name="stream">The stream to write to.</param>
464- private void WriteImageData < TPixel > ( QuantizedFrame < TPixel > image , Stream stream )
470+ private void WriteImageData < TPixel > ( IndexedImageFrame < TPixel > image , Stream stream )
465471 where TPixel : unmanaged, IPixel < TPixel >
466472 {
467- using ( var encoder = new LzwEncoder ( this . memoryAllocator , ( byte ) this . bitDepth ) )
468- {
469- encoder . Encode ( image . GetPixelSpan ( ) , stream ) ;
470- }
473+ using var encoder = new LzwEncoder ( this . memoryAllocator , ( byte ) this . bitDepth ) ;
474+ encoder . Encode ( image . GetPixelBufferSpan ( ) , stream ) ;
471475 }
472476 }
473477}
0 commit comments