#include #include #include #include #include #include /* Only available as of GTK 3.4 */ #ifndef GDK_BUTTON_PRIMARY #define GDK_BUTTON_PRIMARY 1 #endif struct roi { GtkWidget *window; GtkWidget *drawing_area; GdkPixbuf *input, *modified_input; gboolean doing_rubberband; struct { double x1, y1, x2, y2; } rubberband; }; static inline void calc_rubberband_rect(struct roi *roi, double *x, double *y, double *width, double *height) { *x = MIN(roi->rubberband.x1, roi->rubberband.x2); *y = MIN(roi->rubberband.y1, roi->rubberband.y2); *width = ABS(roi->rubberband.x1 - roi->rubberband.x2); *height = ABS(roi->rubberband.y1 - roi->rubberband.y2); } static gboolean do_roi(struct roi *roi) { GdkPixbuf *match; cairo_rectangle_t area; gint x, y, i; gint width, height; gint stride, nch; guchar *pix, *row; gint s; gint middle[3] = { 0, 0, 0 }; gint sigma[3] = { 0, 0, 0 }; struct match_interval { gint min, max; } interval[3]; calc_rubberband_rect(roi, &area.x, &area.y, &area.width, &area.height); if (area.width < 1 || area.height < 1) return FALSE; match = gdk_pixbuf_new_subpixbuf(roi->input, area.x, area.y, area.width, area.height); nch = gdk_pixbuf_get_n_channels(match); stride = gdk_pixbuf_get_rowstride(match); s = area.width * area.height; for (y = 0, row = gdk_pixbuf_get_pixels(match); y < area.height; ++y, row += stride) { for (x = 0, pix = row; x < area.width; ++x, pix += nch) { for (i = 0; i < 3; ++i) middle[i] += pix[i]; } } for (i = 0; i < 3; ++i) middle[i] /= s; for (y = 0, row = gdk_pixbuf_get_pixels(match); y < area.height; ++y, row += stride) { for (x = 0, pix = row; x < area.width; ++x, pix += nch) { for (i = 0; i < 3; ++i) sigma[i] += (pix[i] - middle[i]) * (pix[i] - middle[i]); } } for (i = 0; i < 3; ++i) sigma[i] = sqrt(sigma[i] / (s - 1)); printf("middle: %3d, %3d, %3d\n", middle[0], middle[1], middle[2]); printf("sigma: %3d, %3d, %3d\n", sigma[0], sigma[1], sigma[2]); for (i = 0; i < 3; ++i) { interval[i].min = middle[i] - 3 * sigma[i]; interval[i].max = middle[i] + 3 * sigma[i]; printf("interval %d: [%3d - %3d]\n", i, interval[i].min, interval[i].max); } nch = gdk_pixbuf_get_n_channels(roi->input); stride = gdk_pixbuf_get_rowstride(roi->input); width = gdk_pixbuf_get_width(roi->input); height = gdk_pixbuf_get_height(roi->input); if (roi->modified_input) g_object_unref(roi->modified_input); roi->modified_input = gdk_pixbuf_copy(roi->input); for (y = 0, row = gdk_pixbuf_get_pixels(roi->modified_input); y < height; ++y, row += stride) { for (x = 0, pix = row; x < width; ++x, pix += nch) { int H = 1; for (i = 0; i < 3; ++i) { if (!(pix[i] >= interval[i].min && pix[i] <= interval[i].max)) H = 0; } if (H) pix[0] = pix[1] = pix[2] = 0; } } g_object_unref(match); return TRUE; } static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event, gpointer userdata) { struct roi *roi = userdata; if (roi->doing_rubberband) { roi->rubberband.x2 = event->x; roi->rubberband.y2 = event->y; gtk_widget_queue_draw(roi->drawing_area); } return TRUE; } static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer userdata) { struct roi *roi = userdata; switch (event->button) { case GDK_BUTTON_PRIMARY: roi->rubberband.x1 = event->x; roi->rubberband.x2 = event->x; roi->rubberband.y1 = event->y; roi->rubberband.y2 = event->y; roi->doing_rubberband = TRUE; return FALSE; default: return TRUE; } } static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event, gpointer userdata) { struct roi *roi = userdata; if (event->button != GDK_BUTTON_PRIMARY) return TRUE; roi->doing_rubberband = FALSE; if (do_roi(roi)) gtk_widget_queue_draw(roi->drawing_area); return FALSE; } static gboolean key_event(GtkWidget *widget, GdkEventKey *event, gpointer userdata) { struct roi *roi = userdata; switch (event->type) { case GDK_KEY_RELEASE: switch (event->keyval) { case 'd': g_object_unref(roi->modified_input); roi->modified_input = NULL; gtk_widget_queue_draw(roi->drawing_area); break; case 'q': gtk_main_quit(); break; } return FALSE; default: return TRUE; } } static void draw_rubberband(struct roi *roi, GtkWidget *widget, cairo_t *cr) { GtkStyleContext *context; double x, y, width, height; context = gtk_widget_get_style_context(widget); gtk_style_context_save(context); gtk_style_context_add_class(context, GTK_STYLE_CLASS_RUBBERBAND); calc_rubberband_rect(roi, &x, &y, &width, &height); gtk_render_background(context, cr, x, y, width, height); gtk_render_frame(context, cr, x, y, width, height); gtk_style_context_restore(context); } static gboolean draw_cb(GtkWidget *widget, cairo_t *cr, gpointer userdata) { struct roi *roi = userdata; gdk_cairo_set_source_pixbuf(cr, roi->modified_input ? roi->modified_input : roi->input, 0, 0); cairo_paint(cr); if (roi->doing_rubberband) draw_rubberband(roi, widget, cr); return FALSE; } int main(int argc, char *argv[]) { struct roi roi; memset(&roi, 0, sizeof roi); gtk_init(&argc, &argv); if (argc < 2) exit(EXIT_FAILURE); roi.input = gdk_pixbuf_new_from_file(argv[1], NULL); if (!roi.input) exit(EXIT_FAILURE); roi.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); roi.drawing_area = gtk_drawing_area_new(); gtk_widget_set_size_request(roi.drawing_area, gdk_pixbuf_get_width(roi.input), gdk_pixbuf_get_height(roi.input)); gtk_container_add(GTK_CONTAINER(roi.window), roi.drawing_area); g_signal_connect(roi.window, "destroy", G_CALLBACK(gtk_main_quit), NULL); g_signal_connect(roi.window, "key-release-event", G_CALLBACK(key_event), &roi); gtk_widget_set_events(roi.drawing_area, GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); g_signal_connect(roi.drawing_area, "draw", G_CALLBACK(draw_cb), &roi); g_signal_connect(roi.drawing_area, "button-press-event", G_CALLBACK(button_press_event), &roi); g_signal_connect(roi.drawing_area, "button-release-event", G_CALLBACK(button_release_event), &roi); g_signal_connect(roi.drawing_area, "motion-notify-event", G_CALLBACK(motion_notify_event), &roi); gtk_widget_show_all(roi.window); gtk_main(); g_object_unref(roi.input); if (roi.modified_input) g_object_unref(roi.modified_input); return 0; }