VectSharp  2.2.1
A light library for C# vector graphics
GaussianBlurFilter.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 Gaussian blur effect.
25  /// </summary>
27  {
28  /// <summary>
29  /// The standard deviation of the Gaussian blur.
30  /// </summary>
31  public double StandardDeviation { get; }
32 
33  /// <inheritdoc/>
34  public Point TopLeftMargin { get; }
35 
36  /// <inheritdoc/>
37  public Point BottomRightMargin { get; }
38 
39  /// <summary>
40  /// Creates a new <see cref="GaussianBlurFilter"/> with the specified standard deviation.
41  /// </summary>
42  /// <param name="standardDeviation">The standard deviation of the Gaussian blur.</param>
43  public GaussianBlurFilter(double standardDeviation)
44  {
45  this.StandardDeviation = standardDeviation;
46  this.TopLeftMargin = new Point(standardDeviation * 3, standardDeviation * 3);
47  this.BottomRightMargin = new Point(standardDeviation * 3, standardDeviation * 3);
48  }
49 
50  /// <inheritdoc/>
51  public RasterImage Filter(RasterImage image, double scale)
52  {
53  return FilterSRGB(image, scale);
54  }
55 
56  private RasterImage FilterSRGB(RasterImage image, double scale)
57  {
58  if (this.StandardDeviation * scale > 2)
59  {
60  int[] boxes = BoxesForGauss(this.StandardDeviation * scale, 3);
61 
62  for (int i = 0; i < boxes.Length; i++)
63  {
64  BoxBlurFilter box = new BoxBlurFilter((boxes[i] - 1) / 2);
65 
66  image = box.Filter(image, 1);
67  }
68  }
69  else
70  {
71  image = FilterSRGBExact(image, this.StandardDeviation * scale);
72  }
73 
74  return image;
75  }
76 
77  // Adapted from http://blog.ivank.net/fastest-gaussian-blur.html
78  private static int[] BoxesForGauss(double standardDeviation, int count)
79  {
80  double idealWeight = Math.Sqrt((12 * standardDeviation * standardDeviation / count) + 1);
81 
82  int lowerWeight = (int)Math.Floor(idealWeight);
83 
84  if (lowerWeight % 2 == 0)
85  {
86  lowerWeight--;
87  }
88 
89  int upperWeight = lowerWeight + 2;
90 
91  double idealSize = (12 * standardDeviation * standardDeviation - count * lowerWeight * lowerWeight - 4 * count * lowerWeight - 3 * count) / (-4 * lowerWeight - 4);
92 
93  int m = (int)Math.Round(idealSize);
94 
95  int[] sizes = new int[count];
96 
97  for (int i = 0; i < count; i++)
98  {
99  sizes[i] = i < m ? lowerWeight : upperWeight;
100  }
101 
102  return sizes;
103  }
104 
105  private RasterImage FilterSRGBExact(RasterImage image, double standardDeviation)
106  {
107  IntPtr intermediateData = System.Runtime.InteropServices.Marshal.AllocHGlobal(image.Width * image.Height * (image.HasAlpha ? 4 : 3));
108  IntPtr tbrData = System.Runtime.InteropServices.Marshal.AllocHGlobal(image.Width * image.Height * (image.HasAlpha ? 4 : 3));
109  GC.AddMemoryPressure(2 * image.Width * image.Height * (image.HasAlpha ? 4 : 3));
110 
111  int width = image.Width;
112  int height = image.Height;
113 
114  int kernelSize = (int)Math.Ceiling(standardDeviation * 3);
115 
116  double[] kernel = new double[kernelSize * 2 + 1];
117 
118  double total = 0;
119 
120  for (int i = 0; i <= kernelSize; i++)
121  {
122  kernel[i] = Math.Exp(-(kernelSize - i) * (kernelSize - i) / (2 * standardDeviation * standardDeviation));
123 
124  if (i < kernelSize)
125  {
126  kernel[2 * kernelSize - i] = kernel[i];
127 
128  total += kernel[i] * 2;
129  }
130  else
131  {
132  total += kernel[i];
133  }
134  }
135 
136  for (int i = 0; i < kernel.Length; i++)
137  {
138  kernel[i] /= total;
139  }
140 
141  int pixelSize = image.HasAlpha ? 4 : 3;
142  int stride = image.Width * pixelSize;
143 
144  int threads;
145 
146  double size = Math.Sqrt((double)image.Width * image.Height);
147 
148  if (size <= 128)
149  {
150  threads = 1;
151  }
152  else if (size <= 512)
153  {
154  threads = Math.Min(4, Environment.ProcessorCount);
155  }
156  else
157  {
158  threads = Math.Min(8, Environment.ProcessorCount);
159  }
160 
161  unsafe
162  {
163  byte* input = (byte*)image.ImageDataAddress;
164  byte* intermediate = (byte*)intermediateData;
165  byte* output = (byte*)tbrData;
166 
167  Action<int> yLoop;
168 
169  if (image.HasAlpha)
170  {
171  yLoop = y =>
172  {
173  for (int x = 0; x < width; x++)
174  {
175  double R = 0;
176  double G = 0;
177  double B = 0;
178  double weight = 0;
179 
180  for (int targetX = 0; targetX <= kernelSize * 2; targetX++)
181  {
182  int tX = Math.Min(Math.Max(0, x + targetX - kernelSize), width - 1);
183 
184  double a = input[y * stride + tX * 4 + 3] / 255.0;
185 
186  weight += kernel[targetX] * a;
187 
188  R += kernel[targetX] * input[y * stride + tX * 4] * a;
189  G += kernel[targetX] * input[y * stride + tX * 4 + 1] * a;
190  B += kernel[targetX] * input[y * stride + tX * 4 + 2] * a;
191  }
192 
193  if (weight != 0)
194  {
195  intermediate[y * stride + x * 4] = (byte)Math.Min(255, Math.Max(0, R / weight));
196  intermediate[y * stride + x * 4 + 1] = (byte)Math.Min(255, Math.Max(0, G / weight));
197  intermediate[y * stride + x * 4 + 2] = (byte)Math.Min(255, Math.Max(0, B / weight));
198  intermediate[y * stride + x * 4 + 3] = (byte)Math.Min(255, Math.Max(0, weight * 255));
199  }
200  else
201  {
202  intermediate[y * stride + x * 4] = 0;
203  intermediate[y * stride + x * 4 + 1] = 0;
204  intermediate[y * stride + x * 4 + 2] = 0;
205  intermediate[y * stride + x * 4 + 3] = 0;
206  }
207  }
208  };
209  }
210  else
211  {
212  yLoop = y =>
213  {
214  for (int x = 0; x < width; x++)
215  {
216  double R = 0;
217  double G = 0;
218  double B = 0;
219 
220  for (int targetX = 0; targetX <= kernelSize * 2; targetX++)
221  {
222  int tX = Math.Min(Math.Max(0, x + targetX - kernelSize), width - 1);
223 
224  R += kernel[targetX] * input[y * stride + tX * 3];
225  G += kernel[targetX] * input[y * stride + tX * 3 + 1];
226  B += kernel[targetX] * input[y * stride + tX * 3 + 2];
227  }
228 
229  intermediate[y * stride + x * 3] = (byte)Math.Min(255, Math.Max(0, R));
230  intermediate[y * stride + x * 3 + 1] = (byte)Math.Min(255, Math.Max(0, G));
231  intermediate[y * stride + x * 3 + 2] = (byte)Math.Min(255, Math.Max(0, B));
232  }
233  };
234  }
235 
236  Action<int> xLoop;
237 
238  if (image.HasAlpha)
239  {
240  xLoop = y =>
241  {
242  for (int x = 0; x < width; x++)
243  {
244  double R = 0;
245  double G = 0;
246  double B = 0;
247 
248 
249  double weight = 0;
250 
251  for (int targetY = 0; targetY <= kernelSize * 2; targetY++)
252  {
253  int tY = Math.Min(Math.Max(0, y + targetY - kernelSize), height - 1);
254 
255  double a = intermediate[tY * stride + x * 4 + 3] / 255.0;
256 
257  weight += kernel[targetY] * a;
258 
259  R += kernel[targetY] * intermediate[tY * stride + x * 4] * a;
260  G += kernel[targetY] * intermediate[tY * stride + x * 4 + 1] * a;
261  B += kernel[targetY] * intermediate[tY * stride + x * 4 + 2] * a;
262  }
263 
264  if (weight != 0)
265  {
266  output[y * stride + x * 4] = (byte)Math.Min(255, Math.Max(0, R / weight));
267  output[y * stride + x * 4 + 1] = (byte)Math.Min(255, Math.Max(0, G / weight));
268  output[y * stride + x * 4 + 2] = (byte)Math.Min(255, Math.Max(0, B / weight));
269  output[y * stride + x * 4 + 3] = (byte)Math.Min(255, Math.Max(0, weight * 255));
270  }
271  else
272  {
273  output[y * stride + x * 4] = 0;
274  output[y * stride + x * 4 + 1] = 0;
275  output[y * stride + x * 4 + 2] = 0;
276  output[y * stride + x * 4 + 3] = 0;
277  }
278  }
279  };
280  }
281  else
282  {
283  xLoop = y =>
284  {
285  for (int x = 0; x < width; x++)
286  {
287  double R = 0;
288  double G = 0;
289  double B = 0;
290 
291  for (int targetY = 0; targetY <= kernelSize * 2; targetY++)
292  {
293  int tY = Math.Min(Math.Max(0, y + targetY - kernelSize), height - 1);
294 
295  R += kernel[targetY] * intermediate[tY * stride + x * 3];
296  G += kernel[targetY] * intermediate[tY * stride + x * 3 + 1];
297  B += kernel[targetY] * intermediate[tY * stride + x * 3 + 2];
298  }
299 
300  output[y * stride + x * 3] = (byte)Math.Min(255, Math.Max(0, R));
301  output[y * stride + x * 3 + 1] = (byte)Math.Min(255, Math.Max(0, G));
302  output[y * stride + x * 3 + 2] = (byte)Math.Min(255, Math.Max(0, B));
303  }
304  };
305  }
306 
307 
308 
309  if (threads == 1)
310  {
311  for (int y = 0; y < height; y++)
312  {
313  yLoop(y);
314  }
315 
316  for (int y = 0; y < height; y++)
317  {
318  xLoop(y);
319  }
320  }
321  else
322  {
323  ParallelOptions options = new ParallelOptions() { MaxDegreeOfParallelism = threads };
324 
325  Parallel.For(0, height, options, yLoop);
326  Parallel.For(0, height, options, xLoop);
327  }
328  }
329 
330  System.Runtime.InteropServices.Marshal.FreeHGlobal(intermediateData);
331  GC.RemoveMemoryPressure(image.Width * image.Height * (image.HasAlpha ? 4 : 3));
332 
333  DisposableIntPtr disp = new DisposableIntPtr(tbrData);
334 
335  return new RasterImage(ref disp, image.Width, image.Height, image.HasAlpha, image.Interpolate);
336  }
337 
338  }
339 }
340 
VectSharp.Filters.GaussianBlurFilter.Filter
RasterImage Filter(RasterImage image, double scale)
Applies the filter to a RasterImage.
Definition: GaussianBlurFilter.cs:51
VectSharp.RasterImage
Represents a raster image, created from raw pixel data. Consider using the derived classes included i...
Definition: RasterImage.cs:99
VectSharp.Filters.BoxBlurFilter
Represents a filter applying a box blur.
Definition: BoxBlurFilter.cs:27
VectSharp.RasterImage.Width
int Width
The width in pixels of the image.
Definition: RasterImage.cs:123
VectSharp.Filters.GaussianBlurFilter.StandardDeviation
double StandardDeviation
The standard deviation of the Gaussian blur.
Definition: GaussianBlurFilter.cs:31
VectSharp.Filters.GaussianBlurFilter.TopLeftMargin
Point TopLeftMargin
Definition: GaussianBlurFilter.cs:34
VectSharp.Filters
Definition: BoxBlurFilter.cs:22
VectSharp.Filters.GaussianBlurFilter.GaussianBlurFilter
GaussianBlurFilter(double standardDeviation)
Creates a new GaussianBlurFilter with the specified standard deviation.
Definition: GaussianBlurFilter.cs:43
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.Point
Represents a point relative to an origin in the top-left corner.
Definition: Point.cs:28
VectSharp.Filters.GaussianBlurFilter.BottomRightMargin
Point BottomRightMargin
Definition: GaussianBlurFilter.cs:37
VectSharp.Filters.BoxBlurFilter.Filter
RasterImage Filter(RasterImage image, double scale)
Applies the filter to a RasterImage.
Definition: BoxBlurFilter.cs:50
VectSharp.Filters.GaussianBlurFilter
Represents a filter that applies a Gaussian blur effect.
Definition: GaussianBlurFilter.cs:27