/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * 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 3 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, see . */ #include "config.h" #include #include #include #include "libpikabase/pikabase.h" #include "libpikacolor/pikacolor.h" #include "core-types.h" #include "pika.h" #include "pika-edit.h" #include "pikabuffer.h" #include "pikacontext.h" #include "pikadrawable-edit.h" #include "pikagrouplayer.h" #include "pikaimage.h" #include "pikaimage-duplicate.h" #include "pikaimage-merge.h" #include "pikaimage-new.h" #include "pikaimage-resize.h" #include "pikaimage-undo.h" #include "pikalayer-floating-selection.h" #include "pikalayer-new.h" #include "pikalayermask.h" #include "pikalist.h" #include "pikapickable.h" #include "pikaselection.h" #include "pika-intl.h" /* local function protypes */ static PikaBuffer * pika_edit_extract (PikaImage *image, GList *pickables, PikaContext *context, gboolean cut_pixels, GError **error); static PikaDrawable * pika_edit_paste_get_top_item (GList *drawables); /* public functions */ PikaObject * pika_edit_cut (PikaImage *image, GList *drawables, PikaContext *context, GError **error) { GList *iter; gboolean layers_only = TRUE; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); for (iter = drawables; iter; iter = iter->next) if (! PIKA_IS_LAYER (iter->data)) { layers_only = FALSE; break; } if (layers_only) { gchar *undo_label; undo_label = g_strdup_printf (ngettext ("Cut Layer", "Cut %d Layers", g_list_length (drawables)), g_list_length (drawables)); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_EDIT_CUT, undo_label); g_free (undo_label); if (pika_channel_is_empty (pika_image_get_mask (image))) { GList *remove = NULL; PikaImage *clip_image; /* Let's work on a copy because we will edit the list to remove * layers whose ancestor is also cut. */ drawables = g_list_copy (drawables); for (iter = drawables; iter; iter = iter->next) { GList *iter2; for (iter2 = drawables; iter2; iter2 = iter2->next) { if (iter2 == iter) continue; if (pika_viewable_is_ancestor (iter2->data, iter->data)) { /* When cutting a layer group, all its children come * with anyway. */ remove = g_list_prepend (remove, iter); break; } } } for (iter = remove; iter; iter = iter->next) drawables = g_list_delete_link (drawables, iter->data); g_list_free (remove); /* Now copy all layers into the clipboard image. */ clip_image = pika_image_new_from_drawables (image->pika, drawables, FALSE, TRUE); pika_container_remove (image->pika->images, PIKA_OBJECT (clip_image)); pika_set_clipboard_image (image->pika, clip_image); g_object_unref (clip_image); /* Remove layers from source image. */ for (iter = drawables; iter; iter = iter->next) pika_image_remove_layer (image, PIKA_LAYER (iter->data), TRUE, NULL); g_list_free (drawables); } else { /* With selection, a cut is similar to a copy followed by a clear. */ pika_edit_copy (image, drawables, context, error); for (iter = drawables; iter; iter = iter->next) if (! PIKA_IS_GROUP_LAYER (iter->data)) pika_drawable_edit_clear (PIKA_DRAWABLE (iter->data), context); } pika_image_undo_group_end (image); return PIKA_OBJECT (pika_get_clipboard_image (image->pika)); } else { PikaBuffer *buffer; buffer = pika_edit_extract (image, drawables, context, TRUE, error); if (buffer) { pika_set_clipboard_buffer (image->pika, buffer); g_object_unref (buffer); return PIKA_OBJECT (pika_get_clipboard_buffer (image->pika)); } } return NULL; } PikaObject * pika_edit_copy (PikaImage *image, GList *drawables, PikaContext *context, GError **error) { GList *iter; gboolean drawables_are_layers = TRUE; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (drawables != NULL, NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); for (iter = drawables; iter; iter = iter->next) { g_return_val_if_fail (PIKA_IS_DRAWABLE (iter->data), NULL); g_return_val_if_fail (pika_item_is_attached (iter->data), NULL); if (! PIKA_IS_LAYER (iter->data)) drawables_are_layers = FALSE; } /* Only accept multiple drawables for layers. */ g_return_val_if_fail (g_list_length (drawables) == 1 || drawables_are_layers, NULL); if (drawables_are_layers) { /* Special-casing the 1 layer with no selection case. * It allows us to save the whole layer with all pixels as stored, * not the rendered version of it. */ PikaImage *clip_image; PikaChannel *clip_selection; GeglRectangle selection_bounds; gboolean has_selection = FALSE; clip_image = pika_image_new_from_drawables (image->pika, drawables, TRUE, TRUE); pika_container_remove (image->pika->images, PIKA_OBJECT (clip_image)); pika_set_clipboard_image (image->pika, clip_image); g_object_unref (clip_image); clip_selection = pika_image_get_mask (clip_image); if (! pika_channel_is_empty (clip_selection)) { pika_item_bounds (PIKA_ITEM (clip_selection), &selection_bounds.x, &selection_bounds.y, &selection_bounds.width, &selection_bounds.height); /* Invert the selection. */ pika_channel_invert (clip_selection, FALSE); /* Empty selection work the same as full selection for the * pika_drawable_edit_*() APIs. So as a special case, we check again * after inverting the selection (if the inverted selection is empty, * it meant we were in "all" selection case). * Otherwise we were ending up clearing everything with * pika_drawable_edit_clear() on the inverted "all" selection. */ has_selection = (! pika_channel_is_empty (clip_selection)); } if (has_selection) { GList *all_items; all_items = pika_image_get_layer_list (clip_image); for (iter = all_items; iter; iter = g_list_next (iter)) { gint item_x; gint item_y; pika_item_get_offset (PIKA_ITEM (iter->data), &item_x, &item_y); /* Even if the original layer may not have an alpha channel, the * selected data must always have one. First because a selection * is in some way an alpha channel when we copy (we may copy part * of a pixel, i.e. with transparency). Second because the * selection is not necessary rectangular, unlike layers. So when * we will clear, if we hadn't added an alpha channel, we'd end up * with background color all over the place. */ pika_layer_add_alpha (PIKA_LAYER (iter->data)); pika_drawable_edit_clear (PIKA_DRAWABLE (iter->data), context); /* Finally shrink the copied layer to selection bounds. */ pika_item_resize (iter->data, context, PIKA_FILL_TRANSPARENT, selection_bounds.width, selection_bounds.height, item_x - selection_bounds.x, item_y - selection_bounds.y); } g_list_free (all_items); /* We need to keep the image size as-is, because even after cropping * layers to selection, their offset stay important for in-place paste * variants. Yet we also need to store the dimensions where we'd have * cropped the image for the "Paste as New Image" action. */ g_object_set_data (G_OBJECT (clip_image), "pika-edit-new-image-x", GINT_TO_POINTER (selection_bounds.x)); g_object_set_data (G_OBJECT (clip_image), "pika-edit-new-image-y", GINT_TO_POINTER (selection_bounds.y)); g_object_set_data (G_OBJECT (clip_image), "pika-edit-new-image-width", GINT_TO_POINTER (selection_bounds.width)); g_object_set_data (G_OBJECT (clip_image), "pika-edit-new-image-height", GINT_TO_POINTER (selection_bounds.height)); } /* Remove selection from the clipboard image. */ pika_channel_clear (clip_selection, NULL, FALSE); return PIKA_OBJECT (pika_get_clipboard_image (image->pika)); } else { PikaBuffer *buffer; buffer = pika_edit_extract (image, drawables, context, FALSE, error); if (buffer) { pika_set_clipboard_buffer (image->pika, buffer); g_object_unref (buffer); return PIKA_OBJECT (pika_get_clipboard_buffer (image->pika)); } } return NULL; } PikaBuffer * pika_edit_copy_visible (PikaImage *image, PikaContext *context, GError **error) { PikaBuffer *buffer; GList *pickables; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); pickables = g_list_prepend (NULL, image); buffer = pika_edit_extract (image, pickables, context, FALSE, error); g_list_free (pickables); if (buffer) { pika_set_clipboard_buffer (image->pika, buffer); g_object_unref (buffer); return pika_get_clipboard_buffer (image->pika); } return NULL; } static gboolean pika_edit_paste_is_in_place (PikaPasteType paste_type) { switch (paste_type) { case PIKA_PASTE_TYPE_FLOATING: case PIKA_PASTE_TYPE_FLOATING_INTO: case PIKA_PASTE_TYPE_NEW_LAYER: case PIKA_PASTE_TYPE_NEW_LAYER_OR_FLOATING: case PIKA_PASTE_TYPE_NEW_MERGED_LAYER_OR_FLOATING: return FALSE; case PIKA_PASTE_TYPE_FLOATING_IN_PLACE: case PIKA_PASTE_TYPE_FLOATING_INTO_IN_PLACE: case PIKA_PASTE_TYPE_NEW_LAYER_IN_PLACE: case PIKA_PASTE_TYPE_NEW_LAYER_OR_FLOATING_IN_PLACE: case PIKA_PASTE_TYPE_NEW_MERGED_LAYER_OR_FLOATING_IN_PLACE: return TRUE; } g_return_val_if_reached (FALSE); } static gboolean pika_edit_paste_is_floating (PikaPasteType paste_type, PikaDrawable *drawable) { switch (paste_type) { case PIKA_PASTE_TYPE_FLOATING: case PIKA_PASTE_TYPE_FLOATING_INTO: case PIKA_PASTE_TYPE_FLOATING_IN_PLACE: case PIKA_PASTE_TYPE_FLOATING_INTO_IN_PLACE: return TRUE; case PIKA_PASTE_TYPE_NEW_LAYER: case PIKA_PASTE_TYPE_NEW_LAYER_IN_PLACE: return FALSE; case PIKA_PASTE_TYPE_NEW_LAYER_OR_FLOATING: case PIKA_PASTE_TYPE_NEW_LAYER_OR_FLOATING_IN_PLACE: case PIKA_PASTE_TYPE_NEW_MERGED_LAYER_OR_FLOATING: case PIKA_PASTE_TYPE_NEW_MERGED_LAYER_OR_FLOATING_IN_PLACE: if (PIKA_IS_LAYER_MASK (drawable)) return TRUE; else return FALSE; } g_return_val_if_reached (FALSE); } static GList * pika_edit_paste_get_tagged_layers (PikaImage *image, GList *layers, GList *returned_layers, const Babl *floating_format, PikaImageBaseType base_type, PikaPrecision precision, PikaPasteType paste_type) { GList *iter; for (iter = layers; iter; iter = iter->next) { PikaLayer *layer; GType layer_type; gboolean copied = TRUE; switch (paste_type) { case PIKA_PASTE_TYPE_FLOATING: case PIKA_PASTE_TYPE_FLOATING_IN_PLACE: case PIKA_PASTE_TYPE_FLOATING_INTO: case PIKA_PASTE_TYPE_FLOATING_INTO_IN_PLACE: /* when pasting as floating make sure pika_item_convert() * will turn group layers into normal layers, otherwise use * the same layer type so e.g. text information gets * preserved. See issue #2667. */ if (PIKA_IS_GROUP_LAYER (iter->data)) layer_type = PIKA_TYPE_LAYER; else layer_type = G_TYPE_FROM_INSTANCE (iter->data); break; case PIKA_PASTE_TYPE_NEW_LAYER: case PIKA_PASTE_TYPE_NEW_LAYER_IN_PLACE: layer_type = G_TYPE_FROM_INSTANCE (iter->data); break; default: g_return_val_if_reached (NULL); } if (PIKA_IS_GROUP_LAYER (iter->data)) copied = (gboolean) GPOINTER_TO_INT (g_object_get_data (G_OBJECT (iter->data), "pika-image-copied-layer")); if (copied) { layer = PIKA_LAYER (pika_item_convert (PIKA_ITEM (iter->data), image, layer_type)); returned_layers = g_list_prepend (returned_layers, layer); switch (paste_type) { case PIKA_PASTE_TYPE_FLOATING: case PIKA_PASTE_TYPE_FLOATING_IN_PLACE: case PIKA_PASTE_TYPE_FLOATING_INTO: case PIKA_PASTE_TYPE_FLOATING_INTO_IN_PLACE: /* when pasting as floating selection, get rid of the layer mask, * and make sure the layer has the right format */ if (pika_layer_get_mask (iter->data)) pika_layer_apply_mask (iter->data, PIKA_MASK_DISCARD, FALSE); if (pika_drawable_get_format (PIKA_DRAWABLE (iter->data)) != floating_format) { pika_drawable_convert_type (PIKA_DRAWABLE (iter->data), image, base_type, precision, TRUE, NULL, NULL, GEGL_DITHER_NONE, GEGL_DITHER_NONE, FALSE, NULL); } break; default: break; } } else { PikaContainer *container; container = pika_viewable_get_children (iter->data); returned_layers = pika_edit_paste_get_tagged_layers (image, PIKA_LIST (container)->queue->head, returned_layers, floating_format, base_type, precision, paste_type); } } return returned_layers; } static GList * pika_edit_paste_get_layers (PikaImage *image, GList *drawables, PikaObject *paste, PikaPasteType *paste_type) { GList *layers = NULL; const Babl *floating_format; /* change paste type to NEW_LAYER for cases where we can't attach a * floating selection */ if (g_list_length (drawables) != 1 || pika_viewable_get_children (drawables->data) || pika_item_is_content_locked (drawables->data, NULL)) { if (pika_edit_paste_is_in_place (*paste_type)) *paste_type = PIKA_PASTE_TYPE_NEW_LAYER_IN_PLACE; else *paste_type = PIKA_PASTE_TYPE_NEW_LAYER; } /* floating pastes always have the pasted-to drawable's format with * alpha; if drawable == NULL, user is pasting into an empty image */ if (drawables && pika_edit_paste_is_floating (*paste_type, drawables->data)) floating_format = pika_drawable_get_format_with_alpha (drawables->data); else floating_format = pika_image_get_layer_format (image, TRUE); if (PIKA_IS_IMAGE (paste)) { layers = pika_image_get_layer_iter (PIKA_IMAGE (paste)); if (g_list_length (layers) > 1) { if (pika_edit_paste_is_in_place (*paste_type)) *paste_type = PIKA_PASTE_TYPE_NEW_LAYER_IN_PLACE; else *paste_type = PIKA_PASTE_TYPE_NEW_LAYER; } layers = pika_edit_paste_get_tagged_layers (image, layers, NULL, floating_format, pika_drawable_get_base_type (drawables->data), pika_drawable_get_precision (drawables->data), *paste_type); layers = g_list_reverse (layers); } else if (PIKA_IS_BUFFER (paste)) { PikaLayer *layer; layer = pika_layer_new_from_buffer (PIKA_BUFFER (paste), image, floating_format, _("Pasted Layer"), PIKA_OPACITY_OPAQUE, pika_image_get_default_new_layer_mode (image)); layers = g_list_prepend (layers, layer); } return layers; } static void pika_edit_paste_get_viewport_offset (PikaImage *image, GList *drawables, GList *pasted_layers, gint viewport_x, gint viewport_y, gint viewport_width, gint viewport_height, gint *pasted_bbox_x, gint *pasted_bbox_y, gint *offset_x, gint *offset_y) { GList *iter; gint image_width; gint image_height; /* Source: pasted layers */ gint source_width; gint source_height; gint x1 = G_MAXINT; gint y1 = G_MAXINT; gint x2 = G_MININT; gint y2 = G_MININT; gboolean clamp_to_image = TRUE; g_return_if_fail (PIKA_IS_IMAGE (image)); g_return_if_fail (pasted_layers != NULL); g_return_if_fail (offset_x != NULL); g_return_if_fail (offset_y != NULL); image_width = pika_image_get_width (image); image_height = pika_image_get_height (image); for (iter = pasted_layers; iter; iter = iter->next) { gint layer_off_x; gint layer_off_y; gint layer_width; gint layer_height; g_return_if_fail (PIKA_IS_VIEWABLE (iter->data)); pika_item_get_offset (PIKA_ITEM (iter->data), &layer_off_x, &layer_off_y); pika_viewable_get_size (iter->data, &layer_width, &layer_height); x1 = MIN (layer_off_x, x1); y1 = MIN (layer_off_y, y1); x2 = MAX (layer_off_x + layer_width, x2); y2 = MAX (layer_off_y + layer_height, y2); } /* Source offset and dimensions: this is the bounding box for all layers which * we want to paste together, keeping their relative position to each others. */ *pasted_bbox_x = x1; *pasted_bbox_y = y1; source_width = x2 - x1; source_height = y2 - y1; if (viewport_width == image_width && viewport_height == image_height) { /* if the whole image is visible, act as if there was no viewport */ viewport_x = 0; viewport_y = 0; viewport_width = 0; viewport_height = 0; } if (drawables) { /* if pasting while 1 or more drawables are selected */ gboolean empty_target = TRUE; gint target_x; gint target_y; gint target_width; gint target_height; gint paste_x, paste_y; gint paste_width; gint paste_height; gboolean have_mask; have_mask = ! pika_channel_is_empty (pika_image_get_mask (image)); for (iter = drawables; iter; iter = iter->next) { PikaContainer *children; children = pika_viewable_get_children (iter->data); if (! children || pika_container_get_n_children (children) > 0) { empty_target = FALSE; break; } } if (empty_target) { /* treat empty layer groups as image-sized, use the selection * as target */ pika_item_bounds (PIKA_ITEM (pika_image_get_mask (image)), &target_x, &target_y, &target_width, &target_height); } else { gint x1 = G_MAXINT; gint y1 = G_MAXINT; gint x2 = G_MININT; gint y2 = G_MININT; for (iter = drawables; iter; iter = iter->next) { gint layer_off_x; gint layer_off_y; gint mask_off_x; gint mask_off_y; gint mask_width; gint mask_height; pika_item_get_offset (PIKA_ITEM (iter->data), &layer_off_x, &layer_off_y); /* This is the bounds relatively to the drawable. */ pika_item_mask_intersect (PIKA_ITEM (iter->data), &mask_off_x, &mask_off_y, &mask_width, &mask_height); /* The bounds relatively to the image. */ x1 = MIN (layer_off_x + mask_off_x, x1); y1 = MIN (layer_off_y + mask_off_y, y1); x2 = MAX (layer_off_x + mask_off_x + mask_width, x2); y2 = MAX (layer_off_y + mask_off_y + mask_height, y2); } target_x = x1; target_y = y1; target_width = x2 - x1; target_height = y2 - y1; } if (! have_mask && /* if we have no mask */ viewport_width > 0 && /* and we have a viewport */ viewport_height > 0 && (source_width < target_width || /* and the paste is smaller than the target */ source_height < target_height) && /* and the viewport intersects with the target */ pika_rectangle_intersect (viewport_x, viewport_y, viewport_width, viewport_height, target_x, target_x, target_width, target_height, &paste_x, &paste_y, &paste_width, &paste_height)) { /* center on the viewport */ *offset_x = viewport_x + (viewport_width - source_width) / 2; *offset_y = viewport_y + (viewport_height- source_height) / 2; } else { /* otherwise center on the target */ *offset_x = target_x + (target_width - source_width) / 2; *offset_y = target_y + (target_height - source_height) / 2; /* and keep it that way */ clamp_to_image = FALSE; } } else if (viewport_width > 0 && /* if we have a viewport */ viewport_height > 0 && (source_width < image_width || /* and the paste is */ source_height < image_height)) /* smaller than the image */ { /* center on the viewport */ *offset_x = viewport_x + (viewport_width - source_width) / 2; *offset_y = viewport_y + (viewport_height - source_height) / 2; } else { /* otherwise center on the image */ *offset_x = (image_width - source_width) / 2; *offset_y = (image_height - source_height) / 2; /* and keep it that way */ clamp_to_image = FALSE; } if (clamp_to_image) { /* Ensure that the pasted layer is always within the image, if it * fits and aligned at top left if it doesn't. (See bug #142944). */ *offset_x = MIN (*offset_x, image_width - source_width); *offset_y = MIN (*offset_y, image_height - source_height); *offset_x = MAX (*offset_x, 0); *offset_y = MAX (*offset_y, 0); } } static GList * pika_edit_paste_paste (PikaImage *image, GList *drawables, GList *layers, PikaPasteType paste_type, gboolean use_offset, gint layers_bbox_x, gint layers_bbox_y, gint offset_x, gint offset_y) { GList *iter; g_return_val_if_fail (paste_type > PIKA_PASTE_TYPE_FLOATING_INTO_IN_PLACE || g_list_length (drawables) == 1, NULL); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_EDIT_PASTE, C_("undo-type", "Paste")); for (iter = layers; iter; iter = iter->next) { /* Layers for in-place paste are already translated. */ if (use_offset) pika_item_translate (PIKA_ITEM (iter->data), offset_x - layers_bbox_x, offset_y - layers_bbox_y, FALSE); switch (paste_type) { case PIKA_PASTE_TYPE_FLOATING: case PIKA_PASTE_TYPE_FLOATING_IN_PLACE: /* if there is a selection mask clear it - this might not * always be desired, but in general, it seems like the correct * behavior */ if (! pika_channel_is_empty (pika_image_get_mask (image))) pika_channel_clear (pika_image_get_mask (image), NULL, TRUE); /* fall thru */ case PIKA_PASTE_TYPE_FLOATING_INTO: case PIKA_PASTE_TYPE_FLOATING_INTO_IN_PLACE: floating_sel_attach (iter->data, drawables->data); break; case PIKA_PASTE_TYPE_NEW_LAYER_OR_FLOATING: case PIKA_PASTE_TYPE_NEW_LAYER_OR_FLOATING_IN_PLACE: if (g_list_length (drawables) == 1 && PIKA_IS_LAYER_MASK (drawables->data)) { if (! pika_channel_is_empty (pika_image_get_mask (image))) pika_channel_clear (pika_image_get_mask (image), NULL, TRUE); floating_sel_attach (iter->data, drawables->data); break; } /* fall thru if not a layer mask */ case PIKA_PASTE_TYPE_NEW_LAYER: case PIKA_PASTE_TYPE_NEW_LAYER_IN_PLACE: { PikaLayer *parent = NULL; gint position = 0; /* always add on top of a passed layer, where we would attach * a floating selection */ if (g_list_length (drawables) > 0 && PIKA_IS_LAYER (drawables->data)) { PikaDrawable *top_drawable; top_drawable = pika_edit_paste_get_top_item (drawables); parent = pika_layer_get_parent (PIKA_LAYER (top_drawable)); position = pika_item_get_index (PIKA_ITEM (top_drawable)); } pika_image_add_layer (image, iter->data, parent, position, TRUE); } break; case PIKA_PASTE_TYPE_NEW_MERGED_LAYER_OR_FLOATING: case PIKA_PASTE_TYPE_NEW_MERGED_LAYER_OR_FLOATING_IN_PLACE: g_return_val_if_reached (NULL); } } pika_image_undo_group_end (image); return layers; } GList * pika_edit_paste (PikaImage *image, GList *drawables, PikaObject *paste, PikaPasteType paste_type, PikaContext *context, gboolean merged, gint viewport_x, gint viewport_y, gint viewport_width, gint viewport_height) { GList *layers; gboolean use_offset = FALSE; gint layers_bbox_x = 0; gint layers_bbox_y = 0; gint offset_y = 0; gint offset_x = 0; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (PIKA_IS_IMAGE (paste) || PIKA_IS_BUFFER (paste), NULL); for (GList *iter = drawables; iter; iter = iter->next) { g_return_val_if_fail (PIKA_IS_DRAWABLE (iter->data), NULL); g_return_val_if_fail (pika_item_is_attached (PIKA_ITEM (iter->data)), NULL); } if (merged && PIKA_IS_IMAGE (paste)) { PikaImage *tmp_image; tmp_image = pika_image_duplicate (PIKA_IMAGE (paste)); pika_container_remove (image->pika->images, PIKA_OBJECT (tmp_image)); pika_image_merge_visible_layers (tmp_image, context, PIKA_EXPAND_AS_NECESSARY, FALSE, FALSE, NULL); layers = g_list_copy (pika_image_get_layer_iter (tmp_image)); /* The merge process should ensure that we get a single non-group and * no-mask layer. */ g_return_val_if_fail (g_list_length (layers) == 1, NULL); layers->data = pika_item_convert (PIKA_ITEM (layers->data), image, G_TYPE_FROM_INSTANCE (layers->data)); switch (paste_type) { case PIKA_PASTE_TYPE_FLOATING: case PIKA_PASTE_TYPE_FLOATING_IN_PLACE: case PIKA_PASTE_TYPE_FLOATING_INTO: case PIKA_PASTE_TYPE_FLOATING_INTO_IN_PLACE: if (pika_drawable_get_format (PIKA_DRAWABLE (layers->data)) != pika_drawable_get_format_with_alpha (PIKA_DRAWABLE (layers->data))) { pika_drawable_convert_type (PIKA_DRAWABLE (layers->data), image, pika_drawable_get_base_type (layers->data), pika_drawable_get_precision (layers->data), TRUE, NULL, NULL, GEGL_DITHER_NONE, GEGL_DITHER_NONE, FALSE, NULL); } break; default: break; } g_object_unref (tmp_image); } else { layers = pika_edit_paste_get_layers (image, drawables, paste, &paste_type); } if (! layers) return NULL; if (pika_edit_paste_is_in_place (paste_type)) { if (PIKA_IS_BUFFER (paste)) { PikaBuffer *buffer = PIKA_BUFFER (paste); use_offset = TRUE; offset_x = buffer->offset_x; offset_y = buffer->offset_y; } } else { use_offset = TRUE; pika_edit_paste_get_viewport_offset (image, drawables, layers, viewport_x, viewport_y, viewport_width, viewport_height, &layers_bbox_x, &layers_bbox_y, &offset_x, &offset_y); } return pika_edit_paste_paste (image, drawables, layers, paste_type, use_offset, layers_bbox_x, layers_bbox_y, offset_x, offset_y); } PikaImage * pika_edit_paste_as_new_image (Pika *pika, PikaObject *paste, PikaContext *context) { PikaImage *image = NULL; g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (PIKA_IS_IMAGE (paste) || PIKA_IS_BUFFER (paste), NULL); if (PIKA_IS_IMAGE (paste)) { gint offset_x; gint offset_y; gint new_width; gint new_height; offset_x = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (paste), "pika-edit-new-image-x")); offset_y = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (paste), "pika-edit-new-image-y")); new_width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (paste), "pika-edit-new-image-width")); new_height = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (paste), "pika-edit-new-image-height")); image = pika_image_duplicate (PIKA_IMAGE (paste)); if (new_width > 0 && new_height > 0) { pika_image_undo_disable (image); pika_image_resize (image, context, new_width, new_height, -offset_x, -offset_y, NULL); pika_image_undo_enable (image); } } else if (PIKA_IS_BUFFER (paste)) { image = pika_image_new_from_buffer (pika, PIKA_BUFFER (paste)); } return image; } const gchar * pika_edit_named_cut (PikaImage *image, const gchar *name, GList *drawables, PikaContext *context, GError **error) { PikaBuffer *buffer; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (name != NULL, NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); buffer = pika_edit_extract (image, drawables, context, TRUE, error); if (buffer) { pika_object_set_name (PIKA_OBJECT (buffer), name); pika_container_add (image->pika->named_buffers, PIKA_OBJECT (buffer)); g_object_unref (buffer); return pika_object_get_name (buffer); } return NULL; } const gchar * pika_edit_named_copy (PikaImage *image, const gchar *name, GList *drawables, PikaContext *context, GError **error) { PikaBuffer *buffer; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (name != NULL, NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); buffer = pika_edit_extract (image, drawables, context, FALSE, error); if (buffer) { pika_object_set_name (PIKA_OBJECT (buffer), name); pika_container_add (image->pika->named_buffers, PIKA_OBJECT (buffer)); g_object_unref (buffer); return pika_object_get_name (buffer); } return NULL; } const gchar * pika_edit_named_copy_visible (PikaImage *image, const gchar *name, PikaContext *context, GError **error) { PikaBuffer *buffer; GList *pickables; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (name != NULL, NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); pickables = g_list_prepend (NULL, image); buffer = pika_edit_extract (image, pickables, context, FALSE, error); g_list_free (pickables); if (buffer) { pika_object_set_name (PIKA_OBJECT (buffer), name); pika_container_add (image->pika->named_buffers, PIKA_OBJECT (buffer)); g_object_unref (buffer); return pika_object_get_name (buffer); } return NULL; } /* private functions */ /** * pika_edit_extract: * @image: * @pickables: * @context: * @cut_pixels: * @error: * * Extracts the selected part of @image from the list of @pickables. * If @cut_pixels is %TRUE, and there is only one pickable input, and if * this pickable is a #PikaDrawable, then the selected pixels will be * effectively erased from the input pickable. * Otherwise @cut_pixels has no additional effect. * Note that all @pickables must belong to the same @image. * * Returns: a #PikaBuffer of the selected part of @image as if only the * selected @pickables were present (composited according to their * properties, unless there is only one pickable, in which case direct * pixel information is used without composition). */ static PikaBuffer * pika_edit_extract (PikaImage *image, GList *pickables, PikaContext *context, gboolean cut_pixels, GError **error) { GeglBuffer *buffer; gint offset_x; gint offset_y; g_return_val_if_fail (g_list_length (pickables) > 0, NULL); if (g_list_length (pickables) > 1 || ! PIKA_IS_DRAWABLE (pickables->data)) cut_pixels = FALSE; if (cut_pixels) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_EDIT_CUT, C_("undo-type", "Cut")); /* Cut/copy the mask portion from the image */ buffer = pika_selection_extract (PIKA_SELECTION (pika_image_get_mask (image)), pickables, context, cut_pixels, FALSE, FALSE, &offset_x, &offset_y, error); if (cut_pixels) pika_image_undo_group_end (image); if (buffer) { PikaBuffer *pika_buffer; gdouble res_x; gdouble res_y; pika_buffer = pika_buffer_new (buffer, _("Global Buffer"), offset_x, offset_y, FALSE); g_object_unref (buffer); pika_image_get_resolution (image, &res_x, &res_y); pika_buffer_set_resolution (pika_buffer, res_x, res_y); pika_buffer_set_unit (pika_buffer, pika_image_get_unit (image)); if (PIKA_IS_COLOR_MANAGED (pickables->data)) { PikaColorProfile *profile = pika_color_managed_get_color_profile (PIKA_COLOR_MANAGED (pickables->data)); if (profile) pika_buffer_set_color_profile (pika_buffer, profile); } return pika_buffer; } return NULL; } /* Return the visually top item. */ static PikaDrawable * pika_edit_paste_get_top_item (GList *drawables) { GList *iter; PikaDrawable *top = NULL; GList *top_path = NULL; for (iter = drawables; iter; iter = iter->next) { GList *path = pika_item_get_path (iter->data); if (top == NULL) { top = iter->data; top_path = path; path = NULL; } else { GList *p_iter; GList *tp_iter; for (p_iter = path, tp_iter = top_path; p_iter || tp_iter; p_iter = p_iter->next, tp_iter = tp_iter->next) { if (tp_iter == NULL) { break; } else if (p_iter == NULL || GPOINTER_TO_UINT (p_iter->data) < GPOINTER_TO_UINT (tp_iter->data)) { g_list_free (top_path); top_path = path; path = NULL; top = iter->data; break; } else if (GPOINTER_TO_UINT (p_iter->data) > GPOINTER_TO_UINT (tp_iter->data)) { break; } } } g_list_free (path); } g_list_free (top_path); return top; }