/* 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. */ #include #include "gdk/gdkkeysyms.h" #include "appenv.h" #include "actionarea.h" #include "crop.h" #include "cursorutil.h" #include "draw_core.h" #include "drawable.h" #include "floating_sel.h" #include "gdisplay.h" #include "gimage_mask.h" #include "info_dialog.h" #include "tool_options_ui.h" #include "undo.h" #include "libgimp/gimpsizeentry.h" #include "libgimp/gimpintl.h" #define STATUSBAR_SIZE 128 /* possible crop functions */ #define CREATING 0 #define MOVING 1 #define RESIZING_LEFT 2 #define RESIZING_RIGHT 3 #define CROPPING 4 /* speed of key movement */ #define ARROW_VELOCITY 25 /* the crop structures */ typedef struct _Crop Crop; struct _Crop { DrawCore * core; /* Core select object */ int startx; /* starting x coord */ int starty; /* starting y coord */ int lastx; /* previous x coord */ int lasty; /* previous y coord */ int x1, y1; /* upper left hand coordinate */ int x2, y2; /* lower right hand coords */ int srw, srh; /* width and height of corners */ int tx1, ty1; /* transformed coords */ int tx2, ty2; /* */ int function; /* moving or resizing */ guint context_id; /* for the statusbar */ }; typedef struct _CropOptions CropOptions; struct _CropOptions { ToolOptions tool_options; int layer_only; int layer_only_d; GtkWidget *layer_only_w; int allow_enlarge; int allow_enlarge_d; GtkWidget *allow_enlarge_w; CropType type; CropType type_d; GtkWidget *type_w[2]; }; /* the crop tool options */ static CropOptions *crop_options = NULL; /* the crop tool info dialog */ static InfoDialog *crop_info = NULL; static gdouble orig_vals[2]; static gdouble size_vals[2]; static GtkWidget *origin_sizeentry; static GtkWidget *size_sizeentry; /* Crop type functions */ static void crop_button_press (Tool *, GdkEventButton *, gpointer); static void crop_button_release (Tool *, GdkEventButton *, gpointer); static void crop_motion (Tool *, GdkEventMotion *, gpointer); static void crop_cursor_update (Tool *, GdkEventMotion *, gpointer); static void crop_control (Tool *, int, gpointer); static void crop_arrow_keys_func (Tool *, GdkEventKey *, gpointer); static void crop_modifier_key_func (Tool *, GdkEventKey *, gpointer); /* Crop helper functions */ static void crop_recalc (Tool *, Crop *); static void crop_start (Tool *, Crop *); static void crop_adjust_guides (GImage *, int, int, int, int); /* Crop dialog functions */ static void crop_info_update (Tool *); static void crop_info_create (Tool *); static void crop_crop_callback (GtkWidget *, gpointer); static void crop_resize_callback (GtkWidget *, gpointer); static void crop_close_callback (GtkWidget *, gpointer); /* Crop area-select functions */ typedef enum { AUTO_CROP_NOTHING = 0, AUTO_CROP_ALPHA = 1, AUTO_CROP_COLOR = 2 } AutoCropType; typedef guchar * (*GetColorFunc) (GtkObject *, int, int); typedef AutoCropType (*ColorsEqualFunc) (guchar *, guchar *, int); static void crop_selection_callback (GtkWidget *, gpointer); static void crop_automatic_callback (GtkWidget *, gpointer); static AutoCropType crop_guess_bgcolor (GtkObject *, GetColorFunc, int, int, int, int, guchar *); static int crop_colors_equal (guchar *, guchar *, int); static int crop_colors_alpha (guchar *, guchar *, int); /* Crop dialog callback funtions */ static void crop_orig_changed (GtkWidget *, gpointer); static void crop_size_changed (GtkWidget *, gpointer); /* Functions */ static void crop_options_reset (void) { CropOptions *options = crop_options; gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (options->layer_only_w), options->layer_only_d); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(options->allow_enlarge_w), options->allow_enlarge_d); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (options->type_w[options->type_d]), TRUE); } static CropOptions * crop_options_new (void) { CropOptions *options; GtkWidget *vbox; GtkWidget *frame; gchar* type_label[2] = { N_("Crop"), N_("Resize") }; gint type_value[2] = { CROP_CROP, RESIZE_CROP }; /* the new crop tool options structure */ options = (CropOptions *) g_malloc (sizeof (CropOptions)); tool_options_init ((ToolOptions *) options, N_("Crop & Resize Options"), crop_options_reset); options->layer_only = options->layer_only_d = FALSE; options->allow_enlarge = options->allow_enlarge_d = FALSE; options->type = options->type_d = CROP_CROP; /* the main vbox */ vbox = options->tool_options.main_vbox; /* layer toggle */ options->layer_only_w = gtk_check_button_new_with_label(_("Current layer only")); gtk_box_pack_start (GTK_BOX (vbox), options->layer_only_w, FALSE, FALSE, 0); gtk_signal_connect (GTK_OBJECT (options->layer_only_w), "toggled", (GtkSignalFunc) tool_options_toggle_update, &options->layer_only); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (options->layer_only_w), options->layer_only_d); gtk_widget_show (options->layer_only_w); /* enlarge toggle */ options->allow_enlarge_w = gtk_check_button_new_with_label (_("Allow Enlarging")); gtk_box_pack_start (GTK_BOX (vbox), options->allow_enlarge_w, FALSE, FALSE, 0); gtk_signal_connect (GTK_OBJECT (options->allow_enlarge_w), "toggled", (GtkSignalFunc) tool_options_toggle_update, &options->allow_enlarge); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (options->allow_enlarge_w), options->allow_enlarge_d); gtk_widget_show (options->allow_enlarge_w); /* tool toggle */ frame = tool_options_radio_buttons_new (N_("Tool Toggle"), &options->type, options->type_w, type_label, type_value, 2); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (options->type_w[options->type_d]), TRUE); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); return options; } static void crop_button_press (Tool *tool, GdkEventButton *bevent, gpointer gdisp_ptr) { Crop * crop; GDisplay * gdisp; gdisp = (GDisplay *) gdisp_ptr; crop = (Crop *) tool->private; if (tool->state == INACTIVE) crop->function = CREATING; else if (gdisp_ptr != tool->gdisp_ptr) crop->function = CREATING; else { /* If the cursor is in either the upper left or lower right boxes, The new function will be to resize the current crop area */ if (bevent->x == BOUNDS (bevent->x, crop->x1, crop->x1 + crop->srw) && bevent->y == BOUNDS (bevent->y, crop->y1, crop->y1 + crop->srh)) crop->function = RESIZING_LEFT; else if (bevent->x == BOUNDS (bevent->x, crop->x2 - crop->srw, crop->x2) && bevent->y == BOUNDS (bevent->y, crop->y2 - crop->srh, crop->y2)) crop->function = RESIZING_RIGHT; /* If the cursor is in either the upper right or lower left boxes, The new function will be to translate the current crop area */ else if ((bevent->x == BOUNDS (bevent->x, crop->x1, crop->x1 + crop->srw) && bevent->y == BOUNDS (bevent->y, crop->y2 - crop->srh, crop->y2)) || (bevent->x == BOUNDS (bevent->x, crop->x2 - crop->srw, crop->x2) && bevent->y == BOUNDS (bevent->y, crop->y1, crop->y1 + crop->srh))) crop->function = MOVING; /* If the pointer is in the rectangular region, crop or resize it! */ else if (bevent->x > crop->x1 && bevent->x < crop->x2 && bevent->y > crop->y1 && bevent->y < crop->y2) crop->function = CROPPING; /* otherwise, the new function will be creating, since we want to start anew */ else crop->function = CREATING; } if (crop->function == CREATING) { if (tool->state == ACTIVE) draw_core_stop (crop->core, tool); tool->gdisp_ptr = gdisp_ptr; gdisplay_untransform_coords (gdisp, bevent->x, bevent->y, &crop->tx1, &crop->ty1, TRUE, FALSE); crop->tx2 = crop->tx1; crop->ty2 = crop->ty1; crop_start (tool, crop); } gdisplay_untransform_coords (gdisp, bevent->x, bevent->y, &crop->startx, &crop->starty, TRUE, FALSE); crop->lastx = crop->startx; crop->lasty = crop->starty; gdk_pointer_grab (gdisp->canvas->window, FALSE, GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, NULL, NULL, bevent->time); tool->state = ACTIVE; } static void crop_button_release (Tool *tool, GdkEventButton *bevent, gpointer gdisp_ptr) { Crop * crop; GDisplay *gdisp; crop = (Crop *) tool->private; gdisp = (GDisplay *) gdisp_ptr; gdk_pointer_ungrab (bevent->time); gdk_flush (); gtk_statusbar_pop (GTK_STATUSBAR (gdisp->statusbar), crop->context_id); if (! (bevent->state & GDK_BUTTON3_MASK)) { if (crop->function == CROPPING) { if (crop_options->type == CROP_CROP) crop_image (gdisp->gimage, crop->tx1, crop->ty1, crop->tx2, crop->ty2, crop_options->layer_only, TRUE); else crop_image (gdisp->gimage, crop->tx1, crop->ty1, crop->tx2, crop->ty2, crop_options->layer_only, FALSE); /* Finish the tool */ draw_core_stop (crop->core, tool); info_dialog_popdown (crop_info); tool->state = INACTIVE; } else crop_info_update (tool); } } static void crop_adjust_guides (GImage *gimage, int x1, int y1, int x2, int y2) { GList * glist; Guide * guide; gint remove_guide; /* initialize the traverser */ glist = gimage->guides; while (glist != NULL) { guide = (Guide *) glist->data; remove_guide = FALSE; switch (guide->orientation) { case HORIZONTAL_GUIDE: if ((guide->position < y1) ||(guide->position > y2)) remove_guide = TRUE; break; case VERTICAL_GUIDE: if ((guide->position < x1) ||(guide->position > x2)) remove_guide = TRUE; break; } /* edit the guide */ gdisplays_expose_guide (gimage, guide); gdisplays_flush(); if (remove_guide) { guide->position = -1; guide = NULL; } else { if (guide->orientation == HORIZONTAL_GUIDE) { guide->position -= y1 ; } else { guide->position -= x1; } } glist = glist->next; } } static void crop_motion (Tool *tool, GdkEventMotion *mevent, gpointer gdisp_ptr) { Crop * crop; GDisplay * gdisp; Layer * layer; int x1, y1, x2, y2; int curx, cury; int inc_x, inc_y; gchar size[STATUSBAR_SIZE]; int min_x, min_y, max_x, max_y; crop = (Crop *) tool->private; gdisp = (GDisplay *) gdisp_ptr; /* This is the only case when the motion events should be ignored-- we're just waiting for the button release event to crop the image */ if (crop->function == CROPPING) return; gdisplay_untransform_coords (gdisp, mevent->x, mevent->y, &curx, &cury, TRUE, FALSE); x1 = crop->startx; y1 = crop->starty; x2 = curx; y2 = cury; inc_x = (x2 - x1); inc_y = (y2 - y1); /* If there have been no changes... return */ if (crop->lastx == curx && crop->lasty == cury) return; draw_core_pause (crop->core, tool); if (crop_options->layer_only) { layer = (gdisp->gimage)->active_layer; drawable_offsets (GIMP_DRAWABLE(layer), &min_x, &min_y); max_x = drawable_width (GIMP_DRAWABLE(layer)) + min_x; max_y = drawable_height (GIMP_DRAWABLE(layer)) + min_y; } else { min_x = min_y = 0; max_x = gdisp->gimage->width; max_y = gdisp->gimage->height; } switch (crop->function) { case CREATING : if (!crop_options->allow_enlarge) { x1 = BOUNDS (x1, min_x, max_x); y1 = BOUNDS (y1, min_y, max_y); x2 = BOUNDS (x2, min_x, max_x); y2 = BOUNDS (y2, min_y, max_y); } break; case RESIZING_LEFT : x1 = crop->tx1 + inc_x; y1 = crop->ty1 + inc_y; if (!crop_options->allow_enlarge) { x1 = BOUNDS (x1, min_x, max_x); y1 = BOUNDS (y1, min_y, max_y); } x2 = MAXIMUM (x1, crop->tx2); y2 = MAXIMUM (y1, crop->ty2); crop->startx = curx; crop->starty = cury; break; case RESIZING_RIGHT : x2 = crop->tx2 + inc_x; y2 = crop->ty2 + inc_y; if (!crop_options->allow_enlarge) { x2 = BOUNDS (x2, min_x, max_x); y2 = BOUNDS (y2, min_y, max_y); } x1 = MINIMUM (crop->tx1, x2); y1 = MINIMUM (crop->ty1, y2); crop->startx = curx; crop->starty = cury; break; case MOVING : if (!crop_options->allow_enlarge) { inc_x = BOUNDS (inc_x, min_x - crop->tx1, max_x - crop->tx2); inc_y = BOUNDS (inc_y, min_y - crop->ty1, max_y - crop->ty2); } x1 = crop->tx1 + inc_x; x2 = crop->tx2 + inc_x; y1 = crop->ty1 + inc_y; y2 = crop->ty2 + inc_y; crop->startx = curx; crop->starty = cury; break; } /* make sure that the coords are in bounds */ crop->tx1 = MINIMUM (x1, x2); crop->ty1 = MINIMUM (y1, y2); crop->tx2 = MAXIMUM (x1, x2); crop->ty2 = MAXIMUM (y1, y2); crop->lastx = curx; crop->lasty = cury; /* recalculate the coordinates for crop_draw based on the new values */ crop_recalc (tool, crop); if (crop->function == CREATING || crop->function == RESIZING_LEFT || crop->function == RESIZING_RIGHT) { gtk_statusbar_pop (GTK_STATUSBAR (gdisp->statusbar), crop->context_id); if (gdisp->dot_for_dot) { g_snprintf (size, STATUSBAR_SIZE, gdisp->cursor_format_str, _("Crop: "), (crop->tx2 - crop->tx1), " x ", (crop->ty2 - crop->ty1)); } else /* show real world units */ { gdouble unit_factor = gimp_unit_get_factor (gdisp->gimage->unit); g_snprintf (size, STATUSBAR_SIZE, gdisp->cursor_format_str, _("Crop: "), (gdouble) (crop->tx2 - crop->tx1) * unit_factor / gdisp->gimage->xresolution, " x ", (gdouble) (crop->ty2 - crop->ty1) * unit_factor / gdisp->gimage->yresolution); } gtk_statusbar_push (GTK_STATUSBAR (gdisp->statusbar), crop->context_id, size); } draw_core_resume (crop->core, tool); } static void crop_cursor_update (Tool *tool, GdkEventMotion *mevent, gpointer gdisp_ptr) { GDisplay *gdisp; GdkCursorType ctype; Crop * crop; gdisp = (GDisplay *) gdisp_ptr; crop = (Crop *) tool->private; if (tool->state == INACTIVE || (tool->state == ACTIVE && tool->gdisp_ptr != gdisp_ptr)) ctype = GDK_CROSS; else if (mevent->x == BOUNDS (mevent->x, crop->x1, crop->x1 + crop->srw) && mevent->y == BOUNDS (mevent->y, crop->y1, crop->y1 + crop->srh)) ctype = GDK_TOP_LEFT_CORNER; else if (mevent->x == BOUNDS (mevent->x, crop->x2 - crop->srw, crop->x2) && mevent->y == BOUNDS (mevent->y, crop->y2 - crop->srh, crop->y2)) ctype = GDK_BOTTOM_RIGHT_CORNER; else if (mevent->x == BOUNDS (mevent->x, crop->x1, crop->x1 + crop->srw) && mevent->y == BOUNDS (mevent->y, crop->y2 - crop->srh, crop->y2)) ctype = GDK_FLEUR; else if (mevent->x == BOUNDS (mevent->x, crop->x2 - crop->srw, crop->x2) && mevent->y == BOUNDS (mevent->y, crop->y1, crop->y1 + crop->srh)) ctype = GDK_FLEUR; else if (mevent->x > crop->x1 && mevent->x < crop->x2 && mevent->y > crop->y1 && mevent->y < crop->y2) { if ( crop_options->type == CROP_CROP ) ctype = GDK_ICON; else ctype = GDK_SIZING; } else ctype = GDK_CROSS; gdisplay_install_tool_cursor (gdisp, ctype); } static void crop_arrow_keys_func (Tool *tool, GdkEventKey *kevent, gpointer gdisp_ptr) { int inc_x, inc_y; GDisplay * gdisp; Layer * layer; Crop * crop; int min_x, min_y, max_x, max_y; gdisp = (GDisplay *) gdisp_ptr; if (tool->state == ACTIVE) { crop = (Crop *) tool->private; inc_x = inc_y = 0; switch (kevent->keyval) { case GDK_Up : inc_y = -1; break; case GDK_Left : inc_x = -1; break; case GDK_Right : inc_x = 1; break; case GDK_Down : inc_y = 1; break; } /* If the shift key is down, move by an accelerated increment */ if (kevent->state & GDK_SHIFT_MASK) { inc_y *= ARROW_VELOCITY; inc_x *= ARROW_VELOCITY; } draw_core_pause (crop->core, tool); if (crop_options->layer_only) { layer = (gdisp->gimage)->active_layer; drawable_offsets (GIMP_DRAWABLE(layer), &min_x, &min_y); max_x = drawable_width (GIMP_DRAWABLE(layer)) + min_x; max_y = drawable_height (GIMP_DRAWABLE(layer)) + min_y; } else { min_x = min_y = 0; max_x = gdisp->gimage->width; max_y = gdisp->gimage->height; } if (kevent->state & GDK_CONTROL_MASK) /* RESIZING */ { crop->tx2 = crop->tx2 + inc_x; crop->ty2 = crop->ty2 + inc_y; if (!crop_options->allow_enlarge) { crop->tx2 = BOUNDS (crop->tx2, min_x, max_x); crop->ty2 = BOUNDS (crop->ty2, min_y, max_y); } crop->tx1 = MINIMUM (crop->tx1, crop->tx2); crop->ty1 = MINIMUM (crop->ty1, crop->ty2); } else { if (!crop_options->allow_enlarge) { inc_x = BOUNDS (inc_x, -crop->tx1, gdisp->gimage->width - crop->tx2); inc_y = BOUNDS (inc_y, -crop->ty1, gdisp->gimage->height - crop->ty2); } crop->tx1 += inc_x; crop->tx2 += inc_x; crop->ty1 += inc_y; crop->ty2 += inc_y; } crop_recalc (tool, crop); draw_core_resume (crop->core, tool); } } static void crop_modifier_key_func (Tool *tool, GdkEventKey *kevent, gpointer gdisp_ptr) { switch (kevent->keyval) { case GDK_Alt_L: case GDK_Alt_R: gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (crop_options->allow_enlarge_w), !crop_options->allow_enlarge); break; case GDK_Shift_L: case GDK_Shift_R: break; case GDK_Control_L: case GDK_Control_R: if (crop_options->type == CROP_CROP) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (crop_options->type_w[RESIZE_CROP]), TRUE); else gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (crop_options->type_w[CROP_CROP]), TRUE); break; } } static void crop_control (Tool *tool, int action, gpointer gdisp_ptr) { Crop * crop; crop = (Crop *) tool->private; switch (action) { case PAUSE : draw_core_pause (crop->core, tool); break; case RESUME : crop_recalc (tool, crop); draw_core_resume (crop->core, tool); break; case HALT : draw_core_stop (crop->core, tool); info_dialog_popdown (crop_info); break; } } void crop_draw (Tool *tool) { Crop * crop; GDisplay * gdisp; #define SRW 10 #define SRH 10 gdisp = (GDisplay *) tool->gdisp_ptr; crop = (Crop *) tool->private; gdk_draw_line (crop->core->win, crop->core->gc, crop->x1, crop->y1, gdisp->disp_width, crop->y1); gdk_draw_line (crop->core->win, crop->core->gc, crop->x1, crop->y1, crop->x1, gdisp->disp_height); gdk_draw_line (crop->core->win, crop->core->gc, crop->x2, crop->y2, 0, crop->y2); gdk_draw_line (crop->core->win, crop->core->gc, crop->x2, crop->y2, crop->x2, 0); crop->srw = ((crop->x2 - crop->x1) < SRW) ? (crop->x2 - crop->x1) : SRW; crop->srh = ((crop->y2 - crop->y1) < SRH) ? (crop->y2 - crop->y1) : SRH; gdk_draw_rectangle (crop->core->win, crop->core->gc, 1, crop->x1, crop->y1, crop->srw, crop->srh); gdk_draw_rectangle (crop->core->win, crop->core->gc, 1, crop->x2 - crop->srw, crop->y2-crop->srh, crop->srw, crop->srh); gdk_draw_rectangle (crop->core->win, crop->core->gc, 1, crop->x2 - crop->srw, crop->y1, crop->srw, crop->srh); gdk_draw_rectangle (crop->core->win, crop->core->gc, 1, crop->x1, crop->y2-crop->srh, crop->srw, crop->srh); crop_info_update (tool); } Tool * tools_new_crop () { Tool * tool; Crop * private; /* The tool options */ if (! crop_options) { crop_options = crop_options_new (); tools_register (CROP, (ToolOptions *) crop_options); } tool = (Tool *) g_malloc (sizeof (Tool)); private = (Crop *) g_malloc (sizeof (Crop)); private->core = draw_core_new (crop_draw); private->startx = private->starty = 0; private->function = CREATING; tool->type = CROP; tool->state = INACTIVE; tool->scroll_lock = 0; /* Allow scrolling */ tool->auto_snap_to = TRUE; tool->private = (void *) private; tool->button_press_func = crop_button_press; tool->button_release_func = crop_button_release; tool->motion_func = crop_motion; tool->arrow_keys_func = crop_arrow_keys_func; tool->modifier_key_func = crop_modifier_key_func; tool->cursor_update_func = crop_cursor_update; tool->control_func = crop_control; tool->preserve = TRUE; /* XXX Check me */ return tool; } void tools_free_crop (Tool *tool) { Crop * crop; crop = (Crop *) tool->private; if (tool->state == ACTIVE) draw_core_stop (crop->core, tool); draw_core_free (crop->core); if (crop_info) { info_dialog_popdown (crop_info); crop_info = NULL; } g_free (crop); } void crop_image (GImage *gimage, int x1, int y1, int x2, int y2, int layer_only, int crop_layers) { Layer *layer; Layer *floating_layer; Channel *channel; GList *guide_list_ptr; GSList *list; int width, height; int lx1, ly1, lx2, ly2; int off_x, off_y; int doff_x, doff_y; width = x2 - x1; height = y2 - y1; /* Make sure new width and height are non-zero */ if (width && height) { gimp_add_busy_cursors(); if (layer_only) { undo_push_group_start (gimage, LAYER_RESIZE_UNDO); layer = gimage->active_layer; if (layer_is_floating_sel (layer)) floating_sel_relax (layer, TRUE); drawable_offsets (GIMP_DRAWABLE(layer), &doff_x, &doff_y); off_x = (doff_x - x1); off_y = (doff_y - y1); layer_resize (layer, width, height, off_x, off_y); if (layer_is_floating_sel (layer)) floating_sel_rigor (layer, TRUE); undo_push_group_end (gimage); } else { floating_layer = gimage_floating_sel (gimage); undo_push_group_start (gimage, CROP_UNDO); /* relax the floating layer */ if (floating_layer) floating_sel_relax (floating_layer, TRUE); /* Push the image size to the stack */ undo_push_gimage_mod (gimage); /* Set the new width and height */ gimage->width = width; gimage->height = height; /* Resize all channels */ list = gimage->channels; while (list) { channel = (Channel *) list->data; channel_resize (channel, width, height, -x1, -y1); list = g_slist_next (list); } /* Don't forget the selection mask! */ channel_resize (gimage->selection_mask, width, height, -x1, -y1); gimage_mask_invalidate (gimage); /* crop all layers */ list = gimage->layers; while (list) { GSList * next; layer = (Layer *) list->data; next = g_slist_next (list); layer_translate (layer, -x1, -y1); drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y); if (crop_layers) { drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y); lx1 = BOUNDS (off_x, 0, gimage->width); ly1 = BOUNDS (off_y, 0, gimage->height); lx2 = BOUNDS ((drawable_width (GIMP_DRAWABLE (layer)) + off_x), 0, gimage->width); ly2 = BOUNDS ((drawable_height (GIMP_DRAWABLE (layer)) + off_y), 0, gimage->height); width = lx2 - lx1; height = ly2 - ly1; if (width && height) layer_resize (layer, width, height, -(lx1 - off_x), -(ly1 - off_y)); else gimage_remove_layer (gimage, layer); } list = next; } /* Make sure the projection matches the gimage size */ gimage_projection_realloc (gimage); /* rigor the floating layer */ if (floating_layer) floating_sel_rigor (floating_layer, TRUE); guide_list_ptr = gimage->guides; while ( guide_list_ptr != NULL) { undo_push_guide (gimage, (Guide *)guide_list_ptr->data); guide_list_ptr = guide_list_ptr->next; } undo_push_group_end (gimage); /* Adjust any guides we might have laying about */ crop_adjust_guides (gimage, x1, y1, x2, y2); /* shrink wrap and update all views */ channel_invalidate_previews (gimage); layer_invalidate_previews (gimage); gimage_invalidate_preview (gimage); gdisplays_update_full (gimage); gdisplays_shrink_wrap (gimage); } gimp_remove_busy_cursors(NULL); gdisplays_flush (); } } static void crop_recalc (Tool *tool, Crop *crop) { GDisplay * gdisp; gdisp = (GDisplay *) tool->gdisp_ptr; gdisplay_transform_coords (gdisp, crop->tx1, crop->ty1, &crop->x1, &crop->y1, FALSE); gdisplay_transform_coords (gdisp, crop->tx2, crop->ty2, &crop->x2, &crop->y2, FALSE); } static void crop_start (Tool *tool, Crop *crop) { static GDisplay * old_gdisp = NULL; GDisplay * gdisp; gdisp = (GDisplay *) tool->gdisp_ptr; crop_recalc (tool, crop); if (! crop_info) crop_info_create (tool); gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (origin_sizeentry), 0, gdisp->gimage->xresolution, FALSE); gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (origin_sizeentry), 1, gdisp->gimage->yresolution, FALSE); gimp_size_entry_set_size (GIMP_SIZE_ENTRY (origin_sizeentry), 0, 0, gdisp->gimage->width); gimp_size_entry_set_size (GIMP_SIZE_ENTRY (origin_sizeentry), 1, 0, gdisp->gimage->height); gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (size_sizeentry), 0, gdisp->gimage->xresolution, FALSE); gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (size_sizeentry), 1, gdisp->gimage->yresolution, FALSE); gimp_size_entry_set_size (GIMP_SIZE_ENTRY (size_sizeentry), 0, 0, gdisp->gimage->width); gimp_size_entry_set_size (GIMP_SIZE_ENTRY (size_sizeentry), 1, 0, gdisp->gimage->height); if (old_gdisp != gdisp) { gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (origin_sizeentry), gdisp->gimage->unit) ; gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (size_sizeentry), gdisp->gimage->unit); if (gdisp->dot_for_dot) { gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (origin_sizeentry), UNIT_PIXEL); gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (size_sizeentry), UNIT_PIXEL); } } old_gdisp = gdisp; /* initialize the statusbar display */ crop->context_id = gtk_statusbar_get_context_id (GTK_STATUSBAR (gdisp->statusbar), "crop"); gtk_statusbar_push (GTK_STATUSBAR (gdisp->statusbar), crop->context_id, _("Crop: 0 x 0")); draw_core_start (crop->core, gdisp->canvas->window, tool); } /*******************************************************/ /* Crop dialog functions */ /*******************************************************/ static ActionAreaItem action_items[3] = { { N_("Crop"), crop_crop_callback, NULL, NULL }, { N_("Resize"), crop_resize_callback, NULL, NULL }, { N_("Close"), crop_close_callback, NULL, NULL }, }; static void crop_info_create (Tool *tool) { GDisplay *gdisp; GtkWidget *spinbutton; GtkWidget *bbox; GtkWidget *button; gdisp = (GDisplay *) tool->gdisp_ptr; /* create the info dialog */ crop_info = info_dialog_new (_("Crop & Resize Information")); /* add the information fields */ spinbutton = info_dialog_add_spinbutton (crop_info, _("Origin X:"), NULL, -1, 1, 1, 10, 1, 1, 2, NULL, NULL); origin_sizeentry = info_dialog_add_sizeentry (crop_info, _("Y:"), orig_vals, 1, gdisp->dot_for_dot ? UNIT_PIXEL : gdisp->gimage->unit, "%a", TRUE, TRUE, FALSE, GIMP_SIZE_ENTRY_UPDATE_SIZE, crop_orig_changed, tool); gimp_size_entry_add_field (GIMP_SIZE_ENTRY (origin_sizeentry), GTK_SPIN_BUTTON (spinbutton), NULL); gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (origin_sizeentry), 0, gdisp->gimage->xresolution, FALSE); gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (origin_sizeentry), 1, gdisp->gimage->yresolution, FALSE); gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (origin_sizeentry), 0, -65536, 65536); gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (origin_sizeentry), 1, -65536, 65536); gimp_size_entry_set_size (GIMP_SIZE_ENTRY (origin_sizeentry), 0, 0, gdisp->gimage->width); gimp_size_entry_set_size (GIMP_SIZE_ENTRY (origin_sizeentry), 1, 0, gdisp->gimage->height); gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (origin_sizeentry), 0, orig_vals[0]); gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (origin_sizeentry), 1, orig_vals[1]); spinbutton = info_dialog_add_spinbutton (crop_info, _("Width:"), NULL, -1, 1, 1, 10, 1, 1, 2, NULL, NULL); size_sizeentry = info_dialog_add_sizeentry (crop_info, _("Height:"), size_vals, 1, gdisp->dot_for_dot ? UNIT_PIXEL : gdisp->gimage->unit, "%a", TRUE, TRUE, FALSE, GIMP_SIZE_ENTRY_UPDATE_SIZE, crop_size_changed, tool); gimp_size_entry_add_field (GIMP_SIZE_ENTRY (size_sizeentry), GTK_SPIN_BUTTON (spinbutton), NULL); gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (size_sizeentry), 0, gdisp->gimage->xresolution, FALSE); gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (size_sizeentry), 1, gdisp->gimage->yresolution, FALSE); gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (size_sizeentry), 0, -65536, 65536); gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (size_sizeentry), 1, -65536, 65536); gimp_size_entry_set_size (GIMP_SIZE_ENTRY (size_sizeentry), 0, 0, gdisp->gimage->width); gimp_size_entry_set_size (GIMP_SIZE_ENTRY (size_sizeentry), 1, 0, gdisp->gimage->height); gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (size_sizeentry), 0, size_vals[0]); gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (size_sizeentry), 1, size_vals[1]); gtk_table_set_row_spacing (GTK_TABLE (crop_info->info_table), 0, 0); gtk_table_set_row_spacing (GTK_TABLE (crop_info->info_table), 1, 6); gtk_table_set_row_spacing (GTK_TABLE (crop_info->info_table), 2, 0); /* Create the area selection buttons */ bbox = gtk_hbutton_box_new (); gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD); gtk_button_box_set_spacing (GTK_BUTTON_BOX (bbox), 4); button = gtk_button_new_with_label (_("From Selection")); gtk_container_add( GTK_CONTAINER(bbox), button); gtk_signal_connect(GTK_OBJECT (button) , "clicked", (GtkSignalFunc) crop_selection_callback, NULL); gtk_widget_show (button); button = gtk_button_new_with_label (_("Automatic")); gtk_container_add(GTK_CONTAINER (bbox), button); gtk_signal_connect(GTK_OBJECT (button) , "clicked", (GtkSignalFunc) crop_automatic_callback, NULL); gtk_widget_show (button); gtk_box_pack_start (GTK_BOX (crop_info->vbox), bbox, FALSE, FALSE, 2); gtk_widget_show (bbox); /* Create the action area */ build_action_area (GTK_DIALOG (crop_info->shell), action_items, 3, 0); } static void crop_info_update (Tool *tool) { Crop * crop; GDisplay * gdisp; crop = (Crop *) tool->private; gdisp = (GDisplay *) tool->gdisp_ptr; orig_vals[0] = crop->tx1; orig_vals[1] = crop->ty1; size_vals[0] = crop->tx2 - crop->tx1; size_vals[1] = crop->ty2 - crop->ty1; info_dialog_update (crop_info); info_dialog_popup (crop_info); } static void crop_crop_callback (GtkWidget *w, gpointer client_data) { Tool * tool; Crop * crop; GDisplay * gdisp; tool = active_tool; crop = (Crop *) tool->private; gdisp = (GDisplay *) tool->gdisp_ptr; crop_image (gdisp->gimage, crop->tx1, crop->ty1, crop->tx2, crop->ty2, crop_options->layer_only, TRUE); /* Finish the tool */ draw_core_stop (crop->core, tool); info_dialog_popdown (crop_info); tool->state = INACTIVE; } static void crop_resize_callback (GtkWidget *w, gpointer client_data) { Tool * tool; Crop * crop; GDisplay * gdisp; tool = active_tool; crop = (Crop *) tool->private; gdisp = (GDisplay *) tool->gdisp_ptr; crop_image (gdisp->gimage, crop->tx1, crop->ty1, crop->tx2, crop->ty2, crop_options->layer_only, FALSE); /* Finish the tool */ draw_core_stop (crop->core, tool); info_dialog_popdown (crop_info); tool->state = INACTIVE; } static void crop_selection_callback (GtkWidget *w, gpointer client_data) { Tool * tool; Crop * crop; Layer * layer; GDisplay * gdisp; tool = active_tool; crop = (Crop *) tool->private; gdisp = (GDisplay *) tool->gdisp_ptr; draw_core_pause (crop->core, tool); if (! gimage_mask_bounds (gdisp->gimage, &crop->tx1, &crop->ty1, &crop->tx2, &crop->ty2)) { if (crop_options->layer_only) { layer = (gdisp->gimage)->active_layer; drawable_offsets (GIMP_DRAWABLE(layer), &crop->tx1, &crop->ty1); crop->tx2 = drawable_width (GIMP_DRAWABLE(layer)) + crop->tx1; crop->ty2 = drawable_height (GIMP_DRAWABLE(layer)) + crop->ty1; } else { crop->tx1 = crop->ty1 = 0; crop->tx2 = gdisp->gimage->width; crop->ty2 = gdisp->gimage->height; } } crop_recalc (tool, crop); draw_core_resume (crop->core, tool); } static void crop_automatic_callback (GtkWidget *w, gpointer client_data) { Tool * tool; Crop * crop; GDisplay * gdisp; GimpDrawable * active_drawable = NULL; GetColorFunc get_color_func; ColorsEqualFunc colors_equal_func; GtkObject *get_color_obj; guchar bgcolor[4] = {0, 0, 0, 0}; gint has_alpha = FALSE; PixelRegion PR; guchar *buffer; gint width, height, bytes; gint x, y, abort; gint x1, y1, x2, y2; tool = active_tool; crop = (Crop *) tool->private; gdisp = (GDisplay *) tool->gdisp_ptr; draw_core_pause (crop->core, tool); gimp_add_busy_cursors (); if (crop_options->layer_only) { if (!(active_drawable = gimage_active_drawable (gdisp->gimage))) return; width = drawable_width (GIMP_DRAWABLE (active_drawable)); height = drawable_height (GIMP_DRAWABLE (active_drawable)); bytes = drawable_bytes (GIMP_DRAWABLE (active_drawable)); if (drawable_has_alpha (GIMP_DRAWABLE (active_drawable))) has_alpha = TRUE; get_color_obj = GTK_OBJECT (active_drawable); get_color_func = (GetColorFunc) gimp_drawable_get_color_at; drawable_offsets (GIMP_DRAWABLE (active_drawable), &crop->tx1, &crop->ty1); crop->tx2 = width + crop->tx1; crop->ty2 = height + crop->ty1; } else { width = gdisp->gimage->width; height = gdisp->gimage->height; get_color_obj = GTK_OBJECT (gdisp->gimage); get_color_func = (GetColorFunc) gimp_image_get_color_at; bytes = gimp_image_composite_bytes (gdisp->gimage); has_alpha = TRUE; crop->tx1 = crop->ty1 = 0; crop->tx2 = width; crop->ty2 = height; } switch (crop_guess_bgcolor (get_color_obj, get_color_func, width, height, bytes, has_alpha, bgcolor)) { case AUTO_CROP_ALPHA: colors_equal_func = (ColorsEqualFunc) crop_colors_alpha; break; case AUTO_CROP_COLOR: colors_equal_func = (ColorsEqualFunc) crop_colors_equal; break; default: goto FINISH; } if (crop_options->layer_only) pixel_region_init (&PR, drawable_data (active_drawable), 0, 0, width, height, FALSE); else pixel_region_init (&PR, gimp_image_composite (gdisp->gimage), 0, 0, width, height, FALSE); buffer = g_malloc((width > height ? width : height) * bytes); x1 = x2 = y1 = y2 = 0; /* Check how many of the top lines are uniform/transparent. */ abort = FALSE; for (y = 0; y < height && !abort; y++) { pixel_region_get_row (&PR, 0, y, width, buffer, 1); for (x = 0; x < width && !abort; x++) abort = !(colors_equal_func) (bgcolor, buffer + x * bytes, bytes); } if (y == height && !abort) { g_free (buffer); goto FINISH; } y1 = y - 1; /* Check how many of the bottom lines are uniform/transparent. */ abort = FALSE; for (y = height - 1; y >= y1 && !abort; y--) { pixel_region_get_row (&PR, 0, y, width, buffer, 1); for (x = 0; x < width && !abort; x++) abort = !(colors_equal_func) (bgcolor, buffer + x * bytes, bytes); } y2 = y + 1; /* Check how many of the left lines are uniform/transparent. */ abort = FALSE; for (x = 0; x < width && !abort; x++) { pixel_region_get_col (&PR, x, y1, y2 - y1 + 1, buffer, 1); for (y = 0; y <= (y2 - y1) && !abort; y++) abort = !(colors_equal_func) (bgcolor, buffer + y * bytes, bytes); } x1 = x - 1; /* Check how many of the right lines are uniform/transparent. */ abort = FALSE; for (x = width - 1; x >= x1 && !abort; x--) { pixel_region_get_col (&PR, x, y1, y2 - y1 + 1, buffer, 1); for (y = 0; y <= (y2 - y1) && !abort; y++) abort = !(colors_equal_func) (bgcolor, buffer + y * bytes, bytes); } x2 = x + 1; g_free (buffer); crop->tx2 = crop->tx1 + x2 + 1; crop->ty2 = crop->ty1 + y2 + 1; crop->tx1 += x1; crop->ty1 += y1; FINISH: crop_recalc (tool, crop); gimp_remove_busy_cursors (NULL); draw_core_resume (crop->core, tool); return; } static AutoCropType crop_guess_bgcolor (GtkObject *get_color_obj, GetColorFunc get_color_func, int width, int height, int bytes, int has_alpha, guchar *color) { guchar *tl = NULL; guchar *tr = NULL; guchar *bl = NULL; guchar *br = NULL; gint i, alpha; for (i=0; i< bytes; i++) color[i] = 0; /* First check if there's transparency to crop. If not, guess the * background-color to see if at least 2 corners are equal. */ if (!(tl = (*get_color_func) (get_color_obj, 0, 0))) goto ERROR; if (!(tr = (*get_color_func) (get_color_obj, width - 1, 0))) goto ERROR; if (!(bl = (*get_color_func) (get_color_obj, 0, height - 1))) goto ERROR; if (!(br = (*get_color_func) (get_color_obj, width - 1, height - 1))) goto ERROR; if (has_alpha) { alpha = bytes - 1; if ((tl[alpha] == 0 && tr[alpha] == 0) || (tl[alpha] == 0 && bl[alpha] == 0) || (tr[alpha] == 0 && br[alpha] == 0) || (bl[alpha] == 0 && br[alpha] == 0)) { g_free (tl); g_free (tr); g_free (bl); g_free (br); return AUTO_CROP_ALPHA; } } if (crop_colors_equal (tl, tr, bytes) || crop_colors_equal (tl, bl, bytes)) memcpy (color, tl, bytes); else if (crop_colors_equal (br, bl, bytes) || crop_colors_equal (br, tr, bytes)) memcpy (color, br, bytes); else goto ERROR; g_free (tl); g_free (tr); g_free (bl); g_free (br); return AUTO_CROP_COLOR; ERROR: g_free (tl); g_free (tr); g_free (bl); g_free (br); return AUTO_CROP_NOTHING; } static int crop_colors_equal (guchar *col1, guchar *col2, int bytes) { int equal = TRUE; int b; for (b = 0; b < bytes; b++) { if (col1[b] != col2[b]) { equal = FALSE; break; } } return equal; } static int crop_colors_alpha (guchar *dummy, guchar *col, int bytes) { if (col[bytes-1] == 0) return TRUE; else return FALSE; } static void crop_close_callback (GtkWidget *w, gpointer client_data) { Tool * tool; tool = active_tool; draw_core_stop (((Crop *) tool->private)->core, tool); info_dialog_popdown (crop_info); tool->state = INACTIVE; } static void crop_orig_changed (GtkWidget *w, gpointer data) { Tool *tool; Crop *crop; GDisplay *gdisp; int ox; int oy; tool = (Tool *)data; if (tool) { gdisp = (GDisplay *) tool->gdisp_ptr; crop = (Crop *) tool->private; ox = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (w), 0); oy = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (w), 1); if ((ox != crop->tx1) || (oy != crop->ty1)) { draw_core_pause (crop->core, tool); crop->tx2 = crop->tx2 + (ox - crop->tx1); crop->tx1 = ox; crop->ty2 = crop->ty2 + (oy - crop->ty1); crop->ty1 = oy; crop_recalc (tool, crop); draw_core_resume (crop->core, tool); } } } static void crop_size_changed (GtkWidget *w, gpointer data) { Tool *tool; Crop *crop; GDisplay *gdisp; int sx; int sy; tool = (Tool *)data; if (tool) { gdisp = (GDisplay *) tool->gdisp_ptr; crop = (Crop *) tool->private; sx = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (w), 0); sy = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (w), 1); if ((sx != (crop->tx2 - crop->tx1)) || (sy != (crop->ty2 - crop->ty1))) { draw_core_pause (crop->core, tool); crop->tx2 = sx + crop->tx1; crop->ty2 = sy + crop->ty1; crop_recalc (tool, crop); draw_core_resume (crop->core, tool); } } }