app: add generic support for less destructive transform tools.

This commit will make all transform tools run on a link layer cumulate
their transform matrix on top of the previous transform steps. It means
that as long as you don't edit the pixels another way (e.g. with a paint
tool), all your transformations will apply as a single transformation.

For instance it means that applying several transform tool steps on a
monitored link layer will be less destructive than applying the exact
same transformations on the exact same "normal" layer contents.
Even scaling an image to 1x1 then back to a big size will work very
fine!

Note nevertheless the following limitations in current implementation:

* The link layer with transformations will still show as a standard link
  layer. Nothing says it has transformation applied on it right now.
* To drop transformations applied on a link layer, you have to discard
  the link info, then monitor the link again. A specific action in the
  contextual menu could be worth it.
* This should work with all transform tools (scale, rotation, unified,
  perspective, 3D, even Warp…) but it won't work for the Flip tool, nor
  will it work for the Transform actions. I will need to implement
  GimpItem's rotate(), flip() and resize() methods next.
* The layer mask would still be destructively transformed (I have not
  made any tests with layer masks yet, but this should be done next
  too).
* I think that the "scaled-only" property is now meaningless. It is now
  being replaced by the presence of the GimpMatrix3. Nevertheless I have
  still not removed this property.
* The load/save code has not been redone yet to include all these
  changes.

The kind of caveats we'd have to know about (and which are not planned
for change, because it's just how it is):

* Any intermediate interpolation methods are dropped when cumulating
  transform steps. Only the last interpolation is stored. This is
  because anyway the interpolation is only there as the best algorithm
  where we visually see the less quality loss. Applying several
  transformations as a single matrix will always be visually better than
  applying several matrices (whatever the intermediate interpolation
  methods chosen).
* This only works with the "Adjust" clipping method (basically no
  clipping) because 2 transform steps with clipping won't produce the
  same result as the multiplied matrix with clipping. It means that
  applying a transform with clipping will downgrade your link layer to
  being a normal layer.
  The only issue I have with this is how to best convey that clipping is
  a major setting setting here, which disables our less-destructive
  abilities. Right now, people will just have to "know" it.
* Vector link layers in particular will have 2 levels of
  non/less-destructivity transforms. In particular any scaling (both
  through "Scale Image", "Scale Layers" and transform tools — since I
  added code to detect a matrix doing only scaling and optionally
  translation) done **first** will be completely non-destructive since
  we will simply reload the original vector source at the right
  dimensions. Any other kind of transforms will be appended through the
  cumulative matrix, as raster link layers. This also includes scaling
  done **after** other transforms, since we cannot easily move the
  scaling first (matrix multiplication is not commutative). This second
  level of scaling will therefore be *less* destructive, but still
  destructive.
  It is possible eventually to improve the whole thing if we add some
  day the ability to request loading a vector image with a transform
  matrix (it will then be up to each vector format plug-in to support
  this feature or not).

Note: it could be argued that this whole implementation could in fact be
moved over to base layers, since it would allow also less-destructivity
when applying multiple transformations in a row. We would only merge
results once we edit pixels more directly.
But I think that it's not a bad idea to experiment with this new code in
the link layer. Eventually I may likely move this to the parent
GimpLayer if no blocking issues are found.
This commit is contained in:
Jehan 2025-08-13 14:49:12 +02:00
parent 7c7587bb2d
commit 2821dbab58
15 changed files with 487 additions and 110 deletions

View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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));

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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;
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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;