Bug 780859 - Brush hardness blur is slow

Add a specialized convolution algorithm for the hardness blur.  It
uses the same kernel as before, but performs the convolution in
(amortized) O(1)-per-pixel time, instead of O(n^2), where n is the
size of the kernel (or the blur radius).

Note that the new code performs the convolution in the input color
space, instead of always using a linear space.  Since our brush
pixmaps (but the not masks) are currently perceptual, the result is
a bit different.
This commit is contained in:
Ell 2017-04-07 10:14:05 -04:00
parent c585c99e80
commit da30b86ffc
1 changed files with 173 additions and 88 deletions

View File

@ -19,6 +19,8 @@
#include "config.h"
#include <string.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
@ -45,10 +47,8 @@ static void gimp_brush_transform_bounding_box (GimpBrush *brush,
gint *width,
gint *height);
static gdouble gimp_brush_transform_array_sum (gfloat *arr,
gint len);
static void gimp_brush_transform_fill_blur_kernel (gfloat *arr,
gint len);
static void gimp_brush_transform_blur (GimpTempBuf *buf,
gint r);
static gint gimp_brush_transform_blur_kernel_size (gint height,
gint width,
gdouble hardness);
@ -347,36 +347,7 @@ gimp_brush_real_transform_mask (GimpBrush *brush,
if (hardness < 1.0)
{
GimpTempBuf *blur_src;
GeglBuffer *src_buffer;
GeglBuffer *dest_buffer;
gint kernel_len = kernel_size * kernel_size;
gfloat blur_kernel[kernel_len];
gimp_brush_transform_fill_blur_kernel (blur_kernel, kernel_len);
blur_src = gimp_temp_buf_copy (result);
src_buffer = gimp_temp_buf_create_buffer (blur_src);
dest_buffer = gimp_temp_buf_create_buffer (result);
gimp_temp_buf_unref (blur_src);
gimp_gegl_convolve (src_buffer,
GEGL_RECTANGLE (0, 0,
gimp_temp_buf_get_width (blur_src),
gimp_temp_buf_get_height (blur_src)),
dest_buffer,
GEGL_RECTANGLE (0, 0,
gimp_temp_buf_get_width (result),
gimp_temp_buf_get_height (result)),
blur_kernel, kernel_size,
gimp_brush_transform_array_sum (blur_kernel,
kernel_len),
GIMP_NORMAL_CONVOL, FALSE);
g_object_unref (src_buffer);
g_object_unref (dest_buffer);
gimp_brush_transform_blur (result, kernel_size / 2);
}
return result;
@ -658,36 +629,7 @@ gimp_brush_real_transform_pixmap (GimpBrush *brush,
if (hardness < 1.0)
{
GimpTempBuf *blur_src;
GeglBuffer *src_buffer;
GeglBuffer *dest_buffer;
gint kernel_len = kernel_size * kernel_size;
gfloat blur_kernel[kernel_len];
gimp_brush_transform_fill_blur_kernel (blur_kernel, kernel_len);
blur_src = gimp_temp_buf_copy (result);
src_buffer = gimp_temp_buf_create_buffer (blur_src);
dest_buffer = gimp_temp_buf_create_buffer (result);
gimp_temp_buf_unref (blur_src);
gimp_gegl_convolve (src_buffer,
GEGL_RECTANGLE (0, 0,
gimp_temp_buf_get_width (blur_src),
gimp_temp_buf_get_height (blur_src)),
dest_buffer,
GEGL_RECTANGLE (0, 0,
gimp_temp_buf_get_width (result),
gimp_temp_buf_get_height (result)),
blur_kernel, kernel_size,
gimp_brush_transform_array_sum (blur_kernel,
kernel_len),
GIMP_NORMAL_CONVOL, FALSE);
g_object_unref (src_buffer);
g_object_unref (dest_buffer);
gimp_brush_transform_blur (result, kernel_size / 2);
}
return result;
@ -775,35 +717,178 @@ gimp_brush_transform_bounding_box (GimpBrush *brush,
*height = MAX (1, *height);
}
static gdouble
gimp_brush_transform_array_sum (gfloat *arr,
gint len)
{
gfloat total = 0;
gint i;
for (i = 0; i < len; i++)
{
total += arr [i];
}
return total;
}
/* Blurs the brush mask/pixmap using a convolution of the form:
*
* 12 11 10 9 8
* 7 6 5 4 3
* 2 1 0 1 2
* 3 4 5 6 7
* 8 9 10 11 12
*
* (i.e., an array, wrapped into a matrix, whose i-th element is
* `abs (i - a / 2)`, where `a` is the length of the array.) `r` specifies the
* convolution kernel's radius.
*/
static void
gimp_brush_transform_fill_blur_kernel (gfloat *arr,
gint len)
gimp_brush_transform_blur (GimpTempBuf *buf,
gint r)
{
gint half_point = ((gint) len / 2) + 1;
gint i;
typedef struct
{
gint sum;
gint weighted_sum;
gint middle_sum;
} Sums;
for (i = 0; i < len; i++)
const Babl *format = gimp_temp_buf_get_format (buf);
gint components = babl_format_get_n_components (format);
gint width = gimp_temp_buf_get_width (buf);
gint height = gimp_temp_buf_get_height (buf);
gint stride = components * width;
guchar *data = gimp_temp_buf_get_data (buf);
gint rw = MIN (r, width - 1);
gint rh = MIN (r, height - 1);
gint n = 2 * r + 1;
gint weight = (n * n / 2) * (n * n / 2 + 1);
gint x;
gint y;
gint c;
Sums *sums;
guchar *d;
Sums *s;
if (rw <= 0 || rh <= 0)
return;
sums = g_new (Sums, width * height * components);
d = data;
s = sums;
for (y = 0; y < height; y++)
{
if (i < half_point)
arr [i] = half_point - i;
else
arr [i] = i - half_point;
struct
{
gint sum;
gint weighted_sum;
gint leading_sum;
gint leading_weighted_sum;
} acc[components];
memset (acc, 0, sizeof (acc));
for (x = 0; x <= rw; x++)
{
for (c = 0; c < components; c++)
{
acc[c].sum += d[components * x + c];
acc[c].weighted_sum += -x * d[components * x + c];
}
}
for (x = 0; x < width; x++)
{
for (c = 0; c < components; c++)
{
if (x > 0)
{
acc[c].weighted_sum += acc[c].sum;
acc[c].leading_weighted_sum += acc[c].leading_sum;
if (x < width - r)
{
acc[c].sum += d[components * r];
acc[c].weighted_sum += -r * d[components * r];
}
}
acc[c].leading_sum += d[0];
s->sum = acc[c].sum;
s->weighted_sum = acc[c].weighted_sum;
s->middle_sum = 2 * acc[c].leading_weighted_sum -
acc[c].weighted_sum;
if (x >= r)
{
acc[c].sum -= d[components * -r];
acc[c].weighted_sum -= r * d[components * -r];
acc[c].leading_sum -= d[components * -r];
acc[c].leading_weighted_sum -= r * d[components * -r];
}
d++;
s++;
}
}
}
for (x = 0; x < width; x++)
{
struct
{
gint weighted_sum;
gint leading_sum;
gint trailing_sum;
} acc[components];
memset (acc, 0, sizeof (acc));
d = data + components * x;
s = sums + components * x;
for (y = 1; y <= rh; y++)
{
for (c = 0; c < components; c++)
{
acc[c].weighted_sum += n * y * s[stride * y + c].sum -
s[stride * y + c].weighted_sum;
acc[c].trailing_sum += s[stride * y + c].sum;
}
}
for (y = 0; y < height; y++)
{
for (c = 0; c < components; c++)
{
if (y > 0)
{
acc[c].weighted_sum += s->weighted_sum +
n * (acc[c].leading_sum -
acc[c].trailing_sum);
acc[c].trailing_sum -= s->sum;
if (y < height - r)
{
acc[c].weighted_sum += n * r * s[stride * r].sum -
s[stride * r].weighted_sum;
acc[c].trailing_sum += s[stride * r].sum;
}
}
acc[c].leading_sum += s->sum;
*d = (acc[c].weighted_sum + s->middle_sum + weight / 2) / weight;
acc[c].weighted_sum += s->weighted_sum;
if (y >= r)
{
acc[c].weighted_sum -= n * r * s[stride * -r].sum +
s[stride * -r].weighted_sum;
acc[c].leading_sum -= s[stride * -r].sum;
}
d++;
s++;
}
d += stride - components;
s += stride - components;
}
}
g_free (sums);
}
static gint