diff --git a/.gitignore b/.gitignore index bc78471..b8dfa8d 100644 --- a/.gitignore +++ b/.gitignore @@ -482,3 +482,9 @@ $RECYCLE.BIN/ # Vim temporary swap files *.swp + + +ImageConvolution/Agent_Processed_Output/ +ImageConvolution/Processed_Output_Par/ +ImageConvolution/Processed_Output_Seq/ +ImageConvolution/Processed_Output_*/ \ No newline at end of file diff --git a/ImageConvolution.Tests/ConvolutionTests.cs b/ImageConvolution.Tests/ConvolutionTests.cs new file mode 100644 index 0000000..8672a84 --- /dev/null +++ b/ImageConvolution.Tests/ConvolutionTests.cs @@ -0,0 +1,82 @@ +using System; +using System.IO; + +using ImageConvolution; + +using Xunit; + +namespace ImageConvolution.Tests; + +public class ConvolutionTests +{ + [Fact] + public void Test_ImageIO_SaveAndLoad() + { + string testPath = "test_image.png"; + double[,] originalData = new double[2, 2] { { 0, 255 }, { 128, 64 } }; + + try + { + ImageIO.SaveImage(originalData, testPath); + Assert.True(File.Exists(testPath)); + + double[,] loadedData = ImageIO.LoadAsGrayscale(testPath); + + Assert.Equal(2, loadedData.GetLength(0)); + Assert.Equal(2, loadedData.GetLength(1)); + } + finally + { + if (File.Exists(testPath)) File.Delete(testPath); + } + } + + [Fact] + public void Test_Convolve_ExtendStrategy() + { + double[,] image = { { 10, 10, 10 }, { 10, 10, 10 }, { 10, 10, 10 } }; + double[,] result = ConvolutionProcessor.Convolve(image, Kernels.BlurBox, EdgeStrategy.Extend); + Assert.Equal(10, Math.Round(result[1, 1])); + } + + [Fact] + public void Test_Convolve_ZeroPaddingStrategy() + { + double[,] image = { { 10, 10, 10 }, { 10, 10, 10 }, { 10, 10, 10 } }; + double[,] result = ConvolutionProcessor.Convolve(image, Kernels.BlurBox, EdgeStrategy.ZeroPadding); + Assert.True(result[0, 0] < 10); + } + + [Fact] + public void Test_ConvolveParallel() + { + double[,] image = { { 100, 100, 100 }, { 100, 100, 100 }, { 100, 100, 100 } }; + double[,] result = ParallelConvolutionProcessor.ConvolveParallel(image, Kernels.BlurBox, EdgeStrategy.Extend); + Assert.Equal(100, Math.Round(result[1, 1])); + } + + [Fact] + public void Test_KernelsExist() + { + Assert.NotNull(Kernels.BlurBox); + Assert.NotNull(Kernels.Sharpen); + Assert.NotNull(Kernels.Laplacian); + } + + [Fact] + public void Test_ConvolveParallel_ZeroPadding() + { + double[,] image = { + { 10, 10, 10 }, + { 10, 10, 10 }, + { 10, 10, 10 } + }; + + double[,] result = ParallelConvolutionProcessor.ConvolveParallel( + image, + Kernels.BlurBox, + EdgeStrategy.ZeroPadding); + + Assert.True(result[0, 0] < 10); + } +} \ No newline at end of file diff --git a/ImageConvolution.Tests/ImageConvolution.Tests.csproj b/ImageConvolution.Tests/ImageConvolution.Tests.csproj new file mode 100644 index 0000000..73f3a52 --- /dev/null +++ b/ImageConvolution.Tests/ImageConvolution.Tests.csproj @@ -0,0 +1,29 @@ + + + + net9.0 + enable + enable + false + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + diff --git a/ImageConvolution/ImageConvolution.csproj b/ImageConvolution/ImageConvolution.csproj new file mode 100644 index 0000000..01d83e7 --- /dev/null +++ b/ImageConvolution/ImageConvolution.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/ImageConvolution/Program.cs b/ImageConvolution/Program.cs new file mode 100644 index 0000000..e6c21f9 --- /dev/null +++ b/ImageConvolution/Program.cs @@ -0,0 +1,214 @@ +using System; +using System.Data; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading.Tasks; + +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace ImageConvolution +{ + public enum EdgeStrategy + { + Extend, + ZeroPadding + } + + [ExcludeFromCodeCoverage] + class Program + { + static void Main(string[] args) + { + Console.WriteLine("=== Программа свёртки изображений ==="); + Console.Write("Введите путь к изображению (перетащите файл в консоль): "); + + string? inputPath = Console.ReadLine()?.Trim('\"', ' ', '\''); + + if (string.IsNullOrEmpty(inputPath) || !File.Exists(inputPath)) + { + Console.WriteLine("Ошибка: Файл не найден! Проверьте путь и попробуйте снова."); + return; + } + + string directory = Path.GetDirectoryName(inputPath) ?? ""; + string fileName = Path.GetFileNameWithoutExtension(inputPath); + string extension = Path.GetExtension(inputPath); + string outputPath = Path.Combine(directory, fileName + "_filtered" + extension); + + try + { + Console.WriteLine("\n1. Загрузка изображения..."); + double[,] image = ImageIO.LoadAsGrayscale(inputPath); + + Console.WriteLine("2. Применение свёртки (Размытие / Blur)..."); + Stopwatch sw = Stopwatch.StartNew(); + + double[,] result = ConvolutionProcessor.Convolve( + image, + Kernels.BlurBox, + EdgeStrategy.Extend); + + sw.Stop(); + Console.WriteLine("2.1. Применение параллельной свёртки (Размытие / Blur)..."); + Stopwatch sw1 = Stopwatch.StartNew(); + + double[,] result2 = ParallelConvolutionProcessor.ConvolveParallel( + image, + Kernels.BlurBox, + EdgeStrategy.Extend); + + sw1.Stop(); + + Console.WriteLine($"Свёртка завершена за {sw.ElapsedMilliseconds} мс"); + + Console.WriteLine($"Параллельная свёртка завершена за {sw1.ElapsedMilliseconds} мс"); + + Console.WriteLine("3. Сохранение результата..."); + ImageIO.SaveImage(result, outputPath); + + Console.WriteLine($"\nГотово! Изображение сохранено здесь:\n{outputPath}"); + } + catch (Exception ex) + { + Console.WriteLine($"Произошла ошибка: {ex.Message}"); + } + } + } + + public class ImageIO + { + public static double[,] LoadAsGrayscale(string path) + { + using Image image = Image.Load(path); + int width = image.Width; + int height = image.Height; + double[,] result = new double[height, width]; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + Rgba32 pixel = image[x, y]; + double gray = 0.299 * pixel.R + 0.587 * pixel.G + 0.114 * pixel.B; + result[y, x] = gray; + } + } + return result; + } + public static void SaveImage(double[,] data, string path) + { + int height = data.GetLength(0); + int width = data.GetLength(1); + + using Image image = new Image(width, height); + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + double val = data[y, x]; + byte gray = (byte)Math.Clamp(val, 0, 255); + image[x, y] = new Rgba32(gray, gray, gray); + } + } + image.Save(path); + } + } + + public class Kernels + { + public static double[,] Sharpen = new double[,] + { + { 0, -1, 0}, + { -1, 5, -1}, + { 0, -1, 0} + }; + public static double[,] Laplacian = new double[,] + { + {-1, -1, -1}, + {-1, 8, -1}, + {-1, -1, -1} + }; + public static double[,] BlurBox = new double[,] + { + {1.0/9.0, 1.0/9.0, 1.0/9.0}, + {1.0/9.0, 1.0/9.0, 1.0/9.0}, + {1.0/9.0, 1.0/9.0, 1.0/9.0} + }; + } + + public class ConvolutionProcessor + { + internal static double CalculatePixelValue(double[,] image, double[,] kernel, int x, int y, EdgeStrategy strategy) + { + double sum = 0.0; + int imgheight = image.GetLength(0); + int imgwidth = image.GetLength(1); + int kheight = kernel.GetLength(0); + int kwidth = kernel.GetLength(1); + int offsetY = kheight / 2; + int offsetX = kwidth / 2; + + for (int ky = 0; ky < kheight; ky++) + { + for (int kx = 0; kx < kwidth; kx++) + { + int pixelY = y + ky - offsetY; + int pixelX = x + kx - offsetX; + double pixelValue = 0.0; + + if (strategy == EdgeStrategy.Extend) + { + pixelY = Math.Clamp(pixelY, 0, imgheight - 1); + pixelX = Math.Clamp(pixelX, 0, imgwidth - 1); + pixelValue = image[pixelY, pixelX]; + } + else if (strategy == EdgeStrategy.ZeroPadding) + { + if (pixelY >= 0 && pixelY < imgheight && pixelX >= 0 && pixelX < imgwidth) + { + pixelValue = image[pixelY, pixelX]; + } + } + sum += pixelValue * kernel[ky, kx]; + } + } + return sum; + } + + public static double[,] Convolve(double[,] image, double[,] kernel, EdgeStrategy strategy = EdgeStrategy.Extend) + { + int imgheight = image.GetLength(0); + int imgwidth = image.GetLength(1); + double[,] result = new double[imgheight, imgwidth]; + for (int y = 0; y < imgheight; y++) + { + for (int x = 0; x < imgwidth; x++) + { + result[y, x] = CalculatePixelValue(image, kernel, x, y, strategy); + } + } + return result; + } + } + public class ParallelConvolutionProcessor + { + + public static double[,] ConvolveParallel(double[,] image, double[,] kernal, EdgeStrategy strategy = EdgeStrategy.Extend) + { + int imgheight = image.GetLength(0); + int imgwidth = image.GetLength(1); + double[,] result = new double[imgheight, imgwidth]; + + Parallel.For(0, imgheight, y => + { + for (int x = 0; x < imgwidth; x++) + { + result[y, x] = ConvolutionProcessor.CalculatePixelValue(image, kernal, x, y, strategy); + } + }); + return result; + } + } +} \ No newline at end of file