Browse Source

Merge branch 'master' into js/bmp-managed-byte-buffer

pull/1676/head
James Jackson-South 5 years ago
committed by GitHub
parent
commit
dee41b631a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  2. 30
      src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
  3. 21
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
  4. 4
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  5. 23
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
  6. 76
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs

23
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -7,7 +7,6 @@ using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
@ -54,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary> /// <summary>
/// The pixel sampling strategy for global quantization. /// The pixel sampling strategy for global quantization.
/// </summary> /// </summary>
private IPixelSamplingStrategy pixelSamplingStrategy; private readonly IPixelSamplingStrategy pixelSamplingStrategy;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class. /// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
@ -150,8 +149,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
// The palette quantizer can reuse the same pixel map across multiple frames // 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 // since the palette is unchanging. This allows a reduction of memory usage across
// multi frame gifs using a global palette. // multi frame gifs using a global palette.
EuclideanPixelMap<TPixel> pixelMap = default; PaletteQuantizer<TPixel> paletteFrameQuantizer = default;
bool pixelMapHasValue = false; bool quantizerInitialized = false;
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];
@ -166,22 +165,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
} }
else else
{ {
if (!pixelMapHasValue) if (!quantizerInitialized)
{ {
pixelMapHasValue = true; quantizerInitialized = true;
pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette); paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, quantized.Palette);
} }
using var paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap, true);
using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
this.WriteImageData(paletteQuantized, stream); this.WriteImageData(paletteQuantized, stream);
} }
} }
if (pixelMapHasValue) paletteFrameQuantizer.Dispose();
{
pixelMap.Dispose();
}
} }
private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream) private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream)
@ -310,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
} }
else else
{ {
ratio = (byte)(((1 / vr) * 64) - 15); ratio = (byte)((1 / vr * 64) - 15);
} }
} }
} }
@ -354,7 +349,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
return; return;
} }
for (var i = 0; i < metadata.Comments.Count; i++) for (int i = 0; i < metadata.Comments.Count; i++)
{ {
string comment = metadata.Comments[i]; string comment = metadata.Comments[i];
this.buffer[0] = GifConstants.ExtensionIntroducer; this.buffer[0] = GifConstants.ExtensionIntroducer;

30
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. /// This class is not threadsafe and should not be accessed in parallel.
/// Doing so will result in non-idempotent results. /// Doing so will result in non-idempotent results.
/// </para> /// </para>
internal readonly struct EuclideanPixelMap<TPixel> : IDisposable internal sealed class EuclideanPixelMap<TPixel> : IDisposable
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly Rgba32[] rgbaPalette; private Rgba32[] rgbaPalette;
private readonly ColorDistanceCache cache; private readonly ColorDistanceCache cache;
private readonly Configuration configuration;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> struct. /// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="palette">The color palette to map from.</param> /// <param name="palette">The color palette to map from.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory<TPixel> palette) public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory<TPixel> palette)
{ {
this.configuration = configuration;
this.Palette = palette; this.Palette = palette;
this.rgbaPalette = new Rgba32[palette.Length]; this.rgbaPalette = new Rgba32[palette.Length];
this.cache = new ColorDistanceCache(configuration.MemoryAllocator); this.cache = new ColorDistanceCache(configuration.MemoryAllocator);
@ -46,6 +47,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
get; get;
[MethodImpl(InliningOptions.ShortMethod)]
private set;
} }
/// <summary> /// <summary>
@ -72,6 +76,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return index; return index;
} }
/// <summary>
/// Clears the map, resetting it to use the given palette.
/// </summary>
/// <param name="palette">The color palette to map from.</param>
public void Clear(ReadOnlyMemory<TPixel> palette)
{
this.Palette = palette;
this.rgbaPalette = new Rgba32[palette.Length];
PixelOperations<TPixel>.Instance.ToRgba32(this.configuration, this.Palette.Span, this.rgbaPalette);
this.cache.Clear();
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match) private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match)
{ {
@ -177,6 +193,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return match > -1; return match > -1;
} }
/// <summary>
/// Clears the cache resetting each entry to empty.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Clear() => this.table.GetSpan().Fill(-1);
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static int GetPaletteIndex(int r, int g, int b, int a) private static int GetPaletteIndex(int r, int g, int b, int a)
=> (r << ((IndexBits << 1) + IndexAlphaBits)) => (r << ((IndexBits << 1) + IndexAlphaBits))

