Browse Source

Dump progress so far

pull/1138/head
James Jackson-South 6 years ago
parent
commit
d113c787fd
  1. 2
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  2. 3
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  3. 54
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  4. 2
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  5. 2
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  6. 2
      src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
  7. 6
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  8. 33
      src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
  9. 18
      src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs
  10. 6
      src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs
  11. 61
      src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs
  12. 56
      src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs
  13. 16
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  14. 2
      src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs
  15. 18
      src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs
  16. 56
      src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
  17. 14
      tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs
  18. 2
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
  19. 8
      tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs

2
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -339,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
using IFrameQuantizer<TPixel> quantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration); using IFrameQuantizer<TPixel> quantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
using QuantizedFrame<TPixel> quantized = quantizer.QuantizeFrame(image, image.Bounds()); using QuantizedFrame<TPixel> quantized = quantizer.QuantizeFrame(image, image.Bounds());
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span; ReadOnlySpan<TPixel> quantizedColors = quantized.Palette;
var color = default(Rgba32); var color = default(Rgba32);
// TODO: Use bulk conversion here for better perf // TODO: Use bulk conversion here for better perf

3
src/ImageSharp/Formats/Gif/GifEncoder.cs

@ -4,6 +4,7 @@
using System.IO; using System.IO;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Gif namespace SixLabors.ImageSharp.Formats.Gif
@ -17,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Gets or sets the quantizer for reducing the color count. /// Gets or sets the quantizer for reducing the color count.
/// Defaults to the <see cref="OctreeQuantizer"/> /// Defaults to the <see cref="OctreeQuantizer"/>
/// </summary> /// </summary>
public IQuantizer Quantizer { get; set; } = new OctreeQuantizer(); public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree;
/// <summary> /// <summary>
/// Gets or sets the color table mode: Global or local. /// Gets or sets the color table mode: Global or local.

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

@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
} }
// Clean up. // Clean up.
quantized?.Dispose(); quantized.Dispose();
// TODO: Write extension etc // TODO: Write extension etc
stream.WriteByte(GifConstants.EndIntroducer); stream.WriteByte(GifConstants.EndIntroducer);
@ -142,11 +142,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
} }
else else
{ {
using (var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, quantized.Palette)) using var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, quantized.Palette);
using (QuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds())) using QuantizedFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
{ this.WriteImageData(paletteQuantized, stream);
this.WriteImageData(paletteQuantized, stream);
}
} }
} }
} }
@ -156,8 +154,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
ImageFrame<TPixel> previousFrame = null; ImageFrame<TPixel> previousFrame = null;
GifFrameMetadata previousMeta = null; GifFrameMetadata previousMeta = null;
foreach (ImageFrame<TPixel> frame in image.Frames) for (int i = 0; i < image.Frames.Count; i++)
{ {
ImageFrame<TPixel> frame = image.Frames[i];
ImageFrameMetadata metadata = frame.Metadata; ImageFrameMetadata metadata = frame.Metadata;
GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); GifFrameMetadata frameMetadata = metadata.GetGifMetadata();
if (quantized is null) if (quantized is null)
@ -173,17 +172,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
MaxColors = frameMetadata.ColorTableLength MaxColors = frameMetadata.ColorTableLength
}; };
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, options)) using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, options);
{ quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
} }
else else
{ {
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration)) using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration);
{ quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
}
} }
} }
@ -193,7 +188,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.WriteColorTable(quantized, stream); this.WriteColorTable(quantized, stream);
this.WriteImageData(quantized, stream); this.WriteImageData(quantized, stream);
quantized?.Dispose(); quantized.Dispose();
quantized = null; // So next frame can regenerate it quantized = null; // So next frame can regenerate it
previousFrame = frame; previousFrame = frame;
previousMeta = frameMetadata; previousMeta = frameMetadata;
@ -219,7 +214,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{ {
Span<Rgba32> rgbaSpan = rgbaBuffer.GetSpan(); Span<Rgba32> rgbaSpan = rgbaBuffer.GetSpan();
ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan); ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan);
PixelOperations<TPixel>.Instance.ToRgba32(this.configuration, quantized.Palette.Span, rgbaSpan); PixelOperations<TPixel>.Instance.ToRgba32(this.configuration, quantized.Palette, rgbaSpan);
for (int i = quantized.Palette.Length - 1; i >= 0; i--) for (int i = quantized.Palette.Length - 1; i >= 0; i--)
{ {
@ -391,7 +386,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
/// <param name="extension">The extension to write to the stream.</param> /// <param name="extension">The extension to write to the stream.</param>
/// <param name="stream">The stream to write to.</param> /// <param name="stream">The stream to write to.</param>
public void WriteExtension(IGifExtension extension, Stream stream) private void WriteExtension<TGifExtension>(TGifExtension extension, Stream stream)
where TGifExtension : struct, IGifExtension
{ {
this.buffer[0] = GifConstants.ExtensionIntroducer; this.buffer[0] = GifConstants.ExtensionIntroducer;
this.buffer[1] = extension.Label; this.buffer[1] = extension.Label;
@ -444,15 +440,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3; int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3;
int pixelCount = image.Palette.Length; int pixelCount = image.Palette.Length;
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength);
{ PixelOperations<TPixel>.Instance.ToRgb24Bytes(
PixelOperations<TPixel>.Instance.ToRgb24Bytes( this.configuration,
this.configuration, image.Palette,
image.Palette.Span, colorTable.GetSpan(),
colorTable.GetSpan(), pixelCount);
pixelCount); stream.Write(colorTable.Array, 0, colorTableLength);
stream.Write(colorTable.Array, 0, colorTableLength);
}
} }
/// <summary> /// <summary>
@ -464,10 +458,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void WriteImageData<TPixel>(QuantizedFrame<TPixel> image, Stream stream) private void WriteImageData<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth)) using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth);
{ encoder.Encode(image.GetPixelSpan(), stream);
encoder.Encode(image.GetPixelSpan(), stream);
}
} }
} }
} }

