Browse Source

Merge branch 'master' into release/rc-1

pull/1574/head
James Jackson-South 6 years ago
parent
commit
25306636b8
  1. 6
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 4
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  3. 4
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  4. 6
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  5. 29
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  6. 8
      src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
  7. 6
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs
  8. 4
      src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs
  9. 12
      src/ImageSharp/Image{TPixel}.cs
  10. 26
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  11. 92
      src/ImageSharp/Memory/BufferRegion{T}.cs
  12. 2
      src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs
  13. 2
      src/ImageSharp/Processing/Processors/Dithering/IDither.cs
  14. 4
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  15. 107
      src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs
  16. 25
      src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs
  17. 24
      src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs
  18. 8
      src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs
  19. 16
      src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs
  20. 8
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs
  21. 16
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
  22. 8
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  23. 11
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
  24. 4
      src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs
  25. 56
      src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs
  26. 8
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs
  27. 19
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
  28. 4
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  29. 4
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  30. 28
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs
  31. 33
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  32. 10
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs
  33. 32
      tests/ImageSharp.Tests/Memory/BufferAreaTests.cs
  34. 6
      tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs
  35. 6
      tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs
  36. 6
      tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs
  37. 99
      tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs
  38. 16
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
  39. 20
      tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs
  40. 1
      tests/ImageSharp.Tests/TestImages.cs
  41. 3
      tests/Images/Input/Gif/GlobalQuantizationTest.gif

6
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileOctreeQuantizer<TPixel>() private static void AotCompileOctreeQuantizer<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (var test = new OctreeFrameQuantizer<TPixel>(Configuration.Default, new OctreeQuantizer().Options)) using (var test = new OctreeQuantizer<TPixel>(Configuration.Default, new OctreeQuantizer().Options))
{ {
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1); var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
test.QuantizeFrame(frame, frame.Bounds()); test.QuantizeFrame(frame, frame.Bounds());
@ -126,7 +126,7 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileWuQuantizer<TPixel>() private static void AotCompileWuQuantizer<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default, new WuQuantizer().Options)) using (var test = new WuQuantizer<TPixel>(Configuration.Default, new WuQuantizer().Options))
{ {
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1); var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
test.QuantizeFrame(frame, frame.Bounds()); test.QuantizeFrame(frame, frame.Bounds());
@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompilePaletteQuantizer<TPixel>() private static void AotCompilePaletteQuantizer<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (var test = (PaletteFrameQuantizer<TPixel>)new PaletteQuantizer(Array.Empty<Color>()).CreateFrameQuantizer<TPixel>(Configuration.Default)) using (var test = (PaletteQuantizer<TPixel>)new PaletteQuantizer(Array.Empty<Color>()).CreatePixelSpecificQuantizer<TPixel>(Configuration.Default))
{ {
var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1); var frame = new ImageFrame<TPixel>(Configuration.Default, 1, 1);
test.QuantizeFrame(frame, frame.Bounds()); test.QuantizeFrame(frame, frame.Bounds());

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

@ -336,8 +336,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette) private void Write8BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration); using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(image, image.Bounds()); using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span; ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span;
var color = default(Rgba32); var color = default(Rgba32);

4
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -543,8 +543,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
return; return;
} }
BufferArea<TPixel> pixelArea = frame.PixelBuffer.GetArea(this.restoreArea.Value); BufferRegion<TPixel> pixelRegion = frame.PixelBuffer.GetRegion(this.restoreArea.Value);
pixelArea.Clear(); pixelRegion.Clear();
this.restoreArea = null; this.restoreArea = null;
} }

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

@ -25,6 +25,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
public GifColorTableMode? ColorTableMode { get; set; } public GifColorTableMode? ColorTableMode { get; set; }
/// <summary>
/// Gets or sets the <see cref="IPixelSamplingStrategy"/> used for quantization
/// when building a global color table in case of <see cref="GifColorTableMode.Global"/>.
/// </summary>
public IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; set; } = new DefaultPixelSamplingStrategy();
/// <inheritdoc/> /// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream) public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>

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

@ -49,6 +49,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
private int bitDepth; private int bitDepth;
/// <summary>
/// The pixel sampling strategy for global quantization.
/// </summary>
private IPixelSamplingStrategy pixelSamplingStrategy;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class. /// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary> /// </summary>
@ -60,6 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.memoryAllocator = configuration.MemoryAllocator; this.memoryAllocator = configuration.MemoryAllocator;
this.quantizer = options.Quantizer; this.quantizer = options.Quantizer;
this.colorTableMode = options.ColorTableMode; this.colorTableMode = options.ColorTableMode;
this.pixelSamplingStrategy = options.GlobalPixelSamplingStrategy;
} }
/// <summary> /// <summary>
@ -81,9 +87,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Quantize the image returning a palette. // Quantize the image returning a palette.
IndexedImageFrame<TPixel> quantized; IndexedImageFrame<TPixel> quantized;
using (IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration))
using (IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration))
{ {
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); if (useGlobalTable)
{
frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image);
quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
}
else
{
quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds());
}
} }
// Get the number of bits. // Get the number of bits.
@ -154,7 +169,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette); pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette);
} }
using var paletteFrameQuantizer = new PaletteFrameQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap); using var paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap);
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);
} }
@ -184,13 +199,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
MaxColors = frameMetadata.ColorTableLength MaxColors = frameMetadata.ColorTableLength
}; };
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration, options); using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, options);
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
} }
else else
{ {
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(this.configuration); using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
} }
} }

8
src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs

@ -1,6 +1,9 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Gif namespace SixLabors.ImageSharp.Formats.Gif
@ -19,5 +22,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Gets the color table mode: Global or local. /// Gets the color table mode: Global or local.
/// </summary> /// </summary>
GifColorTableMode? ColorTableMode { get; } GifColorTableMode? ColorTableMode { get; }
/// <summary>
/// Gets the <see cref="IPixelSamplingStrategy"/> used for quantization when building a global color table.
/// </summary>
IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; }
} }
} }

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

@ -15,10 +15,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors. /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors.
/// </summary> /// </summary>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void ScaledCopyTo(in BufferArea<float> area, int horizontalScale, int verticalScale) public void ScaledCopyTo(in BufferRegion<float> region, int horizontalScale, int verticalScale)
{ {
ref float areaOrigin = ref area.GetReferenceToOrigin(); ref float areaOrigin = ref region.GetReferenceToOrigin();
this.ScaledCopyTo(ref areaOrigin, area.Stride, horizontalScale, verticalScale); this.ScaledCopyTo(ref areaOrigin, region.Stride, horizontalScale, verticalScale);
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]

4
src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs

@ -77,10 +77,10 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
// Create quantized frame returning the palette and set the bit depth. // Create quantized frame returning the palette and set the bit depth.
using (IFrameQuantizer<TPixel> frameQuantizer = options.Quantizer.CreateFrameQuantizer<TPixel>(image.GetConfiguration())) using (IQuantizer<TPixel> frameQuantizer = options.Quantizer.CreatePixelSpecificQuantizer<TPixel>(image.GetConfiguration()))
{ {
ImageFrame<TPixel> frame = image.Frames.RootFrame; ImageFrame<TPixel> frame = image.Frames.RootFrame;
return frameQuantizer.QuantizeFrame(frame, frame.Bounds()); return frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
} }
} }

12
src/ImageSharp/Image{TPixel}.cs

@ -48,6 +48,18 @@ namespace SixLabors.ImageSharp
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
/// </summary>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="backgroundColor">The color to initialize the pixels with.</param>
public Image(int width, int height, TPixel backgroundColor)
: this(Configuration.Default, width, height, backgroundColor, new ImageMetadata())
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class /// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image. /// with the height and the width of the image.

