/* TODO: make sure has_alpha gets set */ /* 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 #include #include #include "libgimpmath/gimpmath.h" #include "core-types.h" #include "base/boundary.h" #include "base/pixel-region.h" #include "base/tile-manager.h" #include "base/tile.h" #include "paint-funcs/paint-funcs.h" #include "gimpdrawable-invert.h" #include "gimpcontainer.h" #include "gimpimage.h" #include "gimplayer.h" #include "gimplayermask.h" #include "gimpparasitelist.h" #include "floating_sel.h" #include "undo.h" #include "libgimp/gimpintl.h" enum { OPACITY_CHANGED, MODE_CHANGED, PRESERVE_TRANS_CHANGED, LINKED_CHANGED, MASK_CHANGED, LAST_SIGNAL }; static void gimp_layer_class_init (GimpLayerClass *klass); static void gimp_layer_init (GimpLayer *layer); static void gimp_layer_finalize (GObject *object); static void gimp_layer_invalidate_preview (GimpViewable *viewable); static void gimp_layer_transform_color (GimpImage *gimage, PixelRegion *layerPR, PixelRegion *bufPR, GimpDrawable *drawable, GimpImageBaseType type); static guint layer_signals[LAST_SIGNAL] = { 0 }; static GimpDrawableClass *parent_class = NULL; GType gimp_layer_get_type (void) { static GType layer_type = 0; if (! layer_type) { static const GTypeInfo layer_info = { sizeof (GimpLayerClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) gimp_layer_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GimpLayer), 0, /* n_preallocs */ (GInstanceInitFunc) gimp_layer_init, }; layer_type = g_type_register_static (GIMP_TYPE_DRAWABLE, "GimpLayer", &layer_info, 0); } return layer_type; } static void gimp_layer_class_init (GimpLayerClass *klass) { GObjectClass *object_class; GimpViewableClass *viewable_class; object_class = G_OBJECT_CLASS (klass); viewable_class = GIMP_VIEWABLE_CLASS (klass); parent_class = g_type_class_peek_parent (klass); layer_signals[OPACITY_CHANGED] = g_signal_new ("opacity_changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GimpLayerClass, opacity_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); layer_signals[MODE_CHANGED] = g_signal_new ("mode_changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GimpLayerClass, mode_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); layer_signals[PRESERVE_TRANS_CHANGED] = g_signal_new ("preserve_trans_changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GimpLayerClass, preserve_trans_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); layer_signals[LINKED_CHANGED] = g_signal_new ("linked_changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GimpLayerClass, linked_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); layer_signals[MASK_CHANGED] = g_signal_new ("mask_changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GimpLayerClass, mask_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); object_class->finalize = gimp_layer_finalize; viewable_class->invalidate_preview = gimp_layer_invalidate_preview; klass->mask_changed = NULL; } static void gimp_layer_init (GimpLayer *layer) { layer->linked = FALSE; layer->preserve_trans = FALSE; layer->mask = NULL; layer->opacity = 100; layer->mode = NORMAL_MODE; /* floating selection */ layer->fs.backing_store = NULL; layer->fs.drawable = NULL; layer->fs.initial = TRUE; layer->fs.boundary_known = FALSE; layer->fs.segs = NULL; layer->fs.num_segs = 0; } static void gimp_layer_finalize (GObject *object) { GimpLayer *layer; g_return_if_fail (GIMP_IS_LAYER (object)); layer = GIMP_LAYER (object); if (layer->mask) { g_object_unref (G_OBJECT (layer->mask)); layer->mask = NULL; } if (layer->fs.segs) { g_free (layer->fs.segs); layer->fs.segs = NULL; } /* free the floating selection if it exists */ if (layer->fs.backing_store) { tile_manager_destroy (layer->fs.backing_store); layer->fs.backing_store = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } static void gimp_layer_invalidate_preview (GimpViewable *viewable) { GimpLayer *layer; if (GIMP_VIEWABLE_CLASS (parent_class)->invalidate_preview) GIMP_VIEWABLE_CLASS (parent_class)->invalidate_preview (viewable); layer = GIMP_LAYER (viewable); if (gimp_layer_is_floating_sel (layer)) floating_sel_invalidate (layer); } static void gimp_layer_transform_color (GimpImage *gimage, PixelRegion *layerPR, PixelRegion *bufPR, GimpDrawable *drawable, GimpImageBaseType type) { gint i; gint h; guchar *s; guchar *d; gpointer pr; for (pr = pixel_regions_register (2, layerPR, bufPR); pr != NULL; pr = pixel_regions_process (pr)) { h = layerPR->h; s = bufPR->data; d = layerPR->data; while (h--) { for (i = 0; i < layerPR->w; i++) { gimp_image_transform_color (gimage, drawable, s + (i * bufPR->bytes), d + (i * layerPR->bytes), type); /* copy alpha channel */ d[(i + 1) * layerPR->bytes - 1] = s[(i + 1) * bufPR->bytes - 1]; } s += bufPR->rowstride; d += layerPR->rowstride; } } } /**************************/ /* Function definitions */ /**************************/ GimpLayer * gimp_layer_new (GimpImage *gimage, gint width, gint height, GimpImageType type, const gchar *name, gint opacity, LayerModeEffects mode) { GimpLayer *layer; if (width < 1 || height < 1) { g_message (_("Zero width or height layers not allowed.")); return NULL; } layer = g_object_new (GIMP_TYPE_LAYER, NULL); gimp_drawable_configure (GIMP_DRAWABLE (layer), gimage, width, height, type, name); /* mode and opacity */ layer->mode = mode; layer->opacity = opacity; return layer; } GimpLayer * gimp_layer_copy (GimpLayer *layer, gboolean add_alpha) { gchar *layer_name; GimpLayer *new_layer; GimpImageType new_type; gchar *ext; gint number; const gchar *name; gint len; PixelRegion srcPR; PixelRegion destPR; /* formulate the new layer name */ name = gimp_object_get_name (GIMP_OBJECT (layer)); ext = strrchr (name, '#'); len = strlen (_("copy")); if ((strlen(name) >= len && strcmp (&name[strlen (name) - len], _("copy")) == 0) || (ext && (number = atoi (ext + 1)) > 0 && ((gint) (log10 (number) + 1)) == strlen (ext + 1))) /* don't have redundant "copy"s */ layer_name = g_strdup (name); else layer_name = g_strdup_printf (_("%s copy"), name); /* when copying a layer, the copy ALWAYS has an alpha channel */ if (add_alpha) { switch (GIMP_DRAWABLE (layer)->type) { case RGB_GIMAGE: new_type = RGBA_GIMAGE; break; case GRAY_GIMAGE: new_type = GRAYA_GIMAGE; break; case INDEXED_GIMAGE: new_type = INDEXEDA_GIMAGE; break; default: new_type = GIMP_DRAWABLE (layer)->type; break; } } else { new_type = GIMP_DRAWABLE (layer)->type; } /* allocate a new layer object */ new_layer = gimp_layer_new (GIMP_DRAWABLE (layer)->gimage, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, new_type, layer_name, layer->opacity, layer->mode); if (! new_layer) { g_message ("gimp_layer_copy: could not allocate new layer"); goto cleanup; } GIMP_DRAWABLE (new_layer)->offset_x = GIMP_DRAWABLE (layer)->offset_x; GIMP_DRAWABLE (new_layer)->offset_y = GIMP_DRAWABLE (layer)->offset_y; GIMP_DRAWABLE (new_layer)->visible = GIMP_DRAWABLE (layer)->visible; new_layer->linked = layer->linked; new_layer->preserve_trans = layer->preserve_trans; /* copy the contents across layers */ if (new_type == GIMP_DRAWABLE (layer)->type) { pixel_region_init (&srcPR, GIMP_DRAWABLE (layer)->tiles, 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, FALSE); pixel_region_init (&destPR, GIMP_DRAWABLE(new_layer)->tiles, 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, TRUE); copy_region (&srcPR, &destPR); } else { pixel_region_init (&srcPR, GIMP_DRAWABLE (layer)->tiles, 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, FALSE); pixel_region_init (&destPR, GIMP_DRAWABLE (new_layer)->tiles, 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE(layer)->height, TRUE); add_alpha_region (&srcPR, &destPR); } /* duplicate the layer mask if necessary */ if (layer->mask) { new_layer->mask = gimp_layer_mask_copy (layer->mask); gimp_layer_mask_set_layer (new_layer->mask, new_layer); } /* copy the parasites */ g_object_unref (G_OBJECT (GIMP_DRAWABLE (new_layer)->parasites)); GIMP_DRAWABLE (new_layer)->parasites = gimp_parasite_list_copy (GIMP_DRAWABLE (layer)->parasites); cleanup: /* free up the layer_name memory */ g_free (layer_name); return new_layer; } GimpLayer * gimp_layer_new_from_tiles (GimpImage *gimage, GimpImageType layer_type, TileManager *tiles, gchar *name, gint opacity, LayerModeEffects mode) { GimpLayer *new_layer; PixelRegion layerPR; PixelRegion bufPR; /* Function copies buffer to a layer * taking into consideration the possibility of transforming * the contents to meet the requirements of the target image type */ /* If no image or no tile manager, return NULL */ if (!gimage || !tiles) return NULL; /* the layer_type needs to have alpha */ g_return_val_if_fail (GIMP_IMAGE_TYPE_HAS_ALPHA (layer_type), NULL); /* Create the new layer */ new_layer = gimp_layer_new (gimage, tile_manager_width (tiles), tile_manager_height (tiles), layer_type, name, opacity, mode); if (!new_layer) { g_message ("gimp_layer_new_from_tiles: could not allocate new layer"); return NULL; } /* Configure the pixel regions */ pixel_region_init (&layerPR, GIMP_DRAWABLE (new_layer)->tiles, 0, 0, GIMP_DRAWABLE (new_layer)->width, GIMP_DRAWABLE (new_layer)->height, TRUE); pixel_region_init (&bufPR, tiles, 0, 0, GIMP_DRAWABLE (new_layer)->width, GIMP_DRAWABLE (new_layer)->height, FALSE); if ((tile_manager_bpp (tiles) == 4 && GIMP_DRAWABLE (new_layer)->type == RGBA_GIMAGE) || (tile_manager_bpp (tiles) == 2 && GIMP_DRAWABLE (new_layer)->type == GRAYA_GIMAGE)) /* If we want a layer the same type as the buffer */ copy_region (&bufPR, &layerPR); else /* Transform the contents of the buf to the new_layer */ gimp_layer_transform_color (gimage, &layerPR, &bufPR, GIMP_DRAWABLE (new_layer), (tile_manager_bpp (tiles) == 4) ? RGB : GRAY); return new_layer; } GimpLayerMask * gimp_layer_add_mask (GimpLayer *layer, GimpLayerMask *mask, gboolean push_undo) { GimpImage *gimage; LayerMaskUndo *lmu; g_return_val_if_fail (GIMP_IS_LAYER (layer), NULL); g_return_val_if_fail (GIMP_IS_LAYER_MASK (mask), NULL); gimage = gimp_drawable_gimage (GIMP_DRAWABLE (layer)); if (! gimage) { g_message (_("Cannot add layer mask to layer\n" "which is not part of an image.")); return NULL; } if (layer->mask) { g_message(_("Unable to add a layer mask since\n" "the layer already has one.")); return NULL; } if (gimp_drawable_is_indexed (GIMP_DRAWABLE (layer))) { g_message(_("Unable to add a layer mask to a\n" "layer in an indexed image.")); return NULL; } if (! gimp_layer_has_alpha (layer)) { g_message (_("Cannot add layer mask to a layer\n" "with no alpha channel.")); return NULL; } if ((gimp_drawable_width (GIMP_DRAWABLE (layer)) != gimp_drawable_width (GIMP_DRAWABLE (mask))) || (gimp_drawable_height (GIMP_DRAWABLE (layer)) != gimp_drawable_height (GIMP_DRAWABLE (mask)))) { g_message(_("Cannot add layer mask of different\n" "dimensions than specified layer.")); return NULL; } layer->mask = mask; gimp_layer_mask_set_layer (mask, layer); gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height); if (push_undo) { /* Prepare a layer undo and push it */ lmu = g_new0 (LayerMaskUndo, 1); lmu->layer = layer; lmu->mask = mask; undo_push_layer_mask (gimage, LAYER_MASK_ADD_UNDO, lmu); } g_signal_emit (G_OBJECT (layer), layer_signals[MASK_CHANGED], 0); return layer->mask; } GimpLayerMask * gimp_layer_create_mask (GimpLayer *layer, AddMaskType add_mask_type) { PixelRegion maskPR; PixelRegion layerPR; GimpLayerMask *mask; GimpImage *gimage; GimpDrawable *selection; gchar *mask_name; GimpRGB black = { 0.0, 0.0, 0.0, 1.0 }; guchar white_mask = OPAQUE_OPACITY; guchar black_mask = TRANSPARENT_OPACITY; gimage = GIMP_DRAWABLE (layer)->gimage; selection = GIMP_DRAWABLE(gimage->selection_mask); mask_name = g_strdup_printf (_("%s mask"), gimp_object_get_name (GIMP_OBJECT (layer))); /* Create the layer mask */ mask = gimp_layer_mask_new (GIMP_DRAWABLE (layer)->gimage, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, mask_name, &black); GIMP_DRAWABLE (mask)->offset_x = GIMP_DRAWABLE (layer)->offset_x; GIMP_DRAWABLE (mask)->offset_y = GIMP_DRAWABLE (layer)->offset_y; pixel_region_init (&maskPR, GIMP_DRAWABLE (mask)->tiles, 0, 0, GIMP_DRAWABLE (mask)->width, GIMP_DRAWABLE (mask)->height, TRUE); switch (add_mask_type) { case ADD_WHITE_MASK: color_region (&maskPR, &white_mask); break; case ADD_BLACK_MASK: color_region (&maskPR, &black_mask); break; case ADD_ALPHA_MASK: /* Extract the layer's alpha channel */ if (gimp_layer_has_alpha (layer)) { pixel_region_init (&layerPR, GIMP_DRAWABLE (layer)->tiles, 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, FALSE); extract_alpha_region (&layerPR, NULL, &maskPR); } break; case ADD_SELECTION_MASK: pixel_region_init (&layerPR, GIMP_DRAWABLE (selection)->tiles, GIMP_DRAWABLE (layer)->offset_x, GIMP_DRAWABLE (layer)->offset_y, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, FALSE); copy_region (&layerPR, &maskPR); break; case ADD_INV_SELECTION_MASK: pixel_region_init (&layerPR, GIMP_DRAWABLE (selection)->tiles, GIMP_DRAWABLE (layer)->offset_x, GIMP_DRAWABLE (layer)->offset_y, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, FALSE); copy_region (&layerPR, &maskPR); gimp_drawable_invert (GIMP_DRAWABLE (mask)); break; } g_free (mask_name); return mask; } void gimp_layer_apply_mask (GimpLayer *layer, MaskApplyMode mode, gboolean push_undo) { GimpImage *gimage; LayerMaskUndo *lmu = NULL; gint off_x; gint off_y; PixelRegion srcPR, maskPR; gboolean view_changed = FALSE; g_return_if_fail (layer != NULL); g_return_if_fail (GIMP_IS_LAYER (layer)); if (! layer->mask) return; /* this operation can only be done to layers with an alpha channel */ if (! gimp_layer_has_alpha (layer)) return; gimage = gimp_drawable_gimage (GIMP_DRAWABLE (layer)); if (! gimage) return; if (push_undo) { /* Start an undo group */ undo_push_group_start (gimage, LAYER_APPLY_MASK_UNDO); /* Prepare a layer mask undo--push it below */ lmu = g_new (LayerMaskUndo, 1); lmu->layer = layer; lmu->mask = layer->mask; lmu->mode = mode; } /* check if applying the mask changes the projection */ if ((mode == APPLY && (!layer->mask->apply_mask || layer->mask->show_mask)) || (mode == DISCARD && ( layer->mask->apply_mask || layer->mask->show_mask))) { view_changed = TRUE; } if (mode == APPLY) { if (push_undo) { /* Put this apply mask operation on the undo stack */ gimp_drawable_apply_image (GIMP_DRAWABLE (layer), 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, NULL, FALSE); } /* Combine the current layer's alpha channel and the mask */ pixel_region_init (&srcPR, GIMP_DRAWABLE (layer)->tiles, 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, TRUE); pixel_region_init (&maskPR, GIMP_DRAWABLE (layer->mask)->tiles, 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, FALSE); apply_mask_to_region (&srcPR, &maskPR, OPAQUE_OPACITY); GIMP_DRAWABLE (layer)->preview_valid = FALSE; } layer->mask = NULL; if (push_undo) { /* Push the undo--Important to do it here, AFTER applying * the mask, in case the undo push fails and the * mask is deleted */ undo_push_layer_mask (gimage, LAYER_MASK_REMOVE_UNDO, lmu); /* end the undo group */ undo_push_group_end (gimage); } /* If applying actually changed the view */ if (view_changed) { gimp_viewable_invalidate_preview (GIMP_VIEWABLE (gimage)); gimp_drawable_offsets (GIMP_DRAWABLE (layer), &off_x, &off_y); gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, gimp_drawable_width (GIMP_DRAWABLE (layer)), gimp_drawable_height (GIMP_DRAWABLE (layer))); } g_signal_emit (G_OBJECT (layer), layer_signals[MASK_CHANGED], 0); } void gimp_layer_translate (GimpLayer *layer, gint off_x, gint off_y) { /* the undo call goes here */ undo_push_layer_displace (GIMP_DRAWABLE (layer)->gimage, layer); /* update the affected region */ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height); /* invalidate the selection boundary because of a layer modification */ gimp_layer_invalidate_boundary (layer); /* update the layer offsets */ GIMP_DRAWABLE (layer)->offset_x += off_x; GIMP_DRAWABLE (layer)->offset_y += off_y; /* update the affected region */ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height); if (layer->mask) { GIMP_DRAWABLE (layer->mask)->offset_x += off_x; GIMP_DRAWABLE (layer->mask)->offset_y += off_y; /* invalidate the mask preview */ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (layer->mask)); } } void gimp_layer_add_alpha (GimpLayer *layer) { PixelRegion srcPR, destPR; TileManager *new_tiles; GimpImageType type; GimpImage *gimage; /* Don't bother if the layer already has alpha */ switch (GIMP_DRAWABLE (layer)->type) { case RGB_GIMAGE: type = RGBA_GIMAGE; break; case GRAY_GIMAGE: type = GRAYA_GIMAGE; break; case INDEXED_GIMAGE: type = INDEXEDA_GIMAGE; break; case RGBA_GIMAGE: case GRAYA_GIMAGE: case INDEXEDA_GIMAGE: default: return; break; } /* Configure the pixel regions */ pixel_region_init (&srcPR, GIMP_DRAWABLE (layer)->tiles, 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, FALSE); /* Allocate the new layer, configure dest region */ new_tiles = tile_manager_new (GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, GIMP_DRAWABLE (layer)->bytes + 1); pixel_region_init (&destPR, new_tiles, 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, TRUE); /* Add an alpha channel */ add_alpha_region (&srcPR, &destPR); /* Push the layer on the undo stack */ undo_push_layer_mod (GIMP_DRAWABLE (layer)->gimage, layer); /* Configure the new layer */ GIMP_DRAWABLE (layer)->tiles = new_tiles; GIMP_DRAWABLE (layer)->type = type; GIMP_DRAWABLE (layer)->bytes = GIMP_DRAWABLE (layer)->bytes + 1; GIMP_DRAWABLE (layer)->has_alpha = GIMP_IMAGE_TYPE_HAS_ALPHA (type); GIMP_DRAWABLE (layer)->preview_valid = FALSE; gimage = gimp_drawable_gimage (GIMP_DRAWABLE (layer)); if (gimage->layers->num_children == 1) { gimp_image_alpha_changed (gimage); } } static void gimp_layer_scale_lowlevel (GimpLayer *layer, gint new_width, gint new_height, gint new_offset_x, gint new_offset_y) { PixelRegion srcPR, destPR; TileManager *new_tiles; /* Update the old layer position */ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height); /* Configure the pixel regions */ pixel_region_init (&srcPR, GIMP_DRAWABLE(layer)->tiles, 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height, FALSE); /* Allocate the new layer, configure dest region */ new_tiles = tile_manager_new (new_width, new_height, GIMP_DRAWABLE (layer)->bytes); pixel_region_init (&destPR, new_tiles, 0, 0, new_width, new_height, TRUE); /* Scale the layer - * If the layer is of type INDEXED, then we don't use pixel-value * resampling because that doesn't necessarily make sense for INDEXED * images. */ if ((GIMP_DRAWABLE (layer)->type == INDEXED_GIMAGE) || (GIMP_DRAWABLE (layer)->type == INDEXEDA_GIMAGE)) scale_region_no_resample (&srcPR, &destPR); else scale_region (&srcPR, &destPR); /* Push the layer on the undo stack */ undo_push_layer_mod (GIMP_DRAWABLE (layer)->gimage, layer); /* Configure the new layer */ GIMP_DRAWABLE (layer)->offset_x = new_offset_x; GIMP_DRAWABLE (layer)->offset_y = new_offset_y; GIMP_DRAWABLE (layer)->tiles = new_tiles; GIMP_DRAWABLE (layer)->width = new_width; GIMP_DRAWABLE (layer)->height = new_height; /* If there is a layer mask, make sure it gets scaled also */ if (layer->mask) { GIMP_DRAWABLE (layer->mask)->offset_x = GIMP_DRAWABLE (layer)->offset_x; GIMP_DRAWABLE (layer->mask)->offset_y = GIMP_DRAWABLE (layer)->offset_y; gimp_channel_scale (GIMP_CHANNEL (layer->mask), new_width, new_height); } /* Make sure we're not caching any old selection info */ gimp_layer_invalidate_boundary (layer); /* Update the new layer position */ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height); } /** * gimp_layer_check_scaling: * @layer: Layer to check * @new_width: proposed width of layer's image, in pixels * @new_height: proposed height of layer's image, in pixels * * Scales layer dimensions, then snaps them to pixel centers * * Returns: #FALSE if any dimension reduces to zero as a result * of this; otherwise, returns #TRUE. **/ gboolean gimp_layer_check_scaling (GimpLayer *layer, gint new_width, gint new_height) { GimpImage *gimage; gdouble img_scale_w; gdouble img_scale_h; gint new_layer_width; gint new_layer_height; gimage = GIMP_DRAWABLE (layer)->gimage; img_scale_w = (gdouble) new_width / (gdouble) gimage->width; img_scale_h = (gdouble) new_height / (gdouble) gimage->height; new_layer_width = ROUND (img_scale_w * (gdouble) GIMP_DRAWABLE (layer)->width); new_layer_height = ROUND (img_scale_h * (gdouble) GIMP_DRAWABLE (layer)->height); return (new_layer_width != 0 && new_layer_height != 0); } /** * gimp_layer_scale_by_factors: * @layer: Layer to be transformed by explicit width and height factors. * @w_factor: scale factor to apply to width and horizontal offset * @h_factor: scale factor to apply to height and vertical offset * * Scales layer dimensions and offsets by uniform width and * height factors. * * Use gimp_layer_scale_by_factors() in circumstances when the * same width and height scaling factors are to be uniformly * applied to a set of layers. In this context, the layer's * dimensions and offsets from the sides of the containing * image all change by these predetermined factors. By fiat, * the fixed point of the transform is the upper left hand * corner of the image. Returns gboolean FALSE if a requested * scale factor is zero or if a scaling zero's out a layer * dimension; returns #TRUE otherwise. * * Use gimp_layer_scale() in circumstances where new layer width * and height dimensions are predetermined instead. * * Side effects: Undo set created for layer. Old layer imagery * scaled & painted to new layer tiles. * * Returns: #TRUE, if the scaled layer has positive dimensions * #FALSE if the scaled layer has at least one zero dimension **/ gboolean gimp_layer_scale_by_factors (GimpLayer *layer, gdouble w_factor, gdouble h_factor) { gint new_width, new_height; gint new_offset_x, new_offset_y; if (w_factor == 0.0 || h_factor == 0.0) { g_message ("gimp_layer_scale_by_factors: Error. Requested width or height scale equals zero."); return FALSE; } new_offset_x = ROUND (w_factor * (gdouble) GIMP_DRAWABLE (layer)->offset_x); new_offset_y = ROUND (h_factor * (gdouble) GIMP_DRAWABLE (layer)->offset_y); new_width = ROUND (w_factor * (gdouble) GIMP_DRAWABLE (layer)->width); new_height = ROUND (h_factor * (gdouble) GIMP_DRAWABLE (layer)->height); if (new_width != 0 && new_height != 0) { gimp_layer_scale_lowlevel (layer, new_width, new_height, new_offset_x, new_offset_y); return TRUE; } return FALSE; } /** * gimp_layer_scale: * @layer: The layer to be transformed by width & height scale factors * @new_width: The width that layer will acquire * @new_height: The height that the layer will acquire * @local_origin: sets fixed point of the scaling transform. See below. * * Sets layer dimensions to new_width and * new_height. Derives vertical and horizontal scaling * transforms from new width and height. If local_origin is * TRUE, the fixed point of the scaling transform coincides * with the layer's center point. Otherwise, the fixed * point is taken to be [-GIMP_DRAWABLE(layer)->offset_x, * -GIMP_DRAWABLE(layer)->offset_y]. * * Since this function derives scale factors from new and * current layer dimensions, these factors will vary from * layer to layer because of aliasing artifacts; factor * variations among layers can be quite large where layer * dimensions approach pixel dimensions. Use * gimp_layer_scale_by_factors() where constant scales are to * be uniformly applied to a number of layers. * * Side effects: undo set created for layer. * Old layer imagery scaled * & painted to new layer tiles **/ void gimp_layer_scale (GimpLayer *layer, gint new_width, gint new_height, gboolean local_origin) { gint new_offset_x, new_offset_y; if (new_width == 0 || new_height == 0) { g_message ("gimp_layer_scale: Error. Requested width or height equals zero."); return; } if (local_origin) { new_offset_x = GIMP_DRAWABLE (layer)->offset_x + ((GIMP_DRAWABLE (layer)->width - new_width) / 2.0); new_offset_y = GIMP_DRAWABLE (layer)->offset_y + ((GIMP_DRAWABLE (layer)->height - new_height) / 2.0); } else { new_offset_x = (gint) (((gdouble) new_width * GIMP_DRAWABLE (layer)->offset_x / (gdouble) GIMP_DRAWABLE (layer)->width)); new_offset_y = (gint) (((gdouble) new_height * GIMP_DRAWABLE (layer)->offset_y / (gdouble) GIMP_DRAWABLE (layer)->height)); } gimp_layer_scale_lowlevel (layer, new_width, new_height, new_offset_x, new_offset_y); } void gimp_layer_resize (GimpLayer *layer, gint new_width, gint new_height, gint offx, gint offy) { PixelRegion srcPR, destPR; TileManager *new_tiles; gint w, h; gint x1, y1, x2, y2; if (new_width < 1 || new_height < 1) return; x1 = CLAMP (offx, 0, new_width); y1 = CLAMP (offy, 0, new_height); x2 = CLAMP ((offx + GIMP_DRAWABLE(layer)->width), 0, new_width); y2 = CLAMP ((offy + GIMP_DRAWABLE(layer)->height), 0, new_height); w = x2 - x1; h = y2 - y1; if (offx > 0) { x1 = 0; x2 = offx; } else { x1 = -offx; x2 = 0; } if (offy > 0) { y1 = 0; y2 = offy; } else { y1 = -offy; y2 = 0; } /* Update the old layer position */ gimp_drawable_update (GIMP_DRAWABLE( layer), 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height); /* Configure the pixel regions */ pixel_region_init (&srcPR, GIMP_DRAWABLE (layer)->tiles, x1, y1, w, h, FALSE); /* Allocate the new layer, configure dest region */ new_tiles = tile_manager_new (new_width, new_height, GIMP_DRAWABLE (layer)->bytes); pixel_region_init (&destPR, new_tiles, 0, 0, new_width, new_height, TRUE); /* fill with the fill color */ if (gimp_layer_has_alpha (layer)) { /* Set to transparent and black */ guchar bg[4] = {0, 0, 0, 0}; color_region (&destPR, bg); } else { guchar bg[3]; gimp_image_get_background (GIMP_DRAWABLE (layer)->gimage, GIMP_DRAWABLE (layer), bg); color_region (&destPR, bg); } pixel_region_init (&destPR, new_tiles, x2, y2, w, h, TRUE); /* copy from the old to the new */ if (w && h) copy_region (&srcPR, &destPR); /* Push the layer on the undo stack */ undo_push_layer_mod (GIMP_DRAWABLE(layer)->gimage, layer); /* Configure the new layer */ GIMP_DRAWABLE (layer)->tiles = new_tiles; GIMP_DRAWABLE (layer)->offset_x = x1 + GIMP_DRAWABLE (layer)->offset_x - x2; GIMP_DRAWABLE (layer)->offset_y = y1 + GIMP_DRAWABLE (layer)->offset_y - y2; GIMP_DRAWABLE (layer)->width = new_width; GIMP_DRAWABLE (layer)->height = new_height; /* If there is a layer mask, make sure it gets resized also */ if (layer->mask) { GIMP_DRAWABLE (layer->mask)->offset_x = GIMP_DRAWABLE (layer)->offset_x; GIMP_DRAWABLE (layer->mask)->offset_y = GIMP_DRAWABLE (layer)->offset_y; gimp_channel_resize (GIMP_CHANNEL (layer->mask), new_width, new_height, offx, offy); } /* Make sure we're not caching any old selection info */ gimp_layer_invalidate_boundary (layer); /* update the new layer area */ gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height); } void gimp_layer_resize_to_image (GimpLayer *layer) { GimpImage *gimage; gint offset_x; gint offset_y; if (!(gimage = GIMP_DRAWABLE (layer)->gimage)) return; undo_push_group_start (gimage, LAYER_RESIZE_UNDO); if (gimp_layer_is_floating_sel (layer)) floating_sel_relax (layer, TRUE); gimp_drawable_offsets (GIMP_DRAWABLE (layer), &offset_x, &offset_y); gimp_layer_resize (layer, gimage->width, gimage->height, offset_x, offset_y); if (gimp_layer_is_floating_sel (layer)) floating_sel_rigor (layer, TRUE); undo_push_group_end (gimage); } BoundSeg * gimp_layer_boundary (GimpLayer *layer, gint *num_segs) { BoundSeg *new_segs; /* Create the four boundary segments that encompass this * layer's boundary. */ new_segs = g_new (BoundSeg, 4); *num_segs = 4; /* if the layer is a floating selection */ if (gimp_layer_is_floating_sel (layer)) { if (GIMP_IS_CHANNEL (layer->fs.drawable)) { /* if the owner drawable is a channel, just return nothing */ g_free (new_segs); *num_segs = 0; return NULL; } else { /* otherwise, set the layer to the owner drawable */ layer = GIMP_LAYER (layer->fs.drawable); } } new_segs[0].x1 = GIMP_DRAWABLE (layer)->offset_x; new_segs[0].y1 = GIMP_DRAWABLE (layer)->offset_y; new_segs[0].x2 = GIMP_DRAWABLE (layer)->offset_x; new_segs[0].y2 = GIMP_DRAWABLE (layer)->offset_y + GIMP_DRAWABLE (layer)->height; new_segs[0].open = 1; new_segs[1].x1 = GIMP_DRAWABLE (layer)->offset_x; new_segs[1].y1 = GIMP_DRAWABLE (layer)->offset_y; new_segs[1].x2 = GIMP_DRAWABLE (layer)->offset_x + GIMP_DRAWABLE (layer)->width; new_segs[1].y2 = GIMP_DRAWABLE (layer)->offset_y; new_segs[1].open = 1; new_segs[2].x1 = GIMP_DRAWABLE (layer)->offset_x + GIMP_DRAWABLE (layer)->width; new_segs[2].y1 = GIMP_DRAWABLE (layer)->offset_y; new_segs[2].x2 = GIMP_DRAWABLE (layer)->offset_x + GIMP_DRAWABLE (layer)->width; new_segs[2].y2 = GIMP_DRAWABLE (layer)->offset_y + GIMP_DRAWABLE (layer)->height; new_segs[2].open = 0; new_segs[3].x1 = GIMP_DRAWABLE (layer)->offset_x; new_segs[3].y1 = GIMP_DRAWABLE (layer)->offset_y + GIMP_DRAWABLE (layer)->height; new_segs[3].x2 = GIMP_DRAWABLE (layer)->offset_x + GIMP_DRAWABLE (layer)->width; new_segs[3].y2 = GIMP_DRAWABLE (layer)->offset_y + GIMP_DRAWABLE (layer)->height; new_segs[3].open = 0; return new_segs; } void gimp_layer_invalidate_boundary (GimpLayer *layer) { GimpImage *gimage; GimpChannel *mask; if (! (gimage = gimp_drawable_gimage (GIMP_DRAWABLE (layer)))) return; /* Turn the current selection off */ gimp_image_selection_control (gimage, GIMP_SELECTION_OFF); /* clear the affected region surrounding the layer */ gimp_image_selection_control (gimage, GIMP_SELECTION_LAYER_OFF); /* get the selection mask channel */ mask = gimp_image_get_mask (gimage); /* Only bother with the bounds if there is a selection */ if (! gimp_channel_is_empty (mask)) { mask->bounds_known = FALSE; mask->boundary_known = FALSE; } if (gimp_layer_is_floating_sel (layer)) floating_sel_invalidate (layer); } gint gimp_layer_pick_correlate (GimpLayer *layer, gint x, gint y) { Tile *tile; Tile *mask_tile; gint val; /* Is the point inside the layer? * First transform the point to layer coordinates... */ x -= GIMP_DRAWABLE (layer)->offset_x; y -= GIMP_DRAWABLE (layer)->offset_y; if (x >= 0 && x < GIMP_DRAWABLE (layer)->width && y >= 0 && y < GIMP_DRAWABLE (layer)->height && gimp_drawable_get_visible (GIMP_DRAWABLE (layer))) { /* If the point is inside, and the layer has no * alpha channel, success! */ if (! gimp_layer_has_alpha (layer)) return TRUE; /* Otherwise, determine if the alpha value at * the given point is non-zero */ tile = tile_manager_get_tile (GIMP_DRAWABLE(layer)->tiles, x, y, TRUE, FALSE); val = * ((guchar *) tile_data_pointer (tile, x % TILE_WIDTH, y % TILE_HEIGHT) + tile_bpp (tile) - 1); if (layer->mask) { guchar *ptr; mask_tile = tile_manager_get_tile (GIMP_DRAWABLE(layer->mask)->tiles, x, y, TRUE, FALSE); ptr = tile_data_pointer (mask_tile, x % TILE_WIDTH, y % TILE_HEIGHT); val = val * (*ptr) / 255; tile_release (mask_tile, FALSE); } tile_release (tile, FALSE); if (val > 63) return TRUE; } return FALSE; } /**********************/ /* access functions */ /**********************/ GimpLayerMask * gimp_layer_get_mask (GimpLayer *layer) { return layer->mask; } gboolean gimp_layer_has_alpha (GimpLayer *layer) { g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE); return GIMP_IMAGE_TYPE_HAS_ALPHA (GIMP_DRAWABLE (layer)->type); } gboolean gimp_layer_is_floating_sel (GimpLayer *layer) { g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE); return (layer->fs.drawable != NULL); } void gimp_layer_set_opacity (GimpLayer *layer, gdouble opacity) { gint layer_opacity; g_return_if_fail (GIMP_IS_LAYER (layer)); layer_opacity = (gint) (opacity * 255.999); if (layer->opacity != layer_opacity) { layer->opacity = layer_opacity; g_signal_emit (G_OBJECT (layer), layer_signals[OPACITY_CHANGED], 0); gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height); } } gdouble gimp_layer_get_opacity (GimpLayer *layer) { g_return_val_if_fail (GIMP_IS_LAYER (layer), 1.0); return (gdouble) layer->opacity / 255.0; } void gimp_layer_set_mode (GimpLayer *layer, LayerModeEffects mode) { g_return_if_fail (GIMP_IS_LAYER (layer)); if (layer->mode != mode) { layer->mode = mode; g_signal_emit (G_OBJECT (layer), layer_signals[MODE_CHANGED], 0); gimp_drawable_update (GIMP_DRAWABLE (layer), 0, 0, GIMP_DRAWABLE (layer)->width, GIMP_DRAWABLE (layer)->height); } } LayerModeEffects gimp_layer_get_mode (GimpLayer *layer) { g_return_val_if_fail (GIMP_IS_LAYER (layer), NORMAL_MODE); return layer->mode; } void gimp_layer_set_preserve_trans (GimpLayer *layer, gboolean preserve) { g_return_if_fail (GIMP_IS_LAYER (layer)); if (layer->preserve_trans != preserve) { layer->preserve_trans = preserve ? TRUE : FALSE; g_signal_emit (G_OBJECT (layer), layer_signals[PRESERVE_TRANS_CHANGED], 0); } } gboolean gimp_layer_get_preserve_trans (GimpLayer *layer) { g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE); return layer->preserve_trans; } void gimp_layer_set_linked (GimpLayer *layer, gboolean linked) { g_return_if_fail (GIMP_IS_LAYER (layer)); if (layer->linked != linked) { layer->linked = linked ? TRUE : FALSE; g_signal_emit (G_OBJECT (layer), layer_signals[LINKED_CHANGED], 0); } } gboolean gimp_layer_get_linked (GimpLayer *layer) { g_return_val_if_fail (GIMP_IS_LAYER (layer), FALSE); return layer->linked; }