2
src/ImageSharp/Formats/Gif/LzwEncoder.cs

@ -274,7 +274,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
ent = this.NextPixel(indexedPixels); ent = this.NextPixel(indexedPixels);
// TODO: PERF: It looks likt hshift could be calculated once statically. // TODO: PERF: It looks like hshift could be calculated once statically.
hshift = 0; hshift = 0;
for (fcode = this.hsize; fcode < 65536; fcode *= 2) for (fcode = this.hsize; fcode < 65536; fcode *= 2)
{ {

2
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -555,7 +555,7 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
// Grab the palette and write it to the stream. // Grab the palette and write it to the stream.
ReadOnlySpan<TPixel> palette = quantized.Palette.Span; ReadOnlySpan<TPixel> palette = quantized.Palette;
int paletteLength = Math.Min(palette.Length, 256); int paletteLength = Math.Min(palette.Length, 256);
int colorTableLength = paletteLength * 3; int colorTableLength = paletteLength * 3;
bool anyAlpha = false; bool anyAlpha = false;

2
src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs

@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IFrameQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
ReadOnlySpan<TPixel> paletteSpan = destination.Palette.Span; ReadOnlySpan<TPixel> paletteSpan = destination.Palette;
int offsetY = bounds.Top; int offsetY = bounds.Top;
int offsetX = bounds.Left; int offsetX = bounds.Left;
float scale = quantizer.Options.DitherScale; float scale = quantizer.Options.DitherScale;

6
src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs

@ -203,7 +203,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly QuantizedFrame<TPixel> destination; private readonly QuantizedFrame<TPixel> destination;
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly ReadOnlyMemory<TPixel> palette;
private readonly int bitDepth; private readonly int bitDepth;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -219,14 +218,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
this.source = source; this.source = source;
this.destination = destination; this.destination = destination;
this.bounds = bounds; this.bounds = bounds;
this.palette = destination.Palette; this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(destination.Palette.Length);
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(destination.Palette.Span.Length);
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows) public void Invoke(in RowInterval rows)
{ {
ReadOnlySpan<TPixel> paletteSpan = this.palette.Span; ReadOnlySpan<TPixel> paletteSpan = this.destination.Palette;
int offsetY = this.bounds.Top; int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left; int offsetX = this.bounds.Left;
float scale = this.quantizer.Options.DitherScale; float scale = this.quantizer.Options.DitherScale;

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

@ -10,7 +10,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
/// <summary> /// <summary>
/// Gets the closest color to the supplied color based upon the Eucladean distance. /// Gets the closest color to the supplied color based upon the Euclidean distance.
/// TODO: Expose this somehow. /// TODO: Expose this somehow.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -62,18 +62,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ReadOnlySpan<TPixel> paletteSpan = this.Palette.Span; ReadOnlySpan<TPixel> paletteSpan = this.Palette.Span;
// Check if the color is in the lookup table // Check if the color is in the lookup table
if (this.distanceCache.TryGetValue(color, out int index)) if (!this.distanceCache.TryGetValue(color, out int index))
{ {
match = paletteSpan[index]; return this.GetClosestColorSlow(color, paletteSpan, out match);
return index;
} }
return this.GetClosestColorSlow(color, paletteSpan, out match); match = paletteSpan[index];
return index;
} }
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode() => this.vectorCache.GetHashCode();
=> this.vectorCache.GetHashCode();
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private int GetClosestColorSlow(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match) private int GetClosestColorSlow(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
@ -88,17 +87,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Vector4 candidate = this.vectorCache[i]; Vector4 candidate = this.vectorCache[i];
float distance = Vector4.DistanceSquared(vector, candidate); float distance = Vector4.DistanceSquared(vector, candidate);
if (!(distance < leastDistance))
{
continue;
}
// Less than... assign. // Less than... assign.
if (distance < leastDistance) index = i;
leastDistance = distance;
// And if it's an exact match, exit the loop
if (distance == 0)
{ {
index = i; break;
leastDistance = distance;
// And if it's an exact match, exit the loop
if (distance == 0)
{
break;
}
} }
} }

