From ec57728521d201375f2cb424b0d137341d764fde Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Wed, 6 Sep 2023 21:49:13 +0200 Subject: [PATCH] Added Tests for Images; Small improvements --- ScreenCapture.NET.sln | 11 ++ ScreenCapture.NET.sln.DotSettings | 1 + ScreenCapture.NET/Model/Colors/ColorARGB.cs | 47 +++++ ScreenCapture.NET/Model/Colors/ColorBGR.cs | 45 +++++ ScreenCapture.NET/Model/Colors/ColorFormat.cs | 3 + ScreenCapture.NET/Model/Colors/ColorRGB.cs | 45 +++++ ScreenCapture.NET/Model/IImage.cs | 4 + ScreenCapture.NET/Model/Image.cs | 10 +- ScreenCapture.NET/Model/RefImage.cs | 6 +- Tests/ScreenCapture.NET.Tests/ImageTest.cs | 165 ++++++++++++++++++ Tests/ScreenCapture.NET.Tests/RefImageTest.cs | 165 ++++++++++++++++++ .../ScreenCapture.NET.Tests.csproj | 22 +++ .../TestScreenCapture.cs | 37 ++++ 13 files changed, 559 insertions(+), 2 deletions(-) create mode 100644 ScreenCapture.NET/Model/Colors/ColorARGB.cs create mode 100644 ScreenCapture.NET/Model/Colors/ColorBGR.cs create mode 100644 ScreenCapture.NET/Model/Colors/ColorRGB.cs create mode 100644 Tests/ScreenCapture.NET.Tests/ImageTest.cs create mode 100644 Tests/ScreenCapture.NET.Tests/RefImageTest.cs create mode 100644 Tests/ScreenCapture.NET.Tests/ScreenCapture.NET.Tests.csproj create mode 100644 Tests/ScreenCapture.NET.Tests/TestScreenCapture.cs diff --git a/ScreenCapture.NET.sln b/ScreenCapture.NET.sln index 8f891db..abd9a1d 100644 --- a/ScreenCapture.NET.sln +++ b/ScreenCapture.NET.sln @@ -7,6 +7,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET", "Screen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScreenCapture.NET.DX11", "ScreenCapture.NET.DX11\ScreenCapture.NET.DX11.csproj", "{58A09AD8-D66F-492E-8BC7-62BDB85E57EC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{CF7A1475-3A44-4870-A80F-5988DA25418B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScreenCapture.NET.Tests", "Tests\ScreenCapture.NET.Tests\ScreenCapture.NET.Tests.csproj", "{AA1829BB-EFA7-4BB8-8041-76374659A42B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,10 +25,17 @@ Global {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {58A09AD8-D66F-492E-8BC7-62BDB85E57EC}.Release|Any CPU.Build.0 = Release|Any CPU + {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA1829BB-EFA7-4BB8-8041-76374659A42B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {AA1829BB-EFA7-4BB8-8041-76374659A42B} = {CF7A1475-3A44-4870-A80F-5988DA25418B} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B5111031-6E65-4331-9E6E-A07165289860} EndGlobalSection diff --git a/ScreenCapture.NET.sln.DotSettings b/ScreenCapture.NET.sln.DotSettings index 4097b57..4061c9f 100644 --- a/ScreenCapture.NET.sln.DotSettings +++ b/ScreenCapture.NET.sln.DotSettings @@ -1,4 +1,5 @@  + BGR BGRA DPI DX \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Colors/ColorARGB.cs b/ScreenCapture.NET/Model/Colors/ColorARGB.cs new file mode 100644 index 0000000..c00980f --- /dev/null +++ b/ScreenCapture.NET/Model/Colors/ColorARGB.cs @@ -0,0 +1,47 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorARGB : IColor +{ + #region Properties & Fields + + public static ColorFormat ColorFormat => ColorFormat.ARGB; + + private readonly byte _a; + private readonly byte _r; + private readonly byte _g; + private readonly byte _b; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + public byte A => _a; + public byte R => _r; + public byte G => _g; + public byte B => _b; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + public ColorARGB(byte a, byte r, byte g, byte b) + { + this._a = a; + this._r = r; + this._g = g; + this._b = b; + } + + #endregion + + #region Methods + + public override string ToString() => $"[A: {_a}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Colors/ColorBGR.cs b/ScreenCapture.NET/Model/Colors/ColorBGR.cs new file mode 100644 index 0000000..829d3b3 --- /dev/null +++ b/ScreenCapture.NET/Model/Colors/ColorBGR.cs @@ -0,0 +1,45 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorBGR : IColor +{ + #region Properties & Fields + + public static ColorFormat ColorFormat => ColorFormat.BGR; + + private readonly byte _b; + private readonly byte _g; + private readonly byte _r; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + public byte A => byte.MaxValue; + public byte B => _b; + public byte G => _g; + public byte R => _r; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + public ColorBGR(byte b, byte g, byte r) + { + this._b = b; + this._g = g; + this._r = r; + } + + #endregion + + #region Methods + + public override string ToString() => $"[A: {A}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Colors/ColorFormat.cs b/ScreenCapture.NET/Model/Colors/ColorFormat.cs index c376580..eafc1e6 100644 --- a/ScreenCapture.NET/Model/Colors/ColorFormat.cs +++ b/ScreenCapture.NET/Model/Colors/ColorFormat.cs @@ -5,6 +5,9 @@ public readonly struct ColorFormat #region Instances public static readonly ColorFormat BGRA = new(1, 4); + public static readonly ColorFormat ARGB = new(2, 4); + public static readonly ColorFormat RGB = new(3, 3); + public static readonly ColorFormat BGR = new(4, 3); #endregion diff --git a/ScreenCapture.NET/Model/Colors/ColorRGB.cs b/ScreenCapture.NET/Model/Colors/ColorRGB.cs new file mode 100644 index 0000000..ec4acc6 --- /dev/null +++ b/ScreenCapture.NET/Model/Colors/ColorRGB.cs @@ -0,0 +1,45 @@ +// ReSharper disable ConvertToAutoProperty + +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET; + +[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")] +[StructLayout(LayoutKind.Sequential)] +public readonly struct ColorRGB : IColor +{ + #region Properties & Fields + + public static ColorFormat ColorFormat => ColorFormat.RGB; + + private readonly byte _r; + private readonly byte _g; + private readonly byte _b; + + // ReSharper disable ConvertToAutoPropertyWhenPossible + public byte A => byte.MaxValue; + public byte R => _r; + public byte G => _g; + public byte B => _b; + // ReSharper restore ConvertToAutoPropertyWhenPossible + + #endregion + + #region Constructors + + public ColorRGB(byte r, byte g, byte b) + { + this._r = r; + this._g = g; + this._b = b; + } + + #endregion + + #region Methods + + public override string ToString() => $"[A: {A}, R: {_r}, G: {_g}, B: {_b}]"; + + #endregion +} \ No newline at end of file diff --git a/ScreenCapture.NET/Model/IImage.cs b/ScreenCapture.NET/Model/IImage.cs index bbe18e7..3037521 100644 --- a/ScreenCapture.NET/Model/IImage.cs +++ b/ScreenCapture.NET/Model/IImage.cs @@ -15,21 +15,25 @@ public interface IImage : IEnumerable public interface IImageRows : IEnumerable { + int Count { get; } IImageRow this[int column] { get; } } public interface IImageColumns : IEnumerable { + int Count { get; } IImageColumn this[int column] { get; } } public interface IImageRow : IEnumerable { + int Length { get; } IColor this[int x] { get; } } public interface IImageColumn : IEnumerable { + int Length { get; } IColor this[int y] { get; } } } \ No newline at end of file diff --git a/ScreenCapture.NET/Model/Image.cs b/ScreenCapture.NET/Model/Image.cs index 87a3760..295287a 100644 --- a/ScreenCapture.NET/Model/Image.cs +++ b/ScreenCapture.NET/Model/Image.cs @@ -40,7 +40,7 @@ public sealed class Image : IImage [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) >= Width) || ((y + height) >= Height)) throw new IndexOutOfRangeException(); + if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); return new Image(_buffer, _x + x, _y + y, width, height, _stride); } @@ -100,6 +100,8 @@ public sealed class Image : IImage private readonly int _height; private readonly int _stride; + public int Count => _height; + #endregion #region Indexer @@ -151,6 +153,8 @@ public sealed class Image : IImage private readonly int _start; private readonly int _length; + public int Length => _length; + #endregion #region Indexer @@ -203,6 +207,8 @@ public sealed class Image : IImage private readonly int _height; private readonly int _stride; + public int Count => _width; + #endregion #region Indexer @@ -255,6 +261,8 @@ public sealed class Image : IImage private readonly int _length; private readonly int _step; + public int Length => _length; + #endregion #region Indexer diff --git a/ScreenCapture.NET/Model/RefImage.cs b/ScreenCapture.NET/Model/RefImage.cs index 2fe734d..e15b0a1 100644 --- a/ScreenCapture.NET/Model/RefImage.cs +++ b/ScreenCapture.NET/Model/RefImage.cs @@ -38,7 +38,7 @@ public readonly ref struct RefImage [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) >= Width) || ((y + height) >= Height)) throw new IndexOutOfRangeException(); + 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); } @@ -151,6 +151,8 @@ public readonly ref struct RefImage private readonly int _height; private readonly int _stride; + public int Count => _height; + #endregion #region Indexer @@ -243,6 +245,8 @@ public readonly ref struct RefImage private readonly int _height; private readonly int _stride; + public int Count => _width; + #endregion #region Indexer diff --git a/Tests/ScreenCapture.NET.Tests/ImageTest.cs b/Tests/ScreenCapture.NET.Tests/ImageTest.cs new file mode 100644 index 0000000..5e5694b --- /dev/null +++ b/Tests/ScreenCapture.NET.Tests/ImageTest.cs @@ -0,0 +1,165 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ScreenCapture.NET.Tests; + +[TestClass] +public class ImageTest +{ + #region Properties & Fields + + private static IScreenCapture? _screenCapture; + private static ICaptureZone? _captureZone; + + #endregion + + #region Methods + + [ClassInitialize] + public static void ClassInit(TestContext _) + { + _screenCapture = new TestScreenCapture(); + _captureZone = _screenCapture.RegisterCaptureZone(0, 0, _screenCapture.Display.Width, _screenCapture.Display.Height); + _screenCapture.CaptureScreen(); + } + + [ClassCleanup] + public static void ClassCleanup() + { + _screenCapture?.Dispose(); + _screenCapture = null; + } + + [TestMethod] + public void TestImageFullScreen() + { + IImage image = _captureZone!.Image; + + Assert.AreEqual(_captureZone.Width, image.Width); + Assert.AreEqual(_captureZone.Height, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), image[x, y]); + } + + [TestMethod] + public void TestImageInnerFull() + { + IImage image = _captureZone!.Image; + image = image[0, 0, image.Width, image.Height]; + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), image[x, y]); + } + + [TestMethod] + public void TestImageEnumerator() + { + IImage image = _captureZone!.Image; + + int counter = 0; + foreach (IColor color in image) + { + int x = counter % image.Width; + int y = counter / image.Width; + + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), color); + + counter++; + } + } + + [TestMethod] + public void TestImageInnerPartial() + { + IImage image = _captureZone!.Image; + image = image[163, 280, 720, 13]; + + Assert.AreEqual(720, image.Width); + Assert.AreEqual(13, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(163 + x, 280 + y), image[x, y]); + } + + [TestMethod] + public void TestImageInnerInnerPartial() + { + IImage image = _captureZone!.Image; + image = image[163, 280, 720, 13]; + image = image[15, 2, 47, 8]; + + Assert.AreEqual(47, image.Width); + Assert.AreEqual(8, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(178 + x, 282 + y), image[x, y]); + } + + [TestMethod] + public void TestImageRowIndexer() + { + IImage image = _captureZone!.Image; + + Assert.AreEqual(image.Height, image.Rows.Count); + + for (int y = 0; y < image.Height; y++) + { + IImage.IImageRow row = image.Rows[y]; + Assert.AreEqual(image.Width, row.Length); + for (int x = 0; x < row.Length; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), row[x]); + } + } + + [TestMethod] + public void TestImageRowEnumerator() + { + IImage image = _captureZone!.Image; + + int y = 0; + foreach (IImage.IImageRow row in image.Rows) + { + for (int x = 0; x < row.Length; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), row[x]); + + y++; + } + } + + [TestMethod] + public void TestImageColumnIndexer() + { + IImage image = _captureZone!.Image; + + Assert.AreEqual(image.Width, image.Columns.Count); + + for (int x = 0; x < image.Width; x++) + { + IImage.IImageColumn column = image.Columns[x]; + Assert.AreEqual(image.Height, column.Length); + for (int y = 0; y < column.Length; y++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), column[y]); + } + } + + [TestMethod] + public void TestImageColumnEnumerator() + { + IImage image = _captureZone!.Image; + + int x = 0; + foreach (IImage.IImageColumn column in image.Columns) + { + for (int y = 0; y < column.Length; y++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), column[y]); + + x++; + } + } + + #endregion +} \ No newline at end of file diff --git a/Tests/ScreenCapture.NET.Tests/RefImageTest.cs b/Tests/ScreenCapture.NET.Tests/RefImageTest.cs new file mode 100644 index 0000000..dae38bd --- /dev/null +++ b/Tests/ScreenCapture.NET.Tests/RefImageTest.cs @@ -0,0 +1,165 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ScreenCapture.NET.Tests; + +[TestClass] +public class RefImageTest +{ + #region Properties & Fields + + private static TestScreenCapture? _screenCapture; + private static CaptureZone? _captureZone; + + #endregion + + #region Methods + + [ClassInitialize] + public static void ClassInit(TestContext _) + { + _screenCapture = new TestScreenCapture(); + _captureZone = _screenCapture.RegisterCaptureZone(0, 0, _screenCapture.Display.Width, _screenCapture.Display.Height); + _screenCapture.CaptureScreen(); + } + + [ClassCleanup] + public static void ClassCleanup() + { + _screenCapture?.Dispose(); + _screenCapture = null; + } + + [TestMethod] + public void TestImageFullScreen() + { + RefImage image = _captureZone!.Image; + + Assert.AreEqual(_captureZone.Width, image.Width); + Assert.AreEqual(_captureZone.Height, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), image[x, y]); + } + + [TestMethod] + public void TestImageInnerFull() + { + RefImage image = _captureZone!.Image; + image = image[0, 0, image.Width, image.Height]; + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), image[x, y]); + } + + [TestMethod] + public void TestImageEnumerator() + { + RefImage image = _captureZone!.Image; + + int counter = 0; + foreach (ColorARGB color in image) + { + int x = counter % image.Width; + int y = counter / image.Width; + + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), color); + + counter++; + } + } + + [TestMethod] + public void TestImageInnerPartial() + { + RefImage image = _captureZone!.Image; + image = image[163, 280, 720, 13]; + + Assert.AreEqual(720, image.Width); + Assert.AreEqual(13, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(163 + x, 280 + y), image[x, y]); + } + + [TestMethod] + public void TestImageInnerInnerPartial() + { + RefImage image = _captureZone!.Image; + image = image[163, 280, 720, 13]; + image = image[15, 2, 47, 8]; + + Assert.AreEqual(47, image.Width); + Assert.AreEqual(8, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(178 + x, 282 + y), image[x, y]); + } + + [TestMethod] + public void TestImageRowIndexer() + { + RefImage image = _captureZone!.Image; + + Assert.AreEqual(image.Height, image.Rows.Count); + + for (int y = 0; y < image.Height; y++) + { + ReadOnlyRefEnumerable row = image.Rows[y]; + Assert.AreEqual(image.Width, row.Length); + for (int x = 0; x < row.Length; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), row[x]); + } + } + + [TestMethod] + public void TestImageRowEnumerator() + { + RefImage image = _captureZone!.Image; + + int y = 0; + foreach (ReadOnlyRefEnumerable row in image.Rows) + { + for (int x = 0; x < row.Length; x++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), row[x]); + + y++; + } + } + + [TestMethod] + public void TestImageColumnIndexer() + { + RefImage image = _captureZone!.Image; + + Assert.AreEqual(image.Width, image.Columns.Count); + + for (int x = 0; x < image.Width; x++) + { + ReadOnlyRefEnumerable column = image.Columns[x]; + Assert.AreEqual(image.Height, column.Length); + for (int y = 0; y < column.Length; y++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), column[y]); + } + } + + [TestMethod] + public void TestImageColumnEnumerator() + { + RefImage image = _captureZone!.Image; + + int x = 0; + foreach (ReadOnlyRefEnumerable column in image.Columns) + { + for (int y = 0; y < column.Length; y++) + Assert.AreEqual(TestScreenCapture.GetColorFromLocation(x, y), column[y]); + + x++; + } + } + + #endregion +} \ No newline at end of file diff --git a/Tests/ScreenCapture.NET.Tests/ScreenCapture.NET.Tests.csproj b/Tests/ScreenCapture.NET.Tests/ScreenCapture.NET.Tests.csproj new file mode 100644 index 0000000..fbf4484 --- /dev/null +++ b/Tests/ScreenCapture.NET.Tests/ScreenCapture.NET.Tests.csproj @@ -0,0 +1,22 @@ + + + + net7.0 + enable + + false + true + + + + + + + + + + + + + + diff --git a/Tests/ScreenCapture.NET.Tests/TestScreenCapture.cs b/Tests/ScreenCapture.NET.Tests/TestScreenCapture.cs new file mode 100644 index 0000000..e1de0e5 --- /dev/null +++ b/Tests/ScreenCapture.NET.Tests/TestScreenCapture.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.InteropServices; + +namespace ScreenCapture.NET.Tests; + +internal class TestScreenCapture : AbstractScreenCapture +{ + #region Constructors + + public TestScreenCapture(Rotation rotation = Rotation.None) + : base(new Display(0, "Test", 1920, 1080, rotation, new GraphicsCard(0, "Test", 0, 0))) + { } + + #endregion + + #region Methods + + protected override bool PerformScreenCapture() => true; + + protected override void PerformCaptureZoneUpdate(CaptureZone captureZone, in Span buffer) + { + Span pixels = MemoryMarshal.Cast(buffer); + + for (int y = 0; y < captureZone.Height; y++) + for (int x = 0; x < captureZone.Width; x++) + pixels[(y * captureZone.Width) + x] = GetColorFromLocation(x, y); + } + + public static ColorARGB GetColorFromLocation(int x, int y) + { + byte[] xBytes = BitConverter.GetBytes((short)x); + byte[] yBytes = BitConverter.GetBytes((short)y); + return new ColorARGB(xBytes[0], xBytes[1], yBytes[0], yBytes[1]); + } + + #endregion +} \ No newline at end of file