26
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -80,29 +80,29 @@ namespace SixLabors.ImageSharp.Memory
} }
/// <summary> /// <summary>
/// Return a <see cref="BufferArea{T}"/> to the subarea represented by 'rectangle' /// Return a <see cref="BufferRegion{T}"/> to the subarea represented by 'rectangle'
/// </summary> /// </summary>
/// <typeparam name="T">The element type</typeparam> /// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param> /// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <param name="rectangle">The rectangle subarea</param> /// <param name="rectangle">The rectangle subarea</param>
/// <returns>The <see cref="BufferArea{T}"/></returns> /// <returns>The <see cref="BufferRegion{T}"/></returns>
internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, in Rectangle rectangle) internal static BufferRegion<T> GetRegion<T>(this Buffer2D<T> buffer, in Rectangle rectangle)
where T : struct => where T : unmanaged =>
new BufferArea<T>(buffer, rectangle); new BufferRegion<T>(buffer, rectangle);
internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, int x, int y, int width, int height) internal static BufferRegion<T> GetRegion<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
where T : struct => where T : unmanaged =>
new BufferArea<T>(buffer, new Rectangle(x, y, width, height)); new BufferRegion<T>(buffer, new Rectangle(x, y, width, height));
/// <summary> /// <summary>
/// Return a <see cref="BufferArea{T}"/> to the whole area of 'buffer' /// Return a <see cref="BufferRegion{T}"/> to the whole area of 'buffer'
/// </summary> /// </summary>
/// <typeparam name="T">The element type</typeparam> /// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param> /// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="BufferArea{T}"/></returns> /// <returns>The <see cref="BufferRegion{T}"/></returns>
internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer) internal static BufferRegion<T> GetRegion<T>(this Buffer2D<T> buffer)
where T : struct => where T : unmanaged =>
new BufferArea<T>(buffer); new BufferRegion<T>(buffer);
/// <summary> /// <summary>
/// Returns the size of the buffer. /// Returns the size of the buffer.

92
src/ImageSharp/Memory/BufferArea{T}.cs → src/ImageSharp/Memory/BufferRegion{T}.cs