18
src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerExtensions.cs

@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
/// <typeparam name="TFrameQuantizer">The type of frame quantizer.</typeparam> /// <typeparam name="TFrameQuantizer">The type of frame quantizer.</typeparam>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="quantizer">The frame </param> /// <param name="quantizer">The frame quantizer.</param>
/// <param name="source">The source image frame to quantize.</param> /// <param name="source">The source image frame to quantize.</param>
/// <param name="bounds">The bounds within the frame to quantize.</param> /// <param name="bounds">The bounds within the frame to quantize.</param>
/// <returns> /// <returns>
@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
var interest = Rectangle.Intersect(source.Bounds(), bounds); var interest = Rectangle.Intersect(source.Bounds(), bounds);
// Collect the palette. Required before the second pass runs. // Collect the palette. Required before the second pass runs.
ReadOnlyMemory<TPixel> palette = quantizer.BuildPalette(source, interest); ReadOnlySpan<TPixel> palette = quantizer.BuildPalette(source, interest);
MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator; MemoryAllocator memoryAllocator = quantizer.Configuration.MemoryAllocator;
var quantizedFrame = new QuantizedFrame<TPixel>(memoryAllocator, interest.Width, interest.Height, palette); var quantizedFrame = new QuantizedFrame<TPixel>(memoryAllocator, interest.Width, interest.Height, palette);
@ -49,10 +49,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
else else
{ {
// We clone the image as we don't want to alter the original via error diffusion based dithering. // We clone the image as we don't want to alter the original via error diffusion based dithering.
using (ImageFrame<TPixel> clone = source.Clone()) using ImageFrame<TPixel> clone = source.Clone();
{ SecondPass(ref quantizer, clone, quantizedFrame, interest);
SecondPass(ref quantizer, clone, quantizedFrame, interest);
}
} }
return quantizedFrame; return quantizedFrame;
@ -71,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (dither is null) if (dither is null)
{ {
var operation = new RowIntervalOperation<TFrameQuantizer, TPixel>(quantizer, source, destination, bounds); var operation = new RowIntervalOperation<TFrameQuantizer, TPixel>(ref quantizer, source, destination, bounds);
ParallelRowIterator.IterateRows( ParallelRowIterator.IterateRows(
quantizer.Configuration, quantizer.Configuration,
bounds, bounds,
@ -91,11 +89,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private readonly ImageFrame<TPixel> source; private readonly ImageFrame<TPixel> source;
private readonly QuantizedFrame<TPixel> destination; private readonly QuantizedFrame<TPixel> destination;
private readonly Rectangle bounds; private readonly Rectangle bounds;
private readonly ReadOnlyMemory<TPixel> palette;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation( public RowIntervalOperation(
in TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
QuantizedFrame<TPixel> destination, QuantizedFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
@ -104,13 +101,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.source = source; this.source = source;
this.destination = destination; this.destination = destination;
this.bounds = bounds; this.bounds = bounds;
this.palette = destination.Palette;
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows) public void Invoke(in RowInterval rows)
{ {
ReadOnlySpan<TPixel> paletteSpan = this.palette.Span; ReadOnlySpan<TPixel> paletteSpan = this.destination.Palette;
int offsetY = this.bounds.Top; int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left; int offsetX = this.bounds.Left;

6
src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs

@ -38,8 +38,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
/// <param name="source">The source image frame.</param> /// <param name="source">The source image frame.</param>
/// <param name="bounds">The region of interest bounds.</param> /// <param name="bounds">The region of interest bounds.</param>
/// <returns>The <see cref="ReadOnlyMemory{TPixel}"/> palette.</returns> /// <returns>The <see cref="ReadOnlySpan{TPixel}"/> palette.</returns>
ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds); ReadOnlySpan<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds);
/// <summary> /// <summary>
/// Returns the index and color from the quantized palette corresponding to the given color. /// Returns the index and color from the quantized palette corresponding to the given color.
@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="palette">The output color palette.</param> /// <param name="palette">The output color palette.</param>
/// <param name="match">The matched color.</param> /// <param name="match">The matched color.</param>
/// <returns>The <see cref="byte"/> index.</returns> /// <returns>The <see cref="byte"/> index.</returns>
public byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match); byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match);
// TODO: Enable bulk operations. // TODO: Enable bulk operations.
// void GetQuantizedColors(ReadOnlySpan<TPixel> colors, ReadOnlySpan<TPixel> palette, Span<byte> indices, Span<TPixel> matches); // void GetQuantizedColors(ReadOnlySpan<TPixel> colors, ReadOnlySpan<TPixel> palette, Span<byte> indices, Span<TPixel> matches);

61
src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs

@ -3,7 +3,6 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
@ -22,8 +21,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
private readonly int colors; private readonly int colors;
private readonly Octree octree; private readonly Octree octree;
private IMemoryOwner<TPixel> palette;
private EuclideanPixelMap<TPixel> pixelMap; private EuclideanPixelMap<TPixel> pixelMap;
private readonly bool isDithering; private readonly bool isDithering;
private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OctreeFrameQuantizer{TPixel}"/> struct. /// Initializes a new instance of the <see cref="OctreeFrameQuantizer{TPixel}"/> struct.
@ -41,8 +42,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.colors = this.Options.MaxColors; this.colors = this.Options.MaxColors;
this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8)); this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8));
this.palette = configuration.MemoryAllocator.Allocate<TPixel>(this.colors, AllocationOptions.Clean);
this.pixelMap = default; this.pixelMap = default;
this.isDithering = !(this.Options.Dither is null); this.isDithering = !(this.Options.Dither is null);
this.isDisposed = false;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -53,12 +56,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds) public readonly QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> FrameQuantizerExtensions.QuantizeFrame(ref this, source, bounds); => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds);
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds) public ReadOnlySpan<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
{ {
using IMemoryOwner<Rgba32> buffer = this.Configuration.MemoryAllocator.Allocate<Rgba32>(bounds.Width); using IMemoryOwner<Rgba32> buffer = this.Configuration.MemoryAllocator.Allocate<Rgba32>(bounds.Width);
Span<Rgba32> bufferSpan = buffer.GetSpan(); Span<Rgba32> bufferSpan = buffer.GetSpan();
@ -78,15 +81,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
} }
TPixel[] palette = this.octree.Palletize(this.colors); Span<TPixel> paletteSpan = this.palette.GetSpan();
this.pixelMap = new EuclideanPixelMap<TPixel>(palette); this.octree.Palletize(paletteSpan, this.colors);
return palette; // TODO: Cannot make method readonly due to this line.
this.pixelMap = new EuclideanPixelMap<TPixel>(this.palette.Memory);
return paletteSpan;
} }
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match) public readonly byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
{ {
// Octree only maps the RGB component of a color // Octree only maps the RGB component of a color
// so cannot tell the difference between a fully transparent // so cannot tell the difference between a fully transparent
@ -104,6 +110,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
if (!this.isDisposed)
{
this.isDisposed = true;
this.palette.Dispose();
this.palette = null;
}
} }
/// <summary> /// <summary>
@ -116,14 +128,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
private static readonly byte[] Mask = new byte[] private static readonly byte[] Mask = new byte[]
{ {
0b10000000, 0b10000000, 0b1000000, 0b100000, 0b10000, 0b1000, 0b100, 0b10, 0b1
0b1000000,
0b100000,
0b10000,
0b1000,
0b100,
0b10,
0b1
}; };
/// <summary> /// <summary>
@ -216,26 +221,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary> /// <summary>
/// Convert the nodes in the Octree to a palette with a maximum of colorCount colors /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors
/// </summary> /// </summary>
/// <param name="palette">The palette to fill.</param>
/// <param name="colorCount">The maximum number of colors</param> /// <param name="colorCount">The maximum number of colors</param>
/// <returns>
/// An <see cref="List{TPixel}"/> with the palletized colors
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public TPixel[] Palletize(int colorCount) public void Palletize(Span<TPixel> palette, int colorCount)
{ {
while (this.Leaves > colorCount - 1) while (this.Leaves > colorCount - 1)
{ {
this.Reduce(); this.Reduce();
} }
// Now palletize the nodes
var palette = new TPixel[colorCount];
int paletteIndex = 0; int paletteIndex = 0;
this.root.ConstructPalette(palette, ref paletteIndex); this.root.ConstructPalette(palette, ref paletteIndex);
// And return the palette
return palette;
} }
/// <summary> /// <summary>
@ -437,12 +434,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="palette">The palette</param> /// <param name="palette">The palette</param>
/// <param name="index">The current palette index</param> /// <param name="index">The current palette index</param>
[MethodImpl(InliningOptions.ColdPath)] [MethodImpl(InliningOptions.ColdPath)]
public void ConstructPalette(TPixel[] palette, ref int index) public void ConstructPalette(Span<TPixel> palette, ref int index)
{ {
if (this.leaf) if (this.leaf)
{ {
// Set the color of the palette entry // Set the color of the palette entry
var vector = Vector3.Clamp(new Vector3(this.red, this.green, this.blue) / this.pixelCount, Vector3.Zero, new Vector3(255)); var vector = Vector3.Clamp(
new Vector3(this.red, this.green, this.blue) / this.pixelCount,
Vector3.Zero,
new Vector3(255));
TPixel pixel = default; TPixel pixel = default;
pixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, byte.MaxValue)); pixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, byte.MaxValue));
palette[index] = pixel; palette[index] = pixel;
@ -516,8 +517,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
int shift = 7 - level; int shift = 7 - level;
byte mask = Mask[level]; byte mask = Mask[level];
return ((color.R & mask) >> shift) return ((color.R & mask) >> shift)
| ((color.G & mask) >> (shift - 1)) | ((color.G & mask) >> (shift - 1))
| ((color.B & mask) >> (shift - 2)); | ((color.B & mask) >> (shift - 2));
} }
/// <summary> /// <summary>

