Skip to content

Commit

Permalink
Added ToArray and CopyTo Methods to Images; Made RefImage pinnable
Browse files Browse the repository at this point in the history
  • Loading branch information
DarthAffe committed Sep 12, 2023
1 parent f04d270 commit b8b6423
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 14 deletions.
75 changes: 74 additions & 1 deletion ScreenCapture.NET/Model/IImage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;

namespace ScreenCapture.NET;

Expand All @@ -7,6 +8,11 @@ namespace ScreenCapture.NET;
/// </summary>
public interface IImage : IEnumerable<IColor>
{
/// <summary>
/// Gets the color format used in this image.
/// </summary>
ColorFormat ColorFormat { get; }

/// <summary>
/// Gets the width of this image.
/// </summary>
Expand All @@ -17,6 +23,11 @@ public interface IImage : IEnumerable<IColor>
/// </summary>
int Height { get; }

/// <summary>
/// Gets the size in bytes of this image.
/// </summary>
int SizeInBytes { get; }

/// <summary>
/// Gets the color at the specified location.
/// </summary>
Expand Down Expand Up @@ -45,6 +56,28 @@ public interface IImage : IEnumerable<IColor>
/// </summary>
IImageColumns Columns { get; }

/// <summary>
/// Gets an <see cref="RefImage{TColor}"/> representing this <see cref="IImage"/>.
/// </summary>
/// <typeparam name="TColor">The color-type of the iamge.</typeparam>
/// <returns>The <inheritdoc cref="RefImage{TColor}"/>.</returns>
RefImage<TColor> AsRefImage<TColor>() where TColor : struct, IColor;

/// <summary>
/// Copies the contents of this <see cref="IImage"/> into a destination <see cref="Span{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="IImage"/> instance.
/// </exception>
void CopyTo(in Span<byte> destination);

/// <summary>
/// Allocates a new array and copies this <see cref="IImage"/> into it.
/// </summary>
/// <returns>The new array containing the data of this <see cref="IImage"/>.</returns>
byte[] ToArray();

/// <summary>
/// Represents a list of rows of an image.
/// </summary>
Expand Down Expand Up @@ -91,12 +124,32 @@ public interface IImageRow : IEnumerable<IColor>
/// </summary>
int Length { get; }

/// <summary>
/// Gets the size in bytes of this row.
/// </summary>
int SizeInBytes { get; }

/// <summary>
/// Gets the <see cref="IColor"/> at the specified location.
/// </summary>
/// <param name="x">The location to get the color from.</param>
/// <returns>The <see cref="IColor"/> at the specified location.</returns>
IColor this[int x] { get; }

/// <summary>
/// Copies the contents of this <see cref="IImageRow"/> into a destination <see cref="Span{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="IImageRow"/> instance.
/// </exception>
void CopyTo(in Span<byte> destination);

/// <summary>
/// Allocates a new array and copies this <see cref="IImageRow"/> into it.
/// </summary>
/// <returns>The new array containing the data of this <see cref="IImageRow"/>.</returns>
byte[] ToArray();
}

/// <summary>
Expand All @@ -109,11 +162,31 @@ public interface IImageColumn : IEnumerable<IColor>
/// </summary>
int Length { get; }

/// <summary>
/// Gets the size in bytes of this column.
/// </summary>
int SizeInBytes { get; }

/// <summary>
/// Gets the <see cref="IColor"/> at the specified location.
/// </summary>
/// <param name="y">The location to get the color from.</param>
/// <returns>The <see cref="IColor"/> at the specified location.</returns>
IColor this[int y] { get; }

/// <summary>
/// Copies the contents of this <see cref="IImageColumn"/> into a destination <see cref="Span{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="IImageColumn"/> instance.
/// </exception>
void CopyTo(in Span<byte> destination);

/// <summary>
/// Allocates a new array and copies this <see cref="IImageColumn"/> into it.
/// </summary>
/// <returns>The new array containing the data of this <see cref="IImageColumn"/>.</returns>
byte[] ToArray();
}
}
91 changes: 89 additions & 2 deletions ScreenCapture.NET/Model/Image.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@ public sealed class Image<TColor> : IImage
private readonly int _y;
private readonly int _stride;

