@ -79,14 +79,14 @@ namespace SixLabors.ImageSharp.Formats.Gif
bool useGlobalTable = this . colorTableMode = = GifColorTableMode . Global ;
bool useGlobalTable = this . colorTableMode = = GifColorTableMode . Global ;
// Quantize the image returning a palette.
// Quantize the image returning a palette.
Quantized Frame< TPixel > quantized ;
IndexedImage Frame< TPixel > quantized ;
using ( IFrameQuantizer < TPixel > frameQuantizer = this . quantizer . CreateFrameQuantizer < TPixel > ( this . configuration ) )
using ( IFrameQuantizer < TPixel > frameQuantizer = this . quantizer . CreateFrameQuantizer < TPixel > ( this . configuration ) )
{
{
quantized = frameQuantizer . QuantizeFrame ( image . Frames . RootFrame , image . Bounds ( ) ) ;
quantized = frameQuantizer . QuantizeFrame ( image . Frames . RootFrame , image . Bounds ( ) ) ;
}
}
// Get the number of bits.
// Get the number of bits.
this . bitDepth = ImageMaths . GetBitsNeededForColorDepth ( quantized . Palette . Length ) . Clamp ( 1 , 8 ) ;
this . bitDepth = ImageMaths . GetBitsNeededForColorDepth ( quantized . Palette . Length ) ;
// Write the header.
// Write the header.
this . WriteHeader ( stream ) ;
this . WriteHeader ( stream ) ;
@ -119,15 +119,20 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
}
// Clean up.
// Clean up.
quantized ? . Dispose ( ) ;
quantized . Dispose ( ) ;
// TODO: Write extension etc
// TODO: Write extension etc
stream . WriteByte ( GifConstants . EndIntroducer ) ;
stream . WriteByte ( GifConstants . EndIntroducer ) ;
}
}
private void EncodeGlobal < TPixel > ( Image < TPixel > image , Quantized Frame< TPixel > quantized , int transparencyIndex , Stream stream )
private void EncodeGlobal < TPixel > ( Image < TPixel > image , IndexedImage Frame< TPixel > quantized , int transparencyIndex , Stream stream )
where TPixel : unmanaged , IPixel < TPixel >
where TPixel : unmanaged , IPixel < TPixel >
{
{
// The palette quantizer can reuse the same pixel map across multiple frames
// since the palette is unchanging. This allows a reduction of memory usage across
// multi frame gifs using a global palette.
EuclideanPixelMap < TPixel > pixelMap = default ;
bool pixelMapSet = false ;
for ( int i = 0 ; i < image . Frames . Count ; i + + )
for ( int i = 0 ; i < image . Frames . Count ; i + + )
{
{
ImageFrame < TPixel > frame = image . Frames [ i ] ;
ImageFrame < TPixel > frame = image . Frames [ i ] ;
@ -142,22 +147,27 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
}
else
else
{
{
using ( var paletteFrameQuantizer = new PaletteFrameQuantizer < TPixel > ( this . configuration , this . quantizer . Options , quantized . Palette ) )
if ( ! pixelMapSet )
using ( QuantizedFrame < TPixel > paletteQuantized = paletteFrameQuantizer . QuantizeFrame ( frame , frame . Bounds ( ) ) )
{
{
this . WriteImageData ( paletteQuantized , stream ) ;
pixelMapSet = true ;
pixelMap = new EuclideanPixelMap < TPixel > ( this . configuration , quantized . Palette ) ;
}
}
using var paletteFrameQuantizer = new PaletteFrameQuantizer < TPixel > ( this . configuration , this . quantizer . Options , pixelMap ) ;
using IndexedImageFrame < TPixel > paletteQuantized = paletteFrameQuantizer . QuantizeFrame ( frame , frame . Bounds ( ) ) ;
this . WriteImageData ( paletteQuantized , stream ) ;
}
}
}
}
}
}
private void EncodeLocal < TPixel > ( Image < TPixel > image , Quantized Frame< TPixel > quantized , Stream stream )
private void EncodeLocal < TPixel > ( Image < TPixel > image , IndexedImage Frame< TPixel > quantized , Stream stream )
where TPixel : unmanaged , IPixel < TPixel >
where TPixel : unmanaged , IPixel < TPixel >
{
{
ImageFrame < TPixel > previousFrame = null ;
ImageFrame < TPixel > previousFrame = null ;
GifFrameMetadata previousMeta = null ;
GifFrameMetadata previousMeta = null ;
foreach ( ImageFrame < TPixel > frame in image . Frames )
for ( int i = 0 ; i < image . Frames . Count ; i + + )
{
{
ImageFrame < TPixel > frame = image . Frames [ i ] ;
ImageFrameMetadata metadata = frame . Metadata ;
ImageFrameMetadata metadata = frame . Metadata ;
GifFrameMetadata frameMetadata = metadata . GetGifMetadata ( ) ;
GifFrameMetadata frameMetadata = metadata . GetGifMetadata ( ) ;
if ( quantized is null )
if ( quantized is null )
@ -173,27 +183,23 @@ namespace SixLabors.ImageSharp.Formats.Gif
MaxColors = frameMetadata . ColorTableLength
MaxColors = frameMetadata . ColorTableLength
} ;
} ;
using ( IFrameQuantizer < TPixel > frameQuantizer = this . quantizer . CreateFrameQuantizer < TPixel > ( this . configuration , options ) )
using IFrameQuantizer < TPixel > frameQuantizer = this . quantizer . CreateFrameQuantizer < TPixel > ( this . configuration , options ) ;
{
quantized = frameQuantizer . QuantizeFrame ( frame , frame . Bounds ( ) ) ;
quantized = frameQuantizer . QuantizeFrame ( frame , frame . Bounds ( ) ) ;
}
}
}
else
else
{
{
using ( IFrameQuantizer < TPixel > frameQuantizer = this . quantizer . CreateFrameQuantizer < TPixel > ( this . configuration ) )
using IFrameQuantizer < TPixel > frameQuantizer = this . quantizer . CreateFrameQuantizer < TPixel > ( this . configuration ) ;
{
quantized = frameQuantizer . QuantizeFrame ( frame , frame . Bounds ( ) ) ;
quantized = frameQuantizer . QuantizeFrame ( frame , frame . Bounds ( ) ) ;
}
}
}
}
}
this . bitDepth = ImageMaths . GetBitsNeededForColorDepth ( quantized . Palette . Length ) . Clamp ( 1 , 8 ) ;
this . bitDepth = ImageMaths . GetBitsNeededForColorDepth ( quantized . Palette . Length ) ;
this . WriteGraphicalControlExtension ( frameMetadata , this . GetTransparentIndex ( quantized ) , stream ) ;
this . WriteGraphicalControlExtension ( frameMetadata , this . GetTransparentIndex ( quantized ) , stream ) ;
this . WriteImageDescriptor ( frame , true , stream ) ;
this . WriteImageDescriptor ( frame , true , stream ) ;
this . WriteColorTable ( quantized , stream ) ;
this . WriteColorTable ( quantized , stream ) ;
this . WriteImageData ( quantized , stream ) ;
this . WriteImageData ( quantized , stream ) ;
quantized ? . Dispose ( ) ;
quantized . Dispose ( ) ;
quantized = null ; // So next frame can regenerate it
quantized = null ; // So next frame can regenerate it
previousFrame = frame ;
previousFrame = frame ;
previousMeta = frameMetadata ;
previousMeta = frameMetadata ;
@ -208,27 +214,25 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <returns>
/// <returns>
/// The <see cref="int"/>.
/// The <see cref="int"/>.
/// </returns>
/// </returns>
private int GetTransparentIndex < TPixel > ( Quantized Frame< TPixel > quantized )
private int GetTransparentIndex < TPixel > ( IndexedImage Frame< TPixel > quantized )
where TPixel : unmanaged , IPixel < TPixel >
where TPixel : unmanaged , IPixel < TPixel >
{
{
// Transparent pixels are much more likely to be found at the end of a palette
// Transparent pixels are much more likely to be found at the end of a palette.
int index = - 1 ;
int index = - 1 ;
int length = quantized . Palette . Length ;
ReadOnlySpan < TPixel > paletteSpan = quantized . Palette . Span ;
using ( IMemoryOwner < Rgba32 > rgbaBuffer = this . memoryAllocator . Allocate < Rgba32 > ( length ) )
using IMemoryOwner < Rgba32 > rgbaOwner = quantized . Configuration . MemoryAllocator . Allocate < Rgba32 > ( paletteSpan . Length ) ;
{
Span < Rgba32 > rgbaSpan = rgbaOwner . GetSpan ( ) ;
Span < Rgba32 > rgbaSpan = rgbaBuffer . GetSpan ( ) ;
PixelOperations < TPixel > . Instance . ToRgba32 ( quantized . Configuration , paletteSpan , rgbaSpan ) ;
ref Rgba32 paletteRef = ref MemoryMarshal . GetReference ( rgbaSpan ) ;
ref Rgba32 rgbaSpanRef = ref MemoryMarshal . GetReference ( rgbaSpan ) ;
PixelOperations < TPixel > . Instance . ToRgba32 ( this . configuration , quantized . Palette . Span , rgbaSpan ) ;
for ( int i = quantized . Palette . Length - 1 ; i > = 0 ; i - - )
for ( int i = rgbaSpan . Length - 1 ; i > = 0 ; i - - )
{
{
if ( Unsafe . Add ( ref palette Ref, i ) . Equals ( default ) )
if ( Unsafe . Add ( ref rgbaSpan Ref, i ) . Equals ( default ) )
{
{
index = i ;
index = i ;
}
}
}
}
}
return index ;
return index ;
}
}
@ -238,7 +242,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
/// </summary>
/// <param name="stream">The stream to write to.</param>
/// <param name="stream">The stream to write to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteHeader ( Stream stream ) = > stream . Write ( GifConstants . MagicNumber , 0 , GifConstants . MagicNumber . Length ) ;
private void WriteHeader ( Stream stream ) = > stream . Write ( GifConstants . MagicNumber ) ;
/// <summary>
/// <summary>
/// Writes the logical screen descriptor to the stream.
/// Writes the logical screen descriptor to the stream.
@ -326,8 +330,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
return ;
return ;
}
}
foreach ( string comment in metadata . Comments )
for ( var i = 0 ; i < metadata . Comments . Count ; i + + )
{
{
string comment = metadata . Comments [ i ] ;
this . buffer [ 0 ] = GifConstants . ExtensionIntroducer ;
this . buffer [ 0 ] = GifConstants . ExtensionIntroducer ;
this . buffer [ 1 ] = GifConstants . CommentLabel ;
this . buffer [ 1 ] = GifConstants . CommentLabel ;
stream . Write ( this . buffer , 0 , 2 ) ;
stream . Write ( this . buffer , 0 , 2 ) ;
@ -335,7 +340,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Comment will be stored in chunks of 255 bytes, if it exceeds this size.
// Comment will be stored in chunks of 255 bytes, if it exceeds this size.
ReadOnlySpan < char > commentSpan = comment . AsSpan ( ) ;
ReadOnlySpan < char > commentSpan = comment . AsSpan ( ) ;
int idx = 0 ;
int idx = 0 ;
for ( ; idx < = comment . Length - GifConstants . MaxCommentSubBlockLength ; idx + = GifConstants . MaxCommentSubBlockLength )
for ( ;
idx < = comment . Length - GifConstants . MaxCommentSubBlockLength ;
idx + = GifConstants . MaxCommentSubBlockLength )
{
{
WriteCommentSubBlock ( stream , commentSpan , idx , GifConstants . MaxCommentSubBlockLength ) ;
WriteCommentSubBlock ( stream , commentSpan , idx , GifConstants . MaxCommentSubBlockLength ) ;
}
}
@ -391,7 +398,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
/// </summary>
/// <param name="extension">The extension to write to the stream.</param>
/// <param name="extension">The extension to write to the stream.</param>
/// <param name="stream">The stream to write to.</param>
/// <param name="stream">The stream to write to.</param>
public void WriteExtension ( IGifExtension extension , Stream stream )
private void WriteExtension < TGifExtension > ( TGifExtension extension , Stream stream )
where TGifExtension : struct , IGifExtension
{
{
this . buffer [ 0 ] = GifConstants . ExtensionIntroducer ;
this . buffer [ 0 ] = GifConstants . ExtensionIntroducer ;
this . buffer [ 1 ] = extension . Label ;
this . buffer [ 1 ] = extension . Label ;
@ -437,37 +445,33 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode.</param>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode.</param>
/// <param name="stream">The stream to write to.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteColorTable < TPixel > ( Quantized Frame< TPixel > image , Stream stream )
private void WriteColorTable < TPixel > ( IndexedImage Frame< TPixel > image , Stream stream )
where TPixel : unmanaged , IPixel < TPixel >
where TPixel : unmanaged , IPixel < TPixel >
{
{
// The maximum number of colors for the bit depth
// The maximum number of colors for the bit depth
int colorTableLength = ImageMaths . GetColorCountForBitDepth ( this . bitDepth ) * 3 ;
int colorTableLength = ImageMaths . GetColorCountForBitDepth ( this . bitDepth ) * Unsafe . SizeOf < Rgb24 > ( ) ;
int pixelCount = image . Palette . Length ;
using ( IManagedByteBuffer colorTable = this . memoryAllocator . AllocateManagedByteBuffer ( colorTableLength ) )
using IManagedByteBuffer colorTable = this . memoryAllocator . AllocateManagedByteBuffer ( colorTableLength , AllocationOptions . Clean ) ;
{
PixelOperations < TPixel > . Instance . ToRgb24Bytes (
PixelOperations < TPixel > . Instance . ToRgb24Bytes (
this . configuration ,
this . configuration ,
image . Palette . Span ,
image . Palette . Span ,
colorTable . GetSpan ( ) ,
colorTable . GetSpan ( ) ,
pixelCount ) ;
image . Palette . Length ) ;
stream . Write ( colorTable . Array , 0 , colorTableLength ) ;
stream . Write ( colorTable . Array , 0 , colorTableLength ) ;
}
}
}
/// <summary>
/// <summary>
/// Writes the image pixel data to the stream.
/// Writes the image pixel data to the stream.
/// </summary>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Quantized Frame{TPixel}"/> containing indexed pixels.</param>
/// <param name="image">The <see cref="IndexedImage Frame{TPixel}"/> containing indexed pixels.</param>
/// <param name="stream">The stream to write to.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteImageData < TPixel > ( Quantized Frame< TPixel > image , Stream stream )
private void WriteImageData < TPixel > ( IndexedImage Frame< TPixel > image , Stream stream )
where TPixel : unmanaged , IPixel < TPixel >
where TPixel : unmanaged , IPixel < TPixel >
{
{
using ( var encoder = new LzwEncoder ( this . memoryAllocator , ( byte ) this . bitDepth ) )
using var encoder = new LzwEncoder ( this . memoryAllocator , ( byte ) this . bitDepth ) ;
{
encoder . Encode ( image . GetPixelBufferSpan ( ) , stream ) ;
encoder . Encode ( image . GetPixelSpan ( ) , stream ) ;
}
}
}
}
}
}
}