Skip to content

Commit 0c6f0dc

Browse files
Merge pull request #1193 from SixLabors/af/extended-quantization
Efficient global quantization in GifEncoder
2 parents ddbd624 + 5db749e commit 0c6f0dc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+582
-198
lines changed

src/ImageSharp/Advanced/AotCompilerTools.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ private static void Seed<TPixel>()
112112
private static void AotCompileOctreeQuantizer<TPixel>()
113113
where TPixel : unmanaged, IPixel<TPixel>
114114
{
115-
using (var test = new OctreeFrameQuantizer<TPixel>(Configuration.Default, new OctreeQuantizer().Options))
115+
using (var test = new OctreeQuantizer<TPixel>(Configuration.Default, new OctreeQuantizer().Options))
116116
{
117117
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
118118
test.QuantizeFrame(frame, frame.Bounds());
@@ -126,7 +126,7 @@ private static void AotCompileOctreeQuantizer<TPixel>()
126126
private static void AotCompileWuQuantizer<TPixel>()
127127
where TPixel : unmanaged, IPixel<TPixel>
128128
{
129-
using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default, new WuQuantizer().Options))
129+
using (var test = new WuQuantizer<TPixel>(Configuration.Default, new WuQuantizer().Options))
130130
{
131131
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
132132
test.QuantizeFrame(frame, frame.Bounds());
@@ -140,7 +140,7 @@ private static void AotCompileWuQuantizer<TPixel>()
140140
private static void AotCompilePaletteQuantizer<TPixel>()
141141
where TPixel : unmanaged, IPixel<TPixel>
142142
{
143-
using (var test = (PaletteFrameQuantizer<TPixel>)new PaletteQuantizer(Array.Empty<Color>()).CreateFrameQuantizer<TPixel>(Configuration.Default))
143+
using (var test = (PaletteQuantizer<TPixel>)new PaletteQuantizer(Array.Empty<Color>()).CreatePixelSpecificQuantizer<TPixel>(Configuration.Default))
144144
{
145145
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
146146
test.QuantizeFrame(frame, frame.Bounds());

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,8 @@ private void Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)
336336
private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
337337
where TPixel : unmanaged, IPixel<TPixel>
338338
{
339-
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
340-
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image, image.Bounds());
339+
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
340+
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
341341

342342
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span;
343343
var color = default(Rgba32);

src/ImageSharp/Formats/Gif/GifDecoderCore.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -543,8 +543,8 @@ private void RestoreToBackground<TPixel>(ImageFrame<TPixel> frame)
543543
return;
544544
}
545545

546-
BufferArea<TPixel> pixelArea = frame.PixelBuffer.GetArea(this.restoreArea.Value);
547-
pixelArea.Clear();
546+
BufferRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion(this.restoreArea.Value);
547+
pixelRegion.Clear();
548548

549549
this.restoreArea = null;
550550
}

src/ImageSharp/Formats/Gif/GifEncoder.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions
2525
/// </summary>
2626
public GifColorTableMode? ColorTableMode { get; set; }
2727

28+
/// <summary>
29+
/// Gets or sets the <see cref="IPixelSamplingStrategy"/> used for quantization
30+
/// when building a global color table in case of <see cref="GifColorTableMode.Global"/>.
31+
/// </summary>
32+
public IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; set; } = new DefaultPixelSamplingStrategy();
33+
2834
/// <inheritdoc/>
2935
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
3036
where TPixel : unmanaged, IPixel<TPixel>

src/ImageSharp/Formats/Gif/GifEncoderCore.cs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ internal sealed class GifEncoderCore
4949
/// </summary>
5050
private int bitDepth;
5151

52+
/// <summary>
53+
/// The pixel sampling strategy for global quantization.
54+
/// </summary>
55+
private IPixelSamplingStrategy pixelSamplingStrategy;
56+
5257
/// <summary>
5358
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
5459
/// </summary>
@@ -60,6 +65,7 @@ public GifEncoderCore(Configuration configuration, IGifEncoderOptions options)
6065
this.memoryAllocator = configuration.MemoryAllocator;
6166
this.quantizer = options.Quantizer;
6267
this.colorTableMode = options.ColorTableMode;
68+
this.pixelSamplingStrategy = options.GlobalPixelSamplingStrategy;
6369
}
6470

6571
/// <summary>
@@ -81,9 +87,18 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
8187

8288
// Quantize the image returning a palette.
8389
IndexedImageFrame<TPixel> quantized;
84-
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
90+
91+
using (IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration))
8592
{
86-
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
93+
if (useGlobalTable)
94+
{
95+
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
96+
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
97+
}
98+
else
99+
{
100+
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds());
101+
}
87102
}
88103

89104
// Get the number of bits.
@@ -154,7 +169,7 @@ private void EncodeGlobal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel>
154169
pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette);
155170
}
156171

157-
using var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap);
172+
using var paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap);
158173
using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
159174
this.WriteImageData(paletteQuantized, stream);
160175
}
@@ -184,13 +199,13 @@ private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel>
184199
MaxColors = frameMetadata.ColorTableLength
185200
};
186201

187-
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, options);
188-
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
202+
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, options);
203+
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
189204
}
190205
else
191206
{
192-
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
193-
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
207+
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
208+
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
194209
}
195210
}
196211