@ -6,45 +6,48 @@ using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory namespace SixLabors.ImageSharp.Memory
{ {
/// <summary> /// <summary>
/// Represents a rectangular area inside a 2D memory buffer (<see cref="Buffer2D{T}"/>). /// Represents a rectangular region inside a 2D memory buffer (<see cref="Buffer2D{T}"/>).
/// This type is kind-of 2D Span, but it can live on heap.
/// </summary> /// </summary>
/// <typeparam name="T">The element type.</typeparam> /// <typeparam name="T">The element type.</typeparam>
internal readonly struct BufferArea<T> public readonly struct BufferRegion<T>
where T : struct where T : unmanaged
{ {
/// <summary> /// <summary>
/// The rectangle specifying the boundaries of the area in <see cref="DestinationBuffer"/>. /// Initializes a new instance of the <see cref="BufferRegion{T}"/> struct.
/// </summary> /// </summary>
public readonly Rectangle Rectangle; /// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param>
/// <param name="rectangle">The <see cref="Rectangle"/> defining a rectangular area within the buffer.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferArea(Buffer2D<T> destinationBuffer, Rectangle rectangle) public BufferRegion(Buffer2D<T> buffer, Rectangle rectangle)
{ {
DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle)); DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle));
DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle)); DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle));
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, destinationBuffer.Width, nameof(rectangle)); DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, buffer.Width, nameof(rectangle));
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, destinationBuffer.Height, nameof(rectangle)); DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, buffer.Height, nameof(rectangle));
this.DestinationBuffer = destinationBuffer; this.Buffer = buffer;
this.Rectangle = rectangle; this.Rectangle = rectangle;
} }
/// <summary>
/// Initializes a new instance of the <see cref="BufferRegion{T}"/> struct.
/// </summary>
/// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferArea(Buffer2D<T> destinationBuffer) public BufferRegion(Buffer2D<T> buffer)
: this(destinationBuffer, destinationBuffer.FullRectangle()) : this(buffer, buffer.FullRectangle())
{ {
} }
/// <summary> /// <summary>
/// Gets the <see cref="Buffer2D{T}"/> being pointed by this instance. /// Gets the rectangle specifying the boundaries of the area in <see cref="Buffer"/>.
/// </summary> /// </summary>
public Buffer2D<T> DestinationBuffer { get; } public Rectangle Rectangle { get; }
/// <summary> /// <summary>
/// Gets the size of the area. /// Gets the <see cref="Buffer2D{T}"/> being pointed by this instance.
/// </summary> /// </summary>
public Size Size => this.Rectangle.Size; public Buffer2D<T> Buffer { get; }
/// <summary> /// <summary>
/// Gets the width /// Gets the width
@ -57,14 +60,19 @@ namespace SixLabors.ImageSharp.Memory
public int Height => this.Rectangle.Height; public int Height => this.Rectangle.Height;
/// <summary> /// <summary>
/// Gets the pixel stride which is equal to the width of <see cref="DestinationBuffer"/>. /// Gets the pixel stride which is equal to the width of <see cref="Buffer"/>.
/// </summary> /// </summary>
public int Stride => this.DestinationBuffer.Width; public int Stride => this.Buffer.Width;
/// <summary> /// <summary>
/// Gets a value indicating whether the area refers to the entire <see cref="DestinationBuffer"/> /// Gets the size of the area.
/// </summary> /// </summary>
public bool IsFullBufferArea => this.Size == this.DestinationBuffer.Size(); internal Size Size => this.Rectangle.Size;
/// <summary>
/// Gets a value indicating whether the area refers to the entire <see cref="Buffer"/>
/// </summary>
internal bool IsFullBufferArea => this.Size == this.Buffer.Size();
/// <summary> /// <summary>
/// Gets or sets a value at the given index. /// Gets or sets a value at the given index.
@ -72,19 +80,7 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="x">The position inside a row</param> /// <param name="x">The position inside a row</param>
/// <param name="y">The row index</param> /// <param name="y">The row index</param>
/// <returns>The reference to the value</returns> /// <returns>The reference to the value</returns>
public ref T this[int x, int y] => ref this.DestinationBuffer[x + this.Rectangle.X, y + this.Rectangle.Y]; internal ref T this[int x, int y] => ref this.Buffer[x + this.Rectangle.X, y + this.Rectangle.Y];
/// <summary>
/// Gets a reference to the [0,0] element.
/// </summary>
/// <returns>The reference to the [0,0] element</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetReferenceToOrigin()
{
int y = this.Rectangle.Y;
int x = this.Rectangle.X;
return ref this.DestinationBuffer.GetRowSpan(y)[x];
}
/// <summary> /// <summary>
/// Gets a span to row 'y' inside this area. /// Gets a span to row 'y' inside this area.
@ -98,11 +94,11 @@ namespace SixLabors.ImageSharp.Memory
int xx = this.Rectangle.X; int xx = this.Rectangle.X;
int width = this.Rectangle.Width; int width = this.Rectangle.Width;
return this.DestinationBuffer.GetRowSpan(yy).Slice(xx, width); return this.Buffer.GetRowSpan(yy).Slice(xx, width);
} }
/// <summary> /// <summary>
/// Returns a sub-area as <see cref="BufferArea{T}"/>. (Similar to <see cref="Span{T}.Slice(int, int)"/>.) /// Returns a sub-area as <see cref="BufferRegion{T}"/>. (Similar to <see cref="Span{T}.Slice(int, int)"/>.)
/// </summary> /// </summary>
/// <param name="x">The x index at the subarea origin.</param> /// <param name="x">The x index at the subarea origin.</param>
/// <param name="y">The y index at the subarea origin.</param> /// <param name="y">The y index at the subarea origin.</param>
@ -110,19 +106,19 @@ namespace SixLabors.ImageSharp.Memory
/// <param name="height">The desired height of the subarea.</param> /// <param name="height">The desired height of the subarea.</param>
/// <returns>The subarea</returns> /// <returns>The subarea</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferArea<T> GetSubArea(int x, int y, int width, int height) public BufferRegion<T> GetSubArea(int x, int y, int width, int height)
{ {
var rectangle = new Rectangle(x, y, width, height); var rectangle = new Rectangle(x, y, width, height);
return this.GetSubArea(rectangle); return this.GetSubArea(rectangle);
} }
/// <summary> /// <summary>
/// Returns a sub-area as <see cref="BufferArea{T}"/>. (Similar to <see cref="Span{T}.Slice(int, int)"/>.) /// Returns a sub-area as <see cref="BufferRegion{T}"/>. (Similar to <see cref="Span{T}.Slice(int, int)"/>.)
/// </summary> /// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/> specifying the boundaries of the subarea</param> /// <param name="rectangle">The <see cref="Rectangle"/> specifying the boundaries of the subarea</param>
/// <returns>The subarea</returns> /// <returns>The subarea</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferArea<T> GetSubArea(Rectangle rectangle) public BufferRegion<T> GetSubArea(Rectangle rectangle)
{ {
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle)); DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle));
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle)); DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle));
@ -130,15 +126,27 @@ namespace SixLabors.ImageSharp.Memory
int x = this.Rectangle.X + rectangle.X; int x = this.Rectangle.X + rectangle.X;
int y = this.Rectangle.Y + rectangle.Y; int y = this.Rectangle.Y + rectangle.Y;
rectangle = new Rectangle(x, y, rectangle.Width, rectangle.Height); rectangle = new Rectangle(x, y, rectangle.Width, rectangle.Height);
return new BufferArea<T>(this.DestinationBuffer, rectangle); return new BufferRegion<T>(this.Buffer, rectangle);
}
/// <summary>
/// Gets a reference to the [0,0] element.
/// </summary>
/// <returns>The reference to the [0,0] element</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ref T GetReferenceToOrigin()
{
int y = this.Rectangle.Y;
int x = this.Rectangle.X;
return ref this.Buffer.GetRowSpan(y)[x];
} }
public void Clear() internal void Clear()
{ {
// Optimization for when the size of the area is the same as the buffer size. // Optimization for when the size of the area is the same as the buffer size.
if (this.IsFullBufferArea) if (this.IsFullBufferArea)
{ {
this.DestinationBuffer.FastMemoryGroup.Clear(); this.Buffer.FastMemoryGroup.Clear();
return; return;
} }

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

@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
IndexedImageFrame<TPixel> destination, IndexedImageFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int offsetY = bounds.Top; int offsetY = bounds.Top;

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

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
IndexedImageFrame<TPixel> destination, IndexedImageFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>;
/// <summary> /// <summary>

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

@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
IndexedImageFrame<TPixel> destination, IndexedImageFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var ditherOperation = new QuantizeDitherRowOperation<TFrameQuantizer, TPixel>( var ditherOperation = new QuantizeDitherRowOperation<TFrameQuantizer, TPixel>(
@ -195,7 +195,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
=> HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); => HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY);
private readonly struct QuantizeDitherRowOperation<TFrameQuantizer, TPixel> : IRowOperation private readonly struct QuantizeDitherRowOperation<TFrameQuantizer, TPixel> : IRowOperation
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly TFrameQuantizer quantizer; private readonly TFrameQuantizer quantizer;

107
src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs

@ -0,0 +1,107 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// A pixel sampling strategy that enumerates a limited amount of rows from different frames,
/// if the total number of pixels is over a threshold.
/// </summary>
public class DefaultPixelSamplingStrategy : IPixelSamplingStrategy
{
// TODO: This value shall be determined by benchmarking.
// A smaller value should likely work well, providing better perf.
private const int DefaultMaximumPixels = 4096 * 4096;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultPixelSamplingStrategy"/> class.
/// </summary>
public DefaultPixelSamplingStrategy()
: this(DefaultMaximumPixels, 0.1)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DefaultPixelSamplingStrategy"/> class.
/// </summary>
/// <param name="maximumPixels">The maximum number of pixels to process.</param>
/// <param name="minimumScanRatio">always scan at least this portion of total pixels within the image.</param>
public DefaultPixelSamplingStrategy(int maximumPixels, double minimumScanRatio)
{
Guard.MustBeGreaterThan(maximumPixels, 0, nameof(maximumPixels));
this.MaximumPixels = maximumPixels;
this.MinimumScanRatio = minimumScanRatio;
}
/// <summary>
/// Gets the maximum number of pixels to process. (The threshold.)
/// </summary>
public long MaximumPixels { get; }
/// <summary>
/// Gets a value indicating: always scan at least this portion of total pixels within the image.
/// The default is 0.1 (10%).
/// </summary>
public double MinimumScanRatio { get; }
/// <inheritdoc />
public IEnumerable<BufferRegion<TPixel>> EnumeratePixelRegions<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
long maximumPixels = Math.Min(this.MaximumPixels, (long)image.Width * image.Height * image.Frames.Count);
long maxNumberOfRows = maximumPixels / image.Width;
long totalNumberOfRows = (long)image.Height * image.Frames.Count;
if (totalNumberOfRows <= maxNumberOfRows)
{
// Enumerate all pixels
foreach (ImageFrame<TPixel> frame in image.Frames)
{
yield return frame.PixelBuffer.GetRegion();
}
}
else
{
double r = maxNumberOfRows / (double)totalNumberOfRows;
// Use a rough approximation to make sure we don't leave out large contiguous regions:
if (maxNumberOfRows > 200)
{
r = Math.Round(r, 2);
}
else
{
r = Math.Round(r, 1);
}
r = Math.Max(this.MinimumScanRatio, r); // always visit the minimum defined portion of the image.
var ratio = new Rational(r);
int denom = (int)ratio.Denominator;
int num = (int)ratio.Numerator;
for (int pos = 0; pos < totalNumberOfRows; pos++)
{
int subPos = pos % denom;
if (subPos < num)
{
yield return GetRow(pos);
}
}
BufferRegion<TPixel> GetRow(int pos)
{
int frameIdx = pos / image.Height;
int y = pos % image.Height;
return image.Frames[frameIdx].PixelBuffer.GetRegion(0, y, image.Width, 1);
}
}
}
}
}

25
src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs

@ -0,0 +1,25 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// A pixel sampling strategy that enumerates all pixels.
/// </summary>
public class ExtensivePixelSamplingStrategy : IPixelSamplingStrategy
{
/// <inheritdoc />
public IEnumerable<BufferRegion<TPixel>> EnumeratePixelRegions<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (ImageFrame<TPixel> frame in image.Frames)
{
yield return frame.PixelBuffer.GetRegion();
}
}
}
}

24
src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs

@ -0,0 +1,24 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{
/// <summary>
/// Provides an abstraction to enumerate pixel regions within a multi-framed <see cref="Image{TPixel}"/>.
/// </summary>
public interface IPixelSamplingStrategy
{
/// <summary>
/// Enumerates pixel regions within the image as <see cref="BufferRegion{T}"/>.
/// </summary>
/// <param name="image">The image.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <returns>An enumeration of pixel regions.</returns>
IEnumerable<BufferRegion<TPixel>> EnumeratePixelRegions<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>;
}
}

8
src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs

@ -20,8 +20,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
/// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param> /// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="IFrameQuantizer{TPixel}"/>.</returns> /// <returns>The <see cref="IQuantizer{TPixel}"/>.</returns>
IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration) IQuantizer<TPixel> CreatePixelSpecificQuantizer<TPixel>(Configuration configuration)
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>;
/// <summary> /// <summary>
@ -30,8 +30,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param> /// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param>
/// <param name="options">The options to create the quantizer with.</param> /// <param name="options">The options to create the quantizer with.</param>
/// <returns>The <see cref="IFrameQuantizer{TPixel}"/>.</returns> /// <returns>The <see cref="IQuantizer{TPixel}"/>.</returns>
IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, QuantizerOptions options) IQuantizer<TPixel> CreatePixelSpecificQuantizer<TPixel>(Configuration configuration, QuantizerOptions options)
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>;
} }
} }

