diff --git a/ScreenCapture.NET/Extensions/BlackBarDetection.cs b/ScreenCapture.NET/Extensions/BlackBarDetection.cs
index 4042fee..a4c218e 100644
--- a/ScreenCapture.NET/Extensions/BlackBarDetection.cs
+++ b/ScreenCapture.NET/Extensions/BlackBarDetection.cs
@@ -27,7 +27,13 @@ public static IImage RemoveBlackBars(this IImage image, int threshold = 0, bool
return image[left, top, right - left, bottom - top];
}
- private static int CalculateTop(IImage image, int threshold)
+ ///
+ /// Calculates the first row starting from the top with at least one non black pixel.
+ ///
+ /// The image to check.
+ /// The threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.)
+ /// The row number of the first row with at least one non-black pixel.
+ public static int CalculateTop(IImage image, int threshold)
{
IImage.IImageRows rows = image.Rows;
for (int y = 0; y < rows.Count; y++)
@@ -43,7 +49,13 @@ private static int CalculateTop(IImage image, int threshold)
return 0;
}
- private static int CalculateBottom(IImage image, int threshold)
+ ///
+ /// Calculates the last row starting from the top with at least one non black pixel.
+ ///
+ /// The image to check.
+ /// The threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.)
+ /// The row number of the last row with at least one non-black pixel.
+ public static int CalculateBottom(IImage image, int threshold)
{
IImage.IImageRows rows = image.Rows;
for (int y = rows.Count - 1; y >= 0; y--)
@@ -59,7 +71,13 @@ private static int CalculateBottom(IImage image, int threshold)
return rows.Count;
}
- private static int CalculateLeft(IImage image, int threshold)
+ ///
+ /// Calculates the first column starting from the left with at least one non black pixel.
+ ///
+ /// The image to check.
+ /// The threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.)
+ /// The column number of the first column with at least one non-black pixel.
+ public static int CalculateLeft(IImage image, int threshold)
{
IImage.IImageColumns columns = image.Columns;
for (int x = 0; x < columns.Count; x++)
@@ -75,7 +93,13 @@ private static int CalculateLeft(IImage image, int threshold)
return 0;
}
- private static int CalculateRight(IImage image, int threshold)
+ ///
+ /// Calculates the last column starting from the top with at least one non black pixel.
+ ///
+ /// The image to check.
+ /// The threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.)
+ /// The column number of the last column with at least one non-black pixel.
+ public static int CalculateRight(IImage image, int threshold)
{
IImage.IImageColumns columns = image.Columns;
for (int x = columns.Count - 1; x >= 0; x--)
@@ -109,14 +133,20 @@ public static RefImage RemoveBlackBars(this RefImage ima
where TColor : struct, IColor
{
int top = removeTop ? CalculateTop(image, threshold) : 0;
- int bottom = removeBottom ? CalculateBottom(image, threshold) : image.Height;
+ int bottom = removeBottom ? CalculateBottom(image, threshold) : image.Height - 1;
int left = removeLeft ? CalculateLeft(image, threshold) : 0;
- int right = removeRight ? CalculateRight(image, threshold) : image.Width;
+ int right = removeRight ? CalculateRight(image, threshold) : image.Width - 1;
- return image[left, top, right - left, bottom - top];
+ return image[left, top, (right - left) + 1, (bottom - top) + 1];
}
- private static int CalculateTop(this RefImage image, int threshold)
+ ///
+ /// Calculates the first row starting from the top with at least one non black pixel.
+ ///
+ /// The image to check.
+ /// The threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.)
+ /// The row number of the first row with at least one non-black pixel.
+ public static int CalculateTop(this RefImage image, int threshold)
where TColor : struct, IColor
{
RefImage.ImageRows rows = image.Rows;
@@ -133,7 +163,13 @@ private static int CalculateTop(this RefImage image, int thresho
return 0;
}
- private static int CalculateBottom(this RefImage image, int threshold)
+ ///
+ /// Calculates the last row starting from the top with at least one non black pixel.
+ ///
+ /// The image to check.
+ /// The threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.)
+ /// The row number of the last row with at least one non-black pixel.
+ public static int CalculateBottom(this RefImage image, int threshold)
where TColor : struct, IColor
{
RefImage.ImageRows rows = image.Rows;
@@ -150,7 +186,13 @@ private static int CalculateBottom(this RefImage image, int thre
return rows.Count;
}
- private static int CalculateLeft(this RefImage image, int threshold)
+ ///
+ /// Calculates the first column starting from the left with at least one non black pixel.
+ ///
+ /// The image to check.
+ /// The threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.)
+ /// The column number of the first column with at least one non-black pixel.
+ public static int CalculateLeft(this RefImage image, int threshold)
where TColor : struct, IColor
{
RefImage.ImageColumns columns = image.Columns;
@@ -167,7 +209,13 @@ private static int CalculateLeft(this RefImage image, int thresh
return 0;
}
- private static int CalculateRight(this RefImage image, int threshold)
+ ///
+ /// Calculates the last column starting from the top with at least one non black pixel.
+ ///
+ /// The image to check.
+ /// The threshold of "blackness" used to detect black bars. (e. g. Threshold 5 will consider a pixel of color [5,5,5] as black.)
+ /// The column number of the last column with at least one non-black pixel.
+ public static int CalculateRight(this RefImage image, int threshold)
where TColor : struct, IColor
{
RefImage.ImageColumns columns = image.Columns;
diff --git a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs
index 297d7e6..9ff4753 100644
--- a/ScreenCapture.NET/Generic/AbstractScreenCapture.cs
+++ b/ScreenCapture.NET/Generic/AbstractScreenCapture.cs
@@ -240,7 +240,7 @@ public void Dispose()
_isDisposed = true;
}
- ///
+ ///
protected virtual void Dispose(bool disposing) { }
#endregion
diff --git a/ScreenCapture.NET/Model/IImage.cs b/ScreenCapture.NET/Model/IImage.cs
index a406a1f..027b205 100644
--- a/ScreenCapture.NET/Model/IImage.cs
+++ b/ScreenCapture.NET/Model/IImage.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
namespace ScreenCapture.NET;
@@ -7,6 +8,11 @@ namespace ScreenCapture.NET;
///
public interface IImage : IEnumerable
{
+ ///
+ /// Gets the color format used in this image.
+ ///
+ ColorFormat ColorFormat { get; }
+
///
/// Gets the width of this image.
///
@@ -17,6 +23,11 @@ public interface IImage : IEnumerable
///
int Height { get; }
+ ///
+ /// Gets the size in bytes of this image.
+ ///
+ int SizeInBytes { get; }
+
///
/// Gets the color at the specified location.
///
@@ -45,6 +56,28 @@ public interface IImage : IEnumerable
///
IImageColumns Columns { get; }
+ ///
+ /// Gets an representing this .
+ ///
+ /// The color-type of the iamge.
+ /// The .
+ RefImage AsRefImage() where TColor : struct, IColor;
+
+ ///
+ /// Copies the contents of this into a destination instance.
+ ///
+ /// The destination instance.
+ ///
+ /// Thrown when is shorter than the source instance.
+ ///
+ void CopyTo(in Span destination);
+
+ ///
+ /// Allocates a new array and copies this into it.
+ ///
+ /// The new array containing the data of this .
+ byte[] ToArray();
+
///
/// Represents a list of rows of an image.
///
@@ -91,12 +124,32 @@ public interface IImageRow : IEnumerable
///
int Length { get; }
+ ///
+ /// Gets the size in bytes of this row.
+ ///
+ int SizeInBytes { get; }
+
///
/// Gets the at the specified location.
///
/// The location to get the color from.
/// The at the specified location.
IColor this[int x] { get; }
+
+ ///
+ /// Copies the contents of this into a destination instance.
+ ///
+ /// The destination instance.
+ ///
+ /// Thrown when is shorter than the source instance.
+ ///
+ void CopyTo(in Span destination);
+
+ ///
+ /// Allocates a new array and copies this into it.
+ ///
+ /// The new array containing the data of this .
+ byte[] ToArray();
}
///
@@ -109,11 +162,31 @@ public interface IImageColumn : IEnumerable
///
int Length { get; }
+ ///
+ /// Gets the size in bytes of this column.
+ ///
+ int SizeInBytes { get; }
+
///
/// Gets the at the specified location.
///
/// The location to get the color from.
/// The at the specified location.
IColor this[int y] { get; }
+
+ ///
+ /// Copies the contents of this into a destination instance.
+ ///
+ /// The destination instance.
+ ///
+ /// Thrown when is shorter than the source instance.
+ ///
+ void CopyTo(in Span destination);
+
+ ///
+ /// Allocates a new array and copies this into it.
+ ///
+ /// The new array containing the data of this .
+ byte[] ToArray();
}
}
\ No newline at end of file
diff --git a/ScreenCapture.NET/Model/Image.cs b/ScreenCapture.NET/Model/Image.cs
index bb3c1d2..2eb99a0 100644
--- a/ScreenCapture.NET/Model/Image.cs
+++ b/ScreenCapture.NET/Model/Image.cs
@@ -18,12 +18,18 @@ public sealed class Image : IImage
private readonly int _y;
private readonly int _stride;
+ ///
+ public ColorFormat ColorFormat => TColor.ColorFormat;
+
///
public int Width { get; }
///
public int Height { get; }
+ ///
+ public int SizeInBytes => Width * Height * TColor.ColorFormat.BytesPerPixel;
+
#endregion
#region Indexer
@@ -84,6 +90,39 @@ internal Image(byte[] buffer, int x, int y, int width, int height, int stride)
#region Methods
+ ///
+ public void CopyTo(in Span 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 target = destination;
+ foreach (IImage.IImageRow row in rows)
+ {
+ row.CopyTo(target);
+ target = target[targetStride..];
+ }
+ }
+
+ ///
+ public byte[] ToArray()
+ {
+ byte[] array = new byte[SizeInBytes];
+ CopyTo(array);
+ return array;
+ }
+
+ ///
+ public RefImage AsRefImage()
+ 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(MemoryMarshal.Cast(_buffer), _x, _y, Width, Height, _stride);
+ }
+
///
public IEnumerator GetEnumerator()
{
@@ -172,6 +211,9 @@ private sealed class ImageRow : IImage.IImageRow
///
public int Length => _length;
+ ///
+ public int SizeInBytes => Length * TColor.ColorFormat.BytesPerPixel;
+
#endregion
#region Indexer
@@ -203,6 +245,23 @@ internal ImageRow(byte[] buffer, int start, int length)
#region Methods
+ ///
+ public void CopyTo(in Span 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);
+ }
+
+ ///
+ public byte[] ToArray()
+ {
+ byte[] array = new byte[SizeInBytes];
+ CopyTo(array);
+ return array;
+ }
+
///
public IEnumerator GetEnumerator()
{
@@ -290,6 +349,9 @@ private sealed class ImageColumn : IImage.IImageColumn
///
public int Length => _length;
+ ///
+ public int SizeInBytes => Length * TColor.ColorFormat.BytesPerPixel;
+
#endregion
#region Indexer
@@ -301,8 +363,8 @@ public IColor this[int y]
{
if ((y < 0) || (y >= _length)) throw new IndexOutOfRangeException();
- ReadOnlySpan row = MemoryMarshal.Cast(_buffer)[_start..];
- return row[y * _step];
+ ReadOnlySpan data = MemoryMarshal.Cast(_buffer)[_start..];
+ return data[y * _step];
}
}
@@ -322,6 +384,31 @@ internal ImageColumn(byte[] buffer, int start, int length, int step)
#region Methods
+ ///
+ public void CopyTo(in Span 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 data = MemoryMarshal.Cast(_buffer)[_start..];
+ Span target = MemoryMarshal.Cast(destination);
+ for (int i = 0; i < Length; i++)
+ target[i] = data[i * _step];
+ }
+ }
+
+ ///
+ public byte[] ToArray()
+ {
+ byte[] array = new byte[SizeInBytes];
+ CopyTo(array);
+ return array;
+ }
+
///
public IEnumerator GetEnumerator()
{
diff --git a/ScreenCapture.NET/Model/RefImage.cs b/ScreenCapture.NET/Model/RefImage.cs
index e15b0a1..72272c2 100644
--- a/ScreenCapture.NET/Model/RefImage.cs
+++ b/ScreenCapture.NET/Model/RefImage.cs
@@ -13,23 +13,37 @@ public readonly ref struct RefImage
private readonly int _x;
private readonly int _y;
- private readonly int _stride;
+ ///
+ /// Gets the width of the image.
+ ///
public int Width { get; }
+
+ ///
+ /// Gets the height of the image.
+ ///
public int Height { get; }
+ ///
+ /// Gets the stride (entries per row) of the underlying buffer.
+ /// Only useful if you want to work with a pinned buffer.
+ ///
+ 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);
}
}
@@ -40,19 +54,19 @@ public readonly ref struct RefImage
{
if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException();
- return new RefImage(_pixels, _x + x, _y + y, width, height, _stride);
+ return new RefImage(_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
@@ -66,20 +80,27 @@ internal RefImage(ReadOnlySpan 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 dest)
+ ///
+ /// Copies the contents of this into a destination instance.
+ ///
+ /// The destination instance.
+ ///
+ /// Thrown when is shorter than the source instance.
+ ///
+ public void CopyTo(in Span 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 target = dest;
+ Span target = destination;
foreach (ReadOnlyRefEnumerable row in rows)
{
row.CopyTo(target);
@@ -87,6 +108,10 @@ public void CopyTo(in Span dest)
}
}
+ ///
+ /// Allocates a new array and copies this into it.
+ ///
+ /// The new array containing the data of this .
public TColor[] ToArray()
{
TColor[] array = new TColor[Width * Height];
@@ -94,6 +119,18 @@ public TColor[] ToArray()
return array;
}
+ ///
+ /// Returns a reference to the first element of this image inside the full image buffer.
+ ///
+ public ref readonly TColor GetPinnableReference()
+ {
+ if (_pixels.Length == 0)
+ return ref Unsafe.NullRef();
+
+ int offset = (_y * RawStride) + _x;
+ return ref MemoryMarshal.GetReference(_pixels[offset..]);
+ }
+
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ImageEnumerator GetEnumerator() => new(_pixels);
diff --git a/ScreenCapture.NET/ScreenCapture.NET.csproj b/ScreenCapture.NET/ScreenCapture.NET.csproj
index aab9f70..c844fab 100644
--- a/ScreenCapture.NET/ScreenCapture.NET.csproj
+++ b/ScreenCapture.NET/ScreenCapture.NET.csproj
@@ -29,9 +29,9 @@
Reworked most of the data handling and splitted capture-logic into separate packages.
- 2.0.0
- 2.0.0
- 2.0.0
+ 2.0.1
+ 2.0.1
+ 2.0.1
..\bin\
true
diff --git a/Tests/ScreenCapture.NET.Tests/ImageTest.cs b/Tests/ScreenCapture.NET.Tests/ImageTest.cs
index 5e5694b..7834319 100644
--- a/Tests/ScreenCapture.NET.Tests/ImageTest.cs
+++ b/Tests/ScreenCapture.NET.Tests/ImageTest.cs
@@ -161,5 +161,19 @@ public void TestImageColumnEnumerator()
}
}
+ [TestMethod]
+ public void TestAsRefImage()
+ {
+ IImage image = _captureZone!.Image;
+ image = image[163, 280, 720, 13];
+ image = image[15, 2, 47, 8];
+
+ RefImage refImage = image.AsRefImage();
+
+ for (int y = 0; y < image.Height; y++)
+ for (int x = 0; x < image.Width; x++)
+ Assert.AreEqual(image[x, y], refImage[x, y]);
+ }
+
#endregion
}
\ No newline at end of file