From 113ae0b2fa24a17a99758be5cd3f99db55833711 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sun, 15 Oct 2023 12:35:15 +0200 Subject: [PATCH] Added GetReadOnlyArea to IPixelCollection (#1456) --- .../Pixels/IPixelCollection{TQuantumType}.cs | 17 ++++++ .../Native/Pixels/PixelCollection.cs | 31 +++++++++++ .../Native/Pixels/PixelCollection.json | 23 ++++++++ .../Netstandard21/Pixels/PixelCollection.cs | 24 ++++++++ .../Pixels/SafePixelCollection.cs | 14 +++++ .../Pixels/UnsafePixelCollection.cs | 8 +++ .../TheGetReadonlyAreaMethod.cs | 54 ++++++++++++++++++ .../TheGetReadonlyAreaMethod.cs | 55 +++++++++++++++++++ 8 files changed, 226 insertions(+) create mode 100644 tests/Magick.NET.Tests/Netstandard21/Pixels/SafePixelCollectionTests/TheGetReadonlyAreaMethod.cs create mode 100644 tests/Magick.NET.Tests/Netstandard21/Pixels/UnsafePixelCollectionTests/TheGetReadonlyAreaMethod.cs diff --git a/src/Magick.NET.Core/Netstandard21/Pixels/IPixelCollection{TQuantumType}.cs b/src/Magick.NET.Core/Netstandard21/Pixels/IPixelCollection{TQuantumType}.cs index b68fce9e01..ef60813269 100644 --- a/src/Magick.NET.Core/Netstandard21/Pixels/IPixelCollection{TQuantumType}.cs +++ b/src/Magick.NET.Core/Netstandard21/Pixels/IPixelCollection{TQuantumType}.cs @@ -10,6 +10,23 @@ namespace ImageMagick; /// public partial interface IPixelCollection { + /// + /// Returns the pixels at the specified area. + /// + /// The X coordinate of the area. + /// The Y coordinate of the area. + /// The width of the area. + /// The height of the area. + /// A array. + ReadOnlySpan GetReadOnlyArea(int x, int y, int width, int height); + + /// + /// Returns the pixels of the specified area. + /// + /// The geometry of the area. + /// A array. + ReadOnlySpan GetReadOnlyArea(IMagickGeometry geometry); + /// /// Changes the values of the specified pixels. /// diff --git a/src/Magick.NET/Native/Pixels/PixelCollection.cs b/src/Magick.NET/Native/Pixels/PixelCollection.cs index 2bee893e23..6249bbe330 100644 --- a/src/Magick.NET/Native/Pixels/PixelCollection.cs +++ b/src/Magick.NET/Native/Pixels/PixelCollection.cs @@ -34,6 +34,8 @@ public static class X64 [DllImport(NativeLibrary.X64Name, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr PixelCollection_GetArea(IntPtr Instance, IntPtr x, IntPtr y, UIntPtr width, UIntPtr height, out IntPtr exception); [DllImport(NativeLibrary.X64Name, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr PixelCollection_GetReadOnlyArea(IntPtr Instance, IntPtr x, IntPtr y, UIntPtr width, UIntPtr height, out IntPtr exception); + [DllImport(NativeLibrary.X64Name, CallingConvention = CallingConvention.Cdecl)] public static extern void PixelCollection_SetArea(IntPtr Instance, IntPtr x, IntPtr y, UIntPtr width, UIntPtr height, QuantumType* values, UIntPtr length, out IntPtr exception); [DllImport(NativeLibrary.X64Name, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr PixelCollection_ToByteArray(IntPtr Instance, IntPtr x, IntPtr y, UIntPtr width, UIntPtr height, IntPtr mapping, out IntPtr exception); @@ -51,6 +53,8 @@ public static class ARM64 [DllImport(NativeLibrary.ARM64Name, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr PixelCollection_GetArea(IntPtr Instance, IntPtr x, IntPtr y, UIntPtr width, UIntPtr height, out IntPtr exception); [DllImport(NativeLibrary.ARM64Name, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr PixelCollection_GetReadOnlyArea(IntPtr Instance, IntPtr x, IntPtr y, UIntPtr width, UIntPtr height, out IntPtr exception); + [DllImport(NativeLibrary.ARM64Name, CallingConvention = CallingConvention.Cdecl)] public static extern void PixelCollection_SetArea(IntPtr Instance, IntPtr x, IntPtr y, UIntPtr width, UIntPtr height, QuantumType* values, UIntPtr length, out IntPtr exception); [DllImport(NativeLibrary.ARM64Name, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr PixelCollection_ToByteArray(IntPtr Instance, IntPtr x, IntPtr y, UIntPtr width, UIntPtr height, IntPtr mapping, out IntPtr exception); @@ -68,6 +72,8 @@ public static class X86 [DllImport(NativeLibrary.X86Name, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr PixelCollection_GetArea(IntPtr Instance, IntPtr x, IntPtr y, UIntPtr width, UIntPtr height, out IntPtr exception); [DllImport(NativeLibrary.X86Name, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr PixelCollection_GetReadOnlyArea(IntPtr Instance, IntPtr x, IntPtr y, UIntPtr width, UIntPtr height, out IntPtr exception); + [DllImport(NativeLibrary.X86Name, CallingConvention = CallingConvention.Cdecl)] public static extern void PixelCollection_SetArea(IntPtr Instance, IntPtr x, IntPtr y, UIntPtr width, UIntPtr height, QuantumType* values, UIntPtr length, out IntPtr exception); [DllImport(NativeLibrary.X86Name, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr PixelCollection_ToByteArray(IntPtr Instance, IntPtr x, IntPtr y, UIntPtr width, UIntPtr height, IntPtr mapping, out IntPtr exception); @@ -157,6 +163,31 @@ public IntPtr GetArea(int x, int y, int width, int height) CheckException(exception); return result; } + public IntPtr GetReadOnlyArea(int x, int y, int width, int height) + { + IntPtr exception = IntPtr.Zero; + IntPtr result; + #if PLATFORM_AnyCPU + if (Runtime.IsArm64) + #endif + #if PLATFORM_arm64 || PLATFORM_AnyCPU + result = NativeMethods.ARM64.PixelCollection_GetReadOnlyArea(Instance, (IntPtr)x, (IntPtr)y, (UIntPtr)width, (UIntPtr)height, out exception); + #endif + #if PLATFORM_AnyCPU + else if (Runtime.Is64Bit) + #endif + #if PLATFORM_x64 || PLATFORM_AnyCPU + result = NativeMethods.X64.PixelCollection_GetReadOnlyArea(Instance, (IntPtr)x, (IntPtr)y, (UIntPtr)width, (UIntPtr)height, out exception); + #endif + #if PLATFORM_AnyCPU + else + #endif + #if PLATFORM_x86 || PLATFORM_AnyCPU + result = NativeMethods.X86.PixelCollection_GetReadOnlyArea(Instance, (IntPtr)x, (IntPtr)y, (UIntPtr)width, (UIntPtr)height, out exception); + #endif + CheckException(exception); + return result; + } public void SetArea(int x, int y, int width, int height, QuantumType[] values, int length) { fixed (QuantumType* valuesFixed = values) diff --git a/src/Magick.NET/Native/Pixels/PixelCollection.json b/src/Magick.NET/Native/Pixels/PixelCollection.json index 3567547d42..1ee81abfa7 100644 --- a/src/Magick.NET/Native/Pixels/PixelCollection.json +++ b/src/Magick.NET/Native/Pixels/PixelCollection.json @@ -34,6 +34,29 @@ } ] }, + { + "name": "GetReadOnlyArea", + "type": "Instance", + "throws": "true", + "arguments": [ + { + "name": "x", + "type": "ssize_t" + }, + { + "name": "y", + "type": "ssize_t" + }, + { + "name": "width", + "type": "size_t" + }, + { + "name": "height", + "type": "size_t" + } + ], + } { "name": "SetArea", "throws": "true", diff --git a/src/Magick.NET/Netstandard21/Pixels/PixelCollection.cs b/src/Magick.NET/Netstandard21/Pixels/PixelCollection.cs index c7cd98f3bf..ffdc4495d6 100644 --- a/src/Magick.NET/Netstandard21/Pixels/PixelCollection.cs +++ b/src/Magick.NET/Netstandard21/Pixels/PixelCollection.cs @@ -19,6 +19,30 @@ namespace ImageMagick; internal abstract partial class PixelCollection { + public virtual unsafe ReadOnlySpan GetReadOnlyArea(int x, int y, int width, int height) + { + var area = GetAreaPointer(x, y, width, height); + if (area == IntPtr.Zero) + { + return default; + } + + var length = width * height * Image.ChannelCount; + +#if Q8 + return new ReadOnlySpan((byte*)area, length); +#elif Q16 + return new ReadOnlySpan((ushort*)area, length); +#elif Q16HDRI + return new ReadOnlySpan((float*)area, length); +#else +#error Not implemented! +#endif + } + + public virtual ReadOnlySpan GetReadOnlyArea(IMagickGeometry geometry) + => GetReadOnlyArea(geometry.X, geometry.Y, geometry.Width, geometry.Height); + public virtual void SetArea(int x, int y, int width, int height, ReadOnlySpan values) => SetAreaUnchecked(x, y, width, height, values); diff --git a/src/Magick.NET/Netstandard21/Pixels/SafePixelCollection.cs b/src/Magick.NET/Netstandard21/Pixels/SafePixelCollection.cs index f7f23f0ad9..ab63822371 100644 --- a/src/Magick.NET/Netstandard21/Pixels/SafePixelCollection.cs +++ b/src/Magick.NET/Netstandard21/Pixels/SafePixelCollection.cs @@ -19,6 +19,20 @@ namespace ImageMagick; internal sealed partial class SafePixelCollection { + public override ReadOnlySpan GetReadOnlyArea(int x, int y, int width, int height) + { + CheckArea(x, y, width, height); + + return base.GetReadOnlyArea(x, y, width, height); + } + + public override ReadOnlySpan GetReadOnlyArea(IMagickGeometry geometry) + { + Throw.IfNull(nameof(geometry), geometry); + + return base.GetReadOnlyArea(geometry); + } + public override void SetArea(int x, int y, int width, int height, ReadOnlySpan values) { CheckValues(x, y, width, height, values); diff --git a/src/Magick.NET/Netstandard21/Pixels/UnsafePixelCollection.cs b/src/Magick.NET/Netstandard21/Pixels/UnsafePixelCollection.cs index 47204256ab..1aeb47ce9a 100644 --- a/src/Magick.NET/Netstandard21/Pixels/UnsafePixelCollection.cs +++ b/src/Magick.NET/Netstandard21/Pixels/UnsafePixelCollection.cs @@ -19,6 +19,14 @@ namespace ImageMagick; internal sealed partial class UnsafePixelCollection { + public override ReadOnlySpan GetReadOnlyArea(IMagickGeometry geometry) + { + if (geometry is null) + return default; + + return base.GetReadOnlyArea(geometry); + } + public override void SetArea(IMagickGeometry geometry, ReadOnlySpan values) { if (geometry is not null) diff --git a/tests/Magick.NET.Tests/Netstandard21/Pixels/SafePixelCollectionTests/TheGetReadonlyAreaMethod.cs b/tests/Magick.NET.Tests/Netstandard21/Pixels/SafePixelCollectionTests/TheGetReadonlyAreaMethod.cs new file mode 100644 index 0000000000..d94d3b473e --- /dev/null +++ b/tests/Magick.NET.Tests/Netstandard21/Pixels/SafePixelCollectionTests/TheGetReadonlyAreaMethod.cs @@ -0,0 +1,54 @@ +// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET. +// Licensed under the Apache License, Version 2.0. + +#if NETCOREAPP + +using System; +using ImageMagick; +using Xunit; + +namespace Magick.NET.Tests +{ + public partial class SafePixelCollectionTests + { + public partial class TheGetReadonlyAreaMethod + { + [Fact] + public void ShouldThrowExceptionWhenAreaIsInvalid() + { + using var image = new MagickImage(MagickColors.Red, 1, 1); + using var pixels = image.GetPixels(); + + Assert.Throws(() => pixels.GetReadOnlyArea(1, 2, 3, 4)); + } + + [Fact] + public void ShouldNotThrowExceptionWhenGeometryIsNull() + { + using var image = new MagickImage(Files.ImageMagickJPG); + using var pixels = image.GetPixels(); + + Assert.Throws("geometry", () => pixels.GetReadOnlyArea(null)); + } + + [Fact] + public void ShouldReturnAllPixelsOfTheImage() + { + using var input = new MagickImage(Files.ImageMagickJPG); + using var pixels = input.GetPixels(); + var area = pixels.GetReadOnlyArea(0, 0, input.Width, input.Height); + + Assert.Equal(43542, area.Length); + + var settings = new PixelReadSettings(input.Width, input.Height, StorageType.Quantum, PixelMapping.RGB); + using var output = new MagickImage(); + output.ReadPixels(area, settings); + + var difference = output.Compare(input, ErrorMetric.RootMeanSquared); + Assert.Equal(0.0, difference); + } + } + } +} + +#endif diff --git a/tests/Magick.NET.Tests/Netstandard21/Pixels/UnsafePixelCollectionTests/TheGetReadonlyAreaMethod.cs b/tests/Magick.NET.Tests/Netstandard21/Pixels/UnsafePixelCollectionTests/TheGetReadonlyAreaMethod.cs new file mode 100644 index 0000000000..50a9018124 --- /dev/null +++ b/tests/Magick.NET.Tests/Netstandard21/Pixels/UnsafePixelCollectionTests/TheGetReadonlyAreaMethod.cs @@ -0,0 +1,55 @@ +// Copyright Dirk Lemstra https://github.com/dlemstra/Magick.NET. +// Licensed under the Apache License, Version 2.0. + +#if NETCOREAPP + +using ImageMagick; +using Xunit; + +namespace Magick.NET.Tests +{ + public partial class UnsafePixelCollectionTests + { + public partial class TheGetReadonlyAreaMethod + { + [Fact] + public void ShouldNotThrowExceptionWhenAreaIsInvalid() + { + using var image = new MagickImage(MagickColors.Red, 1, 1); + using var pixels = image.GetPixelsUnsafe(); + var area = pixels.GetReadOnlyArea(1, 2, 3, 4); + + Assert.Equal(36, area.Length); + } + + [Fact] + public void ShouldNotThrowExceptionWhenGeometryIsNull() + { + using var image = new MagickImage(Files.ImageMagickJPG); + using var pixels = image.GetPixelsUnsafe(); + var area = pixels.GetReadOnlyArea(null); + + Assert.Equal(0, area.Length); + } + + [Fact] + public void ShouldReturnAllPixelsOfTheImage() + { + using var input = new MagickImage(Files.ImageMagickJPG); + using var pixels = input.GetPixelsUnsafe(); + var area = pixels.GetReadOnlyArea(0, 0, input.Width, input.Height); + + Assert.Equal(43542, area.Length); + + var settings = new PixelReadSettings(input.Width, input.Height, StorageType.Quantum, PixelMapping.RGB); + using var output = new MagickImage(); + output.ReadPixels(area, settings); + + var difference = output.Compare(input, ErrorMetric.RootMeanSquared); + Assert.Equal(0.0, difference); + } + } + } +} + +#endif