16
src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs → src/ImageSharp/Processing/Processors/Quantization/IQuantizer{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
@ -10,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Provides methods to allow the execution of the quantization process on an image frame. /// Provides methods to allow the execution of the quantization process on an image frame.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IFrameQuantizer<TPixel> : IDisposable public interface IQuantizer<TPixel> : IDisposable
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
/// <summary> /// <summary>
@ -27,16 +28,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Gets the quantized color palette. /// Gets the quantized color palette.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// The palette has not been built via <see cref="BuildPalette(ImageFrame{TPixel}, Rectangle)"/>. /// The palette has not been built via <see cref="AddPaletteColors"/>.
/// </exception> /// </exception>
ReadOnlyMemory<TPixel> Palette { get; } ReadOnlyMemory<TPixel> Palette { get; }
/// <summary> /// <summary>
/// Builds the quantized palette from the given image frame and bounds. /// Adds colors to the quantized palette from the given pixel source.
/// </summary> /// </summary>
/// <param name="source">The source image frame.</param> /// <param name="pixelRegion">The <see cref="BufferRegion{T}"/> of source pixels to register.</param>
/// <param name="bounds">The region of interest bounds.</param> void AddPaletteColors(BufferRegion<TPixel> pixelRegion);
void BuildPalette(ImageFrame<TPixel> source, Rectangle bounds);
/// <summary> /// <summary>
/// Quantizes an image frame and return the resulting output pixels. /// Quantizes an image frame and return the resulting output pixels.
@ -46,6 +46,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <returns> /// <returns>
/// A <see cref="IndexedImageFrame{TPixel}"/> representing a quantized version of the source frame pixels. /// A <see cref="IndexedImageFrame{TPixel}"/> representing a quantized version of the source frame pixels.
/// </returns> /// </returns>
/// <remarks>
/// Only executes the second (quantization) step. The palette has to be built by calling <see cref="AddPaletteColors"/>.
/// To run both steps, use <see cref="QuantizerUtilities.BuildPaletteAndQuantizeFrame{TPixel}"/>.
/// </remarks>
IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds); IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds);
/// <summary> /// <summary>

8
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs

@ -36,13 +36,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public QuantizerOptions Options { get; } public QuantizerOptions Options { get; }
/// <inheritdoc /> /// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration) public IQuantizer<TPixel> CreatePixelSpecificQuantizer<TPixel>(Configuration configuration)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> this.CreateFrameQuantizer<TPixel>(configuration, this.Options); => this.CreatePixelSpecificQuantizer<TPixel>(configuration, this.Options);
/// <inheritdoc /> /// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, QuantizerOptions options) public IQuantizer<TPixel> CreatePixelSpecificQuantizer<TPixel>(Configuration configuration, QuantizerOptions options)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> new OctreeFrameQuantizer<TPixel>(configuration, options); => new OctreeQuantizer<TPixel>(configuration, options);
} }
} }

16
src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs → src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/> /// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
public struct OctreeFrameQuantizer<TPixel> : IFrameQuantizer<TPixel> public struct OctreeQuantizer<TPixel> : IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly int maxColors; private readonly int maxColors;
@ -29,12 +29,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private bool isDisposed; private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OctreeFrameQuantizer{TPixel}"/> struct. /// Initializes a new instance of the <see cref="OctreeQuantizer{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>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public OctreeFrameQuantizer(Configuration configuration, QuantizerOptions options) public OctreeQuantizer(Configuration configuration, QuantizerOptions options)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
@ -62,22 +62,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
get get
{ {
FrameQuantizerUtilities.CheckPaletteState(in this.palette); QuantizerUtilities.CheckPaletteState(in this.palette);
return this.palette; return this.palette;
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void BuildPalette(ImageFrame<TPixel> source, Rectangle bounds) public void AddPaletteColors(BufferRegion<TPixel> pixelRegion)
{ {
Rectangle bounds = pixelRegion.Rectangle;
Buffer2D<TPixel> source = pixelRegion.Buffer;
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();
// Loop through each row // Loop through each row
for (int y = bounds.Top; y < bounds.Bottom; y++) for (int y = bounds.Top; y < bounds.Bottom; y++)
{ {
Span<TPixel> row = source.GetPixelRowSpan(y).Slice(bounds.Left, bounds.Width); Span<TPixel> row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width);
PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan); PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan);
for (int x = 0; x < bufferSpan.Length; x++) for (int x = 0; x < bufferSpan.Length; x++)
@ -103,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds) public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds);
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]

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

@ -41,12 +41,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public QuantizerOptions Options { get; } public QuantizerOptions Options { get; }
/// <inheritdoc /> /// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration) public IQuantizer<TPixel> CreatePixelSpecificQuantizer<TPixel>(Configuration configuration)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> this.CreateFrameQuantizer<TPixel>(configuration, this.Options); => this.CreatePixelSpecificQuantizer<TPixel>(configuration, this.Options);
/// <inheritdoc /> /// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, QuantizerOptions options) public IQuantizer<TPixel> CreatePixelSpecificQuantizer<TPixel>(Configuration configuration, QuantizerOptions options)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan());
var pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette); var pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette);
return new PaletteFrameQuantizer<TPixel>(configuration, options, pixelMap); return new PaletteQuantizer<TPixel>(configuration, options, pixelMap);
} }
} }
} }

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

@ -3,6 +3,7 @@
using System; using System;
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
@ -12,19 +13,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/> /// <see href="http://msdn.microsoft.com/en-us/library/aa479306.aspx"/>
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal struct PaletteFrameQuantizer<TPixel> : IFrameQuantizer<TPixel> internal struct PaletteQuantizer<TPixel> : IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly EuclideanPixelMap<TPixel> pixelMap; private readonly EuclideanPixelMap<TPixel> pixelMap;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PaletteFrameQuantizer{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="pixelMap">The pixel map for looking up color matches from a predefined palette.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public PaletteFrameQuantizer( public PaletteQuantizer(
Configuration configuration, Configuration configuration,
QuantizerOptions options, QuantizerOptions options,
EuclideanPixelMap<TPixel> pixelMap) EuclideanPixelMap<TPixel> pixelMap)
@ -49,11 +50,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds) public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds);
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void BuildPalette(ImageFrame<TPixel> source, Rectangle bounds) public void AddPaletteColors(BufferRegion<TPixel> pixelRegion)
{ {
} }

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

