Commit 58db43a1 authored by Paolo Bacchilega's avatar Paolo Bacchilega
Browse files

added "reg eye removal" tool

parent ab563738
......@@ -274,6 +274,9 @@ extensions/list_tools/data/ui/Makefile
extensions/photo_importer/Makefile
extensions/photo_importer/data/Makefile
extensions/photo_importer/data/ui/Makefile
extensions/red_eye_removal/Makefile
extensions/red_eye_removal/data/Makefile
extensions/red_eye_removal/data/ui/Makefile
extensions/rename_series/Makefile
extensions/rename_series/data/Makefile
extensions/rename_series/data/ui/Makefile
......
......@@ -9,6 +9,7 @@ SUBDIRS = \
image_viewer \
list_tools \
photo_importer \
red_eye_removal \
rename_series \
search \
slideshow
......
......@@ -165,13 +165,18 @@ gth_comment_real_load_from_element (DomDomizable *base,
else if (g_strcmp0 (node->tag_name, "Time") == 0)
gth_comment_set_time_from_time_t (self, atol (dom_element_get_inner_text (node)));
else if (g_strcmp0 (node->tag_name, "Keywords") == 0) {
char **categories;
int i;
categories = g_strsplit (dom_element_get_inner_text (node), ",", -1);
for (i = 0; categories[i] != NULL; i++)
gth_comment_add_category (self, categories[i]);
g_strfreev (categories);
const char *text;
text = dom_element_get_inner_text (node);
if (text != NULL) {
char **categories;
int i;
categories = g_strsplit (text, ",", -1);
for (i = 0; categories[i] != NULL; i++)
gth_comment_add_category (self, categories[i]);
g_strfreev (categories);
}
}
}
}
......
SUBDIRS = data
extensiondir = $(libdir)/gthumb-2.0/extensions
extension_LTLIBRARIES = libred_eye_removal.la
libred_eye_removal_la_SOURCES = \
gth-file-tool-red-eye.c \
gth-file-tool-red-eye.h \
main.c
libred_eye_removal_la_CFLAGS = $(GTHUMB_CFLAGS) $(DISABLE_DEPRECATED) $(WARNINGS) -I$(top_srcdir) -I$(top_builddir)/gthumb
libred_eye_removal_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
libred_eye_removal_la_LIBADD = $(GTHUMB_LIBS)
libred_eye_removal_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
extensioninidir = $(extensiondir)
extensionini_in_files = red_eye_removal.extension.in.in
extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
$< > $@
%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
EXTRA_DIST = $(extensionini_in_files)
DISTCLEANFILES = $(extensionini_DATA)
-include $(top_srcdir)/git.mk
SUBDIRS = ui
-include $(top_srcdir)/git.mk
uidir = $(datadir)/gthumb-2.0/ui
ui_DATA = red-eye-removal-options.ui
EXTRA_DIST = $(ui_DATA)
-include $(top_srcdir)/git.mk
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkAlignment" id="options">
<property name="visible">True</property>
<property name="top_padding">6</property>
<child>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkFrame" id="frame4">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkAlignment" id="alignment12">
<property name="visible">True</property>
<property name="top_padding">6</property>
<property name="left_padding">12</property>
<child>
<object class="GtkVBox" id="vbox10">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<property name="column_spacing">6</property>
<property name="row_spacing">6</property>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">_X:</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">_Y:</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox5">
<property name="visible">True</property>
<child>
<object class="GtkSpinButton" id="x_spinbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<property name="adjustment">x_adjustment</property>
<property name="climb_rate">1</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox6">
<property name="visible">True</property>
<child>
<object class="GtkSpinButton" id="y_spinbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<property name="adjustment">y_adjustment</property>
<property name="climb_rate">1</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="label15">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Selection&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="padding">6</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="spacing">12</property>
<property name="layout_style">center</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">6</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkAdjustment" id="x_adjustment">
<property name="upper">10000</property>
<property name="step_increment">1</property>
</object>
<object class="GtkAdjustment" id="y_adjustment">
<property name="upper">10000</property>
<property name="step_increment">1</property>
</object>
</interface>
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* GThumb
*
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
*/
#include <config.h>
#include <gthumb.h>
#include <extensions/image_viewer/gth-image-viewer-page.h>
#include "gth-file-tool-red-eye.h"
#define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
#define REGION_SEARCH_SIZE 3
static const double RED_FACTOR = 0.5133333;
static const double GREEN_FACTOR = 1.0;
static const double BLUE_FACTOR = 0.1933333;
static gpointer parent_class = NULL;
struct _GthFileToolRedEyePrivate {
GdkPixbuf *src_pixbuf;
GtkBuilder *builder;
GthImageSelector *selector;
char *is_red;
};
static void
gth_file_tool_red_eye_update_sensitivity (GthFileTool *base)
{
GtkWidget *window;
GtkWidget *viewer_page;
window = gth_file_tool_get_window (base);
viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
if (! GTH_IS_IMAGE_VIEWER_PAGE (viewer_page))
gtk_widget_set_sensitive (GTK_WIDGET (base), FALSE);
else
gtk_widget_set_sensitive (GTK_WIDGET (base), TRUE);
}
static void
cancel_button_clicked_cb (GtkButton *button,
GthFileToolRedEye *self)
{
gth_file_tool_hide_options (GTH_FILE_TOOL (self));
}
static int
find_region (int row,
int col,
int *rtop,
int *rbot,
int *rleft,
int *rright,
char *isred,
int width,
int height)
{
int *rows, *cols, list_length = 0;
int mydir;
int total = 1;
/* a relatively efficient way to find all connected points in a
* region. It considers points that have isred == 1, sets them to 2
* if they are connected to the starting point.
* row and col are the starting point of the region,
* the next four params define a rectangle our region fits into.
* isred is an array that tells us if pixels are red or not.
*/
*rtop = row;
*rbot = row;
*rleft = col;
*rright = col;
rows = g_malloc (width * height * sizeof(int));
cols = g_malloc (width * height * sizeof(int));
rows[0] = row;
cols[0] = col;
list_length = 1;
do {
list_length -= 1;
row = rows[list_length];
col = cols[list_length];
for (mydir = 0; mydir < 8 ; mydir++) {
switch (mydir) {
case 0:
/* going left */
if (col - 1 < 0) break;
if (isred[col-1+row*width] == 1) {
isred[col-1+row*width] = 2;
if (*rleft > col-1) *rleft = col-1;
rows[list_length] = row;
cols[list_length] = col-1;
list_length+=1;
total += 1;
}
break;
case 1:
/* up and left */
if (col - 1 < 0 || row -1 < 0 ) break;
if (isred[col-1+(row-1)*width] == 1 ) {
isred[col-1+(row-1)*width] = 2;
if (*rleft > col -1) *rleft = col-1;
if (*rtop > row -1) *rtop = row-1;
rows[list_length] = row-1;
cols[list_length] = col-1;
list_length += 1;
total += 1;
}
break;
case 2:
/* up */
if (row -1 < 0 ) break;
if (isred[col + (row-1)*width] == 1) {
isred[col + (row-1)*width] = 2;
if (*rtop > row-1) *rtop=row-1;
rows[list_length] = row-1;
cols[list_length] = col;
list_length +=1;
total += 1;
}
break;
case 3:
/* up and right */
if (col + 1 >= width || row -1 < 0 ) break;
if (isred[col+1+(row-1)*width] == 1) {
isred[col+1+(row-1)*width] = 2;
if (*rright < col +1) *rright = col+1;
if (*rtop > row -1) *rtop = row-1;
rows[list_length] = row-1;
cols[list_length] = col+1;
list_length += 1;
total +=1;
}
break;
case 4:
/* going right */
if (col + 1 >= width) break;
if (isred[col+1+row*width] == 1) {
isred[col+1+row*width] = 2;
if (*rright < col+1) *rright = col+1;
rows[list_length] = row;
cols[list_length] = col+1;
list_length += 1;
total += 1;
}
break;
case 5:
/* down and right */
if (col + 1 >= width || row +1 >= height ) break;
if (isred[col+1+(row+1)*width] ==1) {
isred[col+1+(row+1)*width] = 2;
if (*rright < col +1) *rright = col+1;
if (*rbot < row +1) *rbot = row+1;
rows[list_length] = row+1;
cols[list_length] = col+1;
list_length += 1;
total += 1;
}
break;
case 6:
/* down */
if (row +1 >= height ) break;
if (isred[col + (row+1)*width] == 1) {
isred[col + (row+1)*width] = 2;
if (*rbot < row+1) *rbot=row+1;
rows[list_length] = row+1;
cols[list_length] = col;
list_length += 1;
total += 1;
}
break;
case 7:
/* down and left */
if (col - 1 < 0 || row +1 >= height ) break;
if (isred[col-1+(row+1)*width] == 1) {
isred[col-1+(row+1)*width] = 2;
if (*rleft > col -1) *rleft = col-1;
if (*rbot < row +1) *rbot = row+1;
rows[list_length] = row+1;
cols[list_length] = col-1;
list_length += 1;
total += 1;
}
break;
default:
break;
}
}
}
while (list_length > 0); /* stop when we add no more */
g_free (rows);
g_free (cols);
return total;
}
static void
init_is_red (GthFileToolRedEye *self,
GdkPixbuf *pixbuf)
{
int width, height;
int rowstride, channels;
guchar *pixels;
int i, j;
int ad_red, ad_green, ad_blue;
const int THRESHOLD = 0;
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
rowstride = gdk_pixbuf_get_rowstride (pixbuf);
channels = gdk_pixbuf_get_n_channels (pixbuf);
pixels = gdk_pixbuf_get_pixels(pixbuf);
g_free (self->priv->is_red);
self->priv->is_red = g_new0 (char, width * height);
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
int ofs = channels * j + i * rowstride;
ad_red = pixels[ofs] * RED_FACTOR;
ad_green = pixels[ofs + 1] * GREEN_FACTOR;
ad_blue = pixels[ofs + 2] * BLUE_FACTOR;
// This test from the gimp redeye plugin.
if ((ad_red >= ad_green - THRESHOLD) && (ad_red >= ad_blue - THRESHOLD))
self->priv->is_red[j + i * width] = 1;
}
}
}
/* returns TRUE if the pixbuf has been modified */
static gboolean
fix_redeye (GdkPixbuf *pixbuf,
char *isred,
int x,
int y)
{
gboolean region_fixed = FALSE;
int width;
int height;
int rowstride;
int channels;
guchar *pixels;
int search, i, j, ii, jj;
int ad_red, ad_blue, ad_green;
int rtop, rbot, rleft, rright; /* edges of region */
int num_pix;
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
rowstride = gdk_pixbuf_get_rowstride (pixbuf);
channels = gdk_pixbuf_get_n_channels (pixbuf);
pixels = gdk_pixbuf_get_pixels (pixbuf);
/*
* if isred is 0, we don't think the point is red, 1 means red, 2 means
* part of our region already.
*/
for (search = 0; ! region_fixed && (search < REGION_SEARCH_SIZE); search++)
for (i = MAX (0, y - search); ! region_fixed && (i <= MIN (height - 1, y + search)); i++ )
for (j = MAX (0, x - search); ! region_fixed && (j <= MIN (width - 1, x + search)); j++) {
if (isred[j + i * width] == 0)
continue;
isred[j + i * width] = 2;
num_pix = find_region (i, j, &rtop, &rbot, &rleft, &rright, isred, width, height);
/* Fix the region. */
for (ii = rtop; ii <= rbot; ii++)
for (jj = rleft; jj <= rright; jj++)
if (isred[jj + ii * width] == 2) { /* Fix the pixel. */
int ofs;
ofs = channels*jj + ii*rowstride;
ad_red = pixels[ofs] * RED_FACTOR;
ad_green = pixels[ofs + 1] * GREEN_FACTOR;
ad_blue = pixels[ofs + 2] * BLUE_FACTOR;