mirror of
https://gitlab.gnome.org/GNOME/gimp
synced 2024-10-22 20:41:43 +00:00
33bba65728
2002-08-20 Michael Natterer <mitch@gimp.org> Make sure the selection (gimpimage-mask.c) functionality is really built *on top* of the GimpChannel functionality: * app/undo.[ch]: renamed undo_push_image_mask() to undo_push_mask() and generalized it's API to take a GimpChannel param so undos can be pushed for channels which are not the image's selection. Simplified the API and added code which copies the region of interest instead of leaving this to callers. * app/undo_types.h: s/IMAGE_MASK_UNDO/MASK_UNDO/ * app/undo_history.c: changed accordingly. * app/core/gimpchannel.[ch]: don't #include "gimpimage-mask.h". Changed gimp_channel_push_undo() to really push a channel undo, not a selection undo. Added "gboolean push_undo" params to all functions which are called from gimpimage-mask.c. Various cleanups and optimizations. Added /*< proxy-foo >*/ stuff to the header so we export just the struct itself to libgimpproxy. Added accessors gimp_channel_[get|set]_show_masked(). * app/core/gimpimage-mask.[ch]: renamed gimp_image_mask_undo() to gimp_image_mask_push_undo(). Call it before calling GimpChannel functions which modify the mask, also call all GimpChannel functions with push_undo = FALSE. Emit gimp_image_mask_changed() after each operation instead of calling it in gimp_image_mask_invalidate(). Removed gimp_image_mask_none() because it is the same as gimp_image_mask_clear(). General cleanup. * app/core/gimpimage-mask-select.c * app/core/gimpimage-qmask.c: changed accordingly. * app/core/gimpedit.c: call gimp_image_mask_clear(), not gimp_channel_clear (gimp_image_get_mask()). * app/core/gimpimage-crop.c * app/core/gimpimage-resize.c * app/core/gimpimage-scale.c: call gimp_image_mask_changed() * app/gui/channels-commands.c * app/gui/select-commands.c * app/tools/gimptexttool.c * tools/pdbgen/pdb/channel.pdb * tools/pdbgen/pdb/selection.pdb: follow GimpChannel and gimp_image_mask* API changes. * app/pdb/channel_cmds.c * app/pdb/selection_cmds.c * libgimpproxy/gimpchannel.h: regenerated. Unrelated: * app/core/gimpimage.c: call gimp_drawable_push_undo() instead of undo_push_image() directly.
553 lines
14 KiB
C
553 lines
14 KiB
C
/* 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 "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <glib-object.h>
|
|
|
|
#include "core-types.h"
|
|
|
|
#include "base/pixel-region.h"
|
|
|
|
#include "gimp.h"
|
|
#include "gimpchannel.h"
|
|
#include "gimpdrawable.h"
|
|
#include "gimpimage.h"
|
|
#include "gimpimage-crop.h"
|
|
#include "gimpimage-mask.h"
|
|
#include "gimpimage-projection.h"
|
|
#include "gimplayer.h"
|
|
#include "gimplayer-floating-sel.h"
|
|
#include "gimplist.h"
|
|
|
|
#include "undo.h"
|
|
|
|
|
|
typedef enum
|
|
{
|
|
AUTO_CROP_NOTHING = 0,
|
|
AUTO_CROP_ALPHA = 1,
|
|
AUTO_CROP_COLOR = 2
|
|
} AutoCropType;
|
|
|
|
|
|
typedef guchar * (* GetColorFunc) (GObject *crop_object,
|
|
gint ,
|
|
gint );
|
|
typedef AutoCropType (* ColorsEqualFunc) (guchar *,
|
|
guchar *,
|
|
gint );
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static void gimp_image_crop_adjust_guides (GimpImage *gimage,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2);
|
|
|
|
static AutoCropType gimp_image_crop_guess_bgcolor (GObject *get_color_obj,
|
|
GetColorFunc get_color_func,
|
|
gint bytes,
|
|
gboolean has_alpha,
|
|
guchar *color,
|
|
gint x1,
|
|
gint x2,
|
|
gint y1,
|
|
gint y2);
|
|
static gint gimp_image_crop_colors_equal (guchar *col1,
|
|
guchar *col2,
|
|
gint bytes);
|
|
static gint gimp_image_crop_colors_alpha (guchar *col1,
|
|
guchar *col2,
|
|
gint bytes);
|
|
|
|
|
|
/* public functions */
|
|
|
|
void
|
|
gimp_image_crop (GimpImage *gimage,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2,
|
|
gboolean active_layer_only,
|
|
gboolean crop_layers)
|
|
{
|
|
GimpLayer *layer;
|
|
GimpLayer *floating_layer;
|
|
GimpChannel *channel;
|
|
GList *guide_list_ptr;
|
|
GList *list;
|
|
gint width, height;
|
|
gint lx1, ly1, lx2, ly2;
|
|
gint off_x, off_y;
|
|
|
|
g_return_if_fail (gimage != NULL);
|
|
g_return_if_fail (GIMP_IS_IMAGE (gimage));
|
|
|
|
width = x2 - x1;
|
|
height = y2 - y1;
|
|
|
|
/* Make sure new width and height are non-zero */
|
|
if (width && height)
|
|
{
|
|
gimp_set_busy (gimage->gimp);
|
|
|
|
if (active_layer_only)
|
|
{
|
|
gint doff_x, doff_y;
|
|
|
|
layer = gimp_image_get_active_layer (gimage);
|
|
|
|
gimp_drawable_offsets (GIMP_DRAWABLE (layer), &doff_x, &doff_y);
|
|
|
|
off_x = (doff_x - x1);
|
|
off_y = (doff_y - y1);
|
|
|
|
undo_push_group_start (gimage, LAYER_RESIZE_UNDO_GROUP);
|
|
|
|
if (gimp_layer_is_floating_sel (layer))
|
|
floating_sel_relax (layer, TRUE);
|
|
|
|
gimp_layer_resize (layer, width, height, off_x, off_y);
|
|
|
|
if (gimp_layer_is_floating_sel (layer))
|
|
floating_sel_rigor (layer, TRUE);
|
|
|
|
undo_push_group_end (gimage);
|
|
}
|
|
else
|
|
{
|
|
floating_layer = gimp_image_floating_sel (gimage);
|
|
|
|
undo_push_group_start (gimage, IMAGE_CROP_UNDO_GROUP);
|
|
|
|
/* relax the floating layer */
|
|
if (floating_layer)
|
|
floating_sel_relax (floating_layer, TRUE);
|
|
|
|
/* Push the image size to the stack */
|
|
undo_push_image_size (gimage);
|
|
|
|
/* Set the new width and height */
|
|
gimage->width = width;
|
|
gimage->height = height;
|
|
|
|
/* Resize all channels */
|
|
for (list = GIMP_LIST (gimage->channels)->list;
|
|
list;
|
|
list = g_list_next (list))
|
|
{
|
|
channel = (GimpChannel *) list->data;
|
|
|
|
gimp_channel_resize (channel, width, height, -x1, -y1);
|
|
}
|
|
|
|
/* Don't forget the selection mask! */
|
|
gimp_channel_resize (gimage->selection_mask, width, height, -x1, -y1);
|
|
gimp_image_mask_invalidate (gimage);
|
|
|
|
/* crop all layers */
|
|
list = GIMP_LIST (gimage->layers)->list;
|
|
|
|
while (list)
|
|
{
|
|
GList *next;
|
|
|
|
layer = (GimpLayer *) list->data;
|
|
|
|
next = g_list_next (list);
|
|
|
|
gimp_layer_translate (layer, -x1, -y1);
|
|
|
|
gimp_drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y);
|
|
|
|
if (crop_layers)
|
|
{
|
|
gimp_drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y);
|
|
|
|
lx1 = CLAMP (off_x, 0, gimage->width);
|
|
ly1 = CLAMP (off_y, 0, gimage->height);
|
|
lx2 = CLAMP ((gimp_drawable_width (GIMP_DRAWABLE (layer)) + off_x),
|
|
0, gimage->width);
|
|
ly2 = CLAMP ((gimp_drawable_height (GIMP_DRAWABLE (layer)) + off_y),
|
|
0, gimage->height);
|
|
width = lx2 - lx1;
|
|
height = ly2 - ly1;
|
|
|
|
if (width && height)
|
|
gimp_layer_resize (layer, width, height,
|
|
-(lx1 - off_x),
|
|
-(ly1 - off_y));
|
|
else
|
|
gimp_image_remove_layer (gimage, layer);
|
|
}
|
|
|
|
list = next;
|
|
}
|
|
|
|
/* Make sure the projection matches the gimage size */
|
|
gimp_image_projection_allocate (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_image_guide (gimage, (GimpGuide *) guide_list_ptr->data);
|
|
guide_list_ptr = guide_list_ptr->next;
|
|
}
|
|
undo_push_group_end (gimage);
|
|
|
|
/* Adjust any guides we might have laying about */
|
|
gimp_image_crop_adjust_guides (gimage, x1, y1, x2, y2);
|
|
}
|
|
|
|
gimp_image_update (gimage,
|
|
0, 0,
|
|
gimage->width,
|
|
gimage->height);
|
|
|
|
gimp_viewable_size_changed (GIMP_VIEWABLE (gimage));
|
|
|
|
gimp_image_mask_changed (gimage);
|
|
|
|
gimp_unset_busy (gimage->gimp);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gimp_image_crop_auto_shrink (GimpImage *gimage,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2,
|
|
gboolean active_drawable_only,
|
|
gint *shrunk_x1,
|
|
gint *shrunk_y1,
|
|
gint *shrunk_x2,
|
|
gint *shrunk_y2)
|
|
{
|
|
GimpDrawable *active_drawable = NULL;
|
|
GetColorFunc get_color_func;
|
|
ColorsEqualFunc colors_equal_func;
|
|
GObject *get_color_obj;
|
|
guchar bgcolor[MAX_CHANNELS] = { 0, 0, 0, 0 };
|
|
gboolean has_alpha;
|
|
PixelRegion PR;
|
|
guchar *buffer = NULL;
|
|
gint width, height;
|
|
gint bytes;
|
|
gint x, y, abort;
|
|
gboolean retval = FALSE;
|
|
|
|
g_return_val_if_fail (gimage != NULL, FALSE);
|
|
g_return_val_if_fail (GIMP_IS_IMAGE (gimage), FALSE);
|
|
g_return_val_if_fail (shrunk_x1 != NULL, FALSE);
|
|
g_return_val_if_fail (shrunk_y1 != NULL, FALSE);
|
|
g_return_val_if_fail (shrunk_x2 != NULL, FALSE);
|
|
g_return_val_if_fail (shrunk_y2 != NULL, FALSE);
|
|
|
|
gimp_set_busy (gimage->gimp);
|
|
|
|
/* You should always keep in mind that crop->tx2 and crop->ty2 are the NOT the
|
|
coordinates of the bottomright corner of the area to be cropped. They point
|
|
at the pixel located one to the right and one to the bottom.
|
|
*/
|
|
|
|
if (active_drawable_only)
|
|
{
|
|
active_drawable = gimp_image_active_drawable (gimage);
|
|
|
|
if (! active_drawable)
|
|
goto FINISH;
|
|
|
|
bytes = gimp_drawable_bytes (GIMP_DRAWABLE (active_drawable));
|
|
has_alpha = gimp_drawable_has_alpha (GIMP_DRAWABLE (active_drawable));
|
|
get_color_obj = G_OBJECT (active_drawable);
|
|
get_color_func = (GetColorFunc) gimp_drawable_get_color_at;
|
|
}
|
|
else
|
|
{
|
|
has_alpha = TRUE;
|
|
bytes = gimp_image_projection_bytes (gimage);
|
|
get_color_obj = G_OBJECT (gimage);
|
|
get_color_func = (GetColorFunc) gimp_image_projection_get_color_at;
|
|
}
|
|
|
|
switch (gimp_image_crop_guess_bgcolor (get_color_obj, get_color_func,
|
|
bytes, has_alpha, bgcolor,
|
|
x1, x2-1, y1, y2-1))
|
|
{
|
|
case AUTO_CROP_ALPHA:
|
|
colors_equal_func = (ColorsEqualFunc) gimp_image_crop_colors_alpha;
|
|
break;
|
|
case AUTO_CROP_COLOR:
|
|
colors_equal_func = (ColorsEqualFunc) gimp_image_crop_colors_equal;
|
|
break;
|
|
default:
|
|
goto FINISH;
|
|
break;
|
|
}
|
|
|
|
width = x2 - x1;
|
|
height = y2 - y1;
|
|
|
|
if (active_drawable_only)
|
|
pixel_region_init (&PR, gimp_drawable_data (active_drawable),
|
|
x1, y1, width, height, FALSE);
|
|
else
|
|
pixel_region_init (&PR, gimp_image_projection (gimage),
|
|
x1, y1, width, height, FALSE);
|
|
|
|
/* The following could be optimized further by processing
|
|
* the smaller side first instead of defaulting to width --Sven
|
|
*/
|
|
|
|
buffer = g_malloc ((width > height ? width : height) * bytes);
|
|
|
|
/* Check how many of the top lines are uniform/transparent. */
|
|
abort = FALSE;
|
|
for (y = y1; y < y2 && !abort; y++)
|
|
{
|
|
pixel_region_get_row (&PR, x1, y, width, buffer, 1);
|
|
for (x = 0; x < width && !abort; x++)
|
|
abort = !(colors_equal_func) (bgcolor, buffer + x * bytes, bytes);
|
|
}
|
|
if (y == y2 && !abort)
|
|
goto FINISH;
|
|
y1 = y - 1;
|
|
|
|
/* Check how many of the bottom lines are uniform/transparent. */
|
|
abort = FALSE;
|
|
for (y = y2; y > y1 && !abort; y--)
|
|
{
|
|
pixel_region_get_row (&PR, x1, y-1 , width, buffer, 1);
|
|
for (x = 0; x < width && !abort; x++)
|
|
abort = !(colors_equal_func) (bgcolor, buffer + x * bytes, bytes);
|
|
}
|
|
y2 = y + 1;
|
|
|
|
/* compute a new height for the next operations */
|
|
height = y2 - y1;
|
|
|
|
/* Check how many of the left lines are uniform/transparent. */
|
|
abort = FALSE;
|
|
for (x = x1; x < x2 && !abort; x++)
|
|
{
|
|
pixel_region_get_col (&PR, x, y1, height, buffer, 1);
|
|
for (y = 0; y < height && !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 = x2; x > x1 && !abort; x--)
|
|
{
|
|
pixel_region_get_col (&PR, x-1, y1, height, buffer, 1);
|
|
for (y = 0; y < height && !abort; y++)
|
|
abort = !(colors_equal_func) (bgcolor, buffer + y * bytes, bytes);
|
|
}
|
|
x2 = x + 1;
|
|
|
|
*shrunk_x1 = x1;
|
|
*shrunk_y1 = y1;
|
|
*shrunk_x2 = x2;
|
|
*shrunk_y2 = y2;
|
|
|
|
retval = TRUE;
|
|
|
|
FINISH:
|
|
g_free (buffer);
|
|
gimp_unset_busy (gimage->gimp);
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
/* private functions */
|
|
|
|
static void
|
|
gimp_image_crop_adjust_guides (GimpImage *gimage,
|
|
gint x1,
|
|
gint y1,
|
|
gint x2,
|
|
gint y2)
|
|
|
|
{
|
|
GList *glist;
|
|
GimpGuide *guide;
|
|
gboolean remove_guide;
|
|
|
|
for (glist = gimage->guides; glist; glist = g_list_next (glist))
|
|
{
|
|
guide = (GimpGuide *) glist->data;
|
|
|
|
remove_guide = FALSE;
|
|
|
|
switch (guide->orientation)
|
|
{
|
|
case ORIENTATION_HORIZONTAL:
|
|
if ((guide->position < y1) || (guide->position > y2))
|
|
remove_guide = TRUE;
|
|
break;
|
|
|
|
case ORIENTATION_VERTICAL:
|
|
if ((guide->position < x1) || (guide->position > x2))
|
|
remove_guide = TRUE;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (remove_guide)
|
|
{
|
|
guide->position = -1;
|
|
guide = NULL;
|
|
}
|
|
else
|
|
{
|
|
if (guide->orientation == ORIENTATION_HORIZONTAL)
|
|
{
|
|
guide->position -= y1 ;
|
|
}
|
|
else
|
|
{
|
|
guide->position -= x1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static AutoCropType
|
|
gimp_image_crop_guess_bgcolor (GObject *get_color_obj,
|
|
GetColorFunc get_color_func,
|
|
gint bytes,
|
|
gboolean has_alpha,
|
|
guchar *color,
|
|
gint x1,
|
|
gint x2,
|
|
gint y1,
|
|
gint y2)
|
|
{
|
|
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, x1, y1)))
|
|
goto ERROR;
|
|
if (!(tr = (*get_color_func) (get_color_obj, x1, y2)))
|
|
goto ERROR;
|
|
if (!(bl = (*get_color_func) (get_color_obj, x2, y1)))
|
|
goto ERROR;
|
|
if (!(br = (*get_color_func) (get_color_obj, x2, y2)))
|
|
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 (gimp_image_crop_colors_equal (tl, tr, bytes) ||
|
|
gimp_image_crop_colors_equal (tl, bl, bytes))
|
|
{
|
|
memcpy (color, tl, bytes);
|
|
}
|
|
else if (gimp_image_crop_colors_equal (br, bl, bytes) ||
|
|
gimp_image_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
|
|
gimp_image_crop_colors_equal (guchar *col1,
|
|
guchar *col2,
|
|
gint bytes)
|
|
{
|
|
gboolean equal = TRUE;
|
|
gint b;
|
|
|
|
for (b = 0; b < bytes; b++)
|
|
{
|
|
if (col1[b] != col2[b])
|
|
{
|
|
equal = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return equal;
|
|
}
|
|
|
|
static gboolean
|
|
gimp_image_crop_colors_alpha (guchar *dummy,
|
|
guchar *col,
|
|
gint bytes)
|
|
{
|
|
if (col[bytes-1] == 0)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|