diff --git a/app/core/gimpdrawable-transform.c b/app/core/gimpdrawable-transform.c index 5d0295e2dc..e84ab2da0e 100644 --- a/app/core/gimpdrawable-transform.c +++ b/app/core/gimpdrawable-transform.c @@ -766,7 +766,7 @@ gimp_drawable_transform_affine (GimpDrawable *drawable, { result = gimp_drawable_transform_paste (drawable, new_buffer, profile, new_offset_x, new_offset_y, - new_layer); + new_layer, TRUE); g_object_unref (new_buffer); } } @@ -848,7 +848,7 @@ gimp_drawable_transform_flip (GimpDrawable *drawable, { result = gimp_drawable_transform_paste (drawable, new_buffer, profile, new_offset_x, new_offset_y, - new_layer); + new_layer, TRUE); g_object_unref (new_buffer); } } @@ -933,7 +933,7 @@ gimp_drawable_transform_rotate (GimpDrawable *drawable, { result = gimp_drawable_transform_paste (drawable, new_buffer, profile, new_offset_x, new_offset_y, - new_layer); + new_layer, TRUE); g_object_unref (new_buffer); } } @@ -1026,11 +1026,11 @@ gimp_drawable_transform_paste (GimpDrawable *drawable, GimpColorProfile *buffer_profile, gint offset_x, gint offset_y, - gboolean new_layer) + gboolean new_layer, + gboolean push_undo) { - GimpImage *image; - GimpLayer *layer = NULL; - const gchar *undo_desc = NULL; + GimpImage *image; + GimpLayer *layer = NULL; g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL); g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL); @@ -1039,14 +1039,19 @@ gimp_drawable_transform_paste (GimpDrawable *drawable, image = gimp_item_get_image (GIMP_ITEM (drawable)); - if (GIMP_IS_LAYER (drawable)) - undo_desc = C_("undo-type", "Transform Layer"); - else if (GIMP_IS_CHANNEL (drawable)) - undo_desc = C_("undo-type", "Transform Channel"); - else - return NULL; + if (push_undo) + { + const gchar *undo_desc = NULL; - gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE, undo_desc); + if (GIMP_IS_LAYER (drawable)) + undo_desc = C_("undo-type", "Transform Layer"); + else if (GIMP_IS_CHANNEL (drawable)) + undo_desc = C_("undo-type", "Transform Channel"); + else + return NULL; + + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_EDIT_PASTE, undo_desc); + } if (new_layer) { @@ -1066,13 +1071,14 @@ gimp_drawable_transform_paste (GimpDrawable *drawable, } else { - gimp_drawable_set_buffer_full (drawable, TRUE, NULL, + gimp_drawable_set_buffer_full (drawable, push_undo, NULL, buffer, GEGL_RECTANGLE (offset_x, offset_y, 0, 0), TRUE); } - gimp_image_undo_group_end (image); + if (push_undo) + gimp_image_undo_group_end (image); return drawable; } diff --git a/app/core/gimpdrawable-transform.h b/app/core/gimpdrawable-transform.h index 176e8b3130..612124cd81 100644 --- a/app/core/gimpdrawable-transform.h +++ b/app/core/gimpdrawable-transform.h @@ -87,4 +87,5 @@ GimpDrawable * gimp_drawable_transform_paste (GimpDrawable GimpColorProfile *buffer_profile, gint offset_x, gint offset_y, - gboolean new_layer); + gboolean new_layer, + gboolean push_undo); diff --git a/app/core/gimpdrawable.c b/app/core/gimpdrawable.c index 7d0e1abbb9..fbcc5a1a1f 100644 --- a/app/core/gimpdrawable.c +++ b/app/core/gimpdrawable.c @@ -142,7 +142,8 @@ static void gimp_drawable_transform (GimpItem *item, GimpTransformDirection direction, GimpInterpolationType interpolation_type, GimpTransformResize clip_result, - GimpProgress *progress); + GimpProgress *progress, + gboolean push_undo); static const guint8 * gimp_drawable_get_icc_profile (GimpColorManaged *managed, @@ -742,7 +743,7 @@ gimp_drawable_flip (GimpItem *item, if (buffer) { gimp_drawable_transform_paste (drawable, buffer, buffer_profile, - new_off_x, new_off_y, FALSE); + new_off_x, new_off_y, FALSE, TRUE); g_object_unref (buffer); } } @@ -774,7 +775,7 @@ gimp_drawable_rotate (GimpItem *item, if (buffer) { gimp_drawable_transform_paste (drawable, buffer, buffer_profile, - new_off_x, new_off_y, FALSE); + new_off_x, new_off_y, FALSE, TRUE); g_object_unref (buffer); } } @@ -786,7 +787,8 @@ gimp_drawable_transform (GimpItem *item, GimpTransformDirection direction, GimpInterpolationType interpolation_type, GimpTransformResize clip_result, - GimpProgress *progress) + GimpProgress *progress, + gboolean push_undo) { GimpDrawable *drawable = GIMP_DRAWABLE (item); GeglBuffer *buffer; @@ -809,7 +811,7 @@ gimp_drawable_transform (GimpItem *item, if (buffer) { gimp_drawable_transform_paste (drawable, buffer, buffer_profile, - new_off_x, new_off_y, FALSE); + new_off_x, new_off_y, FALSE, push_undo); g_object_unref (buffer); } } diff --git a/app/core/gimpgrouplayer.c b/app/core/gimpgrouplayer.c index 60660c097c..3fcf7e26ae 100644 --- a/app/core/gimpgrouplayer.c +++ b/app/core/gimpgrouplayer.c @@ -159,7 +159,8 @@ static void gimp_group_layer_transform (GimpLayer *layer, GimpTransformDirection direction, GimpInterpolationType interpolation_type, GimpTransformResize clip_result, - GimpProgress *progress); + GimpProgress *progress, + gboolean push_undo); static void gimp_group_layer_convert_type (GimpLayer *layer, GimpImage *dest_image, const Babl *new_format, @@ -1016,7 +1017,8 @@ gimp_group_layer_transform (GimpLayer *layer, GimpTransformDirection direction, GimpInterpolationType interpolation_type, GimpTransformResize clip_result, - GimpProgress *progress) + GimpProgress *progress, + gboolean push_undo) { GimpGroupLayer *group = GIMP_GROUP_LAYER (layer); GimpGroupLayerPrivate *private = GET_PRIVATE (layer); diff --git a/app/core/gimpitem.c b/app/core/gimpitem.c index 331e6a02bd..29f684005d 100644 --- a/app/core/gimpitem.c +++ b/app/core/gimpitem.c @@ -1777,7 +1777,7 @@ gimp_item_transform (GimpItem *item, g_object_freeze_notify (G_OBJECT (item)); item_class->transform (item, context, matrix, direction, interpolation, - clip_result, progress); + clip_result, progress, TRUE); g_object_thaw_notify (G_OBJECT (item)); diff --git a/app/core/gimpitem.h b/app/core/gimpitem.h index 53e89f6a4d..b35637b662 100644 --- a/app/core/gimpitem.h +++ b/app/core/gimpitem.h @@ -107,7 +107,8 @@ struct _GimpItemClass GimpTransformDirection direction, GimpInterpolationType interpolation_type, GimpTransformResize clip_result, - GimpProgress *progress); + GimpProgress *progress, + gboolean push_undo); GimpTransformResize (* get_clip) (GimpItem *item, GimpTransformResize clip_result); gboolean (* fill) (GimpItem *item, diff --git a/app/core/gimplayer.c b/app/core/gimplayer.c index 0fd1a8e198..678c34aaf9 100644 --- a/app/core/gimplayer.c +++ b/app/core/gimplayer.c @@ -169,7 +169,8 @@ static void gimp_layer_transform (GimpItem *item, GimpTransformDirection direction, GimpInterpolationType interpolation_type, GimpTransformResize clip_result, - GimpProgress *progress); + GimpProgress *progress, + gboolean push_undo); static void gimp_layer_to_selection (GimpItem *item, GimpChannelOps op, gboolean antialias, @@ -248,7 +249,8 @@ static void gimp_layer_real_transform (GimpLayer *layer, GimpTransformDirection direction, GimpInterpolationType interpolation_type, GimpTransformResize clip_result, - GimpProgress *progress); + GimpProgress *progress, + gboolean push_undo); static void gimp_layer_real_convert_type (GimpLayer *layer, GimpImage *dest_image, const Babl *new_format, @@ -1298,7 +1300,8 @@ gimp_layer_transform (GimpItem *item, GimpTransformDirection direction, GimpInterpolationType interpolation_type, GimpTransformResize clip_result, - GimpProgress *progress) + GimpProgress *progress, + gboolean push_undo) { GimpLayer *layer = GIMP_LAYER (item); GimpObjectQueue *queue = NULL; @@ -1328,7 +1331,7 @@ gimp_layer_transform (GimpItem *item, GIMP_LAYER_GET_CLASS (layer)->transform (layer, context, matrix, direction, interpolation_type, clip_result, - progress); + progress, push_undo); if (layer->mask) { @@ -1743,7 +1746,8 @@ gimp_layer_real_transform (GimpLayer *layer, GimpTransformDirection direction, GimpInterpolationType interpolation_type, GimpTransformResize clip_result, - GimpProgress *progress) + GimpProgress *progress, + gboolean push_undo) { if (! gimp_matrix3_is_simple (matrix) && ! gimp_drawable_has_alpha (GIMP_DRAWABLE (layer))) @@ -1753,7 +1757,7 @@ gimp_layer_real_transform (GimpLayer *layer, context, matrix, direction, interpolation_type, clip_result, - progress); + progress, push_undo); } static void diff --git a/app/core/gimplayer.h b/app/core/gimplayer.h index f02d6038bf..790814e41a 100644 --- a/app/core/gimplayer.h +++ b/app/core/gimplayer.h @@ -122,7 +122,8 @@ struct _GimpLayerClass GimpTransformDirection direction, GimpInterpolationType interpolation_type, GimpTransformResize clip_result, - GimpProgress *progress); + GimpProgress *progress, + gboolean push_undo); void (* convert_type) (GimpLayer *layer, GimpImage *dest_image, const Babl *new_format, diff --git a/app/core/gimplinklayer.c b/app/core/gimplinklayer.c index ebee4f7256..495db0115b 100644 --- a/app/core/gimplinklayer.c +++ b/app/core/gimplinklayer.c @@ -29,6 +29,7 @@ #include "libgimpbase/gimpbase.h" #include "libgimpcolor/gimpcolor.h" #include "libgimpconfig/gimpconfig.h" +#include "libgimpmath/gimpmath.h" #include "core-types.h" @@ -69,9 +70,19 @@ enum struct _GimpLinkLayerPrivate { - GimpLink *link; - gboolean scaled_only; - gboolean auto_rename; + GimpLink *link; + gboolean scaled_only; + gboolean auto_rename; + + GimpMatrix3 matrix; + gint offset_x; + gint offset_y; + GimpInterpolationType interpolation; + + /* A transient value only useful to know when to drop monitoring after + * a buffer update. + */ + gboolean keep_monitoring; }; static void gimp_link_layer_finalize (GObject *object); @@ -93,6 +104,11 @@ static gboolean gimp_link_layer_rename (GimpItem *item, const gchar *new_name, const gchar *undo_desc, GError **error); + +static void gimp_link_layer_translate (GimpItem *item, + gdouble offset_x, + gdouble offset_y, + gboolean push_undo); static void gimp_link_layer_scale (GimpItem *item, gint new_width, gint new_height, @@ -100,6 +116,14 @@ static void gimp_link_layer_scale (GimpItem *item, gint new_offset_y, GimpInterpolationType interpolation_type, GimpProgress *progress); +static void gimp_link_layer_transform (GimpItem *item, + GimpContext *context, + const GimpMatrix3 *matrix, + GimpTransformDirection direction, + GimpInterpolationType interpolation_type, + GimpTransformResize clip_result, + GimpProgress *progress, + gboolean push_undo); static void gimp_link_layer_set_buffer (GimpDrawable *drawable, gboolean push_undo, @@ -124,10 +148,18 @@ static void gimp_link_layer_convert_type (GimpLayer *layer, gboolean push_undo, GimpProgress *progress); -static gboolean gimp_link_layer_render (GimpLinkLayer *layer); +static gboolean gimp_link_layer_render_link (GimpLinkLayer *layer); static void gimp_link_layer_set_xcf_flags (GimpLinkLayer *layer, guint32 flags); +static gboolean + gimp_link_layer_is_scaling_matrix (GimpLinkLayer *layer, + const GimpMatrix3 *matrix, + gint *new_width, + gint *new_height, + gint *new_offset_x, + gint *new_offset_y); + G_DEFINE_TYPE_WITH_PRIVATE (GimpLinkLayer, gimp_link_layer, GIMP_TYPE_LAYER) @@ -157,7 +189,9 @@ gimp_link_layer_class_init (GimpLinkLayerClass *klass) item_class->duplicate = gimp_link_layer_duplicate; item_class->rename = gimp_link_layer_rename; + item_class->translate = gimp_link_layer_translate; item_class->scale = gimp_link_layer_scale; + item_class->transform = gimp_link_layer_transform; item_class->rename_desc = _("Rename Link Layer"); item_class->translate_desc = _("Move Link Layer"); @@ -196,8 +230,17 @@ gimp_link_layer_class_init (GimpLinkLayerClass *klass) static void gimp_link_layer_init (GimpLinkLayer *layer) { - layer->p = gimp_link_layer_get_instance_private (layer); - layer->p->link = NULL; + layer->p = gimp_link_layer_get_instance_private (layer); + layer->p->link = NULL; + gimp_matrix3_identity (&layer->p->matrix); + + layer->p->scaled_only = FALSE; + layer->p->auto_rename = FALSE; + layer->p->offset_x = 0; + layer->p->offset_y = 0; + layer->p->interpolation = GIMP_INTERPOLATION_NONE; + + layer->p->keep_monitoring = FALSE; } static void @@ -313,11 +356,18 @@ gimp_link_layer_duplicate (GimpItem *item, if (width != gimp_item_get_width (GIMP_ITEM (new_layer)) || height != gimp_item_get_height (GIMP_ITEM (new_layer))) { - GeglBuffer *new_buffer; + GeglBuffer *new_buffer; + GeglRectangle bounds; + + gimp_item_get_offset (GIMP_ITEM (new_layer), &bounds.x, &bounds.y); + bounds.width = 0; + bounds.height = 0; new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height), gimp_drawable_get_format (GIMP_DRAWABLE (new_layer))); - gimp_drawable_set_buffer (GIMP_DRAWABLE (new_layer), FALSE, NULL, new_buffer); + GIMP_DRAWABLE_CLASS (parent_class)->set_buffer (GIMP_DRAWABLE (new_layer), + FALSE, NULL, + new_buffer, &bounds); g_object_unref (new_buffer); } @@ -325,8 +375,14 @@ gimp_link_layer_duplicate (GimpItem *item, gimp_drawable_get_buffer (GIMP_DRAWABLE (new_layer)), NULL); } - new_layer->p->scaled_only = layer->p->scaled_only; - new_layer->p->auto_rename = layer->p->auto_rename; + new_layer->p->scaled_only = layer->p->scaled_only; + new_layer->p->auto_rename = layer->p->auto_rename; + new_layer->p->matrix = layer->p->matrix; + new_layer->p->offset_x = layer->p->offset_x; + new_layer->p->offset_y = layer->p->offset_y; + new_layer->p->interpolation = layer->p->interpolation; + + new_layer->p->keep_monitoring = FALSE; g_clear_object (&link); } @@ -350,6 +406,20 @@ gimp_link_layer_rename (GimpItem *item, return FALSE; } +static void +gimp_link_layer_translate (GimpItem *item, + gdouble offset_x, + gdouble offset_y, + gboolean push_undo) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (item); + + if (! gimp_matrix3_is_identity (&layer->p->matrix)) + gimp_matrix3_translate (&layer->p->matrix, offset_x, offset_y); + + GIMP_ITEM_CLASS (parent_class)->translate (item, offset_x, offset_y, push_undo); +} + static void gimp_link_layer_scale (GimpItem *item, gint new_width, @@ -385,37 +455,70 @@ gimp_link_layer_scale (GimpItem *item, if (queue) gimp_object_queue_pop (queue); - if (gimp_link_is_vector (link_layer->p->link) && - gimp_link_is_monitored (link_layer->p->link)) + if (gimp_link_is_vector (link_layer->p->link) && + gimp_link_is_monitored (link_layer->p->link) && + gimp_matrix3_is_identity (&link_layer->p->matrix)) { /* Non-modified vector images are always recomputed from the * source file and therefore are always sharp. */ gimp_link_set_size (link_layer->p->link, new_width, new_height); - gimp_link_layer_render (link_layer); + gimp_item_set_offset (item, new_offset_x, new_offset_y); + gimp_link_layer_render_link (link_layer); + } + else if (! gimp_matrix3_is_identity (&link_layer->p->matrix)) + { + GimpMatrix3 matrix = link_layer->p->matrix; + gdouble x_scale_factor; + gdouble y_scale_factor; + gint offset_x; + gint offset_y; + + x_scale_factor = (gdouble) new_width / gimp_item_get_width (item); + y_scale_factor = (gdouble) new_height / gimp_item_get_height (item); + + gimp_matrix3_scale (&matrix, x_scale_factor, y_scale_factor); + + gimp_link_layer_set_transform (link_layer, &matrix, interpolation_type, TRUE); + + /* Unfortunately we can't know the proper translation offset to + * set before we replay the full matrix (the offsets may have been + * changed in previous transformations). + * To avoid re-loading the source image twice though, I don't call + * gimp_link_layer_set_transform() again but directly edit the + * matrix and set the offset. + */ + gimp_item_get_offset (item, &offset_x, &offset_y); + gimp_matrix3_translate (&link_layer->p->matrix, + new_offset_x - offset_x, + new_offset_y - offset_y); + gimp_item_set_offset (item, new_offset_x, new_offset_y); + gimp_drawable_update_all (GIMP_DRAWABLE (layer)); + } + else if (gimp_link_is_monitored (link_layer->p->link)) + { + GimpMatrix3 matrix = link_layer->p->matrix; + gdouble x_scale_factor; + gdouble y_scale_factor; + gint offset_x; + gint offset_y; + + x_scale_factor = (gdouble) new_width / gimp_item_get_width (item); + y_scale_factor = (gdouble) new_height / gimp_item_get_height (item); + + gimp_matrix3_scale (&matrix, x_scale_factor, y_scale_factor); + gimp_item_get_offset (item, &offset_x, &offset_y); + gimp_matrix3_translate (&matrix, + new_offset_x - offset_x, + new_offset_y - offset_y); + gimp_link_layer_set_transform (link_layer, &matrix, interpolation_type, TRUE); } else { - gboolean scaled_only = FALSE; - - if (gimp_link_is_monitored (link_layer->p->link) || link_layer->p->scaled_only) - { - /* Raster images whose only modification are previous scaling - * are scaled back from the source file. Though they are still - * considered "modified" after a scaling (unlike vector - * images) and therefore are demoted to work like normal - * layers, scaling is still special-cased for better image - * quality. - */ - gimp_link_layer_render (link_layer); - scaled_only = TRUE; - } - GIMP_ITEM_CLASS (parent_class)->scale (GIMP_ITEM (layer), new_width, new_height, new_offset_x, new_offset_y, interpolation_type, progress); - g_object_set (layer, "scaled-only", scaled_only, NULL); } if (layer->mask) @@ -432,6 +535,81 @@ gimp_link_layer_scale (GimpItem *item, g_clear_object (&queue); } +static void +gimp_link_layer_transform (GimpItem *item, + GimpContext *context, + const GimpMatrix3 *matrix, + GimpTransformDirection direction, + GimpInterpolationType interpolation_type, + GimpTransformResize clip_result, + GimpProgress *progress, + gboolean push_undo) +{ + GimpLinkLayer *layer = GIMP_LINK_LAYER (item); + gboolean keep_monitoring; + + if (gimp_matrix3_is_identity (matrix)) + return; + + if (gimp_link_is_vector (layer->p->link) && + gimp_link_is_monitored (layer->p->link) && + gimp_matrix3_is_identity (&layer->p->matrix)) + { + gint new_width; + gint new_height; + gint new_offset_x; + gint new_offset_y; + + if (gimp_link_layer_is_scaling_matrix (layer, matrix, + &new_width, &new_height, + &new_offset_x, &new_offset_y)) + { + /* Scaling when no other transformation has happened yet is + * special-case for vector links, because we can do even + * better by reloading from source. + */ + gimp_link_layer_scale (item, new_width, new_height, + new_offset_x, new_offset_y, + interpolation_type, progress); + return; + } + } + + /* Clipping would produce different results when applied on a single + * step of multiple transformations. So only "adjust" transformations + * are stored non-destructively. Any clip/crop transformation triggers + * a destructive transform. + * + * Note: we could also store non-destructively a single clipped + * transformation, but that would be harder to document and explain. + */ + keep_monitoring = (gimp_link_is_monitored (layer->p->link) && + clip_result == GIMP_TRANSFORM_RESIZE_ADJUST); + + if (keep_monitoring) + { + GimpMatrix3 left = *matrix; + GimpMatrix3 right = layer->p->matrix; + + if (direction == GIMP_TRANSFORM_BACKWARD) + gimp_matrix3_invert (&left); + + gimp_matrix3_mult (&left, &right); + gimp_link_layer_set_transform (layer, &right, interpolation_type, TRUE); + } + else + { + /* Reset the transformation matrix. */ + gimp_matrix3_identity (&layer->p->matrix); + + GIMP_ITEM_CLASS (parent_class)->transform (item, context, + matrix, direction, + interpolation_type, + clip_result, progress, + push_undo); + } +} + static void gimp_link_layer_set_buffer (GimpDrawable *drawable, gboolean push_undo, @@ -442,21 +620,23 @@ gimp_link_layer_set_buffer (GimpDrawable *drawable, GimpLinkLayer *layer = GIMP_LINK_LAYER (drawable); GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer)); - if (push_undo && gimp_link_is_monitored (layer->p->link)) - gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_DRAWABLE_MOD, - undo_desc); + if (push_undo) + { + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_DRAWABLE_MOD, undo_desc); + gimp_image_undo_push_link_layer (image, NULL, layer); + } GIMP_DRAWABLE_CLASS (parent_class)->set_buffer (drawable, - push_undo, undo_desc, + FALSE, undo_desc, buffer, bounds); - if (push_undo && gimp_link_is_monitored (layer->p->link)) + if (push_undo) { - gimp_image_undo_push_link_layer (image, NULL, layer); - - gimp_link_freeze (layer->p->link); - g_object_set (drawable, "scaled-only", FALSE, NULL); + if (layer->p->link && + gimp_link_is_monitored (layer->p->link) && + ! layer->p->keep_monitoring) + gimp_link_freeze (layer->p->link); gimp_image_undo_group_end (image); } @@ -527,7 +707,7 @@ gimp_link_layer_convert_type (GimpLayer *layer, if (push_undo) gimp_image_undo_push_link_layer (image, NULL, link_layer); - gimp_link_layer_render (link_layer); + gimp_link_layer_render_link (link_layer); } } @@ -579,6 +759,20 @@ gboolean gimp_link_layer_set_link (GimpLinkLayer *layer, GimpLink *link, gboolean push_undo) +{ + return gimp_link_layer_set_link_with_matrix (layer, link, NULL, + GIMP_INTERPOLATION_NONE, + 0, 0, push_undo); +} + +gboolean +gimp_link_layer_set_link_with_matrix (GimpLinkLayer *layer, + GimpLink *link, + GimpMatrix3 *matrix, + GimpInterpolationType interpolation_type, + gint offset_x, + gint offset_y, + gboolean push_undo) { gboolean rendered = FALSE; @@ -592,7 +786,7 @@ gimp_link_layer_set_link (GimpLinkLayer *layer, if (layer->p->link) { g_signal_handlers_disconnect_by_func (layer->p->link, - G_CALLBACK (gimp_link_layer_render), + G_CALLBACK (gimp_link_layer_render_link), layer); } @@ -602,11 +796,25 @@ gimp_link_layer_set_link (GimpLinkLayer *layer, if (link) { g_signal_connect_object (link, "changed", - G_CALLBACK (gimp_link_layer_render), + G_CALLBACK (gimp_link_layer_render_link), layer, G_CONNECT_SWAPPED); if (gimp_link_is_monitored (link)) - rendered = gimp_link_layer_render (layer); + { + if (matrix == NULL) + { + rendered = gimp_link_layer_render_link (layer); + } + else + { + if (! gimp_matrix3_is_identity (matrix)) + { + layer->p->offset_x = offset_x; + layer->p->offset_y = offset_y; + } + rendered = gimp_link_layer_set_transform (layer, matrix, interpolation_type, push_undo); + } + } } g_object_notify (G_OBJECT (layer), "link"); @@ -649,7 +857,7 @@ gimp_link_layer_monitor (GimpLinkLayer *layer) _("Monitor Link"), layer); gimp_link_thaw (layer->p->link); - gimp_link_layer_render (layer); + gimp_link_layer_render_link (layer); } gboolean @@ -661,6 +869,69 @@ gimp_link_layer_is_monitored (GimpLinkLayer *layer) gimp_link_is_monitored (GIMP_LINK_LAYER (layer)->p->link)); } +gboolean +gimp_link_layer_get_transform (GimpLinkLayer *layer, + GimpMatrix3 *matrix, + gint *offset_x, + gint *offset_y, + GimpInterpolationType *interpolation) +{ + g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), FALSE); + + *matrix = layer->p->matrix; + *offset_x = layer->p->offset_x; + *offset_y = layer->p->offset_y; + *interpolation = layer->p->interpolation; + + return ! gimp_matrix3_is_identity (matrix); +} + +gboolean +gimp_link_layer_set_transform (GimpLinkLayer *layer, + GimpMatrix3 *matrix, + GimpInterpolationType interpolation_type, + gboolean push_undo) +{ + Gimp *gimp; + gboolean rendered; + + g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), FALSE); + g_return_val_if_fail (gimp_link_is_monitored (layer->p->link), FALSE); + + gimp = (gimp_item_get_image (GIMP_ITEM (layer)))->gimp; + + if (gimp_matrix3_is_identity (&layer->p->matrix)) + /* First transformation: store the initial offset. */ + gimp_item_get_offset (GIMP_ITEM (layer), &layer->p->offset_x, &layer->p->offset_y); + + gimp_item_set_offset (GIMP_ITEM (layer), layer->p->offset_x, layer->p->offset_y); + + rendered = gimp_link_layer_render_link (layer); + if (! gimp_matrix3_is_identity (matrix)) + { + layer->p->keep_monitoring = TRUE; + GIMP_ITEM_CLASS (parent_class)->transform (GIMP_ITEM (layer), + gimp_get_user_context (gimp), + matrix, + GIMP_TRANSFORM_FORWARD, + interpolation_type, + GIMP_TRANSFORM_RESIZE_ADJUST, + NULL, push_undo); + layer->p->keep_monitoring = FALSE; + } + + layer->p->matrix = *matrix; + + /* Interpolations are used to obtain reasonable quality. Considering + * that doing a single transform will always be better than several + * transforms in a row, it's OK to just drop the interpolation + * algorithm of previous transforms. We just store the last one. + */ + layer->p->interpolation = interpolation_type; + + return rendered; +} + guint32 gimp_link_layer_get_xcf_flags (GimpLinkLayer *link_layer) { @@ -738,7 +1009,7 @@ gimp_link_layer_from_layer (GimpLayer **layer, /* private functions */ static gboolean -gimp_link_layer_render (GimpLinkLayer *layer) +gimp_link_layer_render_link (GimpLinkLayer *layer) { GimpDrawable *drawable; GimpItem *item; @@ -773,11 +1044,19 @@ gimp_link_layer_render (GimpLinkLayer *layer) if ((width != gimp_item_get_width (item) || height != gimp_item_get_height (item))) { - GeglBuffer *new_buffer; + GeglBuffer *new_buffer; + GeglRectangle bounds; + + gimp_item_get_offset (GIMP_ITEM (drawable), + &bounds.x, &bounds.y); + bounds.width = 0; + bounds.height = 0; new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height), gimp_drawable_get_format (drawable)); - gimp_drawable_set_buffer (drawable, FALSE, NULL, new_buffer); + GIMP_DRAWABLE_CLASS (parent_class)->set_buffer (drawable, + FALSE, NULL, + new_buffer, &bounds); g_object_unref (new_buffer); if (gimp_layer_get_mask (GIMP_LAYER (layer))) @@ -849,3 +1128,39 @@ gimp_link_layer_set_xcf_flags (GimpLinkLayer *layer, else gimp_link_thaw (layer->p->link); } + +static gboolean +gimp_link_layer_is_scaling_matrix (GimpLinkLayer *layer, + const GimpMatrix3 *matrix, + gint *new_width, + gint *new_height, + gint *new_offset_x, + gint *new_offset_y) +{ + gboolean is_scaling; + + /* Scaling 3x3 matrix on a 2D plane (with optional translation). */ + is_scaling = (matrix->coeff[0][0] > 0.0 && matrix->coeff[0][1] == 0.0 && + matrix->coeff[1][0] == 0.0 && matrix->coeff[1][1] > 0.0 && + matrix->coeff[2][0] == 0.0 && matrix->coeff[2][1] == 0.0 && matrix->coeff[2][2] == 1.0); + + if (is_scaling) + { + gint width; + gint height; + gint offset_x; + gint offset_y; + + width = gimp_item_get_width (GIMP_ITEM (layer)); + height = gimp_item_get_height (GIMP_ITEM (layer)); + gimp_item_get_offset (GIMP_ITEM (layer), &offset_x, &offset_y); + + *new_width = (gint) (width * matrix->coeff[0][0]); + *new_height = (gint) (height * matrix->coeff[0][0]); + + *new_offset_x = (gint) (offset_x + matrix->coeff[0][2]); + *new_offset_y = (gint) (offset_y + matrix->coeff[1][2]); + } + + return is_scaling; +} diff --git a/app/core/gimplinklayer.h b/app/core/gimplinklayer.h index f643ed7087..886cd9f1c0 100644 --- a/app/core/gimplinklayer.h +++ b/app/core/gimplinklayer.h @@ -47,24 +47,40 @@ struct _GimpLinkLayerClass }; -GType gimp_link_layer_get_type (void) G_GNUC_CONST; +GType gimp_link_layer_get_type (void) G_GNUC_CONST; -GimpLayer * gimp_link_layer_new (GimpImage *image, - GimpLink *link); +GimpLayer * gimp_link_layer_new (GimpImage *image, + GimpLink *link); -GimpLink * gimp_link_layer_get_link (GimpLinkLayer *layer); -gboolean gimp_link_layer_set_link (GimpLinkLayer *layer, - GimpLink *link, - gboolean push_undo); +GimpLink * gimp_link_layer_get_link (GimpLinkLayer *layer); +gboolean gimp_link_layer_set_link (GimpLinkLayer *layer, + GimpLink *link, + gboolean push_undo); +gboolean gimp_link_layer_set_link_with_matrix (GimpLinkLayer *layer, + GimpLink *link, + GimpMatrix3 *matrix, + GimpInterpolationType interpolation_type, + gint offset_x, + gint offset_y, + gboolean push_undo); -void gimp_link_layer_discard (GimpLinkLayer *layer); -void gimp_link_layer_monitor (GimpLinkLayer *layer); -gboolean gimp_link_layer_is_monitored (GimpLinkLayer *layer); +void gimp_link_layer_discard (GimpLinkLayer *layer); +void gimp_link_layer_monitor (GimpLinkLayer *layer); +gboolean gimp_link_layer_is_monitored (GimpLinkLayer *layer); +gboolean gimp_link_layer_get_transform (GimpLinkLayer *layer, + GimpMatrix3 *matrix, + gint *offset_x, + gint *offset_y, + GimpInterpolationType *interpolation); +gboolean gimp_link_layer_set_transform (GimpLinkLayer *layer, + GimpMatrix3 *matrix, + GimpInterpolationType interpolation_type, + gboolean push_undo); /* Only to be used for XCF loading/saving. */ -guint32 gimp_link_layer_get_xcf_flags (GimpLinkLayer *layer); -void gimp_link_layer_from_layer (GimpLayer **layer, - GimpLink *link, - guint32 flags); +guint32 gimp_link_layer_get_xcf_flags (GimpLinkLayer *layer); +void gimp_link_layer_from_layer (GimpLayer **layer, + GimpLink *link, + guint32 flags); diff --git a/app/core/gimplinklayerundo.c b/app/core/gimplinklayerundo.c index fbe0fb87f0..b90ca80f49 100644 --- a/app/core/gimplinklayerundo.c +++ b/app/core/gimplinklayerundo.c @@ -108,6 +108,11 @@ gimp_link_layer_undo_constructed (GObject *object) link = gimp_link_layer_get_link (layer); undo->link = link ? gimp_link_duplicate (link) : NULL; + gimp_link_layer_get_transform (layer, + &undo->matrix, + &undo->offset_x, + &undo->offset_y, + &undo->interpolation); } static void @@ -181,18 +186,33 @@ gimp_link_layer_undo_pop (GimpUndo *undo, GimpUndoMode undo_mode, GimpUndoAccumulator *accum) { - GimpLinkLayerUndo *layer_undo = GIMP_LINK_LAYER_UNDO (undo); - GimpLinkLayer *layer = GIMP_LINK_LAYER (GIMP_ITEM_UNDO (undo)->item); - GimpLink *link; + GimpLinkLayerUndo *layer_undo = GIMP_LINK_LAYER_UNDO (undo); + GimpLinkLayer *layer = GIMP_LINK_LAYER (GIMP_ITEM_UNDO (undo)->item); + GimpLink *link; + GimpMatrix3 matrix; + gint offset_x; + gint offset_y; + GimpInterpolationType interpolation; GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum); link = gimp_link_layer_get_link (layer); link = link ? g_object_ref (link) : NULL; - gimp_link_layer_set_link (layer, layer_undo->link, FALSE); + gimp_link_layer_get_transform (layer, &matrix, &offset_x, &offset_y, &interpolation); + gimp_link_layer_set_link_with_matrix (layer, layer_undo->link, + &layer_undo->matrix, + layer_undo->interpolation, + layer_undo->offset_x, + layer_undo->offset_y, + FALSE); + + + layer_undo->matrix = matrix; + layer_undo->interpolation = interpolation; + layer_undo->offset_x = offset_x; + layer_undo->offset_y = offset_y; g_clear_object (&layer_undo->link); - layer_undo->link = link; } diff --git a/app/core/gimplinklayerundo.h b/app/core/gimplinklayerundo.h index ced5d63483..98fd536a03 100644 --- a/app/core/gimplinklayerundo.h +++ b/app/core/gimplinklayerundo.h @@ -35,14 +35,18 @@ typedef struct _GimpLinkLayerUndoClass GimpLinkLayerUndoClass; struct _GimpLinkLayerUndo { - GimpItemUndo parent_instance; + GimpItemUndo parent_instance; - GimpLink *link; + GimpLink *link; + GimpMatrix3 matrix; + gint offset_x; + gint offset_y; + GimpInterpolationType interpolation; }; struct _GimpLinkLayerUndoClass { - GimpItemUndoClass parent_class; + GimpItemUndoClass parent_class; }; diff --git a/app/path/gimppath.c b/app/path/gimppath.c index 2f2a498cc8..95adfe0c74 100644 --- a/app/path/gimppath.c +++ b/app/path/gimppath.c @@ -117,7 +117,8 @@ static void gimp_path_transform (GimpItem *item, GimpTransformDirection direction, GimpInterpolationType interp_type, GimpTransformResize clip_result, - GimpProgress *progress); + GimpProgress *progress, + gboolean push_undo); static GimpTransformResize gimp_path_get_clip (GimpItem *item, GimpTransformResize clip_result); @@ -611,7 +612,8 @@ gimp_path_transform (GimpItem *item, GimpTransformDirection direction, GimpInterpolationType interpolation_type, GimpTransformResize clip_result, - GimpProgress *progress) + GimpProgress *progress, + gboolean push_undo) { GimpPath *path = GIMP_PATH (item); GimpMatrix3 local_matrix; @@ -620,9 +622,10 @@ gimp_path_transform (GimpItem *item, gimp_path_freeze (path); - gimp_image_undo_push_path_mod (gimp_item_get_image (item), - _("Transform Path"), - path); + if (push_undo) + gimp_image_undo_push_path_mod (gimp_item_get_image (item), + _("Transform Path"), + path); local_matrix = *matrix; diff --git a/app/path/gimpvectorlayer.c b/app/path/gimpvectorlayer.c index 470f429154..a31d68232a 100644 --- a/app/path/gimpvectorlayer.c +++ b/app/path/gimpvectorlayer.c @@ -119,7 +119,8 @@ static void gimp_vector_layer_transform (GimpItem *ite GimpTransformDirection direction, GimpInterpolationType interp_type, GimpTransformResize clip_result, - GimpProgress *progress); + GimpProgress *progress, + gboolean push_undo); static gboolean gimp_vector_layer_render (GimpVectorLayer *layer); static void gimp_vector_layer_render_path (GimpVectorLayer *layer); @@ -497,7 +498,8 @@ gimp_vector_layer_transform (GimpItem *item, GimpTransformDirection direction, GimpInterpolationType interp_type, GimpTransformResize clip_result, - GimpProgress *progress) + GimpProgress *progress, + gboolean push_undo) { GimpVectorLayer *vector_layer = GIMP_VECTOR_LAYER (item); @@ -511,7 +513,7 @@ gimp_vector_layer_transform (GimpItem *item, { GIMP_ITEM_CLASS (parent_class)->transform (item, context, matrix, direction, interp_type, - clip_result, progress); + clip_result, progress, push_undo); } } diff --git a/app/tools/gimptransformtool.c b/app/tools/gimptransformtool.c index 704b038c2a..429c79642a 100644 --- a/app/tools/gimptransformtool.c +++ b/app/tools/gimptransformtool.c @@ -924,7 +924,7 @@ gimp_transform_tool_transform (GimpTransformTool *tr_tool, gimp_drawable_transform_paste (GIMP_DRAWABLE (selected_objects->data), new_buffer, buffer_profile, new_offset_x, new_offset_y, - new_layer); + new_layer, TRUE); g_object_unref (new_buffer); } break;