From 9d8c45da39a93a5ed8ed52164de1a4d5b03a0936 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Wed, 13 Sep 2023 00:12:48 +0200 Subject: [PATCH 1/4] Made black-var calculations public --- .../Extensions/BlackBarDetection.cs | 70 ++++++++++++++++--- 1 file changed, 59 insertions(+), 11 deletions(-) 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; From f04d270d8ea172e15035445c2436a930fd2e773d Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Wed, 13 Sep 2023 00:13:01 +0200 Subject: [PATCH 2/4] Fixed doc-comment --- ScreenCapture.NET/Generic/AbstractScreenCapture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b8b64230056fd8e2b83e08b58a54c578503863bf Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Wed, 13 Sep 2023 00:13:56 +0200 Subject: [PATCH 3/4] Added ToArray and CopyTo Methods to Images; Made RefImage pinnable --- ScreenCapture.NET/Model/IImage.cs | 75 +++++++++++++++++- ScreenCapture.NET/Model/Image.cs | 91 +++++++++++++++++++++- ScreenCapture.NET/Model/RefImage.cs | 59 +++++++++++--- Tests/ScreenCapture.NET.Tests/ImageTest.cs | 14 ++++ 4 files changed, 225 insertions(+), 14 deletions(-) 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/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 From d5e17f7370d93e7046c8f3d3cdcc11f17fffb586 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Wed, 13 Sep 2023 00:21:56 +0200 Subject: [PATCH 4/4] Bumper version to 2.0.1 --- ScreenCapture.NET/ScreenCapture.NET.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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