/* 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-2001 Spencer Kimball, Peter Mattis, and others * * 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 "libpikamath/pikamath.h" #include "libpikawidgets/pikawidgets.h" #include "tools-types.h" #include "gegl/pikaapplicator.h" #include "gegl/pika-gegl-nodes.h" #include "gegl/pika-gegl-utils.h" #include "core/pika.h" #include "core/pika-transform-resize.h" #include "core/pika-transform-utils.h" #include "core/pikaboundary.h" #include "core/pikacontainer.h" #include "core/pikadrawablefilter.h" #include "core/pikaerror.h" #include "core/pikafilter.h" #include "core/pikagrouplayer.h" #include "core/pikaimage.h" #include "core/pikaimage-item-list.h" #include "core/pikaimage-undo.h" #include "core/pikaimage-undo-push.h" #include "core/pikalayer.h" #include "core/pikalayermask.h" #include "core/pikapickable.h" #include "core/pikaprojection.h" #include "core/pikatoolinfo.h" #include "core/pikaviewable.h" #include "vectors/pikavectors.h" #include "vectors/pikastroke.h" #include "widgets/pikawidgets-utils.h" #include "display/pikacanvasitem.h" #include "display/pikadisplay.h" #include "display/pikadisplayshell.h" #include "display/pikatoolgui.h" #include "display/pikatoolwidget.h" #include "pikatoolcontrol.h" #include "pikatransformgridoptions.h" #include "pikatransformgridtool.h" #include "pikatransformgridtoolundo.h" #include "pikatransformoptions.h" #include "pika-intl.h" #define EPSILON 1e-6 #define RESPONSE_RESET 1 #define RESPONSE_READJUST 2 #define UNDO_COMPRESS_TIME (0.5 * G_TIME_SPAN_SECOND) typedef struct { PikaTransformGridTool *tg_tool; PikaDrawable *drawable; PikaDrawableFilter *filter; PikaDrawable *root_drawable; GeglNode *transform_node; GeglNode *crop_node; PikaMatrix3 transform; GeglRectangle bounds; } Filter; typedef struct { PikaTransformGridTool *tg_tool; PikaDrawable *root_drawable; } AddFilterData; typedef struct { gint64 time; PikaTransformDirection direction; TransInfo trans_infos[2]; } UndoInfo; static void pika_transform_grid_tool_finalize (GObject *object); static gboolean pika_transform_grid_tool_initialize (PikaTool *tool, PikaDisplay *display, GError **error); static void pika_transform_grid_tool_control (PikaTool *tool, PikaToolAction action, PikaDisplay *display); static void pika_transform_grid_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display); static void pika_transform_grid_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display); static void pika_transform_grid_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display); static void pika_transform_grid_tool_modifier_key (PikaTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, PikaDisplay *display); static void pika_transform_grid_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display); static const gchar * pika_transform_grid_tool_can_undo (PikaTool *tool, PikaDisplay *display); static const gchar * pika_transform_grid_tool_can_redo (PikaTool *tool, PikaDisplay *display); static gboolean pika_transform_grid_tool_undo (PikaTool *tool, PikaDisplay *display); static gboolean pika_transform_grid_tool_redo (PikaTool *tool, PikaDisplay *display); static void pika_transform_grid_tool_options_notify (PikaTool *tool, PikaToolOptions *options, const GParamSpec *pspec); static void pika_transform_grid_tool_draw (PikaDrawTool *draw_tool); static void pika_transform_grid_tool_recalc_matrix (PikaTransformTool *tr_tool); static gchar * pika_transform_grid_tool_get_undo_desc (PikaTransformTool *tr_tool); static PikaTransformDirection pika_transform_grid_tool_get_direction (PikaTransformTool *tr_tool); static GeglBuffer * pika_transform_grid_tool_transform (PikaTransformTool *tr_tool, GList *objects, GeglBuffer *orig_buffer, gint orig_offset_x, gint orig_offset_y, PikaColorProfile **buffer_profile, gint *new_offset_x, gint *new_offset_y); static void pika_transform_grid_tool_real_apply_info (PikaTransformGridTool *tg_tool, const TransInfo info); static gchar * pika_transform_grid_tool_real_get_undo_desc (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_real_update_widget (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_real_widget_changed (PikaTransformGridTool *tg_tool); static GeglBuffer * pika_transform_grid_tool_real_transform (PikaTransformGridTool *tg_tool, GList *objects, GeglBuffer *orig_buffer, gint orig_offset_x, gint orig_offset_y, PikaColorProfile **buffer_profile, gint *new_offset_x, gint *new_offset_y); static void pika_transform_grid_tool_widget_changed (PikaToolWidget *widget, PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_widget_response (PikaToolWidget *widget, gint response_id, PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_filter_flush (PikaDrawableFilter *filter, PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_halt (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_commit (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_dialog (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_dialog_update (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_prepare (PikaTransformGridTool *tg_tool, PikaDisplay *display); static PikaToolWidget * pika_transform_grid_tool_get_widget (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_update_widget (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_response (PikaToolGui *gui, gint response_id, PikaTransformGridTool *tg_tool); static gboolean pika_transform_grid_tool_composited_preview (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_update_sensitivity (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_update_preview (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_update_filters (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_hide_selected_objects (PikaTransformGridTool *tg_tool, GList *objects); static void pika_transform_grid_tool_show_selected_objects (PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_add_filter (PikaDrawable *drawable, AddFilterData *data); static void pika_transform_grid_tool_remove_filter (PikaDrawable *drawable, PikaTransformGridTool *tg_tool); static void pika_transform_grid_tool_effective_mode_changed (PikaLayer *layer, PikaTransformGridTool *tg_tool); static Filter * filter_new (PikaTransformGridTool *tg_tool, PikaDrawable *drawable, PikaDrawable *root_drawable, gboolean add_filter); static void filter_free (Filter *filter); static UndoInfo * undo_info_new (void); static void undo_info_free (UndoInfo *info); static gboolean trans_info_equal (const TransInfo trans_info1, const TransInfo trans_info2); static gboolean trans_infos_equal (const TransInfo *trans_infos1, const TransInfo *trans_infos2); G_DEFINE_TYPE (PikaTransformGridTool, pika_transform_grid_tool, PIKA_TYPE_TRANSFORM_TOOL) #define parent_class pika_transform_grid_tool_parent_class static void pika_transform_grid_tool_class_init (PikaTransformGridToolClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaToolClass *tool_class = PIKA_TOOL_CLASS (klass); PikaDrawToolClass *draw_class = PIKA_DRAW_TOOL_CLASS (klass); PikaTransformToolClass *tr_class = PIKA_TRANSFORM_TOOL_CLASS (klass); object_class->finalize = pika_transform_grid_tool_finalize; tool_class->initialize = pika_transform_grid_tool_initialize; tool_class->control = pika_transform_grid_tool_control; tool_class->button_press = pika_transform_grid_tool_button_press; tool_class->button_release = pika_transform_grid_tool_button_release; tool_class->motion = pika_transform_grid_tool_motion; tool_class->modifier_key = pika_transform_grid_tool_modifier_key; tool_class->cursor_update = pika_transform_grid_tool_cursor_update; tool_class->can_undo = pika_transform_grid_tool_can_undo; tool_class->can_redo = pika_transform_grid_tool_can_redo; tool_class->undo = pika_transform_grid_tool_undo; tool_class->redo = pika_transform_grid_tool_redo; tool_class->options_notify = pika_transform_grid_tool_options_notify; draw_class->draw = pika_transform_grid_tool_draw; tr_class->recalc_matrix = pika_transform_grid_tool_recalc_matrix; tr_class->get_undo_desc = pika_transform_grid_tool_get_undo_desc; tr_class->get_direction = pika_transform_grid_tool_get_direction; tr_class->transform = pika_transform_grid_tool_transform; klass->info_to_matrix = NULL; klass->matrix_to_info = NULL; klass->apply_info = pika_transform_grid_tool_real_apply_info; klass->get_undo_desc = pika_transform_grid_tool_real_get_undo_desc; klass->dialog = NULL; klass->dialog_update = NULL; klass->prepare = NULL; klass->readjust = NULL; klass->get_widget = NULL; klass->update_widget = pika_transform_grid_tool_real_update_widget; klass->widget_changed = pika_transform_grid_tool_real_widget_changed; klass->transform = pika_transform_grid_tool_real_transform; klass->ok_button_label = _("_Transform"); } static void pika_transform_grid_tool_init (PikaTransformGridTool *tg_tool) { PikaTool *tool = PIKA_TOOL (tg_tool); pika_tool_control_set_scroll_lock (tool->control, TRUE); pika_tool_control_set_preserve (tool->control, FALSE); pika_tool_control_set_dirty_mask (tool->control, PIKA_DIRTY_IMAGE_SIZE | PIKA_DIRTY_IMAGE_STRUCTURE | PIKA_DIRTY_DRAWABLE | PIKA_DIRTY_SELECTION | PIKA_DIRTY_ACTIVE_DRAWABLE); pika_tool_control_set_active_modifiers (tool->control, PIKA_TOOL_ACTIVE_MODIFIERS_SAME); pika_tool_control_set_precision (tool->control, PIKA_CURSOR_PRECISION_SUBPIXEL); pika_tool_control_set_cursor (tool->control, PIKA_CURSOR_CROSSHAIR_SMALL); pika_tool_control_set_action_opacity (tool->control, "tools-transform-preview-opacity-set"); tg_tool->strokes = g_ptr_array_new (); } static void pika_transform_grid_tool_finalize (GObject *object) { PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (object); g_clear_object (&tg_tool->gui); g_clear_pointer (&tg_tool->strokes, g_ptr_array_unref); G_OBJECT_CLASS (parent_class)->finalize (object); } static gboolean pika_transform_grid_tool_initialize (PikaTool *tool, PikaDisplay *display, GError **error) { PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tool); PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tool); PikaTransformGridOptions *tg_options = PIKA_TRANSFORM_GRID_TOOL_GET_OPTIONS (tool); PikaImage *image = pika_display_get_image (display); GList *drawables; GList *objects; GList *iter; UndoInfo *undo_info; objects = pika_transform_tool_check_selected_objects (tr_tool, display, error); if (! objects) return FALSE; drawables = pika_image_get_selected_drawables (image); if (g_list_length (drawables) < 1) { pika_tool_message_literal (tool, display, _("No selected drawables.")); g_list_free (drawables); g_list_free (objects); return FALSE; } tool->display = display; tool->drawables = drawables; tr_tool->objects = objects; for (iter = objects; iter; iter = iter->next) if (PIKA_IS_DRAWABLE (iter->data)) pika_viewable_preview_freeze (iter->data); /* Initialize the transform_grid tool dialog */ if (! tg_tool->gui) pika_transform_grid_tool_dialog (tg_tool); /* Find the transform bounds for some tools (like scale, * perspective) that actually need the bounds for initializing */ pika_transform_tool_bounds (tr_tool, display); /* Initialize the tool-specific trans_info, and adjust the tool dialog */ pika_transform_grid_tool_prepare (tg_tool, display); /* Recalculate the tool's transformation matrix */ pika_transform_tool_recalc_matrix (tr_tool, display); /* Get the on-canvas gui */ tg_tool->widget = pika_transform_grid_tool_get_widget (tg_tool); pika_transform_grid_tool_hide_selected_objects (tg_tool, objects); /* start drawing the bounding box and handles... */ pika_draw_tool_start (PIKA_DRAW_TOOL (tool), display); /* Initialize undo and redo lists */ undo_info = undo_info_new (); tg_tool->undo_list = g_list_prepend (NULL, undo_info); tg_tool->redo_list = NULL; /* Save the current transformation info */ memcpy (undo_info->trans_infos, tg_tool->trans_infos, sizeof (tg_tool->trans_infos)); if (tg_options->direction_chain_button) gtk_widget_set_sensitive (tg_options->direction_chain_button, TRUE); return TRUE; } static void pika_transform_grid_tool_control (PikaTool *tool, PikaToolAction action, PikaDisplay *display) { PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tool); switch (action) { case PIKA_TOOL_ACTION_PAUSE: case PIKA_TOOL_ACTION_RESUME: break; case PIKA_TOOL_ACTION_HALT: pika_transform_grid_tool_halt (tg_tool); break; case PIKA_TOOL_ACTION_COMMIT: if (tool->display) pika_transform_grid_tool_commit (tg_tool); break; } PIKA_TOOL_CLASS (parent_class)->control (tool, action, display); } static void pika_transform_grid_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display) { PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tool); if (tg_tool->widget) { pika_tool_widget_hover (tg_tool->widget, coords, state, TRUE); if (pika_tool_widget_button_press (tg_tool->widget, coords, time, state, press_type)) { tg_tool->grab_widget = tg_tool->widget; } } pika_tool_control_activate (tool->control); } static void pika_transform_grid_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display) { PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tool); PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tool); pika_tool_control_halt (tool->control); if (tg_tool->grab_widget) { pika_tool_widget_button_release (tg_tool->grab_widget, coords, time, state, release_type); tg_tool->grab_widget = NULL; } if (release_type != PIKA_BUTTON_RELEASE_CANCEL) { /* We're done with an interaction, save it on the undo list */ pika_transform_grid_tool_push_internal_undo (tg_tool, FALSE); } else { UndoInfo *undo_info = tg_tool->undo_list->data; /* Restore the last saved state */ memcpy (tg_tool->trans_infos, undo_info->trans_infos, sizeof (tg_tool->trans_infos)); /* recalculate the tool's transformation matrix */ pika_transform_tool_recalc_matrix (tr_tool, display); } } static void pika_transform_grid_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display) { PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tool); if (tg_tool->grab_widget) { pika_tool_widget_motion (tg_tool->grab_widget, coords, time, state); } } static void pika_transform_grid_tool_modifier_key (PikaTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, PikaDisplay *display) { PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tool); if (tg_tool->widget) { PIKA_TOOL_CLASS (parent_class)->modifier_key (tool, key, press, state, display); } else { PikaTransformGridOptions *options = PIKA_TRANSFORM_GRID_TOOL_GET_OPTIONS (tool); if (key == pika_get_constrain_behavior_mask ()) { g_object_set (options, "frompivot-scale", ! options->frompivot_scale, "frompivot-shear", ! options->frompivot_shear, "frompivot-perspective", ! options->frompivot_perspective, NULL); } else if (key == pika_get_extend_selection_mask ()) { g_object_set (options, "cornersnap", ! options->cornersnap, "constrain-move", ! options->constrain_move, "constrain-scale", ! options->constrain_scale, "constrain-rotate", ! options->constrain_rotate, "constrain-shear", ! options->constrain_shear, "constrain-perspective", ! options->constrain_perspective, NULL); } } } static void pika_transform_grid_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display) { PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tool); GList *objects; objects = pika_transform_tool_check_selected_objects (tr_tool, display, NULL); if (display != tool->display && ! objects) { pika_tool_set_cursor (tool, display, pika_tool_control_get_cursor (tool->control), pika_tool_control_get_tool_cursor (tool->control), PIKA_CURSOR_MODIFIER_BAD); return; } g_list_free (objects); PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display); } static const gchar * pika_transform_grid_tool_can_undo (PikaTool *tool, PikaDisplay *display) { PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tool); if (! tg_tool->undo_list || ! tg_tool->undo_list->next) return NULL; return _("Transform Step"); } static const gchar * pika_transform_grid_tool_can_redo (PikaTool *tool, PikaDisplay *display) { PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tool); if (! tg_tool->redo_list) return NULL; return _("Transform Step"); } static gboolean pika_transform_grid_tool_undo (PikaTool *tool, PikaDisplay *display) { PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tool); PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tool); PikaTransformOptions *tr_options = PIKA_TRANSFORM_TOOL_GET_OPTIONS (tool); UndoInfo *undo_info; PikaTransformDirection direction; undo_info = tg_tool->undo_list->data; direction = undo_info->direction; /* Move undo_info from undo_list to redo_list */ tg_tool->redo_list = g_list_prepend (tg_tool->redo_list, undo_info); tg_tool->undo_list = g_list_remove (tg_tool->undo_list, undo_info); undo_info = tg_tool->undo_list->data; /* Restore the previous transformation info */ memcpy (tg_tool->trans_infos, undo_info->trans_infos, sizeof (tg_tool->trans_infos)); /* Restore the previous transformation direction */ if (direction != tr_options->direction) { g_object_set (tr_options, "direction", direction, NULL); } /* recalculate the tool's transformation matrix */ pika_transform_tool_recalc_matrix (tr_tool, display); return TRUE; } static gboolean pika_transform_grid_tool_redo (PikaTool *tool, PikaDisplay *display) { PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tool); PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tool); PikaTransformOptions *tr_options = PIKA_TRANSFORM_TOOL_GET_OPTIONS (tool); UndoInfo *undo_info; PikaTransformDirection direction; undo_info = tg_tool->redo_list->data; direction = undo_info->direction; /* Move undo_info from redo_list to undo_list */ tg_tool->undo_list = g_list_prepend (tg_tool->undo_list, undo_info); tg_tool->redo_list = g_list_remove (tg_tool->redo_list, undo_info); /* Restore the previous transformation info */ memcpy (tg_tool->trans_infos, undo_info->trans_infos, sizeof (tg_tool->trans_infos)); /* Restore the previous transformation direction */ if (direction != tr_options->direction) { g_object_set (tr_options, "direction", direction, NULL); } /* recalculate the tool's transformation matrix */ pika_transform_tool_recalc_matrix (tr_tool, display); return TRUE; } static void pika_transform_grid_tool_options_notify (PikaTool *tool, PikaToolOptions *options, const GParamSpec *pspec) { PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tool); PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tool); PikaTransformGridOptions *tg_options = PIKA_TRANSFORM_GRID_OPTIONS (options); PIKA_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec); if (! strcmp (pspec->name, "type")) { pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display); return; } if (! tg_tool->widget) return; if (! strcmp (pspec->name, "direction")) { /* recalculate the tool's transformation matrix */ pika_transform_tool_recalc_matrix (tr_tool, tool->display); } else if (! strcmp (pspec->name, "show-preview") || ! strcmp (pspec->name, "composited-preview")) { if (tg_tool->previews) { PikaDisplay *display; GList *objects; display = tool->display; objects = pika_transform_tool_get_selected_objects (tr_tool, display); if (objects) { if (tg_options->show_preview && ! pika_transform_grid_tool_composited_preview (tg_tool)) { pika_transform_grid_tool_hide_selected_objects (tg_tool, objects); } else { pika_transform_grid_tool_show_selected_objects (tg_tool); } g_list_free (objects); } pika_transform_grid_tool_update_preview (tg_tool); } } else if (! strcmp (pspec->name, "interpolation") || ! strcmp (pspec->name, "clip") || ! strcmp (pspec->name, "preview-opacity")) { pika_transform_grid_tool_update_preview (tg_tool); } else if (g_str_has_prefix (pspec->name, "constrain-") || g_str_has_prefix (pspec->name, "frompivot-") || ! strcmp (pspec->name, "fixedpivot") || ! strcmp (pspec->name, "cornersnap")) { pika_transform_grid_tool_dialog_update (tg_tool); } } static void pika_transform_grid_tool_draw (PikaDrawTool *draw_tool) { PikaTool *tool = PIKA_TOOL (draw_tool); PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (draw_tool); PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (draw_tool); PikaTransformGridOptions *options = PIKA_TRANSFORM_GRID_TOOL_GET_OPTIONS (tool); PikaTransformOptions *tr_options = PIKA_TRANSFORM_OPTIONS (options); PikaDisplayShell *shell = pika_display_get_shell (tool->display); PikaImage *image = pika_display_get_image (tool->display); PikaMatrix3 matrix = tr_tool->transform; PikaCanvasItem *item; if (tr_options->direction == PIKA_TRANSFORM_BACKWARD) pika_matrix3_invert (&matrix); if (tr_options->type == PIKA_TRANSFORM_TYPE_LAYER || tr_options->type == PIKA_TRANSFORM_TYPE_IMAGE) { GList *pickables = NULL; GList *iter; if (tr_options->type == PIKA_TRANSFORM_TYPE_IMAGE) { if (! shell->show_all) pickables = g_list_prepend (pickables, image); else pickables = g_list_prepend (pickables, pika_image_get_projection (image)); } else { for (iter = tool->drawables; iter; iter = iter->next) pickables = g_list_prepend (pickables, iter->data); } for (iter = pickables; iter; iter = iter->next) { PikaCanvasItem *preview; preview = pika_draw_tool_add_transform_preview (draw_tool, PIKA_PICKABLE (iter->data), &matrix, tr_tool->x1, tr_tool->y1, tr_tool->x2, tr_tool->y2); tg_tool->previews = g_list_prepend (tg_tool->previews, preview); /* NOT g_set_weak_pointr() because the pointer is already set */ g_object_add_weak_pointer (G_OBJECT (tg_tool->previews->data), (gpointer) &tg_tool->previews->data); } g_list_free (pickables); } if (tr_options->type == PIKA_TRANSFORM_TYPE_SELECTION) { const PikaBoundSeg *segs_in; const PikaBoundSeg *segs_out; gint n_segs_in; gint n_segs_out; pika_channel_boundary (pika_image_get_mask (image), &segs_in, &segs_out, &n_segs_in, &n_segs_out, 0, 0, 0, 0); if (segs_in) { g_set_weak_pointer (&tg_tool->boundary_in, pika_draw_tool_add_boundary (draw_tool, segs_in, n_segs_in, &matrix, 0, 0)); pika_canvas_item_set_visible (tg_tool->boundary_in, tr_tool->transform_valid); } if (segs_out) { g_set_weak_pointer (&tg_tool->boundary_out, pika_draw_tool_add_boundary (draw_tool, segs_out, n_segs_out, &matrix, 0, 0)); pika_canvas_item_set_visible (tg_tool->boundary_out, tr_tool->transform_valid); } } else if (tr_options->type == PIKA_TRANSFORM_TYPE_PATH) { GList *iter; for (iter = pika_image_get_selected_vectors (image); iter; iter = iter->next) { PikaVectors *vectors = iter->data; PikaStroke *stroke = NULL; while ((stroke = pika_vectors_stroke_get_next (vectors, stroke))) { GArray *coords; gboolean closed; coords = pika_stroke_interpolate (stroke, 1.0, &closed); if (coords && coords->len) { item = pika_draw_tool_add_strokes (draw_tool, &g_array_index (coords, PikaCoords, 0), coords->len, &matrix, FALSE); g_ptr_array_add (tg_tool->strokes, item); g_object_weak_ref (G_OBJECT (item), (GWeakNotify) g_ptr_array_remove, tg_tool->strokes); pika_canvas_item_set_visible (item, tr_tool->transform_valid); } if (coords) g_array_free (coords, TRUE); } } } PIKA_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool); pika_transform_grid_tool_update_preview (tg_tool); } static void pika_transform_grid_tool_recalc_matrix (PikaTransformTool *tr_tool) { PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tr_tool); PikaTransformOptions *tr_options = PIKA_TRANSFORM_TOOL_GET_OPTIONS (tr_tool); PikaTransformGridOptions *tg_options = PIKA_TRANSFORM_GRID_TOOL_GET_OPTIONS (tr_tool); if (PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->info_to_matrix) { PikaMatrix3 forward_transform; PikaMatrix3 backward_transform; gboolean forward_transform_valid; gboolean backward_transform_valid; tg_tool->trans_info = tg_tool->trans_infos[PIKA_TRANSFORM_FORWARD]; forward_transform_valid = pika_transform_grid_tool_info_to_matrix ( tg_tool, &forward_transform); tg_tool->trans_info = tg_tool->trans_infos[PIKA_TRANSFORM_BACKWARD]; backward_transform_valid = pika_transform_grid_tool_info_to_matrix ( tg_tool, &backward_transform); if (PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info && tg_options->direction_linked) { PikaMatrix3 transform = tr_tool->transform; switch (tr_options->direction) { case PIKA_TRANSFORM_FORWARD: if (forward_transform_valid) { pika_matrix3_invert (&transform); backward_transform = forward_transform; pika_matrix3_mult (&transform, &backward_transform); tg_tool->trans_info = tg_tool->trans_infos[PIKA_TRANSFORM_BACKWARD]; pika_transform_grid_tool_matrix_to_info (tg_tool, &backward_transform); backward_transform_valid = pika_transform_grid_tool_info_to_matrix ( tg_tool, &backward_transform); } break; case PIKA_TRANSFORM_BACKWARD: if (backward_transform_valid) { forward_transform = backward_transform; pika_matrix3_mult (&transform, &forward_transform); tg_tool->trans_info = tg_tool->trans_infos[PIKA_TRANSFORM_FORWARD]; pika_transform_grid_tool_matrix_to_info (tg_tool, &forward_transform); forward_transform_valid = pika_transform_grid_tool_info_to_matrix ( tg_tool, &forward_transform); } break; } } else if (forward_transform_valid && backward_transform_valid) { tr_tool->transform = backward_transform; pika_matrix3_invert (&tr_tool->transform); pika_matrix3_mult (&forward_transform, &tr_tool->transform); } tr_tool->transform_valid = forward_transform_valid && backward_transform_valid; } tg_tool->trans_info = tg_tool->trans_infos[tr_options->direction]; pika_transform_grid_tool_dialog_update (tg_tool); pika_transform_grid_tool_update_sensitivity (tg_tool); pika_transform_grid_tool_update_widget (tg_tool); pika_transform_grid_tool_update_preview (tg_tool); if (tg_tool->gui) pika_tool_gui_show (tg_tool->gui); } static gchar * pika_transform_grid_tool_get_undo_desc (PikaTransformTool *tr_tool) { PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tr_tool); PikaTransformOptions *tr_options = PIKA_TRANSFORM_TOOL_GET_OPTIONS (tr_tool); gchar *result; if (PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info) { TransInfo trans_info; memcpy (&trans_info, &tg_tool->init_trans_info, sizeof (TransInfo)); tg_tool->trans_info = trans_info; pika_transform_grid_tool_matrix_to_info (tg_tool, &tr_tool->transform); result = PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_undo_desc ( tg_tool); } else if (trans_info_equal (tg_tool->trans_infos[PIKA_TRANSFORM_BACKWARD], tg_tool->init_trans_info)) { tg_tool->trans_info = tg_tool->trans_infos[PIKA_TRANSFORM_FORWARD]; result = PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_undo_desc ( tg_tool); } else if (trans_info_equal (tg_tool->trans_infos[PIKA_TRANSFORM_FORWARD], tg_tool->init_trans_info)) { gchar *desc; tg_tool->trans_info = tg_tool->trans_infos[PIKA_TRANSFORM_BACKWARD]; desc = PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_undo_desc ( tg_tool); result = g_strdup_printf (_("%s (Corrective)"), desc); g_free (desc); } else { result = PIKA_TRANSFORM_TOOL_CLASS (parent_class)->get_undo_desc ( tr_tool); } tg_tool->trans_info = tg_tool->trans_infos[tr_options->direction]; return result; } static PikaTransformDirection pika_transform_grid_tool_get_direction (PikaTransformTool *tr_tool) { return PIKA_TRANSFORM_FORWARD; } static GeglBuffer * pika_transform_grid_tool_transform (PikaTransformTool *tr_tool, GList *objects, GeglBuffer *orig_buffer, gint orig_offset_x, gint orig_offset_y, PikaColorProfile **buffer_profile, gint *new_offset_x, gint *new_offset_y) { PikaTool *tool = PIKA_TOOL (tr_tool); PikaTransformGridTool *tg_tool = PIKA_TRANSFORM_GRID_TOOL (tr_tool); PikaDisplay *display = tool->display; PikaImage *image = pika_display_get_image (display); GeglBuffer *new_buffer; /* Send the request for the transformation to the tool... */ new_buffer = PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->transform (tg_tool, objects, orig_buffer, orig_offset_x, orig_offset_y, buffer_profile, new_offset_x, new_offset_y); pika_image_undo_push (image, PIKA_TYPE_TRANSFORM_GRID_TOOL_UNDO, PIKA_UNDO_TRANSFORM_GRID, NULL, 0, "transform-tool", tg_tool, NULL); return new_buffer; } static void pika_transform_grid_tool_real_apply_info (PikaTransformGridTool *tg_tool, const TransInfo info) { memcpy (tg_tool->trans_info, info, sizeof (TransInfo)); } static gchar * pika_transform_grid_tool_real_get_undo_desc (PikaTransformGridTool *tg_tool) { PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tg_tool); return PIKA_TRANSFORM_TOOL_CLASS (parent_class)->get_undo_desc (tr_tool); } static void pika_transform_grid_tool_real_update_widget (PikaTransformGridTool *tg_tool) { if (PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->info_to_matrix) { PikaMatrix3 transform; pika_transform_grid_tool_info_to_matrix (tg_tool, &transform); g_object_set (tg_tool->widget, "transform", &transform, NULL); } } static void pika_transform_grid_tool_real_widget_changed (PikaTransformGridTool *tg_tool) { PikaTool *tool = PIKA_TOOL (tg_tool); PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tg_tool); PikaToolWidget *widget = tg_tool->widget; /* suppress the call to PikaTransformGridTool::update_widget() when * recalculating the matrix */ tg_tool->widget = NULL; pika_transform_tool_recalc_matrix (tr_tool, tool->display); tg_tool->widget = widget; } static GeglBuffer * pika_transform_grid_tool_real_transform (PikaTransformGridTool *tg_tool, GList *objects, GeglBuffer *orig_buffer, gint orig_offset_x, gint orig_offset_y, PikaColorProfile **buffer_profile, gint *new_offset_x, gint *new_offset_y) { PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tg_tool); return PIKA_TRANSFORM_TOOL_CLASS (parent_class)->transform (tr_tool, objects, orig_buffer, orig_offset_x, orig_offset_y, buffer_profile, new_offset_x, new_offset_y); } static void pika_transform_grid_tool_widget_changed (PikaToolWidget *widget, PikaTransformGridTool *tg_tool) { if (PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->widget_changed) PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->widget_changed (tg_tool); } static void pika_transform_grid_tool_widget_response (PikaToolWidget *widget, gint response_id, PikaTransformGridTool *tg_tool) { switch (response_id) { case PIKA_TOOL_WIDGET_RESPONSE_CONFIRM: pika_transform_grid_tool_response (NULL, GTK_RESPONSE_OK, tg_tool); break; case PIKA_TOOL_WIDGET_RESPONSE_CANCEL: pika_transform_grid_tool_response (NULL, GTK_RESPONSE_CANCEL, tg_tool); break; case PIKA_TOOL_WIDGET_RESPONSE_RESET: pika_transform_grid_tool_response (NULL, RESPONSE_RESET, tg_tool); break; } } static void pika_transform_grid_tool_filter_flush (PikaDrawableFilter *filter, PikaTransformGridTool *tg_tool) { PikaTool *tool = PIKA_TOOL (tg_tool); PikaImage *image = pika_display_get_image (tool->display); pika_projection_flush (pika_image_get_projection (image)); } static void pika_transform_grid_tool_halt (PikaTransformGridTool *tg_tool) { PikaTool *tool = PIKA_TOOL (tg_tool); PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tg_tool); PikaTransformGridOptions *tg_options = PIKA_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool); GList *iter; if (pika_draw_tool_is_active (PIKA_DRAW_TOOL (tg_tool))) pika_draw_tool_stop (PIKA_DRAW_TOOL (tg_tool)); pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tg_tool), NULL); g_clear_object (&tg_tool->widget); g_clear_pointer (&tg_tool->filters, g_hash_table_unref); g_clear_pointer (&tg_tool->preview_drawables, g_list_free); for (iter = tg_tool->previews; iter; iter = iter->next) { if (iter->data) g_object_unref (iter->data); } g_clear_pointer (&tg_tool->previews, g_list_free); if (tg_tool->gui) pika_tool_gui_hide (tg_tool->gui); if (tg_tool->redo_list) { g_list_free_full (tg_tool->redo_list, (GDestroyNotify) undo_info_free); tg_tool->redo_list = NULL; } if (tg_tool->undo_list) { g_list_free_full (tg_tool->undo_list, (GDestroyNotify) undo_info_free); tg_tool->undo_list = NULL; } pika_transform_grid_tool_show_selected_objects (tg_tool); if (tg_options->direction_chain_button) { g_object_set (tg_options, "direction-linked", FALSE, NULL); gtk_widget_set_sensitive (tg_options->direction_chain_button, FALSE); } tool->display = NULL; g_list_free (tool->drawables); tool->drawables = NULL; if (tr_tool->objects) { GList *iter; for (iter = tr_tool->objects; iter; iter = iter->next) if (PIKA_IS_DRAWABLE (iter->data)) pika_viewable_preview_thaw (iter->data); g_list_free (tr_tool->objects); tr_tool->objects = NULL; } } static void pika_transform_grid_tool_commit (PikaTransformGridTool *tg_tool) { PikaTool *tool = PIKA_TOOL (tg_tool); PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tg_tool); PikaDisplay *display = tool->display; /* undraw the tool before we muck around with the transform matrix */ pika_draw_tool_stop (PIKA_DRAW_TOOL (tg_tool)); pika_transform_tool_transform (tr_tool, display); } static void pika_transform_grid_tool_dialog (PikaTransformGridTool *tg_tool) { PikaTool *tool = PIKA_TOOL (tg_tool); PikaToolInfo *tool_info = tool->tool_info; PikaDisplayShell *shell; const gchar *ok_button_label; if (! PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->dialog) return; g_return_if_fail (tool->display != NULL); shell = pika_display_get_shell (tool->display); ok_button_label = PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->ok_button_label; tg_tool->gui = pika_tool_gui_new (tool_info, NULL, NULL, NULL, NULL, pika_widget_get_monitor (GTK_WIDGET (shell)), TRUE, NULL); pika_tool_gui_add_button (tg_tool->gui, _("_Reset"), RESPONSE_RESET); if (PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->readjust) pika_tool_gui_add_button (tg_tool->gui, _("Re_adjust"), RESPONSE_READJUST); pika_tool_gui_add_button (tg_tool->gui, _("_Cancel"), GTK_RESPONSE_CANCEL); pika_tool_gui_add_button (tg_tool->gui, ok_button_label, GTK_RESPONSE_OK); pika_tool_gui_set_auto_overlay (tg_tool->gui, TRUE); pika_tool_gui_set_default_response (tg_tool->gui, GTK_RESPONSE_OK); pika_tool_gui_set_alternative_button_order (tg_tool->gui, RESPONSE_RESET, RESPONSE_READJUST, GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); g_signal_connect (tg_tool->gui, "response", G_CALLBACK (pika_transform_grid_tool_response), tg_tool); PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->dialog (tg_tool); } static void pika_transform_grid_tool_dialog_update (PikaTransformGridTool *tg_tool) { if (tg_tool->gui && PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->dialog_update) { PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->dialog_update (tg_tool); } } static void pika_transform_grid_tool_prepare (PikaTransformGridTool *tg_tool, PikaDisplay *display) { PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tg_tool); if (tg_tool->gui) { GList *objects = pika_transform_tool_get_selected_objects (tr_tool, display); pika_tool_gui_set_shell (tg_tool->gui, pika_display_get_shell (display)); pika_tool_gui_set_viewables (tg_tool->gui, objects); g_list_free (objects); } if (PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->prepare) { tg_tool->trans_info = tg_tool->init_trans_info; PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->prepare (tg_tool); memcpy (tg_tool->trans_infos[PIKA_TRANSFORM_FORWARD], tg_tool->init_trans_info, sizeof (TransInfo)); memcpy (tg_tool->trans_infos[PIKA_TRANSFORM_BACKWARD], tg_tool->init_trans_info, sizeof (TransInfo)); } pika_matrix3_identity (&tr_tool->transform); tr_tool->transform_valid = TRUE; } static PikaToolWidget * pika_transform_grid_tool_get_widget (PikaTransformGridTool *tg_tool) { static const gchar *properties[] = { "constrain-move", "constrain-scale", "constrain-rotate", "constrain-shear", "constrain-perspective", "frompivot-scale", "frompivot-shear", "frompivot-perspective", "cornersnap", "fixedpivot" }; PikaToolWidget *widget = NULL; if (PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_widget) { PikaTransformGridOptions *options = PIKA_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool); gint i; widget = PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->get_widget (tg_tool); pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tg_tool), widget); g_object_bind_property (G_OBJECT (options), "grid-type", G_OBJECT (widget), "guide-type", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); g_object_bind_property (G_OBJECT (options), "grid-size", G_OBJECT (widget), "n-guides", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); for (i = 0; i < G_N_ELEMENTS (properties); i++) g_object_bind_property (G_OBJECT (options), properties[i], G_OBJECT (widget), properties[i], G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); g_signal_connect (widget, "changed", G_CALLBACK (pika_transform_grid_tool_widget_changed), tg_tool); g_signal_connect (widget, "response", G_CALLBACK (pika_transform_grid_tool_widget_response), tg_tool); } return widget; } static void pika_transform_grid_tool_update_widget (PikaTransformGridTool *tg_tool) { if (tg_tool->widget && PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->update_widget) { g_signal_handlers_block_by_func ( tg_tool->widget, G_CALLBACK (pika_transform_grid_tool_widget_changed), tg_tool); PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->update_widget (tg_tool); g_signal_handlers_unblock_by_func ( tg_tool->widget, G_CALLBACK (pika_transform_grid_tool_widget_changed), tg_tool); } } static void pika_transform_grid_tool_response (PikaToolGui *gui, gint response_id, PikaTransformGridTool *tg_tool) { PikaTool *tool = PIKA_TOOL (tg_tool); PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tg_tool); PikaTransformOptions *tr_options = PIKA_TRANSFORM_TOOL_GET_OPTIONS (tg_tool); PikaTransformGridOptions *tg_options = PIKA_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool); PikaDisplay *display = tool->display; /* we can get here while already committing a transformation. just return in * this case. see issue #4734. */ if (! pika_draw_tool_is_active (PIKA_DRAW_TOOL (tg_tool))) return; switch (response_id) { case RESPONSE_RESET: { gboolean direction_linked; /* restore the initial transformation info */ memcpy (tg_tool->trans_infos[PIKA_TRANSFORM_FORWARD], tg_tool->init_trans_info, sizeof (TransInfo)); memcpy (tg_tool->trans_infos[PIKA_TRANSFORM_BACKWARD], tg_tool->init_trans_info, sizeof (TransInfo)); /* recalculate the tool's transformation matrix */ direction_linked = tg_options->direction_linked; tg_options->direction_linked = FALSE; pika_transform_tool_recalc_matrix (tr_tool, display); tg_options->direction_linked = direction_linked; /* push the restored info to the undo stack */ pika_transform_grid_tool_push_internal_undo (tg_tool, FALSE); } break; case RESPONSE_READJUST: if (PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->readjust && PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info && tr_tool->transform_valid) { TransInfo old_trans_infos[2]; gboolean direction_linked; gboolean transform_valid; /* save the current transformation info */ memcpy (old_trans_infos, tg_tool->trans_infos, sizeof (old_trans_infos)); /* readjust the transformation info to view */ PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->readjust (tg_tool); /* recalculate the tool's transformation matrix, preserving the * overall transformation */ direction_linked = tg_options->direction_linked; tg_options->direction_linked = TRUE; pika_transform_tool_recalc_matrix (tr_tool, display); tg_options->direction_linked = direction_linked; transform_valid = tr_tool->transform_valid; /* if the resulting transformation is invalid, or if the * transformation info is already adjusted to view ... */ if (! transform_valid || trans_infos_equal (old_trans_infos, tg_tool->trans_infos)) { /* ... readjust the transformation info to the item bounds */ PikaMatrix3 transform = tr_tool->transform; if (tr_options->direction == PIKA_TRANSFORM_BACKWARD) pika_matrix3_invert (&transform); PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->apply_info ( tg_tool, tg_tool->init_trans_info); PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info ( tg_tool, &transform); /* recalculate the tool's transformation matrix, preserving the * overall transformation */ direction_linked = tg_options->direction_linked; tg_options->direction_linked = TRUE; pika_transform_tool_recalc_matrix (tr_tool, display); tg_options->direction_linked = direction_linked; if (! tr_tool->transform_valid || ! trans_infos_equal (old_trans_infos, tg_tool->trans_infos)) { transform_valid = tr_tool->transform_valid; } } if (transform_valid) { /* push the new info to the undo stack */ pika_transform_grid_tool_push_internal_undo (tg_tool, FALSE); } else { /* restore the old transformation info */ memcpy (tg_tool->trans_infos, old_trans_infos, sizeof (old_trans_infos)); /* recalculate the tool's transformation matrix */ direction_linked = tg_options->direction_linked; tg_options->direction_linked = FALSE; pika_transform_tool_recalc_matrix (tr_tool, display); tg_options->direction_linked = direction_linked; pika_tool_message_literal (tool, tool->display, _("Cannot readjust the transformation")); } } break; case GTK_RESPONSE_OK: g_return_if_fail (display != NULL); pika_tool_control (tool, PIKA_TOOL_ACTION_COMMIT, display); break; default: pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display); /* update the undo actions / menu items */ if (display) pika_image_flush (pika_display_get_image (display)); break; } } static gboolean pika_transform_grid_tool_composited_preview (PikaTransformGridTool *tg_tool) { PikaTool *tool = PIKA_TOOL (tg_tool); PikaTransformOptions *tr_options = PIKA_TRANSFORM_TOOL_GET_OPTIONS (tg_tool); PikaTransformGridOptions *tg_options = PIKA_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool); PikaImage *image = pika_display_get_image (tool->display); return tg_options->composited_preview && tr_options->type == PIKA_TRANSFORM_TYPE_LAYER && pika_channel_is_empty (pika_image_get_mask (image)); } static void pika_transform_grid_tool_update_sensitivity (PikaTransformGridTool *tg_tool) { PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tg_tool); if (! tg_tool->gui) return; pika_tool_gui_set_response_sensitive ( tg_tool->gui, GTK_RESPONSE_OK, tr_tool->transform_valid); pika_tool_gui_set_response_sensitive ( tg_tool->gui, RESPONSE_RESET, ! (trans_info_equal (tg_tool->trans_infos[PIKA_TRANSFORM_FORWARD], tg_tool->init_trans_info) && trans_info_equal (tg_tool->trans_infos[PIKA_TRANSFORM_BACKWARD], tg_tool->init_trans_info))); pika_tool_gui_set_response_sensitive ( tg_tool->gui, RESPONSE_READJUST, tr_tool->transform_valid); } static void pika_transform_grid_tool_update_preview (PikaTransformGridTool *tg_tool) { PikaTool *tool = PIKA_TOOL (tg_tool); PikaTransformTool *tr_tool = PIKA_TRANSFORM_TOOL (tg_tool); PikaTransformOptions *tr_options = PIKA_TRANSFORM_TOOL_GET_OPTIONS (tg_tool); PikaTransformGridOptions *tg_options = PIKA_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool); GList *iter; gint i; if (! tool->display) return; if (tg_options->show_preview && pika_transform_grid_tool_composited_preview (tg_tool) && tr_tool->transform_valid) { GHashTableIter iter; PikaDrawable *drawable; Filter *filter; gboolean flush = FALSE; if (! tg_tool->filters) { tg_tool->filters = g_hash_table_new_full ( g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) filter_free); pika_transform_grid_tool_update_filters (tg_tool); } g_hash_table_iter_init (&iter, tg_tool->filters); while (g_hash_table_iter_next (&iter, (gpointer *) &drawable, (gpointer *) &filter)) { PikaMatrix3 transform; GeglRectangle bounds; gint offset_x; gint offset_y; gint width; gint height; gint x1, y1; gint x2, y2; gboolean update = FALSE; if (! filter->filter) continue; pika_item_get_offset (PIKA_ITEM (drawable), &offset_x, &offset_y); width = pika_item_get_width (PIKA_ITEM (drawable)); height = pika_item_get_height (PIKA_ITEM (drawable)); pika_matrix3_identity (&transform); pika_matrix3_translate (&transform, +offset_x, +offset_y); pika_matrix3_mult (&tr_tool->transform, &transform); pika_matrix3_translate (&transform, -offset_x, -offset_y); pika_transform_resize_boundary (&tr_tool->transform, pika_item_get_clip ( PIKA_ITEM (filter->root_drawable), tr_options->clip), offset_x, offset_y, offset_x + width, offset_y + height, &x1, &y1, &x2, &y2); bounds.x = x1 - offset_x; bounds.y = y1 - offset_y; bounds.width = x2 - x1; bounds.height = y2 - y1; if (! pika_matrix3_equal (&transform, &filter->transform)) { filter->transform = transform; pika_gegl_node_set_matrix (filter->transform_node, &transform); update = TRUE; } if (! gegl_rectangle_equal (&bounds, &filter->bounds)) { filter->bounds = bounds; gegl_node_set (filter->crop_node, "x", (gdouble) bounds.x, "y", (gdouble) bounds.y, "width", (gdouble) bounds.width, "height", (gdouble) bounds.height, NULL); update = TRUE; } if (PIKA_IS_LAYER (drawable)) { pika_drawable_filter_set_add_alpha ( filter->filter, tr_options->interpolation != PIKA_INTERPOLATION_NONE); } if (update) { if (tg_options->synchronous_preview) { g_signal_handlers_block_by_func ( filter->filter, G_CALLBACK (pika_transform_grid_tool_filter_flush), tg_tool); } pika_drawable_filter_apply (filter->filter, NULL); if (tg_options->synchronous_preview) { g_signal_handlers_unblock_by_func ( filter->filter, G_CALLBACK (pika_transform_grid_tool_filter_flush), tg_tool); flush = TRUE; } } } if (flush) { PikaImage *image = pika_display_get_image (tool->display); pika_projection_flush_now (pika_image_get_projection (image), TRUE); pika_display_flush_now (tool->display); } } else { g_clear_pointer (&tg_tool->filters, g_hash_table_unref); g_clear_pointer (&tg_tool->preview_drawables, g_list_free); } for (iter = tg_tool->previews; iter; iter = iter->next) { PikaCanvasItem *preview = iter->data; if (preview == NULL) continue; if (tg_options->show_preview && ! pika_transform_grid_tool_composited_preview (tg_tool) && tr_tool->transform_valid) { pika_canvas_item_begin_change (preview); pika_canvas_item_set_visible (preview, TRUE); g_object_set ( preview, "transform", &tr_tool->transform, "clip", pika_item_get_clip (PIKA_ITEM (tool->drawables->data), tr_options->clip), "opacity", tg_options->preview_opacity, NULL); pika_canvas_item_end_change (preview); } else { pika_canvas_item_set_visible (preview, FALSE); } } if (tg_tool->boundary_in) { pika_canvas_item_begin_change (tg_tool->boundary_in); pika_canvas_item_set_visible (tg_tool->boundary_in, tr_tool->transform_valid); g_object_set (tg_tool->boundary_in, "transform", &tr_tool->transform, NULL); pika_canvas_item_end_change (tg_tool->boundary_in); } if (tg_tool->boundary_out) { pika_canvas_item_begin_change (tg_tool->boundary_out); pika_canvas_item_set_visible (tg_tool->boundary_out, tr_tool->transform_valid); g_object_set (tg_tool->boundary_out, "transform", &tr_tool->transform, NULL); pika_canvas_item_end_change (tg_tool->boundary_out); } for (i = 0; i < tg_tool->strokes->len; i++) { PikaCanvasItem *item = g_ptr_array_index (tg_tool->strokes, i); pika_canvas_item_begin_change (item); pika_canvas_item_set_visible (item, tr_tool->transform_valid); g_object_set (item, "transform", &tr_tool->transform, NULL); pika_canvas_item_end_change (item); } } static void pika_transform_grid_tool_update_filters (PikaTransformGridTool *tg_tool) { PikaTool *tool = PIKA_TOOL (tg_tool); GHashTable *new_drawables; GList *drawables; GList *iter; PikaDrawable *drawable; GHashTableIter hash_iter; if (! tg_tool->filters) return; drawables = pika_image_item_list_filter (g_list_copy (tool->drawables)); new_drawables = g_hash_table_new (g_direct_hash, g_direct_equal); for (iter = drawables; iter; iter = g_list_next (iter)) g_hash_table_add (new_drawables, iter->data); for (iter = tg_tool->preview_drawables; iter; iter = g_list_next (iter)) { drawable = iter->data; if (! g_hash_table_remove (new_drawables, drawable)) pika_transform_grid_tool_remove_filter (drawable, tg_tool); } g_hash_table_iter_init (&hash_iter, new_drawables); while (g_hash_table_iter_next (&hash_iter, (gpointer *) &drawable, NULL)) { AddFilterData data; data.tg_tool = tg_tool; data.root_drawable = drawable; pika_transform_grid_tool_add_filter (drawable, &data); } g_hash_table_unref (new_drawables); g_list_free (tg_tool->preview_drawables); tg_tool->preview_drawables = drawables; } static void pika_transform_grid_tool_hide_selected_objects (PikaTransformGridTool *tg_tool, GList *objects) { PikaTransformGridOptions *options = PIKA_TRANSFORM_GRID_TOOL_GET_OPTIONS (tg_tool); PikaTransformOptions *tr_options = PIKA_TRANSFORM_OPTIONS (options); PikaDisplay *display = PIKA_TOOL (tg_tool)->display; PikaImage *image = pika_display_get_image (display); g_return_if_fail (tr_options->type != PIKA_TRANSFORM_TYPE_IMAGE || (g_list_length (objects) == 1 && PIKA_IS_IMAGE (objects->data))); g_list_free (tg_tool->hidden_objects); tg_tool->hidden_objects = NULL; if (options->show_preview) { if (tr_options->type == PIKA_TRANSFORM_TYPE_IMAGE) { tg_tool->hidden_objects = g_list_copy (objects); pika_display_shell_set_show_image (pika_display_get_shell (display), FALSE); } else { /* hide only complete layers and channels, not layer masks */ GList *iter; for (iter = objects; iter; iter = iter->next) { if (tr_options->type == PIKA_TRANSFORM_TYPE_LAYER && ! options->composited_preview && PIKA_IS_DRAWABLE (iter->data) && ! PIKA_IS_LAYER_MASK (iter->data) && pika_item_get_visible (iter->data) && pika_channel_is_empty (pika_image_get_mask (image))) { tg_tool->hidden_objects = g_list_prepend (tg_tool->hidden_objects, iter->data); pika_item_set_visible (iter->data, FALSE, FALSE); } } pika_projection_flush (pika_image_get_projection (image)); } } } static void pika_transform_grid_tool_show_selected_objects (PikaTransformGridTool *tg_tool) { if (tg_tool->hidden_objects) { PikaDisplay *display = PIKA_TOOL (tg_tool)->display; PikaImage *image = pika_display_get_image (display); GList *iter; for (iter = tg_tool->hidden_objects; iter; iter = iter->next) { if (PIKA_IS_ITEM (iter->data)) { pika_item_set_visible (PIKA_ITEM (iter->data), TRUE, FALSE); } else { g_return_if_fail (PIKA_IS_IMAGE (iter->data)); pika_display_shell_set_show_image (pika_display_get_shell (display), TRUE); } } g_list_free (tg_tool->hidden_objects); tg_tool->hidden_objects = NULL; pika_image_flush (image); } } static void pika_transform_grid_tool_add_filter (PikaDrawable *drawable, AddFilterData *data) { Filter *filter; PikaLayerMode mode = PIKA_LAYER_MODE_NORMAL; if (PIKA_IS_LAYER (drawable)) { pika_layer_get_effective_mode (PIKA_LAYER (drawable), &mode, NULL, NULL, NULL); } if (mode != PIKA_LAYER_MODE_PASS_THROUGH) { filter = filter_new (data->tg_tool, drawable, data->root_drawable, TRUE); } else { PikaContainer *container; filter = filter_new (data->tg_tool, drawable, data->root_drawable, FALSE); container = pika_viewable_get_children (PIKA_VIEWABLE (drawable)); pika_container_foreach (container, (GFunc) pika_transform_grid_tool_add_filter, data); } g_hash_table_insert (data->tg_tool->filters, drawable, filter); if (PIKA_IS_LAYER (drawable)) { PikaLayerMask *mask = pika_layer_get_mask (PIKA_LAYER (drawable)); if (mask) pika_transform_grid_tool_add_filter (PIKA_DRAWABLE (mask), data); } } static void pika_transform_grid_tool_remove_filter (PikaDrawable *drawable, PikaTransformGridTool *tg_tool) { Filter *filter = g_hash_table_lookup (tg_tool->filters, drawable); if (PIKA_IS_LAYER (drawable)) { PikaLayerMask *mask = pika_layer_get_mask (PIKA_LAYER (drawable)); if (mask) pika_transform_grid_tool_remove_filter (PIKA_DRAWABLE (mask), tg_tool); } if (! filter->filter) { PikaContainer *container; container = pika_viewable_get_children (PIKA_VIEWABLE (drawable)); pika_container_foreach (container, (GFunc) pika_transform_grid_tool_remove_filter, tg_tool); } g_hash_table_remove (tg_tool->filters, drawable); } static void pika_transform_grid_tool_effective_mode_changed (PikaLayer *layer, PikaTransformGridTool *tg_tool) { Filter *filter = g_hash_table_lookup (tg_tool->filters, layer); PikaLayerMode mode; gboolean old_pass_through; gboolean new_pass_through; pika_layer_get_effective_mode (layer, &mode, NULL, NULL, NULL); old_pass_through = ! filter->filter; new_pass_through = mode == PIKA_LAYER_MODE_PASS_THROUGH; if (old_pass_through != new_pass_through) { AddFilterData data; data.tg_tool = tg_tool; data.root_drawable = filter->root_drawable; pika_transform_grid_tool_remove_filter (PIKA_DRAWABLE (layer), tg_tool); pika_transform_grid_tool_add_filter (PIKA_DRAWABLE (layer), &data); pika_transform_grid_tool_update_preview (tg_tool); } } static Filter * filter_new (PikaTransformGridTool *tg_tool, PikaDrawable *drawable, PikaDrawable *root_drawable, gboolean add_filter) { Filter *filter = g_slice_new0 (Filter); GeglNode *node; GeglNode *input_node; GeglNode *output_node; filter->tg_tool = tg_tool; filter->drawable = drawable; filter->root_drawable = root_drawable; if (add_filter) { node = gegl_node_new (); input_node = gegl_node_get_input_proxy (node, "input"); output_node = gegl_node_get_input_proxy (node, "output"); filter->transform_node = gegl_node_new_child ( node, "operation", "gegl:transform", "near-z", PIKA_TRANSFORM_NEAR_Z, "sampler", GEGL_SAMPLER_NEAREST, NULL); filter->crop_node = gegl_node_new_child ( node, "operation", "gegl:crop", NULL); gegl_node_link_many (input_node, filter->transform_node, filter->crop_node, output_node, NULL); pika_gegl_node_set_underlying_operation (node, filter->transform_node); filter->filter = pika_drawable_filter_new ( drawable, PIKA_TRANSFORM_TOOL_GET_CLASS (tg_tool)->undo_desc, node, pika_tool_get_icon_name (PIKA_TOOL (tg_tool))); pika_drawable_filter_set_clip (filter->filter, FALSE); pika_drawable_filter_set_override_constraints (filter->filter, TRUE); g_signal_connect ( filter->filter, "flush", G_CALLBACK (pika_transform_grid_tool_filter_flush), tg_tool); g_object_unref (node); } if (PIKA_IS_GROUP_LAYER (drawable)) { g_signal_connect ( drawable, "effective-mode-changed", G_CALLBACK (pika_transform_grid_tool_effective_mode_changed), tg_tool); } return filter; } static void filter_free (Filter *filter) { if (filter->filter) { pika_drawable_filter_abort (filter->filter); g_object_unref (filter->filter); } if (PIKA_IS_GROUP_LAYER (filter->drawable)) { g_signal_handlers_disconnect_by_func ( filter->drawable, pika_transform_grid_tool_effective_mode_changed, filter->tg_tool); } g_slice_free (Filter, filter); } static UndoInfo * undo_info_new (void) { return g_slice_new0 (UndoInfo); } static void undo_info_free (UndoInfo *info) { g_slice_free (UndoInfo, info); } static gboolean trans_info_equal (const TransInfo trans_info1, const TransInfo trans_info2) { gint i; for (i = 0; i < TRANS_INFO_SIZE; i++) { if (fabs (trans_info1[i] - trans_info2[i]) > EPSILON) return FALSE; } return TRUE; } static gboolean trans_infos_equal (const TransInfo *trans_infos1, const TransInfo *trans_infos2) { return trans_info_equal (trans_infos1[PIKA_TRANSFORM_FORWARD], trans_infos2[PIKA_TRANSFORM_FORWARD]) && trans_info_equal (trans_infos1[PIKA_TRANSFORM_BACKWARD], trans_infos2[PIKA_TRANSFORM_BACKWARD]); } gboolean pika_transform_grid_tool_info_to_matrix (PikaTransformGridTool *tg_tool, PikaMatrix3 *transform) { g_return_val_if_fail (PIKA_IS_TRANSFORM_GRID_TOOL (tg_tool), FALSE); g_return_val_if_fail (transform != NULL, FALSE); if (PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->info_to_matrix) { return PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->info_to_matrix ( tg_tool, transform); } return FALSE; } void pika_transform_grid_tool_matrix_to_info (PikaTransformGridTool *tg_tool, const PikaMatrix3 *transform) { g_return_if_fail (PIKA_IS_TRANSFORM_GRID_TOOL (tg_tool)); g_return_if_fail (transform != NULL); if (PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info) { return PIKA_TRANSFORM_GRID_TOOL_GET_CLASS (tg_tool)->matrix_to_info ( tg_tool, transform); } } void pika_transform_grid_tool_push_internal_undo (PikaTransformGridTool *tg_tool, gboolean compress) { UndoInfo *undo_info; g_return_if_fail (PIKA_IS_TRANSFORM_GRID_TOOL (tg_tool)); g_return_if_fail (tg_tool->undo_list != NULL); undo_info = tg_tool->undo_list->data; /* push current state on the undo list and set this state as the * current state, but avoid doing this if there were no changes */ if (! trans_infos_equal (undo_info->trans_infos, tg_tool->trans_infos)) { PikaTransformOptions *tr_options = PIKA_TRANSFORM_TOOL_GET_OPTIONS (tg_tool); gint64 time = 0; gboolean flush = FALSE; if (tg_tool->undo_list->next == NULL) flush = TRUE; if (compress) time = g_get_monotonic_time (); if (! compress || time - undo_info->time >= UNDO_COMPRESS_TIME) { undo_info = undo_info_new (); tg_tool->undo_list = g_list_prepend (tg_tool->undo_list, undo_info); } undo_info->time = time; undo_info->direction = tr_options->direction; memcpy (undo_info->trans_infos, tg_tool->trans_infos, sizeof (tg_tool->trans_infos)); /* If we undid anything and started interacting, we have to * discard the redo history */ if (tg_tool->redo_list) { g_list_free_full (tg_tool->redo_list, (GDestroyNotify) undo_info_free); tg_tool->redo_list = NULL; flush = TRUE; } pika_transform_grid_tool_update_sensitivity (tg_tool); /* update the undo actions / menu items */ if (flush) { pika_image_flush ( pika_display_get_image (PIKA_TOOL (tg_tool)->display)); } } }