/// <inheritdoc />
public ColorFormat ColorFormat => TColor.ColorFormat;

/// <inheritdoc />
public int Width { get; }

/// <inheritdoc />
public int Height { get; }

/// <inheritdoc />
public int SizeInBytes => Width * Height * TColor.ColorFormat.BytesPerPixel;

#endregion

#region Indexer
Expand Down Expand Up @@ -84,6 +90,39 @@ internal Image(byte[] buffer, int x, int y, int width, int height, int stride)

#region Methods

/// <inheritdoc />
public void CopyTo(in Span<byte> destination)
{
if (destination == null) throw new ArgumentNullException(nameof(destination));
if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination));

int targetStride = Width * TColor.ColorFormat.BytesPerPixel;
IImage.IImageRows rows = Rows;
Span<byte> target = destination;
foreach (IImage.IImageRow row in rows)
{
row.CopyTo(target);
target = target[targetStride..];
}
}

/// <inheritdoc />
public byte[] ToArray()
{
byte[] array = new byte[SizeInBytes];
CopyTo(array);
return array;
}

/// <inheritdoc />
public RefImage<T> AsRefImage<T>()
where T : struct, IColor
{
if (typeof(T) != typeof(TColor)) throw new ArgumentException("The requested color format does not fit this image.", nameof(T));

return new RefImage<T>(MemoryMarshal.Cast<byte, T>(_buffer), _x, _y, Width, Height, _stride);
}

/// <inheritdoc />
public IEnumerator<IColor> GetEnumerator()
{
Expand Down Expand Up @@ -172,6 +211,9 @@ private sealed class ImageRow : IImage.IImageRow
/// <inheritdoc />
public int Length => _length;

/// <inheritdoc />
public int SizeInBytes => Length * TColor.ColorFormat.BytesPerPixel;

#endregion

#region Indexer
Expand Down Expand Up @@ -203,6 +245,23 @@ internal ImageRow(byte[] buffer, int start, int length)

#region Methods

/// <inheritdoc />
public void CopyTo(in Span<byte> destination)
{
if (destination == null) throw new ArgumentNullException(nameof(destination));
if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination));

_buffer.AsSpan(_start, SizeInBytes).CopyTo(destination);
}

/// <inheritdoc />
public byte[] ToArray()
{
byte[] array = new byte[SizeInBytes];
CopyTo(array);
return array;
}

/// <inheritdoc />
public IEnumerator<IColor> GetEnumerator()
{
Expand Down Expand Up @@ -290,6 +349,9 @@ private sealed class ImageColumn : IImage.IImageColumn
/// <inheritdoc />
public int Length => _length;

/// <inheritdoc />
public int SizeInBytes => Length * TColor.ColorFormat.BytesPerPixel;

#endregion

#region Indexer
Expand All @@ -301,8 +363,8 @@ public IColor this[int y]
{
if ((y < 0) || (y >= _length)) throw new IndexOutOfRangeException();

ReadOnlySpan<TColor> row = MemoryMarshal.Cast<byte, TColor>(_buffer)[_start..];
return row[y * _step];
ReadOnlySpan<TColor> data = MemoryMarshal.Cast<byte, TColor>(_buffer)[_start..];
return data[y * _step];
}
}

Expand All @@ -322,6 +384,31 @@ internal ImageColumn(byte[] buffer, int start, int length, int step)

#region Methods

/// <inheritdoc />
public void CopyTo(in Span<byte> destination)
{
if (destination == null) throw new ArgumentNullException(nameof(destination));
if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination));

if (_step == 1)
_buffer.AsSpan(_start, SizeInBytes).CopyTo(destination);
else
{
ReadOnlySpan<TColor> data = MemoryMarshal.Cast<byte, TColor>(_buffer)[_start..];
Span<TColor> target = MemoryMarshal.Cast<byte, TColor>(destination);
for (int i = 0; i < Length; i++)
target[i] = data[i * _step];
}
}

