Skip to content

Commit 98ed0f1

Browse files
Refactor and fix gif encoder
1 parent f33f67d commit 98ed0f1

File tree

7 files changed

+366
-212
lines changed

7 files changed

+366
-212
lines changed

src/ImageSharp/Color/Color.Conversions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public Color(Vector4 vector)
139139
/// </summary>
140140
/// <param name="color">The <see cref="Color"/>.</param>
141141
/// <returns>The <see cref="Vector4"/>.</returns>
142-
public static explicit operator Vector4(Color color) => color.ToVector4();
142+
public static explicit operator Vector4(Color color) => color.ToScaledVector4();
143143

144144
/// <summary>
145145
/// Converts an <see cref="Vector4"/> to <see cref="Color"/>.
@@ -228,7 +228,7 @@ internal Bgr24 ToBgr24()
228228
}
229229

230230
[MethodImpl(InliningOptions.ShortMethod)]
231-
internal Vector4 ToVector4()
231+
internal Vector4 ToScaledVector4()
232232
{
233233
if (this.boxedHighPrecisionPixel is null)
234234
{

src/ImageSharp/Formats/Gif/GifEncoderCore.cs

Lines changed: 277 additions & 198 deletions
Large diffs are not rendered by default.

src/ImageSharp/ImageFrame{TPixel}.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
2121
{
2222
private bool isDisposed;
2323

24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class.
26+
/// </summary>
27+
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
28+
/// <param name="size">The <see cref="Size"/> of the frame.</param>
29+
internal ImageFrame(Configuration configuration, Size size)
30+
: this(configuration, size.Width, size.Height, new ImageFrameMetadata())
31+
{
32+
}
33+
2434
/// <summary>
2535
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}" /> class.
2636
/// </summary>

src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Buffers;
5+
using System.Numerics;
56
using System.Runtime.CompilerServices;
67
using System.Runtime.InteropServices;
78
using SixLabors.ImageSharp.Memory;
@@ -14,13 +15,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization;
1415
/// </summary>
1516
/// <typeparam name="TPixel">The pixel format.</typeparam>
1617
/// <para>
17-
/// This class is not threadsafe and should not be accessed in parallel.
18+
/// This class is not thread safe and should not be accessed in parallel.
1819
/// Doing so will result in non-idempotent results.
1920
/// </para>
2021
internal sealed class EuclideanPixelMap<TPixel> : IDisposable
2122
where TPixel : unmanaged, IPixel<TPixel>
2223
{
2324
private Rgba32[] rgbaPalette;
25+
private int transparentIndex;
2426

2527
/// <summary>
2628
/// Do not make this readonly! Struct value would be always copied on non-readonly method calls.
@@ -34,12 +36,24 @@ internal sealed class EuclideanPixelMap<TPixel> : IDisposable
3436
/// <param name="configuration">The configuration.</param>
3537
/// <param name="palette">The color palette to map from.</param>
3638
public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory<TPixel> palette)
39+
: this(configuration, palette, -1)
40+
{
41+
}
42+
43+
/// <summary>
44+
/// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> class.
45+
/// </summary>
46+
/// <param name="configuration">The configuration.</param>
47+
/// <param name="palette">The color palette to map from.</param>
48+
/// <param name="transparentIndex">An explicit index at which to match transparent pixels.</param>
49+
public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory<TPixel> palette, int transparentIndex = -1)
3750
{
3851
this.configuration = configuration;
3952
this.Palette = palette;
4053
this.rgbaPalette = new Rgba32[palette.Length];
4154
this.cache = new ColorDistanceCache(configuration.MemoryAllocator);
4255
PixelOperations<TPixel>.Instance.ToRgba32(configuration, this.Palette.Span, this.rgbaPalette);
56+
this.transparentIndex = transparentIndex;
4357
}
4458

4559
/// <summary>
@@ -91,16 +105,43 @@ public void Clear(ReadOnlyMemory<TPixel> palette)
91105
this.cache.Clear();
92106
}
93107