21
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs

@ -25,7 +25,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private IMemoryOwner<TPixel> paletteOwner; private IMemoryOwner<TPixel> paletteOwner;
private ReadOnlyMemory<TPixel> palette; private ReadOnlyMemory<TPixel> palette;
private EuclideanPixelMap<TPixel> pixelMap; private EuclideanPixelMap<TPixel> pixelMap;
private bool pixelMapHasValue;
private readonly bool isDithering; private readonly bool isDithering;
private bool isDisposed; private bool isDisposed;
@ -48,7 +47,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.octree = new Octree(this.bitDepth); this.octree = new Octree(this.bitDepth);
this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean); this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean);
this.pixelMap = default; this.pixelMap = default;
this.pixelMapHasValue = false;
this.palette = default; this.palette = default;
this.isDithering = !(this.Options.Dither is null); this.isDithering = !(this.Options.Dither is null);
this.isDisposed = false; this.isDisposed = false;
@ -112,15 +110,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.octree.Palletize(paletteSpan, max, ref paletteIndex); this.octree.Palletize(paletteSpan, max, ref paletteIndex);
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length); ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length);
// When called by QuantizerUtilities.BuildPalette this prevents // When called multiple times by QuantizerUtilities.BuildPalette
// mutiple instances of the map being created but not disposed. // this prevents memory churn caused by reallocation.
if (this.pixelMapHasValue) if (this.pixelMap is null)
{ {
this.pixelMap.Dispose(); this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
}
else
{
this.pixelMap.Clear(result);
} }
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
this.pixelMapHasValue = true;
this.palette = result; this.palette = result;
} }
@ -153,9 +153,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (!this.isDisposed) if (!this.isDisposed)
{ {
this.isDisposed = true; this.isDisposed = true;
this.paletteOwner.Dispose(); this.paletteOwner?.Dispose();
this.paletteOwner = null; this.paletteOwner = null;
this.pixelMap.Dispose(); this.pixelMap?.Dispose();
this.pixelMap = null;
} }
} }

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

@ -58,9 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
var palette = new TPixel[length]; var palette = new TPixel[length];
Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan());
return new PaletteQuantizer<TPixel>(configuration, options, palette);
var pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette);
return new PaletteQuantizer<TPixel>(configuration, options, pixelMap, false);
} }
} }
} }

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

@ -16,32 +16,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
internal struct PaletteQuantizer<TPixel> : IQuantizer<TPixel> internal struct PaletteQuantizer<TPixel> : IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly EuclideanPixelMap<TPixel> pixelMap; private EuclideanPixelMap<TPixel> pixelMap;
private readonly bool leaveMap;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> struct. /// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> struct.
/// </summary> /// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="options">The quantizer options defining quantization rules.</param> /// <param name="options">The quantizer options defining quantization rules.</param>
/// <param name="pixelMap">The pixel map for looking up color matches from a predefined palette.</param> /// <param name="palette">The palette to use.</param>
/// <param name="leaveMap">
/// <see langword="true"/> to leave the pixel map undisposed after disposing the <see cref="PaletteQuantizer{TPixel}"/> object; otherwise, <see langword="false"/>.
/// </param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public PaletteQuantizer( public PaletteQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory<TPixel> palette)
Configuration configuration,
QuantizerOptions options,
EuclideanPixelMap<TPixel> pixelMap,
bool leaveMap)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
this.Configuration = configuration; this.Configuration = configuration;
this.Options = options; this.Options = options;
this.pixelMap = pixelMap; this.pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette);
this.leaveMap = leaveMap;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -72,10 +63,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
if (!this.leaveMap) this.pixelMap?.Dispose();
{ this.pixelMap = null;
this.pixelMap.Dispose();
}
} }
} }
} }

76
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs

