Browse Source

Add ImageFrame visitor; use Buffer2D Size/Bounds + expose subregion extensions

pull/3125/head
James Jackson-South 2 weeks ago
parent
commit
37f534ea5d
  1. 9
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  2. 21
      src/ImageSharp/Advanced/IImageFrameVisitor.cs
  3. 4
      src/ImageSharp/Advanced/IImageVisitor.cs
  4. 2
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  5. 13
      src/ImageSharp/ImageFrame.cs
  6. 15
      src/ImageSharp/ImageFrame{TPixel}.cs
  7. 1
      src/ImageSharp/Image{TPixel}.cs
  8. 50
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  9. 60
      src/ImageSharp/Memory/Buffer2DRegion{T}.cs
  10. 21
      src/ImageSharp/Memory/Buffer2D{T}.cs
  11. 2
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  12. 2
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  13. 36
      tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs
  14. 8
      tests/ImageSharp.Tests/Memory/BufferAreaTests.cs
  15. 2
      tests/ImageSharp.Tests/Processing/IntegralImageTests.cs
  16. 2
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

9
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -76,6 +76,15 @@ public static class AdvancedImageExtensions
public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default) public static Task AcceptVisitorAsync(this Image source, IImageVisitorAsync visitor, CancellationToken cancellationToken = default)
=> source.AcceptAsync(visitor, cancellationToken); => source.AcceptAsync(visitor, cancellationToken);
/// <summary>
/// Accepts a <see cref="IImageVisitor"/> to implement a double-dispatch pattern in order to
/// apply pixel-specific operations on non-generic <see cref="Image"/> instances
/// </summary>
/// <param name="source">The source image frame.</param>
/// <param name="visitor">The image visitor.</param>
public static void AcceptVisitor(this ImageFrame source, IImageFrameVisitor visitor)
=> source.Accept(visitor);
/// <summary> /// <summary>
/// Gets the representation of the pixels as a <see cref="IMemoryGroup{T}"/> containing the backing pixel data of the image /// Gets the representation of the pixels as a <see cref="IMemoryGroup{T}"/> containing the backing pixel data of the image
/// stored in row major order, as a list of contiguous <see cref="Memory{T}"/> blocks in the source image's pixel format. /// stored in row major order, as a list of contiguous <see cref="Memory{T}"/> blocks in the source image's pixel format.

21
src/ImageSharp/Advanced/IImageFrameVisitor.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Advanced;
/// <summary>
/// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations
/// on non-generic <see cref="ImageFrame"/> instances.
/// </summary>
public interface IImageFrameVisitor
{
/// <summary>
/// Provides a pixel-specific implementation for a given operation.
/// </summary>
/// <param name="frame">The image frame.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam>
public void Visit<TPixel>(ImageFrame<TPixel> frame)
where TPixel : unmanaged, IPixel<TPixel>;
}

4
src/ImageSharp/Advanced/IImageVisitor.cs

@ -16,7 +16,7 @@ public interface IImageVisitor
/// </summary> /// </summary>
/// <param name="image">The image.</param> /// <param name="image">The image.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam> /// <typeparam name="TPixel">The pixel type.</typeparam>
void Visit<TPixel>(Image<TPixel> image) public void Visit<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>;
} }
@ -33,6 +33,6 @@ public interface IImageVisitorAsync
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <typeparam name="TPixel">The pixel type.</typeparam> /// <typeparam name="TPixel">The pixel type.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task VisitAsync<TPixel>(Image<TPixel> image, CancellationToken cancellationToken) public Task VisitAsync<TPixel>(Image<TPixel> image, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>; where TPixel : unmanaged, IPixel<TPixel>;
} }

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

@ -303,7 +303,7 @@ internal sealed class GifEncoderCore
this.WriteGraphicalControlExtension(metadata, stream); this.WriteGraphicalControlExtension(metadata, stream);
Buffer2D<byte> indices = ((IPixelSource)quantized).PixelBuffer; Buffer2D<byte> indices = ((IPixelSource)quantized).PixelBuffer;
Rectangle interest = indices.FullRectangle(); Rectangle interest = indices.Bounds;
bool useLocal = this.colorTableMode == FrameColorTableMode.Local || (metadata.ColorTableMode == FrameColorTableMode.Local); bool useLocal = this.colorTableMode == FrameColorTableMode.Local || (metadata.ColorTableMode == FrameColorTableMode.Local);
int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); int bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length);

