Vuo  2.3.2
VuoImageBlur.c
Go to the documentation of this file.
1 
10 #include "node.h"
11 #include "VuoImageRenderer.h"
12 #include "VuoImageBlur.h"
13 #include <OpenGL/CGLMacro.h>
14 
16 #ifdef VUO_COMPILER
18  "title" : "VuoImageBlur",
19  "dependencies" : [
20  "VuoImageRenderer",
21  "VuoInteger",
22  "VuoReal",
23  ]
24  });
25 #endif
27 
31 typedef struct
32 {
33  VuoShader shader;
34  VuoShader discShader;
36 
40 void VuoImageBlur_free(void *blur)
41 {
43  VuoRelease(bi->shader);
44  VuoRelease(bi->discShader);
45 }
46 
54 static double VuoImageBlur_gauss(double x, double width)
55 {
56  return (erf((x+.5)*width) - erf((x-.5)*width))/2;
57 }
58 
69 {
70  int pixelsWide;
71  float *pixelFloats;
72  if (shape == VuoBlurShape_Gaussian)
73  {
74  // Scale to the same apparent width as box.
75  radius *= .6;
76 
77  const double width = 1. / (radius * sqrt(2));
78  const double threshold = .1 / 255.;
79 
81  VuoLocal(l);
82  pixelsWide = 0;
83  double g = 1;
84  while (g > threshold)
85  {
86  g = VuoImageBlur_gauss(pixelsWide, width);
87  ++pixelsWide;
89  }
90 
91  // Don't try to create textures larger than OpenGL supports.
92  pixelsWide = VuoInteger_clamp(pixelsWide, 0, 16384);
93 
94  VuoReal *pixels = VuoListGetData_VuoReal(l);
95  pixelFloats = (float *)malloc(sizeof(float) * pixelsWide);
96  for (int i = 0; i < pixelsWide; ++i)
97  pixelFloats[i] = pixels[i];
98  }
99  else if (shape == VuoBlurShape_Linear)
100  {
101  // Scale to the same apparent width as box.
102  radius *= 1.4;
103 
104  // When radius is 0, the half-triangle filter should be a single full-intensity pixel (identity).
105  // When radius is 1, the half-triangle filter should be 2 pixels wide, the second pixel half the intensity of the first.
106 
107  pixelsWide = ceil(radius + 1);
108 
109  // Don't try to create textures larger than OpenGL supports.
110  pixelsWide = VuoInteger_clamp(pixelsWide, 0, 16384);
111 
112  pixelFloats = (float *)malloc(sizeof(float) * pixelsWide);
113  for (int i = 0; i < pixelsWide; ++i)
114  pixelFloats[i] = 1 - (float)i/(radius + 1);
115  }
116  else // Box
117  {
118  // When radius is 0, the half-box filter should be a single full-intensity pixel (identity).
119  // When radius is 0.5, the half-box filter should be 2 pixels wide, the second pixel half the intensity of the first.
120  // When radius is 1, the half-box filter should be 2 pixels wide, same intensity for both pixels.
121 
122  pixelsWide = ceil(radius + 1);
123 
124  // Don't try to create textures larger than OpenGL supports.
125  pixelsWide = VuoInteger_clamp(pixelsWide, 0, 16384);
126 
127  pixelFloats = (float *)malloc(sizeof(float) * pixelsWide);
128  for (int i = 0; i < pixelsWide - 1; ++i)
129  pixelFloats[i] = 1.;
130 
131  float lastPixel = radius - floor(radius);
132  if (lastPixel == 0) lastPixel = 1;
133  pixelFloats[pixelsWide - 1] = lastPixel;
134  }
135 
136  return VuoImage_makeFromBuffer(pixelFloats, GL_LUMINANCE, pixelsWide, 1, VuoImageColorDepth_32, ^(void *buffer){ free(pixelFloats); });
137 }
138 
143 {
146 
147  static const char *fragmentShader = VUOSHADER_GLSL_SOURCE(120,
148  \n#include "VuoGlslAlpha.glsl"
149  \n#include "VuoGlslRandom.glsl"
150  \n#include "VuoGlslHsl.glsl"
151 
152  varying vec2 fragmentTextureCoordinate;
153 
154  uniform sampler2D texture;
155  uniform sampler2D gaussianWeights;
156  uniform float aspectRatio;
157 
158  uniform sampler2D mask;
159  uniform bool hasMask;
160 
161  uniform float width; // render target width
162  uniform float height; // render target height
163  uniform float inset;
164  uniform int gaussianWeightCount;
165  uniform float quality;
166  uniform bool radial;
167  uniform int symmetry;
168  uniform bool zoom;
169  uniform vec2 center;
170  uniform vec2 direction;
171 
172  float gaussianWeight(int x)
173  {
174  return texture2D(gaussianWeights, vec2((x + .5)/float(gaussianWeightCount), .5)).r;
175  }
176 
177  vec4 sampleInset(vec2 uv)
178  {
179  if (inset > 0.)
180  // Always return transparent black outside the texture (regardless of the texture's wrap mode).
181  if (uv.x < 0. || uv.y < 0. || uv.x > 1. || uv.y > 1.)
182  return vec4(0.);
183 
184  return VuoGlsl_sample(texture, uv);
185  }
186 
187  void main(void)
188  {
189  vec2 uv = fragmentTextureCoordinate;
190 
191  // Inset the source image relative to the output image to account for the optional bleed.
192  uv.x = (uv.x - inset/width) * width/(width -inset*2.);
193  uv.y = (uv.y - inset/height) * height/(height-inset*2.);
194 
195  vec2 dir = direction;
196  if (zoom)
197  dir *= uv - center;
198 
199  float scale = 1.;
200  if (hasMask)
201  {
202  vec4 maskColor = VuoGlsl_sample(mask, uv);
203 
204  // VuoGlsl_sample() returns premultiplied colors,
205  // so we can take into account both the mask's luminance and alpha
206  // by just looking at its (premultiplied) luminance.
207  scale = VuoGlsl_rgbToHsl(maskColor.rgb).z;
208  dir *= scale;
209  }
210 
211  int iterations = int(ceil(float(gaussianWeightCount) * quality));
212 
213  vec2 fuzz = vec2(0.);
214  if (quality < .75)
215  {
216  fuzz = VuoGlsl_random2D2D(uv);
217  if (symmetry == 0) // VuoCurveEasing_In
218  fuzz *= .5;
219  if (symmetry == 1) // VuoCurveEasing_Out
220  fuzz *= -.5;
221  else if (symmetry == 2) // VuoCurveEasing_InOut
222  fuzz -= vec2(.5, .5);
223 
224  fuzz *= dir * float(gaussianWeightCount) * 2. / float(1 + iterations);
225  }
226 
227  float mixSum = gaussianWeight(0);
228  vec4 colorSum = sampleInset(uv + fuzz) * mixSum;
229  if (radial)
230  {
231  vec2 n = uv - center;
232  n.y /= aspectRatio;
233  float dist = distance(uv, center) * direction.x * scale;
234  for (int i = 1; i < iterations; i++)
235  {
236  int is = int(float(i) / quality);
237  float gw = gaussianWeight(is);
238  if (symmetry == 0 // VuoCurveEasing_In
239  || symmetry == 2) // VuoCurveEasing_InOut
240  {
241  float c = cos(float(is)*dist);
242  float s = sin(float(is)*dist);
243  vec2 v = vec2(n.x*c - n.y*s,
244  n.x*s + n.y*c);
245  v.y *= aspectRatio;
246  colorSum += sampleInset(v + center + fuzz) * gw;
247  mixSum += gw;
248  }
249  if (symmetry == 1 // VuoCurveEasing_Out
250  || symmetry == 2) // VuoCurveEasing_InOut
251  {
252  float c = cos(-float(is)*dist);
253  float s = sin(-float(is)*dist);
254  vec2 v = vec2(n.x*c - n.y*s,
255  n.x*s + n.y*c);
256  v.y *= aspectRatio;
257  colorSum += sampleInset(v + center + fuzz) * gw;
258  mixSum += gw;
259  }
260  }
261  }
262  else // linear
263  {
264  for (int i = 1; i < iterations; i++)
265  {
266  int is = int(float(i) / quality);
267  float gw = gaussianWeight(is);
268  if (symmetry == 0 // VuoCurveEasing_In
269  || symmetry == 2) // VuoCurveEasing_InOut
270  {
271  colorSum += sampleInset(uv + dir * float(is) + fuzz) * gw;
272  mixSum += gw;
273  }
274  if (symmetry == 1 // VuoCurveEasing_Out
275  || symmetry == 2) // VuoCurveEasing_InOut
276  {
277  colorSum += sampleInset(uv - dir * float(is) + fuzz) * gw;
278  mixSum += gw;
279  }
280  }
281  }
282 
283  gl_FragColor = colorSum / mixSum;
284  }
285  );
286 
287  bi->shader = VuoShader_make("Blur Shader");
288  VuoShader_addSource(bi->shader, VuoMesh_IndividualTriangles, NULL, NULL, fragmentShader);
289  VuoRetain(bi->shader);
290 
291  static const char *discFragmentShader = VUOSHADER_GLSL_SOURCE(120,
292  \n#include "VuoGlslAlpha.glsl"
293  \n#include "VuoGlslRandom.glsl"
294  \n#include "VuoGlslHsl.glsl"
295 
296  varying vec2 fragmentTextureCoordinate;
297 
298  uniform sampler2D texture;
299  uniform sampler2D gaussianWeights;
300  uniform float aspectRatio;
301 
302  uniform sampler2D mask;
303  uniform bool hasMask;
304 
305  uniform float width;
306  uniform float height;
307  uniform float inset;
308  uniform float radius;
309  uniform float quality;
310 
311  vec4 sampleInset(vec2 uv)
312  {
313  if (inset > 0.)
314  // Always return transparent black outside the texture (regardless of the texture's wrap mode).
315  if (uv.x < 0. || uv.y < 0. || uv.x > 1. || uv.y > 1.)
316  return vec4(0.);
317 
318  return VuoGlsl_sample(texture, uv);
319  }
320 
321  void main(void)
322  {
323  vec2 uv = fragmentTextureCoordinate;
324  float delta = fwidth(uv.x) * width/2.;
325 
326  // Inset the source image relative to the output image to account for the optional bleed.
327  uv.x = (uv.x - inset/width) * width/(width -inset*2.);
328  uv.y = (uv.y - inset/height) * height/(height-inset*2.);
329 
330  float maskedRadius = radius;
331  if (hasMask)
332  {
333  vec4 maskColor = VuoGlsl_sample(mask, uv);
334 
335  // VuoGlsl_sample() returns premultiplied colors,
336  // so we can take into account both the mask's luminance and alpha
337  // by just looking at its (premultiplied) luminance.
338  float maskAmount = VuoGlsl_rgbToHsl(maskColor.rgb).z;
339 
340  maskedRadius *= maskAmount;
341  }
342 
343  vec2 fuzz = vec2(0.);
344  if (quality < .75)
345  fuzz = (VuoGlsl_random2D2D(uv) - vec2(.5,.5)) * (.75 - quality) * maskedRadius / vec2(width, height) / .75;
346 
347  int pixelRadius = int(ceil(maskedRadius*quality));
348  vec4 colorSum = vec4(0.);
349  float mixSum = 0.;
350  for (int x = 0; x <= pixelRadius; ++x)
351  for (int y = 0; y <= pixelRadius; ++y)
352  {
353  float dist = length(vec2(x,y) / quality);
354  float f = smoothstep(maskedRadius + delta, maskedRadius - delta, dist);
355 
356  float sx = (float(x) / quality)/width;
357  float sy = (float(y) / quality)/height;
358 
359  // Fourfold symmetry
360  vec4 s = vec4(0.);
361  s += sampleInset(uv + vec2( sx, sy) + fuzz); mixSum += f;
362  if (x > 0) { s += sampleInset(uv + vec2(-sx, sy) + fuzz); mixSum += f; }
363  if (y > 0) { s += sampleInset(uv + vec2( sx,-sy) + fuzz); mixSum += f; }
364  if (x > 0 && y > 0) { s += sampleInset(uv + vec2(-sx,-sy) + fuzz); mixSum += f; }
365  colorSum += s * f;
366  }
367 
368  gl_FragColor = colorSum / mixSum;
369  }
370  );
371 
372  bi->discShader = VuoShader_make("Disc Blur Shader");
373  VuoShader_addSource(bi->discShader, VuoMesh_IndividualTriangles, NULL, NULL, discFragmentShader);
374  VuoRetain(bi->discShader);
375 
376  return (VuoImageBlur)bi;
377 }
378 
388 VuoImage VuoImageBlur_blur(VuoImageBlur blur, VuoImage image, VuoImage mask, VuoBlurShape shape, VuoReal radius, VuoReal quality, VuoBoolean expandBounds)
389 {
390  if (!image)
391  return NULL;
392 
393  if (radius < 0.0001)
394  return image;
395 
396  radius *= image->scaleFactor;
397 
399 
400  VuoImage gaussianWeights = VuoImageBlur_calculateWeights(shape, radius);
401  VuoLocal(gaussianWeights);
402 
403  int inset = expandBounds ? gaussianWeights->pixelsWide-1 : 0;
404 
405  int w = image->pixelsWide + inset*2;
406  int h = image->pixelsHigh + inset*2;
407 
408  if (shape == VuoBlurShape_Disc)
409  {
410  VuoShader_setUniform_VuoReal (bi->discShader, "inset", inset);
411  VuoShader_setUniform_VuoReal (bi->discShader, "width", w);
412  VuoShader_setUniform_VuoReal (bi->discShader, "height", h);
413  VuoShader_setUniform_VuoImage (bi->discShader, "texture", image);
414  VuoShader_setUniform_VuoImage (bi->discShader, "mask", mask);
415  VuoShader_setUniform_VuoBoolean(bi->discShader, "hasMask", mask ? true : false);
416  VuoShader_setUniform_VuoReal (bi->discShader, "radius", radius);
417  VuoShader_setUniform_VuoReal (bi->discShader, "quality", VuoReal_clamp(quality, 0.01, 1));
418 
419  VuoShader_setTransparent(bi->discShader, expandBounds);
420 
421  VuoImage blurredImage = VuoImageRenderer_render(bi->discShader, w, h, VuoImage_getColorDepth(image));
422  VuoImage_setWrapMode(blurredImage, VuoImage_getWrapMode(image));
423  return blurredImage;
424  }
425  else // Gaussian, Triangle, Box
426  {
427  VuoShader_setUniform_VuoReal (bi->shader, "inset", inset);
428  VuoShader_setUniform_VuoReal (bi->shader, "width", w);
429  VuoShader_setUniform_VuoReal (bi->shader, "height", h);
430  VuoShader_setUniform_VuoImage (bi->shader, "texture", image);
431  VuoShader_setUniform_VuoImage (bi->shader, "mask", mask);
432  VuoShader_setUniform_VuoBoolean(bi->shader, "hasMask", mask ? true : false);
433  VuoShader_setUniform_VuoImage (bi->shader, "gaussianWeights", gaussianWeights);
434  VuoShader_setUniform_VuoInteger(bi->shader, "gaussianWeightCount", gaussianWeights->pixelsWide);
435  VuoShader_setUniform_VuoReal (bi->shader, "quality", VuoReal_clamp(quality, 0, 1));
436  VuoShader_setUniform_VuoBoolean(bi->shader, "radial", false);
437  VuoShader_setUniform_VuoInteger(bi->shader, "symmetry", VuoCurveEasing_InOut);
438  VuoShader_setUniform_VuoBoolean(bi->shader, "zoom", false);
439 
440  VuoShader_setTransparent(bi->shader, expandBounds);
441 
442  VuoShader_setUniform_VuoPoint2d(bi->shader, "direction", (VuoPoint2d){1./image->pixelsWide, 0});
443  VuoImage horizontalPassImage = VuoImageRenderer_render(bi->shader, w, h, VuoImage_getColorDepth(image));
444  VuoLocal(horizontalPassImage);
445 
446  VuoImageWrapMode wrapMode = VuoImage_getWrapMode(image);
447  VuoImage_setWrapMode(horizontalPassImage, wrapMode);
448 
449  // Only inset the first pass.
450  VuoShader_setUniform_VuoReal (bi->shader, "inset", 0);
451 
452  VuoShader_setUniform_VuoImage (bi->shader, "texture", horizontalPassImage);
453  VuoShader_setUniform_VuoPoint2d(bi->shader, "direction", (VuoPoint2d){0, 1.f/h});
454  VuoImage bothPassesImage = VuoImageRenderer_render(bi->shader, w, h, VuoImage_getColorDepth(image));
455 
456  VuoImage_setWrapMode(bothPassesImage, wrapMode);
457 
458  return bothPassesImage;
459  }
460 }
461 
462 #ifndef DOXYGEN
466 VuoImage VuoImage_blur(VuoImage image, VuoReal radius) __attribute__ ((deprecated("VuoImage_blur is deprecated, please use VuoImageBlur_* instead.")));
467 VuoImage VuoImage_blur(VuoImage image, VuoReal radius)
468 {
470  VuoLocal(ib);
471  return VuoImageBlur_blur(ib, image, NULL, VuoBlurShape_Gaussian, radius, 1, false);
472 }
473 #endif
474 
481 VuoImage VuoImageBlur_blurDirectionally(VuoImageBlur blur, VuoImage image, VuoImage mask, VuoBlurShape shape, VuoReal radius, VuoReal quality, VuoReal angle, VuoBoolean symmetric, VuoBoolean expandBounds)
482 {
483  if (!image)
484  return NULL;
485 
486  if (radius < 0.0001)
487  return image;
488 
489  radius *= image->scaleFactor;
490 
492 
493  VuoImage gaussianWeights = VuoImageBlur_calculateWeights(shape, radius);
494  VuoLocal(gaussianWeights);
495 
496  int inset = expandBounds ? gaussianWeights->pixelsWide-1 : 0;
497 
498  int w = image->pixelsWide + inset*2;
499  int h = image->pixelsHigh + inset*2;
500 
501  VuoShader_setUniform_VuoReal (bi->shader, "inset", inset);
502  VuoShader_setUniform_VuoReal (bi->shader, "width", w);
503  VuoShader_setUniform_VuoReal (bi->shader, "height", h);
504  VuoShader_setUniform_VuoImage (bi->shader, "texture", image);
505  VuoShader_setUniform_VuoImage (bi->shader, "mask", mask);
506  VuoShader_setUniform_VuoBoolean(bi->shader, "hasMask", mask ? true : false);
507  VuoShader_setUniform_VuoImage (bi->shader, "gaussianWeights", gaussianWeights);
508  VuoShader_setUniform_VuoInteger(bi->shader, "gaussianWeightCount", gaussianWeights->pixelsWide);
509  VuoShader_setUniform_VuoReal (bi->shader, "quality", VuoReal_clamp(quality, 0, 1));
510  VuoShader_setUniform_VuoBoolean(bi->shader, "radial", false);
511  VuoShader_setUniform_VuoInteger(bi->shader, "symmetry", symmetric ? VuoCurveEasing_InOut : VuoCurveEasing_Out);
512  VuoShader_setUniform_VuoBoolean(bi->shader, "zoom", false);
513  VuoShader_setUniform_VuoPoint2d(bi->shader, "direction",
514  (VuoPoint2d){
515  cos(angle*M_PI/180) / image->pixelsWide,
516  sin(angle*M_PI/180) / image->pixelsHigh,
517  });
518 
519  VuoShader_setTransparent(bi->shader, expandBounds);
520 
521  VuoImage blurredImage = VuoImageRenderer_render(bi->shader, w, h, VuoImage_getColorDepth(image));
522 
523  VuoImage_setWrapMode(blurredImage, VuoImage_getWrapMode(image));
524 
525  return blurredImage;
526 }
527 
534 VuoImage VuoImageBlur_blurRadially(VuoImageBlur blur, VuoImage image, VuoImage mask, VuoBlurShape shape, VuoPoint2d center, VuoReal radius, VuoReal quality, VuoDispersion dispersion, VuoCurveEasing symmetry, VuoBoolean expandBounds)
535 {
536  if (!image)
537  return NULL;
538 
539  if (radius < 0.0001)
540  return image;
541 
542  radius *= image->scaleFactor;
543 
545 
546  // Make the apparent size of the radial blur roughly match that of the linear blur.
547  if (dispersion == VuoDispersion_Radial)
548  radius *= 2;
549 
550  VuoImage gaussianWeights = VuoImageBlur_calculateWeights(shape, radius);
551  VuoLocal(gaussianWeights);
552 
553  int inset = expandBounds ? gaussianWeights->pixelsWide-1 : 0;
554 
555  int w = image->pixelsWide + inset*2;
556  int h = image->pixelsHigh + inset*2;
557 
558  VuoShader_setUniform_VuoReal (bi->shader, "inset", inset);
559  VuoShader_setUniform_VuoReal (bi->shader, "width", w);
560  VuoShader_setUniform_VuoReal (bi->shader, "height", h);
561  VuoShader_setUniform_VuoImage (bi->shader, "texture", image);
562  VuoShader_setUniform_VuoImage (bi->shader, "mask", mask);
563  VuoShader_setUniform_VuoBoolean(bi->shader, "hasMask", mask ? true : false);
564  VuoShader_setUniform_VuoImage (bi->shader, "gaussianWeights", gaussianWeights);
565  VuoShader_setUniform_VuoInteger(bi->shader, "gaussianWeightCount", gaussianWeights->pixelsWide);
566  VuoShader_setUniform_VuoReal (bi->shader, "quality", VuoReal_clamp(quality, 0, 1));
567  VuoShader_setUniform_VuoBoolean(bi->shader, "radial", dispersion == VuoDispersion_Radial ? true : false);
568  VuoShader_setUniform_VuoInteger(bi->shader, "symmetry", symmetry);
569  VuoShader_setUniform_VuoBoolean(bi->shader, "zoom", true);
571  VuoShader_setUniform_VuoPoint2d(bi->shader, "direction", (VuoPoint2d){1./image->pixelsWide, 1.f/image->pixelsWide});
572 
573  VuoShader_setTransparent(bi->shader, expandBounds);
574 
575  VuoImage blurredImage = VuoImageRenderer_render(bi->shader, w, h, VuoImage_getColorDepth(image));
576 
577  VuoImage_setWrapMode(blurredImage, VuoImage_getWrapMode(image));
578 
579  return blurredImage;
580 }