1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2009 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 <gthumb.h>
24 #include <extensions/image_viewer/gth-image-viewer-page.h>
25 #include "gth-file-tool-red-eye.h"
26 
27 
28 #define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
29 #define REGION_SEARCH_SIZE 3
30 
31 
32 static const double RED_FACTOR = 0.5133333;
33 static const double GREEN_FACTOR  = 1.0;
34 static const double BLUE_FACTOR =  0.1933333;
35 
36 
37 struct _GthFileToolRedEyePrivate {
38 	GdkPixbuf        *src_pixbuf;
39 	GtkBuilder       *builder;
40 	GthImageSelector *selector;
41 	GthZoomChange     original_zoom_change;
42 	GdkPixbuf        *new_pixbuf;
43 	char             *is_red;
44 };
45 
46 
G_DEFINE_TYPE_WITH_CODE(GthFileToolRedEye,gth_file_tool_red_eye,GTH_TYPE_IMAGE_VIEWER_PAGE_TOOL,G_ADD_PRIVATE (GthFileToolRedEye))47 G_DEFINE_TYPE_WITH_CODE (GthFileToolRedEye,
48 			 gth_file_tool_red_eye,
49 			 GTH_TYPE_IMAGE_VIEWER_PAGE_TOOL,
50 			 G_ADD_PRIVATE (GthFileToolRedEye))
51 
52 
53 static int
54 find_region (int   row,
55  	     int   col,
56  	     int  *rtop,
57  	     int  *rbot,
58 	     int  *rleft,
59 	     int  *rright,
60 	     char *isred,
61 	     int   width,
62 	     int   height)
63 {
64 	int *rows, *cols, list_length = 0;
65 	int  mydir;
66 	int  total = 1;
67 
68 	/* a relatively efficient way to find all connected points in a
69 	 * region.  It considers points that have isred == 1, sets them to 2
70 	 * if they are connected to the starting point.
71 	 * row and col are the starting point of the region,
72 	 * the next four params define a rectangle our region fits into.
73 	 * isred is an array that tells us if pixels are red or not.
74 	 */
75 
76 	*rtop = row;
77 	*rbot = row;
78 	*rleft = col;
79 	*rright = col;
80 
81 	rows = g_malloc (width * height * sizeof(int));
82 	cols = g_malloc (width * height * sizeof(int));
83 
84 	rows[0] = row;
85 	cols[0] = col;
86 	list_length = 1;
87 
88 	do {
89 		list_length -= 1;
90 		row = rows[list_length];
91 		col = cols[list_length];
92 		for (mydir = 0; mydir < 8 ; mydir++) {
93 			switch (mydir) {
94 			case 0:
95 				/*  going left */
96 				if (col - 1 < 0) break;
97 				if (isred[col-1+row*width] == 1) {
98 					isred[col-1+row*width] = 2;
99 					if (*rleft > col-1) *rleft = col-1;
100 					rows[list_length] = row;
101 					cols[list_length] = col-1;
102 					list_length+=1;
103 					total += 1;
104 				}
105 				break;
106 			case 1:
107 				/* up and left */
108 				if (col - 1 < 0 || row -1 < 0 ) break;
109 				if (isred[col-1+(row-1)*width] == 1 ) {
110 					isred[col-1+(row-1)*width] = 2;
111 					if (*rleft > col -1) *rleft = col-1;
112 					if (*rtop > row -1) *rtop = row-1;
113 					rows[list_length] = row-1;
114 					cols[list_length] = col-1;
115 					list_length += 1;
116 					total += 1;
117 				}
118 				break;
119 			case 2:
120 				/* up */
121 				if (row -1 < 0 ) break;
122 				if (isred[col + (row-1)*width] == 1) {
123 					isred[col + (row-1)*width] = 2;
124 					if (*rtop > row-1) *rtop=row-1;
125 					rows[list_length] = row-1;
126 					cols[list_length] = col;
127 					list_length +=1;
128 					total += 1;
129 				}
130 				break;
131 			case 3:
132 				/*  up and right */
133 				if (col + 1 >= width || row -1 < 0 ) break;
134 				if (isred[col+1+(row-1)*width] == 1) {
135 					isred[col+1+(row-1)*width] = 2;
136 					if (*rright < col +1) *rright = col+1;
137 					if (*rtop > row -1) *rtop = row-1;
138 					rows[list_length] = row-1;
139 					cols[list_length] = col+1;
140 					list_length += 1;
141 					total +=1;
142 				}
143 				break;
144 			case 4:
145 				/* going right */
146 				if (col + 1 >= width) break;
147 				if (isred[col+1+row*width] == 1) {
148 					isred[col+1+row*width] = 2;
149 					if (*rright < col+1) *rright = col+1;
150 					rows[list_length] = row;
151 					cols[list_length] = col+1;
152 					list_length += 1;
153 					total += 1;
154 				}
155 				break;
156 			case 5:
157 				/* down and right */
158 				if (col + 1 >= width || row +1 >= height ) break;
159 				if (isred[col+1+(row+1)*width] ==1) {
160 					isred[col+1+(row+1)*width] = 2;
161 					if (*rright < col +1) *rright = col+1;
162 					if (*rbot < row +1) *rbot = row+1;
163 					rows[list_length] = row+1;
164 					cols[list_length] = col+1;
165 					list_length += 1;
166 					total += 1;
167 				}
168 				break;
169 			case 6:
170 				/* down */
171 				if (row +1 >=  height ) break;
172 				if (isred[col + (row+1)*width] == 1) {
173 					isred[col + (row+1)*width] = 2;
174 					if (*rbot < row+1) *rbot=row+1;
175 					rows[list_length] = row+1;
176 					cols[list_length] = col;
177 					list_length += 1;
178 					total += 1;
179 				}
180 				break;
181 			case 7:
182 				/* down and left */
183 				if (col - 1 < 0  || row +1 >= height ) break;
184 				if (isred[col-1+(row+1)*width] == 1) {
185 					isred[col-1+(row+1)*width] = 2;
186 					if (*rleft  > col -1) *rleft = col-1;
187 					if (*rbot < row +1) *rbot = row+1;
188 					rows[list_length] = row+1;
189 					cols[list_length] = col-1;
190 					list_length += 1;
191 					total += 1;
192 				}
193 				break;
194 			default:
195 				break;
196 			}
197 		}
198 	}
199 	while (list_length > 0);  /* stop when we add no more */
200 
201 	g_free (rows);
202 	g_free (cols);
203 
204 	return total;
205 }
206 
207 
208 static void
init_is_red(GthFileToolRedEye * self,GdkPixbuf * pixbuf)209 init_is_red (GthFileToolRedEye *self,
210 	     GdkPixbuf         *pixbuf)
211 {
212 	int        width, height;
213 	int        rowstride, channels;
214 	guchar    *pixels;
215 	int        i, j;
216 	int        ad_red, ad_green, ad_blue;
217 	const int  THRESHOLD = 0;
218 
219 	width = gdk_pixbuf_get_width (pixbuf);
220 	height = gdk_pixbuf_get_height (pixbuf);
221 	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
222 	channels = gdk_pixbuf_get_n_channels (pixbuf);
223 	pixels = gdk_pixbuf_get_pixels(pixbuf);
224 
225 	g_free (self->priv->is_red);
226 	self->priv->is_red = g_new0 (char, width * height);
227 
228 	for (i = 0; i < height; i++)  {
229 		for (j = 0; j < width; j++) {
230 			int ofs = channels * j + i * rowstride;
231 
232       			ad_red = pixels[ofs] * RED_FACTOR;
233 			ad_green = pixels[ofs + 1] * GREEN_FACTOR;
234 			ad_blue = pixels[ofs + 2] * BLUE_FACTOR;
235 
236 			//  This test from the gimp redeye plugin.
237 
238 			if ((ad_red >= ad_green - THRESHOLD) && (ad_red >= ad_blue - THRESHOLD))
239 				self->priv->is_red[j + i * width] = 1;
240      		}
241 	}
242 }
243 
244 
245 /* returns TRUE if the pixbuf has been modified */
246 static gboolean
fix_redeye(GdkPixbuf * pixbuf,char * isred,int x,int y)247 fix_redeye (GdkPixbuf *pixbuf,
248 	    char      *isred,
249 	    int        x,
250 	    int        y)
251 {
252 	gboolean  region_fixed = FALSE;
253 	int       width;
254 	int       height;
255 	int       rowstride;
256 	int       channels;
257 	guchar   *pixels;
258 	int       search, i, j, ii, jj;
259 	int       ad_blue, ad_green;
260 	int       rtop, rbot, rleft, rright; /* edges of region */
261 
262 	width = gdk_pixbuf_get_width (pixbuf);
263 	height = gdk_pixbuf_get_height (pixbuf);
264 	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
265 	channels = gdk_pixbuf_get_n_channels (pixbuf);
266 	pixels = gdk_pixbuf_get_pixels (pixbuf);
267 
268 	/*
269    	 * if isred is 0, we don't think the point is red, 1 means red, 2 means
270    	 * part of our region already.
271    	 */
272 
273 	for (search = 0; ! region_fixed && (search < REGION_SEARCH_SIZE); search++)
274     		for (i = MAX (0, y - search); ! region_fixed && (i <= MIN (height - 1, y + search)); i++ )
275       			for (j = MAX (0, x - search); ! region_fixed && (j <= MIN (width - 1, x + search)); j++) {
276 				if (isred[j + i * width] == 0)
277 					continue;
278 
279 				isred[j + i * width] = 2;
280 
281 				find_region (i, j, &rtop, &rbot, &rleft, &rright, isred, width, height);
282 
283 				/* Fix the region. */
284 				for (ii = rtop; ii <= rbot; ii++)
285 					for (jj = rleft; jj <= rright; jj++)
286 						if (isred[jj + ii * width] == 2) { /* Fix the pixel. */
287 							int ofs;
288 
289 							ofs = channels*jj + ii*rowstride;
290 							/*ad_red = pixels[ofs] * RED_FACTOR;*/
291 							ad_green = pixels[ofs + 1] * GREEN_FACTOR;
292 							ad_blue = pixels[ofs + 2] * BLUE_FACTOR;
293 
294 							pixels[ofs] = ((float) (ad_green + ad_blue)) / (2.0 * RED_FACTOR);
295 
296 							isred[jj + ii * width] = 0;
297 						}
298 
299 				region_fixed = TRUE;
300       			}
301 
302 	return region_fixed;
303 }
304 
305 
306 static void
selector_selected_cb(GthImageSelector * selector,int x,int y,GthFileToolRedEye * self)307 selector_selected_cb (GthImageSelector  *selector,
308 		      int                x,
309 		      int                y,
310 		      GthFileToolRedEye *self)
311 {
312 	GthViewerPage *viewer_page;
313 
314 	viewer_page = gth_image_viewer_page_tool_get_page (GTH_IMAGE_VIEWER_PAGE_TOOL (self));
315 
316 	_g_object_unref (self->priv->new_pixbuf);
317 	self->priv->new_pixbuf = gth_image_viewer_page_get_pixbuf (GTH_IMAGE_VIEWER_PAGE (viewer_page));
318 	init_is_red (self, self->priv->new_pixbuf);
319 	if (fix_redeye (self->priv->new_pixbuf, self->priv->is_red, x, y))
320 		gth_image_viewer_page_set_pixbuf (GTH_IMAGE_VIEWER_PAGE (viewer_page), self->priv->new_pixbuf, FALSE);
321 }
322 
323 
324 static void
selector_motion_notify_cb(GthImageSelector * selector,int x,int y,GthFileToolRedEye * self)325 selector_motion_notify_cb (GthImageSelector  *selector,
326 		           int                x,
327 		           int                y,
328 		           GthFileToolRedEye *self)
329 {
330 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (GET_WIDGET ("x_spinbutton")), (double) x);
331 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (GET_WIDGET ("y_spinbutton")), (double) y);
332 }
333 
334 
335 static GtkWidget *
gth_file_tool_red_eye_get_options(GthFileTool * base)336 gth_file_tool_red_eye_get_options (GthFileTool *base)
337 {
338 	GthFileToolRedEye *self;
339 	GtkWidget         *window;
340 	GthViewerPage     *viewer_page;
341 	GtkWidget         *viewer;
342 	GtkWidget         *options;
343 
344 	self = (GthFileToolRedEye *) base;
345 
346 	window = gth_file_tool_get_window (base);
347 	viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
348 	if (! GTH_IS_IMAGE_VIEWER_PAGE (viewer_page))
349 		return NULL;
350 
351 	self->priv->builder = _gtk_builder_new_from_file ("red-eye-removal-options.ui", "red_eye_removal");
352 	options = _gtk_builder_get_widget (self->priv->builder, "options");
353 	gtk_widget_show (options);
354 
355 	viewer = gth_image_viewer_page_get_image_viewer (GTH_IMAGE_VIEWER_PAGE (viewer_page));
356 	self->priv->original_zoom_change = gth_image_viewer_get_zoom_change (GTH_IMAGE_VIEWER (viewer));
357 	gth_image_viewer_set_zoom_change (GTH_IMAGE_VIEWER (viewer), GTH_ZOOM_CHANGE_KEEP_PREV);
358 	self->priv->selector = (GthImageSelector *) gth_image_selector_new (GTH_SELECTOR_TYPE_POINT);
359 	gth_image_selector_set_mask_visible (self->priv->selector, FALSE);
360 	g_signal_connect (self->priv->selector,
361 			  "selected",
362 			  G_CALLBACK (selector_selected_cb),
363 			  self);
364 	g_signal_connect (self->priv->selector,
365 			  "motion_notify",
366 			  G_CALLBACK (selector_motion_notify_cb),
367 			  self);
368 	gth_image_viewer_set_tool (GTH_IMAGE_VIEWER (viewer), (GthImageViewerTool *) self->priv->selector);
369 
370 	return options;
371 }
372 
373 
374 static void
gth_file_tool_red_eye_destroy_options(GthFileTool * base)375 gth_file_tool_red_eye_destroy_options (GthFileTool *base)
376 {
377 	GthFileToolRedEye *self;
378 	GtkWidget         *window;
379 	GthViewerPage     *viewer_page;
380 
381 	self = (GthFileToolRedEye *) base;
382 
383 	window = gth_file_tool_get_window (GTH_FILE_TOOL (self));
384 	viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
385 	gth_image_viewer_page_reset_viewer_tool (GTH_IMAGE_VIEWER_PAGE (viewer_page));
386 
387 	_g_object_unref (self->priv->builder);
388 	_g_object_unref (self->priv->selector);
389 	g_free (self->priv->is_red);
390 	self->priv->builder = NULL;
391 	self->priv->selector = NULL;
392 	self->priv->is_red = NULL;
393 }
394 
395 
396 static void
gth_file_tool_red_eye_apply_options(GthFileTool * base)397 gth_file_tool_red_eye_apply_options (GthFileTool *base)
398 {
399 	GthFileToolRedEye *self;
400 	GthViewerPage     *viewer_page;
401 	GtkWidget         *viewer;
402 
403 	self = (GthFileToolRedEye *) base;
404 
405 	if (self->priv->new_pixbuf == NULL)
406 		return;
407 
408 	viewer_page = gth_image_viewer_page_tool_get_page (GTH_IMAGE_VIEWER_PAGE_TOOL (self));
409 	viewer = gth_image_viewer_page_get_image_viewer (GTH_IMAGE_VIEWER_PAGE (viewer_page));
410 
411 	gth_image_viewer_set_zoom_change (GTH_IMAGE_VIEWER (viewer), self->priv->original_zoom_change);
412 	gth_image_viewer_page_set_pixbuf (GTH_IMAGE_VIEWER_PAGE (viewer_page), self->priv->new_pixbuf, TRUE);
413 	gth_file_tool_hide_options (GTH_FILE_TOOL (self));
414 }
415 
416 
417 static void
gth_file_tool_red_eye_finalize(GObject * object)418 gth_file_tool_red_eye_finalize (GObject *object)
419 {
420 	GthFileToolRedEye *self;
421 
422 	g_return_if_fail (object != NULL);
423 	g_return_if_fail (GTH_IS_FILE_TOOL_RED_EYE (object));
424 
425 	self = (GthFileToolRedEye *) object;
426 
427 	_g_object_unref (self->priv->new_pixbuf);
428 	g_free (self->priv->is_red);
429 	_g_object_unref (self->priv->selector);
430 	_g_object_unref (self->priv->builder);
431 
432 	/* Chain up */
433 	G_OBJECT_CLASS (gth_file_tool_red_eye_parent_class)->finalize (object);
434 }
435 
436 
437 static void
gth_file_tool_red_eye_reset_image(GthImageViewerPageTool * self)438 gth_file_tool_red_eye_reset_image (GthImageViewerPageTool *self)
439 {
440 	gth_image_viewer_page_reset (GTH_IMAGE_VIEWER_PAGE (gth_image_viewer_page_tool_get_page (GTH_IMAGE_VIEWER_PAGE_TOOL (self))));
441 	gth_file_tool_hide_options (GTH_FILE_TOOL (self));
442 }
443 
444 
445 static void
gth_file_tool_red_eye_class_init(GthFileToolRedEyeClass * klass)446 gth_file_tool_red_eye_class_init (GthFileToolRedEyeClass *klass)
447 {
448 	GObjectClass		    *gobject_class;
449 	GthFileToolClass	    *file_tool_class;
450 	GthImageViewerPageToolClass *image_viewer_page_tool_class;
451 
452 	gobject_class = (GObjectClass*) klass;
453 	gobject_class->finalize = gth_file_tool_red_eye_finalize;
454 
455 	file_tool_class = (GthFileToolClass *) klass;
456 	file_tool_class->get_options = gth_file_tool_red_eye_get_options;
457 	file_tool_class->destroy_options = gth_file_tool_red_eye_destroy_options;
458 	file_tool_class->apply_options = gth_file_tool_red_eye_apply_options;
459 
460 	image_viewer_page_tool_class = (GthImageViewerPageToolClass *) klass;
461 	image_viewer_page_tool_class->reset_image = gth_file_tool_red_eye_reset_image;
462 }
463 
464 
465 static void
gth_file_tool_red_eye_init(GthFileToolRedEye * self)466 gth_file_tool_red_eye_init (GthFileToolRedEye *self)
467 {
468 	self->priv = gth_file_tool_red_eye_get_instance_private (self);
469 	self->priv->new_pixbuf = NULL;
470 	self->priv->is_red = NULL;
471 	gth_file_tool_construct (GTH_FILE_TOOL (self), "image-red-eye-symbolic", _("Red Eye Removal"), GTH_TOOLBOX_SECTION_COLORS);
472 	gtk_widget_set_tooltip_text (GTK_WIDGET (self), _("Remove the red eye effect caused by camera flashes"));
473 }
474