13
src/ImageSharp/ImageFrame.cs

@ -71,6 +71,19 @@ public abstract partial class ImageFrame : IConfigurationProvider, IDisposable
/// <param name="disposing">Whether to dispose of managed and unmanaged objects.</param> /// <param name="disposing">Whether to dispose of managed and unmanaged objects.</param>
protected abstract void Dispose(bool disposing); protected abstract void Dispose(bool disposing);
/// <summary>
/// Accepts a <see cref="IImageFrameVisitor"/>.
/// Implemented by <see cref="ImageFrame{TPixel}"/> invoking <see cref="IImageFrameVisitor.Visit{TPixel}"/>
/// with the pixel type of the image.
/// </summary>
/// <param name="visitor">The visitor.</param>
internal abstract void Accept(IImageFrameVisitor visitor);
/// <summary>
/// Copies the pixel data of the image frame to a <see cref="Buffer2D{TDestinationPixel}"/> of a specific pixel type.
/// </summary>
/// <typeparam name="TDestinationPixel">The pixel type of the destination buffer.</typeparam>
/// <param name="destination">The buffer to copy the pixel data to.</param>
internal abstract void CopyPixelsTo<TDestinationPixel>(Buffer2D<TDestinationPixel> destination) internal abstract void CopyPixelsTo<TDestinationPixel>(Buffer2D<TDestinationPixel> destination)
where TDestinationPixel : unmanaged, IPixel<TDestinationPixel>; where TDestinationPixel : unmanaged, IPixel<TDestinationPixel>;

15
src/ImageSharp/ImageFrame{TPixel}.cs

