1/* GSK - The GIMP Toolkit
2 *
3 * Copyright (C) 2014 Red Hat
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * Written by:
19 * Jasper St. Pierre <jstpierre@mecheye.net>
20 * Owen Taylor <otaylor@redhat.com>
21 */
22
23#include "gskcairoblurprivate.h"
24
25#include <math.h>
26#include <string.h>
27
28/*
29 * Gets the size for a single box blur.
30 *
31 * Much of this, the 3 * sqrt(2 * pi) / 4, is the known value for
32 * approximating a Gaussian using box blurs. This yields quite a good
33 * approximation for a Gaussian. For more details, see:
34 * http://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement
35 * https://bugzilla.mozilla.org/show_bug.cgi?id=590039#c19
36 */
37#define GAUSSIAN_SCALE_FACTOR ((3.0 * sqrt(2 * G_PI) / 4))
38
39#define get_box_filter_size(radius) ((int)(GAUSSIAN_SCALE_FACTOR * (radius)))
40
41/* Sadly, clang is picky about get_box_filter_size(2) not being a
42 * constant expression, thus we have to use precomputed values.
43 */
44#define BOX_FILTER_SIZE_2 3
45#define BOX_FILTER_SIZE_3 5
46#define BOX_FILTER_SIZE_4 7
47#define BOX_FILTER_SIZE_5 9
48#define BOX_FILTER_SIZE_6 11
49#define BOX_FILTER_SIZE_7 13
50#define BOX_FILTER_SIZE_8 15
51#define BOX_FILTER_SIZE_9 16
52#define BOX_FILTER_SIZE_10 18
53
54/* This applies a single box blur pass to a horizontal range of pixels;
55 * since the box blur has the same weight for all pixels, we can
56 * implement an efficient sliding window algorithm where we add
57 * in pixels coming into the window from the right and remove
58 * them when they leave the windw to the left.
59 *
60 * d is the filter width; for even d shift indicates how the blurred
61 * result is aligned with the original - does ' x ' go to ' yy' (shift=1)
62 * or 'yy ' (shift=-1)
63 */
64static void
65blur_xspan (guchar *row,
66 guchar *tmp_buffer,
67 int row_width,
68 int d,
69 int shift)
70{
71 int offset;
72 int sum = 0;
73 int i;
74
75 if (d % 2 == 1)
76 offset = d / 2;
77 else
78 offset = (d - shift) / 2;
79
80 /* All the conditionals in here look slow, but the branches will
81 * be well predicted and there are enough different possibilities
82 * that trying to write this as a series of unconditional loops
83 * is hard and not an obvious win. The main slow down here seems
84 * to be the integer division per pixel; one possible optimization
85 * would be to accumulate into two 16-bit integer buffers and
86 * only divide down after all three passes. (SSE parallel implementation
87 * of the divide step is possible.)
88 */
89
90#define BLUR_ROW_KERNEL(D) \
91 for (i = -(D) + offset; i < row_width + offset; i++) \
92 { \
93 if (i >= 0 && i < row_width) \
94 sum += row[i]; \
95 \
96 if (i >= offset) \
97 { \
98 if (i >= (D)) \
99 sum -= row[i - (D)]; \
100 \
101 tmp_buffer[i - offset] = (sum + (D) / 2) / (D); \
102 } \
103 } \
104 break;
105
106 /* We unroll the values for d for radius 2-10 to avoid a generic
107 * divide operation (not radius 1, because its a no-op) */
108 switch (d)
109 {
110 case BOX_FILTER_SIZE_2: BLUR_ROW_KERNEL (BOX_FILTER_SIZE_2);
111 case BOX_FILTER_SIZE_3: BLUR_ROW_KERNEL (BOX_FILTER_SIZE_3);
112 case BOX_FILTER_SIZE_4: BLUR_ROW_KERNEL (BOX_FILTER_SIZE_4);
113 case BOX_FILTER_SIZE_5: BLUR_ROW_KERNEL (BOX_FILTER_SIZE_5);
114 case BOX_FILTER_SIZE_6: BLUR_ROW_KERNEL (BOX_FILTER_SIZE_6);
115 case BOX_FILTER_SIZE_7: BLUR_ROW_KERNEL (BOX_FILTER_SIZE_7);
116 case BOX_FILTER_SIZE_8: BLUR_ROW_KERNEL (BOX_FILTER_SIZE_8);
117 case BOX_FILTER_SIZE_9: BLUR_ROW_KERNEL (BOX_FILTER_SIZE_9);
118 case BOX_FILTER_SIZE_10: BLUR_ROW_KERNEL (BOX_FILTER_SIZE_10);
119 default: BLUR_ROW_KERNEL (d);
120 }
121
122 memcpy (dest: row, src: tmp_buffer, n: row_width);
123}
124
125static void
126blur_rows (guchar *dst_buffer,
127 guchar *tmp_buffer,
128 int buffer_width,
129 int buffer_height,
130 int d)
131{
132 int i;
133
134 for (i = 0; i < buffer_height; i++)
135 {
136 guchar *row = dst_buffer + i * buffer_width;
137
138 /* We want to produce a symmetric blur that spreads a pixel
139 * equally far to the left and right. If d is odd that happens
140 * naturally, but for d even, we approximate by using a blur
141 * on either side and then a centered blur of size d + 1.
142 * (technique also from the SVG specification)
143 */
144 if (d % 2 == 1)
145 {
146 blur_xspan (row, tmp_buffer, row_width: buffer_width, d, shift: 0);
147 blur_xspan (row, tmp_buffer, row_width: buffer_width, d, shift: 0);
148 blur_xspan (row, tmp_buffer, row_width: buffer_width, d, shift: 0);
149 }
150 else
151 {
152 blur_xspan (row, tmp_buffer, row_width: buffer_width, d, shift: 1);
153 blur_xspan (row, tmp_buffer, row_width: buffer_width, d, shift: -1);
154 blur_xspan (row, tmp_buffer, row_width: buffer_width, d: d + 1, shift: 0);
155 }
156 }
157}
158
159/* Swaps width and height.
160 */
161static void
162flip_buffer (guchar *dst_buffer,
163 guchar *src_buffer,
164 int width,
165 int height)
166{
167 /* Working in blocks increases cache efficiency, compared to reading
168 * or writing an entire column at once
169 */
170#define BLOCK_SIZE 16
171
172 int i0, j0;
173
174 for (i0 = 0; i0 < width; i0 += BLOCK_SIZE)
175 for (j0 = 0; j0 < height; j0 += BLOCK_SIZE)
176 {
177 int max_j = MIN(j0 + BLOCK_SIZE, height);
178 int max_i = MIN(i0 + BLOCK_SIZE, width);
179 int i, j;
180
181 for (i = i0; i < max_i; i++)
182 for (j = j0; j < max_j; j++)
183 dst_buffer[i * height + j] = src_buffer[j * width + i];
184 }
185#undef BLOCK_SIZE
186}
187
188static void
189_boxblur (guchar *buffer,
190 int width,
191 int height,
192 int radius,
193 GskBlurFlags flags)
194{
195 guchar *flipped_buffer;
196 int d = get_box_filter_size (radius);
197
198 flipped_buffer = g_malloc (n_bytes: width * height);
199
200 if (flags & GSK_BLUR_Y)
201 {
202 /* Step 1: swap rows and columns */
203 flip_buffer (dst_buffer: flipped_buffer, src_buffer: buffer, width, height);
204
205 /* Step 2: blur rows (really columns) */
206 blur_rows (dst_buffer: flipped_buffer, tmp_buffer: buffer, buffer_width: height, buffer_height: width, d);
207
208 /* Step 3: swap rows and columns */
209 flip_buffer (dst_buffer: buffer, src_buffer: flipped_buffer, width: height, height: width);
210 }
211
212 if (flags & GSK_BLUR_X)
213 {
214 /* Step 4: blur rows */
215 blur_rows (dst_buffer: buffer, tmp_buffer: flipped_buffer, buffer_width: width, buffer_height: height, d);
216 }
217
218 g_free (mem: flipped_buffer);
219}
220
221/*
222 * _gsk_cairo_blur_surface:
223 * @surface: a cairo image surface.
224 * @radius: the blur radius.
225 *
226 * Blurs the cairo image surface at the given radius.
227 */
228void
229gsk_cairo_blur_surface (cairo_surface_t* surface,
230 double radius_d,
231 GskBlurFlags flags)
232{
233 int radius = radius_d;
234
235 g_return_if_fail (surface != NULL);
236 g_return_if_fail (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE);
237 g_return_if_fail (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_A8);
238
239 /* The code doesn't actually do any blurring for radius 1, as it
240 * ends up with box filter size 1 */
241 if (radius <= 1)
242 return;
243
244 if ((flags & (GSK_BLUR_X|GSK_BLUR_Y)) == 0)
245 return;
246
247 /* Before we mess with the surface, execute any pending drawing. */
248 cairo_surface_flush (surface);
249
250 _boxblur (buffer: cairo_image_surface_get_data (surface),
251 width: cairo_image_surface_get_stride (surface),
252 height: cairo_image_surface_get_height (surface),
253 radius, flags);
254
255 /* Inform cairo we altered the surface contents. */
256 cairo_surface_mark_dirty (surface);
257}
258
259/*<private>
260 * gsk_cairo_blur_compute_pixels:
261 * @radius: the radius to compute the pixels for
262 *
263 * Computes the number of pixels necessary to extend an image in one
264 * direction to hold the image with shadow.
265 *
266 * This is just the number of pixels added by the blur radius, shadow
267 * offset and spread are not included.
268 *
269 * Much of this, the 3 * sqrt(2 * pi) / 4, is the known value for
270 * approximating a Gaussian using box blurs. This yields quite a good
271 * approximation for a Gaussian. Then we multiply this by 1.5 since our
272 * code wants the radius of the entire triple-box-blur kernel instead of
273 * the diameter of an individual box blur. For more details, see:
274 * http://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement
275 * https://bugzilla.mozilla.org/show_bug.cgi?id=590039#c19
276 */
277int
278gsk_cairo_blur_compute_pixels (double radius)
279{
280 return floor (x: radius * GAUSSIAN_SCALE_FACTOR * 1.5 + 0.5);
281}
282
283static gboolean
284needs_blur (float radius,
285 GskBlurFlags blur_flags)
286{
287 /* Neither blurring horizontal nor vertical means no blurring at all. */
288 if ((blur_flags & (GSK_BLUR_X | GSK_BLUR_Y)) == 0)
289 return FALSE;
290
291 /* The code doesn't actually do any blurring for radius 1, as it
292 * ends up with box filter size 1 */
293 if (radius <= 1.0)
294 return FALSE;
295
296 return TRUE;
297}
298
299static const cairo_user_data_key_t original_cr_key;
300
301cairo_t *
302gsk_cairo_blur_start_drawing (cairo_t *cr,
303 float radius,
304 GskBlurFlags blur_flags)
305{
306 double clip_x1, clip_x2, clip_y1, clip_y2, clip_width, clip_height;
307 cairo_surface_t *surface;
308 cairo_t *blur_cr;
309 double clip_radius;
310 double x_scale, y_scale;
311 gboolean blur_x = (blur_flags & GSK_BLUR_X) != 0;
312 gboolean blur_y = (blur_flags & GSK_BLUR_Y) != 0;
313
314 if (!needs_blur (radius, blur_flags))
315 return cr;
316
317 cairo_clip_extents (cr, x1: &clip_x1, y1: &clip_y1, x2: &clip_x2, y2: &clip_y2);
318 clip_width = clip_x2 - clip_x1;
319 clip_height = clip_y2 - clip_y1;
320
321 clip_radius = gsk_cairo_blur_compute_pixels (radius);
322
323 x_scale = y_scale = 1;
324 cairo_surface_get_device_scale (surface: cairo_get_target (cr), x_scale: &x_scale, y_scale: &y_scale);
325
326 if (blur_flags & GSK_BLUR_REPEAT)
327 {
328 if (!blur_x)
329 clip_width = 1;
330 if (!blur_y)
331 clip_height = 1;
332 }
333
334 /* Create a larger surface to center the blur. */
335 surface = cairo_surface_create_similar_image (other: cairo_get_target (cr),
336 format: CAIRO_FORMAT_A8,
337 width: x_scale * (clip_width + (blur_x ? 2 * clip_radius : 0)),
338 height: y_scale * (clip_height + (blur_y ? 2 * clip_radius : 0)));
339 cairo_surface_set_device_scale (surface, x_scale, y_scale);
340 cairo_surface_set_device_offset (surface,
341 x_offset: x_scale * ((blur_x ? clip_radius : 0) - clip_x1),
342 y_offset: y_scale * ((blur_y ? clip_radius : 0) - clip_y1));
343
344 blur_cr = cairo_create (target: surface);
345 cairo_set_user_data (cr: blur_cr, key: &original_cr_key, user_data: cairo_reference (cr), destroy: (cairo_destroy_func_t) cairo_destroy);
346
347 if (cairo_has_current_point (cr))
348 {
349 double x, y;
350
351 cairo_get_current_point (cr, x: &x, y: &y);
352 cairo_move_to (cr: blur_cr, x, y);
353 }
354
355 return blur_cr;
356}
357
358static void
359mask_surface_repeat (cairo_t *cr,
360 cairo_surface_t *surface)
361{
362 cairo_pattern_t *pattern;
363
364 pattern = cairo_pattern_create_for_surface (surface);
365 cairo_pattern_set_extend (pattern, extend: CAIRO_EXTEND_REPEAT);
366
367 cairo_mask (cr, pattern);
368
369 cairo_pattern_destroy (pattern);
370}
371
372cairo_t *
373gsk_cairo_blur_finish_drawing (cairo_t *cr,
374 float radius,
375 const GdkRGBA *color,
376 GskBlurFlags blur_flags)
377{
378 cairo_t *original_cr;
379 cairo_surface_t *surface;
380 double x_scale;
381
382 if (!needs_blur (radius, blur_flags))
383 return cr;
384
385 original_cr = cairo_get_user_data (cr, key: &original_cr_key);
386
387 /* Blur the surface. */
388 surface = cairo_get_target (cr);
389
390 x_scale = 1;
391 cairo_surface_get_device_scale (surface: cairo_get_target (cr), x_scale: &x_scale, NULL);
392
393 gsk_cairo_blur_surface (surface, radius_d: x_scale * radius, flags: blur_flags);
394
395 gdk_cairo_set_source_rgba (cr: original_cr, rgba: color);
396 if (blur_flags & GSK_BLUR_REPEAT)
397 mask_surface_repeat (cr: original_cr, surface);
398 else
399 cairo_mask_surface (cr: original_cr, surface, surface_x: 0, surface_y: 0);
400
401 cairo_destroy (cr);
402
403 cairo_surface_destroy (surface);
404
405 return original_cr;
406}
407
408

source code of gtk/gsk/gskcairoblur.c