/// <inheritdoc />
public byte[] ToArray()
{
byte[] array = new byte[SizeInBytes];
CopyTo(array);
return array;
}

/// <inheritdoc />
public IEnumerator<IColor> GetEnumerator()
{
Expand Down
59 changes: 48 additions & 11 deletions ScreenCapture.NET/Model/RefImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,37 @@ public readonly ref struct RefImage<TColor>

private readonly int _x;
private readonly int _y;
private readonly int _stride;

/// <summary>
/// Gets the width of the image.
/// </summary>
public int Width { get; }

/// <summary>
/// Gets the height of the image.
/// </summary>
public int Height { get; }

/// <summary>
/// Gets the stride (entries per row) of the underlying buffer.
/// Only useful if you want to work with a pinned buffer.
/// </summary>
public int RawStride { get; }

#endregion

#region Indexer

public TColor this[int x, int y]
public ref readonly TColor this[int x, int y]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException();

return _pixels[((_y + y) * _stride) + (_x + x)];
ref TColor r0 = ref MemoryMarshal.GetReference(_pixels);
nint offset = (nint)(uint)((_y + y) * RawStride) + (_x + x);
return ref Unsafe.Add(ref r0, offset);
}
}

Expand All @@ -40,19 +54,19 @@ public readonly ref struct RefImage<TColor>
{
if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException();

return new RefImage<TColor>(_pixels, _x + x, _y + y, width, height, _stride);
return new RefImage<TColor>(_pixels, _x + x, _y + y, width, height, RawStride);
}
}

public ImageRows Rows
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(_pixels, _x, _y, Width, Height, _stride);
get => new(_pixels, _x, _y, Width, Height, RawStride);
}
public ImageColumns Columns
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(_pixels, _x, _y, Width, Height, _stride);
get => new(_pixels, _x, _y, Width, Height, RawStride);
}

#endregion
Expand All @@ -66,34 +80,57 @@ internal RefImage(ReadOnlySpan<TColor> pixels, int x, int y, int width, int heig
this._y = y;
this.Width = width;
this.Height = height;
this._stride = stride;
this.RawStride = stride;
}

#endregion

#region Methods

public void CopyTo(in Span<TColor> dest)
/// <summary>
/// Copies the contents of this <see cref="RefImage{TColor}"/> into a destination <see cref="Span{T}"/> instance.
/// </summary>
/// <param name="destination">The destination <see cref="Span{T}"/> instance.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="destination"/> is shorter than the source <see cref="RefImage{TColor}"/> instance.
/// </exception>
public void CopyTo(in Span<TColor> destination)
{
if (dest == null) throw new ArgumentNullException(nameof(dest));
if (dest.Length < (Width * Height)) throw new ArgumentException("The destination is too small to fit this image.", nameof(dest));
if (destination == null) throw new ArgumentNullException(nameof(destination));
if (destination.Length < (Width * Height)) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination));

ImageRows rows = Rows;
Span<TColor> target = dest;
Span<TColor> target = destination;
foreach (ReadOnlyRefEnumerable<TColor> row in rows)
{
row.CopyTo(target);
target = target[Width..];
}
}

/// <summary>
/// Allocates a new array and copies this <see cref="RefImage{TColor}"/> into it.
/// </summary>
/// <returns>The new array containing the data of this <see cref="RefImage{TColor}"/>.</returns>
public TColor[] ToArray()
{
TColor[] array = new TColor[Width * Height];
CopyTo(array);
return array;
}

/// <summary>
/// Returns a reference to the first element of this image inside the full image buffer.
/// </summary>
public ref readonly TColor GetPinnableReference()
{
if (_pixels.Length == 0)
return ref Unsafe.NullRef<TColor>();

int offset = (_y * RawStride) + _x;
return ref MemoryMarshal.GetReference(_pixels[offset..]);
}

/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ImageEnumerator GetEnumerator() => new(_pixels);
Expand Down
Loading

0 comments on commit b8b6423

Please sign in to comment.