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