#include #include #include typedef struct { double x, y; } point; #define ROWS 7 #define COLUMNS 16 static GtkWidget *img, *ent; static GdkPixbuf *orig_pb; static GdkPixbuf *scaled_pb; static double sx, sy; static GdkGC *invgc; static point corner[4]; static double fhoriz[COLUMNS - 1]; static double fvert[ROWS - 1]; static int dragmode, dragpt, dragupdate; static void pointweight(point* dest, const point* a, const point* b, double f) { dest->x = (a->x * (1.0 - f)) + (b->x * f); dest->y = (a->y * (1.0 - f)) + (b->y * f); } static double dist2(const point* a, const point* b) { return ((a->x - b->x) * (a->x - b->x) + (a->y - b->y) * (a->y - b->y)); } static gboolean paint_img(GtkWidget* w, G_GNUC_UNUSED GdkEventExpose* ev, G_GNUC_UNUSED gpointer data) { int i; point a, b; if (!scaled_pb || w->allocation.width != gdk_pixbuf_get_width(scaled_pb) || w->allocation.height != gdk_pixbuf_get_height(scaled_pb)) { if (scaled_pb) g_object_unref(scaled_pb); sx = (double) w->allocation.width / gdk_pixbuf_get_width(orig_pb); sy = (double) w->allocation.height / gdk_pixbuf_get_height(orig_pb); scaled_pb = gdk_pixbuf_scale_simple(orig_pb, w->allocation.width, w->allocation.height, GDK_INTERP_BILINEAR); } gdk_draw_pixbuf(GDK_DRAWABLE(w->window), NULL, scaled_pb, 0, 0, 0, 0, -1, -1, GDK_RGB_DITHER_NORMAL, 0, 0); if (!invgc) { invgc = gdk_gc_new(GDK_DRAWABLE(w->window)); gdk_gc_set_function(invgc, GDK_INVERT); } gdk_draw_line(GDK_DRAWABLE(w->window), invgc, corner[0].x * sx, corner[0].y * sy, corner[1].x * sx, corner[1].y * sy); gdk_draw_line(GDK_DRAWABLE(w->window), invgc, corner[1].x * sx, corner[1].y * sy, corner[3].x * sx, corner[3].y * sy); gdk_draw_line(GDK_DRAWABLE(w->window), invgc, corner[3].x * sx, corner[3].y * sy, corner[2].x * sx, corner[2].y * sy); gdk_draw_line(GDK_DRAWABLE(w->window), invgc, corner[2].x * sx, corner[2].y * sy, corner[0].x * sx, corner[0].y * sy); for (i = 0; i < COLUMNS - 1; i++) { pointweight(&a, &corner[0], &corner[1], fhoriz[i]); pointweight(&b, &corner[2], &corner[3], fhoriz[i]); gdk_draw_line(GDK_DRAWABLE(w->window), invgc, a.x * sx, a.y * sy, b.x * sx, b.y * sy); } for (i = 0; i < ROWS - 1; i++) { pointweight(&a, &corner[0], &corner[2], fvert[i]); pointweight(&b, &corner[1], &corner[3], fvert[i]); gdk_draw_line(GDK_DRAWABLE(w->window), invgc, a.x * sx, a.y * sy, b.x * sx, b.y * sy); } return TRUE; } static gboolean button_press(G_GNUC_UNUSED GtkWidget* w, GdkEventButton* ev, G_GNUC_UNUSED gpointer data) { int i; double dist, bestdist; point mp, p; mp.x = ev->x / sx; mp.y = ev->y / sy; dragmode = 1; dragpt = 0; bestdist = dist2(&mp, &corner[0]); for (i = 1; i < 4; i++) { dist = dist2(&mp, &corner[i]); if (dist < bestdist) { bestdist = dist; dragpt = i; } } for (i = 0; i < COLUMNS - 1; i++) { pointweight(&p, &corner[0], &corner[1], fhoriz[i]); dist = dist2(&mp, &p); if (dist < bestdist) { bestdist = dist; dragmode = 2; dragpt = i; } pointweight(&p, &corner[2], &corner[3], fhoriz[i]); dist = dist2(&mp, &p); if (dist < bestdist) { bestdist = dist; dragmode = 2; dragpt = i; } } for (i = 0; i < ROWS - 1; i++) { pointweight(&p, &corner[0], &corner[2], fvert[i]); dist = dist2(&mp, &p); if (dist < bestdist) { bestdist = dist; dragmode = 3; dragpt = i; } pointweight(&p, &corner[1], &corner[3], fvert[i]); dist = dist2(&mp, &p); if (dist < bestdist) { bestdist = dist; dragmode = 3; dragpt = i; } } return TRUE; } static void formatparams(char* buf, int size) { int i, n; for (i = 0; i < 4; i++) { n = g_snprintf(buf, size, "%.4f %.4f ", corner[i].x, corner[i].y); if (n < size) { buf += n; size -= n; } else return; } for (i = 0; i < COLUMNS - 1; i++) { n = g_snprintf(buf, size, " %.4f", fhoriz[i]); if (n < size) { buf += n; size -= n; } else return; } for (i = 0; i < ROWS - 1; i++) { n = g_snprintf(buf, size, " %.4f", fvert[i]); if (n < size) { buf += n; size -= n; } else return; } } static gboolean pointer_moved(G_GNUC_UNUSED GtkWidget* w, GdkEventMotion* ev, G_GNUC_UNUSED gpointer data) { char buf[1024]; switch (dragmode) { case 1: corner[dragpt].x = ev->x / sx; corner[dragpt].y = ev->y / sy; break; case 2: fhoriz[dragpt] = (ev->x / sx - corner[0].x) / (corner[1].x - corner[0].x); break; case 3: fvert[dragpt] = (ev->y / sy - corner[0].y) / (corner[2].y - corner[0].y); break; } formatparams(buf, sizeof(buf)); dragupdate = 1; gtk_entry_set_text(GTK_ENTRY(ent), buf); dragupdate = 0; gtk_widget_queue_draw(img); gdk_event_request_motions(ev); return TRUE; } static void ent_changed(GtkEntry* e, G_GNUC_UNUSED gpointer data) { char *text, *p; int i; if (dragupdate) return; p = text = g_strdup(gtk_entry_get_text(e)); for (i = 0; i < 4; i++) { if (p && *g_strchug(p)) corner[i].x = strtod(p, &p); if (p && *g_strchug(p)) corner[i].y = strtod(p, &p); } for (i = 0; i < COLUMNS - 1; i++) { if (p && *g_strchug(p)) fhoriz[i] = strtod(p, &p); } for (i = 0; i < ROWS - 1; i++) { if (p && *g_strchug(p)) fvert[i] = strtod(p, &p); } if (img) gtk_widget_queue_draw(img); g_free(text); } int main(int argc, char** argv) { GtkWidget *win, *vbox; GError* err = NULL; gtk_init(&argc, &argv); if (argc != 2) { fprintf(stderr, "Usage: %s [gtk-options] image-file\n", argv[0]); return 1; } orig_pb = gdk_pixbuf_new_from_file(argv[1], &err); if (err) { fprintf(stderr, "%s\n", err->message); return 1; } win = gtk_window_new(GTK_WINDOW_TOPLEVEL); { vbox = gtk_vbox_new(FALSE, 0); { ent = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(ent), "10 10 200 10 10 200 200 200 " " 0.0625 0.1250 0.1875 0.2500 0.3125" " 0.3750 0.4375 0.5000 0.5625 0.6250" " 0.6875 0.7500 0.8125 0.8750 0.9375" " 0.1429 0.2857 0.4286 0.5714 0.7143 0.8571"); g_signal_connect(ent, "changed", G_CALLBACK(ent_changed), NULL); gtk_widget_show(ent); gtk_box_pack_start(GTK_BOX(vbox), ent, FALSE, FALSE, 0); img = gtk_drawing_area_new(); gtk_widget_set_size_request(img, gdk_pixbuf_get_width(orig_pb), gdk_pixbuf_get_height(orig_pb)); gtk_widget_add_events(img, (GDK_BUTTON_PRESS_MASK | GDK_BUTTON1_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK)); g_signal_connect(img, "expose-event", G_CALLBACK(paint_img), NULL); g_signal_connect(img, "button-press-event", G_CALLBACK(button_press), NULL); g_signal_connect(img, "motion-notify-event", G_CALLBACK(pointer_moved), NULL); gtk_widget_show(img); gtk_box_pack_start(GTK_BOX(vbox), img, TRUE, TRUE, 0); } gtk_widget_show(vbox); gtk_container_add(GTK_CONTAINER(win), vbox); } g_signal_connect(win, "destroy", G_CALLBACK(gtk_main_quit), NULL); ent_changed(GTK_ENTRY(ent), NULL); gtk_widget_show(win); gtk_main(); return 0; }