Browse Source

BufferArea -> BufferRegion, introduce PixelSamplingStrategy

pull/1193/head
Anton Firszov 6 years ago
parent
commit
3e2f7dd310
  1. 4
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  2. 2
      src/ImageSharp/Formats/Gif/GifEncoder.cs
  3. 3
      src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
  4. 6
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs
  5. 12
      src/ImageSharp/Image{TPixel}.cs
  6. 26
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  7. 92
      src/ImageSharp/Memory/BufferRegion{T}.cs
  8. 90
      src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs
  9. 25
      src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs
  10. 24
      src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs
  11. 4
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  12. 4
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  13. 28
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs
  14. 10
      tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs
  15. 32
      tests/ImageSharp.Tests/Memory/BufferAreaTests.cs
  16. 99
      tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs

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

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

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

@ -25,6 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
public GifColorTableMode? ColorTableMode { get; set; }
internal IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; set; }
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>

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

@ -1,6 +1,9 @@
// 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;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Gif

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.
/// </summary>
[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();
this.ScaledCopyTo(ref areaOrigin, area.Stride, horizontalScale, verticalScale);
ref float areaOrigin = ref region.GetReferenceToOrigin();
this.ScaledCopyTo(ref areaOrigin, region.Stride, horizontalScale, verticalScale);
}
[MethodImpl(InliningOptions.ShortMethod)]

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>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.

26
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -80,29 +80,29 @@ namespace SixLabors.ImageSharp.Memory
}
/// <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>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <param name="rectangle">The rectangle subarea</param>
/// <returns>The <see cref="BufferArea{T}"/></returns>
internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, in Rectangle rectangle)
where T : struct =>
new BufferArea<T>(buffer, rectangle);
/// <returns>The <see cref="BufferRegion{T}"/></returns>
internal static BufferRegion<T> GetRegion<T>(this Buffer2D<T> buffer, in Rectangle rectangle)
where T : unmanaged =>
new BufferRegion<T>(buffer, rectangle);
internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
where T : struct =>
new BufferArea<T>(buffer, new Rectangle(x, y, width, height));
internal static BufferRegion<T> GetRegion<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
where T : unmanaged =>
new BufferRegion<T>(buffer, new Rectangle(x, y, width, height));
/// <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>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="BufferArea{T}"/></returns>
internal static BufferArea<T> GetArea<T>(this Buffer2D<T> buffer)
where T : struct =>
new BufferArea<T>(buffer);
/// <returns>The <see cref="BufferRegion{T}"/></returns>
internal static BufferRegion<T> GetRegion<T>(this Buffer2D<T> buffer)
where T : unmanaged =>
new BufferRegion<T>(buffer);
/// <summary>
/// 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
{
/// <summary>
/// Represents a rectangular area inside a 2D memory buffer (<see cref="Buffer2D{T}"/>).
/// This type is kind-of 2D Span, but it can live on heap.
/// Represents a rectangular region inside a 2D memory buffer (<see cref="Buffer2D{T}"/>).
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
internal readonly struct BufferArea<T>
where T : struct
public readonly struct BufferRegion<T>
where T : unmanaged
{
/// <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>
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)]
public BufferArea(Buffer2D<T> destinationBuffer, Rectangle rectangle)
public BufferRegion(Buffer2D<T> buffer, Rectangle rectangle)
{
DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle));
DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle));
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, destinationBuffer.Width, nameof(rectangle));
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, destinationBuffer.Height, nameof(rectangle));
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, buffer.Width, nameof(rectangle));
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, buffer.Height, nameof(rectangle));
this.DestinationBuffer = destinationBuffer;
this.Buffer = buffer;
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)]
public BufferArea(Buffer2D<T> destinationBuffer)
: this(destinationBuffer, destinationBuffer.FullRectangle())
public BufferRegion(Buffer2D<T> buffer)
: this(buffer, buffer.FullRectangle())
{
}
/// <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>
public Buffer2D<T> DestinationBuffer { get; }
public Rectangle Rectangle { get; }
/// <summary>
/// Gets the size of the area.
/// Gets the <see cref="Buffer2D{T}"/> being pointed by this instance.
/// </summary>
public Size Size => this.Rectangle.Size;
public Buffer2D<T> Buffer { get; }
/// <summary>
/// Gets the width
@ -57,14 +60,19 @@ namespace SixLabors.ImageSharp.Memory
public int Height => this.Rectangle.Height;
/// <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>
public int Stride => this.DestinationBuffer.Width;
public int Stride => this.Buffer.Width;
/// <summary>
/// Gets a value indicating whether the area refers to the entire <see cref="DestinationBuffer"/>
/// Gets the size of the area.
/// </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>
/// 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="y">The row index</param>
/// <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];
/// <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];
}
internal ref T this[int x, int y] => ref this.Buffer[x + this.Rectangle.X, y + this.Rectangle.Y];
/// <summary>
/// Gets a span to row 'y' inside this area.
@ -98,11 +94,11 @@ namespace SixLabors.ImageSharp.Memory
int xx = this.Rectangle.X;
int width = this.Rectangle.Width;
return this.DestinationBuffer.GetRowSpan(yy).Slice(xx, width);
return this.Buffer.GetRowSpan(yy).Slice(xx, width);
}
/// <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>
/// <param name="x">The x 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>
/// <returns>The subarea</returns>
[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);
return this.GetSubArea(rectangle);
}
/// <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>
/// <param name="rectangle">The <see cref="Rectangle"/> specifying the boundaries of the subarea</param>
/// <returns>The subarea</returns>
[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.Height, this.Rectangle.Height, nameof(rectangle));
@ -130,15 +126,27 @@ namespace SixLabors.ImageSharp.Memory
int x = this.Rectangle.X + rectangle.X;
int y = this.Rectangle.Y + rectangle.Y;
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.
if (this.IsFullBufferArea)
{
this.DestinationBuffer.FastMemoryGroup.Clear();
this.Buffer.FastMemoryGroup.Clear();
return;
}

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

