Skip to content

Commit b4567a4

Browse files
Normalize encoders and update refs
1 parent 1ec479e commit b4567a4

26 files changed

+391
-194
lines changed

src/ImageSharp/Formats/FormatConnectingMetadata.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class FormatConnectingMetadata
4545
/// Gets the default background color of the canvas when animating.
4646
/// This color may be used to fill the unused space on the canvas around the frames,
4747
/// as well as the transparent pixels of the first frame.
48-
/// The background color is also used when the disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
48+
/// The background color is also used when a frame disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
4949
/// </summary>
5050
/// <remarks>
5151
/// Defaults to <see cref="Color.Transparent"/>.

src/ImageSharp/Formats/Gif/GifEncoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Gif;
66
/// <summary>
77
/// Image encoder for writing image data to a stream in gif format.
88
/// </summary>
9-
public sealed class GifEncoder : QuantizingImageEncoder
9+
public sealed class GifEncoder : QuantizingAnimatedImageEncoder
1010
{
1111
/// <summary>
1212
/// Gets the color table mode: Global or local.

src/ImageSharp/Formats/Gif/GifEncoderCore.cs

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ internal sealed class GifEncoderCore
5454
/// </summary>
5555
private readonly IPixelSamplingStrategy pixelSamplingStrategy;
5656

57+
/// <summary>
58+
/// The default background color of the canvas when animating.
59+
/// This color may be used to fill the unused space on the canvas around the frames,
60+
/// as well as the transparent pixels of the first frame.
61+
/// The background color is also used when a frame disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
62+
/// </summary>
63+
private readonly Color? backgroundColor;
64+
65+
/// <summary>
66+
/// The number of times any animation is repeated.
67+
/// </summary>
68+
private readonly ushort? repeatCount;
69+
5770
/// <summary>
5871
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
5972
/// </summary>
@@ -68,6 +81,8 @@ public GifEncoderCore(Configuration configuration, GifEncoder encoder)
6881
this.hasQuantizer = encoder.Quantizer is not null;
6982
this.colorTableMode = encoder.ColorTableMode;
7083
this.pixelSamplingStrategy = encoder.PixelSamplingStrategy;
84+
this.backgroundColor = encoder.BackgroundColor;
85+
this.repeatCount = encoder.RepeatCount;
7186
}
7287

7388
/// <summary>
@@ -141,9 +156,17 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
141156
frameMetadata.TransparencyIndex = ClampIndex(derivedTransparencyIndex);
142157
}
143158

144-
byte backgroundIndex = derivedTransparencyIndex >= 0
145-
? frameMetadata.TransparencyIndex
146-
: gifMetadata.BackgroundColorIndex;
159+
byte backgroundIndex;
160+
if (this.backgroundColor.HasValue)
161+
{
162+
backgroundIndex = GetBackgroundIndex(quantized, this.backgroundColor.Value);
163+
}
164+
else
165+
{
166+
backgroundIndex = derivedTransparencyIndex >= 0
167+
? frameMetadata.TransparencyIndex
168+
: gifMetadata.BackgroundColorIndex;
169+
}
147170

148171
// Get the number of bits.
149172
int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length);
@@ -161,15 +184,21 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken
161184

162185
// Write application extensions.
163186
XmpProfile? xmpProfile = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile;
164-
this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, xmpProfile);
187+
this.WriteApplicationExtensions(stream, image.Frames.Count, this.repeatCount ?? gifMetadata.RepeatCount, xmpProfile);
165188
}
166189

167190
this.EncodeFirstFrame(stream, frameMetadata, quantized);
168191

169192
// Capture the global palette for reuse on subsequent frames and cleanup the quantized frame.
170193
TPixel[] globalPalette = image.Frames.Count == 1 ? [] : quantized.Palette.ToArray();
171194

172-
this.EncodeAdditionalFrames(stream, image, globalPalette, derivedTransparencyIndex, frameMetadata.DisposalMode);
195+
this.EncodeAdditionalFrames(
196+
stream,
197+
image,
198+
globalPalette,
199+
derivedTransparencyIndex,
200+
frameMetadata.DisposalMode,
201+
cancellationToken);
173202

174203
stream.WriteByte(GifConstants.EndIntroducer);
175204

