Home | Download | Documents | Tips & Tutorials | Code Library | Help / User Group | Order

Blur filters tutorial, Part III

 

In the recent Part Two of current tutorial we got almost all of the blur speed possible. It's hard to imagine we can get some more. But you should never say "never"...

Let's see what is happening when we just finished to calculate a sum of pixels in a row (or column) within kernel based upon current pixel and we are about to proceed to the next one. Calculating blur for the next pixel, we will be retrieving pixels of new kernel again but... evidently, most of them almost were retrieved and added together during calculating blur for a previous pixel! We just need to eliminate the leftmost (or topmost) pixel of an "old" kernel and add one new pixel at the right (or bottom) to get the "new", current kernel! That means we can use the sum calculated for previous pixel and retrieve only two image pixels (one to be eliminated, one to added) instead of whole string!

This is the huge improvement. Below you can see the code utilizing this idea. Pay attention, the code is slightly different in many details from both codes of previous blur tutorials. Filter code fragments are followed by explanations. The clear sourcecode is given.

%ffp

Category:"FM Tutorial"
Title: "Blur III"
Author: "Alex Hunter; Ilyich the Toad"

ctl(0):"V Blur (pixels)",val=10,range=(0,scaleFactor*Y/2)
ctl(1):"H Blur (pixels)",val=10,range=(0,scaleFactor*X/2)

ForEveryTile:{

int vBlurRadius = (2*ctl(0)+scaleFactor)/(2*scaleFactor);
int hBlurRadius = (2*ctl(1)+scaleFactor)/(2*scaleFactor);

 

This filter suggest user to input kernel radius instead of kernel size and allow the kernel radius control to be set to 0.


if (vBlurRadius == 0 && ctl(0) > 0) vBlurRadius = 1;
if (hBlurRadius == 0 && ctl(1) > 0) hBlurRadius = 1;

 

This part of the code was made to avoid 0/0 calculations, but using a bit different approach (conditional expression instead of max function).


{
  int vWeight = vBlurRadius*2 + 1;
  int hWeight = hBlurRadius*2 + 1;

 

Kernel sizes calculated based on kernel radii.


  int total = (x_end-x_start) + (y_end-y_start);
  int x, y, z;
  int i;

 

A slight difference in terms used here compared to previous codes: author used x_end instead of X and y_end instead of Y, also using x_start and y_start.

 

Below, the first filter pass is started:

  for (x=x_start; x < x_end; x++) {
  if (updateProgress(x-x_start, total)) break;
  for (z=0; z < Z; z++) {
  int sum = 0;

 

An important detail: here z-loop is placed above y-loop. The reason is simple: as we need to pass the sum from one pixel to another, we have to separate channels under calculation (that means, we are to take care so red channel sum won't pass to a green channel on next pixel and so on). If we use y-loop above z-loop, we gonna need the Z-component vector for storing sum for all Z channels at once. By proceeding picture channel-by-chanel prior row-by-row, we get to a simpler solution using the only integer variable for sum.


  for (i=-vBlurRadius; i <= vBlurRadius; i++) {
  sum += src(x, y_start-1+i, z);
  }

 

Starting calculating sum. For just the one first pixel, we have to play fair, retrieving all the pixels in a string. We cannot just subtract one pixel and add one pixel to an old sum since there is no "old" sum yet.


  for (y=y_start; y < y_end; y++) {
  sum += src(x, y+vBlurRadius, z) - src(x, y-vBlurRadius-1, z);

 

Once the sum value was calculated for the first pixel, we utilize the value for calculating the next one. We just subtract the topmost pixel of the"previous" kernel and add one new pixel at the bottom instead.


  tset(x,y,z,sum/vWeight);
  }
  }
  }

 

Calculating the "vertically blurred" pixel and storing it into the cell of "t" array

 

Below, the second pass (horizontal blur) is described. It is very similar to the first one:

  for (y=y_start; y < y_end; y++) {
  if (updateProgress(y-y_start + (x_end-x_start), total)) break;
  for (z=0; z < Z; z++) {
  int sum = 0;
  for (i=-hBlurRadius; i <= hBlurRadius; i++) {
  sum += tget(x_start-1+i, y, z);
  }
  for (x=x_start; x < x_end; x++) {
  sum += tget(x+hBlurRadius, y, z) - tget(x-hBlurRadius-1, y, z);
  pset(x,y,z,sum/hWeight);
  }
  }
  }
}

 

Finishing the second pass, applying resul to the image.


updateProgress(0,0);
true; //Done!
}

That's all folks. Now we get a blur filter those speed depends on 2nd power of source image size. We can hardly get any better...

Of course the linear "average" blur described above is not the only one. There are some other blurs like Gaussian, and there are some other convolutions as well. But that's all a next story...

 

Home | Download | Documents | Tips & Tutorials | Code Library | Help / User Group | Order