Skip to content

Commit 51bfd07

Browse files
Sven NeumannSven Neumann
Sven Neumann
authored and
Sven Neumann
committed
Bug 568095 – Patch to improve unsharp mask performance
2009-01-17 Sven Neumann <[email protected]> Bug 568095 – Patch to improve unsharp mask performance * plug-ins/common/unsharp-mask.c (unsharp_region): applied patch from Winston Chang that improves performance for larger radii by approximating the gaussian blur with a three-pass box blur. svn path=/trunk/; revision=27912
1 parent 7905a7c commit 51bfd07

File tree

2 files changed

+224
-40
lines changed

2 files changed

+224
-40
lines changed

ChangeLog

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
2009-01-17 Sven Neumann <[email protected]>
2+
3+
Bug 568095 – Patch to improve unsharp mask performance
4+
5+
* plug-ins/common/unsharp-mask.c (unsharp_region): applied patch
6+
from Winston Chang that improves performance for larger radii by
7+
approximating the gaussian blur with a three-pass box blur.
8+
19
2009-01-17 Martin Nordholts <[email protected]>
210

311
Adapt to new babl API, s/babl_format/babl_format_from_name/

plug-ins/common/unsharp-mask.c

+216-40
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,23 @@ static void run (const gchar *name,
6161
gint *nreturn_vals,
6262
GimpParam **return_vals);
6363

