diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index 585f87b3e8..4c881ec3f2 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -7,7 +7,6 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
-using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
@@ -54,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// The pixel sampling strategy for global quantization.
///
- private IPixelSamplingStrategy pixelSamplingStrategy;
+ private readonly IPixelSamplingStrategy pixelSamplingStrategy;
///
/// Initializes a new instance of the class.
@@ -150,8 +149,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
// 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 pixelMap = default;
- bool pixelMapHasValue = false;
+ PaletteQuantizer paletteFrameQuantizer = default;
+ bool quantizerInitialized = false;
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame frame = image.Frames[i];
@@ -166,22 +165,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
- if (!pixelMapHasValue)
+ if (!quantizerInitialized)
{
- pixelMapHasValue = true;
- pixelMap = new EuclideanPixelMap(this.configuration, quantized.Palette);
+ quantizerInitialized = true;
+ paletteFrameQuantizer = new PaletteQuantizer(this.configuration, this.quantizer.Options, quantized.Palette);
}
- using var paletteFrameQuantizer = new PaletteQuantizer(this.configuration, this.quantizer.Options, pixelMap, true);
using IndexedImageFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
this.WriteImageData(paletteQuantized, stream);
}
}
- if (pixelMapHasValue)
- {
- pixelMap.Dispose();
- }
+ paletteFrameQuantizer.Dispose();
}
private void EncodeLocal(Image image, IndexedImageFrame quantized, Stream stream)
@@ -310,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
- ratio = (byte)(((1 / vr) * 64) - 15);
+ ratio = (byte)((1 / vr * 64) - 15);
}
}
}
@@ -354,7 +349,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
return;
}
- for (var i = 0; i < metadata.Comments.Count; i++)
+ for (int i = 0; i < metadata.Comments.Count; i++)
{
string comment = metadata.Comments[i];
this.buffer[0] = GifConstants.ExtensionIntroducer;
diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
index 0311c40be4..b82ce71bbd 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
@@ -18,20 +18,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// This class is not threadsafe and should not be accessed in parallel.
/// Doing so will result in non-idempotent results.
///
- internal readonly struct EuclideanPixelMap : IDisposable
+ internal sealed class EuclideanPixelMap : IDisposable
where TPixel : unmanaged, IPixel
{
- private readonly Rgba32[] rgbaPalette;
+ private Rgba32[] rgbaPalette;
private readonly ColorDistanceCache cache;
+ private readonly Configuration configuration;
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the class.
///
/// The configuration.
/// The color palette to map from.
- [MethodImpl(InliningOptions.ShortMethod)]
public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette)
{
+ this.configuration = configuration;
this.Palette = palette;
this.rgbaPalette = new Rgba32[palette.Length];
this.cache = new ColorDistanceCache(configuration.MemoryAllocator);
@@ -46,6 +47,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
[MethodImpl(InliningOptions.ShortMethod)]
get;
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private set;
}
///
@@ -72,6 +76,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return index;
}
+ ///
+ /// Clears the map, resetting it to use the given palette.
+ ///
+ /// The color palette to map from.
+ public void Clear(ReadOnlyMemory palette)
+ {
+ this.Palette = palette;
+ this.rgbaPalette = new Rgba32[palette.Length];
+ PixelOperations.Instance.ToRgba32(this.configuration, this.Palette.Span, this.rgbaPalette);
+ this.cache.Clear();
+ }
+
[MethodImpl(InliningOptions.ShortMethod)]
private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match)
{
@@ -177,6 +193,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return match > -1;
}
+ ///
+ /// Clears the cache resetting each entry to empty.
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Clear() => this.table.GetSpan().Fill(-1);
+
[MethodImpl(InliningOptions.ShortMethod)]
private static int GetPaletteIndex(int r, int g, int b, int a)
=> (r << ((IndexBits << 1) + IndexAlphaBits))
diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
index 10b26337f4..311a8aa2e0 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
@@ -25,7 +25,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private IMemoryOwner paletteOwner;
private ReadOnlyMemory palette;
private EuclideanPixelMap pixelMap;
- private bool pixelMapHasValue;
private readonly bool isDithering;
private bool isDisposed;
@@ -48,7 +47,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.octree = new Octree(this.bitDepth);
this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean);
this.pixelMap = default;
- this.pixelMapHasValue = false;
this.palette = default;
this.isDithering = !(this.Options.Dither is null);
this.isDisposed = false;
@@ -112,15 +110,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.octree.Palletize(paletteSpan, max, ref paletteIndex);
ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length);
- // When called by QuantizerUtilities.BuildPalette this prevents
- // mutiple instances of the map being created but not disposed.
- if (this.pixelMapHasValue)
+ // When called multiple times by QuantizerUtilities.BuildPalette
+ // this prevents memory churn caused by reallocation.
+ if (this.pixelMap is null)
{
- this.pixelMap.Dispose();
+ this.pixelMap = new EuclideanPixelMap(this.Configuration, result);
+ }
+ else
+ {
+ this.pixelMap.Clear(result);
}
- this.pixelMap = new EuclideanPixelMap(this.Configuration, result);
- this.pixelMapHasValue = true;
this.palette = result;
}
@@ -153,9 +153,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (!this.isDisposed)
{
this.isDisposed = true;
- this.paletteOwner.Dispose();
+ this.paletteOwner?.Dispose();
this.paletteOwner = null;
- this.pixelMap.Dispose();
+ this.pixelMap?.Dispose();
+ this.pixelMap = null;
}
}
diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
index a83c760c20..4f73f4ac81 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
@@ -58,9 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
var palette = new TPixel[length];
Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan());
-
- var pixelMap = new EuclideanPixelMap(configuration, palette);
- return new PaletteQuantizer(configuration, options, pixelMap, false);
+ return new PaletteQuantizer(configuration, options, palette);
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
index 9329bdfebe..284f4fa701 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
@@ -16,32 +16,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
internal struct PaletteQuantizer : IQuantizer
where TPixel : unmanaged, IPixel
{
- private readonly EuclideanPixelMap pixelMap;
- private readonly bool leaveMap;
+ private EuclideanPixelMap pixelMap;
///
/// Initializes a new instance of the struct.
///
/// The configuration which allows altering default behaviour or extending the library.
/// The quantizer options defining quantization rules.
- /// The pixel map for looking up color matches from a predefined palette.
- ///
- /// to leave the pixel map undisposed after disposing the object; otherwise, .
- ///
+ /// The palette to use.
[MethodImpl(InliningOptions.ShortMethod)]
- public PaletteQuantizer(
- Configuration configuration,
- QuantizerOptions options,
- EuclideanPixelMap pixelMap,
- bool leaveMap)
+ public PaletteQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory palette)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
this.Configuration = configuration;
this.Options = options;
- this.pixelMap = pixelMap;
- this.leaveMap = leaveMap;
+ this.pixelMap = new EuclideanPixelMap(configuration, palette);
}
///
@@ -72,10 +63,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
///
public void Dispose()
{
- if (!this.leaveMap)
- {
- this.pixelMap.Dispose();
- }
+ this.pixelMap?.Dispose();
+ this.pixelMap = null;
}
}
}
diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
index b6f4be4949..bf4a5ca41e 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
@@ -72,7 +72,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private int maxColors;
private readonly Box[] colorCube;
private EuclideanPixelMap pixelMap;
- private bool pixelMapHasValue;
private readonly bool isDithering;
private bool isDisposed;
@@ -97,7 +96,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.colorCube = new Box[this.maxColors];
this.isDisposed = false;
this.pixelMap = default;
- this.pixelMapHasValue = false;
this.palette = default;
this.isDithering = this.isDithering = !(this.Options.Dither is null);
}
@@ -147,15 +145,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length);
if (this.isDithering)
{
- // When called by QuantizerUtilities.BuildPalette this prevents
- // mutiple instances of the map being created but not disposed.
- if (this.pixelMapHasValue)
+ // When called multiple times by QuantizerUtilities.BuildPalette
+ // this prevents memory churn caused by reallocation.
+ if (this.pixelMap is null)
{
- this.pixelMap.Dispose();
+ this.pixelMap = new EuclideanPixelMap(this.Configuration, result);
+ }
+ else
+ {
+ this.pixelMap.Clear(result);
}
-
- this.pixelMap = new EuclideanPixelMap(this.Configuration, result);
- this.pixelMapHasValue = true;
}
this.palette = result;
@@ -201,7 +200,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.momentsOwner = null;
this.tagsOwner = null;
this.paletteOwner = null;
- this.pixelMap.Dispose();
+ this.pixelMap?.Dispose();
+ this.pixelMap = null;
}
}
@@ -215,16 +215,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The index.
[MethodImpl(InliningOptions.ShortMethod)]
private static int GetPaletteIndex(int r, int g, int b, int a)
- {
- return (r << ((IndexBits * 2) + IndexAlphaBits))
- + (r << (IndexBits + IndexAlphaBits + 1))
- + (g << (IndexBits + IndexAlphaBits))
- + (r << (IndexBits * 2))
- + (r << (IndexBits + 1))
- + (g << IndexBits)
- + ((r + g + b) << IndexAlphaBits)
- + r + g + b + a;
- }
+ => (r << ((IndexBits * 2) + IndexAlphaBits))
+ + (r << (IndexBits + IndexAlphaBits + 1))
+ + (g << (IndexBits + IndexAlphaBits))
+ + (r << (IndexBits * 2))
+ + (r << (IndexBits + 1))
+ + (g << IndexBits)
+ + ((r + g + b) << IndexAlphaBits)
+ + r + g + b + a;
///
/// Computes sum over a box of any given statistic.
@@ -233,24 +231,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The moment.
/// The result.
private static Moment Volume(ref Box cube, ReadOnlySpan moments)
- {
- return moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)]
- - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)]
- - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)]
- + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)]
- - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)]
- + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)]
- + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
- - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
- - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)]
- + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
- + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)]
- - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
- + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)]
- - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
- - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
- + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
- }
+ => moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)]
+ - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)]
+ - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)]
+ + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)]
+ - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)]
+ + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)]
+ + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
+ - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
+ - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)]
+ + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
+ + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)]
+ - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
+ + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)]
+ - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
+ - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)]
+ + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
///
/// Computes part of Volume(cube, moment) that doesn't depend on RMax, GMax, BMax, or AMax (depending on direction).
@@ -835,7 +831,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public int Volume;
///
- public readonly override bool Equals(object obj)
+ public override readonly bool Equals(object obj)
=> obj is Box box
&& this.Equals(box);
@@ -852,7 +848,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
&& this.Volume == other.Volume;
///
- public readonly override int GetHashCode()
+ public override readonly int GetHashCode()
{
HashCode hash = default;
hash.Add(this.RMin);