56
src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
@ -15,8 +17,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
internal struct PaletteFrameQuantizer<TPixel> : IFrameQuantizer<TPixel> internal struct PaletteFrameQuantizer<TPixel> : IFrameQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly ReadOnlyMemory<TPixel> palette; private IMemoryOwner<TPixel> paletteOwner;
private readonly EuclideanPixelMap<TPixel> pixelMap; private readonly EuclideanPixelMap<TPixel> pixelMap;
private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PaletteFrameQuantizer{TPixel}"/> struct. /// Initializes a new instance of the <see cref="PaletteFrameQuantizer{TPixel}"/> struct.
@ -25,7 +28,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="options">The quantizer options defining quantization rules.</param> /// <param name="options">The quantizer options defining quantization rules.</param>
/// <param name="colors">A <see cref="ReadOnlyMemory{TPixel}"/> containing all colors in the palette.</param> /// <param name="colors">A <see cref="ReadOnlyMemory{TPixel}"/> containing all colors in the palette.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public PaletteFrameQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlyMemory<TPixel> colors) public PaletteFrameQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlySpan<Color> colors)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
@ -33,8 +36,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Configuration = configuration; this.Configuration = configuration;
this.Options = options; this.Options = options;
this.palette = colors; int maxLength = Math.Min(colors.Length, options.MaxColors);
this.pixelMap = new EuclideanPixelMap<TPixel>(colors); this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(maxLength);
Color.ToPixel(configuration, colors, this.paletteOwner.GetSpan());
this.pixelMap = new EuclideanPixelMap<TPixel>(this.paletteOwner.Memory);
this.isDisposed = false;
}
/// <summary>
/// Initializes a new instance of the <see cref="PaletteFrameQuantizer{TPixel}"/> struct.
/// </summary>
/// <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="palette">A <see cref="ReadOnlyMemory{TPixel}"/> containing all colors in the palette.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public PaletteFrameQuantizer(Configuration configuration, QuantizerOptions options, ReadOnlySpan<TPixel> palette)
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options));
this.Configuration = configuration;
this.Options = options;
int maxLength = Math.Min(palette.Length, options.MaxColors);
this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(maxLength);
palette.CopyTo(this.paletteOwner.GetSpan());
this.pixelMap = new EuclideanPixelMap<TPixel>(this.paletteOwner.Memory);
this.isDisposed = false;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -45,22 +75,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds) public readonly QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> FrameQuantizerExtensions.QuantizeFrame(ref this, source, bounds); => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds);
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds) public readonly ReadOnlySpan<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
=> this.palette; => this.paletteOwner.GetSpan();
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match) public readonly byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
=> (byte)this.pixelMap.GetClosestColor(color, out match); => (byte)this.pixelMap.GetClosestColor(color, out match);
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
if (this.isDisposed)
{
return;
}
this.isDisposed = true;
this.paletteOwner.Dispose();
this.paletteOwner = null;
} }
} }
} }

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

