|
|
@ -2,7 +2,6 @@ |
|
|
// Licensed under the Apache License, Version 2.0.
|
|
|
// Licensed under the Apache License, Version 2.0.
|
|
|
|
|
|
|
|
|
using System; |
|
|
using System; |
|
|
using System.Buffers.Binary; |
|
|
|
|
|
using System.IO; |
|
|
using System.IO; |
|
|
using System.Runtime.CompilerServices; |
|
|
using System.Runtime.CompilerServices; |
|
|
using System.Runtime.InteropServices; |
|
|
using System.Runtime.InteropServices; |
|
|
@ -44,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// The color table mode: Global or local.
|
|
|
/// The color table mode: Global or local.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private readonly GifColorTableMode colorTableMode; |
|
|
private GifColorTableMode? colorTableMode; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// A flag indicating whether to ingore the metadata when writing the image.
|
|
|
/// A flag indicating whether to ingore the metadata when writing the image.
|
|
|
@ -56,6 +55,11 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
private int bitDepth; |
|
|
private int bitDepth; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gif specific meta data.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private GifMetaData gifMetaData; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
|
|
|
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
@ -66,7 +70,6 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
this.memoryAllocator = memoryAllocator; |
|
|
this.memoryAllocator = memoryAllocator; |
|
|
this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; |
|
|
this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding; |
|
|
this.quantizer = options.Quantizer; |
|
|
this.quantizer = options.Quantizer; |
|
|
this.colorTableMode = options.ColorTableMode; |
|
|
|
|
|
this.ignoreMetadata = options.IgnoreMetadata; |
|
|
this.ignoreMetadata = options.IgnoreMetadata; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -82,6 +85,10 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
Guard.NotNull(image, nameof(image)); |
|
|
Guard.NotNull(image, nameof(image)); |
|
|
Guard.NotNull(stream, nameof(stream)); |
|
|
Guard.NotNull(stream, nameof(stream)); |
|
|
|
|
|
|
|
|
|
|
|
this.gifMetaData = image.MetaData.GetGifMetaData() ?? new GifMetaData(); |
|
|
|
|
|
this.colorTableMode = this.colorTableMode ?? this.gifMetaData.ColorTableMode; |
|
|
|
|
|
bool useGlobalTable = this.colorTableMode.Equals(GifColorTableMode.Global); |
|
|
|
|
|
|
|
|
// Quantize the image returning a palette.
|
|
|
// Quantize the image returning a palette.
|
|
|
QuantizedFrame<TPixel> quantized = |
|
|
QuantizedFrame<TPixel> quantized = |
|
|
this.quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(image.Frames.RootFrame); |
|
|
this.quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(image.Frames.RootFrame); |
|
|
@ -94,7 +101,6 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
|
|
|
|
|
|
// Write the LSD.
|
|
|
// Write the LSD.
|
|
|
int index = this.GetTransparentIndex(quantized); |
|
|
int index = this.GetTransparentIndex(quantized); |
|
|
bool useGlobalTable = this.colorTableMode.Equals(GifColorTableMode.Global); |
|
|
|
|
|
this.WriteLogicalScreenDescriptor(image, index, useGlobalTable, stream); |
|
|
this.WriteLogicalScreenDescriptor(image, index, useGlobalTable, stream); |
|
|
|
|
|
|
|
|
if (useGlobalTable) |
|
|
if (useGlobalTable) |
|
|
@ -108,7 +114,7 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
// Write application extension to allow additional frames.
|
|
|
// Write application extension to allow additional frames.
|
|
|
if (image.Frames.Count > 1) |
|
|
if (image.Frames.Count > 1) |
|
|
{ |
|
|
{ |
|
|
this.WriteApplicationExtension(stream, image.MetaData.RepeatCount); |
|
|
this.WriteApplicationExtension(stream, this.gifMetaData.RepeatCount); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (useGlobalTable) |
|
|
if (useGlobalTable) |
|
|
@ -136,8 +142,8 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
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]; |
|
|
|
|
|
GifFrameMetaData frameMetaData = frame.MetaData.GetGifFrameMetaData() ?? new GifFrameMetaData(); |
|
|
this.WriteGraphicalControlExtension(frame.MetaData, transparencyIndex, stream); |
|
|
this.WriteGraphicalControlExtension(frameMetaData, transparencyIndex, stream); |
|
|
this.WriteImageDescriptor(frame, false, stream); |
|
|
this.WriteImageDescriptor(frame, false, stream); |
|
|
|
|
|
|
|
|
if (i == 0) |
|
|
if (i == 0) |
|
|
@ -159,16 +165,18 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
where TPixel : struct, IPixel<TPixel> |
|
|
{ |
|
|
{ |
|
|
ImageFrame<TPixel> previousFrame = null; |
|
|
ImageFrame<TPixel> previousFrame = null; |
|
|
|
|
|
GifFrameMetaData previousMeta = null; |
|
|
foreach (ImageFrame<TPixel> frame in image.Frames) |
|
|
foreach (ImageFrame<TPixel> frame in image.Frames) |
|
|
{ |
|
|
{ |
|
|
|
|
|
GifFrameMetaData meta = frame.MetaData.GetGifFrameMetaData() ?? new GifFrameMetaData(); |
|
|
if (quantized is null) |
|
|
if (quantized is null) |
|
|
{ |
|
|
{ |
|
|
// Allow each frame to be encoded at whatever color depth the frame designates if set.
|
|
|
// Allow each frame to be encoded at whatever color depth the frame designates if set.
|
|
|
if (previousFrame != null |
|
|
if (previousFrame != null |
|
|
&& previousFrame.MetaData.ColorTableLength != frame.MetaData.ColorTableLength |
|
|
&& previousMeta.ColorTableLength != meta.ColorTableLength |
|
|
&& frame.MetaData.ColorTableLength > 0) |
|
|
&& meta.ColorTableLength > 0) |
|
|
{ |
|
|
{ |
|
|
quantized = this.quantizer.CreateFrameQuantizer<TPixel>(frame.MetaData.ColorTableLength).QuantizeFrame(frame); |
|
|
quantized = this.quantizer.CreateFrameQuantizer<TPixel>(meta.ColorTableLength).QuantizeFrame(frame); |
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
{ |
|
|
{ |
|
|
@ -177,7 +185,7 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); |
|
|
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); |
|
|
this.WriteGraphicalControlExtension(frame.MetaData, this.GetTransparentIndex(quantized), stream); |
|
|
this.WriteGraphicalControlExtension(meta, 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); |
|
|
@ -185,6 +193,7 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
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 = meta; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -290,25 +299,8 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
// Application Extension Header
|
|
|
// Application Extension Header
|
|
|
if (repeatCount != 1) |
|
|
if (repeatCount != 1) |
|
|
{ |
|
|
{ |
|
|
this.buffer[0] = GifConstants.ExtensionIntroducer; |
|
|
var loopingExtension = new GifNetscapeLoopingApplicationExtension(repeatCount); |
|
|
this.buffer[1] = GifConstants.ApplicationExtensionLabel; |
|
|
this.WriteExtension(loopingExtension, stream); |
|
|
this.buffer[2] = GifConstants.ApplicationBlockSize; |
|
|
|
|
|
|
|
|
|
|
|
// Write NETSCAPE2.0
|
|
|
|
|
|
GifConstants.ApplicationIdentificationBytes.AsSpan().CopyTo(this.buffer.AsSpan(3, 11)); |
|
|
|
|
|
|
|
|
|
|
|
// Application Data ----
|
|
|
|
|
|
this.buffer[14] = 3; // Application block length
|
|
|
|
|
|
this.buffer[15] = 1; // Data sub-block index (always 1)
|
|
|
|
|
|
|
|
|
|
|
|
// 0 means loop indefinitely. Count is set as play n + 1 times.
|
|
|
|
|
|
repeatCount = (ushort)Math.Max(0, repeatCount - 1); |
|
|
|
|
|
|
|
|
|
|
|
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer.AsSpan(16, 2), repeatCount); // Repeat count for images.
|
|
|
|
|
|
|
|
|
|
|
|
this.buffer[18] = GifConstants.Terminator; // Terminator
|
|
|
|
|
|
|
|
|
|
|
|
stream.Write(this.buffer, 0, 19); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -348,7 +340,7 @@ namespace SixLabors.ImageSharp.Formats.Gif |
|
|
/// <param name="metaData">The metadata of the image or frame.</param>
|
|
|
/// <param name="metaData">The metadata of the image or frame.</param>
|
|
|
/// <param name="transparencyIndex">The index of the color in the color palette to make transparent.</param>
|
|
|
/// <param name="transparencyIndex">The index of the color in the color palette to make transparent.</param>
|
|
|
/// <param name="stream">The stream to write to.</param>
|
|
|
/// <param name="stream">The stream to write to.</param>
|
|
|
private void WriteGraphicalControlExtension(ImageFrameMetaData metaData, int transparencyIndex, Stream stream) |
|
|
private void WriteGraphicalControlExtension(GifFrameMetaData metaData, int transparencyIndex, Stream stream) |
|
|
{ |
|
|
{ |
|
|
byte packedValue = GifGraphicControlExtension.GetPackedValue( |
|
|
byte packedValue = GifGraphicControlExtension.GetPackedValue( |
|
|
disposalMethod: metaData.DisposalMethod, |
|
|
disposalMethod: metaData.DisposalMethod, |
|
|
|