From 23b8a97eb715ea8e597fda444cc7a449477b94b1 Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 14:14:24 -0800 Subject: [PATCH 01/13] Revision to reference latest System.Drawing.Common NuGet package. --- .../Freedom35.ImageProcessing.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Freedom35.ImageProcessing/Freedom35.ImageProcessing.csproj b/src/Freedom35.ImageProcessing/Freedom35.ImageProcessing.csproj index 84f1f97..12f6814 100644 --- a/src/Freedom35.ImageProcessing/Freedom35.ImageProcessing.csproj +++ b/src/Freedom35.ImageProcessing/Freedom35.ImageProcessing.csproj @@ -5,7 +5,7 @@ .NET Standard library for image processing. Alan Barr (GitHub: freedom35) Alan Barr (GitHub: freedom35) - Copyright © Alan Barr 2022 + Copyright © 2022-2024 Alan Barr https://github.com/freedom35/image-processing false false @@ -15,11 +15,11 @@ git MIT icon.png - 1.4.1 + 1.5.0 - + From e4a8cbfe3c399275d112637c61b4fb19c21c14df Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 14:16:47 -0800 Subject: [PATCH 02/13] Updated for .NET 9.0 and newer action versions. --- .github/workflows/dotnet-windows.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dotnet-windows.yml b/.github/workflows/dotnet-windows.yml index 1c215b3..ef3fb2c 100644 --- a/.github/workflows/dotnet-windows.yml +++ b/.github/workflows/dotnet-windows.yml @@ -40,19 +40,19 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 # Install the .NET workload - name: Install .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 9.0.x # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - name: Setup MSBuild.exe - uses: microsoft/setup-msbuild@2008f912f56e61277eefaac6d1888b750582aa16 + uses: microsoft/setup-msbuild@v2 env: ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' From c4faca86f0b4c90af4e6521f8d279c9a202e078d Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 14:17:42 -0800 Subject: [PATCH 03/13] Updated to target .NET 9.0. --- src/ImageViewerApp/ImageViewerApp.csproj | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageViewerApp/ImageViewerApp.csproj b/src/ImageViewerApp/ImageViewerApp.csproj index b760203..36ba1b7 100644 --- a/src/ImageViewerApp/ImageViewerApp.csproj +++ b/src/ImageViewerApp/ImageViewerApp.csproj @@ -2,14 +2,15 @@ WinExe - net6.0-windows + net9.0-windows true .NET Core Windows app for demonstrating image processing functions. Alan Barr (GitHub: freedom35) https://github.com/freedom35/image-processing MIT - 1.6.0 + 2.0.0 + enable From 655207406a2752d3dc9f0e65f203902862b76f9e Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 14:42:07 -0800 Subject: [PATCH 04/13] Revision to coalesce codec info null check. --- src/Freedom35.ImageProcessing/ImageFormatting.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Freedom35.ImageProcessing/ImageFormatting.cs b/src/Freedom35.ImageProcessing/ImageFormatting.cs index 80d2d92..50c83af 100644 --- a/src/Freedom35.ImageProcessing/ImageFormatting.cs +++ b/src/Freedom35.ImageProcessing/ImageFormatting.cs @@ -106,12 +106,7 @@ public static Image ToJPEG(Image image, long compressionLevel) ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); // Find JPEG codec - ImageCodecInfo jpegEncoder = codecs.FirstOrDefault(c => c.FormatID == ImageFormat.Jpeg.Guid); - - if (jpegEncoder == null) - { - throw new Exception("JPEG image encoder not found."); - } + ImageCodecInfo jpegEncoder = codecs.FirstOrDefault(c => c.FormatID == ImageFormat.Jpeg.Guid) ?? throw new Exception("JPEG image encoder not found."); // Create compression parameters EncoderParameters encParams = new EncoderParameters(1); From ad3200db48ea359fe1b77e311965507088b4a510 Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 14:43:08 -0800 Subject: [PATCH 05/13] Revision to fix nullable warnings. --- src/ImageViewerApp/ImageConverter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageViewerApp/ImageConverter.cs b/src/ImageViewerApp/ImageConverter.cs index 739c947..e92e4de 100644 --- a/src/ImageViewerApp/ImageConverter.cs +++ b/src/ImageViewerApp/ImageConverter.cs @@ -15,7 +15,7 @@ namespace ImageViewerApp [ValueConversion(typeof(Image), typeof(ImageSource))] internal sealed class ImageConverter : IValueConverter { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is Image img) { @@ -33,7 +33,7 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu throw new NotSupportedException(); } - public static BitmapImage ConvertImageToBitmapSource(Image image) + public static BitmapImage? ConvertImageToBitmapSource(Image image) { // First convert to stream using MemoryStream stream = new(); @@ -42,9 +42,9 @@ public static BitmapImage ConvertImageToBitmapSource(Image image) return ConvertStreamToBitmapSource(stream); } - public static BitmapImage ConvertStreamToBitmapSource(MemoryStream stream) + public static BitmapImage? ConvertStreamToBitmapSource(MemoryStream stream) { - BitmapImage bmp = null; + BitmapImage? bmp = null; if (stream != null && stream.CanSeek && stream.CanRead) { From 4d70e701feb67154fa85405cb5e30b1d2e1523ec Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 14:43:46 -0800 Subject: [PATCH 06/13] Revision for enum get description method. --- src/ImageViewerApp/EnumConverter.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ImageViewerApp/EnumConverter.cs b/src/ImageViewerApp/EnumConverter.cs index 9cbd7ee..62e32d3 100644 --- a/src/ImageViewerApp/EnumConverter.cs +++ b/src/ImageViewerApp/EnumConverter.cs @@ -6,6 +6,7 @@ using System.Reflection; using Freedom35.ImageProcessing; +using System.Diagnostics.CodeAnalysis; namespace ImageViewerApp { @@ -35,20 +36,22 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu throw new NotSupportedException(); } - public static T GetValueFromDescription(string description) where T : Enum + public static bool TryGetValueFromDescription(string description, [NotNullWhen(true)] out T? enumValue) where T : Enum { FieldInfo[] fields = typeof(T).GetFields(); - FieldInfo field = fields.FirstOrDefault(f => Attribute.GetCustomAttribute(f, typeof(DescriptionAttribute)) is DescriptionAttribute attr && attr.Description == description); + // Note: Matching enum description attribute (not name) + FieldInfo? field = fields.FirstOrDefault(f => Attribute.GetCustomAttribute(f, typeof(DescriptionAttribute)) is DescriptionAttribute attr && attr.Description == description); if (field != null) { - return (T)field.GetValue(null); - } - else - { - return default; + enumValue = (T?)field.GetValue(null); + return enumValue != null; } + + // Must init + enumValue = default; + return false; } } } From 251b9999cdda983794160945e9b5960c50cab4d4 Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 14:44:20 -0800 Subject: [PATCH 07/13] Revision for nullable warnings (from project setting). --- src/ImageViewerApp/MainWindow.xaml.cs | 57 +++++++++++++++------------ 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/ImageViewerApp/MainWindow.xaml.cs b/src/ImageViewerApp/MainWindow.xaml.cs index b8a2b1b..391e44b 100644 --- a/src/ImageViewerApp/MainWindow.xaml.cs +++ b/src/ImageViewerApp/MainWindow.xaml.cs @@ -2,8 +2,6 @@ using System; using System.Drawing; using System.Windows; -using System.Windows.Media; -using System.Windows.Media.Imaging; using System.Drawing.Imaging; using System.Linq; @@ -23,13 +21,13 @@ public MainWindow() private const string SaveFileFilter = "Bitmap|*.bmp|JPEG|*.jpg|PNG|*.png|TIFF|*.tif"; - private Image originalImage = null; - private Image currentImage = null; - private Image previousImage= null; + private Image? originalImage = null; + private Image? currentImage = null; + private Image? previousImage= null; private System.Windows.Point zoomStartPoint = new(-1, -1); - private System.Windows.Input.Cursor defaultCursor = null; + private System.Windows.Input.Cursor? defaultCursor = null; #endregion @@ -49,14 +47,17 @@ private void Window_Loaded(object sender, EventArgs e) { // Check command line args for any 'Open With...' images. // (First arg will be app path) - string imageName = Environment.GetCommandLineArgs().Skip(1).FirstOrDefault(); + string? imageName = Environment.GetCommandLineArgs().Skip(1).FirstOrDefault(); - string[] SupportedImageTypes = { "bmp", "jpg", "png", "tif" }; - - // Open image if valid file type - if (!string.IsNullOrEmpty(imageName) && SupportedImageTypes.Any(t => imageName.EndsWith(t, StringComparison.OrdinalIgnoreCase))) + if (!string.IsNullOrEmpty(imageName)) { - OpenImage(imageName); + string[] SupportedImageTypes = ["bmp", "jpg", "png", "tif"]; + + // Open image if valid file type + if (SupportedImageTypes.Any(t => imageName.EndsWith(t, StringComparison.OrdinalIgnoreCase))) + { + OpenImage(imageName); + } } } @@ -192,8 +193,8 @@ private static void DisplayHistogram(Image sourceImage, System.Windows.Controls. // Create histogram of image Image histogram = ImageHistogram.Create(sourceImage, new System.Drawing.Size((int)targetPictureBox.DesiredSize.Width - 20, (int)targetPictureBox.DesiredSize.Height), - System.Drawing.Color.DodgerBlue, - System.Drawing.Color.White); + Color.DodgerBlue, + Color.White); // Display histogram targetPictureBox.Source = ImageConverter.ConvertImageToBitmapSource(histogram); @@ -258,7 +259,7 @@ private void Button_RestoreImage_Click(object sender, RoutedEventArgs e) } } - private void Button_UndoImageChange_Click(object sender, RoutedEventArgs e) + private void Button_UndoImageChange_Click(object? sender, RoutedEventArgs? e) { if (previousImage != null) { @@ -268,19 +269,25 @@ private void Button_UndoImageChange_Click(object sender, RoutedEventArgs e) private void Button_ApplyConvolution_Click(object sender, RoutedEventArgs e) { - if (currentImage != null && cmbConvolution.SelectedIndex > -1) + // Check an image is loaded + if (currentImage == null) { - string strValue = cmbConvolution.SelectedItem as string; - - ConvolutionType type = EnumConverter.GetValueFromDescription(strValue); + return; + } - try - { - DisplayImage(ImageConvolution.ApplyKernel(currentImage, type)); - } - catch (Exception ex) + // Check a valid filter is selected + if (cmbConvolution.SelectedIndex > -1 && cmbConvolution.SelectedItem is string strValue) + { + if (EnumConverter.TryGetValueFromDescription(strValue, out ConvolutionType type)) { - ReportException(ex); + try + { + DisplayImage(ImageConvolution.ApplyKernel(currentImage, type)); + } + catch (Exception ex) + { + ReportException(ex); + } } } } From 1cfedc8d14cb878bdb4bb6247815d18ac2b717b9 Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 14:44:53 -0800 Subject: [PATCH 08/13] Revision to target .NET 9.0, updated NuGet packages. --- .../Freedom35.ImageProcessing.Tests.csproj | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/Freedom35.ImageProcessing.Tests/Freedom35.ImageProcessing.Tests.csproj b/tests/Freedom35.ImageProcessing.Tests/Freedom35.ImageProcessing.Tests.csproj index 0582481..a9441af 100644 --- a/tests/Freedom35.ImageProcessing.Tests/Freedom35.ImageProcessing.Tests.csproj +++ b/tests/Freedom35.ImageProcessing.Tests/Freedom35.ImageProcessing.Tests.csproj @@ -1,14 +1,15 @@ - net6.0-windows + net9.0-windows false Alan Barr (GitHub: freedom35) Alan Barr (GitHub: freedom35) Unit tests for Freedom35.ImageProcessing.dll. - Copyright © Alan Barr + Copyright © 2022-2024 Alan Barr https://github.com/freedom35/image-processing - 1.5.0 + 2.0.0 + enable @@ -30,9 +31,9 @@ - - - + + + From ae9953bd7fcc6f91236c01b61dcd0e68a76e8257 Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 15:16:11 -0800 Subject: [PATCH 09/13] Revision for clarity with local variable declaration. --- src/Freedom35.ImageProcessing/ImageConvolution.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Freedom35.ImageProcessing/ImageConvolution.cs b/src/Freedom35.ImageProcessing/ImageConvolution.cs index db8fef6..12fbe7b 100644 --- a/src/Freedom35.ImageProcessing/ImageConvolution.cs +++ b/src/Freedom35.ImageProcessing/ImageConvolution.cs @@ -115,7 +115,7 @@ public static Bitmap ApplyKernelsThenCombine(Bitmap bitmap, params ConvolutionTy } // Combine to create a new image - bitmap = ImageCombine.All(bitmaps); + Bitmap combinedBitmap = ImageCombine.All(bitmaps); // No longer needed foreach (Bitmap bmp in bitmaps) @@ -123,7 +123,7 @@ public static Bitmap ApplyKernelsThenCombine(Bitmap bitmap, params ConvolutionTy bmp.Dispose(); } - return bitmap; + return combinedBitmap; } /// From 979517b811cf44e4f2d9387049801d7ebb9fb15a Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 15:17:16 -0800 Subject: [PATCH 10/13] Revision to throw exception if resource returns a null stream. --- tests/Freedom35.ImageProcessing.Tests/TestImage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Freedom35.ImageProcessing.Tests/TestImage.cs b/tests/Freedom35.ImageProcessing.Tests/TestImage.cs index 5318760..a484733 100644 --- a/tests/Freedom35.ImageProcessing.Tests/TestImage.cs +++ b/tests/Freedom35.ImageProcessing.Tests/TestImage.cs @@ -11,7 +11,7 @@ static class TestImage public static Image FromResource(string resourcePath) { // Keep stream open for processing - Stream resourceStream = System.Reflection.Assembly.GetCallingAssembly().GetManifestResourceStream(resourcePath); + Stream? resourceStream = System.Reflection.Assembly.GetCallingAssembly().GetManifestResourceStream(resourcePath) ?? throw new FileNotFoundException($"Unable to load resource: {resourcePath}"); return Image.FromStream(resourceStream); } From d19166ce549d3a840bc528c2f6fcbed240785de4 Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 15:18:13 -0800 Subject: [PATCH 11/13] Revision to simplify unit test assert statements. --- .../TestImageBinary.cs | 4 +- .../TestImageBytes.cs | 26 ++++----- .../TestImageColor.cs | 19 +++---- .../TestImageCombine.cs | 56 +++++++++---------- .../TestImageConvolution.cs | 24 +++----- .../TestImageCopy.cs | 2 +- .../TestImageCrop.cs | 4 -- .../TestImageResize.cs | 18 ++---- .../TestImageThreshold.cs | 29 +--------- 9 files changed, 62 insertions(+), 120 deletions(-) diff --git a/tests/Freedom35.ImageProcessing.Tests/TestImageBinary.cs b/tests/Freedom35.ImageProcessing.Tests/TestImageBinary.cs index 6037673..4724a69 100644 --- a/tests/Freedom35.ImageProcessing.Tests/TestImageBinary.cs +++ b/tests/Freedom35.ImageProcessing.Tests/TestImageBinary.cs @@ -17,15 +17,13 @@ public void TestAsBytes(string sourceResourcePath) // Load source image using Image sourceImage = TestImage.FromResource(sourceResourcePath); - Assert.IsNotNull(sourceImage); - const byte Threshold = 0x7F; // Get bytes for image byte[] imageBytes = ImageBinary.AsBytes(sourceImage, Threshold); // Check we have some bytes - Assert.IsNotNull(imageBytes); + Assert.IsTrue(imageBytes.Length > 0); // Check all converted to binary (0 or 1) Assert.IsTrue(imageBytes.All(b => b < 0x02)); diff --git a/tests/Freedom35.ImageProcessing.Tests/TestImageBytes.cs b/tests/Freedom35.ImageProcessing.Tests/TestImageBytes.cs index 836910c..cb825b7 100644 --- a/tests/Freedom35.ImageProcessing.Tests/TestImageBytes.cs +++ b/tests/Freedom35.ImageProcessing.Tests/TestImageBytes.cs @@ -17,8 +17,6 @@ public void TestFromResource(string resourcePath) { byte[] imageBytes = ImageBytes.FromResource(resourcePath); - Assert.IsNotNull(imageBytes); - // Check not an empty array Assert.IsTrue(imageBytes.Length > 0); @@ -29,19 +27,19 @@ public void TestFromResource(string resourcePath) [TestMethod] public void TestBytesToBits() { - byte[] byteValues = new byte[] - { + byte[] byteValues = + [ 0xD5, 0x3C - }; + ]; byte[] bitValues = ImageBytes.BytesToBits(byteValues); - byte[] expectedBitValues = new byte[] - { + byte[] expectedBitValues = + [ 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, - }; + ]; // Check expected number in array Assert.AreEqual(expectedBitValues.Length, bitValues.Length); @@ -56,19 +54,19 @@ public void TestBytesToBits() [TestMethod] public void TestBitsToBytes() { - byte[] bitValues = new byte[] - { + byte[] bitValues = + [ 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, - }; + ]; byte[] byteValues = ImageBytes.BitsToBytes(bitValues); - byte[] expectedByteValues = new byte[] - { + byte[] expectedByteValues = + [ 0xD5, 0x3C - }; + ]; // Check expected number in array Assert.AreEqual(expectedByteValues.Length, byteValues.Length); diff --git a/tests/Freedom35.ImageProcessing.Tests/TestImageColor.cs b/tests/Freedom35.ImageProcessing.Tests/TestImageColor.cs index e17622d..fe2ecf9 100644 --- a/tests/Freedom35.ImageProcessing.Tests/TestImageColor.cs +++ b/tests/Freedom35.ImageProcessing.Tests/TestImageColor.cs @@ -22,7 +22,7 @@ private static byte[] CreateColorImage4x4(out BitmapData bitmapData) PixelFormat = PixelFormat.Format24bppRgb }; - List listBytes = new(); + List listBytes = []; for (int i = 0; i < bitmapData.Width * bitmapData.Height; i++) { @@ -32,7 +32,7 @@ private static byte[] CreateColorImage4x4(out BitmapData bitmapData) listBytes.Add(150); } - return listBytes.ToArray(); + return [.. listBytes]; } [TestMethod] @@ -42,7 +42,6 @@ public void TestConvertToGrayscale() byte[] convertedImage = ImageColor.ToGrayscale(imageBytes, bitmapData); - Assert.IsNotNull(convertedImage); Assert.AreEqual(convertedImage.Length, imageBytes.Length / 3); Assert.AreEqual(convertedImage[0], imageBytes.Average(b => b)); } @@ -62,7 +61,7 @@ public void TestConvertInvalidImageToGrayscale() [TestMethod] public void TestConvertNoImageToGrayscale() { - byte[] imageBytes = Array.Empty(); + byte[] imageBytes = []; BitmapData bitmapData = new() { @@ -79,11 +78,11 @@ public void TestConvertNoImageToGrayscale() [TestMethod] public void TestConvertToBackAndWhite() { - byte[] imageBytes = { + byte[] imageBytes = [ 50, 100, 150 - }; + ]; byte[] convertedImage = ImageColor.ToBlackAndWhite(imageBytes); @@ -103,11 +102,11 @@ public void TestConvertToBackAndWhiteWithThreshold() [TestMethod] public void TestConvertToNegative() { - byte[] imageBytes = { + byte[] imageBytes = [ 0x01, 0xf0, 0x3c - }; + ]; ImageColor.ToNegative(imageBytes); @@ -119,10 +118,10 @@ public void TestConvertToNegative() [TestMethod] public void TestConvertMonochromeToNegative() { - byte[] imageBytes = { + byte[] imageBytes = [ 1, 0 - }; + ]; ImageColor.MonochromeToNegative(imageBytes); diff --git a/tests/Freedom35.ImageProcessing.Tests/TestImageCombine.cs b/tests/Freedom35.ImageProcessing.Tests/TestImageCombine.cs index d9a24c9..8109774 100644 --- a/tests/Freedom35.ImageProcessing.Tests/TestImageCombine.cs +++ b/tests/Freedom35.ImageProcessing.Tests/TestImageCombine.cs @@ -10,7 +10,7 @@ public class TestImageCombine [TestMethod] public void TestCombineAllNone() { - Bitmap combinedImage = ImageCombine.All(Array.Empty()); + Bitmap? combinedImage = ImageCombine.All(Array.Empty()); Assert.IsNull(combinedImage); } @@ -18,16 +18,14 @@ public void TestCombineAllNone() public void TestCombineAllMixedColor() { using Image colorImage = TestImage.FromResource("Freedom35.ImageProcessing.Tests.Resources.clock.bmp"); - Assert.IsNotNull(colorImage); - + using Image bwImage = TestImage.FromResource("Freedom35.ImageProcessing.Tests.Resources.clock-bw.bmp"); - Assert.IsNotNull(bwImage); - - Image[] imagesToCombine = new Image[] - { + + Image[] imagesToCombine = + [ colorImage, bwImage - }; + ]; Assert.ThrowsException(() => ImageCombine.All(imagesToCombine)); } @@ -36,15 +34,15 @@ public void TestCombineAllMixedColor() public void TestCombineAllOne() { using Image sourceImage = TestImage.FromResource("Freedom35.ImageProcessing.Tests.Resources.clock.bmp"); - Assert.IsNotNull(sourceImage); - - Image[] imagesToCombine = new Image[] - { + + Image[] imagesToCombine = + [ sourceImage - }; + ]; // Combine - Bitmap combinedBitmap = ImageCombine.All(imagesToCombine); + Bitmap? combinedBitmap = ImageCombine.All(imagesToCombine); + Assert.IsNotNull(combinedBitmap); // Convert for byte comparison byte[] sourceBytes = ImageBytes.FromImage(sourceImage); @@ -64,19 +62,18 @@ public void TestCombineAllSame() // Load source image using Image sourceImage = TestImage.FromResource(resourcePath); - Assert.IsNotNull(sourceImage); - + using Image sourceImageCopy = TestImage.FromResource(resourcePath); - Assert.IsNotNull(sourceImageCopy); - - Image[] imagesToCombine = new Image[] - { + + Image[] imagesToCombine = + [ sourceImage, sourceImageCopy - }; + ]; // Combine - Bitmap combinedImage = ImageCombine.All(imagesToCombine); + Bitmap? combinedImage = ImageCombine.All(imagesToCombine); + Assert.IsNotNull(combinedImage); // Convert for byte comparison Bitmap sourceBitmap = ImageFormatting.ToBitmap(sourceImage); @@ -92,18 +89,17 @@ public void TestCombineAllWithNegative() // Load source image using Image sourceImage = TestImage.FromResource(resourcePath); - Assert.IsNotNull(sourceImage); - + using Image negativeCopy = ImageColor.ToNegative(sourceImage); - Assert.IsNotNull(negativeCopy); - - Image[] imagesToCombine = new Image[] - { + + Image[] imagesToCombine = + [ sourceImage, negativeCopy - }; + ]; - Bitmap combinedImage = ImageCombine.All(imagesToCombine); + Bitmap? combinedImage = ImageCombine.All(imagesToCombine); + Assert.IsNotNull(combinedImage); // Get bytes for images byte[] combinedBytes = ImageBytes.FromImage(combinedImage); diff --git a/tests/Freedom35.ImageProcessing.Tests/TestImageConvolution.cs b/tests/Freedom35.ImageProcessing.Tests/TestImageConvolution.cs index a9a4cfd..f93a136 100644 --- a/tests/Freedom35.ImageProcessing.Tests/TestImageConvolution.cs +++ b/tests/Freedom35.ImageProcessing.Tests/TestImageConvolution.cs @@ -10,12 +10,12 @@ public class TestImageConvolution public void TestApplyKernelGrayscale() { // 4x5 image - byte[] imageBytes = { + byte[] imageBytes = [ 1, 1, 3, 3, 4, 1, 1, 4, 4, 3, 2, 1, 3, 3, 3, 1, 1, 1, 4, 4 - }; + ]; // Grayscale image BitmapData bmpData = new() @@ -34,21 +34,13 @@ public void TestApplyKernelGrayscale() // Apply kernel to bytes byte[] resultBytes = ImageConvolution.ApplyKernel(imageBytes, bmpData, kernelMatrix); - // Edge cases retain original values - //byte[] expectedBytes = { - // 2, 5, 7, 6, 4, - // 2, 4, 7, 7, 3, - // 3, 2, 7, 7, 3, - // 1, 1, 1, 4, 4 - //}; - // Edge cases assigned black value - byte[] expectedBytes = { + byte[] expectedBytes = [ 2, 5, 7, 6, 0, 2, 4, 7, 7, 0, 3, 2, 7, 7, 0, 0, 0, 0, 0, 0 - }; + ]; // Check arrays are same length Assert.AreEqual(expectedBytes.Length, resultBytes.Length); @@ -64,12 +56,12 @@ public void TestApplyKernelGrayscale() public void TestApplyKernelColor() { // 4x5 image (RGB) - byte[] imageBytes = { + byte[] imageBytes = [ 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 4, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4 - }; + ]; // Color image BitmapData bmpData = new() @@ -89,12 +81,12 @@ public void TestApplyKernelColor() byte[] resultBytes = ImageConvolution.ApplyKernel(imageBytes, bmpData, kernelMatrix); // Edge cases assigned black value - byte[] expectedBytes = { + byte[] expectedBytes = [ 2, 2, 2, 5, 5, 5, 7, 7, 7, 6, 6, 6, 0, 0, 0, 2, 2, 2, 4, 4, 4, 7, 7, 7, 7, 7, 7, 0, 0, 0, 3, 3, 3, 2, 2, 2, 7, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; + ]; // Check arrays are same length Assert.AreEqual(expectedBytes.Length, resultBytes.Length); diff --git a/tests/Freedom35.ImageProcessing.Tests/TestImageCopy.cs b/tests/Freedom35.ImageProcessing.Tests/TestImageCopy.cs index c928e09..60cf01b 100644 --- a/tests/Freedom35.ImageProcessing.Tests/TestImageCopy.cs +++ b/tests/Freedom35.ImageProcessing.Tests/TestImageCopy.cs @@ -11,7 +11,7 @@ public class TestImageCopy public void TestFromSourceToDestination(string resourcePath) { // Load source image - using Bitmap sourceBitmap = TestImage.FromResource(resourcePath) as Bitmap; + using Bitmap? sourceBitmap = TestImage.FromResource(resourcePath) as Bitmap; Assert.IsNotNull(sourceBitmap); diff --git a/tests/Freedom35.ImageProcessing.Tests/TestImageCrop.cs b/tests/Freedom35.ImageProcessing.Tests/TestImageCrop.cs index 21541b0..873d2f0 100644 --- a/tests/Freedom35.ImageProcessing.Tests/TestImageCrop.cs +++ b/tests/Freedom35.ImageProcessing.Tests/TestImageCrop.cs @@ -16,15 +16,11 @@ public void TestByRegion(string resourcePath) // Load source image using Image sourceImage = TestImage.FromResource(resourcePath); - Assert.IsNotNull(sourceImage); - Rectangle region = new(0, 0, sourceImage.Width / 2, sourceImage.Height / 2); // Crop region of image using Image croppedImage = ImageCrop.ByRegion(sourceImage, region); - Assert.IsNotNull(croppedImage); - // Check size Assert.AreEqual(region.Width, croppedImage.Width); Assert.AreEqual(region.Height, croppedImage.Height); diff --git a/tests/Freedom35.ImageProcessing.Tests/TestImageResize.cs b/tests/Freedom35.ImageProcessing.Tests/TestImageResize.cs index ebd731c..6d2786c 100644 --- a/tests/Freedom35.ImageProcessing.Tests/TestImageResize.cs +++ b/tests/Freedom35.ImageProcessing.Tests/TestImageResize.cs @@ -13,9 +13,7 @@ public void TestImageResizeAsNew(string resourcePath) // Load source image using Image sourceImage = TestImage.FromResource(resourcePath); - Assert.IsNotNull(sourceImage); - - Bitmap bitmap = sourceImage as Bitmap; + Bitmap? bitmap = sourceImage as Bitmap; Assert.IsNotNull(bitmap); @@ -25,7 +23,6 @@ public void TestImageResizeAsNew(string resourcePath) using Image resizedImage = ImageResize.ResizeAsNew(bitmap, width, height); - Assert.IsNotNull(resizedImage); Assert.AreEqual(width, resizedImage.Width); Assert.AreEqual(height, resizedImage.Height); } @@ -37,15 +34,12 @@ public void TestImageResizeAsNewByRatio(string resourcePath) // Load source image using Image sourceImage = TestImage.FromResource(resourcePath); - Assert.IsNotNull(sourceImage); - - Bitmap bitmap = sourceImage as Bitmap; + Bitmap? bitmap = sourceImage as Bitmap; Assert.IsNotNull(bitmap); using Image resizedImage = ImageResize.ResizeAsNew(bitmap, 2.0); - Assert.IsNotNull(resizedImage); Assert.AreEqual(bitmap.Width * 2, resizedImage.Width); Assert.AreEqual(bitmap.Height * 2, resizedImage.Height); } @@ -57,9 +51,7 @@ public void TestImageResizeOriginal(string resourcePath) // Load source image Image sourceImage = TestImage.FromResource(resourcePath); - Assert.IsNotNull(sourceImage); - - Bitmap bitmap = sourceImage as Bitmap; + Bitmap? bitmap = sourceImage as Bitmap; Assert.IsNotNull(bitmap); @@ -83,9 +75,7 @@ public void TestImageResizeOriginalByRatio(string resourcePath) // Load source image Image sourceImage = TestImage.FromResource(resourcePath); - Assert.IsNotNull(sourceImage); - - Bitmap bitmap = sourceImage as Bitmap; + Bitmap? bitmap = sourceImage as Bitmap; Assert.IsNotNull(bitmap); diff --git a/tests/Freedom35.ImageProcessing.Tests/TestImageThreshold.cs b/tests/Freedom35.ImageProcessing.Tests/TestImageThreshold.cs index 41ddc89..c5853ae 100644 --- a/tests/Freedom35.ImageProcessing.Tests/TestImageThreshold.cs +++ b/tests/Freedom35.ImageProcessing.Tests/TestImageThreshold.cs @@ -14,18 +14,12 @@ public void TestApplyThreshold(string sourceResourcePath, string resultResourceP // Load source image using Image sourceImage = TestImage.FromResource(sourceResourcePath); - Assert.IsNotNull(sourceImage); - // Apply Thresholding - currently uses Otsu as default using Image thresholdImage = ImageThreshold.Apply(sourceImage); - Assert.IsNotNull(thresholdImage); - // Load correct result image using Image resultImage = TestImage.FromResource(resultResourcePath); - Assert.IsNotNull(resultImage); - // Compare images Assert.IsTrue(TestImage.Compare(thresholdImage, resultImage)); } @@ -37,18 +31,12 @@ public void TestOtsuThreshold(string sourceResourcePath, string resultResourcePa // Load source image using Image sourceImage = TestImage.FromResource(sourceResourcePath); - Assert.IsNotNull(sourceImage); - // Apply Thresholding using Image thresholdImage = ImageThreshold.ApplyOtsuMethod(sourceImage); - Assert.IsNotNull(thresholdImage); - // Load correct result image using Image resultImage = TestImage.FromResource(resultResourcePath); - Assert.IsNotNull(resultImage); - // Compare images Assert.IsTrue(TestImage.Compare(thresholdImage, resultImage)); } @@ -63,12 +51,9 @@ public void TestApplyThresholdValue(string sourceResourcePath) // Load source image using Image sourceImage = TestImage.FromResource(sourceResourcePath); - Assert.IsNotNull(sourceImage); - // Apply threshold using Image thresholdImage = ImageThreshold.Apply(sourceImage, 0x40); - Assert.IsNotNull(thresholdImage); - + byte[] withoutAlphaBytes = RemoveAlphaLayerBytes(ImageBytes.FromImage(thresholdImage), sourceImage.PixelFormat); // Check bytes thresholded @@ -85,13 +70,9 @@ public void TestApplyMin(string sourceResourcePath) // Load source image using Image sourceImage = TestImage.FromResource(sourceResourcePath); - Assert.IsNotNull(sourceImage); - // Apply min value using Image minImage = ImageThreshold.ApplyMin(sourceImage, 0x20); - Assert.IsNotNull(minImage); - byte[] withoutAlphaBytes = RemoveAlphaLayerBytes(ImageBytes.FromImage(minImage), sourceImage.PixelFormat); // Check all greater than value @@ -108,13 +89,9 @@ public void TestApplyMax(string sourceResourcePath) // Load source image using Image sourceImage = TestImage.FromResource(sourceResourcePath); - Assert.IsNotNull(sourceImage); - // Apply max value using Image maxImage = ImageThreshold.ApplyMax(sourceImage, 0xD0); - Assert.IsNotNull(maxImage); - byte[] withoutAlphaBytes = RemoveAlphaLayerBytes(ImageBytes.FromImage(maxImage), sourceImage.PixelFormat); // Check all less than value @@ -131,13 +108,9 @@ public void TestApplyMinMax(string sourceResourcePath) // Load source image using Image sourceImage = TestImage.FromResource(sourceResourcePath); - Assert.IsNotNull(sourceImage); - // Apply min/max value using Image minMaxImage = ImageThreshold.ApplyMinMax(sourceImage, 0x25, 0xc3); - Assert.IsNotNull(minMaxImage); - byte[] withoutAlphaBytes = RemoveAlphaLayerBytes(ImageBytes.FromImage(minMaxImage), sourceImage.PixelFormat); // Check all within range From 8b8d5b74b7aa90087e80bc3fd757425c638cea7a Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 15:18:50 -0800 Subject: [PATCH 12/13] Updated copyright. --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 0db21e2..1408380 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Alan Barr +Copyright (c) 2020-2024 Alan Barr Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 11d4f82f6799fa83ac0073dbce4ba0768a4e74b5 Mon Sep 17 00:00:00 2001 From: freedom35 Date: Sat, 28 Dec 2024 15:42:23 -0800 Subject: [PATCH 13/13] Updated readme. --- README.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c59cba2..153beae 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Image Processing Library -This image processing library is a lightweight open source library targeting **.NET Standard v2.0**. +This image processing library is a lightweight open source library targeting [.NET Standard v2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0). This library may be used as an educational tool on how such image processing methods can be implemented, or used within your own projects that require some form of image processing. @@ -8,7 +8,7 @@ See the appropriate section for details on each image processing class and their You are welcome to use/update this software under the terms of the **MIT license**. Notes: -1. **.NET Standard** libraries can be used in **.NET Full Framework** and **.NET Core** (now **.NET 6.0**) projects. +1. **.NET Standard v2.0** libraries can be used in **.NET Full Framework** and **.NET Core** (now known simply as **.NET**) projects. 2. The image processing library is only supported on **Windows OS** due to the dependency on Microsoft's [System.Drawing.Common](https://www.nuget.org/packages/System.Drawing.Common) NuGet package.
@@ -16,25 +16,26 @@ Notes: The published package is available for download on [nuget.org](https://www.nuget.org/packages/Freedom35.ImageProcessing). |Date|Version|Release Notes| |:---|:---:|:----| -|2022/06/16|[1.4.1](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.4.1)|Updated System.Drawing.Common package dependency to v6.0.0.| -|2021/07/30|[1.4.0](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.4.0)|Added overload methods for converting color images to grayscale.
Added overload methods for converting images to black & white.| -|2021/07/28|[1.3.0](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.3.0)|Added support for applying sepia filter to images.| -|2021/07/08|[1.2.0](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.2.0)|Fixed issue with stride padded images causing an *index out of bounds* exception when enhancing contrast.
Fixed issue with RGB color filters not working correctly for images with stride padding and alpha bytes.
Fixed issue with processing images with stride padding and alpha bytes.
Fixed issue where max threshold value was not correctly applied to the red (RGB) byte for color images.
Revision to combine images using bitwise OR.| -|2021/03/24|[1.1.0](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.1.0)|Added support for applying EXIF orientation data to images.| -|2021/01/26|[1.0.2](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.0.2)|Revision to return rounded value for ImageBytes.GetAverageValue.| -|2021/01/25|[1.0.1](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.0.1)|Revision to add enum description attribute for 'Mexican Hat' smoothing filter.| -|2020/10/02|[1.0.0](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.0.0)|Initial release.| +|2024-12-28|[1.5.0](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.5.0)|Updated System.Drawing.Common package dependency to v9.0.0.| +|2022-06-16|[1.4.1](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.4.1)|Updated System.Drawing.Common package dependency to v6.0.0.| +|2021-07-30|[1.4.0](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.4.0)|Added overload methods for converting color images to grayscale.
Added overload methods for converting images to black & white.| +|2021-07-28|[1.3.0](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.3.0)|Added support for applying sepia filter to images.| +|2021-07-08|[1.2.0](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.2.0)|Fixed issue with stride padded images causing an *index out of bounds* exception when enhancing contrast.
Fixed issue with RGB color filters not working correctly for images with stride padding and alpha bytes.
Fixed issue with processing images with stride padding and alpha bytes.
Fixed issue where max threshold value was not correctly applied to the red (RGB) byte for color images.
Revision to combine images using bitwise OR.| +|2021-03-24|[1.1.0](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.1.0)|Added support for applying EXIF orientation data to images.| +|2021-01-26|[1.0.2](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.0.2)|Revision to return rounded value for ImageBytes.GetAverageValue.| +|2021-01-25|[1.0.1](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.0.1)|Revision to add enum description attribute for 'Mexican Hat' smoothing filter.| +|2020-10-02|[1.0.0](https://www.nuget.org/packages/Freedom35.ImageProcessing/1.0.0)|Initial release.|

# Sample Solutions -The repository contains some sample [Visual Studio](https://visualstudio.microsoft.com) solutions described below. +The repository contains some [Visual Studio](https://visualstudio.microsoft.com) solutions described below. |Name|Description| |-----|-----| |Freedom35.ImageProcessing.sln|Base solution containing image processing library and unit tests.| -|Freedom35.ImageProcessing.WindowsDesktop.sln|Extended solution containing projects from base solution, plus an Image Viewer app for Windows desktop.| +|Freedom35.ImageProcessing.WindowsDesktop.sln|Extended solution containing projects from base solution, plus an **Image Viewer app** for Windows desktop.|

@@ -52,7 +53,7 @@ The **Bitmap** class used is **System.Drawing.Bitmap**. ## Usage in Projects Note: Examples are in C#, but the library may also be used in other .NET language projects. -1. Add the **Image Processing Library** NuGet package to your .NET project. +1. Add the [Image Processing Library](https://www.nuget.org/packages/Freedom35.ImageProcessing) NuGet package to your .NET project. 2. Include the namespace for the Image Processing Library at the top of your code file. ```csharp