@ -11,6 +11,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
public class PaletteQuantizer : IQuantizer public class PaletteQuantizer : IQuantizer
{ {
private readonly ReadOnlyMemory<Color> palette;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer"/> class. /// Initializes a new instance of the <see cref="PaletteQuantizer"/> class.
/// </summary> /// </summary>
@ -30,15 +32,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette));
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
this.Palette = palette; this.palette = palette;
this.Options = options; this.Options = options;
} }
/// <summary>
/// Gets the color palette.
/// </summary>
public ReadOnlyMemory<Color> Palette { get; }
/// <inheritdoc /> /// <inheritdoc />
public QuantizerOptions Options { get; } public QuantizerOptions Options { get; }
@ -52,12 +49,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
return new PaletteFrameQuantizer<TPixel>(configuration, options, this.palette.Span);
int length = Math.Min(this.Palette.Span.Length, options.MaxColors);
var palette = new TPixel[length];
Color.ToPixel(configuration, this.Palette.Span, palette.AsSpan());
return new PaletteFrameQuantizer<TPixel>(configuration, options, palette);
} }
} }
} }

2
src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs

@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public void Invoke(in RowInterval rows) public void Invoke(in RowInterval rows)
{ {
ReadOnlySpan<byte> quantizedPixelSpan = this.quantized.GetPixelSpan(); ReadOnlySpan<byte> quantizedPixelSpan = this.quantized.GetPixelSpan();
ReadOnlySpan<TPixel> paletteSpan = this.quantized.Palette.Span; ReadOnlySpan<TPixel> paletteSpan = this.quantized.Palette;
int offsetY = this.bounds.Top; int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left; int offsetX = this.bounds.Left;
int width = this.bounds.Width; int width = this.bounds.Width;

18
src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs

@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public sealed class QuantizedFrame<TPixel> : IDisposable public sealed class QuantizedFrame<TPixel> : IDisposable
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private IMemoryOwner<TPixel> palette;
private IMemoryOwner<byte> pixels; private IMemoryOwner<byte> pixels;
private bool isDisposed; private bool isDisposed;
@ -26,15 +27,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="width">The image width.</param> /// <param name="width">The image width.</param>
/// <param name="height">The image height.</param> /// <param name="height">The image height.</param>
/// <param name="palette">The color palette.</param> /// <param name="palette">The color palette.</param>
internal QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, ReadOnlyMemory<TPixel> palette) internal QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, ReadOnlySpan<TPixel> palette)
{ {
Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height)); Guard.MustBeGreaterThan(height, 0, nameof(height));
this.Width = width; this.Width = width;
this.Height = height; this.Height = height;
this.Palette = palette;
this.pixels = memoryAllocator.AllocateManagedByteBuffer(width * height, AllocationOptions.Clean); this.pixels = memoryAllocator.AllocateManagedByteBuffer(width * height, AllocationOptions.Clean);
this.palette = memoryAllocator.Allocate<TPixel>(palette.Length);
palette.CopyTo(this.palette.GetSpan());
} }
/// <summary> /// <summary>
@ -50,7 +53,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <summary> /// <summary>
/// Gets the color palette of this <see cref="QuantizedFrame{TPixel}"/>. /// Gets the color palette of this <see cref="QuantizedFrame{TPixel}"/>.
/// </summary> /// </summary>
public ReadOnlyMemory<TPixel> Palette { get; private set; } public ReadOnlySpan<TPixel> Palette
{
[MethodImpl(InliningOptions.ShortMethod)]
get { return this.palette.GetSpan(); }
}
/// <summary> /// <summary>
/// Gets the pixels of this <see cref="QuantizedFrame{TPixel}"/>. /// Gets the pixels of this <see cref="QuantizedFrame{TPixel}"/>.
@ -72,15 +79,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
if (this.isDisposed) if (!this.isDisposed)
{ {
return; return;
} }
this.isDisposed = true; this.isDisposed = true;
this.pixels?.Dispose(); this.pixels?.Dispose();
this.palette?.Dispose();
this.pixels = null; this.pixels = null;
this.Palette = null; this.palette = null;
} }
} }
} }