@ -0,0 +1,90 @@
// 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.
// (Maximum quality while decoding time is still tolerable.)
private const int DefaultMaximumPixels = 8192 * 8192;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultPixelSamplingStrategy"/> class.
/// </summary>
public DefaultPixelSamplingStrategy()
: this(DefaultMaximumPixels)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DefaultPixelSamplingStrategy"/> class.
/// </summary>
/// <param name="maximumPixels">The maximum number of pixels to process.</param>
public DefaultPixelSamplingStrategy(int maximumPixels)
{
Guard.MustBeGreaterThan(maximumPixels, 0, nameof(maximumPixels));
this.MaximumPixels = maximumPixels;
}
/// <summary>
/// Gets the maximum number of pixels to process. (The threshold.)
/// </summary>
public long MaximumPixels { get; }
/// <inheritdoc />
public IEnumerable<BufferRegion<TPixel>> EnumeratePixelRegions<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
long maximumPixels = Math.Min(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;
r = Math.Round(r, 1); // Use a rough approximation to make sure we don't leave out large contiguous regions:
r = Math.Max(0.1, r); // always visit at least 10% 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>;
}
}

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

@ -172,13 +172,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
PixelConversionModifiers conversionModifiers =
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
// for different row intervals of the image.
using (var worker = new ResizeWorker<TPixel>(
configuration,
sourceArea,
sourceRegion,
conversionModifiers,
horizontalKernelMap,
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 BufferArea<TPixel> source;
private readonly BufferRegion<TPixel> source;
private readonly Rectangle sourceRectangle;
@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public ResizeWorker(
Configuration configuration,
BufferArea<TPixel> source,
BufferRegion<TPixel> source,
PixelConversionModifiers conversionModifiers,
ResizeKernelMap horizontalKernelMap,
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 BufferArea<float> destArea;
private BufferRegion<float> destRegion;
[GlobalSetup]
public void Setup()
{
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)]
public void Original()
{
ref float destBase = ref this.destArea.GetReferenceToOrigin();
int destStride = this.destArea.Stride;
ref float destBase = ref this.destRegion.GetReferenceToOrigin();
int destStride = this.destRegion.Stride;
ref Block8x8F src = ref this.block;
@ -92,8 +92,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations
[Benchmark]
public void Original_V2()
{
ref float destBase = ref this.destArea.GetReferenceToOrigin();
int destStride = this.destArea.Stride;
ref float destBase = ref this.destRegion.GetReferenceToOrigin();
int destStride = this.destRegion.Stride;
ref Block8x8F src = ref this.block;
@ -160,8 +160,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations
[Benchmark]
public void UseVector2()
{
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destArea.GetReferenceToOrigin());
int destStride = this.destArea.Stride / 2;
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destRegion.GetReferenceToOrigin());
int destStride = this.destRegion.Stride / 2;
ref Block8x8F src = ref this.block;
@ -220,8 +220,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations
[Benchmark]
public void UseVector4()
{
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destArea.GetReferenceToOrigin());
int destStride = this.destArea.Stride / 2;
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destRegion.GetReferenceToOrigin());
int destStride = this.destRegion.Stride / 2;
ref Block8x8F src = ref this.block;
@ -280,8 +280,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations
[Benchmark]
public void UseVector4_SafeRightCorner()
{
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destArea.GetReferenceToOrigin());
int destStride = this.destArea.Stride / 2;
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destRegion.GetReferenceToOrigin());
int destStride = this.destRegion.Stride / 2;
ref Block8x8F src = ref this.block;
@ -338,8 +338,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations
[Benchmark]
public void UseVector4_V2()
{
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destArea.GetReferenceToOrigin());
int destStride = this.destArea.Stride / 2;
ref Vector2 destBase = ref Unsafe.As<float, Vector2>(ref this.destRegion.GetReferenceToOrigin());
int destStride = this.destRegion.Stride / 2;
ref Block8x8F src = ref this.block;

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))
{
BufferArea<float> area = buffer.GetArea(5, 10, 8, 8);
block.Copy1x1Scale(ref area.GetReferenceToOrigin(), area.Stride);
BufferRegion<float> region = buffer.GetRegion(5, 10, 8, 8);
block.Copy1x1Scale(ref region.GetReferenceToOrigin(), region.Stride);
Assert.Equal(block[0, 0], buffer[5, 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))
{
BufferArea<float> area = buffer.GetArea(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor);
block.ScaledCopyTo(area, horizontalFactor, verticalFactor);
BufferRegion<float> region = buffer.GetRegion(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor);
block.ScaledCopyTo(region, horizontalFactor, verticalFactor);
for (int y = 0; y < 8 * verticalFactor; y++)
{
@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
int xx = x / horizontalFactor;
float expected = block[xx, yy];
float actual = area[x, y];
float actual = region[x, y];
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);
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);
}
@ -47,9 +47,9 @@ namespace SixLabors.ImageSharp.Tests.Memory
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
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;
Assert.Equal(expected, value);
}
@ -66,9 +66,9 @@ namespace SixLabors.ImageSharp.Tests.Memory
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
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);
@ -85,13 +85,13 @@ namespace SixLabors.ImageSharp.Tests.Memory
public void GetSubArea()
{
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);
Assert.Equal(buffer, area1.DestinationBuffer);
Assert.Equal(buffer, area1.Buffer);
Assert.Equal(expectedRect, area1.Rectangle);
int value00 = (12 * 100) + 10;
@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity;
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();
@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
using Buffer2D<int> buffer = this.CreateTestBuffer(22, 13);
var emptyRow = new int[22];
buffer.GetArea().Clear();
buffer.GetRegion().Clear();
for (int y = 0; y < 13; y++)
{
@ -140,8 +140,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity;
using Buffer2D<int> buffer = this.CreateTestBuffer(20, 30);
BufferArea<int> area = buffer.GetArea(5, 5, 10, 10);
area.Clear();
BufferRegion<int> region = buffer.GetRegion(5, 5, 10, 10);
region.Clear();
Assert.NotEqual(0, buffer[4, 4]);
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[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);
Assert.True(span.SequenceEqual(new int[area.Width]));
Span<int> span = buffer.GetRowSpan(y).Slice(region.Rectangle.X, region.Width);
Assert.True(span.SequenceEqual(new int[region.Width]));
}
}
}

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);
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}");
}
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 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;
}
}
}
Loading…
Cancel
Save