VectSharp  2.2.1
A light library for C# vector graphics
ConvolutionFilter.cs
1 /*
2  VectSharp - A light library for C# vector graphics.
3  Copyright (C) 2020-2022 Giorgio Bianchini
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, version 3.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU Lesser General Public License for more details.
13 
14  You should have received a copy of the GNU Lesser General Public License
15  along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17 
18 using System;
19 using System.Threading.Tasks;
20 
21 namespace VectSharp.Filters
22 {
23  /// <summary>
24  /// Represents a filter that applies a matrix convolution to the image.
25  /// </summary>
27  {
28  /// <inheritdoc/>
29  public Point TopLeftMargin { get; protected set; }
30  /// <inheritdoc/>
31  public Point BottomRightMargin { get; protected set; }
32 
33  /// <summary>
34  /// The kernel of the <see cref="ConvolutionFilter"/>. The dimensions of this matrix should all be odd numbers. The larger the kernel, the worse the performance.
35  /// </summary>
36  public virtual double[,] Kernel { get; protected set; }
37 
38  /// <summary>
39  /// The normalisation value that is applies to the kernel.
40  /// </summary>
41  public virtual double Normalisation { get; protected set; } = 1;
42 
43  /// <summary>
44  /// The bias value that is added to every colour component when the filter is applied.
45  /// </summary>
46  public virtual double Bias { get; protected set; } = 0;
47 
48  /// <summary>
49  /// The scale relating the size of the kernel to graphics units.
50  /// </summary>
51  public virtual double Scale { get; protected set; }
52 
53  /// <summary>
54  /// If this is <see langword="true"/>, the alpha value of the input pixels is preserved. Otherwise, the alpha channel is subject to the same convolution process as the other colour components.
55  /// </summary>
56  public virtual bool PreserveAlpha { get; protected set; } = true;
57 
58  /// <summary>
59  /// Creates a new <see cref="ConvolutionFilter"/> with the specified parameters.
60  /// </summary>
61  /// <param name="kernel">The kernel of the <see cref="ConvolutionFilter"/>. The dimensions of this matrix should all be odd numbers. The larger the kernel, the worse the performance.</param>
62  /// <param name="scale">The scale relating the size of the kernel to graphics units.</param>
63  /// <param name="preserveAlpha">If this is <see langword="true"/>, the alpha value of the input pixels is preserved. Otherwise, the alpha channel is subject to the same convolution process as the other colour components.</param>
64  /// <param name="normalisation">The normalisation value that is applies to the kernel.</param>
65  /// <param name="bias">The bias value that is added to every colour component when the filter is applied.</param>
66  /// <exception cref="ArgumentException">This exception is thrown when the kernel dimensions are not odd numbers.</exception>
67  public ConvolutionFilter(double[,] kernel, double scale, bool preserveAlpha = true, double normalisation = 1, double bias = 0)
68  {
69  if (kernel.GetLength(0) % 2 != 1 || kernel.GetLength(1) % 2 != 1)
70  {
71  throw new ArgumentException("The kernel must have an odd number of rows and columns!", nameof(kernel));
72  }
73 
74  this.Kernel = kernel;
75 
76  int kernelWidth = (this.Kernel.GetLength(0) - 1) / 2;
77  int kernelHeight = (this.Kernel.GetLength(1) - 1) / 2;
78 
79  this.TopLeftMargin = new Point(kernelWidth * scale, kernelHeight * scale);
80  this.BottomRightMargin = new Point(kernelWidth * scale, kernelHeight * scale);
81  this.Scale = scale;
82  this.PreserveAlpha = preserveAlpha;
83  this.Normalisation = normalisation;
84  this.Bias = bias;
85  }
86 
87  /// <inheritdoc/>
88  public virtual RasterImage Filter(RasterImage image, double scale)
89  {
90  IntPtr tbrData = System.Runtime.InteropServices.Marshal.AllocHGlobal(image.Width * image.Height * (image.HasAlpha ? 4 : 3));
91 
92  int width = image.Width;
93  int height = image.Height;
94 
95  int kernelWidth = (this.Kernel.GetLength(0) - 1) / 2;
96  int kernelHeight = (this.Kernel.GetLength(1) - 1) / 2;
97 
98  int actualKernelWidth = (int)Math.Round(kernelWidth * scale * this.Scale);
99  int actualKernelHeight = (int)Math.Round(kernelHeight * scale * this.Scale);
100 
101  actualKernelWidth = Math.Max(actualKernelWidth, 1);
102  actualKernelHeight = Math.Max(actualKernelHeight, 1);
103 
104  int[] kernelX = new int[actualKernelWidth * 2 + 1];
105 
106  for (int x = 0; x < actualKernelWidth * 2 + 1; x++)
107  {
108  kernelX[x] = (int)Math.Round((double)(x - actualKernelWidth) / actualKernelWidth * kernelWidth + kernelWidth);
109  }
110 
111  int[] kernelY = new int[actualKernelHeight * 2 + 1];
112 
113  for (int y = 0; y < actualKernelHeight * 2 + 1; y++)
114  {
115  kernelY[y] = (int)Math.Round((double)(y - actualKernelHeight) / actualKernelHeight * kernelHeight + kernelHeight);
116  }
117 
118  int[] countsX = new int[2 * kernelWidth + 1];
119 
120  for (int i = 0; i < 2 * actualKernelWidth + 1; i++)
121  {
122  countsX[kernelX[i]]++;
123  }
124 
125  int[] countsY = new int[2 * kernelHeight + 1];
126 
127  for (int i = 0; i < 2 * actualKernelHeight + 1; i++)
128  {
129  countsY[kernelY[i]]++;
130  }
131 
132 
133  double[] weightsX = new double[2 * actualKernelWidth + 1];
134 
135  for (int i = 0; i < 2 * actualKernelWidth + 1; i++)
136  {
137  weightsX[i] = 1.0 / countsX[kernelX[i]];
138  }
139 
140  double[] weightsY = new double[2 * actualKernelHeight + 1];
141 
142  for (int i = 0; i < 2 * actualKernelHeight + 1; i++)
143  {
144  weightsY[i] = 1.0 / countsY[kernelY[i]];
145  }
146 
147  kernelWidth = actualKernelWidth;
148  kernelHeight = actualKernelHeight;
149 
150  double normalisation = this.Normalisation;
151 
152  if (double.IsNaN(normalisation) || normalisation == 0)
153  {
154  normalisation = 1;
155  }
156 
157  double totalWeight = 0;
158  for (int i = 0; i < kernelWidth * 2 + 1; i++)
159  {
160  for (int j = 0; j < kernelHeight * 2 + 1; j++)
161  {
162  totalWeight += Kernel[kernelX[i], kernelY[j]] * weightsX[i] * weightsY[j];
163  }
164  }
165 
166  if (Math.Abs(totalWeight) < 1e-5)
167  {
168  totalWeight = 1;
169  }
170 
171 
172  double bias = this.Bias * 255;
173  int pixelSize = image.HasAlpha ? 4 : 3;
174  int stride = image.Width * pixelSize;
175 
176  int threads = Math.Min(8, Environment.ProcessorCount);
177 
178  unsafe
179  {
180  byte* input = (byte*)image.ImageDataAddress;
181  byte* output = (byte*)tbrData;
182 
183  Action<int> yLoop;
184 
185  if (image.HasAlpha && !PreserveAlpha)
186  {
187  yLoop = (y) =>
188  {
189  for (int x = 0; x < width; x++)
190  {
191  double R = 0;
192  double G = 0;
193  double B = 0;
194 
195  double weight = 0;
196 
197  for (int targetX = 0; targetX <= kernelWidth * 2; targetX++)
198  {
199  for (int targetY = 0; targetY <= kernelHeight * 2; targetY++)
200  {
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);
203 
204  double a = input[tY * stride + tX * 4 + 3] / 255.0 * weightsX[targetX] * weightsY[targetY];
205 
206  int projectedX = kernelX[targetX];
207  int projectedY = kernelY[targetY];
208 
209  weight += Kernel[projectedX, projectedY] * a;
210 
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;
214  }
215  }
216 
217  if (weight != 0)
218  {
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)));
223  }
224  else
225  {
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;
230  }
231  }
232  };
233  }
234  else if (image.HasAlpha && PreserveAlpha)
235  {
236  yLoop = (y) =>
237  {
238  for (int x = 0; x < width; x++)
239  {
240  double R = 0;
241  double G = 0;
242  double B = 0;
243 
244  for (int targetX = 0; targetX <= kernelWidth * 2; targetX++)
245  {
246  for (int targetY = 0; targetY <= kernelHeight * 2; targetY++)
247  {
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);
250 
251  int projectedX = kernelX[targetX];
252  int projectedY = kernelY[targetY];
253 
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];
257  }
258  }
259 
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];
264  }
265  };
266  }
267  else
268  {
269  yLoop = (y) =>
270  {
271  for (int x = 0; x < width; x++)
272  {
273  double R = 0;
274  double G = 0;
275  double B = 0;
276 
277  for (int targetX = 0; targetX <= kernelWidth * 2; targetX++)
278  {
279  for (int targetY = 0; targetY <= kernelHeight * 2; targetY++)
280  {
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);
283 
284  int projectedX = kernelX[targetX];
285  int projectedY = kernelY[targetY];
286 
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];
290  }
291  }
292 
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));
296  }
297  };
298  }
299 
300  if (threads == 1)
301  {
302  for (int y = 0; y < height; y++)
303  {
304  yLoop(y);
305  }
306  }
307  else
308  {
309  ParallelOptions options = new ParallelOptions() { MaxDegreeOfParallelism = threads };
310 
311  Parallel.For(0, height, options, yLoop);
312  }
313  }
314 
315  return new RasterImage(tbrData, image.Width, image.Height, image.HasAlpha, image.Interpolate);
316  }
317  }
318 }
VectSharp.Filters.ConvolutionFilter
Represents a filter that applies a matrix convolution to the image.
Definition: ConvolutionFilter.cs:27
VectSharp.RasterImage
Represents a raster image, created from raw pixel data. Consider using the derived classes included i...
Definition: RasterImage.cs:99
VectSharp.RasterImage.Width
int Width
The width in pixels of the image.
Definition: RasterImage.cs:123
VectSharp.Filters.ConvolutionFilter.Scale
virtual double Scale
The scale relating the size of the kernel to graphics units.
Definition: ConvolutionFilter.cs:51
VectSharp.Filters.ConvolutionFilter.TopLeftMargin
Point TopLeftMargin
Definition: ConvolutionFilter.cs:29
VectSharp.RasterImage.Interpolate
bool Interpolate
Determines whether the image should be interpolated when it is resized.
Definition: RasterImage.cs:133
VectSharp.Filters.ConvolutionFilter.Normalisation
virtual double Normalisation
The normalisation value that is applies to the kernel.
Definition: ConvolutionFilter.cs:41
VectSharp.Filters.ConvolutionFilter.ConvolutionFilter
ConvolutionFilter(double[,] kernel, double scale, bool preserveAlpha=true, double normalisation=1, double bias=0)
Creates a new ConvolutionFilter with the specified parameters.
Definition: ConvolutionFilter.cs:67
VectSharp.RasterImage.HasAlpha
bool HasAlpha
Determines whether the image has an alpha channel.
Definition: RasterImage.cs:118
VectSharp.Filters.ConvolutionFilter.Bias
virtual double Bias
The bias value that is added to every colour component when the filter is applied.
Definition: ConvolutionFilter.cs:46
VectSharp.Filters.ConvolutionFilter.Filter
virtual RasterImage Filter(RasterImage image, double scale)
Applies the filter to a RasterImage.
Definition: ConvolutionFilter.cs:88
VectSharp.Filters.ConvolutionFilter.PreserveAlpha
virtual bool PreserveAlpha
If this is true, the alpha value of the input pixels is preserved. Otherwise, the alpha channel is su...
Definition: ConvolutionFilter.cs:56
VectSharp.RasterImage.Height
int Height
The height in pixels of the image.
Definition: RasterImage.cs:128
VectSharp.Filters
Definition: BoxBlurFilter.cs:22
VectSharp.Filters.ConvolutionFilter.BottomRightMargin
Point BottomRightMargin
Definition: ConvolutionFilter.cs:31
VectSharp.Filters.ILocationInvariantFilter
Represents a filter that can be applied to an image regardless of its location on the graphics surfac...
Definition: Filters.cs:42
VectSharp.RasterImage.ImageDataAddress
IntPtr ImageDataAddress
The memory address of the image pixel data.
Definition: RasterImage.cs:103
VectSharp.Point
Represents a point relative to an origin in the top-left corner.
Definition: Point.cs:28
VectSharp.Filters.ConvolutionFilter.Kernel
virtual double[,] Kernel
The kernel of the ConvolutionFilter. The dimensions of this matrix should all be odd numbers....
Definition: ConvolutionFilter.cs:36