@ -38,8 +38,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle); var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle);
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
using IFrameQuantizer<TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer<TPixel>(configuration); using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(source, interest); using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(source, interest);
var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized);
ParallelRowIterator.IterateRowIntervals( ParallelRowIterator.IterateRowIntervals(

56
src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs → src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs

@ -11,9 +11,9 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
/// <summary> /// <summary>
/// Contains utility methods for <see cref="IFrameQuantizer{TPixel}"/> instances. /// Contains utility methods for <see cref="IQuantizer{TPixel}"/> instances.
/// </summary> /// </summary>
public static class FrameQuantizerUtilities public static class QuantizerUtilities
{ {
/// <summary> /// <summary>
/// Helper method for throwing an exception when a frame quantizer palette has /// Helper method for throwing an exception when a frame quantizer palette has
@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="palette">The frame quantizer palette.</param> /// <param name="palette">The frame quantizer palette.</param>
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// The palette has not been built via <see cref="IFrameQuantizer{TPixel}.BuildPalette(ImageFrame{TPixel}, Rectangle)"/> /// The palette has not been built via <see cref="IQuantizer{TPixel}.AddPaletteColors"/>
/// </exception> /// </exception>
public static void CheckPaletteState<TPixel>(in ReadOnlyMemory<TPixel> palette) public static void CheckPaletteState<TPixel>(in ReadOnlyMemory<TPixel> palette)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -33,12 +33,39 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
} }
/// <summary>
/// Execute both steps of the quantization.
/// </summary>
/// <param name="quantizer">The pixel specific quantizer.</param>
/// <param name="source">The source image frame to quantize.</param>
/// <param name="bounds">The bounds within the frame to quantize.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <returns>
/// A <see cref="IndexedImageFrame{TPixel}"/> representing a quantized version of the source frame pixels.
/// </returns>
public static IndexedImageFrame<TPixel> BuildPaletteAndQuantizeFrame<TPixel>(
this IQuantizer<TPixel> quantizer,
ImageFrame<TPixel> source,
Rectangle bounds)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(quantizer, nameof(quantizer));
Guard.NotNull(source, nameof(source));
var interest = Rectangle.Intersect(source.Bounds(), bounds);
BufferRegion<TPixel> region = source.PixelBuffer.GetRegion(interest);
// Collect the palette. Required before the second pass runs.
quantizer.AddPaletteColors(region);
return quantizer.QuantizeFrame(source, bounds);
}
/// <summary> /// <summary>
/// Quantizes an image frame and return the resulting output pixels. /// Quantizes an image frame and return the resulting output pixels.
/// </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 quantizer.</param> /// <param name="quantizer">The pixel specific 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>
@ -48,15 +75,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Rectangle bounds) Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(source, nameof(source)); Guard.NotNull(source, nameof(source));
var interest = Rectangle.Intersect(source.Bounds(), bounds); var interest = Rectangle.Intersect(source.Bounds(), bounds);
// Collect the palette. Required before the second pass runs.
quantizer.BuildPalette(source, interest);
var destination = new IndexedImageFrame<TPixel>( var destination = new IndexedImageFrame<TPixel>(
quantizer.Configuration, quantizer.Configuration,
interest.Width, interest.Width,
@ -77,13 +101,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return destination; return destination;
} }
internal static void BuildPalette<TPixel>(
this IQuantizer<TPixel> quantizer,
IPixelSamplingStrategy pixelSamplingStrategy,
Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (BufferRegion<TPixel> region in pixelSamplingStrategy.EnumeratePixelRegions(image))
{
quantizer.AddPaletteColors(region);
}
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static void SecondPass<TFrameQuantizer, TPixel>( private static void SecondPass<TFrameQuantizer, TPixel>(
ref TFrameQuantizer quantizer, ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
IndexedImageFrame<TPixel> destination, IndexedImageFrame<TPixel> destination,
Rectangle bounds) Rectangle bounds)
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
IDither dither = quantizer.Options.Dither; IDither dither = quantizer.Options.Dither;
@ -108,7 +144,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
private readonly struct RowIntervalOperation<TFrameQuantizer, TPixel> : IRowIntervalOperation private readonly struct RowIntervalOperation<TFrameQuantizer, TPixel> : IRowIntervalOperation
where TFrameQuantizer : struct, IFrameQuantizer<TPixel> where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly TFrameQuantizer quantizer; private readonly TFrameQuantizer quantizer;

8
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs

@ -35,13 +35,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public QuantizerOptions Options { get; } public QuantizerOptions Options { get; }
/// <inheritdoc /> /// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration) public IQuantizer<TPixel> CreatePixelSpecificQuantizer<TPixel>(Configuration configuration)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> this.CreateFrameQuantizer<TPixel>(configuration, this.Options); => this.CreatePixelSpecificQuantizer<TPixel>(configuration, this.Options);
/// <inheritdoc /> /// <inheritdoc />
public IFrameQuantizer<TPixel> CreateFrameQuantizer<TPixel>(Configuration configuration, QuantizerOptions options) public IQuantizer<TPixel> CreatePixelSpecificQuantizer<TPixel>(Configuration configuration, QuantizerOptions options)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> new WuFrameQuantizer<TPixel>(configuration, options); => new WuQuantizer<TPixel>(configuration, options);
} }
} }