64-
static void blur_line (const gdouble *cmatrix,
64+
static void gaussian_blur_line (const gdouble *cmatrix,
6565
const gint cmatrix_length,
6666
const guchar *src,
6767
guchar *dest,
6868
const gint len,
69-
const gint bytes);
69+
const gint bpp);
70+
static void box_blur_line (const gint box_width,
71+
const gint even_offset,
72+
const guchar *src,
73+
guchar *dest,
74+
const gint len,
75+
const gint bpp);
7076
static gint gen_convolve_matrix (gdouble std_dev,
7177
gdouble **cmatrix);
7278
static void unsharp_region (GimpPixelRgn *srcPTR,
7379
GimpPixelRgn *dstPTR,
74-
gint bytes,
80+
gint bpp,
7581
gdouble radius,
7682
gdouble amount,
7783
gint x1,
@@ -235,17 +241,114 @@ run (const gchar *name,
235241
#endif
236242
}
237243

244+
/* This function is written as if it is blurring a row of pixels,
245+
* even though it can operate on colums, too. There is no difference
246+
* in the processing of the lines, at least to the blur_line function.
247+
*/
248+
static void
249+
box_blur_line (const gint box_width, /* Width of the kernel */
250+
const gint even_offset,/* If even width,
251+
offset to left or right */
252+
const guchar *src, /* Pointer to source buffer */
253+
guchar *dest, /* Pointer to destination buffer */
254+
const gint len, /* Length of buffer, in pixels */
255+
const gint bpp) /* Bytes per pixel */
256+
{
257+
gint i;
258+
gint lead; /* This marks the leading edge of the kernel */
259+
gint output; /* This marks the center of ther kernel */
260+
gint trail; /* This marks the pixel BEHIND the last 1 in the
261+
kernel; it's the pixel to remove from the accumulator. */
262+
gint ac[bpp]; /* Accumulator for each channel */
263+
264+
265+
/* The algorithm differs for even and odd-sized kernels.
266+
* With the output at the center,
267+
* If odd, the kernel might look like this: 0011100
268+
* If even, the kernel will either be centered on the boundary between
269+
* the output and its left neighbor, or on the boundary between the
270+
* output and its right neighbor, depending on even_lr.
271+
* So it might be 0111100 or 0011110, where output is on the center
272+
* of these arrays.
273+
*/
274+
lead = 0;
275+
276+
if (box_width % 2)
277+
/* Odd-width kernel */
278+
{
279+
output = lead - (box_width - 1) / 2;
280+
trail = lead - box_width;
281+
}
282+
else
283+
/* Even-width kernel. */
284+
{
285+
/* Right offset */
286+
if (even_offset == 1)
287+
{
288+
output = lead + 1 - box_width / 2;
289+
trail = lead - box_width;
290+
}
291+
/* Left offset */
292+
else if (even_offset == -1)
293+
{
294+
output = lead - box_width / 2;
295+
trail = lead - box_width;
296+
}
297+
/* If even_offset isn't 1 or -1, there's some error. */
298+
else
299+
g_assert_not_reached ();
300+
}
301+
302+
/* Initialize accumulator */
303+
for (i = 0; i < bpp; i++)
304+
ac[i] = 0;
305+
306+
while (output < len)
307+
{
308+
/* The number of pixels that are both in the image and
309+
* currently covered by the kernel. This is necessary to
310+
* handle edge cases. */
311+
guint coverage = (lead < len ? lead : len-1) - (trail >=0 ? trail : -1);
312+
313+
for (i = 0; i < bpp; i++)
314+
{
315+
/* If the leading edge of the kernel is still on the image,
316+
* add the value there to the accumulator. */
317+
if (lead < len)
318+
ac[i] += src[bpp * lead + i];
319+
320+
/* If the trailing edge of the kernel is on the image,
321+
* subtract the value there from the accumulator. */
322+
if (trail >= 0)
323+
ac[i] -= src[bpp * trail + i];
324+
325+
/* Take the averaged value in the accumulator and store
326+
* that value in the output. The number of pixels currently
327+
* stored in the accumulator can be less than the nominal
328+
* width of the kernel because the kernel can go "over the edge"
329+
* of the image. */
330+
if (output >= 0)
331+
dest[bpp * output + i] = (ac[i] + (coverage >> 1)) / coverage;
332+
}
333+
334+
lead++;
335+
output++;
336+
trail++;
337+
}
338+
}
339+
340+
238341
/* This function is written as if it is blurring a column at a time,
239342
* even though it can operate on rows, too. There is no difference
240343
* in the processing of the lines, at least to the blur_line function.
241344
*/
242345
static void
243-
blur_line (const gdouble *cmatrix,
244-
const gint cmatrix_length,
245-
const guchar *src,
246-
guchar *dest,
247-
const gint len,
248-
const gint bytes)
346+
gaussian_blur_line (const gdouble *cmatrix,
347+
const gint cmatrix_length,
348+
const guchar *src,
349+
guchar *dest,
350+
const gint len,
351+
const gint bpp)
249352
{
250353
const gdouble *cmatrix_p;
251354
const guchar *src_p;
@@ -275,7 +378,7 @@ blur_line (const gdouble *cmatrix,
275378

276379
src_p = src;
277380

278-
for (i = 0; i < bytes; i++)
381+
for (i = 0; i < bpp; i++)
279382
{
280383
gdouble sum = 0;
281384

@@ -287,7 +390,7 @@ blur_line (const gdouble *cmatrix,
287390
j + cmatrix_middle - row < cmatrix_length)
288391
sum += *src_p1 * cmatrix[j];
289392

290-
src_p1 += bytes;
393+
src_p1 += bpp;
291394
}
292395

293396
*dest++ = (guchar) ROUND (sum / scale);
@@ -307,7 +410,7 @@ blur_line (const gdouble *cmatrix,
307410

308411
src_p = src;
309412

310-
for (i = 0; i < bytes; i++)
413+
for (i = 0; i < bpp; i++)
311414
{
312415
gdouble sum = 0;
313416

@@ -316,7 +419,7 @@ blur_line (const gdouble *cmatrix,
316419
for (j = cmatrix_middle - row; j < cmatrix_length; j++)
317420
{
318421
sum += *src_p1 * cmatrix[j];
319-
src_p1 += bytes;
422+
src_p1 += bpp;
320423
}
321424

322425
*dest++ = (guchar) ROUND (sum / scale);
@@ -326,9 +429,9 @@ blur_line (const gdouble *cmatrix,
326429
/* go through each pixel in each col */
327430
for (; row < len - cmatrix_middle; row++)
328431
{
329-
src_p = src + (row - cmatrix_middle) * bytes;
432+
src_p = src + (row - cmatrix_middle) * bpp;
330433

331-
for (i = 0; i < bytes; i++)
434+
for (i = 0; i < bpp; i++)
332435
{
333436
gdouble sum = 0;
334437

@@ -338,7 +441,7 @@ blur_line (const gdouble *cmatrix,
338441
for (j = 0; j < cmatrix_length; j++)
339442
{
340443
sum += cmatrix[j] * *src_p1;
341-
src_p1 += bytes;
444+
src_p1 += bpp;
342445
}
343446

344447
src_p++;
@@ -355,9 +458,9 @@ blur_line (const gdouble *cmatrix,
355458
for (j = 0; j < len - row + cmatrix_middle; j++)
356459
scale += cmatrix[j];
357460

358-
src_p = src + (row - cmatrix_middle) * bytes;
461+
src_p = src + (row - cmatrix_middle) * bpp;
359462

360-
for (i = 0; i < bytes; i++)
463+
for (i = 0; i < bpp; i++)
361464
{
362465
gdouble sum = 0;
363466

@@ -366,7 +469,7 @@ blur_line (const gdouble *cmatrix,
366469
for (j = 0; j < len - row + cmatrix_middle; j++)
367470
{
368471
sum += *src_p1 * cmatrix[j];
369-
src_p1 += bytes;
472+
src_p1 += bpp;
370473
}
371474

372475
*dest++ = (guchar) ROUND (sum / scale);
@@ -409,51 +512,124 @@ unsharp_mask (GimpDrawable *drawable,
409512
static void
410513
unsharp_region (GimpPixelRgn *srcPR,
411514
GimpPixelRgn *destPR,
412-
gint bytes,
413-
gdouble radius,
515+
gint bpp,
516+
gdouble radius, /* Radius, AKA standard deviation */
414517
gdouble amount,
415518
gint x1,
416519
gint x2,
417520
gint y1,
418521
gint y2,
419522
gboolean show_progress)
420523
{
421-
guchar *src;
422-
guchar *dest;
423-
gint width = x2 - x1;
424-
gint height = y2 - y1;
425-
gdouble *cmatrix = NULL;
426-
gint cmatrix_length;
427-
gint row, col;
428-
gint threshold = unsharp_params.threshold;
524+
guchar *src; /* Temporary copy of source row/col */
525+
guchar *dest; /* Temporary copy of destination row/col */
526+
const gint width = x2 - x1;
527+
const gint height = y2 - y1;
528+
gdouble *cmatrix = NULL; /* Convolution matrix (for gaussian) */
529+
gint cmatrix_length = 0;
530+
gint row, col; /* Row,column counter */
531+
const gint threshold = unsharp_params.threshold;
532+
gboolean box_blur; /* If we want to use a three pass box blur
533+
* instead of a gaussian blur
534+
*/
535+
gint box_width = 0;
429536

430537
if (show_progress)
431538
gimp_progress_init (_("Blurring"));
432539

433-
/* generate convolution matrix
434-
and make sure it's smaller than each dimension */
435-
cmatrix_length = gen_convolve_matrix (radius, &cmatrix);
540+
/* If the radius is less than 10, use a true gaussian kernel. This
541+
* is slower, but more accurate and allows for finer adjustments.
542+
* Otherwise use a three-pass box blur; this is much faster but it
543+
* isn't a perfect approximation, and it only allows radius
544+
* increments of about 0.42.
545+
*/
546+
if (radius < 10)
547+
{
548+
box_blur = FALSE;
549+
/* If true gaussian, generate convolution matrix
550+
and make sure it's smaller than each dimension */
551+
cmatrix_length = gen_convolve_matrix (radius, &cmatrix);
552+
}
553+
else
554+
{
555+
box_blur = TRUE;
556+
/* Three box blurs of this width approximate a gaussian */
557+
box_width = ROUND (radius * 3 * sqrt (2 * G_PI) / 4);
558+
}
436559

437-
/* allocate buffers */
438-
src = g_new (guchar, MAX (width, height) * bytes);
439-
dest = g_new (guchar, MAX (width, height) * bytes);
560+
/* Allocate buffers temporary copies of a row/column */
561+
src = g_new (guchar, MAX (width, height) * bpp);
562+
dest = g_new (guchar, MAX (width, height) * bpp);
440563

441-
/* blur the rows */
564+
/* Blur the rows */
442565
for (row = 0; row < height; row++)
443566
{
444567
gimp_pixel_rgn_get_row (srcPR, src, x1, y1 + row, width);
445-
blur_line (cmatrix, cmatrix_length, src, dest, width, bytes);
568+
569+
if (box_blur)
570+
{
571+
/* Odd-width box blur: repeat 3 times, centered on output pixel.
572+
* Swap back and forth between the buffers. */
573+
if (box_width % 2)
574+
{
575+
box_blur_line (box_width, 0, src, dest, width, bpp);
576+
box_blur_line (box_width, 0, dest, src, width, bpp);
577+
box_blur_line (box_width, 0, src, dest, width, bpp);
578+
}
579+
/* Even-width box blur:
580+
* This method is suggested by the specification for SVG.
581+
* One pass with width n, centered between output and right pixel
582+
* One pass with width n, centered between output and left pixel
583+
* One pass with width n+1, centered on output pixel
584+
* Swap back and forth between buffers.
585+
*/
586+
else
587+
{
588+
box_blur_line (box_width, -1, src, dest, width, bpp);
589+
box_blur_line (box_width, 1, dest, src, width, bpp);
590+
box_blur_line (box_width+1, 0, src, dest, width, bpp);
591+
}
592+
}
593+
else
594+
{
595+
/* Gaussian blur */
596+
gaussian_blur_line (cmatrix, cmatrix_length, src, dest, width, bpp);
597+
}
598+
446599
gimp_pixel_rgn_set_row (destPR, dest, x1, y1 + row, width);
447600

448601
if (show_progress && row % 16 == 0)
449602
gimp_progress_update ((gdouble) row / (3 * height));
450603
}
451604

452-
/* blur the cols */
605+
/* Blur the cols. Essentially same as above. */
453606
for (col = 0; col < width; col++)
454607
{
455608
gimp_pixel_rgn_get_col (destPR, src, x1 + col, y1, height);
456-
blur_line (cmatrix, cmatrix_length, src, dest, height, bytes);
609+
610+
if (box_blur)
611+
{
612+
/* Odd-width box blur */
613+
if (box_width % 2)
614+
{
615+
box_blur_line (box_width, 0, src, dest, height, bpp);
616+
box_blur_line (box_width, 0, dest, src, height, bpp);
617+
box_blur_line (box_width, 0, src, dest, height, bpp);
618+
}
619+
/* Even-width box blur */
620+
else
621+
{
622+
box_blur_line (box_width, -1, src, dest, height, bpp);
623+
box_blur_line (box_width, 1, dest, src, height, bpp);
624+
box_blur_line (box_width+1, 0, src, dest, height, bpp);
625+
}
626+
}
627+
else
628+
{
629+
/* Gaussian blur */
630+
gaussian_blur_line (cmatrix, cmatrix_length, src, dest,height, bpp);
631+
}
632+
457633
gimp_pixel_rgn_set_col (destPR, dest, x1 + col, y1, height);
458634

459635
if (show_progress && col % 16 == 0)
@@ -480,7 +656,7 @@ unsharp_region (GimpPixelRgn *srcPR,
480656
/* combine the two */
481657
for (u = 0; u < width; u++)
482658
{
483-
for (v = 0; v < bytes; v++)
659+
for (v = 0; v < bpp; v++)
484660
{
485661
gint value;
486662
gint diff = *s - *d;

0 commit comments

Comments
 (0)