VectSharp  2.2.1
A light library for C# vector graphics
BoxBlurFilter.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 
22 {
23  /// <summary>
24  /// Represents a filter applying a box blur.
25  /// </summary>
27  {
28  /// <summary>
29  /// The radius of the box blur (the actual size of the box is 2 * <see cref="BoxRadius"/> + 1).
30  /// </summary>
31  public double BoxRadius { get; }
32 
33  /// <inheritdoc/>
34  public Point TopLeftMargin { get; }
35  /// <inheritdoc/>
36  public Point BottomRightMargin { get; }
37 
38  /// <summary>
39  /// Creates a new <see cref="BoxBlurFilter"/> with the specified radius.
40  /// </summary>
41  /// <param name="boxRadius">The radius of the box blur (the actual size of the box is 2 * <paramref name="boxRadius"/> + 1).</param>
42  public BoxBlurFilter(double boxRadius)
43  {
44  this.BoxRadius = boxRadius;
45  this.TopLeftMargin = new Point(boxRadius, boxRadius);
46  this.BottomRightMargin = new Point(boxRadius, boxRadius);
47  }
48 
49  /// <inheritdoc/>
50  public RasterImage Filter(RasterImage image, double scale)
51  {
52  return BoxBlurSRGB(image, (int)Math.Round(this.BoxRadius * scale));
53  }
54 
55  private RasterImage BoxBlurSRGB(RasterImage image, int boxRadius)
56  {
57  IntPtr intermediateData = System.Runtime.InteropServices.Marshal.AllocHGlobal(image.Width * image.Height * (image.HasAlpha ? 4 : 3));
58  IntPtr tbrData = System.Runtime.InteropServices.Marshal.AllocHGlobal(image.Width * image.Height * (image.HasAlpha ? 4 : 3));
59  GC.AddMemoryPressure(2 * image.Width * image.Height * (image.HasAlpha ? 4 : 3));
60 
61  int width = image.Width;
62  int height = image.Height;
63 
64  int pixelSize = image.HasAlpha ? 4 : 3;
65  int stride = image.Width * pixelSize;
66 
67  double normFactor = 1.0 / (2 * boxRadius + 1);
68 
69  int threads;
70 
71  double size = Math.Sqrt((double)image.Width * image.Height);
72 
73  if (size <= 128)
74  {
75  threads = 1;
76  }
77  else if (size <= 512)
78  {
79  threads = Math.Min(4, Environment.ProcessorCount);
80  }
81  else
82  {
83  threads = Math.Min(8, Environment.ProcessorCount);
84  }
85 
86  unsafe
87  {
88  byte* input = (byte*)image.ImageDataAddress;
89  byte* intermediate = (byte*)intermediateData;
90  byte* output = (byte*)tbrData;
91 
92  Action<int> yLoop;
93 
94  if (image.HasAlpha)
95  {
96  yLoop = y =>
97  {
98  double rAcc = 0;
99  double gAcc = 0;
100  double bAcc = 0;
101  double aAcc = 0;
102 
103  double prevR = 0;
104  double prevG = 0;
105  double prevB = 0;
106  double prevA = 0;
107 
108  double r = 0;
109  double g = 0;
110  double b = 0;
111 
112  double a = input[y * stride + 3] / 255.0;
113 
114  prevR = input[y * stride] * a;
115  prevG = input[y * stride + 1] * a;
116  prevB = input[y * stride + 2] * a;
117  prevA = a;
118 
119  rAcc = prevR * (boxRadius + 1);
120  gAcc = prevG * (boxRadius + 1);
121  bAcc = prevB * (boxRadius + 1);
122  aAcc = a * (boxRadius + 1);
123 
124  int rX = 0;
125 
126  for (int x = 0; x < boxRadius; x++)
127  {
128  rX = Math.Min(x, width - 1);
129 
130  a = input[y * stride + rX * 4 + 3] / 255.0;
131 
132  rAcc += input[y * stride + rX * 4] * a;
133  gAcc += input[y * stride + rX * 4 + 1] * a;
134  bAcc += input[y * stride + rX * 4 + 2] * a;
135  aAcc += a;
136  }
137 
138  rX = Math.Min(boxRadius, width - 1);
139  int lX = 0;
140 
141  for (int x = 0; x < width; x++)
142  {
143  rX = Math.Min(x + boxRadius, width - 1);
144  lX = Math.Max(x - boxRadius - 1, 0);
145 
146  a = input[y * stride + rX * 4 + 3] / 255.0;
147  r = input[y * stride + rX * 4] * a;
148  g = input[y * stride + rX * 4 + 1] * a;
149  b = input[y * stride + rX * 4 + 2] * a;
150 
151  prevA = input[y * stride + lX * 4 + 3] / 255.0;
152  prevR = input[y * stride + lX * 4] * prevA;
153  prevG = input[y * stride + lX * 4 + 1] * prevA;
154  prevB = input[y * stride + lX * 4 + 2] * prevA;
155 
156  rAcc += r - prevR;
157  gAcc += g - prevG;
158  bAcc += b - prevB;
159  aAcc += a - prevA;
160 
161  if (aAcc != 0)
162  {
163  intermediate[y * stride + x * 4] = (byte)Math.Min(255, Math.Max(0, rAcc / aAcc));
164  intermediate[y * stride + x * 4 + 1] = (byte)Math.Min(255, Math.Max(0, gAcc / aAcc));
165  intermediate[y * stride + x * 4 + 2] = (byte)Math.Min(255, Math.Max(0, bAcc / aAcc));
166  intermediate[y * stride + x * 4 + 3] = (byte)Math.Min(255, Math.Max(0, aAcc * 255 * normFactor));
167  }
168  else
169  {
170  intermediate[y * stride + x * 4] = 0;
171  intermediate[y * stride + x * 4 + 1] = 0;
172  intermediate[y * stride + x * 4 + 2] = 0;
173  intermediate[y * stride + x * 4 + 3] = 0;
174  }
175  }
176  };
177  }
178  else
179  {
180  yLoop = y =>
181  {
182  double rAcc = 0;
183  double gAcc = 0;
184  double bAcc = 0;
185 
186  double prevR = 0;
187  double prevG = 0;
188  double prevB = 0;
189 
190  double r = 0;
191  double g = 0;
192  double b = 0;
193 
194  prevR = input[y * stride];
195  prevG = input[y * stride + 1];
196  prevB = input[y * stride + 2];
197 
198  rAcc = prevR * (boxRadius + 1);
199  gAcc = prevG * (boxRadius + 1);
200  bAcc = prevB * (boxRadius + 1);
201 
202  int rX = 0;
203 
204  for (int x = 0; x < boxRadius; x++)
205  {
206  rX = Math.Min(x, width - 1);
207 
208  rAcc += input[y * stride + rX * 3];
209  gAcc += input[y * stride + rX * 3 + 1];
210  bAcc += input[y * stride + rX * 3 + 2];
211  }
212 
213  rX = Math.Min(boxRadius, width - 1);
214  int lX = 0;
215 
216  for (int x = 0; x < width; x++)
217  {
218  rX = Math.Min(x + boxRadius, width - 1);
219  lX = Math.Max(x - boxRadius - 1, 0);
220 
221  r = input[y * stride + rX * 3];
222  g = input[y * stride + rX * 3 + 1];
223  b = input[y * stride + rX * 3 + 2];
224 
225  prevR = input[y * stride + lX * 3];
226  prevG = input[y * stride + lX * 3 + 1];
227  prevB = input[y * stride + lX * 3 + 2];
228 
229  rAcc += r - prevR;
230  gAcc += g - prevG;
231  bAcc += b - prevB;
232 
233  intermediate[y * stride + x * 3] = (byte)Math.Min(255, Math.Max(0, rAcc * normFactor));
234  intermediate[y * stride + x * 3 + 1] = (byte)Math.Min(255, Math.Max(0, gAcc * normFactor));
235  intermediate[y * stride + x * 3 + 2] = (byte)Math.Min(255, Math.Max(0, bAcc * normFactor));
236  }
237  };
238  }
239 
240  Action<int> xLoop;
241 
242  if (image.HasAlpha)
243  {
244  xLoop = x =>
245  {
246  double rAcc = 0;
247  double gAcc = 0;
248  double bAcc = 0;
249  double aAcc = 0;
250 
251  double prevR = 0;
252  double prevG = 0;
253  double prevB = 0;
254  double prevA = 0;
255 
256  double r = 0;
257  double g = 0;
258  double b = 0;
259 
260  double a = intermediate[x * 4 + 3] / 255.0;
261 
262  prevR = intermediate[x * 4] * a;
263  prevG = intermediate[x * 4 + 1] * a;
264  prevB = intermediate[x * 4 + 2] * a;
265  prevA = a;
266 
267  rAcc = prevR * (boxRadius + 1);
268  gAcc = prevG * (boxRadius + 1);
269  bAcc = prevB * (boxRadius + 1);
270  aAcc = a * (boxRadius + 1);
271 
272  int rY = 0;
273 
274  for (int y = 0; y < boxRadius; y++)
275  {
276  rY = Math.Min(y, height - 1);
277 
278  a = intermediate[rY * stride + x * 4 + 3] / 255.0;
279 
280  rAcc += intermediate[rY * stride + x * 4] * a;
281  gAcc += intermediate[rY * stride + x * 4 + 1] * a;
282  bAcc += intermediate[rY * stride + x * 4 + 2] * a;
283  aAcc += a;
284  }
285 
286  rY = Math.Min(boxRadius, height - 1);
287  int lY = 0;
288 
289  for (int y = 0; y < height; y++)
290  {
291  rY = Math.Min(y + boxRadius, height - 1);
292  lY = Math.Max(y - boxRadius - 1, 0);
293 
294  a = intermediate[rY * stride + x * 4 + 3] / 255.0;
295  r = intermediate[rY * stride + x * 4] * a;
296  g = intermediate[rY * stride + x * 4 + 1] * a;
297  b = intermediate[rY * stride + x * 4 + 2] * a;
298 
299  prevA = intermediate[lY * stride + x * 4 + 3] / 255.0;
300  prevR = intermediate[lY * stride + x * 4] * prevA;
301  prevG = intermediate[lY * stride + x * 4 + 1] * prevA;
302  prevB = intermediate[lY * stride + x * 4 + 2] * prevA;
303 
304  rAcc += r - prevR;
305  gAcc += g - prevG;
306  bAcc += b - prevB;
307  aAcc += a - prevA;
308 
309  if (aAcc != 0)
310  {
311  output[y * stride + x * 4] = (byte)Math.Min(255, Math.Max(0, rAcc / aAcc));
312  output[y * stride + x * 4 + 1] = (byte)Math.Min(255, Math.Max(0, gAcc / aAcc));
313  output[y * stride + x * 4 + 2] = (byte)Math.Min(255, Math.Max(0, bAcc / aAcc));
314  output[y * stride + x * 4 + 3] = (byte)Math.Min(255, Math.Max(0, aAcc * 255 * normFactor));
315  }
316  else
317  {
318  output[y * stride + x * 4] = 0;
319  output[y * stride + x * 4 + 1] = 0;
320  output[y * stride + x * 4 + 2] = 0;
321  output[y * stride + x * 4 + 3] = 0;
322  }
323  }
324  };
325  }
326  else
327  {
328  xLoop = x =>
329  {
330  double rAcc = 0;
331  double gAcc = 0;
332  double bAcc = 0;
333 
334  double prevR = 0;
335  double prevG = 0;
336  double prevB = 0;
337 
338  double r = 0;
339  double g = 0;
340  double b = 0;
341 
342  prevR = intermediate[x * 3];
343  prevG = intermediate[x * 3 + 1];
344  prevB = intermediate[x * 3 + 2];
345 
346  rAcc = prevR * (boxRadius + 1);
347  gAcc = prevG * (boxRadius + 1);
348  bAcc = prevB * (boxRadius + 1);
349 
350  int rY = 0;
351 
352  for (int y = 0; y < boxRadius; y++)
353  {
354  rY = Math.Min(y, height - 1);
355 
356  rAcc += intermediate[rY * stride + x * 3];
357  gAcc += intermediate[rY * stride + x * 3 + 1];
358  bAcc += intermediate[rY * stride + x * 3 + 2];
359  }
360 
361  rY = Math.Min(boxRadius, height - 1);
362  int lY = 0;
363 
364  for (int y = 0; y < height; y++)
365  {
366  rY = Math.Min(y + boxRadius, height - 1);
367  lY = Math.Max(y - boxRadius - 1, 0);
368 
369  r = intermediate[rY * stride + x * 3];
370  g = intermediate[rY * stride + x * 3 + 1];
371  b = intermediate[rY * stride + x * 3 + 2];
372 
373  prevR = intermediate[lY * stride + x * 3];
374  prevG = intermediate[lY * stride + x * 3 + 1];
375  prevB = intermediate[lY * stride + x * 3 + 2];
376 
377  rAcc += r - prevR;
378  gAcc += g - prevG;
379  bAcc += b - prevB;
380 
381  output[y * stride + x * 3] = (byte)Math.Min(255, Math.Max(0, rAcc * normFactor));
382  output[y * stride + x * 3 + 1] = (byte)Math.Min(255, Math.Max(0, gAcc * normFactor));
383  output[y * stride + x * 3 + 2] = (byte)Math.Min(255, Math.Max(0, bAcc * normFactor));
384  }
385  };
386  }
387 
388  if (threads == 1)
389  {
390  for (int y = 0; y < height; y++)
391  {
392  yLoop(y);
393  }
394 
395  for (int x = 0; x < width; x++)
396  {
397  xLoop(x);
398  }
399  }
400  else
401  {
402  ParallelOptions options = new ParallelOptions() { MaxDegreeOfParallelism = threads };
403 
404  Parallel.For(0, height, options, yLoop);
405  Parallel.For(0, width, options, xLoop);
406  }
407  }
408 
409  System.Runtime.InteropServices.Marshal.FreeHGlobal(intermediateData);
410  GC.RemoveMemoryPressure(image.Width * image.Height * (image.HasAlpha ? 4 : 3));
411 
412  DisposableIntPtr disp = new DisposableIntPtr(tbrData);
413 
414  return new RasterImage(ref disp, image.Width, image.Height, image.HasAlpha, image.Interpolate);
415  }
416  }
417 }
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.TopLeftMargin
Point TopLeftMargin
Definition: BoxBlurFilter.cs:34
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.RasterImage.Interpolate
bool Interpolate
Determines whether the image should be interpolated when it is resized.
Definition: RasterImage.cs:133
VectSharp.RasterImage.HasAlpha
bool HasAlpha
Determines whether the image has an alpha channel.
Definition: RasterImage.cs:118
VectSharp.Filters.BoxBlurFilter.BoxRadius
double BoxRadius
The radius of the box blur (the actual size of the box is 2 * BoxRadius + 1).
Definition: BoxBlurFilter.cs:31
VectSharp.RasterImage.Height
int Height
The height in pixels of the image.
Definition: RasterImage.cs:128
VectSharp.Filters.BoxBlurFilter.BottomRightMargin
Point BottomRightMargin
Definition: BoxBlurFilter.cs:36
VectSharp.Filters
Definition: BoxBlurFilter.cs:22
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.BoxBlurFilter.BoxBlurFilter
BoxBlurFilter(double boxRadius)
Creates a new BoxBlurFilter with the specified radius.
Definition: BoxBlurFilter.cs:42
VectSharp.Filters.BoxBlurFilter.Filter
RasterImage Filter(RasterImage image, double scale)
Applies the filter to a RasterImage.
Definition: BoxBlurFilter.cs:50