19
src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs → src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </para> /// </para>
/// </remarks> /// </remarks>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal struct WuFrameQuantizer<TPixel> : IFrameQuantizer<TPixel> internal struct WuQuantizer<TPixel> : IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly MemoryAllocator memoryAllocator; private readonly MemoryAllocator memoryAllocator;
@ -77,12 +77,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private bool isDisposed; private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WuFrameQuantizer{TPixel}"/> struct. /// Initializes a new instance of the <see cref="WuQuantizer{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>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public WuFrameQuantizer(Configuration configuration, QuantizerOptions options) public WuQuantizer(Configuration configuration, QuantizerOptions options)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
@ -112,14 +112,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
{ {
get get
{ {
FrameQuantizerUtilities.CheckPaletteState(in this.palette); QuantizerUtilities.CheckPaletteState(in this.palette);
return this.palette; return this.palette;
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
public void BuildPalette(ImageFrame<TPixel> source, Rectangle bounds) public void AddPaletteColors(BufferRegion<TPixel> pixelRegion)
{ {
Rectangle bounds = pixelRegion.Rectangle;
Buffer2D<TPixel> source = pixelRegion.Buffer;
this.Build3DHistogram(source, bounds); this.Build3DHistogram(source, bounds);
this.Get3DMoments(this.memoryAllocator); this.Get3DMoments(this.memoryAllocator);
this.BuildCube(); this.BuildCube();
@ -147,7 +150,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds) public readonly IndexedImageFrame<TPixel> QuantizeFrame(ImageFrame<TPixel> source, Rectangle bounds)
=> FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); => QuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds);
/// <inheritdoc/> /// <inheritdoc/>
public readonly byte GetQuantizedColor(TPixel color, out TPixel match) public readonly byte GetQuantizedColor(TPixel color, out TPixel match)
@ -360,7 +363,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// </summary> /// </summary>
/// <param name="source">The source data.</param> /// <param name="source">The source data.</param>
/// <param name="bounds">The bounds within the source image to quantize.</param> /// <param name="bounds">The bounds within the source image to quantize.</param>
private void Build3DHistogram(ImageFrame<TPixel> source, Rectangle bounds) private void Build3DHistogram(Buffer2D<TPixel> source, Rectangle bounds)
{ {
Span<Moment> momentSpan = this.momentsOwner.GetSpan(); Span<Moment> momentSpan = this.momentsOwner.GetSpan();
@ -370,7 +373,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
for (int y = bounds.Top; y < bounds.Bottom; y++) for (int y = bounds.Top; y < bounds.Bottom; y++)
{ {
Span<TPixel> row = source.GetPixelRowSpan(y).Slice(bounds.Left, bounds.Width); Span<TPixel> row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width);
PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan); PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan);
for (int x = 0; x < bufferSpan.Length; x++) for (int x = 0; x < bufferSpan.Length; x++)

4
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -172,13 +172,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
PixelConversionModifiers conversionModifiers = PixelConversionModifiers conversionModifiers =
PixelConversionModifiers.Premultiply.ApplyCompanding(compand); PixelConversionModifiers.Premultiply.ApplyCompanding(compand);
BufferArea<TPixel> sourceArea = source.PixelBuffer.GetArea(sourceRectangle); BufferRegion<TPixel> sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle);
// To reintroduce parallel processing, we would launch multiple workers // To reintroduce parallel processing, we would launch multiple workers
// for different row intervals of the image. // for different row intervals of the image.
using (var worker = new ResizeWorker<TPixel>( using (var worker = new ResizeWorker<TPixel>(
configuration, configuration,
sourceArea, sourceRegion,
conversionModifiers, conversionModifiers,
horizontalKernelMap, horizontalKernelMap,
verticalKernelMap, verticalKernelMap,

4
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly ResizeKernelMap horizontalKernelMap; private readonly ResizeKernelMap horizontalKernelMap;
private readonly BufferArea<TPixel> source; private readonly BufferRegion<TPixel> source;
private readonly Rectangle sourceRectangle; private readonly Rectangle sourceRectangle;
@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public ResizeWorker( public ResizeWorker(
Configuration configuration, Configuration configuration,
BufferArea<TPixel> source, BufferRegion<TPixel> source,
PixelConversionModifiers conversionModifiers, PixelConversionModifiers conversionModifiers,
ResizeKernelMap horizontalKernelMap, ResizeKernelMap horizontalKernelMap,
ResizeKernelMap verticalKernelMap, ResizeKernelMap verticalKernelMap,

28
tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs

@ -18,20 +18,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations
private Buffer2D<float> buffer; private Buffer2D<float> buffer;
private BufferArea<float> destArea; private BufferRegion<float> destRegion;
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ {
this.buffer = Configuration.Default.MemoryAllocator.Allocate2D<float>(1000, 500); this.buffer = Configuration.Default.MemoryAllocator.Allocate2D<float>(1000, 500);
this.destArea = this.buffer.GetArea(200, 100, 128, 128); this.destRegion = this.buffer.GetRegion(200, 100, 128, 128);
} }
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Original() public void Original()
{ {
ref float destBase = ref this.destArea.GetReferenceToOrigin(); ref float destBase = ref this.destRegion.GetReferenceToOrigin();
int destStride = this.destArea.Stride; int destStride = this.destRegion.Stride;
ref Block8x8F src = ref this.block; ref Block8x8F src = ref this.block;
@ -92,8 +92,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations
[Benchmark] [Benchmark]
public void Original_V2() public void Original_V2()
{ {
ref float destBase = ref this.destArea.GetReferenceToOrigin(); ref float destBase = ref this.destRegion.GetReferenceToOrigin();
int destStride = this.destArea.Stride; int destStride = this.destRegion.Stride;
ref Block8x8F src = ref this.block; ref Block8x8F src = ref this.block;
@ -160,8 +160,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations
[Benchmark] [Benchmark]
public void UseVector2() public void UseVector2()
{ {
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destArea.GetReferenceToOrigin()); ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destRegion.GetReferenceToOrigin());
int destStride = this.destArea.Stride / 2; int destStride = this.destRegion.Stride / 2;
ref Block8x8F src = ref this.block; ref Block8x8F src = ref this.block;
@ -220,8 +220,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations
[Benchmark] [Benchmark]
public void UseVector4() public void UseVector4()
{ {
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destArea.GetReferenceToOrigin()); ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destRegion.GetReferenceToOrigin());
int destStride = this.destArea.Stride / 2; int destStride = this.destRegion.Stride / 2;
ref Block8x8F src = ref this.block; ref Block8x8F src = ref this.block;
@ -280,8 +280,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations
[Benchmark] [Benchmark]
public void UseVector4_SafeRightCorner() public void UseVector4_SafeRightCorner()
{ {
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destArea.GetReferenceToOrigin()); ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destRegion.GetReferenceToOrigin());
int destStride = this.destArea.Stride / 2; int destStride = this.destRegion.Stride / 2;
ref Block8x8F src = ref this.block; ref Block8x8F src = ref this.block;
@ -338,8 +338,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations
[Benchmark] [Benchmark]
public void UseVector4_V2() public void UseVector4_V2()
{ {
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destArea.GetReferenceToOrigin()); ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destRegion.GetReferenceToOrigin());
int destStride = this.destArea.Stride / 2; int destStride = this.destRegion.Stride / 2;
ref Block8x8F src = ref this.block; ref Block8x8F src = ref this.block;

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -132,6 +133,38 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
} }
} }
[Theory]
[WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 427500, 0.1)]
[WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 200000, 0.1)]
[WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 100000, 0.1)]
[WithFile(TestImages.Gif.GlobalQuantizationTest, PixelTypes.Rgba32, 50000, 0.1)]
[WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 4000000, 0.01)]
[WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32, 1000000, 0.01)]
public void Encode_GlobalPalette_DefaultPixelSamplingStrategy<TPixel>(TestImageProvider<TPixel> provider, int maxPixels, double scanRatio)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
var encoder = new GifEncoder()
{
ColorTableMode = GifColorTableMode.Global,
GlobalPixelSamplingStrategy = new DefaultPixelSamplingStrategy(maxPixels, scanRatio)
};
string testOutputFile = provider.Utility.SaveTestOutputFile(
image,
"gif",
encoder,
testOutputDetails: $"{maxPixels}_{scanRatio}",
appendPixelTypeToFileName: false);
// TODO: For proper regression testing of gifs, use a multi-frame reference output, or find a working reference decoder.
// IImageDecoder referenceDecoder = TestEnvironment.Ge
// ReferenceDecoder(testOutputFile);
// using var encoded = Image.Load<TPixel>(testOutputFile, referenceDecoder);
// ValidatorComparer.VerifySimilarity(image, encoded);
}
[Fact] [Fact]
public void NonMutatingEncodePreservesPaletteCount() public void NonMutatingEncodePreservesPaletteCount()
{ {

10
tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs

@ -43,8 +43,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (Buffer2D<float> buffer = Configuration.Default.MemoryAllocator.Allocate2D<float>(20, 20, AllocationOptions.Clean)) using (Buffer2D<float> buffer = Configuration.Default.MemoryAllocator.Allocate2D<float>(20, 20, AllocationOptions.Clean))
{ {
BufferArea<float> area = buffer.GetArea(5, 10, 8, 8); BufferRegion<float> region = buffer.GetRegion(5, 10, 8, 8);
block.Copy1x1Scale(ref area.GetReferenceToOrigin(), area.Stride); block.Copy1x1Scale(ref region.GetReferenceToOrigin(), region.Stride);
Assert.Equal(block[0, 0], buffer[5, 10]); Assert.Equal(block[0, 0], buffer[5, 10]);
Assert.Equal(block[1, 0], buffer[6, 10]); Assert.Equal(block[1, 0], buffer[6, 10]);
@ -71,8 +71,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using (Buffer2D<float> buffer = Configuration.Default.MemoryAllocator.Allocate2D<float>(100, 100, AllocationOptions.Clean)) using (Buffer2D<float> buffer = Configuration.Default.MemoryAllocator.Allocate2D<float>(100, 100, AllocationOptions.Clean))
{ {
BufferArea<float> area = buffer.GetArea(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor); BufferRegion<float> region = buffer.GetRegion(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor);
block.ScaledCopyTo(area, horizontalFactor, verticalFactor); block.ScaledCopyTo(region, horizontalFactor, verticalFactor);
for (int y = 0; y < 8 * verticalFactor; y++) for (int y = 0; y < 8 * verticalFactor; y++)
{ {
@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
int xx = x / horizontalFactor; int xx = x / horizontalFactor;
float expected = block[xx, yy]; float expected = block[xx, yy];
float actual = area[x, y]; float actual = region[x, y];
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
} }

32
tests/ImageSharp.Tests/Memory/BufferAreaTests.cs

@ -16,9 +16,9 @@ namespace SixLabors.ImageSharp.Tests.Memory
{ {
using Buffer2D<int> buffer = this.memoryAllocator.Allocate2D<int>(10, 20); using Buffer2D<int> buffer = this.memoryAllocator.Allocate2D<int>(10, 20);
var rectangle = new Rectangle(3, 2, 5, 6); var rectangle = new Rectangle(3, 2, 5, 6);
var area = new BufferArea<int>(buffer, rectangle); var area = new BufferRegion<int>(buffer, rectangle);
Assert.Equal(buffer, area.DestinationBuffer); Assert.Equal(buffer, area.Buffer);
Assert.Equal(rectangle, area.Rectangle); Assert.Equal(rectangle, area.Rectangle);
} }
@ -47,9 +47,9 @@ namespace SixLabors.ImageSharp.Tests.Memory
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30); using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
var r = new Rectangle(rx, ry, 5, 6); var r = new Rectangle(rx, ry, 5, 6);
BufferArea<int> area = buffer.GetArea(r); BufferRegion<int> region = buffer.GetRegion(r);
int value = area[x, y]; int value = region[x, y];
int expected = ((ry + y) * 100) + rx + x; int expected = ((ry + y) * 100) + rx + x;
Assert.Equal(expected, value); Assert.Equal(expected, value);
} }
@ -66,9 +66,9 @@ namespace SixLabors.ImageSharp.Tests.Memory
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30); using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
var r = new Rectangle(rx, ry, w, h); var r = new Rectangle(rx, ry, w, h);
BufferArea<int> area = buffer.GetArea(r); BufferRegion<int> region = buffer.GetRegion(r);
Span<int> span = area.GetRowSpan(y); Span<int> span = region.GetRowSpan(y);
Assert.Equal(w, span.Length); Assert.Equal(w, span.Length);
@ -85,13 +85,13 @@ namespace SixLabors.ImageSharp.Tests.Memory
public void GetSubArea() public void GetSubArea()
{ {
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30); using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
BufferArea<int> area0 = buffer.GetArea(6, 8, 10, 10); BufferRegion<int> area0 = buffer.GetRegion(6, 8, 10, 10);
BufferArea<int> area1 = area0.GetSubArea(4, 4, 5, 5); BufferRegion<int> area1 = area0.GetSubArea(4, 4, 5, 5);
var expectedRect = new Rectangle(10, 12, 5, 5); var expectedRect = new Rectangle(10, 12, 5, 5);
Assert.Equal(buffer, area1.DestinationBuffer); Assert.Equal(buffer, area1.Buffer);
Assert.Equal(expectedRect, area1.Rectangle); Assert.Equal(expectedRect, area1.Rectangle);
int value00 = (12 * 100) + 10; int value00 = (12 * 100) + 10;
@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity;
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30); using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
BufferArea<int> area0 = buffer.GetArea(6, 8, 10, 10); BufferRegion<int> area0 = buffer.GetRegion(6, 8, 10, 10);
ref int r = ref area0.GetReferenceToOrigin(); ref int r = ref area0.GetReferenceToOrigin();
@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
using Buffer2D<int> buffer = this.CreateTestBuffer(22, 13); using Buffer2D<int> buffer = this.CreateTestBuffer(22, 13);
var emptyRow = new int[22]; var emptyRow = new int[22];
buffer.GetArea().Clear(); buffer.GetRegion().Clear();
for (int y = 0; y < 13; y++) for (int y = 0; y < 13; y++)
{ {
@ -140,8 +140,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity;
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30); using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
BufferArea<int> area = buffer.GetArea(5, 5, 10, 10); BufferRegion<int> region = buffer.GetRegion(5, 5, 10, 10);
area.Clear(); region.Clear();
Assert.NotEqual(0, buffer[4, 4]); Assert.NotEqual(0, buffer[4, 4]);
Assert.NotEqual(0, buffer[15, 15]); Assert.NotEqual(0, buffer[15, 15]);
@ -149,10 +149,10 @@ namespace SixLabors.ImageSharp.Tests.Memory
Assert.Equal(0, buffer[5, 5]); Assert.Equal(0, buffer[5, 5]);
Assert.Equal(0, buffer[14, 14]); Assert.Equal(0, buffer[14, 14]);
for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++) for (int y = region.Rectangle.Y; y < region.Rectangle.Bottom; y++)
{ {
Span<int> span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); Span<int> span = buffer.GetRowSpan(y).Slice(region.Rectangle.X, region.Width);
Assert.True(span.SequenceEqual(new int[area.Width])); Assert.True(span.SequenceEqual(new int[region.Width]));
} }
} }
} }