56
src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs

@ -65,30 +65,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
/// <summary>
/// Color moments.
/// </summary>
private IMemoryOwner<Moment> moments; private IMemoryOwner<Moment> moments;
/// <summary>
/// Color space tag.
/// </summary>
private IMemoryOwner<byte> tag; private IMemoryOwner<byte> tag;
private IMemoryOwner<TPixel> palette;
/// <summary>
/// Maximum allowed color depth
/// </summary>
private int colors; private int colors;
/// <summary>
/// The color cube representing the image palette
/// </summary>
private readonly Box[] colorCube; private readonly Box[] colorCube;
private EuclideanPixelMap<TPixel> pixelMap; private EuclideanPixelMap<TPixel> pixelMap;
private readonly bool isDithering; private readonly bool isDithering;
private bool isDisposed; private bool isDisposed;
/// <summary> /// <summary>
@ -104,10 +87,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Configuration = configuration; this.Configuration = configuration;
this.Options = options; this.Options = options;
this.colors = this.Options.MaxColors;
this.memoryAllocator = this.Configuration.MemoryAllocator; this.memoryAllocator = this.Configuration.MemoryAllocator;
this.moments = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean); this.moments = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean);
this.tag = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean); this.tag = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean);
this.colors = this.Options.MaxColors; this.palette = this.memoryAllocator.Allocate<TPixel>(this.colors, AllocationOptions.Clean);
this.colorCube = new Box[this.colors]; this.colorCube = new Box[this.colors];
this.isDisposed = false; this.isDisposed = false;
this.pixelMap = default; this.pixelMap = default;
@ -122,19 +106,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds) public readonly QuantizedFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> FrameQuantizerExtensions.QuantizeFrame(ref this, source, bounds); => FrameQuantizerExtensions.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds);
/// <inheritdoc/> /// <inheritdoc/>
public ReadOnlyMemory<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds) public ReadOnlySpan<TPixel> BuildPalette(ImageFrame<TPixel> source, Rectangle bounds)
{ {
this.Build3DHistogram(source, bounds); this.Build3DHistogram(source, bounds);
this.Get3DMoments(this.memoryAllocator); this.Get3DMoments(this.memoryAllocator);
this.BuildCube(); this.BuildCube();
var palette = new TPixel[this.colors];
ReadOnlySpan<Moment> momentsSpan = this.moments.GetSpan(); ReadOnlySpan<Moment> momentsSpan = this.moments.GetSpan();
Span<TPixel> paletteSpan = this.palette.GetSpan();
for (int k = 0; k < this.colors; k++) for (int k = 0; k < this.colors; k++)
{ {
this.Mark(ref this.colorCube[k], (byte)k); this.Mark(ref this.colorCube[k], (byte)k);
@ -143,17 +126,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (moment.Weight > 0) if (moment.Weight > 0)
{ {
ref TPixel color = ref palette[k]; ref TPixel color = ref paletteSpan[k];
color.FromScaledVector4(moment.Normalize()); color.FromScaledVector4(moment.Normalize());
} }
} }
this.pixelMap = new EuclideanPixelMap<TPixel>(palette); // TODO: Cannot make methods readonly due to this line.
return palette; this.pixelMap = new EuclideanPixelMap<TPixel>(this.palette.Memory);
return paletteSpan;
} }
/// <inheritdoc/> /// <inheritdoc/>
public byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match) public readonly byte GetQuantizedColor(TPixel color, ReadOnlySpan<TPixel> palette, out TPixel match)
{ {
if (!this.isDithering) if (!this.isDithering)
{ {
@ -177,16 +161,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
if (this.isDisposed) if (!this.isDisposed)
{ {
return; this.isDisposed = true;
this.moments?.Dispose();
this.tag?.Dispose();
this.palette?.Dispose();
this.moments = null;
this.tag = null;
this.palette = null;
} }
this.isDisposed = true;
this.moments?.Dispose();
this.tag?.Dispose();
this.moments = null;
this.tag = null;
} }
/// <summary> /// <summary>

14
tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs

@ -21,6 +21,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
private SDImage bmpDrawing; private SDImage bmpDrawing;
private Image<Rgba32> bmpCore; private Image<Rgba32> bmpCore;
// Try to get as close to System.Drawing's output as possible
private readonly GifEncoder encoder = new GifEncoder
{
Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 })
};
[GlobalSetup] [GlobalSetup]
public void ReadImages() public void ReadImages()
{ {
@ -53,15 +59,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Description = "ImageSharp Gif")] [Benchmark(Description = "ImageSharp Gif")]
public void GifCore() public void GifCore()
{ {
// Try to get as close to System.Drawing's output as possible
var options = new GifEncoder
{
Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 })
};
using (var memoryStream = new MemoryStream()) using (var memoryStream = new MemoryStream())
{ {
this.bmpCore.SaveAsGif(memoryStream, options); this.bmpCore.SaveAsGif(memoryStream, this.encoder);
} }
} }
} }