@ -339,6 +339,10 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ref TPixel GetPixelReference(int x, int y) => ref this.PixelBuffer[x, y]; internal ref TPixel GetPixelReference(int x, int y) => ref this.PixelBuffer[x, y];
/// <inheritdoc />
internal override void Accept(IImageFrameVisitor visitor)
=> visitor.Visit(this);
/// <summary> /// <summary>
/// Copies the pixels to a <see cref="Buffer2D{TPixel}"/> of the same size. /// Copies the pixels to a <see cref="Buffer2D{TPixel}"/> of the same size.
/// </summary> /// </summary>
@ -346,7 +350,7 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
/// <exception cref="ArgumentException">ImageFrame{TPixel}.CopyTo(): target must be of the same size!</exception> /// <exception cref="ArgumentException">ImageFrame{TPixel}.CopyTo(): target must be of the same size!</exception>
internal void CopyTo(Buffer2D<TPixel> target) internal void CopyTo(Buffer2D<TPixel> target)
{ {
if (this.Size != target.Size()) if (this.Size != target.Size)
{ {
throw new ArgumentException("ImageFrame<TPixel>.CopyTo(): target must be of the same size!", nameof(target)); throw new ArgumentException("ImageFrame<TPixel>.CopyTo(): target must be of the same size!", nameof(target));
} }
@ -363,8 +367,8 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
{ {
Guard.NotNull(source, nameof(source)); Guard.NotNull(source, nameof(source));
Buffer2D<TPixel>.SwapOrCopyContent(this.PixelBuffer, source.PixelBuffer); _ = Buffer2D<TPixel>.SwapOrCopyContent(this.PixelBuffer, source.PixelBuffer);
this.UpdateSize(this.PixelBuffer.Size()); this.UpdateSize(this.PixelBuffer.Size);
} }
/// <summary> /// <summary>
@ -475,10 +479,7 @@ public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
/// Clears the bitmap. /// Clears the bitmap.
/// </summary> /// </summary>
/// <param name="value">The value to initialize the bitmap with.</param> /// <param name="value">The value to initialize the bitmap with.</param>
internal void Clear(TPixel value) internal void Clear(TPixel value) => this.PixelBuffer.Clear(value);
{
this.PixelBuffer.Clear(value);
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private void VerifyCoords(int x, int y) private void VerifyCoords(int x, int y)

1
src/ImageSharp/Image{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;

50
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -104,60 +104,40 @@ public static class Buffer2DExtensions
} }
/// <summary> /// <summary>
/// Returns a <see cref="Rectangle"/> representing the full area of the buffer. /// Return a <see cref="Buffer2DRegion{T}"/> to the subregion represented by <paramref name="rectangle"/>.
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="Rectangle"/></returns>
internal static Rectangle FullRectangle<T>(this Buffer2D<T> buffer)
where T : struct
=> new(0, 0, buffer.Width, buffer.Height);
/// <summary>
/// Return a <see cref="Buffer2DRegion{T}"/> to the subregion 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 subregion</param> /// <param name="rectangle">The rectangle subregion</param>
/// <returns>The <see cref="Buffer2DRegion{T}"/></returns> /// <returns>The <see cref="Buffer2DRegion{T}"/></returns>
internal static Buffer2DRegion<T> GetRegion<T>(this Buffer2D<T> buffer, Rectangle rectangle) public static Buffer2DRegion<T> GetRegion<T>(this Buffer2D<T> buffer, Rectangle rectangle)
where T : unmanaged => where T : unmanaged =>
new(buffer, rectangle); new(buffer, rectangle);
internal static Buffer2DRegion<T> GetRegion<T>(this Buffer2D<T> buffer, int x, int y, int width, int height) /// <summary>
/// Return a <see cref="Buffer2DRegion{T}"/> to the specified area of <paramref name="buffer"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param>
/// <param name="x">The X coordinate of the region.</param>
/// <param name="y">The Y coordinate of the region.</param>
/// <param name="width">The region width.</param>
/// <param name="height">The region height.</param>
/// <returns>The <see cref="Buffer2DRegion{T}"/>.</returns>
public static Buffer2DRegion<T> GetRegion<T>(this Buffer2D<T> buffer, int x, int y, int width, int height)
where T : unmanaged => where T : unmanaged =>
new(buffer, new Rectangle(x, y, width, height)); new(buffer, new Rectangle(x, y, width, height));
/// <summary> /// <summary>
/// Return a <see cref="Buffer2DRegion{T}"/> to the whole area of 'buffer' /// Return a <see cref="Buffer2DRegion{T}"/> to the whole area of <paramref name="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="Buffer2DRegion{T}"/></returns> /// <returns>The <see cref="Buffer2DRegion{T}"/></returns>
internal static Buffer2DRegion<T> GetRegion<T>(this Buffer2D<T> buffer) public static Buffer2DRegion<T> GetRegion<T>(this Buffer2D<T> buffer)
where T : unmanaged => where T : unmanaged =>
new(buffer); new(buffer);
/// <summary>
/// Returns the size of the buffer.
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="Size{T}"/> of the buffer</returns>
internal static Size Size<T>(this Buffer2D<T> buffer)
where T : struct =>
new(buffer.Width, buffer.Height);
/// <summary>
/// Gets the bounds of the buffer.
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The <see cref="Buffer2D{T}"/></param>
/// <returns>The <see cref="Rectangle"/></returns>
internal static Rectangle Bounds<T>(this Buffer2D<T> buffer)
where T : struct =>
new(0, 0, buffer.Width, buffer.Height);
[Conditional("DEBUG")] [Conditional("DEBUG")]
private static void CheckColumnRegionsDoNotOverlap<T>( private static void CheckColumnRegionsDoNotOverlap<T>(
Buffer2D<T> buffer, Buffer2D<T> buffer,

60
src/ImageSharp/Memory/Buffer2DRegion{T}.cs

@ -15,17 +15,17 @@ public readonly struct Buffer2DRegion<T>
/// Initializes a new instance of the <see cref="Buffer2DRegion{T}"/> struct. /// Initializes a new instance of the <see cref="Buffer2DRegion{T}"/> struct.
/// </summary> /// </summary>
/// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param> /// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param>
/// <param name="rectangle">The <see cref="Rectangle"/> defining a rectangular area within the buffer.</param> /// <param name="bounds">The <see cref="Bounds"/> defining a rectangular area within the buffer.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Buffer2DRegion(Buffer2D<T> buffer, Rectangle rectangle) public Buffer2DRegion(Buffer2D<T> buffer, Rectangle bounds)
{ {
DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle)); DebugGuard.MustBeGreaterThanOrEqualTo(bounds.X, 0, nameof(bounds));
DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle)); DebugGuard.MustBeGreaterThanOrEqualTo(bounds.Y, 0, nameof(bounds));
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, buffer.Width, nameof(rectangle)); DebugGuard.MustBeLessThanOrEqualTo(bounds.Width, buffer.Width, nameof(bounds));
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, buffer.Height, nameof(rectangle)); DebugGuard.MustBeLessThanOrEqualTo(bounds.Height, buffer.Height, nameof(bounds));
this.Buffer = buffer; this.Buffer = buffer;
this.Rectangle = rectangle; this.Bounds = bounds;
} }
/// <summary> /// <summary>
@ -34,15 +34,10 @@ public readonly struct Buffer2DRegion<T>
/// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param> /// <param name="buffer">The <see cref="Buffer2D{T}"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Buffer2DRegion(Buffer2D<T> buffer) public Buffer2DRegion(Buffer2D<T> buffer)
: this(buffer, buffer.FullRectangle()) : this(buffer, buffer.Bounds)
{ {
} }
/// <summary>
/// Gets the rectangle specifying the boundaries of the area in <see cref="Buffer"/>.
/// </summary>
public Rectangle Rectangle { get; }
/// <summary> /// <summary>
/// Gets the <see cref="Buffer2D{T}"/> being pointed by this instance. /// Gets the <see cref="Buffer2D{T}"/> being pointed by this instance.
/// </summary> /// </summary>
@ -51,12 +46,12 @@ public readonly struct Buffer2DRegion<T>
/// <summary> /// <summary>
/// Gets the width /// Gets the width
/// </summary> /// </summary>
public int Width => this.Rectangle.Width; public int Width => this.Bounds.Width;
/// <summary> /// <summary>
/// Gets the height /// Gets the height
/// </summary> /// </summary>
public int Height => this.Rectangle.Height; public int Height => this.Bounds.Height;
/// <summary> /// <summary>
/// Gets the number of elements between row starts in <see cref="Buffer"/>. /// Gets the number of elements between row starts in <see cref="Buffer"/>.
@ -66,12 +61,17 @@ public readonly struct Buffer2DRegion<T>
/// <summary> /// <summary>
/// Gets the size of the area. /// Gets the size of the area.
/// </summary> /// </summary>
internal Size Size => this.Rectangle.Size; public Size Size => this.Bounds.Size;
/// <summary>
/// Gets the rectangle specifying the boundaries of the area in <see cref="Buffer"/>.
/// </summary>
public Rectangle Bounds { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the area refers to the entire <see cref="Buffer"/> /// Gets a value indicating whether the area refers to the entire <see cref="Buffer"/>
/// </summary> /// </summary>
internal bool IsFullBufferArea => this.Size == this.Buffer.Size(); 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.
@ -79,7 +79,7 @@ public readonly struct Buffer2DRegion<T>
/// <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>
internal ref T this[int x, int y] => ref this.Buffer[x + this.Rectangle.X, y + this.Rectangle.Y]; internal ref T this[int x, int y] => ref this.Buffer[x + this.Bounds.X, y + this.Bounds.Y];
/// <summary> /// <summary>
/// Gets a span to row 'y' inside this area. /// Gets a span to row 'y' inside this area.
@ -89,9 +89,9 @@ public readonly struct Buffer2DRegion<T>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> DangerousGetRowSpan(int y) public Span<T> DangerousGetRowSpan(int y)
{ {
int yy = this.Rectangle.Y + y; int yy = this.Bounds.Y + y;
int xx = this.Rectangle.X; int xx = this.Bounds.X;
int width = this.Rectangle.Width; int width = this.Bounds.Width;
return this.Buffer.DangerousGetRowSpan(yy).Slice(xx, width); return this.Buffer.DangerousGetRowSpan(yy).Slice(xx, width);
} }
@ -114,16 +114,16 @@ public readonly struct Buffer2DRegion<T>
/// <summary> /// <summary>
/// Returns a subregion as <see cref="Buffer2DRegion{T}"/>. (Similar to <see cref="Span{T}.Slice(int, int)"/>.) /// Returns a subregion as <see cref="Buffer2DRegion{T}"/>. (Similar to <see cref="Span{T}.Slice(int, int)"/>.)
/// </summary> /// </summary>
/// <param name="rectangle">The <see cref="Rectangle"/> specifying the boundaries of the subregion</param> /// <param name="rectangle">The <see cref="Bounds"/> specifying the boundaries of the subregion</param>
/// <returns>The subregion</returns> /// <returns>The subregion</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Buffer2DRegion<T> GetSubRegion(Rectangle rectangle) public Buffer2DRegion<T> GetSubRegion(Rectangle rectangle)
{ {
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle)); DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Bounds.Width, nameof(rectangle));
DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle)); DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Bounds.Height, nameof(rectangle));
int x = this.Rectangle.X + rectangle.X; int x = this.Bounds.X + rectangle.X;
int y = this.Rectangle.Y + rectangle.Y; int y = this.Bounds.Y + rectangle.Y;
rectangle = new Rectangle(x, y, rectangle.Width, rectangle.Height); rectangle = new Rectangle(x, y, rectangle.Width, rectangle.Height);
return new Buffer2DRegion<T>(this.Buffer, rectangle); return new Buffer2DRegion<T>(this.Buffer, rectangle);
} }
@ -135,8 +135,8 @@ public readonly struct Buffer2DRegion<T>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ref T GetReferenceToOrigin() internal ref T GetReferenceToOrigin()
{ {
int y = this.Rectangle.Y; int y = this.Bounds.Y;
int x = this.Rectangle.X; int x = this.Bounds.X;
return ref this.Buffer.DangerousGetRowSpan(y)[x]; return ref this.Buffer.DangerousGetRowSpan(y)[x];
} }
@ -152,7 +152,7 @@ public readonly struct Buffer2DRegion<T>
return; return;
} }
for (int y = 0; y < this.Rectangle.Height; y++) for (int y = 0; y < this.Bounds.Height; y++)
{ {
Span<T> row = this.DangerousGetRowSpan(y); Span<T> row = this.DangerousGetRowSpan(y);
row.Clear(); row.Clear();
@ -172,7 +172,7 @@ public readonly struct Buffer2DRegion<T>
return; return;
} }
for (int y = 0; y < this.Rectangle.Height; y++) for (int y = 0; y < this.Bounds.Height; y++)
{ {
Span<T> row = this.DangerousGetRowSpan(y); Span<T> row = this.DangerousGetRowSpan(y);
row.Fill(value); row.Fill(value);

21
src/ImageSharp/Memory/Buffer2D{T}.cs

@ -39,20 +39,30 @@ public sealed class Buffer2D<T> : IDisposable
Guard.MustBeGreaterThanOrEqualTo(rowStride, width, nameof(rowStride)); Guard.MustBeGreaterThanOrEqualTo(rowStride, width, nameof(rowStride));
this.FastMemoryGroup = memoryGroup; this.FastMemoryGroup = memoryGroup;
this.Width = width; this.Size = new Size(width, height);
this.Height = height;
this.RowStride = rowStride; this.RowStride = rowStride;
} }
/// <summary> /// <summary>
/// Gets the width. /// Gets the width.
/// </summary> /// </summary>
public int Width { get; private set; } public int Width => this.Size.Width;
/// <summary> /// <summary>
/// Gets the height. /// Gets the height.
/// </summary> /// </summary>
public int Height { get; private set; } public int Height => this.Size.Height;
/// <summary>
/// Gets the size of the buffer.
/// </summary>
public Size Size { get; private set; }
/// <summary>
/// Gets the bounds of the buffer.
/// </summary>
/// <returns>The <see cref="Rectangle"/></returns>
public Rectangle Bounds => new(0, 0, this.Width, this.Height);
/// <summary> /// <summary>
/// Gets the number of elements between row starts in the backing memory. /// Gets the number of elements between row starts in the backing memory.
@ -402,8 +412,7 @@ public sealed class Buffer2D<T> : IDisposable
source.CopyTo(destination); source.CopyTo(destination);
} }
(destination.Width, source.Width) = (source.Width, destination.Width); (destination.Size, source.Size) = (source.Size, destination.Size);
(destination.Height, source.Height) = (source.Height, destination.Height);
(destination.RowStride, source.RowStride) = (source.RowStride, destination.RowStride); (destination.RowStride, source.RowStride) = (source.RowStride, destination.RowStride);
return swapped; return swapped;
} }

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