6
tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs

@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
public void OctreeQuantizerCanCreateFrameQuantizer() public void OctreeQuantizerCanCreateFrameQuantizer()
{ {
var quantizer = new OctreeQuantizer(); var quantizer = new OctreeQuantizer();
IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); IQuantizer<Rgba32> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.NotNull(frameQuantizer.Options); Assert.NotNull(frameQuantizer.Options);
@ -47,14 +47,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
frameQuantizer.Dispose(); frameQuantizer.Dispose();
quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }); quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null });
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.Null(frameQuantizer.Options.Dither); Assert.Null(frameQuantizer.Options.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();
quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson });
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();

6
tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
public void PaletteQuantizerCanCreateFrameQuantizer() public void PaletteQuantizerCanCreateFrameQuantizer()
{ {
var quantizer = new PaletteQuantizer(Palette); var quantizer = new PaletteQuantizer(Palette);
IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); IQuantizer<Rgba32> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.NotNull(frameQuantizer.Options); Assert.NotNull(frameQuantizer.Options);
@ -49,14 +49,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
frameQuantizer.Dispose(); frameQuantizer.Dispose();
quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = null }); quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = null });
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.Null(frameQuantizer.Options.Dither); Assert.Null(frameQuantizer.Options.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();
quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = KnownDitherings.Atkinson }); quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = KnownDitherings.Atkinson });
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();

6
tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs

@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
public void WuQuantizerCanCreateFrameQuantizer() public void WuQuantizerCanCreateFrameQuantizer()
{ {
var quantizer = new WuQuantizer(); var quantizer = new WuQuantizer();
IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); IQuantizer<Rgba32> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.NotNull(frameQuantizer.Options); Assert.NotNull(frameQuantizer.Options);
@ -47,14 +47,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization
frameQuantizer.Dispose(); frameQuantizer.Dispose();
quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); quantizer = new WuQuantizer(new QuantizerOptions { Dither = null });
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.Null(frameQuantizer.Options.Dither); Assert.Null(frameQuantizer.Options.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();
quantizer = new WuQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); quantizer = new WuQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson });
frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(Configuration.Default); frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(Configuration.Default);
Assert.NotNull(frameQuantizer); Assert.NotNull(frameQuantizer);
Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither);
frameQuantizer.Dispose(); frameQuantizer.Dispose();

99
tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs

@ -0,0 +1,99 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Quantization
{
public class PixelSamplingStrategyTests
{
public static readonly TheoryData<int, int, int, int> DefaultPixelSamplingStrategy_Data = new TheoryData<int, int, int, int>()
{
{ 100, 100, 1, 10000 },
{ 100, 100, 1, 5000 },
{ 100, 100, 10, 50000 },
{ 99, 100, 11, 30000 },
{ 97, 99, 11, 80000 },
{ 99, 100, 11, 20000 },
{ 99, 501, 20, 100000 },
{ 97, 500, 20, 10000 },
{ 103, 501, 20, 1000 },
};
[Fact]
public void ExtensivePixelSamplingStrategy_EnumeratesAll()
{
using Image<L8> image = CreateTestImage(100, 100, 100);
var strategy = new ExtensivePixelSamplingStrategy();
foreach (BufferRegion<L8> region in strategy.EnumeratePixelRegions(image))
{
PaintWhite(region);
}
using Image<L8> expected = CreateTestImage(100, 100, 100, true);
ImageComparer.Exact.VerifySimilarity(expected, image);
}
[Theory]
[WithBlankImages(nameof(DefaultPixelSamplingStrategy_Data), 1, 1, PixelTypes.L8)]
public void DefaultPixelSamplingStrategy_IsFair(TestImageProvider<L8> dummyProvider, int width, int height, int noOfFrames, int maximumNumberOfPixels)
{
using Image<L8> image = CreateTestImage(width, height, noOfFrames);
var strategy = new DefaultPixelSamplingStrategy(maximumNumberOfPixels, 0.1);
long visitedPixels = 0;
foreach (BufferRegion<L8> region in strategy.EnumeratePixelRegions(image))
{
PaintWhite(region);
visitedPixels += region.Width * region.Height;
}
image.DebugSaveMultiFrame(
dummyProvider,
$"W{width}_H{height}_noOfFrames_{noOfFrames}_maximumNumberOfPixels_{maximumNumberOfPixels}",
appendPixelTypeToFileName: false);
int maximumPixels = image.Width * image.Height * image.Frames.Count / 10;
maximumPixels = Math.Max(maximumPixels, (int)strategy.MaximumPixels);
// allow some inaccuracy:
double visitRatio = visitedPixels / (double)maximumPixels;
Assert.True(visitRatio <= 1.1, $"{visitedPixels}>{maximumPixels}");
}
private static void PaintWhite(BufferRegion<L8> region)
{
var white = new L8(255);
for (int y = 0; y < region.Height; y++)
{
region.GetRowSpan(y).Fill(white);
}
}
private static Image<L8> CreateTestImage(int width, int height, int noOfFrames, bool paintWhite = false)
{
L8 bg = paintWhite ? new L8(255) : default;
var image = new Image<L8>(width, height, bg);
for (int i = 1; i < noOfFrames; i++)
{
ImageFrame<L8> f = image.Frames.CreateFrame();
if (paintWhite)
{
f.PixelBuffer.MemoryGroup.Fill(bg);
}
}
return image;
}
}
}

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

