/* * Gimp Plug-In to merge two images with a mathematical funtion. * * Copyright (C) 1999 Hans Breuer * Hans Breuer, Hans@Breuer.org * 08/29/99 * * 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. * * Based on (at least) the following plug-ins: * WinClipboard * Compose * * Any suggestions, bug-reports or patches are welcome. * * Modified for 1.2 and the Graphics Muse Tools CD * by Michael J. Hammel * 01/2002 */ #include #include #include "gtk/gtk.h" #include "libgimp/gimp.h" #include "libgimp/gimpmenu.h" #include /* History: * * 08/28/99 Implementation. * 08/29/99 First release. * 09/02/99 First bugfix. (check_compatible, added GIMP_RUN_WITH_LAST_VALS, ...) * * TODO (maybe): * - make a nicer UI * - INTL support (not me!) * - ... */ #define PLUG_IN_NAME "plug_in_math_merge" #define PLUG_IN_VERSION "1999/09/02" /* #define MM_DEBUG */ #ifndef __GIMPINTL_H__ #define _(a) (a) #endif /* Declare some local functions. */ static void query (void); static void run (char *name, int nparams, GimpParam *param, int *nreturn_vals, GimpParam **return_vals); /* Plugin types */ /* operation modes */ typedef enum { MMM_Add = 0, MMM_Substract, MMM_Multiply, MMM_Divide, MMM_Logical_AND, MMM_Logical_OR, MMM_Minimum, MMM_Maximum, MMM_NumberModes } MathMergeMode; static gchar* MathMergeModeNames [MMM_NumberModes] = { "Add", "Substract", "Multiply", "Divide", "Logical AND", "Logical OR", "Minimum", "Maximum" }; typedef struct { guint32 width; guint32 height; guint32 type; guint32 id_A; guint32 id_B; guint32 id_Out; guint32 mode; guint32 normalize; /* 0 == default */ gpointer dlg; /* only valid while dialog open */ guint32 run; /* FALSE, TRUE */ } MathMergeParams; /* Plugin function prototypes */ /* the main function */ static int MathMerge (gboolean interactive, MathMergeParams* params); static int MathMergeDlg (MathMergeParams* params, gint32 drawable_ID); GimpPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run, /* run_proc */ }; /* main entry point implemented by libgimp */ MAIN () /* exported query function */ static void query () { static GimpParamDef my_args[] = { { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" }, { GIMP_PDB_IMAGE, "an_image", "Active Image" }, { GIMP_PDB_DRAWABLE, "source_A", "Source A" }, { GIMP_PDB_DRAWABLE, "source_B", "Source B" }, { GIMP_PDB_INT32, "result_to", "result to new layer,image 0,1"}, { GIMP_PDB_INT32, "merge_mode", "Add=0 Sub Mul Div AND OR Min Max" }, { GIMP_PDB_INT32, "normalize", "normalize with; 0 == mode default"} }; static int nmy_args = sizeof (my_args) / sizeof (my_args[0]); static GimpParamDef my_returns[] = { { GIMP_PDB_IMAGE, "image", "Output image" } }; static int nmy_returns = sizeof (my_returns) / sizeof (my_returns[0]); gimp_install_procedure (PLUG_IN_NAME, "merges two images mathematically", "In fact it merges two drawables with equal dimension into a new one." "The effects are similar to the \"Layer Modes\" but more flexible " "out of a mathematical point of view.", "Hans Breuer", "Hans Breuer ", PLUG_IN_VERSION, "/Filters/Generic/Math Merge ...", "GRAY*, RGB*", GIMP_PLUGIN, nmy_args, nmy_returns, my_args, my_returns); } /* query */ /* exported run function */ static void run (char *name, int nparams, GimpParam *param, int *nreturn_vals, GimpParam **return_vals) { static GimpParam values[2]; GimpRunModeType run_mode; MathMergeParams private_params; run_mode = param[0].data.d_int32; *nreturn_vals = 2; *return_vals = values; values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = GIMP_PDB_SUCCESS; values[1].type = GIMP_PDB_IMAGE; values[1].data.d_int32 = -1; /* not the best initializing, but the simpliest ... */ memset(&private_params, 0, sizeof(MathMergeParams)); if (0 == strcmp (name, "plug_in_math_merge")) { switch (run_mode) { case GIMP_RUN_INTERACTIVE: #ifdef NOT_YET /* todo: dialog initialization */ /* Possibly retrieve data */ gimp_get_data (PLUG_IN_NAME, &private_params); #endif if (!MathMergeDlg(&private_params, param[2].data.d_int32)) values[0].data.d_status = GIMP_PDB_CALLING_ERROR; else gimp_set_data (PLUG_IN_NAME, &private_params, sizeof (MathMergeParams)); break; case GIMP_RUN_NONINTERACTIVE: /* Make sure all the arguments are there! */ if (nparams != 7) /* TODO: get from nmy_args */ values[0].data.d_status = GIMP_PDB_CALLING_ERROR; else { /* transfer data to local param struct */ private_params.id_A = param[2].data.d_drawable; private_params.id_B = param[3].data.d_drawable; private_params.id_Out = param[4].data.d_int32; private_params.mode = param[5].data.d_int32; private_params.normalize = param[6].data.d_int32; } break; case GIMP_RUN_WITH_LAST_VALS : /* Possibly retrieve data */ gimp_get_data (PLUG_IN_NAME, &private_params); break; default: values[0].data.d_status = GIMP_PDB_CALLING_ERROR; break; } /* switch */ #ifdef MM_DEBUG { int delayed = 100; while (delayed) { g_print("."); g_usleep(100000); delayed--; } } #endif /* everything fine until now? */ if (GIMP_PDB_SUCCESS == values[0].data.d_status) { values[1].data.d_int32 = MathMerge(GIMP_RUN_INTERACTIVE == run_mode, &private_params); } } /* if PLUG_IN_NAME */ } /* run */ /**************************************************** * * Plugin Function implementation * ****************************************************/ /* sub function prototype pointer */ typedef int (*PFNSubMM)(guchar* pA, guchar* pB, guchar* pO, gint32 normal, gint32 siz); /* sub functions ... */ int MM_SubAdd(guchar* pA, guchar* pB, guchar* pO, gint32 normal, gint32 siz) { int i; if (normal < 1) normal = 2; for (i = 0; i < siz; i++) pO[i] = (guchar)(((gint32)pA[i] + (gint32)pB[i]) / normal); return 1; } /* MM_SubAdd */ int MM_SubSub(guchar* pA, guchar* pB, guchar* pO, gint32 normal, gint32 siz) { int i; if (normal < 1) normal = 2; for (i = 0; i < siz; i++) pO[i] = (guchar)((256 + (gint32)pA[i] - (gint32)pB[i]) / 2); return 1; } /* MM_SubSub */ int MM_SubMul(guchar* pA, guchar* pB, guchar* pO, gint32 normal, gint32 siz) { int i; if (normal > 0) for (i = 0; i < siz; i++) pO[i] = (guchar)(((gint32)pA[i] * (gint32)pB[i]) / normal); else /* sqrt !!! */ for (i = 0; i < siz; i++) pO[i] = (guchar)(sqrt((double)((gint32)pA[i] * (gint32)pB[i]))); return 1; } /* MM_SubMul */ int MM_SubDiv(guchar* pA, guchar* pB, guchar* pO, gint32 normal, gint32 siz) { int i; if (normal < 1) normal = 1; for (i = 0; i < siz; i++) if (0 != pB[i]) pO[i] = (guchar)(((256 * (gint32)pA[i]) / (gint32)pB[i]) / normal); else pO[i] = 0; /* or 255 ??? */ return 1; } /* MM_SubDiv */ int MM_SubMin(guchar* pA, guchar* pB, guchar* pO, gint32 normal, gint32 siz) { int i; for (i = 0; i < siz; i++) pO[i] = (pA[i] > pB[i]) ? pB[i] : pA[i]; return 1; } /* MM_SubMin */ int MM_SubMax(guchar* pA, guchar* pB, guchar* pO, gint32 normal, gint32 siz) { int i; for (i = 0; i < siz; i++) pO[i] = (pA[i] > pB[i]) ? pA[i] : pB[i]; return 1; } /* MM_SubMax */ int MM_SubOR(guchar* pA, guchar* pB, guchar* pO, gint32 normal, gint32 siz) { int i; for (i = 0; i < siz; i++) pO[i] = pA[i] | pB[i]; return 1; } /* MM_SubOR */ int MM_SubAND(guchar* pA, guchar* pB, guchar* pO, gint32 normal, gint32 siz) { int i; for (i = 0; i < siz; i++) pO[i] = pA[i] & pB[i]; return 1; } /* MM_SubMax */ /********************************************* * Main function */ #define MAX_NAME_LEN 255 static int MathMerge (gboolean interactive, MathMergeParams* params) { PFNSubMM fnSub = NULL; gint32 drwID_A, drwID_B, drwID_O; gint32 imgID_A, imgID_B, imgID_O; gchar sLayerName[MAX_NAME_LEN+1]; if (params->mode < 0 || (params->mode >= MMM_NumberModes)) { g_warning("MM invalid mode!"); return (-1); } /* select sub function */ switch (params->mode) { case MMM_Add : fnSub = MM_SubAdd; break; case MMM_Substract : fnSub = MM_SubSub; break; case MMM_Multiply : fnSub = MM_SubMul; break; case MMM_Divide : fnSub = MM_SubDiv; break; case MMM_Logical_AND : fnSub = MM_SubAND; break; case MMM_Logical_OR : fnSub = MM_SubOR; break; case MMM_Minimum : fnSub = MM_SubMin; break; case MMM_Maximum : fnSub = MM_SubMax; break; default : g_warning("MathMerge: function %s not implemented yet!", MathMergeModeNames[params->mode]); return -1; } /* switch */ /* get image and drawable ids. * * It's a little bit dirty to uses the param fields for * it (it's historical). Need to exchange back to supplied * values, because of gimp_set_data later on ... */ drwID_A = params->id_A; drwID_B = params->id_B; imgID_A = gimp_drawable_image_id(drwID_A); imgID_B = gimp_drawable_image_id(drwID_B); /* real Bad Things would happen below */ if ((-1 == drwID_A) || (-1 == drwID_B)) { g_warning("MathMerge: Invalid drawable ids."); return -1; } g_snprintf(sLayerName, MAX_NAME_LEN, "%s(ed): (%d),(%d)", MathMergeModeNames[params->mode], drwID_A, drwID_B); if (interactive) gimp_progress_init ("Math Mergeing ..."); if (0 == params->id_Out) { imgID_O = gimp_drawable_image_id(drwID_A); drwID_O = gimp_layer_new (imgID_O, sLayerName, params->width, params->height, gimp_drawable_type (drwID_A), 100, GIMP_NORMAL_MODE); } else { imgID_O = gimp_image_new (params->width, params->height, gimp_image_base_type(imgID_A)); drwID_O = gimp_layer_new (imgID_O, sLayerName, params->width, params->height, gimp_drawable_type (drwID_A), 100, GIMP_NORMAL_MODE); } /* don't miss to add the layer to the image !!! */ gimp_image_add_layer(imgID_O, drwID_O,-1); /* process for every tile */ { GimpPixelRgn rgnA, rgnB, rgnO; GimpDrawable *drwA, *drwB, *drwO; gpointer pr; gint nTiles; nTiles = ((params->width - 1) / gimp_tile_width () + 1) * ((params->height - 1) / gimp_tile_height () + 1); drwA = gimp_drawable_get(drwID_A); drwB = gimp_drawable_get(drwID_B); drwO = gimp_drawable_get(drwID_O); gimp_pixel_rgn_init(&rgnA, drwA, 0, 0, params->width, params->height, FALSE, FALSE); // reading only gimp_pixel_rgn_init(&rgnB, drwB, 0, 0, params->width, params->height, FALSE, FALSE); // reading only gimp_pixel_rgn_init(&rgnO, drwO, 0, 0, params->width, params->height, TRUE, TRUE); // writing to /* using tile iterators */ for (pr = gimp_pixel_rgns_register(3, &rgnA, &rgnB, &rgnO); pr != NULL; pr = gimp_pixel_rgns_process(pr)) { if (interactive) gimp_progress_update((double)rgnA.process_count / nTiles); fnSub (rgnA.data, rgnB.data, rgnO.data, params->normalize, rgnA.w * rgnA.h * rgnA.bpp); } gimp_drawable_detach(drwA); gimp_drawable_detach(drwB); gimp_drawable_flush (drwO); gimp_drawable_detach(drwO); gimp_drawable_merge_shadow (drwID_O, TRUE); if (imgID_A != imgID_O) gimp_display_new (imgID_O); else { gimp_layer_set_visible(drwID_O, TRUE); gimp_displays_flush(); } } return imgID_O; } /* MathMerge */ /**************************************************** * * Dialog implementation * ****************************************************/ /************************************** * Callbacks * * Most callbacks are connected to the MathMergeParams pointer * to avoid further global vars. */ static void close_callback (GtkWidget *widget, gpointer data) { MathMergeParams* params; params = (MathMergeParams*)data; gtk_main_quit (); params->run = FALSE; } /* close_callback */ static void ok_callback (GtkWidget *widget, gpointer data) { MathMergeParams* params; params = (MathMergeParams*)data; gtk_widget_destroy (GTK_WIDGET (params->dlg)); /* retrieve or validate data? */ params->run = TRUE; } /* ok_callback */ /* The image_menu_callback is a special case, because it's * connected by gimp_drawable_menu_new */ static void imageA_menu_callback (gint32 id, gpointer data) { MathMergeParams* params; params = (MathMergeParams*)data; params->id_A = id; } /* image_menu_callback */ static void imageB_menu_callback (gint32 id, gpointer data) { MathMergeParams* params; params = (MathMergeParams*)data; params->id_B = id; } /* image_menu_callback */ /* callback for gimp_drawable_menu, filter drawables */ static gint check_compatible (gint32 image_id, gint32 drawable_id, gpointer data) { MathMergeParams* params; params = (MathMergeParams*)data; #ifdef MM_DEBUG g_print("MM check_compatible: image %ld drawable %ld width %ld height %ld\n", image_id, drawable_id, params->width, params->height); #endif /* first called only with valid image_id. Always say true * to get the calls with valid drawables. */ if (drawable_id == -1) return TRUE; return ((gimp_drawable_type (drawable_id) == params->type) && (gimp_drawable_width (drawable_id) == params->width) && (gimp_drawable_height (drawable_id) == params->height)); } /* check_compatible */ /* change a gint32 which pointer is attached to the widget */ static void gint32_menu_callback (GtkWidget *w, gpointer data) { gint32* pdata = (gint32*)gtk_object_get_user_data(GTK_OBJECT(w)); #ifdef MM_DEBUG g_print("MM gin32_menu_callback userdata %ld data\n", *pdata, data); #endif *pdata = (gint32)data; } /* image_menu_callback */ /************************************** * Some Dialog layout helpers */ static void MM_Dlg_add_label(const gchar* s, const gint32 line, const GtkWidget* table) { GtkWidget *label; label = gtk_label_new(s); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (table), label, 1, 2, line, line+1, GTK_FILL, GTK_FILL, 0, 0); gtk_widget_show (label); } /* MM_Dlg_add_label */ static void MM_Dlg_add_image_selector(gpointer data, const gint32 def_val, const gint32 line, const GtkWidget* table) { GtkWidget *image_menu, *image_option_menu; image_option_menu = gtk_option_menu_new (); image_menu = gimp_drawable_menu_new (check_compatible, 0 == line ? imageA_menu_callback : imageB_menu_callback, data, def_val); gtk_table_attach (GTK_TABLE (table), image_option_menu, 2, 3, line, line+1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); gtk_widget_show (image_option_menu); gtk_option_menu_set_menu (GTK_OPTION_MENU (image_option_menu), image_menu); } /* MM_Dlg_add_image_selector */ static GtkWidget* MM_make_menu_item( gchar* name, GtkSignalFunc callback, gpointer pdata, /* address of result */ gpointer data) /* value of this item */ { GtkWidget* item; item = gtk_menu_item_new_with_label(name); gtk_object_set_user_data (GTK_OBJECT (item), pdata); gtk_signal_connect ( GTK_OBJECT (item), "activate", callback, data); gtk_widget_show (item); return (item); } /* MM_make_menu_item */ /*************************************************** * The main dialog function */ static int MathMergeDlg (MathMergeParams* params, gint32 drawable_id) { GtkWidget *dlg; GtkWidget *button; GtkWidget *vbox; GtkWidget *table; GtkWidget *opt, *menu, *item; gchar **argv; gint argc; int j; /* Check default mode */ /* Save original image width/height/type */ params->width = gimp_drawable_width (drawable_id); params->height = gimp_drawable_height (drawable_id); params->type = gimp_drawable_type (drawable_id); argc = 1; argv = g_new (gchar *, 1); argv[0] = g_strdup ("MathMerge"); gtk_init (&argc, &argv); gtk_rc_parse (gimp_gtkrc ()); dlg = gtk_dialog_new (); params->dlg = dlg; gtk_object_set_user_data (GTK_OBJECT (dlg), params); gtk_window_set_title (GTK_WINDOW (dlg), _("MathMerge")); gtk_window_set_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE); gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", (GtkSignalFunc) close_callback, params); /* Action area */ button = gtk_button_new_with_label (_("OK")); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_signal_connect( GTK_OBJECT (button), "clicked", (GtkSignalFunc) ok_callback, params); gtk_box_pack_start( GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0); gtk_widget_grab_default (button); gtk_widget_show (button); button = gtk_button_new_with_label (_("Cancel")); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_signal_connect_object( GTK_OBJECT (button), "clicked", (GtkSignalFunc) gtk_widget_destroy, GTK_OBJECT (dlg)); gtk_box_pack_start( GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0); gtk_widget_show (button); /* parameter settings */ vbox = gtk_vbox_new (FALSE, 2); /* table with selectors ,options */ gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); table = gtk_table_new (4, 2, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 5); gtk_table_set_col_spacings (GTK_TABLE (table), 5); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), vbox, TRUE, TRUE, 0); gtk_widget_show (vbox); gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0); gtk_widget_show (table); /* add table items */ MM_Dlg_add_label(_("Source A:"), 0, table); MM_Dlg_add_label(_("Source B:"), 1, table); MM_Dlg_add_label(_("Destination:"), 2, table); MM_Dlg_add_label(_("Operation:"), 3, table); /* Menues to select images */ /* Source A and B*/ MM_Dlg_add_image_selector(params, drawable_id, 0, table); MM_Dlg_add_image_selector(params, drawable_id, 1, table); /* output "id" selector */ opt = gtk_option_menu_new(); menu = gtk_menu_new(); /* There MUST be a simplier way to do this! */ item = MM_make_menu_item(_("new layer"), GTK_SIGNAL_FUNC(gint32_menu_callback), &(params->id_Out), GINT_TO_POINTER(0)); gtk_menu_append( GTK_MENU (menu), item); item = MM_make_menu_item(_("new image"), GTK_SIGNAL_FUNC(gint32_menu_callback), &(params->id_Out), GINT_TO_POINTER(1)); gtk_menu_append( GTK_MENU (menu), item); gtk_table_attach( GTK_TABLE (table), opt, 2, 3, 2, 2+1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); gtk_option_menu_set_menu(GTK_OPTION_MENU (opt), menu); gtk_widget_show(opt); /* function selector */ opt = gtk_option_menu_new(); menu = gtk_menu_new(); for (j = 0; j < MMM_NumberModes; j++) { /* There MUST be a simplier way to do this! */ item = MM_make_menu_item( MathMergeModeNames[j], GTK_SIGNAL_FUNC(gint32_menu_callback), &(params->mode), GINT_TO_POINTER(j)); gtk_menu_append( GTK_MENU (menu), item); } gtk_table_attach ( GTK_TABLE (table), opt, 2, 3, 3, 3+1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); gtk_option_menu_set_menu(GTK_OPTION_MENU (opt), menu); gtk_widget_show(opt); /* ... */ gtk_widget_show (dlg); gtk_main (); gdk_flush (); params->dlg = NULL; /* just in case ... */ return params->run; } /* MathMergeDlg */ /* * Local Variables: * tab-width: 4 * End: */