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