@@ -194,7 +223,8 @@ private void EncodeAdditionalFrames<TPixel>(
194223
Image<TPixel> image,
195224
ReadOnlyMemory<TPixel> globalPalette,
196225
int globalTransparencyIndex,
197-
FrameDisposalMode previousDisposalMode)
226+
FrameDisposalMode previousDisposalMode,
227+
CancellationToken cancellationToken)
198228
where TPixel : unmanaged, IPixel<TPixel>
199229
{
200230
if (image.Frames.Count == 1)
@@ -213,6 +243,16 @@ private void EncodeAdditionalFrames<TPixel>(
213243

214244
for (int i = 1; i < image.Frames.Count; i++)
215245
{
246+
if (cancellationToken.IsCancellationRequested)
247+
{
248+
if (hasPaletteQuantizer)
249+
{
250+
paletteQuantizer.Dispose();
251+
}
252+
253+
return;
254+
}
255+
216256
// Gather the metadata for this frame.
217257
ImageFrame<TPixel> currentFrame = image.Frames[i];
218258
ImageFrame<TPixel>? nextFrame = i < image.Frames.Count - 1 ? image.Frames[i + 1] : null;
@@ -291,6 +331,10 @@ private void EncodeAdditionalFrame<TPixel>(
291331

292332
ImageFrame<TPixel>? previous = previousDisposalMode == FrameDisposalMode.RestoreToBackground ? null : previousFrame;
293333

334+
Color background = metadata.DisposalMode == FrameDisposalMode.RestoreToBackground
335+
? this.backgroundColor ?? Color.Transparent
336+
: Color.Transparent;
337+
294338
// Deduplicate and quantize the frame capturing only required parts.
295339
(bool difference, Rectangle bounds) =
296340
AnimationUtilities.DeDuplicatePixels(
@@ -299,7 +343,7 @@ private void EncodeAdditionalFrame<TPixel>(
299343
currentFrame,
300344
nextFrame,
301345
encodingFrame,
302-
Color.Transparent,
346+
background,
303347
true);
304348

305349
using IndexedImageFrame<TPixel> quantized = this.QuantizeAdditionalFrameAndUpdateMetadata(
@@ -428,14 +472,12 @@ private IndexedImageFrame<TPixel> QuantizeAdditionalFrameAndUpdateMetadata<TPixe
428472
private static byte ClampIndex(int value) => (byte)Numerics.Clamp(value, byte.MinValue, byte.MaxValue);
429473

430474
/// <summary>
431-
/// Returns the index of the most transparent color in the palette.
475+
/// Returns the index of the transparent color in the palette.
432476
/// </summary>
433477
/// <param name="quantized">The current quantized frame.</param>
434478
/// <param name="metadata">The current gif frame metadata.</param>
435479
/// <typeparam name="TPixel">The pixel format.</typeparam>
436-
/// <returns>
437-
/// The <see cref="int"/>.
438-
/// </returns>
480+
/// <returns>The <see cref="int"/>.</returns>
439481
private static int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel>? quantized, GifFrameMetadata? metadata)
440482
where TPixel : unmanaged, IPixel<TPixel>
441483
{
@@ -463,6 +505,36 @@ private static int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel>? quanti
463505
return index;
464506
}
465507

508+
/// <summary>
509+
/// Returns the index of the background color in the palette.
510+
/// </summary>
511+
/// <param name="quantized">The current quantized frame.</param>
512+
/// <param name="background">The background color to match.</param>
513+
/// <typeparam name="TPixel">The pixel format.</typeparam>
514+
/// <returns>The <see cref="byte"/>.</returns>
515+
private static byte GetBackgroundIndex<TPixel>(IndexedImageFrame<TPixel>? quantized, Color background)
516+
where TPixel : unmanaged, IPixel<TPixel>
517+
{
518+
int index = -1;
519+
if (quantized != null)
520+
{
521+
TPixel backgroundPixel = background.ToPixel<TPixel>();
522+
ReadOnlySpan<TPixel> palette = quantized.Palette.Span;
523+
for (int i = 0; i < palette.Length; i++)
524+
{
525+
if (!backgroundPixel.Equals(palette[i]))
526+
{
527+
continue;
528+
}
529+
530+
index = i;
531+
break;
532+
}
533+
}
534+
535+
return (byte)Numerics.Clamp(index, 0, 255);
536+
}
537+
466538
/// <summary>
467539
/// Writes the file header signature and version to the stream.
468540
/// </summary>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Formats;
5+
6+
/// <summary>
7+
/// Defines the contract for all image encoders that allow encoding animation sequences.
8+
/// </summary>
9+
public interface IAnimatedImageEncoder
10+
{
11+
/// <summary>
12+
/// Gets the default background color of the canvas when animating in supported encoders.
13+
/// This color may be used to fill the unused space on the canvas around the frames,
14+
/// as well as the transparent pixels of the first frame.
15+
/// The background color is also used when a frame disposal mode is <see cref="FrameDisposalMode.RestoreToBackground"/>.
16+
/// </summary>
17+
Color? BackgroundColor { get; }
18+
19+
/// <summary>
20+
/// Gets the number of times any animation is repeated in supported encoders.
21+
/// </summary>
22+
ushort? RepeatCount { get; }
23+
24+
/// <summary>
25+
/// Gets a value indicating whether the root frame is shown as part of the animated sequence in supported encoders.
26+
/// </summary>
27+
bool? AnimateRootFrame { get; }
28+
}
29+
30+
/// <summary>
31+
/// Acts as a base class for all image encoders that allow encoding animation sequences.
32+
/// </summary>
33+
public abstract class AnimatedImageEncoder : ImageEncoder, IAnimatedImageEncoder
34+
{
35+
/// <inheritdoc/>
36+
public Color? BackgroundColor { get; init; }
37+
38+
/// <inheritdoc/>
39+
public ushort? RepeatCount { get; init; }
40+
41+
/// <inheritdoc/>
42+
public bool? AnimateRootFrame { get; init; } = true;
43+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using SixLabors.ImageSharp.Processing.Processors.Quantization;
5+
6+
namespace SixLabors.ImageSharp.Formats;
7+
8+
/// <summary>
9+
/// Defines the contract for all image encoders that allow color palette generation via quantization.
10+
/// </summary>
11+
public interface IQuantizingImageEncoder
12+
{
13+
/// <summary>
14+
/// Gets the quantizer used to generate the color palette.
15+
/// </summary>
16+
IQuantizer? Quantizer { get; }
17+
18+
/// <summary>
19+
/// Gets the <see cref="IPixelSamplingStrategy"/> used for quantization when building color palettes.
20+
/// </summary>
21+
IPixelSamplingStrategy PixelSamplingStrategy { get; }
22+
}
23+
24+
/// <summary>
25+
/// Acts as a base class for all image encoders that allow color palette generation via quantization.
26+
/// </summary>
27+
public abstract class QuantizingImageEncoder : ImageEncoder, IQuantizingImageEncoder
28+
{
29+
/// <inheritdoc/>
30+
public IQuantizer? Quantizer { get; init; }
31+
32+
/// <inheritdoc/>
33+
public IPixelSamplingStrategy PixelSamplingStrategy { get; init; } = new DefaultPixelSamplingStrategy();
34+
}
35+
36+
/// <summary>
37+
/// Acts as a base class for all image encoders that allow color palette generation via quantization when
38+
/// encoding animation sequences.
39+
/// </summary>
40+
public abstract class QuantizingAnimatedImageEncoder : QuantizingImageEncoder, IAnimatedImageEncoder
41+
{
42+
/// <inheritdoc/>
43+
public Color? BackgroundColor { get; }
44+
45+
/// <inheritdoc/>
46+
public ushort? RepeatCount { get; }
47+
48+
/// <inheritdoc/>
49+
public bool? AnimateRootFrame { get; }
50+
}

src/ImageSharp/Formats/Png/PngEncoder.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
3-
#nullable disable
43

54
using SixLabors.ImageSharp.Processing.Processors.Quantization;
65

@@ -9,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
98
/// <summary>
109
/// Image encoder for writing image data to a stream in png format.
1110
/// </summary>
12-
public class PngEncoder : QuantizingImageEncoder
11+
public class PngEncoder : QuantizingAnimatedImageEncoder
1312
{
1413
/// <summary>
1514
/// Initializes a new instance of the <see cref="PngEncoder"/> class.

0 commit comments

Comments
 (0)