From 2d30dc98ec3404186e4cab150b315863c490b56d Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Sun, 14 Jul 2024 18:00:24 +0200 Subject: [PATCH] Finished image-implementation and added some tests --- HPPH.Test/Image/ImageTest.cs | 162 +++++++ HPPH.Test/Image/RefImageTest.cs | 152 +++++++ HPPH.Test/TestDataHelper.cs | 24 ++ .../ReadOnlyRefEnumerable.cs | 265 ------------ HPPH/HPPH.csproj.DotSettings | 2 + HPPH/Images/IImage.cs | 191 --------- HPPH/Images/Image.cs | 397 ++++-------------- HPPH/Images/ImageColumn.cs | 207 +++++++++ HPPH/Images/ImageColumns.cs | 163 +++++++ HPPH/Images/ImageRow.cs | 183 ++++++++ HPPH/Images/ImageRows.cs | 159 +++++++ HPPH/Images/Interfaces/IImage.cs | 133 ++++++ HPPH/Images/Interfaces/IImageColumn.cs | 41 ++ HPPH/Images/Interfaces/IImageColumns.cs | 19 + HPPH/Images/Interfaces/IImageRow.cs | 39 ++ HPPH/Images/Interfaces/IImageRows.cs | 19 + HPPH/Images/RefImage.cs | 246 ++--------- HPPH/PixelHelper.MinMax.cs | 2 +- 18 files changed, 1405 insertions(+), 999 deletions(-) create mode 100644 HPPH.Test/Image/ImageTest.cs create mode 100644 HPPH.Test/Image/RefImageTest.cs create mode 100644 HPPH.Test/TestDataHelper.cs delete mode 100644 HPPH/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs delete mode 100644 HPPH/Images/IImage.cs create mode 100644 HPPH/Images/ImageColumn.cs create mode 100644 HPPH/Images/ImageColumns.cs create mode 100644 HPPH/Images/ImageRow.cs create mode 100644 HPPH/Images/ImageRows.cs create mode 100644 HPPH/Images/Interfaces/IImage.cs create mode 100644 HPPH/Images/Interfaces/IImageColumn.cs create mode 100644 HPPH/Images/Interfaces/IImageColumns.cs create mode 100644 HPPH/Images/Interfaces/IImageRow.cs create mode 100644 HPPH/Images/Interfaces/IImageRows.cs diff --git a/HPPH.Test/Image/ImageTest.cs b/HPPH.Test/Image/ImageTest.cs new file mode 100644 index 0000000..cf4731a --- /dev/null +++ b/HPPH.Test/Image/ImageTest.cs @@ -0,0 +1,162 @@ +namespace HPPH.Test.Image; + +[TestClass] +public class ImageTest +{ + #region Constants + + private const int TEST_WIDTH = 1920; + private const int TEST_HEIGHT = 1080; + + #endregion + + #region Methods + + [TestMethod] + public void TestImageCreation() + { + IImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT); + + Assert.AreEqual(TEST_WIDTH, image.Width); + Assert.AreEqual(TEST_HEIGHT, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestDataHelper.GetColorFromLocation(x, y), image[x, y]); + } + + [TestMethod] + public void TestImageInnerFull() + { + IImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT); + 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(TestDataHelper.GetColorFromLocation(x, y), image[x, y]); + } + + [TestMethod] + public void TestImageEnumerator() + { + IImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT); + + int counter = 0; + foreach (IColor color in image) + { + int x = counter % image.Width; + int y = counter / image.Width; + + Assert.AreEqual(TestDataHelper.GetColorFromLocation(x, y), color); + + counter++; + } + } + + [TestMethod] + public void TestImageInnerPartial() + { + IImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT); + 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(TestDataHelper.GetColorFromLocation(163 + x, 280 + y), image[x, y]); + } + + [TestMethod] + public void TestImageInnerInnerPartial() + { + IImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT); + 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(TestDataHelper.GetColorFromLocation(178 + x, 282 + y), image[x, y]); + } + + [TestMethod] + public void TestImageRowIndexer() + { + IImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT); + + Assert.AreEqual(image.Height, image.Rows.Count); + + for (int y = 0; y < image.Height; y++) + { + IImageRow row = image.Rows[y]; + Assert.AreEqual(image.Width, row.Length); + for (int x = 0; x < row.Length; x++) + Assert.AreEqual(TestDataHelper.GetColorFromLocation(x, y), row[x]); + } + } + + [TestMethod] + public void TestImageRowEnumerator() + { + IImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT); + + int y = 0; + foreach (IImageRow row in image.Rows) + { + for (int x = 0; x < row.Length; x++) + Assert.AreEqual(TestDataHelper.GetColorFromLocation(x, y), row[x]); + + y++; + } + } + + [TestMethod] + public void TestImageColumnIndexer() + { + IImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT); + + Assert.AreEqual(image.Width, image.Columns.Count); + + for (int x = 0; x < image.Width; x++) + { + IImageColumn column = image.Columns[x]; + Assert.AreEqual(image.Height, column.Length); + for (int y = 0; y < column.Length; y++) + Assert.AreEqual(TestDataHelper.GetColorFromLocation(x, y), column[y]); + } + } + + [TestMethod] + public void TestImageColumnEnumerator() + { + IImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT); + + int x = 0; + foreach (IImageColumn column in image.Columns) + { + for (int y = 0; y < column.Length; y++) + Assert.AreEqual(TestDataHelper.GetColorFromLocation(x, y), column[y]); + + x++; + } + } + + [TestMethod] + public void TestAsRefImage() + { + IImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT); + 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 diff --git a/HPPH.Test/Image/RefImageTest.cs b/HPPH.Test/Image/RefImageTest.cs new file mode 100644 index 0000000..cf043b3 --- /dev/null +++ b/HPPH.Test/Image/RefImageTest.cs @@ -0,0 +1,152 @@ +using System.Diagnostics; + +namespace HPPH.Test.Image; + +[TestClass] +public class RefImageTest +{ + #region Constants + + private const int TEST_WIDTH = 1920; + private const int TEST_HEIGHT = 1080; + + #endregion + + #region Methods + + [TestMethod] + public void TestImageCreation() + { + RefImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT).AsRefImage(); + + Assert.AreEqual(TEST_WIDTH, image.Width); + Assert.AreEqual(TEST_HEIGHT, image.Height); + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + Assert.AreEqual(TestDataHelper.GetColorFromLocation(x, y), image[x, y]); + } + + [TestMethod] + public void TestImageInnerFull() + { + RefImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT).AsRefImage(); + 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(TestDataHelper.GetColorFromLocation(x, y), image[x, y]); + } + + [TestMethod] + public void TestImageEnumerator() + { + RefImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT).AsRefImage(); + + int counter = 0; + foreach (ColorARGB color in image) + { + int x = counter % image.Width; + int y = counter / image.Width; + + if(y == 1) Debugger.Break(); + + Assert.AreEqual(TestDataHelper.GetColorFromLocation(x, y), color); + + counter++; + } + } + + [TestMethod] + public void TestImageInnerPartial() + { + RefImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT).AsRefImage(); + 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(TestDataHelper.GetColorFromLocation(163 + x, 280 + y), image[x, y]); + } + + [TestMethod] + public void TestImageInnerInnerPartial() + { + RefImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT).AsRefImage(); + 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(TestDataHelper.GetColorFromLocation(178 + x, 282 + y), image[x, y]); + } + + [TestMethod] + public void TestImageRowIndexer() + { + RefImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT).AsRefImage(); + + Assert.AreEqual(image.Height, image.Rows.Count); + + for (int y = 0; y < image.Height; y++) + { + ImageRow row = image.Rows[y]; + Assert.AreEqual(image.Width, row.Length); + for (int x = 0; x < row.Length; x++) + Assert.AreEqual(TestDataHelper.GetColorFromLocation(x, y), row[x]); + } + } + + [TestMethod] + public void TestImageRowEnumerator() + { + RefImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT).AsRefImage(); + + int y = 0; + foreach (ImageRow row in image.Rows) + { + for (int x = 0; x < row.Length; x++) + Assert.AreEqual(TestDataHelper.GetColorFromLocation(x, y), row[x]); + + y++; + } + } + + [TestMethod] + public void TestImageColumnIndexer() + { + RefImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT).AsRefImage(); + + Assert.AreEqual(image.Width, image.Columns.Count); + + for (int x = 0; x < image.Width; x++) + { + ImageColumn column = image.Columns[x]; + Assert.AreEqual(image.Height, column.Length); + for (int y = 0; y < column.Length; y++) + Assert.AreEqual(TestDataHelper.GetColorFromLocation(x, y), column[y]); + } + } + + [TestMethod] + public void TestImageColumnEnumerator() + { + RefImage image = TestDataHelper.CreateTestImage(TEST_WIDTH, TEST_HEIGHT).AsRefImage(); + + int x = 0; + foreach (ImageColumn column in image.Columns) + { + for (int y = 0; y < column.Length; y++) + Assert.AreEqual(TestDataHelper.GetColorFromLocation(x, y), column[y]); + + x++; + } + } + + #endregion +} \ No newline at end of file diff --git a/HPPH.Test/TestDataHelper.cs b/HPPH.Test/TestDataHelper.cs new file mode 100644 index 0000000..2351ff1 --- /dev/null +++ b/HPPH.Test/TestDataHelper.cs @@ -0,0 +1,24 @@ +namespace HPPH.Test; + +internal static class TestDataHelper +{ + public static T GetColorFromLocation(int x, int y) + where T : struct, IColor + { + byte[] xBytes = BitConverter.GetBytes((short)x); + byte[] yBytes = BitConverter.GetBytes((short)y); + return (T)T.Create(xBytes[0], xBytes[1], yBytes[0], yBytes[1]); + } + + public static Image CreateTestImage(int width, int height) + where T : struct, IColor + { + T[] buffer = new T[width * height]; + + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + buffer[(y * width) + x] = GetColorFromLocation(x, y); + + return Image.Create(buffer, width, height); + } +} \ No newline at end of file diff --git a/HPPH/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs b/HPPH/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs deleted file mode 100644 index cbc49f2..0000000 --- a/HPPH/CommunityToolkit.HighPerformance/ReadOnlyRefEnumerable.cs +++ /dev/null @@ -1,265 +0,0 @@ -// DarthAffe 05.09.2023: Based on https://github.com/CommunityToolkit/dotnet/blob/b0d6c4f9c0cfb5d860400abb00b0ca1b3e94dfa4/src/CommunityToolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable%7BT%7D.cs - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace HPPH; - -/// -/// A that iterates readonly items from arbitrary memory locations. -/// -/// The type of items to enumerate. -public readonly ref struct ReadOnlyRefEnumerable -{ - #region Properties & Fields - - /// - /// The instance pointing to the first item in the target memory area. - /// - /// The field maps to the total available length. - private readonly ReadOnlySpan _span; - - /// - /// The distance between items in the sequence to enumerate. - /// - /// The distance refers to items, not byte offset. - private readonly int _step; - - /// - /// Gets the total available length for the sequence. - /// - public int Length - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _span.Length; - } - - /// - /// Gets the element at the specified zero-based index. - /// - /// The zero-based index of the element. - /// A reference to the element at the specified index. - /// - /// Thrown when is invalid. - /// - public ref readonly T this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - if ((uint)index >= (uint)Length) throw new IndexOutOfRangeException(); - - ref T r0 = ref MemoryMarshal.GetReference(_span); - nint offset = (nint)(uint)index * (nint)(uint)_step; - ref T ri = ref Unsafe.Add(ref r0, offset); - - return ref ri; - } - } - - /// - /// Gets the element at the specified zero-based index. - /// - /// The zero-based index of the element. - /// A reference to the element at the specified index. - /// - /// Thrown when is invalid. - /// - public ref readonly T this[Index index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref this[index.GetOffset(Length)]; - } - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the struct. - /// - /// A reference to the first item of the sequence. - /// The number of items in the sequence. - /// The distance between items in the sequence to enumerate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlyRefEnumerable(in T reference, int length, int step) - { - this._step = step; - - _span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in reference), length); - } - - #endregion - - #region Methods - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() => new(_span, _step); - - public T[] ToArray() - { - int length = _span.Length; - - // Empty array if no data is mapped - if (length == 0) - return []; - - T[] array = new T[length]; - CopyTo(array); - - return array; - } - - /// - /// Copies the contents of this into a destination instance. - /// - /// The destination instance. - /// - /// Thrown when is shorter than the source instance. - /// - public void CopyTo(Span destination) - { - if (_step == 1) - { - _span.CopyTo(destination); - return; - } - - ref T sourceRef = ref MemoryMarshal.GetReference(_span); - int length = _span.Length; - if ((uint)destination.Length < (uint)length) - throw new ArgumentException("The target span is too short to copy all the current items to."); - - ref T destinationRef = ref MemoryMarshal.GetReference(destination); - - CopyTo(ref sourceRef, ref destinationRef, (nint)(uint)length, (nint)(uint)_step); - } - - /// - /// Attempts to copy the current instance to a destination . - /// - /// The target of the copy operation. - /// Whether or not the operation was successful. - public bool TryCopyTo(Span destination) - { - if (destination.Length >= _span.Length) - { - CopyTo(destination); - return true; - } - - return false; - } - - private static void CopyTo(ref T sourceRef, ref T destinationRef, nint length, nint sourceStep) - { - nint sourceOffset = 0; - nint destinationOffset = 0; - - while (length >= 8) - { - Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); - Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); - Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); - Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); - Unsafe.Add(ref destinationRef, destinationOffset + 4) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); - Unsafe.Add(ref destinationRef, destinationOffset + 5) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); - Unsafe.Add(ref destinationRef, destinationOffset + 6) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); - Unsafe.Add(ref destinationRef, destinationOffset + 7) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); - - length -= 8; - sourceOffset += sourceStep; - destinationOffset += 8; - } - - if (length >= 4) - { - Unsafe.Add(ref destinationRef, destinationOffset + 0) = Unsafe.Add(ref sourceRef, sourceOffset); - Unsafe.Add(ref destinationRef, destinationOffset + 1) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); - Unsafe.Add(ref destinationRef, destinationOffset + 2) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); - Unsafe.Add(ref destinationRef, destinationOffset + 3) = Unsafe.Add(ref sourceRef, sourceOffset += sourceStep); - - length -= 4; - sourceOffset += sourceStep; - destinationOffset += 4; - } - - while (length > 0) - { - Unsafe.Add(ref destinationRef, destinationOffset) = Unsafe.Add(ref sourceRef, sourceOffset); - - length -= 1; - sourceOffset += sourceStep; - destinationOffset += 1; - } - } - - #endregion - - /// - /// A custom enumerator type to traverse items within a instance. - /// - public ref struct Enumerator - { - #region Properties & Fields - - /// - private readonly ReadOnlySpan _span; - - /// - private readonly int _step; - - /// - /// The current position in the sequence. - /// - private int _position; - - /// - public readonly ref readonly T Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - ref T r0 = ref MemoryMarshal.GetReference(_span); - - nint offset = (nint)(uint)_position * (nint)(uint)_step; - ref T ri = ref Unsafe.Add(ref r0, offset); - - return ref ri; - } - } - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the struct. - /// - /// The instance with the info on the items to traverse. - /// The distance between items in the sequence to enumerate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Enumerator(ReadOnlySpan span, int step) - { - this._span = span; - this._step = step; - - _position = -1; - } - - #endregion - - #region Methods - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() => ++_position < _span.Length; - - #endregion - } -} diff --git a/HPPH/HPPH.csproj.DotSettings b/HPPH/HPPH.csproj.DotSettings index f85fea1..cd4b0d3 100644 --- a/HPPH/HPPH.csproj.DotSettings +++ b/HPPH/HPPH.csproj.DotSettings @@ -9,6 +9,8 @@ True True True + True + True True True True diff --git a/HPPH/Images/IImage.cs b/HPPH/Images/IImage.cs deleted file mode 100644 index c1fcc61..0000000 --- a/HPPH/Images/IImage.cs +++ /dev/null @@ -1,191 +0,0 @@ -namespace HPPH; - -/// -/// Represents a image. -/// -public interface IImage : IEnumerable -{ - /// - /// Gets the color format used in this image. - /// - IColorFormat ColorFormat { get; } - - /// - /// Gets the width of this image. - /// - int Width { get; } - - /// - /// Gets the height of this image. - /// - int Height { get; } - - /// - /// Gets the size in bytes of this image. - /// - int SizeInBytes { get; } - - /// - /// Gets the color at the specified location. - /// - /// The X-location to read. - /// The Y-location to read. - /// The color at the specified location. - IColor this[int x, int y] { get; } - - /// - /// Gets an image representing the specified location. - /// - /// The X-location of the image. - /// The Y-location of the image. - /// The width of the sub-image. - /// - /// - IImage this[int x, int y, int width, int height] { get; } - - /// - /// Gets a list of all rows of this image. - /// - IImageRows Rows { get; } - - /// - /// Gets a list of all columns of this image. - /// - 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(Span destination); - - /// - /// Allocates a new array and copies this into it. - /// - /// The new array containing the data of this . - byte[] ToRawArray(); - - IColor[] ToArray(); - - /// - /// Represents a list of rows of an image. - /// - public interface IImageRows : IEnumerable - { - /// - /// Gets the amount of rows in this list. - /// - int Count { get; } - - /// - /// Gets a specific . - /// - /// The ´row to get. - /// The requested . - IImageRow this[int column] { get; } - } - - /// - /// Represents a list of columns of an image. - /// - public interface IImageColumns : IEnumerable - { - /// - /// Gets the amount of columns in this list. - /// - int Count { get; } - - /// - /// Gets a specific . - /// - /// The column to get. - /// The requested . - IImageColumn this[int column] { get; } - } - - /// - /// Represents a single row of an image. - /// - public interface IImageRow : IEnumerable - { - /// - /// Gets the length of the row. - /// - 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(Span destination); - - /// - /// Allocates a new array and copies this into it. - /// - /// The new array containing the data of this . - byte[] ToArray(); - } - - /// - /// Represents a single column of an image. - /// - public interface IImageColumn : IEnumerable - { - /// - /// Gets the length of the column. - /// - 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(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/HPPH/Images/Image.cs b/HPPH/Images/Image.cs index 109bc03..821797b 100644 --- a/HPPH/Images/Image.cs +++ b/HPPH/Images/Image.cs @@ -6,8 +6,8 @@ namespace HPPH; /// [SkipLocalsInit] -public sealed class Image : IImage - where TColor : struct, IColor +public sealed class Image : IImage + where T : struct, IColor { #region Properties & Fields @@ -21,7 +21,7 @@ public sealed class Image : IImage public IColorFormat ColorFormat { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => TColor.ColorFormat; + get => T.ColorFormat; } /// @@ -37,42 +37,59 @@ public sealed class Image : IImage #region Indexer + IColor IImage.this[int x, int y] => this[x, y]; + /// - public IColor this[int x, int y] + public ref readonly T this[int x, int y] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException(); - return MemoryMarshal.Cast(_buffer.AsSpan()[((_y + y) * _stride)..])[_x + x]; + return ref Unsafe.Add(ref Unsafe.As(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer.AsSpan()), (nint)(uint)((_y + y) * _stride))), (nint)(uint)(_x + x)); + } + } + + + IImage IImage.this[int x, int y, int width, int height] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + 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); } } /// - public IImage this[int x, int y, int width, int height] + public RefImage this[int x, int y, int width, int height] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { 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); + return new RefImage(_buffer, _x + x, _y + y, width, height, _stride); } } - /// - public IImage.IImageRows Rows - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new ImageRows(_buffer, _x, _y, Width, Height, _stride); - } + IImageRows IImage.Rows => new IColorImageRows(_buffer, _x, _y, Width, Height, _stride); /// - public IImage.IImageColumns Columns + public ImageRows Rows { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new ImageColumns(_buffer, _x, _y, Width, Height, _stride); + get => new(_buffer, _x, _y, Width, Height, _stride); + } + + IImageColumns IImage.Columns => new IColorImageColumns(_buffer, _x, _y, Width, Height, _stride); + /// + public ImageColumns Columns + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new(_buffer, _x, _y, Width, Height, _stride); } #endregion @@ -93,19 +110,21 @@ public sealed class Image : IImage #region Methods - public static Image Create(ReadOnlySpan buffer, int width, int height) - => Create(MemoryMarshal.AsBytes(buffer), width, height, width * TColor.ColorFormat.BytesPerPixel); + public static Image Create(ReadOnlySpan buffer, int width, int height) + => Create(MemoryMarshal.AsBytes(buffer), width, height, width * T.ColorFormat.BytesPerPixel); - public static Image Create(ReadOnlySpan buffer, int width, int height, int stride) + public static Image Create(ReadOnlySpan buffer, int width, int height, int stride) { if (stride < width) throw new ArgumentException("Stride can't be smaller than width."); if (buffer.Length < (height * stride)) throw new ArgumentException("Not enough data in the buffer."); byte[] data = new byte[buffer.Length]; buffer.CopyTo(data); - return new Image(data, 0, 0, width, height, stride); + return new Image(data, 0, 0, width, height, stride); } + public void CopyTo(Span destination) => CopyTo(MemoryMarshal.AsBytes(destination)); + /// public void CopyTo(Span destination) { @@ -113,9 +132,9 @@ public sealed class Image : IImage if (destination.Length < SizeInBytes) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination)); int targetStride = Width * ColorFormat.BytesPerPixel; - IImage.IImageRows rows = Rows; + ImageRows rows = Rows; Span target = destination; - foreach (IImage.IImageRow row in rows) + foreach (ImageRow row in rows) { row.CopyTo(target); target = target[targetStride..]; @@ -130,324 +149,62 @@ public sealed class Image : IImage return array; } - //TODO DarthAffe 06.07.2024: This has some potential for optimization - public IColor[] ToArray() + public T[] ToArray() + { + T[] colors = new T[Width * Height]; + CopyTo(colors); + return colors; + } + + //TODO DarthAffe 11.07.2024: This has some potential for optimization + IColor[] IImage.ToArray() { IColor[] colors = new IColor[Width * Height]; int counter = 0; - foreach (IImage.IImageRow row in Rows) - foreach (IColor color in row) + foreach (ImageRow row in Rows) + foreach (T color in row) colors[counter++] = color; return colors; } - /// - 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)); + public RefImage AsRefImage() => new(_buffer, _x, _y, Width, Height, _stride); - return new RefImage(_buffer, _x, _y, Width, Height, _stride); + public RefImage AsRefImage() + where TColor : struct, IColor + { + if (typeof(TColor) != typeof(T)) throw new ArgumentException("The requested color format does not fit this image.", nameof(TColor)); + + return new RefImage(_buffer, _x, _y, Width, Height, _stride); } - /// - public IEnumerator GetEnumerator() + /// + /// Returns a reference to the first element of this image inside the full image buffer. + /// + public ref readonly byte GetPinnableReference() + { + if (_buffer.Length == 0) + return ref Unsafe.NullRef(); + + return ref MemoryMarshal.GetReference(new ReadOnlySpan(_buffer)[((_y * _stride) + (_x * ColorFormat.BytesPerPixel))..]); + } + + public IEnumerator GetEnumerator() + { + for (int y = 0; y < Height; y++) + for (int x = 0; x < Width; x++) + yield return this[x, y]; + } + + IEnumerator IEnumerable.GetEnumerator() { for (int y = 0; y < Height; y++) for (int x = 0; x < Width; x++) yield return this[x, y]; } - /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion - - #region Indexer-Classes - - /// - private sealed class ImageRows : IImage.IImageRows - { - #region Properties & Fields - - private readonly byte[] _buffer; - private readonly int _x; - private readonly int _y; - private readonly int _width; - private readonly int _height; - private readonly int _stride; - - /// - public int Count => _height; - - #endregion - - #region Indexer - - /// - public IImage.IImageRow this[int row] - { - get - { - if ((row < 0) || (row >= _height)) throw new IndexOutOfRangeException(); - - return new ImageRow(_buffer, ((row + _y) * _stride) + (_x * TColor.ColorFormat.BytesPerPixel), _width); - } - } - - #endregion - - #region Constructors - - internal ImageRows(byte[] buffer, int x, int y, int width, int height, int stride) - { - this._buffer = buffer; - this._x = x; - this._y = y; - this._width = width; - this._height = height; - this._stride = stride; - } - - #endregion - - #region Methods - - /// - public IEnumerator GetEnumerator() - { - for (int y = 0; y < _height; y++) - yield return this[y]; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - #endregion - } - - /// - private sealed class ImageRow : IImage.IImageRow - { - #region Properties & Fields - - private readonly byte[] _buffer; - private readonly int _start; - private readonly int _length; - - /// - public int Length => _length; - - /// - public int SizeInBytes => Length * TColor.ColorFormat.BytesPerPixel; - - #endregion - - #region Indexer - - /// - public IColor this[int x] - { - get - { - if ((x < 0) || (x >= _length)) throw new IndexOutOfRangeException(); - - return MemoryMarshal.Cast(_buffer)[_start..][x]; - } - } - - #endregion - - #region Constructors - - internal ImageRow(byte[] buffer, int start, int length) - { - this._buffer = buffer; - this._start = start; - this._length = length; - } - - #endregion - - #region Methods - - /// - public void CopyTo(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().Slice(_start, SizeInBytes).CopyTo(destination); - } - - /// - public byte[] ToArray() - { - byte[] array = new byte[SizeInBytes]; - CopyTo(array); - return array; - } - - /// - public IEnumerator GetEnumerator() - { - for (int x = 0; x < _length; x++) - yield return this[x]; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - #endregion - } - - /// - private sealed class ImageColumns : IImage.IImageColumns - { - #region Properties & Fields - - private readonly byte[] _buffer; - private readonly int _x; - private readonly int _y; - private readonly int _width; - private readonly int _height; - private readonly int _stride; - - /// - public int Count => _width; - - #endregion - - #region Indexer - - /// - public IImage.IImageColumn this[int column] - { - get - { - if ((column < 0) || (column >= _width)) throw new IndexOutOfRangeException(); - - return new ImageColumn(_buffer, (_y * _stride) + ((_x + column) * TColor.ColorFormat.BytesPerPixel), _height, _stride); - } - } - - #endregion - - #region Constructors - - internal ImageColumns(byte[] buffer, int x, int y, int width, int height, int stride) - { - this._buffer = buffer; - this._x = x; - this._y = y; - this._width = width; - this._height = height; - this._stride = stride; - } - - #endregion - - #region Methods - - /// - public IEnumerator GetEnumerator() - { - for (int y = 0; y < _height; y++) - yield return this[y]; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - #endregion - } - - /// - private sealed class ImageColumn : IImage.IImageColumn - { - #region Properties & Fields - - private readonly byte[] _buffer; - private readonly int _start; - private readonly int _length; - private readonly int _step; - - /// - public int Length => _length; - - /// - public int SizeInBytes => Length * TColor.ColorFormat.BytesPerPixel; - - #endregion - - #region Indexer - - /// - public IColor this[int y] - { - get - { - if ((y < 0) || (y >= _length)) throw new IndexOutOfRangeException(); - - return MemoryMarshal.Cast(_buffer.AsSpan()[_start..])[y * _step]; - } - } - - #endregion - - #region Constructors - - internal ImageColumn(byte[] buffer, int start, int length, int step) - { - this._buffer = buffer; - this._start = start; - this._length = length; - this._step = step; - } - - #endregion - - #region Methods - - /// - public void CopyTo(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.AsSpan()[_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() - { - for (int y = 0; y < _length; y++) - yield return this[y]; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - #endregion - } - - #endregion } \ No newline at end of file diff --git a/HPPH/Images/ImageColumn.cs b/HPPH/Images/ImageColumn.cs new file mode 100644 index 0000000..7ee3a8a --- /dev/null +++ b/HPPH/Images/ImageColumn.cs @@ -0,0 +1,207 @@ +using System.Collections; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace HPPH; + +[SkipLocalsInit] +public readonly ref struct ImageColumn + where T : struct, IColor +{ + #region Properties & Fields + + private readonly ReadOnlySpan _buffer; + private readonly int _start; + private readonly int _length; + private readonly int _step; + + public int Length => _length; + + public int SizeInBytes => Length * T.ColorFormat.BytesPerPixel; + + #endregion + + #region Indexer + + public ref readonly T this[int y] + { + get + { + if ((y < 0) || (y >= _length)) throw new IndexOutOfRangeException(); + + return ref Unsafe.As(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer), (nint)(uint)(_start + (y * _step)))); + } + } + + #endregion + + #region Constructors + + internal ImageColumn(ReadOnlySpan buffer, int start, int length, int step) + { + this._buffer = buffer; + this._start = start; + this._length = length; + this._step = step; + } + + #endregion + + #region Methods + + public void CopyTo(Span destination) => CopyTo(MemoryMarshal.AsBytes(destination)); + + public void CopyTo(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.Slice(_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 T[] ToArray() + { + T[] array = new T[Length]; + CopyTo(array); + return array; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageColumnEnumerator GetEnumerator() => new(this); + + #endregion + + public ref struct ImageColumnEnumerator + { + #region Properties & Fields + + private readonly ImageColumn _column; + private int _position; + + /// + public readonly T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _column[_position]; + } + + #endregion + + #region Constructors + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageColumnEnumerator(ImageColumn column) + { + this._column = column; + this._position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _column.Length; + + #endregion + } +} + +//HACK DarthAffe 14.07.2024: Not nice, should be removed once ref structs are able to implement interfaces (https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md) +[SkipLocalsInit] +internal class IColorImageColumn : IImageColumn + where T : struct, IColor +{ + #region Properties & Fields + + private readonly byte[] _buffer; + private readonly int _start; + private readonly int _length; + private readonly int _step; + + public int Length => _length; + + public int SizeInBytes => Length * T.ColorFormat.BytesPerPixel; + + #endregion + + #region Indexer + + public IColor this[int y] + { + get + { + if ((y < 0) || (y >= _length)) throw new IndexOutOfRangeException(); + + return MemoryMarshal.Cast(_buffer.AsSpan()[(_start + (y * _step))..])[0]; + } + } + + #endregion + + #region Constructors + + internal IColorImageColumn(byte[] buffer, int start, int length, int step) + { + this._buffer = buffer; + this._start = start; + this._length = length; + this._step = step; + } + + #endregion + + #region Methods + + public void CopyTo(Span destination) + { + if (destination == null) throw new ArgumentNullException(nameof(destination)); + if (destination.Length < _length) throw new ArgumentException("The destination is too small to fit this image.", nameof(destination)); + + for (int i = 0; i < _length; i++) + destination[i] = this[i]; + } + + public void CopyTo(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().Slice(_start, SizeInBytes).CopyTo(destination); + else + { + ReadOnlySpan data = MemoryMarshal.Cast(_buffer.AsSpan()[_start..]); + Span target = MemoryMarshal.Cast(destination); + for (int i = 0; i < Length; i++) + target[i] = data[i * _step]; + } + } + + public IColor[] ToArray() + { + IColor[] array = new IColor[Length]; + CopyTo(array); + return array; + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Length; i++) + yield return this[i]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Images/ImageColumns.cs b/HPPH/Images/ImageColumns.cs new file mode 100644 index 0000000..a9f3326 --- /dev/null +++ b/HPPH/Images/ImageColumns.cs @@ -0,0 +1,163 @@ +using System.Collections; +using System.Runtime.CompilerServices; + +namespace HPPH; + +[SkipLocalsInit] +public readonly ref struct ImageColumns + where T : struct, IColor +{ + #region Properties & Fields + + private readonly ReadOnlySpan _data; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + private readonly int _bpp; + + public int Count => _width; + + #endregion + + #region Indexer + + public ImageColumn this[int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((column < 0) || (column > _width)) throw new IndexOutOfRangeException(); + + return new ImageColumn(_data, (_y * _stride) + ((column + _x) * _bpp), _height, _stride); + } + } + + #endregion + + #region Constructors + + // ReSharper disable once ConvertToPrimaryConstructor - Not possible with ref types + internal ImageColumns(ReadOnlySpan data, int x, int y, int width, int height, int stride) + { + this._data = data; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + + _bpp = T.ColorFormat.BytesPerPixel; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageColumnsEnumerator GetEnumerator() => new(this); + + #endregion + + public ref struct ImageColumnsEnumerator + { + #region Properties & Fields + + private readonly ImageColumns _columns; + private int _position; + + /// + public readonly ImageColumn Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _columns[_position]; + } + + #endregion + + #region Constructors + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageColumnsEnumerator(ImageColumns columns) + { + this._columns = columns; + this._position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _columns._width; + + #endregion + } +} + +//HACK DarthAffe 14.07.2024: Not nice, should be removed once ref structs are able to implement interfaces (https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md) +[SkipLocalsInit] +internal class IColorImageColumns : IImageColumns + where T : struct, IColor +{ + #region Properties & Fields + + private readonly byte[] _data; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + private readonly int _bpp; + + public int Count => _width; + + #endregion + + #region Indexer + + public IImageColumn this[int column] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((column < 0) || (column > _width)) throw new IndexOutOfRangeException(); + + return new IColorImageColumn(_data, (_y * _stride) + ((column + _x) * _bpp), _height, _stride); + } + } + + #endregion + + #region Constructors + + // ReSharper disable once ConvertToPrimaryConstructor - Not possible with ref types + internal IColorImageColumns(byte[] data, int x, int y, int width, int height, int stride) + { + this._data = data; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + + _bpp = T.ColorFormat.BytesPerPixel; + } + + #endregion + + #region Methods + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < _width; i++) + yield return this[i]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Images/ImageRow.cs b/HPPH/Images/ImageRow.cs new file mode 100644 index 0000000..793e3f5 --- /dev/null +++ b/HPPH/Images/ImageRow.cs @@ -0,0 +1,183 @@ +using System.Collections; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace HPPH; + +[SkipLocalsInit] +public readonly ref struct ImageRow + where T : struct, IColor +{ + #region Properties & Fields + + private readonly ReadOnlySpan _buffer; + private readonly int _start; + private readonly int _length; + + public int Length => _length; + + public int SizeInBytes => Length * T.ColorFormat.BytesPerPixel; + + #endregion + + #region Indexer + + public ref readonly T this[int x] + { + get + { + if ((x < 0) || (x >= _length)) throw new IndexOutOfRangeException(); + + return ref Unsafe.Add(ref Unsafe.As(ref Unsafe.Add(ref MemoryMarshal.GetReference(_buffer), (nint)(uint)_start)), (nint)(uint)x); + } + } + + #endregion + + #region Constructors + + internal ImageRow(ReadOnlySpan buffer, int start, int length) + { + this._buffer = buffer; + this._start = start; + this._length = length; + } + + #endregion + + #region Methods + + public void CopyTo(Span destination) => CopyTo(MemoryMarshal.AsBytes(destination)); + + public void CopyTo(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.Slice(_start, SizeInBytes).CopyTo(destination); + } + + public T[] ToArray() + { + T[] array = new T[Length]; + CopyTo(array); + return array; + } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageRowEnumerator GetEnumerator() => new(this); + + #endregion + + public ref struct ImageRowEnumerator + { + #region Properties & Fields + + private readonly ImageRow _column; + private int _position; + + /// + public readonly T Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _column[_position]; + } + + #endregion + + #region Constructors + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageRowEnumerator(ImageRow column) + { + this._column = column; + this._position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _column.Length; + + #endregion + } +} + +//HACK DarthAffe 14.07.2024: Not nice, should be removed once ref structs are able to implement interfaces (https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md) +[SkipLocalsInit] +internal class IColorImageRow : IImageRow + where T : struct, IColor +{ + #region Properties & Fields + + private readonly byte[] _buffer; + private readonly int _start; + private readonly int _length; + + public int Length => _length; + + public int SizeInBytes => Length * T.ColorFormat.BytesPerPixel; + + #endregion + + #region Indexer + + public IColor this[int x] + { + get + { + if ((x < 0) || (x >= _length)) throw new IndexOutOfRangeException(); + + return MemoryMarshal.Cast(_buffer.AsSpan()[_start..])[x]; + } + } + + #endregion + + #region Constructors + + internal IColorImageRow(byte[] buffer, int start, int length) + { + this._buffer = buffer; + this._start = start; + this._length = length; + } + + #endregion + + #region Methods + + public void CopyTo(Span destination) + { + for (int i = 0; i < _length; i++) + destination[i] = this[i]; + } + + public void CopyTo(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().Slice(_start, SizeInBytes).CopyTo(destination); + } + + public IColor[] ToArray() + { + IColor[] array = new IColor[Length]; + CopyTo(array); + return array; + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < _length; i++) + yield return this[i]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Images/ImageRows.cs b/HPPH/Images/ImageRows.cs new file mode 100644 index 0000000..22eb1af --- /dev/null +++ b/HPPH/Images/ImageRows.cs @@ -0,0 +1,159 @@ +using System.Collections; +using System.Runtime.CompilerServices; + +namespace HPPH; + +[SkipLocalsInit] +public readonly ref struct ImageRows + where T : struct, IColor +{ + #region Properties & Fields + + private readonly ReadOnlySpan _data; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + public int Count => _height; + + #endregion + + #region Indexer + + public ImageRow this[int row] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((row < 0) || (row > _height)) throw new IndexOutOfRangeException(); + + return new ImageRow(_data, ((row + _y) * _stride) + _x, _width); + } + } + + #endregion + + #region Constructors + + // ReSharper disable once ConvertToPrimaryConstructor - Not possible with ref types + internal ImageRows(ReadOnlySpan data, int x, int y, int width, int height, int stride) + { + this._data = data; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImageRowsEnumerator GetEnumerator() => new(this); + + #endregion + + public ref struct ImageRowsEnumerator + { + #region Properties & Fields + + private readonly ImageRows _rows; + private int _position; + + /// + public readonly ImageRow Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _rows[_position]; + } + + #endregion + + #region Constructors + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ImageRowsEnumerator(ImageRows rows) + { + this._rows = rows; + + _position = -1; + } + + #endregion + + #region Methods + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => ++_position < _rows._height; + + #endregion + } +} + +//HACK DarthAffe 14.07.2024: Not nice, should be removed once ref structs are able to implement interfaces (https://github.com/dotnet/csharplang/blob/main/proposals/ref-struct-interfaces.md) +[SkipLocalsInit] +internal class IColorImageRows : IImageRows + where T : struct, IColor +{ + #region Properties & Fields + + private readonly byte[] _data; + private readonly int _x; + private readonly int _y; + private readonly int _width; + private readonly int _height; + private readonly int _stride; + + public int Count => _height; + + #endregion + + #region Indexer + + public IImageRow this[int row] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((row < 0) || (row > _height)) throw new IndexOutOfRangeException(); + + return new IColorImageRow(_data, ((row + _y) * _stride) + _x, _width); + } + } + + #endregion + + #region Constructors + + // ReSharper disable once ConvertToPrimaryConstructor - Not possible with ref types + internal IColorImageRows(byte[] data, int x, int y, int width, int height, int stride) + { + this._data = data; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._stride = stride; + } + + #endregion + + #region Methods + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < _height; i++) + yield return this[i]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion +} \ No newline at end of file diff --git a/HPPH/Images/Interfaces/IImage.cs b/HPPH/Images/Interfaces/IImage.cs new file mode 100644 index 0000000..ecc3d84 --- /dev/null +++ b/HPPH/Images/Interfaces/IImage.cs @@ -0,0 +1,133 @@ +namespace HPPH; + +/// +/// Represents an image. +/// +public interface IImage : IEnumerable +{ + /// + /// Gets the color format used in this image. + /// + IColorFormat ColorFormat { get; } + + /// + /// Gets the width of this image. + /// + int Width { get; } + + /// + /// Gets the height of this image. + /// + int Height { get; } + + /// + /// Gets the size in bytes of this image. + /// + int SizeInBytes { get; } + + /// + /// Gets the color at the specified location. + /// + /// The X-location to read. + /// The Y-location to read. + /// The color at the specified location. + IColor this[int x, int y] { get; } + + /// + /// Gets an image representing the specified location. + /// + /// The X-location of the image. + /// The Y-location of the image. + /// The width of the sub-image. + /// + /// + IImage this[int x, int y, int width, int height] { get; } + + /// + /// Gets a list of all rows of this image. + /// + IImageRows Rows { get; } + + /// + /// Gets a list of all columns of this image. + /// + 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(Span destination); + + IColor[] ToArray(); + + /// + /// Allocates a new array and copies this into it. + /// + /// The new array containing the data of this . + byte[] ToRawArray(); +} + +/// +/// Represents an image. +/// +public interface IImage : IImage + where T : struct, IColor +{ + /// + /// Gets the color at the specified location. + /// + /// The X-location to read. + /// The Y-location to read. + /// The color at the specified location. + new ref readonly T this[int x, int y] { get; } + + /// + /// Gets an image representing the specified location. + /// + /// The X-location of the image. + /// The Y-location of the image. + /// The width of the sub-image. + /// + /// + new RefImage this[int x, int y, int width, int height] { get; } + + /// + /// Gets a list of all rows of this image. + /// + new ImageRows Rows { get; } + + /// + /// Gets a list of all columns of this image. + /// + new ImageColumns Columns { get; } + + /// + /// Gets an representing this . + /// + /// The . + RefImage AsRefImage(); + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + void CopyTo(Span destination); + + new T[] ToArray(); + + new IEnumerator GetEnumerator(); +} \ No newline at end of file diff --git a/HPPH/Images/Interfaces/IImageColumn.cs b/HPPH/Images/Interfaces/IImageColumn.cs new file mode 100644 index 0000000..47e9641 --- /dev/null +++ b/HPPH/Images/Interfaces/IImageColumn.cs @@ -0,0 +1,41 @@ +namespace HPPH; + +/// +/// Represents a single column of an image. +/// +public interface IImageColumn : IEnumerable +{ + /// + /// Gets the length of the column. + /// + 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; } + + void CopyTo(Span destination); + + /// + /// Copies the contents of this into a destination instance. + /// + /// The destination instance. + /// + /// Thrown when is shorter than the source instance. + /// + void CopyTo(Span destination); + + /// + /// Allocates a new array and copies this into it. + /// + /// The new array containing the data of this . + IColor[] ToArray(); +} \ No newline at end of file diff --git a/HPPH/Images/Interfaces/IImageColumns.cs b/HPPH/Images/Interfaces/IImageColumns.cs new file mode 100644 index 0000000..ed1465e --- /dev/null +++ b/HPPH/Images/Interfaces/IImageColumns.cs @@ -0,0 +1,19 @@ +namespace HPPH; + +/// +/// Represents a list of columns of an image. +/// +public interface IImageColumns : IEnumerable +{ + /// + /// Gets the amount of columns in this list. + /// + int Count { get; } + + /// + /// Gets a specific . + /// + /// The column to get. + /// The requested . + IImageColumn this[int column] { get; } +} \ No newline at end of file diff --git a/HPPH/Images/Interfaces/IImageRow.cs b/HPPH/Images/Interfaces/IImageRow.cs new file mode 100644 index 0000000..5e6b681 --- /dev/null +++ b/HPPH/Images/Interfaces/IImageRow.cs @@ -0,0 +1,39 @@ +namespace HPPH; + +/// +/// Represents a single row of an image. +/// +public interface IImageRow : IEnumerable +{ + /// + /// Gets the length of the row. + /// + 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(Span destination); + + /// + /// Allocates a new array and copies this into it. + /// + /// The new array containing the data of this . + IColor[] ToArray(); +} \ No newline at end of file diff --git a/HPPH/Images/Interfaces/IImageRows.cs b/HPPH/Images/Interfaces/IImageRows.cs new file mode 100644 index 0000000..f253bee --- /dev/null +++ b/HPPH/Images/Interfaces/IImageRows.cs @@ -0,0 +1,19 @@ +namespace HPPH; + +/// +/// Represents a list of rows of an image. +/// +public interface IImageRows : IEnumerable +{ + /// + /// Gets the amount of rows in this list. + /// + int Count { get; } + + /// + /// Gets a specific . + /// + /// The ´row to get. + /// The requested . + IImageRow this[int column] { get; } +} diff --git a/HPPH/Images/RefImage.cs b/HPPH/Images/RefImage.cs index c31b8a9..54129a7 100644 --- a/HPPH/Images/RefImage.cs +++ b/HPPH/Images/RefImage.cs @@ -3,8 +3,9 @@ using System.Runtime.InteropServices; namespace HPPH; -public readonly ref struct RefImage - where TColor : struct, IColor +[SkipLocalsInit] +public readonly ref struct RefImage + where T : struct, IColor { #region Properties & Fields @@ -33,38 +34,35 @@ public readonly ref struct RefImage #region Indexer - public ref readonly TColor this[int x, int y] + public ref readonly T this[int x, int y] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if ((x < 0) || (y < 0) || (x >= Width) || (y >= Height)) throw new IndexOutOfRangeException(); - ReadOnlySpan data = MemoryMarshal.Cast(_data[((_y + y) * RawStride)..]); - ref TColor r0 = ref MemoryMarshal.GetReference(data); - nint offset = (nint)(uint)(_x + x); - return ref Unsafe.Add(ref r0, offset); + return ref Unsafe.Add(ref Unsafe.As(ref Unsafe.Add(ref MemoryMarshal.GetReference(_data), (nint)(uint)((_y + y) * RawStride))), (nint)(uint)(_x + x)); } } - public RefImage this[int x, int y, int width, int height] + public RefImage this[int x, int y, int width, int height] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if ((x < 0) || (y < 0) || (width <= 0) || (height <= 0) || ((x + width) > Width) || ((y + height) > Height)) throw new IndexOutOfRangeException(); - return new RefImage(_data, _x + x, _y + y, width, height, RawStride); + return new RefImage(_data, _x + x, _y + y, width, height, RawStride); } } - public ImageRows Rows + public ImageRows Rows { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(_data, _x, _y, Width, Height, RawStride); } - public ImageColumns Columns + public ImageColumns Columns { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(_data, _x, _y, Width, Height, RawStride); @@ -89,20 +87,20 @@ public readonly ref struct RefImage #region Methods /// - /// Copies the contents of this into a destination instance. + /// Copies the contents of this into a destination instance. /// /// The destination instance. /// - /// Thrown when is shorter than the source instance. + /// Thrown when is shorter than the source instance. /// - public void CopyTo(Span destination) + public void CopyTo(Span destination) { 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 = destination; - foreach (ReadOnlyRefEnumerable row in rows) + ImageRows rows = Rows; + Span target = destination; + foreach (ImageRow row in rows) { row.CopyTo(target); target = target[Width..]; @@ -110,12 +108,12 @@ public readonly ref struct RefImage } /// - /// Allocates a new array and copies this into it. + /// Allocates a new array and copies this into it. /// - /// The new array containing the data of this . - public TColor[] ToArray() + /// The new array containing the data of this . + public T[] ToArray() { - TColor[] array = new TColor[Width * Height]; + T[] array = new T[Width * Height]; CopyTo(array); return array; } @@ -128,7 +126,7 @@ public readonly ref struct RefImage if (_data.Length == 0) return ref Unsafe.NullRef(); - int offset = (_y * RawStride) + (_x * TColor.ColorFormat.BytesPerPixel); + int offset = (_y * RawStride) + (_x * T.ColorFormat.BytesPerPixel); return ref MemoryMarshal.GetReference(_data[offset..]); } @@ -138,7 +136,6 @@ public readonly ref struct RefImage #endregion - //TODO DarthAffe 10.07.2024: anpassen public ref struct ImageEnumerator { #region Properties & Fields @@ -153,15 +150,15 @@ public readonly ref struct RefImage private int _position; /// - public readonly TColor Current + public ref readonly T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - int row = (_position / _width); - int column = _position - (row * _width); + int y = (_position / _width); + int x = _position - (y * _width); - return MemoryMarshal.Cast(_data[(_y * _stride)..])[_x + column]; + return ref Unsafe.Add(ref Unsafe.As(ref Unsafe.Add(ref MemoryMarshal.GetReference(_data), (nint)(uint)((_y + y) * _stride))), (nint)(uint)(_x + x)); } } @@ -194,199 +191,4 @@ public readonly ref struct RefImage #endregion } - - #region Indexer-Structs - - public readonly ref struct ImageRows - { - #region Properties & Fields - - private readonly ReadOnlySpan _data; - private readonly int _x; - private readonly int _y; - private readonly int _width; - private readonly int _height; - private readonly int _stride; - - public int Count => _height; - - #endregion - - #region Indexer - - public ReadOnlyRefEnumerable this[int row] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - if ((row < 0) || (row > _height)) throw new IndexOutOfRangeException(); - - - ReadOnlySpan data = MemoryMarshal.Cast(_data[((row + _y) * _stride)..]); - ref TColor r0 = ref MemoryMarshal.GetReference(data); - ref TColor rr = ref Unsafe.Add(ref r0, (nint)(uint)_x); - - return new ReadOnlyRefEnumerable(rr, _width, 1); - } - } - - #endregion - - #region Constructors - - // ReSharper disable once ConvertToPrimaryConstructor - Not possible with ref types - internal ImageRows(ReadOnlySpan data, int x, int y, int width, int height, int stride) - { - this._data = data; - this._x = x; - this._y = y; - this._width = width; - this._height = height; - this._stride = stride; - } - - #endregion - - #region Methods - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ImageRowsEnumerator GetEnumerator() => new(this); - - #endregion - - public ref struct ImageRowsEnumerator - { - #region Properties & Fields - - private readonly ImageRows _rows; - private int _position; - - /// - public readonly ReadOnlyRefEnumerable Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _rows[_position]; - } - - #endregion - - #region Constructors - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ImageRowsEnumerator(ImageRows rows) - { - this._rows = rows; - - _position = -1; - } - - #endregion - - #region Methods - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() => ++_position < _rows._height; - - #endregion - } - } - - public readonly ref struct ImageColumns - { - #region Properties & Fields - - private readonly ReadOnlySpan _data; - private readonly int _x; - private readonly int _y; - private readonly int _width; - private readonly int _height; - private readonly int _stride; - - public int Count => _width; - - #endregion - - #region Indexer - - public ReadOnlyRefEnumerable this[int column] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - if ((column < 0) || (column > _width)) throw new IndexOutOfRangeException(); - - ReadOnlySpan data = MemoryMarshal.Cast(_data[(_y * _stride)..]); - ref TColor r0 = ref MemoryMarshal.GetReference(data); - ref TColor rc = ref Unsafe.Add(ref r0, (nint)(uint)(column + _x)); - - return new ReadOnlyRefEnumerable(rc, _height, _stride); - } - } - - #endregion - - #region Constructors - - // ReSharper disable once ConvertToPrimaryConstructor - Not possible with ref types - internal ImageColumns(ReadOnlySpan data, int x, int y, int width, int height, int stride) - { - this._data = data; - this._x = x; - this._y = y; - this._width = width; - this._height = height; - this._stride = stride; - } - - #endregion - - #region Methods - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ImageColumnsEnumerator GetEnumerator() => new(this); - - #endregion - - public ref struct ImageColumnsEnumerator - { - #region Properties & Fields - - private readonly ImageColumns _columns; - private int _position; - - /// - public readonly ReadOnlyRefEnumerable Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _columns[_position]; - } - - #endregion - - #region Constructors - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ImageColumnsEnumerator(ImageColumns columns) - { - this._columns = columns; - this._position = -1; - } - - #endregion - - #region Methods - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() => ++_position < _columns._width; - - #endregion - } - } - - #endregion } \ No newline at end of file diff --git a/HPPH/PixelHelper.MinMax.cs b/HPPH/PixelHelper.MinMax.cs index b1d8054..f9bc65c 100644 --- a/HPPH/PixelHelper.MinMax.cs +++ b/HPPH/PixelHelper.MinMax.cs @@ -36,7 +36,7 @@ public static unsafe partial class PixelHelper try { image.CopyTo(buffer); - return MinMax(buffer); + return MinMax(buffer); } finally {