/* * Rotate plug-in v0.6 by Sven Neumann, neumanns@uni-duesseldorf.de * 1998/01/15 * * Any suggestions, bug-reports or patches are very welcome. * * A lot of changes in version 0.3 were inspired by (or even simply * copied from) the similar rotators-plug-in by Adam D. Moss. * * As I still prefer my version I have not stopped developing it. * Probably this will result in one plug-in that includes the advantages * of both approaches. */ /* 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* Revision history * (09/28/97) v0.1 first development release * (09/29/97) v0.2 nicer dialog, * changed the menu-location to Filters/Transforms * (10/01/97) v0.3 now handles layered images and undo * (10/13/97) v0.3a small bugfix, no real changes * (10/17/97) v0.4 now handles selections * (01/09/98) v0.5 a few fixes to support portability * (01/15/98) v0.6 fixed a line that caused rotate to crash on some * systems */ /* TODO List * - handle channels and masks * - rewrite the main function to make it work on tiles rather than * process the image row by row. This should result in a significant * speedup (thanks to quartic for this suggestion). */ #include #include #include /* Defines */ #define PLUG_IN_NAME "plug_in_rotate" #define PLUG_IN_PRINT_NAME "Rotate" #define PLUG_IN_VERSION "v0.6 (01/15/98)" #define PLUG_IN_MENU_PATH "/Image/Transforms/Rotate" #define PLUG_IN_IMAGE_TYPES "RGB*, INDEXED*, GRAY*" #define PLUG_IN_AUTHOR "Sven Neumann (neumanns@uni-duesseldorf.de)" #define PLUG_IN_COPYRIGHT "Sven Neumann" #define PLUG_IN_DESCRIBTION "Rotates a layer or the whole image by 90, 180 or 270 degrees" #define PLUG_IN_HELP "This plug-in does rotate the active layer or the whole image clockwise by multiples of 90 degrees. When the whole image is choosen, the image is resized if necessary." #define NUMBER_IN_ARGS 5 #define IN_ARGS { PARAM_INT32, "run_mode", "Interactive, non-interactive"},\ { PARAM_IMAGE, "image", "Input image" },\ { PARAM_DRAWABLE, "drawable", "Input drawable"},\ { PARAM_INT32, "angle", "Angle { 90 (1), 180 (2), 270 (3) } degrees"},\ { PARAM_INT32, "everything", "Rotate the whole image? { TRUE, FALSE }"} #define NUMBER_OUT_ARGS 0 #define OUT_ARGS NULL #define NUM_ANGLES 4 gchar *angle_label[NUM_ANGLES] = { "0", "90", "180", "270" }; typedef struct { gint angle; gint everything; } RotateValues; typedef struct { gint run; } RotateInterface; static RotateValues rotvals = { 1, /* default to 90 degrees */ 1 /* default to whole image */ }; static RotateInterface rotint = { FALSE /* run */ }; static void query (void); static void run (gchar *name, gint nparams, /* number of parameters passed in */ GParam * param, /* parameters passed in */ gint *nreturn_vals, /* number of parameters returned */ GParam ** return_vals); /* parameters to be returned */ static void rotate (void); static void rotate_drawable (GDrawable *drawable); static void rotate_compute_offsets (gint *offsetx, gint *offsety, gint image_width, gint image_height, gint width, gint height); static gint rotate_dialog (void); static void rotate_close_callback (GtkWidget *widget, gpointer data); static void rotate_ok_callback (GtkWidget *widget, gpointer data); static void rotate_toggle_update (GtkWidget *widget, gpointer data); static void ErrorMessage (gchar *message); gint32 my_gimp_selection_float (gint32 image_ID, gint32 drawable_ID); gint32 my_gimp_selection_is_empty (gint32 image_ID); /* Global Variables */ GPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run /* run_proc */ }; /* the image and drawable that will be used later */ GDrawable *active_drawable = NULL; gint32 image_ID = -1; /* Functions */ MAIN () static void query (void) { static GParamDef args[] = { IN_ARGS }; static gint nargs = NUMBER_IN_ARGS; static GParamDef *return_vals = OUT_ARGS; static gint nreturn_vals = NUMBER_OUT_ARGS; /* the actual installation of the plugin */ gimp_install_procedure (PLUG_IN_NAME, PLUG_IN_DESCRIBTION, PLUG_IN_HELP, PLUG_IN_AUTHOR, PLUG_IN_COPYRIGHT, PLUG_IN_VERSION, PLUG_IN_MENU_PATH, PLUG_IN_IMAGE_TYPES, PROC_PLUG_IN, nargs, nreturn_vals, args, return_vals); } static void run (gchar *name, /* name of plugin */ gint nparams, /* number of in-paramters */ GParam * param, /* in-parameters */ gint *nreturn_vals, /* number of out-parameters */ GParam ** return_vals) /* out-parameters */ { /* Get the runmode from the in-parameters */ GRunModeType run_mode = param[0].data.d_int32; /* status variable, use it to check for errors in invocation usualy only during non-interactive calling */ GStatusType status = STATUS_SUCCESS; /*always return at least the status to the caller. */ static GParam values[1]; /* initialize the return of the status */ values[0].type = PARAM_STATUS; values[0].data.d_status = status; *nreturn_vals = 1; *return_vals = values; /* get image and drawable */ image_ID = param[1].data.d_int32; active_drawable = gimp_drawable_get (param[2].data.d_drawable); /*how are we running today? */ switch (run_mode) { case RUN_INTERACTIVE: /* Possibly retrieve data from a previous run */ gimp_get_data (PLUG_IN_NAME, &rotvals); rotvals.angle = rotvals.angle % NUM_ANGLES; /* Get information from the dialog */ if (!rotate_dialog()) return; break; case RUN_NONINTERACTIVE: /* check to see if invoked with the correct number of parameters */ if (nparams == NUMBER_IN_ARGS) { rotvals.angle = (gint) param[3].data.d_int32; rotvals.angle = rotvals.angle % NUM_ANGLES; rotvals.everything = (gint) param[4].data.d_int32; } else status = STATUS_CALLING_ERROR; break; case RUN_WITH_LAST_VALS: /* Possibly retrieve data from a previous run */ gimp_get_data (PLUG_IN_NAME, &rotvals); rotvals.angle = rotvals.angle % NUM_ANGLES; break; default: break; } /* switch */ if (status == STATUS_SUCCESS) { /* Set the number of tiles you want to cache */ /* gimp_tile_cache_ntiles ( ); */ /* Run the main function */ rotate(); /* If run mode is interactive, flush displays, else (script) don't do it, as the screen updates would make the scripts slow */ if (run_mode != RUN_NONINTERACTIVE) gimp_displays_flush (); /* Store variable states for next run */ if (run_mode == RUN_INTERACTIVE) gimp_set_data (PLUG_IN_NAME, &rotvals, sizeof (RotateValues)); } values[0].data.d_status = status; } /* Some helper functions */ gint32 my_gimp_selection_is_empty (gint32 image_ID) { GParam *return_vals; gint nreturn_vals; gint32 is_empty; /* initialize */ is_empty = 0; return_vals = gimp_run_procedure ("gimp_selection_is_empty", &nreturn_vals, PARAM_IMAGE, image_ID, PARAM_END); if (return_vals[0].data.d_status == STATUS_SUCCESS) is_empty = return_vals[1].data.d_int32; gimp_destroy_params (return_vals, nreturn_vals); return is_empty; } gint32 my_gimp_selection_float (gint32 image_ID, gint32 drawable_ID) { GParam *return_vals; gint nreturn_vals; gint32 layer_ID; return_vals = gimp_run_procedure ("gimp_selection_float", &nreturn_vals, PARAM_IMAGE, image_ID, PARAM_DRAWABLE, drawable_ID, PARAM_INT32, 0, PARAM_INT32, 0, PARAM_END); layer_ID= 0; if (return_vals[0].data.d_status == STATUS_SUCCESS) layer_ID = return_vals[1].data.d_layer; gimp_destroy_params (return_vals, nreturn_vals); return layer_ID; } static void rotate_compute_offsets (gint* offsetx, gint* offsety, gint image_width, gint image_height, gint width, gint height) { gint buffer; if (rotvals.everything) /* rotate around the image center */ { switch ( rotvals.angle ) { case 1: /* 90° */ buffer = *offsetx; *offsetx = image_height - *offsety - height; *offsety = buffer; break; case 2: /* 180° */ *offsetx = image_width - *offsetx - width; *offsety = image_height - *offsety - height; break; case 3: /* 270° */ buffer = *offsetx; *offsetx = *offsety; *offsety = image_width - buffer - width; } } else /* rotate around the drawable center */ { if (rotvals.angle != 2) { *offsetx = *offsetx + (width-height)/2 ; *offsety = *offsety + (height-width)/2 ; } } return; } static void rotate_drawable (GDrawable *drawable) { GPixelRgn srcPR, destPR; gint width, height, longside, bytes; gint row, col, byte; gint offsetx, offsety; gint was_preserve_transparency = FALSE; guchar *buffer, *src_row, *dest_row; /* initialize */ row = 0; /* Get the size of the input drawable. */ width = drawable->width; height = drawable->height; bytes = drawable->bpp; if (gimp_layer_get_preserve_transparency(drawable->id)) { was_preserve_transparency = TRUE; gimp_layer_set_preserve_transparency ( drawable->id,FALSE ); } if (rotvals.angle == 2) /* we're rotating by 180° */ { if ( !gimp_drawable_layer(drawable->id) ) exit; /* not a layer, probably a channel, abort operation */ gimp_tile_cache_ntiles ( 2*((width/gimp_tile_width())+1) ); gimp_pixel_rgn_init (&srcPR, drawable, 0, 0, width, height, FALSE, FALSE); gimp_pixel_rgn_init (&destPR, drawable, 0, 0, width, height, TRUE, TRUE); src_row = (guchar *) g_malloc (width * bytes); dest_row = (guchar *) g_malloc (width * bytes); for (row = 0; row < height; row++) { gimp_pixel_rgn_get_row (&srcPR, src_row, 0, row, width); for (col = 0; col < width; col++) { for (byte = 0; byte < bytes; byte++) { dest_row[col * bytes + byte] = src_row[(width - col - 1) * bytes + byte]; } } gimp_pixel_rgn_set_row (&destPR, dest_row, 0, (height - row - 1), width); if ((row % 5) == 0) gimp_progress_update ((double) row / (double) height); } g_free (src_row); g_free (dest_row); gimp_drawable_flush (drawable); gimp_drawable_merge_shadow (drawable->id, TRUE); gimp_drawable_update (drawable->id, 0, 0, width, height); } else /* we're rotating by 90° or 270° */ { (width > height) ? (longside = width) : (longside = height); if ( gimp_drawable_layer(drawable->id) ) { gimp_layer_resize(drawable->id, longside, longside, 0, 0); gimp_drawable_flush (drawable); drawable = gimp_drawable_get(drawable->id); } else /* not a layer... probably a channel... abort operation */ exit; gimp_tile_cache_ntiles ( ((longside/gimp_tile_width())+1) + ((longside/gimp_tile_height())+1) ); gimp_pixel_rgn_init (&srcPR, drawable, 0, 0, longside, longside, FALSE, FALSE); gimp_pixel_rgn_init (&destPR, drawable, 0, 0, longside, longside, TRUE, TRUE); buffer = g_malloc (longside * bytes); if (rotvals.angle == 1) /* we're rotating by 90° */ { for (row = 0; row < height; row++) { gimp_pixel_rgn_get_row (&srcPR, buffer, 0, row, width); gimp_pixel_rgn_set_col (&destPR, buffer, (height - row - 1), 0, width); if ((row % 5) == 0) gimp_progress_update ((double) row / (double) height); } } else /* we're rotating by 270° */ { for (col = 0; col < width; col++) { gimp_pixel_rgn_get_col (&srcPR, buffer, col, 0, height); gimp_pixel_rgn_set_row (&destPR, buffer, 0, (width - col - 1), height); if ((row % 5) == 0) gimp_progress_update ((double) row / (double) height); } } g_free (buffer); gimp_progress_update ( 1.0 ); gimp_drawable_flush (drawable); gimp_drawable_merge_shadow (drawable->id, TRUE); gimp_drawable_update (drawable->id, 0, 0, height, width); gimp_layer_resize(drawable->id, height, width, 0, 0); drawable = gimp_drawable_get(drawable->id); gimp_drawable_flush (drawable); gimp_drawable_update (drawable->id, 0, 0, height, width); } gimp_drawable_offsets (drawable->id, &offsetx, &offsety); rotate_compute_offsets (&offsetx, &offsety, gimp_image_width(image_ID), gimp_image_height(image_ID), width, height); gimp_layer_set_offsets (drawable->id, offsetx, offsety); if (was_preserve_transparency) gimp_layer_set_preserve_transparency ( drawable->id, TRUE ); return; } /* The main rotate function */ static void rotate (void) { gint nreturn_vals; GDrawable *drawable; gint32 *layers; gint i, nlayers; if (rotvals.angle == 0) return; /* if there's a selection and we try to rotate the whole image */ /* create an error message and exit */ if ( rotvals.everything ) { if ( !my_gimp_selection_is_empty (image_ID) ) { ErrorMessage("You can not rotate the whole image if there's a selection."); gimp_drawable_detach (active_drawable); return; } if ( gimp_layer_is_floating_selection (active_drawable->id) ) { ErrorMessage("You can not rotate the whole image if there's a floating selection."); gimp_drawable_detach (active_drawable); return; } } gimp_progress_init ("Rotating..."); gimp_run_procedure ("gimp_undo_push_group_start", &nreturn_vals, PARAM_IMAGE, image_ID, PARAM_END); if (rotvals.everything) /* rotate the whole image */ { gimp_drawable_detach (active_drawable); layers = gimp_image_get_layers (image_ID, &nlayers); for ( i=0; iid) ) active_drawable = gimp_drawable_get (my_gimp_selection_float (image_ID, active_drawable->id) ); rotate_drawable (active_drawable); gimp_drawable_detach (active_drawable); } gimp_run_procedure ("gimp_undo_push_group_end", &nreturn_vals, PARAM_IMAGE, image_ID, PARAM_END); return; } /* Rotate dialog */ static gint rotate_dialog (void) { GtkWidget *dialog; GtkWidget *button; GtkWidget *frame; GtkWidget *table; GtkWidget *radio_label; GtkWidget *unit_label; GtkWidget *hbox; GtkWidget *radio_button; GtkWidget *check_button; GSList *radio_group = NULL; gint radio_pressed[NUM_ANGLES]; gint everything; gint argc = 1; gint i; gchar **argv = g_new (gchar *, 1); argv[0] = g_strdup ("Rotate"); for (i=0;iaction_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 (dialog)); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area), button, TRUE, TRUE, 0); gtk_widget_show (button); /* parameter settings */ frame = gtk_frame_new ("Rotate clockwise by"); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); gtk_container_border_width (GTK_CONTAINER (frame), 6); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), frame, TRUE, TRUE, 0); /* table for radio_buttons */ table = gtk_table_new (6, 6, FALSE); gtk_table_set_row_spacings (GTK_TABLE (table), 8); gtk_table_set_col_spacings (GTK_TABLE (table), 1); gtk_container_border_width (GTK_CONTAINER (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); /* radio buttons */ /* 0 degrees */ radio_label = gtk_label_new ( angle_label[0] ); gtk_table_attach ( GTK_TABLE (table), radio_label, 2, 3, 0, 1, 0, 0, 0, 0); gtk_widget_show (radio_label); radio_button = gtk_radio_button_new ( radio_group ); radio_group = gtk_radio_button_group ( GTK_RADIO_BUTTON (radio_button) ); gtk_table_attach ( GTK_TABLE (table), radio_button, 2, 3, 1, 2, 0, 0, 0, 0); gtk_signal_connect ( GTK_OBJECT (radio_button), "toggled", (GtkSignalFunc) rotate_toggle_update, &radio_pressed[0]); gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (radio_button), radio_pressed[0]); gtk_widget_show (radio_button); /* 90 degrees */ radio_label = gtk_label_new ( angle_label[1] ); gtk_table_attach ( GTK_TABLE (table), radio_label, 4, 5, 2, 3, 0, 0, 0, 0); gtk_widget_show (radio_label); radio_button = gtk_radio_button_new ( radio_group ); radio_group = gtk_radio_button_group ( GTK_RADIO_BUTTON (radio_button) ); gtk_table_attach ( GTK_TABLE (table), radio_button, 3, 4, 2, 3, 0, 0, 0, 0); gtk_signal_connect ( GTK_OBJECT (radio_button), "toggled", (GtkSignalFunc) rotate_toggle_update, &radio_pressed[1]); gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (radio_button), radio_pressed[1]); gtk_widget_show (radio_button); /* 180 degrees */ radio_label = gtk_label_new ( angle_label[2] ); gtk_table_attach ( GTK_TABLE (table), radio_label, 2, 3, 4, 5, 0, 0, 0, 0); gtk_widget_show (radio_label); radio_button = gtk_radio_button_new ( radio_group ); radio_group = gtk_radio_button_group ( GTK_RADIO_BUTTON (radio_button) ); gtk_table_attach ( GTK_TABLE (table), radio_button, 2, 3, 3, 4, 0, 0, 0, 0); gtk_signal_connect ( GTK_OBJECT (radio_button), "toggled", (GtkSignalFunc) rotate_toggle_update, &radio_pressed[2]); gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (radio_button), radio_pressed[2]); gtk_widget_show (radio_button); /* 270 degrees */ radio_label = gtk_label_new ( angle_label[3] ); gtk_table_attach ( GTK_TABLE (table), radio_label, 0, 1, 2, 3, 0, 0, 0, 0); gtk_widget_show (radio_label); radio_button = gtk_radio_button_new ( radio_group ); radio_group = gtk_radio_button_group ( GTK_RADIO_BUTTON (radio_button) ); gtk_table_attach ( GTK_TABLE (table), radio_button, 1, 2, 2, 3, 0, 0, 0, 0); gtk_signal_connect ( GTK_OBJECT (radio_button), "toggled", (GtkSignalFunc) rotate_toggle_update, &radio_pressed[3]); gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (radio_button), radio_pressed[3]); gtk_widget_show (radio_button); /* label: degrees */ unit_label = gtk_label_new ( "degrees" ); gtk_table_attach ( GTK_TABLE (table), unit_label, 5, 6, 5, 6, 0, 0, 0, 0); gtk_widget_show (unit_label); gtk_widget_show (table); gtk_widget_show (frame); /* hbox for check-button */ hbox = gtk_hbox_new (FALSE, 1); gtk_container_border_width (GTK_CONTAINER (hbox), 6); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, TRUE, TRUE, 0); /* check button */ check_button = gtk_check_button_new_with_label ("Rotate the whole image"); gtk_box_pack_end (GTK_BOX (hbox), check_button, TRUE, TRUE, 0); gtk_signal_connect (GTK_OBJECT (check_button), "toggled", (GtkSignalFunc) rotate_toggle_update, &everything); gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (check_button), rotvals.everything); gtk_widget_show (check_button); gtk_widget_show (hbox); gtk_widget_show (dialog); gtk_main (); gdk_flush (); rotvals.angle=0; for (i = 0; i < NUM_ANGLES; i++) { if (radio_pressed[i]==TRUE) { rotvals.angle = i; break; } } rotvals.everything = everything; return rotint.run; } /* Rotate interface functions */ static void rotate_close_callback (GtkWidget *widget, gpointer data) { gtk_main_quit (); } static void rotate_ok_callback (GtkWidget *widget, gpointer data) { rotint.run = TRUE; gtk_widget_destroy (GTK_WIDGET (data)); } static void rotate_toggle_update (GtkWidget *widget, gpointer data) { gint *toggle_val; toggle_val = (gint *) data; if (GTK_TOGGLE_BUTTON (widget)->active) *toggle_val = TRUE; else *toggle_val = FALSE; } /* Error Message * * This code was stolen from Pavel Greenfield's Colormap Rotation plug-in */ static void ErrorMessage(gchar *message) { GtkWidget *window, *label, *button, *table; gchar **argv=g_new (gchar *, 1); gint argc=1; argv[0] = g_strdup ("rotate"); gtk_init (&argc, &argv); gtk_rc_parse (gimp_gtkrc ()); window=gtk_dialog_new(); gtk_window_position (GTK_WINDOW (window), GTK_WIN_POS_MOUSE); gtk_window_set_title(GTK_WINDOW(window), "Rotate Error Message"); gtk_signal_connect (GTK_OBJECT (window), "destroy", (GtkSignalFunc) rotate_close_callback, NULL); button = gtk_button_new_with_label ("Got It!"); GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_signal_connect (GTK_OBJECT (button), "clicked", (GtkSignalFunc) rotate_ok_callback, window); gtk_widget_grab_default (button); gtk_widget_show (button); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0); table=gtk_table_new(2,2,FALSE); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox),table,TRUE,TRUE,0); gtk_widget_show(table); label=gtk_label_new(""); gtk_label_set(GTK_LABEL(label),message); gtk_widget_show(label); gtk_table_attach(GTK_TABLE(table),label,0,1,0,1, GTK_FILL|GTK_EXPAND,GTK_FILL|GTK_EXPAND,15,15); gtk_widget_show(window); gtk_main (); }