@ -72,7 +72,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private int maxColors; private int maxColors;
private readonly Box[] colorCube; private readonly Box[] colorCube;
private EuclideanPixelMap<TPixel> pixelMap; private EuclideanPixelMap<TPixel> pixelMap;
private bool pixelMapHasValue;
private readonly bool isDithering; private readonly bool isDithering;
private bool isDisposed; private bool isDisposed;
@ -97,7 +96,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.colorCube = new Box[this.maxColors]; this.colorCube = new Box[this.maxColors];
this.isDisposed = false; this.isDisposed = false;
this.pixelMap = default; this.pixelMap = default;
this.pixelMapHasValue = false;
this.palette = default; this.palette = default;
this.isDithering = this.isDithering = !(this.Options.Dither is null); this.isDithering = this.isDithering = !(this.Options.Dither is null);
} }
@ -147,15 +145,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length); ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length);
if (this.isDithering) if (this.isDithering)
{ {
// When called by QuantizerUtilities.BuildPalette this prevents // When called multiple times by QuantizerUtilities.BuildPalette
// mutiple instances of the map being created but not disposed. // this prevents memory churn caused by reallocation.
if (this.pixelMapHasValue) if (this.pixelMap is null)
{ {
this.pixelMap.Dispose(); this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
}
else
{
this.pixelMap.Clear(result);
} }
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
this.pixelMapHasValue = true;
} }
this.palette = result; this.palette = result;
@ -201,7 +200,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.momentsOwner = null; this.momentsOwner = null;
this.tagsOwner = null; this.tagsOwner = null;
this.paletteOwner = null; this.paletteOwner = null;
this.pixelMap.Dispose(); this.pixelMap?.Dispose();
this.pixelMap = null;
} }
} }
@ -215,16 +215,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns>The index.</returns> /// <returns>The index.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static int GetPaletteIndex(int r, int g, int b, int a) private static int GetPaletteIndex(int r, int g, int b, int a)
{ => (r << ((IndexBits * 2) + IndexAlphaBits))
return (r << ((IndexBits * 2) + IndexAlphaBits)) + (r << (IndexBits + IndexAlphaBits + 1))
+ (r << (IndexBits + IndexAlphaBits + 1)) + (g << (IndexBits + IndexAlphaBits))
+ (g << (IndexBits + IndexAlphaBits)) + (r << (IndexBits * 2))
+ (r << (IndexBits * 2)) + (r << (IndexBits + 1))
+ (r << (IndexBits + 1)) + (g << IndexBits)
+ (g << IndexBits) + ((r + g + b) << IndexAlphaBits)
+ ((r + g + b) << IndexAlphaBits) + r + g + b + a;
+ r + g + b + a;
}
/// <summary> /// <summary>
/// Computes sum over a box of any given statistic. /// Computes sum over a box of any given statistic.
@ -233,24 +231,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="moments">The moment.</param> /// <param name="moments">The moment.</param>
/// <returns>The result.</returns> /// <returns>The result.</returns>
private static Moment Volume(ref Box cube, ReadOnlySpan<Moment> moments) private static Moment Volume(ref Box cube, ReadOnlySpan<Moment> moments)
{ => moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)]
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.BMax, cube.AMin)] - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)]
- 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.GMax, cube.BMin, cube.AMin)] - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)]
- 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.BMax, cube.AMin)] + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)]
+ moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)]
- 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.AMax)] + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)]
+ 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.AMax)] - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)]
- 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.AMax)] - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)]
- 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.AMax)] + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
+ moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)];
}
/// <summary> /// <summary>
/// Computes part of Volume(cube, moment) that doesn't depend on RMax, GMax, BMax, or AMax (depending on direction). /// 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 int Volume;
/// <inheritdoc/> /// <inheritdoc/>
public readonly override bool Equals(object obj) public override readonly bool Equals(object obj)
=> obj is Box box => obj is Box box
&& this.Equals(box); && this.Equals(box);
@ -852,7 +848,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
&& this.Volume == other.Volume; && this.Volume == other.Volume;
/// <inheritdoc/> /// <inheritdoc/>
public readonly override int GetHashCode() public override readonly int GetHashCode()
{ {
HashCode hash = default; HashCode hash = default;
hash.Add(this.RMin); hash.Add(this.RMin);

Loading…
Cancel
Save