src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using System.Collections.Generic;
5+
using SixLabors.ImageSharp.Memory;
6+
using SixLabors.ImageSharp.PixelFormats;
47
using SixLabors.ImageSharp.Processing.Processors.Quantization;
58

69
namespace SixLabors.ImageSharp.Formats.Gif
@@ -19,5 +22,10 @@ internal interface IGifEncoderOptions
1922
/// Gets the color table mode: Global or local.
2023
/// </summary>
2124
GifColorTableMode? ColorTableMode { get; }
25+
26+
/// <summary>
27+
/// Gets the <see cref="IPixelSamplingStrategy"/> used for quantization when building a global color table.
28+
/// </summary>
29+
IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; }
2230
}
2331
}

src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ internal partial struct Block8x8F
1515
/// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors.
1616
/// </summary>
1717
[MethodImpl(InliningOptions.ShortMethod)]
18-
public void ScaledCopyTo(in BufferArea<float> area, int horizontalScale, int verticalScale)
18+
public void ScaledCopyTo(in BufferRegion<float> region, int horizontalScale, int verticalScale)
1919
{
20-
ref float areaOrigin = ref area.GetReferenceToOrigin();
21-
this.ScaledCopyTo(ref areaOrigin, area.Stride, horizontalScale, verticalScale);
20+
ref float areaOrigin = ref region.GetReferenceToOrigin();
21+
this.ScaledCopyTo(ref areaOrigin, region.Stride, horizontalScale, verticalScale);
2222
}
2323

2424
[MethodImpl(InliningOptions.ShortMethod)]

src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ public static IndexedImageFrame<TPixel> CreateQuantizedFrame<TPixel>(
7777
}
7878

7979
// Create quantized frame returning the palette and set the bit depth.
80-
using (IFrameQuantizer<TPixel> frameQuantizer = options.Quantizer.CreateFrameQuantizer<TPixel>(image.GetConfiguration()))
80+
using (IQuantizer<TPixel> frameQuantizer = options.Quantizer.CreatePixelSpecificQuantizer<TPixel>(image.GetConfiguration()))
8181
{
8282
ImageFrame<TPixel> frame = image.Frames.RootFrame;
83-
return frameQuantizer.QuantizeFrame(frame, frame.Bounds());
83+
return frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
8484
}
8585
}
8686

src/ImageSharp/Image{TPixel}.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,18 @@ public Image(Configuration configuration, int width, int height, TPixel backgrou
4848
{
4949
}
5050

51+
/// <summary>
52+
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
53+
/// with the height and the width of the image.
54+
/// </summary>
55+
/// <param name="width">The width of the image in pixels.</param>
56+
/// <param name="height">The height of the image in pixels.</param>
57+
/// <param name="backgroundColor">The color to initialize the pixels with.</param>
58+
public Image(int width, int height, TPixel backgroundColor)
59+
: this(Configuration.Default, width, height, backgroundColor, new ImageMetadata())
60+
{
61+
}
62+
5163
/// <summary>
5264
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
5365
/// with the height and the width of the image.

src/ImageSharp/Memory/Buffer2DExtensions.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,29 +80,29 @@ internal static Rectangle FullRectangle<T>(this Buffer2D<T> buffer)
8080
}
8181

8282
/// <summary>
83-
/// Return a <see cref="BufferArea{T}"/> to the subarea represented by 'rectangle'
83+
/// Return a <see cref="BufferRegion{T}"/> to the subarea represented by 'rectangle'
8484
/// </summary>
8585
/// <typeparam name="T">The element type</typeparam>
8686
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
8787
/// <param name="rectangle">The rectangle subarea</param>
88-
/// <returns>The <see cref="BufferArea{T}"/></returns>
89-
internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, in Rectangle rectangle)
90-
where T : struct =>
91-
new BufferArea<T>(buffer, rectangle);
88+
/// <returns>The <see cref="BufferRegion{T}"/></returns>
89+
internal static BufferRegion<T> GetRegion<T>(this Buffer2D<T> buffer, in Rectangle rectangle)
90+
where T : unmanaged =>
91+
new BufferRegion<T>(buffer, rectangle);
9292

93-
internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
94-
where T : struct =>
95-
new BufferArea<T>(buffer, new Rectangle(x, y, width, height));
93+
internal static BufferRegion<T> GetRegion<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
94+
where T : unmanaged =>
95+
new BufferRegion<T>(buffer, new Rectangle(x, y, width, height));
9696

9797
/// <summary>
98-
/// Return a <see cref="BufferArea{T}"/> to the whole area of 'buffer'
98+
/// Return a <see cref="BufferRegion{T}"/> to the whole area of 'buffer'
9999
/// </summary>
100100
/// <typeparam name="T">The element type</typeparam>
101101
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
102-
/// <returns>The <see cref="BufferArea{T}"/></returns>
103-
internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer)
104-
where T : struct =>
105-
new BufferArea<T>(buffer);
102+
/// <returns>The <see cref="BufferRegion{T}"/></returns>
103+
internal static BufferRegion<T> GetRegion<T>(this Buffer2D<T> buffer)
104+
where T : unmanaged =>
105+
new BufferRegion<T>(buffer);
106106

107107
/// <summary>
108108
/// Returns the size of the buffer.

0 commit comments

Comments
 (0)