2
tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs

@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Tests
// Transparent pixels are much more likely to be found at the end of a palette // Transparent pixels are much more likely to be found at the end of a palette
int index = -1; int index = -1;
Rgba32 trans = default; Rgba32 trans = default;
ReadOnlySpan<TPixel> paletteSpan = quantized.Palette.Span; ReadOnlySpan<TPixel> paletteSpan = quantized.Palette;
for (int i = paletteSpan.Length - 1; i >= 0; i--) for (int i = paletteSpan.Length - 1; i >= 0; i--)
{ {
paletteSpan[i].ToRgba32(ref trans); paletteSpan[i].ToRgba32(ref trans);

8
tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.GetPixelSpan().Length); Assert.Equal(1, result.GetPixelSpan().Length);
Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); Assert.Equal(Color.Black, (Color)result.Palette[0]);
Assert.Equal(0, result.GetPixelSpan()[0]); Assert.Equal(0, result.GetPixelSpan()[0]);
} }
@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.GetPixelSpan().Length); Assert.Equal(1, result.GetPixelSpan().Length);
Assert.Equal(default, result.Palette.Span[0]); Assert.Equal(default, result.Palette[0]);
Assert.Equal(0, result.GetPixelSpan()[0]); Assert.Equal(0, result.GetPixelSpan()[0]);
} }
@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
var actualImage = new Image<Rgba32>(1, 256); var actualImage = new Image<Rgba32>(1, 256);
ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span; ReadOnlySpan<Rgba32> paletteSpan = result.Palette;
int paletteCount = result.Palette.Length - 1; int paletteCount = result.Palette.Length - 1;
for (int y = 0; y < actualImage.Height; y++) for (int y = 0; y < actualImage.Height; y++)
{ {
@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization
Assert.Equal(4 * 8, result.Palette.Length); Assert.Equal(4 * 8, result.Palette.Length);
Assert.Equal(256, result.GetPixelSpan().Length); Assert.Equal(256, result.GetPixelSpan().Length);
ReadOnlySpan<Rgba32> paletteSpan = result.Palette.Span; ReadOnlySpan<Rgba32> paletteSpan = result.Palette;
int paletteCount = result.Palette.Length - 1; int paletteCount = result.Palette.Length - 1;
for (int y = 0; y < actualImage.Height; y++) for (int y = 0; y < actualImage.Height; y++)
{ {

Loading…
Cancel
Save