@ -59,7 +59,7 @@ internal sealed class ResizeWorker<TPixel> : IDisposable
{ {
this.configuration = configuration; this.configuration = configuration;
this.source = source; this.source = source;
this.sourceRectangle = source.Rectangle; this.sourceRectangle = source.Bounds;
this.conversionModifiers = conversionModifiers; this.conversionModifiers = conversionModifiers;
this.horizontalKernelMap = horizontalKernelMap; this.horizontalKernelMap = horizontalKernelMap;
this.verticalKernelMap = verticalKernelMap; this.verticalKernelMap = verticalKernelMap;

2
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -119,7 +119,7 @@ public class SpectralJpegTests
this.Output.WriteLine($"Component{i}: [total: {total} | average: {average}]"); this.Output.WriteLine($"Component{i}: [total: {total} | average: {average}]");
averageDifference += average; averageDifference += average;
totalDifference += total; totalDifference += total;
Size s = libJpegComponent.SpectralBlocks.Size(); Size s = libJpegComponent.SpectralBlocks.Size;
tolerance += s.Width * s.Height; tolerance += s.Width * s.Height;
} }

36
tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -15,26 +15,24 @@ public partial class Buffer2DTests
[Fact] [Fact]
public void SwapOrCopyContent_WhenBothAllocated() public void SwapOrCopyContent_WhenBothAllocated()
{ {
using (Buffer2D<int> a = this.memoryAllocator.Allocate2D<int>(10, 5, AllocationOptions.Clean)) using Buffer2D<int> a = this.memoryAllocator.Allocate2D<int>(10, 5, AllocationOptions.Clean);
using (Buffer2D<int> b = this.memoryAllocator.Allocate2D<int>(3, 7, AllocationOptions.Clean)) using Buffer2D<int> b = this.memoryAllocator.Allocate2D<int>(3, 7, AllocationOptions.Clean);
{ a[1, 3] = 666;
a[1, 3] = 666; b[1, 3] = 444;
b[1, 3] = 444;
Memory<int> aa = a.FastMemoryGroup.Single(); Memory<int> aa = a.FastMemoryGroup.Single();
Memory<int> bb = b.FastMemoryGroup.Single(); Memory<int> bb = b.FastMemoryGroup.Single();
Buffer2D<int>.SwapOrCopyContent(a, b); Buffer2D<int>.SwapOrCopyContent(a, b);
Assert.Equal(bb, a.FastMemoryGroup.Single()); Assert.Equal(bb, a.FastMemoryGroup.Single());
Assert.Equal(aa, b.FastMemoryGroup.Single()); Assert.Equal(aa, b.FastMemoryGroup.Single());
Assert.Equal(new Size(3, 7), a.Size()); Assert.Equal(new Size(3, 7), a.Size);
Assert.Equal(new Size(10, 5), b.Size()); Assert.Equal(new Size(10, 5), b.Size);
Assert.Equal(666, b[1, 3]); Assert.Equal(666, b[1, 3]);
Assert.Equal(444, a[1, 3]); Assert.Equal(444, a[1, 3]);
}
} }
[Fact] [Fact]
@ -171,7 +169,7 @@ public partial class Buffer2DTests
bool swap = Buffer2D<int>.SwapOrCopyContent(dest, source); bool swap = Buffer2D<int>.SwapOrCopyContent(dest, source);
Assert.False(swap); Assert.False(swap);
Assert.Equal(new Size(3, 2), dest.Size()); Assert.Equal(new Size(3, 2), dest.Size);
Assert.Equal(6, dest[2, 1]); Assert.Equal(6, dest[2, 1]);
} }
@ -191,7 +189,7 @@ public partial class Buffer2DTests
bool swap = Buffer2D<int>.SwapOrCopyContent(dest, source); bool swap = Buffer2D<int>.SwapOrCopyContent(dest, source);
Assert.False(swap); Assert.False(swap);
Assert.Equal(new Size(1, 5), dest.Size()); Assert.Equal(new Size(1, 5), dest.Size);
Assert.Equal(1, dest[0, 0]); Assert.Equal(1, dest[0, 0]);
Assert.Equal(2, dest[0, 1]); Assert.Equal(2, dest[0, 1]);
Assert.Equal(3, dest[0, 2]); Assert.Equal(3, dest[0, 2]);
@ -215,7 +213,7 @@ public partial class Buffer2DTests
bool swap = Buffer2D<int>.SwapOrCopyContent(dest, source); bool swap = Buffer2D<int>.SwapOrCopyContent(dest, source);
Assert.False(swap); Assert.False(swap);
Assert.Equal(new Size(2, 2), dest.Size()); Assert.Equal(new Size(2, 2), dest.Size);
Assert.Equal(1, dest[0, 0]); Assert.Equal(1, dest[0, 0]);
Assert.Equal(2, dest[1, 0]); Assert.Equal(2, dest[1, 0]);
Assert.Equal(3, dest[0, 1]); Assert.Equal(3, dest[0, 1]);

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