@ -27,22 +27,22 @@ namespace SixLabors.ImageSharp.Tests
Assert.NotNull(octree.Options.Dither); Assert.NotNull(octree.Options.Dither);
Assert.NotNull(wu.Options.Dither); Assert.NotNull(wu.Options.Dither);
using (IFrameQuantizer<Rgba32> quantizer = werner.CreateFrameQuantizer<Rgba32>(this.Configuration)) using (IQuantizer<Rgba32> quantizer = werner.CreatePixelSpecificQuantizer<Rgba32>(this.Configuration))
{ {
Assert.NotNull(quantizer.Options.Dither); Assert.NotNull(quantizer.Options.Dither);
} }
using (IFrameQuantizer<Rgba32> quantizer = webSafe.CreateFrameQuantizer<Rgba32>(this.Configuration)) using (IQuantizer<Rgba32> quantizer = webSafe.CreatePixelSpecificQuantizer<Rgba32>(this.Configuration))
{ {
Assert.NotNull(quantizer.Options.Dither); Assert.NotNull(quantizer.Options.Dither);
} }
using (IFrameQuantizer<Rgba32> quantizer = octree.CreateFrameQuantizer<Rgba32>(this.Configuration)) using (IQuantizer<Rgba32> quantizer = octree.CreatePixelSpecificQuantizer<Rgba32>(this.Configuration))
{ {
Assert.NotNull(quantizer.Options.Dither); Assert.NotNull(quantizer.Options.Dither);
} }
using (IFrameQuantizer<Rgba32> quantizer = wu.CreateFrameQuantizer<Rgba32>(this.Configuration)) using (IQuantizer<Rgba32> quantizer = wu.CreatePixelSpecificQuantizer<Rgba32>(this.Configuration))
{ {
Assert.NotNull(quantizer.Options.Dither); Assert.NotNull(quantizer.Options.Dither);
} }
@ -70,8 +70,8 @@ namespace SixLabors.ImageSharp.Tests
foreach (ImageFrame<TPixel> frame in image.Frames) foreach (ImageFrame<TPixel> frame in image.Frames)
{ {
using (IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(this.Configuration)) using (IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.Configuration))
using (IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) using (IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()))
{ {
int index = this.GetTransparentIndex(quantized); int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]); Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]);
@ -100,8 +100,8 @@ namespace SixLabors.ImageSharp.Tests
foreach (ImageFrame<TPixel> frame in image.Frames) foreach (ImageFrame<TPixel> frame in image.Frames)
{ {
using (IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(this.Configuration)) using (IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(this.Configuration))
using (IndexedImageFrame<TPixel> quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) using (IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()))
{ {
int index = this.GetTransparentIndex(quantized); int index = this.GetTransparentIndex(quantized);
Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]); Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]);

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

@ -20,8 +20,8 @@ namespace SixLabors.ImageSharp.Tests.Quantization
using var image = new Image<Rgba32>(config, 1, 1, Color.Black); using var image = new Image<Rgba32>(config, 1, 1, Color.Black);
ImageFrame<Rgba32> frame = image.Frames.RootFrame; ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config); using IQuantizer<Rgba32> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(config);
using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<Rgba32> result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.Width); Assert.Equal(1, result.Width);
@ -40,8 +40,8 @@ namespace SixLabors.ImageSharp.Tests.Quantization
using var image = new Image<Rgba32>(config, 1, 1, default(Rgba32)); using var image = new Image<Rgba32>(config, 1, 1, default(Rgba32));
ImageFrame<Rgba32> frame = image.Frames.RootFrame; ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config); using IQuantizer<Rgba32> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(config);
using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<Rgba32> result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Palette.Length);
Assert.Equal(1, result.Width); Assert.Equal(1, result.Width);
@ -86,8 +86,8 @@ namespace SixLabors.ImageSharp.Tests.Quantization
ImageFrame<Rgba32> frame = image.Frames.RootFrame; ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config); using IQuantizer<Rgba32> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(config);
using IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<Rgba32> result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
Assert.Equal(256, result.Palette.Length); Assert.Equal(256, result.Palette.Length);
Assert.Equal(1, result.Width); Assert.Equal(1, result.Width);
@ -126,8 +126,8 @@ namespace SixLabors.ImageSharp.Tests.Quantization
var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null });
ImageFrame<TPixel> frame = image.Frames.RootFrame; ImageFrame<TPixel> frame = image.Frames.RootFrame;
using IFrameQuantizer<TPixel> frameQuantizer = quantizer.CreateFrameQuantizer<TPixel>(config); using IQuantizer<TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<TPixel>(config);
using IndexedImageFrame<TPixel> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<TPixel> result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
Assert.Equal(48, result.Palette.Length); Assert.Equal(48, result.Palette.Length);
} }
@ -155,8 +155,8 @@ namespace SixLabors.ImageSharp.Tests.Quantization
var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null });
ImageFrame<Rgba32> frame = image.Frames.RootFrame; ImageFrame<Rgba32> frame = image.Frames.RootFrame;
using (IFrameQuantizer<Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer<Rgba32>(config)) using (IQuantizer<Rgba32> frameQuantizer = quantizer.CreatePixelSpecificQuantizer<Rgba32>(config))
using (IndexedImageFrame<Rgba32> result = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) using (IndexedImageFrame<Rgba32> result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()))
{ {
Assert.Equal(4 * 8, result.Palette.Length); Assert.Equal(4 * 8, result.Palette.Length);
Assert.Equal(1, result.Width); Assert.Equal(1, result.Width);

1
tests/ImageSharp.Tests/TestImages.cs

@ -396,6 +396,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Ratio4x1 = "Gif/base_4x1.gif"; public const string Ratio4x1 = "Gif/base_4x1.gif";
public const string Ratio1x4 = "Gif/base_1x4.gif"; public const string Ratio1x4 = "Gif/base_1x4.gif";
public const string LargeComment = "Gif/large_comment.gif"; public const string LargeComment = "Gif/large_comment.gif";
public const string GlobalQuantizationTest = "Gif/GlobalQuantizationTest.gif";
// Test images from https://github.com/robert-ancell/pygif/tree/master/test-suite // Test images from https://github.com/robert-ancell/pygif/tree/master/test-suite
public const string ZeroSize = "Gif/image-zero-size.gif"; public const string ZeroSize = "Gif/image-zero-size.gif";

3
tests/Images/Input/Gif/GlobalQuantizationTest.gif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c67df4b08561c14054ed911e6cfc99f1fc726b32e2c7a5e2dbb8392e24109b5a
size 101868
Loading…
Cancel
Save