19 using System.Threading.Tasks;
36 public virtual double[,]
Kernel {
get;
protected set; }
46 public virtual double Bias {
get;
protected set; } = 0;
51 public virtual double Scale {
get;
protected set; }
67 public ConvolutionFilter(
double[,] kernel,
double scale,
bool preserveAlpha =
true,
double normalisation = 1,
double bias = 0)
69 if (kernel.GetLength(0) % 2 != 1 || kernel.GetLength(1) % 2 != 1)
71 throw new ArgumentException(
"The kernel must have an odd number of rows and columns!", nameof(kernel));
76 int kernelWidth = (this.
Kernel.GetLength(0) - 1) / 2;
77 int kernelHeight = (this.
Kernel.GetLength(1) - 1) / 2;
90 IntPtr tbrData = System.Runtime.InteropServices.Marshal.AllocHGlobal(image.
Width * image.
Height * (image.
HasAlpha ? 4 : 3));
92 int width = image.
Width;
95 int kernelWidth = (this.
Kernel.GetLength(0) - 1) / 2;
96 int kernelHeight = (this.
Kernel.GetLength(1) - 1) / 2;
98 int actualKernelWidth = (int)Math.Round(kernelWidth * scale *
this.Scale);
99 int actualKernelHeight = (int)Math.Round(kernelHeight * scale *
this.Scale);
101 actualKernelWidth = Math.Max(actualKernelWidth, 1);
102 actualKernelHeight = Math.Max(actualKernelHeight, 1);
104 int[] kernelX =
new int[actualKernelWidth * 2 + 1];
106 for (
int x = 0; x < actualKernelWidth * 2 + 1; x++)
108 kernelX[x] = (int)Math.Round((
double)(x - actualKernelWidth) / actualKernelWidth * kernelWidth + kernelWidth);
111 int[] kernelY =
new int[actualKernelHeight * 2 + 1];
113 for (
int y = 0; y < actualKernelHeight * 2 + 1; y++)
115 kernelY[y] = (int)Math.Round((
double)(y - actualKernelHeight) / actualKernelHeight * kernelHeight + kernelHeight);
118 int[] countsX =
new int[2 * kernelWidth + 1];
120 for (
int i = 0; i < 2 * actualKernelWidth + 1; i++)
122 countsX[kernelX[i]]++;
125 int[] countsY =
new int[2 * kernelHeight + 1];
127 for (
int i = 0; i < 2 * actualKernelHeight + 1; i++)
129 countsY[kernelY[i]]++;
133 double[] weightsX =
new double[2 * actualKernelWidth + 1];
135 for (
int i = 0; i < 2 * actualKernelWidth + 1; i++)
137 weightsX[i] = 1.0 / countsX[kernelX[i]];
140 double[] weightsY =
new double[2 * actualKernelHeight + 1];
142 for (
int i = 0; i < 2 * actualKernelHeight + 1; i++)
144 weightsY[i] = 1.0 / countsY[kernelY[i]];
147 kernelWidth = actualKernelWidth;
148 kernelHeight = actualKernelHeight;
152 if (
double.IsNaN(normalisation) || normalisation == 0)
157 double totalWeight = 0;
158 for (
int i = 0; i < kernelWidth * 2 + 1; i++)
160 for (
int j = 0; j < kernelHeight * 2 + 1; j++)
162 totalWeight +=
Kernel[kernelX[i], kernelY[j]] * weightsX[i] * weightsY[j];
166 if (Math.Abs(totalWeight) < 1e-5)
172 double bias = this.
Bias * 255;
173 int pixelSize = image.
HasAlpha ? 4 : 3;
174 int stride = image.
Width * pixelSize;
176 int threads = Math.Min(8, Environment.ProcessorCount);
181 byte* output = (
byte*)tbrData;
189 for (
int x = 0; x < width; x++)
197 for (
int targetX = 0; targetX <= kernelWidth * 2; targetX++)
199 for (
int targetY = 0; targetY <= kernelHeight * 2; targetY++)
201 int tX = Math.Min(Math.Max(0, x + targetX - kernelWidth), width - 1);
202 int tY = Math.Min(Math.Max(0, y + targetY - kernelHeight), height - 1);
204 double a = input[tY * stride + tX * 4 + 3] / 255.0 * weightsX[targetX] * weightsY[targetY];
206 int projectedX = kernelX[targetX];
207 int projectedY = kernelY[targetY];
209 weight +=
Kernel[projectedX, projectedY] * a;
211 R +=
Kernel[projectedX, projectedY] * input[tY * stride + tX * 4] * a;
212 G +=
Kernel[projectedX, projectedY] * input[tY * stride + tX * 4 + 1] * a;
213 B +=
Kernel[projectedX, projectedY] * input[tY * stride + tX * 4 + 2] * a;
219 output[y * stride + x * 4] = (byte)Math.Min(255, Math.Max(0, R / (normalisation * weight) + bias));
220 output[y * stride + x * 4 + 1] = (byte)Math.Min(255, Math.Max(0, G / (normalisation * weight) + bias));
221 output[y * stride + x * 4 + 2] = (byte)Math.Min(255, Math.Max(0, B / (normalisation * weight) + bias));
222 output[y * stride + x * 4 + 3] = (byte)Math.Min(255, Math.Max(0, (weight / (normalisation * totalWeight) * 255 + bias)));
226 output[y * stride + x * 4] = 0;
227 output[y * stride + x * 4 + 1] = 0;
228 output[y * stride + x * 4 + 2] = 0;
229 output[y * stride + x * 4 + 3] = 0;
238 for (
int x = 0; x < width; x++)
244 for (
int targetX = 0; targetX <= kernelWidth * 2; targetX++)
246 for (
int targetY = 0; targetY <= kernelHeight * 2; targetY++)
248 int tX = Math.Min(Math.Max(0, x + targetX - kernelWidth), width - 1);
249 int tY = Math.Min(Math.Max(0, y + targetY - kernelHeight), height - 1);
251 int projectedX = kernelX[targetX];
252 int projectedY = kernelY[targetY];
254 R +=
Kernel[projectedX, projectedY] * input[tY * stride + tX * 4] * weightsX[targetX] * weightsY[targetY];
255 G +=
Kernel[projectedX, projectedY] * input[tY * stride + tX * 4 + 1] * weightsX[targetX] * weightsY[targetY];
256 B +=
Kernel[projectedX, projectedY] * input[tY * stride + tX * 4 + 2] * weightsX[targetX] * weightsY[targetY];
260 output[y * stride + x * 4] = (byte)Math.Min(255, Math.Max(0, R / (normalisation * totalWeight) + bias));
261 output[y * stride + x * 4 + 1] = (byte)Math.Min(255, Math.Max(0, G / (normalisation * totalWeight) + bias));
262 output[y * stride + x * 4 + 2] = (byte)Math.Min(255, Math.Max(0, B / (normalisation * totalWeight) + bias));
263 output[y * stride + x * 4 + 3] = input[y * stride + x * 4 + 3];
271 for (
int x = 0; x < width; x++)
277 for (
int targetX = 0; targetX <= kernelWidth * 2; targetX++)
279 for (
int targetY = 0; targetY <= kernelHeight * 2; targetY++)
281 int tX = Math.Min(Math.Max(0, x + targetX - kernelWidth), width - 1);
282 int tY = Math.Min(Math.Max(0, y + targetY - kernelHeight), height - 1);
284 int projectedX = kernelX[targetX];
285 int projectedY = kernelY[targetY];
287 R +=
Kernel[projectedX, projectedY] * input[tY * stride + tX * 3] * weightsX[targetX] * weightsY[targetY];
288 G +=
Kernel[projectedX, projectedY] * input[tY * stride + tX * 3 + 1] * weightsX[targetX] * weightsY[targetY];
289 B +=
Kernel[projectedX, projectedY] * input[tY * stride + tX * 3 + 2] * weightsX[targetX] * weightsY[targetY];
293 output[y * stride + x * 3] = (byte)Math.Min(255, Math.Max(0, R / (normalisation * totalWeight) + bias));
294 output[y * stride + x * 3 + 1] = (byte)Math.Min(255, Math.Max(0, G / (normalisation * totalWeight) + bias));
295 output[y * stride + x * 3 + 2] = (byte)Math.Min(255, Math.Max(0, B / (normalisation * totalWeight) + bias));
302 for (
int y = 0; y < height; y++)
309 ParallelOptions options =
new ParallelOptions() { MaxDegreeOfParallelism = threads };
311 Parallel.For(0, height, options, yLoop);