@ -17,7 +17,7 @@ public class BufferAreaTests
Buffer2DRegion<int> area = new(buffer, rectangle); Buffer2DRegion<int> area = new(buffer, rectangle);
Assert.Equal(buffer, area.Buffer); Assert.Equal(buffer, area.Buffer);
Assert.Equal(rectangle, area.Rectangle); Assert.Equal(rectangle, area.Bounds);
} }
private Buffer2D<int> CreateTestBuffer(int w, int h) private Buffer2D<int> CreateTestBuffer(int w, int h)
@ -90,7 +90,7 @@ public class BufferAreaTests
Rectangle expectedRect = new(10, 12, 5, 5); Rectangle expectedRect = new(10, 12, 5, 5);
Assert.Equal(buffer, area1.Buffer); Assert.Equal(buffer, area1.Buffer);
Assert.Equal(expectedRect, area1.Rectangle); Assert.Equal(expectedRect, area1.Bounds);
int value00 = (12 * 100) + 10; int value00 = (12 * 100) + 10;
Assert.Equal(value00, area1[0, 0]); Assert.Equal(value00, area1[0, 0]);
@ -147,9 +147,9 @@ public class BufferAreaTests
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 = region.Rectangle.Y; y < region.Rectangle.Bottom; y++) for (int y = region.Bounds.Y; y < region.Bounds.Bottom; y++)
{ {
Span<int> span = buffer.DangerousGetRowSpan(y).Slice(region.Rectangle.X, region.Width); Span<int> span = buffer.DangerousGetRowSpan(y).Slice(region.Bounds.X, region.Width);
Assert.True(span.SequenceEqual(new int[region.Width])); Assert.True(span.SequenceEqual(new int[region.Width]));
} }
} }

