pandora-0.7.1/0000755000123200001440000000000010266110472012302 5ustar akkanauserspandora-0.7.1/Makefile0000644000123200001440000000451610266110472013750 0ustar akkanausers# Makefile for pandora panorama gimp plugin # This tries to build pandora for every version of GIMP installed. # Unfortunately GIMP doesn't have any standard way of doing that, # so Pandora has to probe for all these possible versions. # But starting with GIMP 2.2, it can't do that probe, because # all possible versions have the same name, gimptool-2.0, even # if they're really 2.2 or 2.3. See bug 310565; maybe this will # be solved eventually. Meanwhile, you can only install to whichever # GIMP comes first in your path. GIMP_1_2_DIR=${HOME}/.gimp-1.2/plug-ins GIMP_2_0_DIR=${HOME}/.gimp-2.0/plug-ins GIMP_2_2_DIR=${HOME}/.gimp-2.2/plug-ins GIMP_2_3_DIR=${HOME}/.gimp-2.3/plug-ins GIMP_2_4_DIR=${HOME}/.gimp-2.4/plug-ins GIMPTOOL_1_2=$(shell which gimptool-1.2) GIMPTOOL_2_0=$(shell which gimptool-2.0) GIMPTOOL_2_2=$(shell which gimptool-2.2) GIMPTOOL_2_3=$(shell which gimptool-2.3) # We don't need C99, but gcc 3.3 forces it by default, so we don't # see errors that might kill other people's builds. Force C89: export CFLAGS=-std=c89 # Most people will want make install, but this dummy target is for # people who don't want a makefile to install anything unless asked. build: @echo Type make install to build and install the plugin. @echo I will try to install it for every version of GIMP @echo you have installed. install: if test -x "${GIMPTOOL_2_0}"; then make install-2.0; fi if test -x "${GIMPTOOL_1_2}"; then make ${GIMP_1_2_DIR}/pandora_gen; fi # pandora-match is experimental and not very useful yet. # Most people probably shouldn't bother. debug-all: if test -x "${GIMPTOOL_1_2}"; then make debug-1.2; fi if test -x "${GIMPTOOL_2_3}"; then make debug-2.3; fi install-2.0: gimptool-2.0 --install-strip pandora_gen.c install-1.2: ${GIMP_1_2_DIR}/pandora_gen debug-2.3: ${GIMP_2_3_DIR}/pandora_gen ${GIMP_2_3_DIR}/pandora_match debug-1.2: ${GIMP_1_2_DIR}/pandora_gen ${GIMP_1_2_DIR}/pandora_match # pandora_gen stuff ${GIMP_1_2_DIR}/pandora_gen: pandora_gen.c mkdir -p ${GIMP_1_2_DIR} gimptool-1.2 --install-strip pandora_gen.c # pandora_match stuff ${GIMP_1_2_DIR}/pandora_match: pandora_match.c mkdir -p ${GIMP_1_2_DIR} gimptool-1.2 --install-strip pandora_match.c ${GIMP_2_0_DIR}/pandora_match: pandora_match.c mkdir -p ${GIMP_2_0_DIR} gimptool-2.0 --install-strip pandora_match.c clean: rm -f pandora_match pandora_gen *.o pandora-0.7.1/pandora.h0000644000123200001440000000221407761323764014116 0ustar akkanausers#ifndef PANDORA_H #define PANDORA_H 1 /* gimp plugin boilerplate, needed for any plugin with UI */ #ifdef IN_GIMP_TREE #include "config.h" #include "libgimp/stdplugins-intl.h" #else /* gimptool doesn't understand these: */ #define _(s) s #define N_(s) s #define INIT_I18N() #define INIT_I18N_UI() #endif #include #include #include #include #include #include #include /* Backwards compatibility defines for gimp 1.2: */ #ifdef G_N_ELEMENTS /* Gimp 1.3 */ #define GIMP_ENABLE_COMPAT_CRUFT #include #define MAYBE_CONST const #else /* It's 1.2 after all */ #define GIMP_1_2 1 #define GimpRunMode gint32 #define drawable_id id #define G_CALLBACK GTK_SIGNAL_FUNC #define G_OBJECT GTK_OBJECT #define g_signal_connect gtk_signal_connect #define gtk_check_button_new_with_mnemonic gtk_check_button_new_with_label #define GTK_STOCK_CANCEL _("Cancel") #define GTK_STOCK_OK _("Ok") #define G_N_ELEMENTS(a) ((sizeof a) / (sizeof (*(a)))) #define MAYBE_CONST #endif /* G_N_ELEMENTS, gimp 1.2 backward compatibility */ #endif /* PANDORA_H */ pandora-0.7.1/pandora_gen.c0000600000123200001440000005074010126043260014714 0ustar akkanausers/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; c-brace-offset: -2 -*- */ /* The GIMP -- an image manipulation program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * 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 Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This is a plugin intended to help with making panoramas. * Copyright (C) 2003 Akkana Peck. * * To build & install: gimptool --install-strip pandora_gen_gen.c */ #include "pandora.h" #include #include #define MAXIMAGES 50 #define FILELIST_WIDTH 400 #define FILELIST_HEIGHT 120 #define SCALE_WIDTH 60 #define SPIN_WIDTH 60 typedef struct { gint run; } Pandora_GenInterface; typedef struct { gdouble overlap; gboolean feather; gchar** files; } Pandora_GenVals; static Pandora_GenVals avals = { .35, /* initial overlap fraction */ TRUE, /* feather edges */ 0 /* file list */ }; /* Declare local functions. */ static void query (void); static void run (MAYBE_CONST gchar *name, gint nparams, MAYBE_CONST GimpParam *param, gint *nreturn_vals, GimpParam **return_vals); /* user interface functions */ static gint pandora_gen_dialog (void); static gint pandora_generate_panorama(); GimpPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run, /* run_proc */ }; MAIN () static void query (void) { static GimpParamDef args[] = { { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" }, { GIMP_PDB_INT32, "overlap", "Overlap" }, { GIMP_PDB_INT32, "feather", "Feather layers" }, { GIMP_PDB_STRINGARRAY, "files", "Input files" } }; static GimpParamDef return_vals[] = { { GIMP_PDB_INT32, "Status", "Success = 0" }, { GIMP_PDB_INT32, "Pano Img id", "The new image." } }; #ifdef IN_GIMP_TREE INIT_I18N(); #endif gimp_install_procedure ( #ifdef GIMP_1_2 "extension_pandora_gen", #else /* GIMP_1_3 */ "plugin_pandora_gen", #endif /* GIMP_1_x */ "Collect images together to make a panorama", "Help not yet written for this plug-in", "Akkana Peck", "Akkana Peck", "2003", N_("/Xtns/Make Panorama..."), "", #ifdef GIMP_1_2 GIMP_EXTENSION, #else /* GIMP_1_3 */ GIMP_PLUGIN, #endif /* GIMP_1_x */ G_N_ELEMENTS (args), G_N_ELEMENTS (return_vals), args, return_vals); } static void run (MAYBE_CONST gchar *name, gint nparams, MAYBE_CONST GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) { static GimpParam values[2]; GimpRunMode run_mode; GimpPDBStatusType status = GIMP_PDB_SUCCESS; run_mode = param[0].data.d_int32; *nreturn_vals = 2; *return_vals = values; values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = status; values[1].type = GIMP_PDB_INT32; values[1].data.d_image = -1; if (run_mode == GIMP_RUN_INTERACTIVE) { INIT_I18N_UI(); /* Possibly retrieve data */ gimp_get_data ("plug_in_pandora_gen", &avals); /* Pop up the dialog, which will then call pandora_generate_panorama */ (void)pandora_gen_dialog (); /* Store avals data for next invocation */ gimp_set_data ("plug_in_pandora_gen", &avals, sizeof (Pandora_GenVals)); } else if (run_mode == GIMP_RUN_NONINTERACTIVE) { INIT_I18N(); /* Make sure all the arguments are there! */ if (nparams != 3) status = GIMP_PDB_CALLING_ERROR; if (status == GIMP_PDB_SUCCESS) { avals.files = param[1].data.d_stringarray; avals.overlap = param[1].data.d_int32; avals.feather = param[1].data.d_int32; } values[1].data.d_image = pandora_generate_panorama (); } else if (run_mode == GIMP_RUN_WITH_LAST_VALS) { /* Possibly retrieve data */ gimp_get_data ("plug_in_pandora_gen", &avals); values[1].data.d_image = pandora_generate_panorama (); } values[0].data.d_status = status; } static GtkWidget *pandora_gen_fsb = NULL; static GtkWidget *pandora_gen_filelist = NULL; static gboolean ok_pressed = FALSE; /* Add a file: this is called from the fsb's ok button, or from doubleclick */ static void pandora_gen_file_selection_add (GtkWidget *widget, gpointer data) { gchar** multi_sel; #ifdef GIMP_1_2 gchar* sarr[2]; #endif /* GIMP 1_3 */ gchar* tmp; /* If OK was pressed but we don't have any files in our list yet, * add any remaining selection to the list, before proceeding * to build the panorama. */ if (!ok_pressed || gtk_clist_get_text(GTK_CLIST (pandora_gen_filelist), 0, 0, &tmp) == 0) { #ifdef GIMP_1_2 sarr[0] = gtk_file_selection_get_filename (GTK_FILE_SELECTION (data)); sarr[1] = 0; multi_sel = sarr; #else /* GIMP 1_3 */ multi_sel = gtk_file_selection_get_selections (GTK_FILE_SELECTION (data)); #endif /* GIMP 1_3 */ /* gtk_clist_append should append the whole array, but it doesn't. * So loop over it: */ while (multi_sel && *multi_sel) { gtk_clist_append(GTK_CLIST (pandora_gen_filelist), multi_sel); ++multi_sel; } } if (ok_pressed) { gtk_widget_hide (GTK_WIDGET (data)); pandora_generate_panorama(); gtk_main_quit(); } } /* Ick. But we get both this and the file_selection_add callback */ static void pandora_gen_file_selection_ok_pressed (GtkWidget *widget, gpointer data) { ok_pressed = TRUE; } /* Add a file: this is called from the fsb's ok button, or from doubleclick */ static void pandora_gen_file_selection_rem (GtkWidget *widget, gpointer data) { /* Wow, getting the selected items in a gtkclist is amazingly baroque! */ GList *selection = GTK_CLIST(pandora_gen_filelist)->selection; GList *selection_end = GTK_CLIST(pandora_gen_filelist)->selection_end; if (selection) { while (selection && selection_end) { int i; int ss = (int)selection->data; int se = (int)selection_end->data; for (i = se; i >= ss; --i) gtk_clist_remove(GTK_CLIST (pandora_gen_filelist), i); selection = selection->next; selection_end = selection_end->next; /* Deletion confuses the clist and we would get several * extra zeroes deleted, so get the selection again: */ selection = GTK_CLIST(pandora_gen_filelist)->selection; selection_end = GTK_CLIST(pandora_gen_filelist)->selection_end; } } } static void pandora_gen_file_selection_cancel (GtkWidget *widget, gpointer data) { gtk_widget_hide (GTK_WIDGET (data)); } #if 0 static void rename_button (GtkWidget* button, const gchar* newname) { #ifdef GIMP_1_2 gtk_label_set_text(GTK_LABEL (GTK_BIN(button)->child), newname); #else button = GTK_BUTTON (button); gtk_button_set_label (button, newname); /* gtk_button_set_use_stock (button, TRUE); */ #endif } #endif static gint pandora_gen_dialog (void) { if (!pandora_gen_fsb) { GtkWidget* w; GtkWidget* table; GtkObject* scale; GtkWidget* scrolled_win; GtkWidget* frame; GtkWidget* vbox; GtkFileSelection* filesel; GtkWidget* button_box; gimp_ui_init ("pandora_gen", TRUE); #ifdef GIMP_1_2 gimp_help_init(); #endif pandora_gen_fsb = gtk_file_selection_new (_("Pandora: Select Images")); filesel = GTK_FILE_SELECTION (pandora_gen_fsb); gtk_file_selection_hide_fileop_buttons (filesel); #ifdef GIMP_1_2 /* This doesn't actually work: */ gtk_clist_set_selection_mode (GTK_CLIST (filesel->file_list), GTK_SELECTION_MULTIPLE); #else gtk_file_selection_set_select_multiple (filesel, TRUE); #endif /* Add in some extra buttons */ button_box = gtk_hbox_new (FALSE, 0); w = gtk_button_new_with_label (_("Remove File \\")); gtk_box_pack_start (GTK_BOX (button_box), w, FALSE, FALSE, 0); g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK (pandora_gen_file_selection_rem), G_OBJECT (pandora_gen_fsb)); gtk_widget_show (w); w = gtk_button_new_with_label (_("\\ Add File")); gtk_box_pack_end (GTK_BOX (button_box), w, FALSE, FALSE, 0); g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK (pandora_gen_file_selection_add), G_OBJECT (pandora_gen_fsb)); gtk_widget_show (w); gtk_widget_show (button_box); gtk_box_pack_start (GTK_BOX (filesel->main_vbox), button_box, FALSE, FALSE, 0); /* Here's where the real stuff goes */ frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (filesel->main_vbox), frame, FALSE, FALSE, 0); vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (frame), vbox); w = gtk_label_new (_("File in panorama (left to right):")); gtk_box_pack_start (GTK_BOX (vbox), w, TRUE, TRUE, 0); gtk_widget_show(w); /* The list of files already selected */ scrolled_win = gtk_scrolled_window_new (NULL, NULL); pandora_gen_filelist = gtk_clist_new (1); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_clist_set_selection_mode (GTK_CLIST (pandora_gen_filelist), GTK_SELECTION_MULTIPLE); gtk_widget_set_usize (scrolled_win, FILELIST_WIDTH, FILELIST_HEIGHT); gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled_win), pandora_gen_filelist); gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE, TRUE, 0); gtk_widget_show(pandora_gen_filelist); gtk_widget_show(scrolled_win); gtk_widget_show(frame); w = gtk_check_button_new_with_mnemonic (_("Feather Layers")); gtk_box_pack_start(GTK_BOX (vbox), w, TRUE, TRUE, 0); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), avals.feather); gtk_widget_show (w); g_signal_connect (G_OBJECT (w), "toggled", G_CALLBACK (gimp_toggle_button_update), &avals.feather); /* The scale widget (and its mandatory table container */ table = gtk_table_new (1, 1, FALSE); /* gtk_table_set_col_spacings (GTK_TABLE (table2), 4); gtk_table_set_row_spacings (GTK_TABLE (table2), 2); */ gtk_container_set_border_width (GTK_CONTAINER (table), 4); gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0); scale = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, _("Overlap:"), SCALE_WIDTH, SPIN_WIDTH, avals.overlap, 0.0, 1.0, 0.01, 0.1, 3, TRUE, 0, 0, /* _("How much each image overlaps"), Why do tooltips always -> Gtk-CRITICAL? */ NULL, NULL); g_signal_connect (G_OBJECT (scale), "value_changed", G_CALLBACK (gimp_double_adjustment_update), &avals.overlap); gtk_widget_show (table); gtk_widget_show(vbox); /* Connect all the signals */ g_signal_connect (G_OBJECT (filesel), "destroy", G_CALLBACK (gtk_widget_destroyed), G_OBJECT (pandora_gen_fsb)); g_signal_connect (G_OBJECT (filesel->ok_button), "clicked", G_CALLBACK (pandora_gen_file_selection_add), G_OBJECT (pandora_gen_fsb)); /* We want doubleclick to work, but we don't want it to dismiss * the dialog. We want OK to dismiss the dialog and do the * function, without adding any more files. * Unfortunately, doubleclick works through the OK button callback. * Fortunately, it only sends clicked, not pressed. * So note when the pressed signal happens, and set a flag * so that the next clicked signal won't add a file but * will instead activate ok. */ g_signal_connect (G_OBJECT (filesel->ok_button), "pressed", G_CALLBACK (pandora_gen_file_selection_ok_pressed), G_OBJECT (pandora_gen_fsb)); g_signal_connect (G_OBJECT (filesel->cancel_button), "clicked", G_CALLBACK (pandora_gen_file_selection_cancel), G_OBJECT (pandora_gen_fsb)); gimp_help_set_help_data (filesel->cancel_button, _("Click here to cancel file selection"), NULL); } if (!GTK_WIDGET_VISIBLE (pandora_gen_fsb)) { gtk_widget_show (pandora_gen_fsb); } gtk_main (); /* This won't return until after _generate has finished */ gdk_flush (); return TRUE; } static void pandora_gen_add_layer_mask (gint32 pan_img, gint32 layer, gint32 w, gint32 h, gdouble overlap) { #ifdef GIMP_1_2 gdouble hgrad = (gdouble)h / 2.; gint32 mask; guchar oldFGr, oldFGg, oldFGb; guchar oldBGr, oldBGg, oldBGb; gimp_palette_get_foreground (&oldFGr, &oldFGg, &oldFGb); gimp_palette_get_background (&oldBGr, &oldBGg, &oldBGb); gimp_palette_set_foreground (0xff, 0xff, 0xff); gimp_palette_set_background ( 0, 0, 0); /* Give it a layer mask */ mask = gimp_layer_create_mask (layer, GIMP_WHITE_MASK); gimp_image_add_layer_mask (pan_img, layer, mask); gimp_blend (mask, GIMP_FG_BG_RGB, GIMP_NORMAL_MODE, GIMP_LINEAR, 100.0, 0.0, GIMP_REPEAT_NONE, FALSE, 0, 0.0, /* supersampling stuff */ w * overlap * .5, hgrad, 0.0, hgrad); gimp_palette_set_foreground (oldFGr, oldFGg, oldFGb); gimp_palette_set_background (oldBGr, oldBGg, oldBGb); #else /* GIMP_1_3 */ gdouble hgrad = (gdouble)h / 2.; gint32 mask; GimpRGB black, white; GimpRGB oldFG, oldBG; gimp_palette_get_foreground (&oldFG); gimp_palette_get_background (&oldBG); gimp_rgb_set (&black, 0., 0., 0.); gimp_rgb_set (&white, 1., 1., 1.); gimp_palette_set_foreground (&white); gimp_palette_set_background (&black); /* Give it a layer mask */ mask = gimp_layer_create_mask (layer, GIMP_ADD_WHITE_MASK); gimp_image_add_layer_mask (pan_img, layer, mask); gimp_blend (mask, GIMP_FG_BG_RGB_MODE, GIMP_NORMAL_MODE, GIMP_GRADIENT_LINEAR, 100.0, 0.0, GIMP_REPEAT_NONE, FALSE, FALSE, 0, 0.0, FALSE, /* supersampling stuff */ w * overlap * .5, hgrad, 0.0, hgrad); gimp_palette_set_background (&oldBG); gimp_palette_set_foreground (&oldFG); /* Set selection back to the layer; if it's on the mask, * then 2.0.1 and later will drag the mask, not the image! */ mask = gimp_layer_set_edit_mask (layer, FALSE); #endif /* GIMP_1_2 | 1.3 */ } static gint pandora_generate_panorama() { int i; int pan_img = 0; int pan_img_w = 0, pan_img_h = 0; int pan_num_layers = 0; gint32 pan_layers[MAXIMAGES]; int layerXoff[MAXIMAGES]; int w = 0, h; layerXoff[0] = 0; /* Get the list of files */ for (i=0; ; ++i) { gchar* fnam; gchar* bnam; int cur_img; int numlayers; int *layers; int j; if (gtk_clist_get_text(GTK_CLIST (pandora_gen_filelist), i, 0, &fnam) == 0) break; bnam = basename(fnam); cur_img = gimp_file_load (GIMP_RUN_INTERACTIVE, fnam, bnam); /* should pass the real run mode */ if (cur_img == -1) { printf("Couldn't open %s\n", fnam); continue; } gimp_image_set_filename (cur_img, bnam); w = gimp_image_width (cur_img); h = gimp_image_height (cur_img); if (pan_img) { /* Resize the image bigger if need be */ if (w > pan_img_w) pan_img_w = w*2; if (h > pan_img_h) pan_img_h = h*2; gimp_image_resize(pan_img, pan_img_w, pan_img_h, 0, 0); } else { /* Make a new image, twice the size of this first component image */ pan_img_w = w*2; pan_img_h = h*2; pan_img = gimp_image_new(pan_img_w, pan_img_h, GIMP_RGB); } /* Now it's safe, so stick the image in. * Loop over layers in the image to be inserted. */ layers = gimp_image_get_layers(cur_img, &numlayers); for (j = 0; j < numlayers && pan_num_layers < MAXIMAGES; ++j) { /* Make a new layer in pan_img and paste the image's layer into it. */ #ifdef GIMP_1_2 pan_layers[pan_num_layers] = gimp_layer_new(pan_img, bnam, w, h, GIMP_RGBA_IMAGE, 100.0, GIMP_NORMAL_MODE); gimp_image_add_layer(pan_img, pan_layers[pan_num_layers], 0); gimp_edit_copy(layers[j]); gimp_edit_paste(pan_layers[pan_num_layers], FALSE); #else /* gimp 1.3 has this great new call: */ pan_layers[pan_num_layers] = gimp_layer_new_from_drawable(layers[0], pan_img); /* but it doesn't copy the drawable's name, so set it: */ gimp_layer_set_name(pan_layers[pan_num_layers], bnam); gimp_image_add_layer(pan_img, pan_layers[pan_num_layers], 0); #endif /* GIMP_1.2 */ /* Give it a layer mask with a gradient on the left, * except the first image. */ if (pan_num_layers > 0 && avals.feather) pandora_gen_add_layer_mask (pan_img, pan_layers[pan_num_layers], w, h, avals.overlap); /* Calculate the offset of the next image * based on the size of this one. * Don't try to set it now -- the image isn't big enough yet. */ layerXoff[pan_num_layers+1] = layerXoff[pan_num_layers] + (w * (1. - avals.overlap)); ++pan_num_layers; } /* Now we're done with the temp image. */ gimp_image_delete(cur_img); if (pan_num_layers >= MAXIMAGES) break; } /* end of loop over files */ /* Now we're probably left with a floating selection from the * last paste, and we need to get rid of it somehow. */ i = gimp_image_floating_selection (pan_img); if (i != -1) gimp_floating_sel_anchor (i); /* Now, resize the image to the right size, and move all the layers. * Allow some extra room on the right in case the user was conservative * with estimating overlap. */ printf("Final image should have %d layers and width %d\n", pan_num_layers, layerXoff[pan_num_layers]); gimp_image_resize(pan_img, layerXoff[pan_num_layers] + w * .6, pan_img_h, 0, 0); /* Loop over all layers in the final image, * moving them to their final resting place. */ for (i=0; i/Pandora/Match Panorama Layers..."), "RGB*, GRAY*, INDEXED*", GIMP_PLUGIN, G_N_ELEMENTS (args), G_N_ELEMENTS (return_vals), args, return_vals); } static void run (MAYBE_CONST gchar *name, gint nparams, MAYBE_CONST GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) { static GimpParam values[2]; GimpRunMode run_mode; GimpPDBStatusType status = GIMP_PDB_SUCCESS; gint32 image_id; run_mode = param[0].data.d_int32; *nreturn_vals = 2; *return_vals = values; values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = status; values[1].type = GIMP_PDB_LAYER; values[1].data.d_layer = -1; switch (run_mode) { case GIMP_RUN_INTERACTIVE: INIT_I18N_UI(); /* Possibly retrieve data */ gimp_get_data ("plug_in_pandora_match", &avals); /* First acquire information with a dialog */ if (! pandora_match_dialog ()) return; break; case GIMP_RUN_NONINTERACTIVE: INIT_I18N(); /* Make sure all the arguments are there! */ if (nparams != 15) status = GIMP_PDB_CALLING_ERROR; if (status == GIMP_PDB_SUCCESS) { avals.xdist = param[1].data.d_int32; avals.ydist = param[2].data.d_int32; } break; case GIMP_RUN_WITH_LAST_VALS: /* Possibly retrieve data */ gimp_get_data ("plug_in_pandora_match", &avals); break; default: break; } /* Get the active drawable */ image_id = param[1].data.d_image; /* Do it! */ if (status == GIMP_PDB_SUCCESS) { /* run the effect */ values[1].data.d_image = do_pandora_match (image_id, avals.xdist, avals.ydist); /* If the run mode is interactive, flush the displays */ if (run_mode != GIMP_RUN_NONINTERACTIVE) gimp_displays_flush (); /* Store avals data */ if (run_mode == GIMP_RUN_INTERACTIVE) gimp_set_data ("plug_in_pandora_match", &avals, sizeof (Pandora_MatchVals)); } else if (status == GIMP_PDB_SUCCESS) { status = GIMP_PDB_EXECUTION_ERROR; } values[0].data.d_status = status; //gimp_drawable_detach (drawable); } static void pandora_match_ok_callback (GtkWidget *widget, gpointer data) { aint.run = TRUE; gtk_widget_destroy (GTK_WIDGET (data)); } /* See http://www.home.unix-ag.org/simon/gimp/guadec2002/gimp-plugin/html/efficientaccess.html for some hints. But we're not using that currently. */ static gdouble CompareRegions (guchar* left, guchar* right, gint w, gint h, gint bpp) { gdouble fval = 0.0; int x, y; int rowstride = w * bpp; guchar* l = left; guchar* r = right; for (y = 0; y < h; y++) { for (x = 0; x < w; x++) printf("%x%x%x ", l[x*bpp+0]&0xf, l[x*bpp+1]&0xf, l[x*bpp+2]&0xf); printf("\t"); for (x = 0; x < w; x++) printf("%x%x%x ", r[x*bpp+0]&0xf, r[x*bpp+1]&0xf, r[x*bpp+2]&0xf); printf("\n"); for (x = 0; x < w; x++) { /* Compare l[n] to r[n] */ fval += fabs (r[0] - l[0]) + fabs (r[1] - l[1]) + fabs (r[2] - l[2]); l += bpp; r += bpp; } left += rowstride; right += rowstride; } printf("Difference: %f\n\n", fval); return fval; } #define MAXWIDTH 50 #define MAXHEIGHT 500 static int min(int a, int b) { if (a > b) return b; return a; } static int max(int a, int b) { if (a > b) return a; return b; } static gint32 do_pandora_match (gint32 img, gint32 xdist, gint32 ydist) { int thislayer; gint32 *layers; gint32 numlayers; gint32 ww, hh; guchar *buf0; guchar *buf1; printf("Pandora_match: max distance (%d, %d)\n", xdist, ydist); /* Loop over layers */ layers = gimp_image_get_layers(img, &numlayers); if (numlayers < 2) return 1; /* The layers returned are backwards, so if we want to go left to right * (bottom to top) we have to loop backwards through the list: */ for (thislayer = numlayers-2; thislayer >= 0; --thislayer) //for (thislayer = 1; thislayer < numlayers; ++thislayer) { GimpDrawable* draw0 = gimp_drawable_get (layers[thislayer+1]); GimpDrawable* draw1 = gimp_drawable_get (layers[thislayer]); GimpPixelRgn rgn0, rgn1; /* The starting offsets of the input layers */ gint x0, y0, x1, y1; /* The size of the input layers */ gint w0 = gimp_drawable_width (layers[thislayer+1]); gint h0 = gimp_drawable_height (layers[thislayer+1]); gint w1 = gimp_drawable_width (layers[thislayer]); gint h1 = gimp_drawable_height (layers[thislayer]); gint overlapw; int xoff, yoff; int xsav = 0, ysav = 0; gdouble minval = G_MAXFLOAT; gimp_drawable_offsets (layers[thislayer+1], &x0, &y0); gimp_drawable_offsets (layers[thislayer], &x1, &y1); printf("left: (%d, %d) %d x %d\n", x0, y0, w0, h0); printf("right: (%d, %d) %d x %d\n", x1, y1, w1, h1); printf("Current X overlap: %d\n", x0 + w0 - x1); /* The size of the block we'll be comparing each time. * In X, that means the size of the overlap, minus a border of * x/ydist on all four sides. * In Y, the maximum amount we can overlap is the minimum of * the Overlap in Y with right shifted uppest: top is max(y0, y1-ydist) bottom is min(y0+h0, y1-ydist+h1) With right shiftest downest: top is max(y0, y1+ydist) bottom is min(y0+h0, y1+ydist+h1) So the safe height is the minimum of these two. */ hh = min( min(y0+h0, y1-ydist+h1) - max(y0, y1-ydist), min(y0+h0, y1+ydist+h1) - max(y0, y1+ydist) ); overlapw = w0 - x1 + x0; /* X size is a bit simpler: the size of the overlap minus 2*xdist. */ ww = overlapw - 2*xdist; printf("ww = %d = %d + %d - %d - 2*%d\n", ww, x0, w0, x1, xdist); printf("hh = %d\n", hh); /* Now, we loop comparing the leftmost column of draw1 * (minus a bit at the top and bottom) * to columns in the overlapping area of draw0. * XXX Leftmost column isn't best; eventually we should compare * a region, or at least a column in the middle of the overlap. */ /* Initialize the pixel regions we'll use */ /* Region 0 is the whole overlap region of the left layer (from y=0) */ gimp_pixel_rgn_init (&rgn0, draw0, x1 - x0, 0, overlapw, h0, FALSE, FALSE); /* Region 1 is the overlap region of the right layer (from x=0, y=0) */ gimp_pixel_rgn_init (&rgn1, draw1, 0, 0, overlapw, hh, FALSE, FALSE); /* Make sure they're the same color depth */ if (rgn0.bpp != rgn1.bpp) { printf("Confused: mismatched bpp, %d vs %d!\n", rgn0.bpp, rgn1.bpp); return 1; } /* Since we have to store these regions in memory, * put a limit on how big they can get: */ if (hh > MAXHEIGHT) hh = MAXHEIGHT; if (ww > MAXWIDTH) ww = MAXWIDTH; buf0 = g_malloc(ww * hh * rgn0.bpp); buf1 = g_malloc(ww * hh * rgn0.bpp); if (!buf0 || !buf1) { if (buf0) g_free(buf0); if (buf1) g_free(buf1); printf("Out of memory!\n"); return 1; } printf("Malloced %p and %p at %d\n", buf0, buf1, ww * hh * rgn0.bpp); /* Virtually move thislayer left and right by xdist */ for (xoff = -xdist; xoff <= xdist; ++xoff) { printf("Trying to get rect: (%d, %d) %d x %d\n", x1 - x0 + xdist + xoff, 0, ww, hh); gimp_pixel_rgn_get_rect (&rgn0, buf0, x1 - x0 + xdist + xoff, 0, ww, hh + 2*ydist); printf("Got one rect\n"); /* Virtually move thislayer up and down by ydist */ for (yoff = -ydist; yoff <= ydist; ++yoff) { gdouble cmp; printf("xoff = %d, yoff = %d\n", xoff, yoff); /* Get the two rects we're going to be comparing. */ #if 0 printf("Trying to get rect: (%d, %d) %d x %d\n", x1 - x0 + xdist + xoff, ydist+yoff, ww, hh); gimp_pixel_rgn_get_rect (&rgn0, buf0, x1 - x0 + xdist + xoff, ydist+yoff, ww, hh); printf("Got one rect\n"); #endif gimp_pixel_rgn_get_rect (&rgn1, buf1, xdist, ydist, ww, hh); printf("Got the regions\n"); cmp = CompareRegions(buf0, buf1, ww, hh, rgn0.bpp); printf ("total for row: %f\n", cmp); if (cmp < minval) { minval = cmp; xsav = xoff; ysav = yoff; } } printf("Done with y loop\n"); } g_free(buf0); g_free(buf1); printf("Recommended offset is (%d, %d) at val %f\n", xsav, ysav, minval); } gimp_displays_flush (); aint.run = TRUE; return 0; } static gint pandora_match_dialog (void) { GtkWidget *dlg; GtkWidget *hbox; GtkWidget *logo_box; GtkWidget *frame; GtkWidget *table; GtkObject *scale; gimp_ui_init ("pandora_match", TRUE); #ifdef GIMP_1_2 dlg = gimp_dialog_new (_("Pandora_Match"), "pandora_match", gimp_standard_help_func, "filters/pandora_match.html", GTK_WIN_POS_MOUSE, FALSE, TRUE, FALSE, GTK_STOCK_CANCEL, gtk_widget_destroy, NULL, 1, NULL, FALSE, TRUE, GTK_STOCK_OK, pandora_match_ok_callback, NULL, NULL, NULL, TRUE, FALSE, NULL); #else /* Gimp 1.3 */ dlg = gimp_dialog_new (_("Pandora_Match"), "pandora_match", NULL, 0, gimp_standard_help_func, "filters/pandora_match.html", GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, pandora_match_ok_callback, NULL); #endif g_signal_connect (G_OBJECT (dlg), "destroy", G_CALLBACK (gtk_main_quit), NULL); hbox = gtk_hbox_new (FALSE, 6); gtk_container_set_border_width (GTK_CONTAINER (hbox), 6); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), hbox, TRUE, TRUE, 0); logo_box = gtk_vbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), logo_box, FALSE, FALSE, 0); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (logo_box), frame, FALSE, FALSE, 0); #ifdef DO_ICON_HERE_IF_THERE_IS_ONE icon_pixbuf = gdk_pixbuf_new_from_inline(-1, pandora_match_icon, FALSE, NULL); preview = gtk_image_new_from_pixbuf (icon_pixbuf); gtk_container_add (GTK_CONTAINER (frame), preview); gtk_widget_show (preview); #endif /* ! GIMP_1_2 */ gtk_widget_show (frame); gtk_widget_show (logo_box); table = gtk_table_new (2, 1, FALSE); //gtk_table_set_col_spacings (GTK_TABLE (table2), 4); //gtk_table_set_row_spacings (GTK_TABLE (table2), 2); gtk_container_set_border_width (GTK_CONTAINER (table), 4); gtk_box_pack_start (GTK_BOX (hbox), table, TRUE, TRUE, 0); scale = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, _("Max X distance:"), SCALE_WIDTH, SPIN_WIDTH, avals.xdist, 1.0, 200.0, 1.0, 5.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (G_OBJECT (scale), "value_changed", G_CALLBACK (gimp_int_adjustment_update), &avals.xdist); scale = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, _("Max Y distance:"), SCALE_WIDTH, SPIN_WIDTH, avals.ydist, 1.0, 100.0, 1.0, 5.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (G_OBJECT (scale), "value_changed", G_CALLBACK (gimp_int_adjustment_update), &avals.ydist); gtk_widget_show (table); gtk_widget_show (frame); gtk_widget_show (hbox); gtk_widget_show (dlg); gtk_main (); gdk_flush (); return aint.run; }