From 3e2f7dd310bf104d26746ecb5a92db752d0fb714 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 30 Apr 2020 03:01:34 +0200 Subject: [PATCH] BufferArea -> BufferRegion, introduce PixelSamplingStrategy --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 4 +- src/ImageSharp/Formats/Gif/GifEncoder.cs | 2 + .../Formats/Gif/IGifEncoderOptions.cs | 3 + .../Jpeg/Components/Block8x8F.ScaledCopyTo.cs | 6 +- src/ImageSharp/Image{TPixel}.cs | 12 +++ src/ImageSharp/Memory/Buffer2DExtensions.cs | 26 ++--- .../{BufferArea{T}.cs => BufferRegion{T}.cs} | 92 +++++++++-------- .../DefaultPixelSamplingStrategy.cs | 90 +++++++++++++++++ .../ExtensivePixelSamplingStrategy.cs | 25 +++++ .../Quantization/IPixelSamplingStrategy.cs | 24 +++++ .../Resize/ResizeProcessor{TPixel}.cs | 4 +- .../Transforms/Resize/ResizeWorker.cs | 4 +- .../BlockOperations/Block8x8F_CopyTo2x2.cs | 28 +++--- .../Jpg/Block8x8FTests.CopyToBufferArea.cs | 10 +- .../Memory/BufferAreaTests.cs | 32 +++--- .../PixelSamplingStrategyTests.cs | 99 +++++++++++++++++++ 16 files changed, 362 insertions(+), 99 deletions(-) rename src/ImageSharp/Memory/{BufferArea{T}.cs => BufferRegion{T}.cs} (65%) create mode 100644 src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs create mode 100644 src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs create mode 100644 src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs create mode 100644 tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index e0d8b3cd9..ba94851f8 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -543,8 +543,8 @@ namespace SixLabors.ImageSharp.Formats.Gif return; } - BufferArea pixelArea = frame.PixelBuffer.GetArea(this.restoreArea.Value); - pixelArea.Clear(); + BufferRegion pixelRegion = frame.PixelBuffer.GetRegion(this.restoreArea.Value); + pixelRegion.Clear(); this.restoreArea = null; } diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 53c4c6f3f..aa276cd98 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -25,6 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public GifColorTableMode? ColorTableMode { get; set; } + internal IPixelSamplingStrategy GlobalPixelSamplingStrategy { get; set; } + /// public void Encode(Image image, Stream stream) where TPixel : unmanaged, IPixel diff --git a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs index 5936d30cb..752e6866f 100644 --- a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs +++ b/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 diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs index 064ca7553..2fbc81707 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs +++ b/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. /// [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyTo(in BufferArea area, int horizontalScale, int verticalScale) + public void ScaledCopyTo(in BufferRegion 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)] diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index f6173db97..2ab472853 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -48,6 +48,18 @@ namespace SixLabors.ImageSharp { } + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The color to initialize the pixels with. + public Image(int width, int height, TPixel backgroundColor) + : this(Configuration.Default, width, height, backgroundColor, new ImageMetadata()) + { + } + /// /// Initializes a new instance of the class /// with the height and the width of the image. diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index fea44f52c..d61ac9087 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -80,29 +80,29 @@ namespace SixLabors.ImageSharp.Memory } /// - /// Return a to the subarea represented by 'rectangle' + /// Return a to the subarea represented by 'rectangle' /// /// The element type /// The /// The rectangle subarea - /// The - internal static BufferArea GetArea(this Buffer2D buffer, in Rectangle rectangle) - where T : struct => - new BufferArea(buffer, rectangle); + /// The + internal static BufferRegion GetRegion(this Buffer2D buffer, in Rectangle rectangle) + where T : unmanaged => + new BufferRegion(buffer, rectangle); - internal static BufferArea GetArea(this Buffer2D buffer, int x, int y, int width, int height) - where T : struct => - new BufferArea(buffer, new Rectangle(x, y, width, height)); + internal static BufferRegion GetRegion(this Buffer2D buffer, int x, int y, int width, int height) + where T : unmanaged => + new BufferRegion(buffer, new Rectangle(x, y, width, height)); /// - /// Return a to the whole area of 'buffer' + /// Return a to the whole area of 'buffer' /// /// The element type /// The - /// The - internal static BufferArea GetArea(this Buffer2D buffer) - where T : struct => - new BufferArea(buffer); + /// The + internal static BufferRegion GetRegion(this Buffer2D buffer) + where T : unmanaged => + new BufferRegion(buffer); /// /// Returns the size of the buffer. diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferRegion{T}.cs similarity index 65% rename from src/ImageSharp/Memory/BufferArea{T}.cs rename to src/ImageSharp/Memory/BufferRegion{T}.cs index 076f7f37c..901c3217b 100644 --- a/src/ImageSharp/Memory/BufferArea{T}.cs +++ b/src/ImageSharp/Memory/BufferRegion{T}.cs @@ -6,45 +6,48 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Memory { /// - /// Represents a rectangular area inside a 2D memory buffer (). - /// This type is kind-of 2D Span, but it can live on heap. + /// Represents a rectangular region inside a 2D memory buffer (). /// /// The element type. - internal readonly struct BufferArea - where T : struct + public readonly struct BufferRegion + where T : unmanaged { /// - /// The rectangle specifying the boundaries of the area in . + /// Initializes a new instance of the struct. /// - public readonly Rectangle Rectangle; - + /// The . + /// The defining a rectangular area within the buffer. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferArea(Buffer2D destinationBuffer, Rectangle rectangle) + public BufferRegion(Buffer2D 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; } + /// + /// Initializes a new instance of the struct. + /// + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferArea(Buffer2D destinationBuffer) - : this(destinationBuffer, destinationBuffer.FullRectangle()) + public BufferRegion(Buffer2D buffer) + : this(buffer, buffer.FullRectangle()) { } /// - /// Gets the being pointed by this instance. + /// Gets the rectangle specifying the boundaries of the area in . /// - public Buffer2D DestinationBuffer { get; } + public Rectangle Rectangle { get; } /// - /// Gets the size of the area. + /// Gets the being pointed by this instance. /// - public Size Size => this.Rectangle.Size; + public Buffer2D Buffer { get; } /// /// Gets the width @@ -57,14 +60,19 @@ namespace SixLabors.ImageSharp.Memory public int Height => this.Rectangle.Height; /// - /// Gets the pixel stride which is equal to the width of . + /// Gets the pixel stride which is equal to the width of . /// - public int Stride => this.DestinationBuffer.Width; + public int Stride => this.Buffer.Width; /// - /// Gets a value indicating whether the area refers to the entire + /// Gets the size of the area. /// - public bool IsFullBufferArea => this.Size == this.DestinationBuffer.Size(); + internal Size Size => this.Rectangle.Size; + + /// + /// Gets a value indicating whether the area refers to the entire + /// + internal bool IsFullBufferArea => this.Size == this.Buffer.Size(); /// /// Gets or sets a value at the given index. @@ -72,19 +80,7 @@ namespace SixLabors.ImageSharp.Memory /// The position inside a row /// The row index /// The reference to the value - public ref T this[int x, int y] => ref this.DestinationBuffer[x + this.Rectangle.X, y + this.Rectangle.Y]; - - /// - /// Gets a reference to the [0,0] element. - /// - /// The reference to the [0,0] element - [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]; /// /// 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); } /// - /// Returns a sub-area as . (Similar to .) + /// Returns a sub-area as . (Similar to .) /// /// The x index at the subarea origin. /// The y index at the subarea origin. @@ -110,19 +106,19 @@ namespace SixLabors.ImageSharp.Memory /// The desired height of the subarea. /// The subarea [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferArea GetSubArea(int x, int y, int width, int height) + public BufferRegion GetSubArea(int x, int y, int width, int height) { var rectangle = new Rectangle(x, y, width, height); return this.GetSubArea(rectangle); } /// - /// Returns a sub-area as . (Similar to .) + /// Returns a sub-area as . (Similar to .) /// /// The specifying the boundaries of the subarea /// The subarea [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferArea GetSubArea(Rectangle rectangle) + public BufferRegion 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(this.DestinationBuffer, rectangle); + return new BufferRegion(this.Buffer, rectangle); + } + + /// + /// Gets a reference to the [0,0] element. + /// + /// The reference to the [0,0] element + [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; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/DefaultPixelSamplingStrategy.cs new file mode 100644 index 000000000..6653259c5 --- /dev/null +++ b/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 +{ + /// + /// A pixel sampling strategy that enumerates a limited amount of rows from different frames, + /// if the total number of pixels is over a threshold. + /// + 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; + + /// + /// Initializes a new instance of the class. + /// + public DefaultPixelSamplingStrategy() + : this(DefaultMaximumPixels) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum number of pixels to process. + public DefaultPixelSamplingStrategy(int maximumPixels) + { + Guard.MustBeGreaterThan(maximumPixels, 0, nameof(maximumPixels)); + this.MaximumPixels = maximumPixels; + } + + /// + /// Gets the maximum number of pixels to process. (The threshold.) + /// + public long MaximumPixels { get; } + + /// + public IEnumerable> EnumeratePixelRegions(Image image) + where TPixel : unmanaged, IPixel + { + 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 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 GetRow(int pos) + { + int frameIdx = pos / image.Height; + int y = pos % image.Height; + return image.Frames[frameIdx].PixelBuffer.GetRegion(0, y, image.Width, 1); + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/ExtensivePixelSamplingStrategy.cs new file mode 100644 index 000000000..e2774850a --- /dev/null +++ b/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 +{ + /// + /// A pixel sampling strategy that enumerates all pixels. + /// + public class ExtensivePixelSamplingStrategy : IPixelSamplingStrategy + { + /// + public IEnumerable> EnumeratePixelRegions(Image image) + where TPixel : unmanaged, IPixel + { + foreach (ImageFrame frame in image.Frames) + { + yield return frame.PixelBuffer.GetRegion(); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs b/src/ImageSharp/Processing/Processors/Quantization/IPixelSamplingStrategy.cs new file mode 100644 index 000000000..71b20174d --- /dev/null +++ b/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 +{ + /// + /// Provides an abstraction to enumerate pixel regions within a multi-framed . + /// + public interface IPixelSamplingStrategy + { + /// + /// Enumerates pixel regions within the image as . + /// + /// The image. + /// The pixel type. + /// An enumeration of pixel regions. + IEnumerable> EnumeratePixelRegions(Image image) + where TPixel : unmanaged, IPixel; + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 6eafbda89..630c88bac 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/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 sourceArea = source.PixelBuffer.GetArea(sourceRectangle); + BufferRegion 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( configuration, - sourceArea, + sourceRegion, conversionModifiers, horizontalKernelMap, verticalKernelMap, diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 898809d5a..6cdb7934c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/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 source; + private readonly BufferRegion source; private readonly Rectangle sourceRectangle; @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public ResizeWorker( Configuration configuration, - BufferArea source, + BufferRegion source, PixelConversionModifiers conversionModifiers, ResizeKernelMap horizontalKernelMap, ResizeKernelMap verticalKernelMap, diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs index 76068ab43..0237c8170 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs @@ -18,20 +18,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations private Buffer2D buffer; - private BufferArea destArea; + private BufferRegion destRegion; [GlobalSetup] public void Setup() { this.buffer = Configuration.Default.MemoryAllocator.Allocate2D(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(ref this.destArea.GetReferenceToOrigin()); - int destStride = this.destArea.Stride / 2; + ref Vector2 destBase = ref Unsafe.As(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(ref this.destArea.GetReferenceToOrigin()); - int destStride = this.destArea.Stride / 2; + ref Vector2 destBase = ref Unsafe.As(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(ref this.destArea.GetReferenceToOrigin()); - int destStride = this.destArea.Stride / 2; + ref Vector2 destBase = ref Unsafe.As(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(ref this.destArea.GetReferenceToOrigin()); - int destStride = this.destArea.Stride / 2; + ref Vector2 destBase = ref Unsafe.As(ref this.destRegion.GetReferenceToOrigin()); + int destStride = this.destRegion.Stride / 2; ref Block8x8F src = ref this.block; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs index f55e46c3d..169b9c0fc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs @@ -43,8 +43,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(20, 20, AllocationOptions.Clean)) { - BufferArea area = buffer.GetArea(5, 10, 8, 8); - block.Copy1x1Scale(ref area.GetReferenceToOrigin(), area.Stride); + BufferRegion 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 buffer = Configuration.Default.MemoryAllocator.Allocate2D(100, 100, AllocationOptions.Clean)) { - BufferArea area = buffer.GetArea(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor); - block.ScaledCopyTo(area, horizontalFactor, verticalFactor); + BufferRegion 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); } diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index 77e899a4c..81173b456 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -16,9 +16,9 @@ namespace SixLabors.ImageSharp.Tests.Memory { using Buffer2D buffer = this.memoryAllocator.Allocate2D(10, 20); var rectangle = new Rectangle(3, 2, 5, 6); - var area = new BufferArea(buffer, rectangle); + var area = new BufferRegion(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 buffer = this.CreateTestBuffer(20, 30); var r = new Rectangle(rx, ry, 5, 6); - BufferArea area = buffer.GetArea(r); + BufferRegion 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 buffer = this.CreateTestBuffer(20, 30); var r = new Rectangle(rx, ry, w, h); - BufferArea area = buffer.GetArea(r); + BufferRegion region = buffer.GetRegion(r); - Span span = area.GetRowSpan(y); + Span span = region.GetRowSpan(y); Assert.Equal(w, span.Length); @@ -85,13 +85,13 @@ namespace SixLabors.ImageSharp.Tests.Memory public void GetSubArea() { using Buffer2D buffer = this.CreateTestBuffer(20, 30); - BufferArea area0 = buffer.GetArea(6, 8, 10, 10); + BufferRegion area0 = buffer.GetRegion(6, 8, 10, 10); - BufferArea area1 = area0.GetSubArea(4, 4, 5, 5); + BufferRegion 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 buffer = this.CreateTestBuffer(20, 30); - BufferArea area0 = buffer.GetArea(6, 8, 10, 10); + BufferRegion area0 = buffer.GetRegion(6, 8, 10, 10); ref int r = ref area0.GetReferenceToOrigin(); @@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Memory using Buffer2D 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 buffer = this.CreateTestBuffer(20, 30); - BufferArea area = buffer.GetArea(5, 5, 10, 10); - area.Clear(); + BufferRegion 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 span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); - Assert.True(span.SequenceEqual(new int[area.Width])); + Span span = buffer.GetRowSpan(y).Slice(region.Rectangle.X, region.Width); + Assert.True(span.SequenceEqual(new int[region.Width])); } } } diff --git a/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs b/tests/ImageSharp.Tests/Quantization/PixelSamplingStrategyTests.cs new file mode 100644 index 000000000..0787b56a9 --- /dev/null +++ b/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 DefaultPixelSamplingStrategy_Data = new TheoryData() + { + { 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 image = CreateTestImage(100, 100, 100); + var strategy = new ExtensivePixelSamplingStrategy(); + + foreach (BufferRegion region in strategy.EnumeratePixelRegions(image)) + { + PaintWhite(region); + } + + using Image 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 dummyProvider, int width, int height, int noOfFrames, int maximumNumberOfPixels) + { + using Image image = CreateTestImage(width, height, noOfFrames); + + var strategy = new DefaultPixelSamplingStrategy(maximumNumberOfPixels); + + long visitedPixels = 0; + foreach (BufferRegion 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 region) + { + var white = new L8(255); + for (int y = 0; y < region.Height; y++) + { + region.GetRowSpan(y).Fill(white); + } + } + + private Image CreateTestImage(int width, int height, int noOfFrames, bool paintWhite = false) + { + L8 bg = paintWhite ? new L8(255) : default; + var image = new Image(width, height, bg); + + for (int i = 1; i < noOfFrames; i++) + { + ImageFrame f = image.Frames.CreateFrame(); + if (paintWhite) + { + f.PixelBuffer.MemoryGroup.Fill(bg); + } + } + + return image; + } + } +}