2
tests/ImageSharp.Tests/Processing/IntegralImageTests.cs

@ -76,7 +76,7 @@ public class IntegralImageTests : BaseImageOperationsExtensionTest
Buffer2D<ulong> integralBuffer, Buffer2D<ulong> integralBuffer,
Func<TPixel, ulong> getPixel) Func<TPixel, ulong> getPixel)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> VerifySumValues(provider, integralBuffer, integralBuffer.Bounds(), getPixel); => VerifySumValues(provider, integralBuffer, integralBuffer.Bounds, getPixel);
private static void VerifySumValues<TPixel>( private static void VerifySumValues<TPixel>(
TestImageProvider<TPixel> provider, TestImageProvider<TPixel> provider,

2
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -505,7 +505,7 @@ public static class TestImageExtensions
public static void CompareBuffers<T>(Buffer2D<T> expected, Buffer2D<T> actual) public static void CompareBuffers<T>(Buffer2D<T> expected, Buffer2D<T> actual)
where T : struct, IEquatable<T> where T : struct, IEquatable<T>
{ {
Assert.True(expected.Size() == actual.Size(), "Buffer sizes are not equal!"); Assert.True(expected.Size == actual.Size, "Buffer sizes are not equal!");
for (int y = 0; y < expected.Height; y++) for (int y = 0; y < expected.Height; y++)
{ {

Loading…
Cancel
Save