1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /*
4 * GThumb
5 *
6 * Copyright (C) 2011 The Free Software Foundation, Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <config.h>
23 #include <math.h>
24 #include <string.h>
25 #include <gthumb.h>
26 #include "cairo-blur.h"
27
28
29 typedef struct {
30 GthAsyncTask *task;
31 gulong total_lines;
32 gulong processed_lines;
33 gboolean cancelled;
34 } ProgressData;
35
36
37 static inline gboolean
progress_data_inc_processed_lines(ProgressData * progress_data)38 progress_data_inc_processed_lines (ProgressData *progress_data)
39 {
40 double progress;
41
42 if (progress_data->task == NULL)
43 return TRUE;
44
45 gth_async_task_get_data (progress_data->task, NULL, &progress_data->cancelled, NULL);
46 if (progress_data->cancelled)
47 return FALSE;
48
49 progress = (double) progress_data->processed_lines++ / progress_data->total_lines;
50 gth_async_task_set_data (progress_data->task, NULL, NULL, &progress);
51
52 return TRUE;
53 }
54
55
56 static gboolean
_cairo_image_surface_gaussian_blur(cairo_surface_t * source,int radius,ProgressData * progress_data)57 _cairo_image_surface_gaussian_blur (cairo_surface_t *source,
58 int radius,
59 ProgressData *progress_data)
60 {
61 /* FIXME: to do */
62 return FALSE;
63 }
64
65
66 static gboolean
box_blur(cairo_surface_t * source,cairo_surface_t * destination,int radius,guchar * div_kernel_size,ProgressData * progress_data)67 box_blur (cairo_surface_t *source,
68 cairo_surface_t *destination,
69 int radius,
70 guchar *div_kernel_size,
71 ProgressData *progress_data)
72 {
73 int width, height, src_rowstride, dest_rowstride;
74 guchar *p_src, *p_dest, *c1, *c2;
75 int x, y, i, i1, i2, width_minus_1, height_minus_1, radius_plus_1;
76 int r, g, b, a;
77 guchar *p_dest_row, *p_dest_col;
78
79 width = cairo_image_surface_get_width (source);
80 height = cairo_image_surface_get_height (source);
81 radius_plus_1 = radius + 1;
82
83 /* horizontal blur */
84
85 p_src = _cairo_image_surface_flush_and_get_data (source);
86 p_dest = _cairo_image_surface_flush_and_get_data (destination);
87 src_rowstride = cairo_image_surface_get_stride (source);
88 dest_rowstride = cairo_image_surface_get_stride (destination);
89 width_minus_1 = width - 1;
90 for (y = 0; y < height; y++) {
91
92 if (! progress_data_inc_processed_lines (progress_data))
93 return FALSE;
94
95 /* calculate the initial sums of the kernel */
96
97 r = g = b = a = 0;
98
99 for (i = -radius; i <= radius; i++) {
100 c1 = p_src + (CLAMP (i, 0, width_minus_1) * 4);
101 r += c1[CAIRO_RED];
102 g += c1[CAIRO_GREEN];
103 b += c1[CAIRO_BLUE];
104 /*if (n_channels == 4)
105 a += c1[CAIRO_ALPHA];*/
106 }
107
108 p_dest_row = p_dest;
109 for (x = 0; x < width; x++) {
110 /* set as the mean of the kernel */
111
112 p_dest_row[CAIRO_RED] = div_kernel_size[r];
113 p_dest_row[CAIRO_GREEN] = div_kernel_size[g];
114 p_dest_row[CAIRO_BLUE] = div_kernel_size[b];
115 p_dest_row[CAIRO_ALPHA] = 0xff;
116 /*if (n_channels == 4)
117 p_dest_row[CAIRO_ALPHA] = div_kernel_size[a];*/
118 p_dest_row += 4;
119
120 /* the pixel to add to the kernel */
121
122 i1 = x + radius_plus_1;
123 if (i1 > width_minus_1)
124 i1 = width_minus_1;
125 c1 = p_src + (i1 * 4);
126
127 /* the pixel to remove from the kernel */
128
129 i2 = x - radius;
130 if (i2 < 0)
131 i2 = 0;
132 c2 = p_src + (i2 * 4);
133
134 /* calculate the new sums of the kernel */
135
136 r += c1[CAIRO_RED] - c2[CAIRO_RED];
137 g += c1[CAIRO_GREEN] - c2[CAIRO_GREEN];
138 b += c1[CAIRO_BLUE] - c2[CAIRO_BLUE];
139 /*if (n_channels == 4)
140 a += c1[CAIRO_ALPHA] - c2[CAIRO_ALPHA];*/
141 }
142
143 p_src += src_rowstride;
144 p_dest += dest_rowstride;
145 }
146 cairo_surface_mark_dirty (destination);
147
148 /* vertical blur */
149
150 p_src = _cairo_image_surface_flush_and_get_data (destination);
151 p_dest = _cairo_image_surface_flush_and_get_data (source);
152 src_rowstride = cairo_image_surface_get_stride (destination);
153 dest_rowstride = cairo_image_surface_get_stride (source);
154 height_minus_1 = height - 1;
155 for (x = 0; x < width; x++) {
156
157 if (! progress_data_inc_processed_lines (progress_data))
158 return FALSE;
159
160 /* calculate the initial sums of the kernel */
161
162 r = g = b = a = 0;
163
164 for (i = -radius; i <= radius; i++) {
165 c1 = p_src + (CLAMP (i, 0, height_minus_1) * src_rowstride);
166 r += c1[CAIRO_RED];
167 g += c1[CAIRO_GREEN];
168 b += c1[CAIRO_BLUE];
169 /*if (n_channels == 4)
170 a += c1[CAIRO_ALPHA];*/
171 }
172
173 p_dest_col = p_dest;
174 for (y = 0; y < height; y++) {
175 /* set as the mean of the kernel */
176
177 p_dest_col[CAIRO_RED] = div_kernel_size[r];
178 p_dest_col[CAIRO_GREEN] = div_kernel_size[g];
179 p_dest_col[CAIRO_BLUE] = div_kernel_size[b];
180 p_dest_col[CAIRO_ALPHA] = 0xff;
181 /*if (n_channels == 4)
182 p_dest_row[CAIRO_ALPHA] = div_kernel_size[a];*/
183 p_dest_col += dest_rowstride;
184
185 /* the pixel to add to the kernel */
186
187 i1 = y + radius_plus_1;
188 if (i1 > height_minus_1)
189 i1 = height_minus_1;
190 c1 = p_src + (i1 * src_rowstride);
191
192 /* the pixel to remove from the kernel */
193
194 i2 = y - radius;
195 if (i2 < 0)
196 i2 = 0;
197 c2 = p_src + (i2 * src_rowstride);
198
199 /* calculate the new sums of the kernel */
200
201 r += c1[CAIRO_RED] - c2[CAIRO_RED];
202 g += c1[CAIRO_GREEN] - c2[CAIRO_GREEN];
203 b += c1[CAIRO_BLUE] - c2[CAIRO_BLUE];
204 /*if (n_channels == 4)
205 a += c1[CAIRO_ALPHA] - c2[CAIRO_ALPHA];*/
206 }
207
208 p_src += 4;
209 p_dest += 4;
210 }
211 cairo_surface_mark_dirty (source);
212
213 return TRUE;
214 }
215
216
217 static gboolean
_cairo_image_surface_box_blur(cairo_surface_t * source,int radius,int iterations,ProgressData * progress_data)218 _cairo_image_surface_box_blur (cairo_surface_t *source,
219 int radius,
220 int iterations,
221 ProgressData *progress_data)
222 {
223 gint64 kernel_size;
224 guchar *div_kernel_size;
225 int i;
226 cairo_surface_t *tmp;
227 gboolean completed;
228
229 kernel_size = 2 * radius + 1;
230
231 /* optimization to avoid divisions: div_kernel_size[x] == x / kernel_size */
232 div_kernel_size = g_new (guchar, 256 * kernel_size);
233 for (i = 0; i < 256 * kernel_size; i++)
234 div_kernel_size[i] = (guchar) (i / kernel_size);
235
236 completed = TRUE;
237 tmp = _cairo_image_surface_create_compatible (source);
238 while (completed && (iterations-- > 0)) {
239 completed = box_blur (source, tmp, radius, div_kernel_size, progress_data);
240 }
241
242 cairo_surface_destroy (tmp);
243
244 return completed;
245 }
246
247
248 static gboolean
_cairo_image_surface_blur_with_progress(cairo_surface_t * source,int radius,ProgressData * progress_data)249 _cairo_image_surface_blur_with_progress (cairo_surface_t *source,
250 int radius,
251 ProgressData *progress_data)
252 {
253 if (radius <= 10)
254 return _cairo_image_surface_box_blur (source, radius, 3, progress_data);
255 else
256 return _cairo_image_surface_gaussian_blur (source, radius, progress_data);
257 }
258
259
260 gboolean
_cairo_image_surface_blur(cairo_surface_t * source,int radius,GthAsyncTask * task)261 _cairo_image_surface_blur (cairo_surface_t *source,
262 int radius,
263 GthAsyncTask *task)
264 {
265 ProgressData progress_data;
266
267 progress_data.task = task;
268 progress_data.total_lines = (cairo_image_surface_get_width (source) * 3) + (cairo_image_surface_get_height (source) * 3);
269 progress_data.processed_lines = 0;
270 progress_data.cancelled = FALSE;
271
272 return _cairo_image_surface_blur_with_progress (source, radius, &progress_data);
273 }
274
275
276 gboolean
_cairo_image_surface_sharpen(cairo_surface_t * source,int radius,double amount,guchar threshold,GthAsyncTask * task)277 _cairo_image_surface_sharpen (cairo_surface_t *source,
278 int radius,
279 double amount,
280 guchar threshold,
281 GthAsyncTask *task)
282 {
283 ProgressData progress_data;
284 cairo_surface_t *blurred;
285 int width, height;
286 int source_rowstride, blurred_rowstride;
287 int x, y;
288 guchar *p_src, *p_blurred;
289 guchar *p_src_row, *p_blurred_row;
290 guchar r1, g1, b1;
291 guchar r2, g2, b2;
292 int tmp;
293
294 progress_data.task = task;
295 progress_data.total_lines = (cairo_image_surface_get_width (source) * 3) + (cairo_image_surface_get_height (source) * 3) + cairo_image_surface_get_height (source);
296 progress_data.processed_lines = 0;
297 progress_data.cancelled = FALSE;
298
299 blurred = _cairo_image_surface_copy (source);
300 if (! _cairo_image_surface_blur_with_progress (blurred, radius, &progress_data)) {
301 cairo_surface_destroy (blurred);
302 return FALSE;
303 }
304
305 width = cairo_image_surface_get_width (source);
306 height = cairo_image_surface_get_height (source);
307 source_rowstride = cairo_image_surface_get_stride (source);
308 blurred_rowstride = cairo_image_surface_get_stride (blurred);
309
310 p_src = _cairo_image_surface_flush_and_get_data (source);
311 p_blurred = _cairo_image_surface_flush_and_get_data (blurred);
312
313 #define ASSIGN_INTERPOLATED_VALUE(x1, x2) \
314 if (ABS (x1 - x2) >= threshold) { \
315 tmp = interpolate_value (x1, x2, amount); \
316 x1 = CLAMP (tmp, 0, 255); \
317 }
318
319 for (y = 0; y < height; y++) {
320 p_src_row = p_src;
321 p_blurred_row = p_blurred;
322
323 if (! progress_data_inc_processed_lines (&progress_data)) {
324 cairo_surface_destroy (blurred);
325 return FALSE;
326 }
327
328 for (x = 0; x < width; x++) {
329 r1 = p_src_row[CAIRO_RED];
330 g1 = p_src_row[CAIRO_GREEN];
331 b1 = p_src_row[CAIRO_BLUE];
332
333 r2 = p_blurred_row[CAIRO_RED];
334 g2 = p_blurred_row[CAIRO_GREEN];
335 b2 = p_blurred_row[CAIRO_BLUE];
336
337 ASSIGN_INTERPOLATED_VALUE (r1, r2)
338 ASSIGN_INTERPOLATED_VALUE (g1, g2)
339 ASSIGN_INTERPOLATED_VALUE (b1, b2)
340
341 p_src_row[CAIRO_RED] = r1;
342 p_src_row[CAIRO_GREEN] = g1;
343 p_src_row[CAIRO_BLUE] = b1;
344
345 p_src_row += 4;
346 p_blurred_row += 4;
347 }
348
349 p_src += source_rowstride;
350 p_blurred += blurred_rowstride;
351 }
352
353 #undef ASSIGN_INTERPOLATED_VALUE
354
355 cairo_surface_mark_dirty (source);
356 cairo_surface_destroy (blurred);
357
358 return TRUE;
359 }
360