#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 *image, *marked_image; gboolean doing_rubberband; struct { double x1, y1, x2, y2; } rubberband; struct match_interval { gint min, max; } interval[3]; }; 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 inline void pixbuf_get(GdkPixbuf *pixbuf, guchar **pixels, gint *width, gint *height, gint *nch, gint *stride) { if (pixels) *pixels = gdk_pixbuf_get_pixels(pixbuf); if (width) *width = gdk_pixbuf_get_width(pixbuf); if (height) *height = gdk_pixbuf_get_height(pixbuf); if (nch) *nch = gdk_pixbuf_get_n_channels(pixbuf); if (stride) *stride = gdk_pixbuf_get_rowstride(pixbuf); } static void calc_roi_interval(struct roi *roi, GdkPixbuf *match) { gint i, s, x, y, width, height, nch, stride; guchar *p, *row; gint middle[3] = { 0, 0, 0 }; gint sigma[3] = { 0, 0, 0 }; pixbuf_get(match, &row, &width, &height, &nch, &stride); for (y = 0; y < height; ++y, row += stride) for (x = 0, p = row; x < width; ++x, p += nch) for (i = 0; i < 3; ++i) middle[i] += p[i]; s = width * height; for (i = 0; i < 3; ++i) middle[i] /= s; row = gdk_pixbuf_get_pixels(match); for (y = 0; y < height; ++y, row += stride) for (x = 0, p = row; x < width; ++x, p += nch) for (i = 0; i < 3; ++i) sigma[i] += ((p[i] - middle[i]) * (p[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) { roi->interval[i].min = middle[i] - 3 * sigma[i]; roi->interval[i].max = middle[i] + 3 * sigma[i]; printf("interval %d: [%3d - %3d]\n", i, roi->interval[i].min, roi->interval[i].max); } } static void mark_matching_pixels(struct roi *roi, GdkPixbuf *image) { gint i, x, y, width, height, nch, stride; guchar *p, *row; pixbuf_get(image, &row, &width, &height, &nch, &stride); for (y = 0; y < height; ++y, row += stride) { for (x = 0, p = row; x < width; ++x, p += nch) { gboolean H = TRUE; for (i = 0; i < 3 && H; ++i) { if (!(p[i] >= roi->interval[i].min && p[i] <= roi->interval[i].max)) H = FALSE; } if (H) p[0] = p[1] = p[2] = 0; } } } static gboolean do_roi(struct roi *roi) { GdkPixbuf *match; double x, y, width, height; calc_rubberband_rect(roi, &x, &y, &width, &height); if (width < 1 || height < 1) return FALSE; match = gdk_pixbuf_new_subpixbuf(roi->image, x, y, width, height); calc_roi_interval(roi, match); g_object_unref(match); g_object_unref(roi->marked_image); roi->marked_image = gdk_pixbuf_copy(roi->image); mark_matching_pixels(roi, roi->marked_image); 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 GDK_KEY_d: case GDK_KEY_D: g_object_unref(roi->marked_image); roi->marked_image = g_object_ref(roi->image); gtk_widget_queue_draw(roi->drawing_area); break; case GDK_KEY_q: case GDK_KEY_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->marked_image, 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.image = gdk_pixbuf_new_from_file(argv[1], NULL); if (!roi.image) exit(EXIT_FAILURE); roi.marked_image = g_object_ref(roi.image); 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.image), gdk_pixbuf_get_height(roi.image)); 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.image); g_object_unref(roi.marked_image); return 0; }