108+
/// <summary>
109+
/// Allows setting the transparent index after construction.
110+
/// </summary>
111+
/// <param name="index">An explicit index at which to match transparent pixels.</param>
112+
public void SetTransparentIndex(int index) => this.transparentIndex = index;
113+
94114
[MethodImpl(InliningOptions.ShortMethod)]
95115
private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match)
96116
{
97117
// Loop through the palette and find the nearest match.
98118
int index = 0;
99119
float leastDistance = float.MaxValue;
120+
121+
if (this.transparentIndex >= 0 && rgba == default)
122+
{
123+
// We have explicit instructions. No need to search.
124+
index = this.transparentIndex;
125+
this.cache.Add(rgba, (byte)index);
126+
127+
if (index >= 0 && index < this.Palette.Length)
128+
{
129+
match = Unsafe.Add(ref paletteRef, (uint)index);
130+
}
131+
else
132+
{
133+
Unsafe.SkipInit(out TPixel pixel);
134+
pixel.FromScaledVector4(Vector4.Zero);
135+
match = pixel;
136+
}
137+
138+
return index;
139+
}
140+
100141
for (int i = 0; i < this.rgbaPalette.Length; i++)
101142
{
102143
Rgba32 candidate = this.rgbaPalette[i];
103-
int distance = DistanceSquared(rgba, candidate);
144+
float distance = DistanceSquared(rgba, candidate);
104145

105146
// If it's an exact match, exit the loop
106147
if (distance == 0)
@@ -130,12 +171,12 @@ private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel m
130171
/// <param name="b">The second point.</param>
131172
/// <returns>The distance squared.</returns>
132173
[MethodImpl(InliningOptions.ShortMethod)]
133-
private static int DistanceSquared(Rgba32 a, Rgba32 b)
174+
private static float DistanceSquared(Rgba32 a, Rgba32 b)
134175
{
135-
int deltaR = a.R - b.R;
136-
int deltaG = a.G - b.G;
137-
int deltaB = a.B - b.B;
138-
int deltaA = a.A - b.A;
176+
float deltaR = a.R - b.R;
177+
float deltaG = a.G - b.G;
178+
float deltaB = a.B - b.B;
179+
float deltaA = a.A - b.A;
139180
return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA);
140181
}
141182

src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization;
1111
public class PaletteQuantizer : IQuantizer
1212
{
1313
private readonly ReadOnlyMemory<Color> colorPalette;
14+
private readonly int transparentIndex;
1415

1516
/// <summary>
1617
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
@@ -27,12 +28,24 @@ public PaletteQuantizer(ReadOnlyMemory<Color> palette)
2728
/// <param name="palette">The color palette.</param>
2829
/// <param name="options">The quantizer options defining quantization rules.</param>
2930
public PaletteQuantizer(ReadOnlyMemory<Color> palette, QuantizerOptions options)
31+
: this(palette, options, -1)
32+
{
33+
}
34+
35+
/// <summary>
36+
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
37+
/// </summary>
38+
/// <param name="palette">The color palette.</param>
39+
/// <param name="options">The quantizer options defining quantization rules.</param>
40+
/// <param name="transparentIndex">An explicit index at which to match transparent pixels.</param>
41+
internal PaletteQuantizer(ReadOnlyMemory<Color> palette, QuantizerOptions options, int transparentIndex)
3042
{
3143
Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette));
3244
Guard.NotNull(options, nameof(options));
3345

3446
this.colorPalette = palette;
3547
this.Options = options;
48+
this.transparentIndex = transparentIndex;
3649
}
3750

3851
/// <inheritdoc />
@@ -52,6 +65,6 @@ public IQuantizer<TPixel> CreatePixelSpecificQuantizer<TPixel>(Configuration con
5265
// Always use the palette length over options since the palette cannot be reduced.
5366
TPixel[] palette = new TPixel[this.colorPalette.Length];
5467
Color.ToPixel(this.colorPalette.Span, palette.AsSpan());
55-
return new PaletteQuantizer<TPixel>(configuration, options, palette);
68+
return new PaletteQuantizer<TPixel>(configuration, options, palette, this.transparentIndex);
5669
}
5770
}

src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization;
2525
/// <summary>
2626
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> struct.
2727
/// </summary>
28-
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
28+
/// <param name="configuration">The configuration which allows altering default behavior or extending the library.</param>
2929
/// <param name="options">The quantizer options defining quantization rules.</param>
3030
/// <param name="palette">The palette to use.</param>
31+
/// <param name="transparentIndex">An explicit index at which to match transparent pixels.</param>
3132
[MethodImpl(InliningOptions.ShortMethod)]
32-
public PaletteQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory<TPixel> palette)
33+
public PaletteQuantizer(
34+
Configuration configuration,
35+
QuantizerOptions options,
36+
ReadOnlyMemory<TPixel> palette,
37+
int transparentIndex)
3338
{
3439
Guard.NotNull(configuration, nameof(configuration));
3540
Guard.NotNull(options, nameof(options));
3641

3742
this.Configuration = configuration;
3843
this.Options = options;
39-
this.pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette);
44+
this.pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette, transparentIndex);
4045
}
4146

4247
/// <inheritdoc/>
@@ -59,6 +64,12 @@ public void AddPaletteColors(Buffer2DRegion<TPixel> pixelRegion)
5964
{
6065
}
6166

67+
/// <summary>
68+
/// Allows setting the transparent index after construction.
69+
/// </summary>
70+
/// <param name="index">An explicit index at which to match transparent pixels.</param>
71+
public void SetTransparentIndex(int index) => this.pixelMap.SetTransparentIndex(index);
72+
6273
/// <inheritdoc/>
6374
[MethodImpl(InliningOptions.ShortMethod)]
6475
public readonly byte GetQuantizedColor(TPixel color, out TPixel match)

tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public GifEncoderTests()
3838

3939
[Theory]
4040
[WithTestPatternImages(100, 100, TestPixelTypes, false)]
41-
[WithTestPatternImages(100, 100, TestPixelTypes, false)]
41+
[WithTestPatternImages(100, 100, TestPixelTypes, true)]
4242
public void EncodeGeneratedPatterns<TPixel>(TestImageProvider<TPixel> provider, bool limitAllocationBuffer)
4343
where TPixel : unmanaged, IPixel<TPixel>
4444
{

0 commit comments

Comments
 (0)