mirror of
https://gitlab.gnome.org/GNOME/gimp.git
synced 2025-10-06 01:12:40 +02:00
1138 lines
40 KiB
C
1138 lines
40 KiB
C
/* GIMP - The GNU Image Manipulation Program
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* GimpLinkLayer
|
|
* Copyright (C) 2019 Jehan
|
|
*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <cairo.h>
|
|
#include <gegl.h>
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
|
|
|
#include "libgimpbase/gimpbase.h"
|
|
#include "libgimpcolor/gimpcolor.h"
|
|
#include "libgimpconfig/gimpconfig.h"
|
|
#include "libgimpmath/gimpmath.h"
|
|
|
|
#include "core-types.h"
|
|
|
|
#include "gegl/gimp-gegl-loops.h"
|
|
#include "gegl/gimp-gegl-utils.h"
|
|
|
|
#include "gimp.h"
|
|
#include "gimpcontext.h"
|
|
#include "gimpimage.h"
|
|
#include "gimpimage-color-profile.h"
|
|
#include "gimpimage-undo.h"
|
|
#include "gimpimage-undo-push.h"
|
|
#include "gimpitemtree.h"
|
|
#include "gimpobjectqueue.h"
|
|
#include "gimpprogress.h"
|
|
|
|
#include "gimplink.h"
|
|
#include "gimplinklayer.h"
|
|
|
|
#include "gimp-intl.h"
|
|
|
|
#define EPSILON 1e-10
|
|
|
|
|
|
enum
|
|
{
|
|
LINK_LAYER_XCF_NONE = 0,
|
|
LINK_LAYER_XCF_DONT_AUTO_RENAME = 1 << 0,
|
|
LINK_LAYER_XCF_MODIFIED = 1 << 1
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_LINK,
|
|
PROP_AUTO_RENAME,
|
|
PROP_SCALED_ONLY,
|
|
N_PROPS
|
|
};
|
|
|
|
struct _GimpLinkLayerPrivate
|
|
{
|
|
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);
|
|
static void gimp_link_layer_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
static void gimp_link_layer_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
static gint64 gimp_link_layer_get_memsize (GimpObject *object,
|
|
gint64 *gui_size);
|
|
|
|
static GimpItem * gimp_link_layer_duplicate (GimpItem *item,
|
|
GType new_type);
|
|
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,
|
|
gint new_offset_x,
|
|
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,
|
|
const gchar *undo_desc,
|
|
GeglBuffer *buffer,
|
|
const GeglRectangle *bounds);
|
|
static void gimp_link_layer_push_undo (GimpDrawable *drawable,
|
|
const gchar *undo_desc,
|
|
GeglBuffer *buffer,
|
|
gint x,
|
|
gint y,
|
|
gint width,
|
|
gint height);
|
|
|
|
static void gimp_link_layer_convert_type (GimpLayer *layer,
|
|
GimpImage *dest_image,
|
|
const Babl *new_format,
|
|
GimpColorProfile *src_profile,
|
|
GimpColorProfile *dest_profile,
|
|
GeglDitherMethod layer_dither_type,
|
|
GeglDitherMethod mask_dither_type,
|
|
gboolean push_undo,
|
|
GimpProgress *progress);
|
|
|
|
static void gimp_link_layer_render_full (GimpLinkLayer *layer);
|
|
static gboolean gimp_link_layer_render_link (GimpLinkLayer *layer);
|
|
|
|
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)
|
|
|
|
#define parent_class gimp_link_layer_parent_class
|
|
|
|
static GParamSpec *link_layer_props[N_PROPS] = { NULL, };
|
|
|
|
|
|
static void
|
|
gimp_link_layer_class_init (GimpLinkLayerClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
|
|
GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
|
|
GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
|
|
GimpDrawableClass *drawable_class = GIMP_DRAWABLE_CLASS (klass);
|
|
GimpLayerClass *layer_class = GIMP_LAYER_CLASS (klass);
|
|
|
|
object_class->finalize = gimp_link_layer_finalize;
|
|
object_class->get_property = gimp_link_layer_get_property;
|
|
object_class->set_property = gimp_link_layer_set_property;
|
|
|
|
gimp_object_class->get_memsize = gimp_link_layer_get_memsize;
|
|
|
|
viewable_class->default_icon_name = "emblem-symbolic-link";
|
|
viewable_class->default_name = _("Link Layer");
|
|
|
|
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");
|
|
item_class->scale_desc = _("Scale Link Layer");
|
|
item_class->resize_desc = _("Resize Link Layer");
|
|
item_class->flip_desc = _("Flip Link Layer");
|
|
item_class->rotate_desc = _("Rotate Link Layer");
|
|
item_class->transform_desc = _("Transform Link Layer");
|
|
|
|
drawable_class->set_buffer = gimp_link_layer_set_buffer;
|
|
drawable_class->push_undo = gimp_link_layer_push_undo;
|
|
|
|
layer_class->convert_type = gimp_link_layer_convert_type;
|
|
|
|
link_layer_props[PROP_LINK] = g_param_spec_object ("link",
|
|
NULL, NULL,
|
|
GIMP_TYPE_LINK,
|
|
GIMP_PARAM_READWRITE |
|
|
GIMP_PARAM_STATIC_STRINGS);
|
|
|
|
link_layer_props[PROP_AUTO_RENAME] = g_param_spec_boolean ("auto-rename",
|
|
NULL, NULL,
|
|
TRUE,
|
|
GIMP_PARAM_READWRITE |
|
|
GIMP_PARAM_STATIC_STRINGS);
|
|
|
|
link_layer_props[PROP_SCALED_ONLY] = g_param_spec_boolean ("scaled-only",
|
|
NULL, NULL,
|
|
FALSE,
|
|
GIMP_PARAM_READWRITE |
|
|
GIMP_PARAM_STATIC_STRINGS);
|
|
|
|
g_object_class_install_properties (object_class, N_PROPS, link_layer_props);
|
|
}
|
|
|
|
static void
|
|
gimp_link_layer_init (GimpLinkLayer *layer)
|
|
{
|
|
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
|
|
gimp_link_layer_finalize (GObject *object)
|
|
{
|
|
GimpLinkLayer *layer = GIMP_LINK_LAYER (object);
|
|
|
|
g_clear_object (&layer->p->link);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gimp_link_layer_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GimpLinkLayer *layer = GIMP_LINK_LAYER (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_LINK:
|
|
g_value_set_object (value, layer->p->link);
|
|
break;
|
|
case PROP_AUTO_RENAME:
|
|
g_value_set_boolean (value, layer->p->auto_rename);
|
|
break;
|
|
case PROP_SCALED_ONLY:
|
|
g_value_set_boolean (value, layer->p->scaled_only);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_link_layer_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GimpLinkLayer *layer = GIMP_LINK_LAYER (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_LINK:
|
|
gimp_link_layer_set_link (layer, g_value_get_object (value), FALSE);
|
|
break;
|
|
case PROP_AUTO_RENAME:
|
|
layer->p->auto_rename = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_SCALED_ONLY:
|
|
layer->p->scaled_only = g_value_get_boolean (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gint64
|
|
gimp_link_layer_get_memsize (GimpObject *object,
|
|
gint64 *gui_size)
|
|
{
|
|
GimpLinkLayer *layer = GIMP_LINK_LAYER (object);
|
|
gint64 memsize = 0;
|
|
|
|
memsize += gimp_object_get_memsize (GIMP_OBJECT (layer->p->link),
|
|
gui_size);
|
|
|
|
return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
|
|
gui_size);
|
|
}
|
|
|
|
static GimpItem *
|
|
gimp_link_layer_duplicate (GimpItem *item,
|
|
GType new_type)
|
|
{
|
|
GimpItem *new_item;
|
|
|
|
g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);
|
|
|
|
new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);
|
|
|
|
if (GIMP_IS_LINK_LAYER (new_item))
|
|
{
|
|
GimpLinkLayer *layer = GIMP_LINK_LAYER (item);
|
|
GimpLinkLayer *new_layer = GIMP_LINK_LAYER (new_item);
|
|
GimpLink *link = NULL;
|
|
GeglBuffer *buffer;
|
|
gint width;
|
|
gint height;
|
|
|
|
if (layer->p->link)
|
|
{
|
|
link = gimp_link_duplicate (layer->p->link);
|
|
gimp_link_layer_set_link (new_layer, link, FALSE);
|
|
}
|
|
|
|
gimp_config_sync (G_OBJECT (layer), G_OBJECT (new_layer), 0);
|
|
|
|
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
|
|
width = gegl_buffer_get_width (buffer);
|
|
height = gegl_buffer_get_height (buffer);
|
|
|
|
if (width != gimp_item_get_width (GIMP_ITEM (new_layer)) ||
|
|
height != gimp_item_get_height (GIMP_ITEM (new_layer)))
|
|
{
|
|
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_CLASS (parent_class)->set_buffer (GIMP_DRAWABLE (new_layer),
|
|
FALSE, NULL,
|
|
new_buffer, &bounds);
|
|
g_object_unref (new_buffer);
|
|
}
|
|
|
|
gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE,
|
|
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->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);
|
|
}
|
|
|
|
return new_item;
|
|
}
|
|
|
|
static gboolean
|
|
gimp_link_layer_rename (GimpItem *item,
|
|
const gchar *new_name,
|
|
const gchar *undo_desc,
|
|
GError **error)
|
|
{
|
|
if (GIMP_ITEM_CLASS (parent_class)->rename (item, new_name, undo_desc, error))
|
|
{
|
|
g_object_set (item, "auto-rename", FALSE, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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,
|
|
gint new_height,
|
|
gint new_offset_x,
|
|
gint new_offset_y,
|
|
GimpInterpolationType interpolation_type,
|
|
GimpProgress *progress)
|
|
{
|
|
GimpLinkLayer *link_layer = GIMP_LINK_LAYER (item);
|
|
GimpLayer *layer = GIMP_LAYER (item);
|
|
GimpObjectQueue *queue = NULL;
|
|
|
|
if (progress && layer->mask)
|
|
{
|
|
GimpLayerMask *mask;
|
|
|
|
queue = gimp_object_queue_new (progress);
|
|
progress = GIMP_PROGRESS (queue);
|
|
|
|
/* temporarily set layer->mask to NULL, so that its size won't be counted
|
|
* when pushing the layer to the queue.
|
|
*/
|
|
mask = layer->mask;
|
|
layer->mask = NULL;
|
|
|
|
gimp_object_queue_push (queue, layer);
|
|
gimp_object_queue_push (queue, mask);
|
|
|
|
layer->mask = mask;
|
|
}
|
|
|
|
if (queue)
|
|
gimp_object_queue_pop (queue);
|
|
|
|
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, FALSE);
|
|
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
|
|
{
|
|
GIMP_ITEM_CLASS (parent_class)->scale (GIMP_ITEM (layer),
|
|
new_width, new_height,
|
|
new_offset_x, new_offset_y,
|
|
interpolation_type, progress);
|
|
}
|
|
|
|
if (layer->mask)
|
|
{
|
|
if (queue)
|
|
gimp_object_queue_pop (queue);
|
|
|
|
gimp_item_scale (GIMP_ITEM (layer->mask),
|
|
new_width, new_height,
|
|
new_offset_x, new_offset_y,
|
|
interpolation_type, progress);
|
|
}
|
|
|
|
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 (push_undo)
|
|
gimp_image_undo_push_link_layer (gimp_item_get_image (GIMP_ITEM (layer)),
|
|
_("Transform Link Layer"), layer);
|
|
|
|
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,
|
|
const gchar *undo_desc,
|
|
GeglBuffer *buffer,
|
|
const GeglRectangle *bounds)
|
|
{
|
|
GimpLinkLayer *layer = GIMP_LINK_LAYER (drawable);
|
|
GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
|
|
|
|
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,
|
|
FALSE, undo_desc,
|
|
buffer,
|
|
bounds);
|
|
|
|
if (push_undo)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_link_layer_push_undo (GimpDrawable *drawable,
|
|
const gchar *undo_desc,
|
|
GeglBuffer *buffer,
|
|
gint x,
|
|
gint y,
|
|
gint width,
|
|
gint height)
|
|
{
|
|
GimpLinkLayer *layer = GIMP_LINK_LAYER (drawable);
|
|
GimpImage *image = gimp_item_get_image (GIMP_ITEM (layer));
|
|
gboolean monitored;
|
|
|
|
monitored = gimp_link_is_monitored (layer->p->link);
|
|
|
|
if (monitored)
|
|
{
|
|
gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_DRAWABLE, undo_desc);
|
|
|
|
gimp_image_undo_push_link_layer (image, NULL, layer);
|
|
gimp_link_freeze (layer->p->link);
|
|
}
|
|
|
|
GIMP_DRAWABLE_CLASS (parent_class)->push_undo (drawable, undo_desc,
|
|
buffer,
|
|
x, y, width, height);
|
|
|
|
if (monitored)
|
|
{
|
|
gimp_image_undo_group_end (image);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_link_layer_convert_type (GimpLayer *layer,
|
|
GimpImage *dest_image,
|
|
const Babl *new_format,
|
|
GimpColorProfile *src_profile,
|
|
GimpColorProfile *dest_profile,
|
|
GeglDitherMethod layer_dither_type,
|
|
GeglDitherMethod mask_dither_type,
|
|
gboolean push_undo,
|
|
GimpProgress *progress)
|
|
{
|
|
GimpLinkLayer *link_layer = GIMP_LINK_LAYER (layer);
|
|
GimpImage *image = gimp_item_get_image (GIMP_ITEM (link_layer));
|
|
|
|
GIMP_LAYER_CLASS (parent_class)->convert_type (layer, dest_image,
|
|
new_format,
|
|
src_profile,
|
|
dest_profile,
|
|
layer_dither_type,
|
|
mask_dither_type,
|
|
push_undo,
|
|
progress);
|
|
|
|
if (link_layer->p->link && gimp_link_is_monitored (link_layer->p->link))
|
|
{
|
|
if (push_undo)
|
|
gimp_image_undo_push_link_layer (image, NULL, link_layer);
|
|
|
|
gimp_link_layer_render_link (link_layer);
|
|
}
|
|
}
|
|
|
|
|
|
/* public functions */
|
|
|
|
/**
|
|
* gimp_link_layer_new:
|
|
* @image: the #GimpImage the layer should belong to
|
|
* @text: a #GimpText object
|
|
*
|
|
* Creates a new link layer.
|
|
*
|
|
* Return value: a new #GimpLinkLayer or %NULL in case of a problem
|
|
**/
|
|
GimpLayer *
|
|
gimp_link_layer_new (GimpImage *image,
|
|
GimpLink *link)
|
|
{
|
|
GimpLinkLayer *layer;
|
|
|
|
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
|
|
|
|
layer =
|
|
GIMP_LINK_LAYER (gimp_drawable_new (GIMP_TYPE_LINK_LAYER,
|
|
image, NULL,
|
|
0, 0, 1, 1,
|
|
gimp_image_get_layer_format (image,
|
|
TRUE)));
|
|
gimp_layer_set_mode (GIMP_LAYER (layer),
|
|
gimp_image_get_default_new_layer_mode (image),
|
|
FALSE);
|
|
|
|
if (! gimp_link_layer_set_link (layer, link, FALSE))
|
|
g_clear_object (&layer);
|
|
|
|
return GIMP_LAYER (layer);
|
|
}
|
|
|
|
GimpLink *
|
|
gimp_link_layer_get_link (GimpLinkLayer *layer)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), NULL);
|
|
|
|
return layer->p->link;
|
|
}
|
|
|
|
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;
|
|
|
|
g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), FALSE);
|
|
g_return_val_if_fail (GIMP_IS_LINK (link), FALSE);
|
|
|
|
if (gimp_item_is_attached (GIMP_ITEM (layer)) && push_undo)
|
|
gimp_image_undo_push_link_layer (gimp_item_get_image (GIMP_ITEM (layer)),
|
|
_("Set layer link"), layer);
|
|
|
|
if (layer->p->link)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (layer->p->link,
|
|
G_CALLBACK (gimp_link_layer_render_full),
|
|
layer);
|
|
|
|
}
|
|
|
|
g_set_object (&layer->p->link, link);
|
|
|
|
if (link)
|
|
{
|
|
g_signal_connect_object (link, "changed",
|
|
G_CALLBACK (gimp_link_layer_render_full),
|
|
layer, G_CONNECT_SWAPPED);
|
|
|
|
if (gimp_link_is_monitored (link))
|
|
{
|
|
if (matrix == NULL)
|
|
{
|
|
gimp_matrix3_identity (&layer->p->matrix);
|
|
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");
|
|
gimp_viewable_invalidate_preview (GIMP_VIEWABLE (layer));
|
|
|
|
return rendered;
|
|
}
|
|
|
|
/**
|
|
* gimp_link_layer_discard:
|
|
* @layer: a #GimpLinkLayer
|
|
*
|
|
* Discards the link. This makes @layer behave like a
|
|
* normal layer.
|
|
*/
|
|
void
|
|
gimp_link_layer_discard (GimpLinkLayer *layer)
|
|
{
|
|
g_return_if_fail (GIMP_IS_LINK_LAYER (layer));
|
|
g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)));
|
|
|
|
gimp_image_undo_push_link_layer (gimp_item_get_image (GIMP_ITEM (layer)),
|
|
_("Discard Link"), layer);
|
|
|
|
gimp_link_freeze (layer->p->link);
|
|
|
|
/* Triggers thumbnail update. */
|
|
gimp_drawable_update_all (GIMP_DRAWABLE (layer));
|
|
/* Triggers contextual menu update. */
|
|
gimp_image_flush (gimp_item_get_image (GIMP_ITEM (layer)));
|
|
}
|
|
|
|
void
|
|
gimp_link_layer_monitor (GimpLinkLayer *layer)
|
|
{
|
|
g_return_if_fail (GIMP_IS_LINK_LAYER (layer));
|
|
g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (layer)));
|
|
|
|
gimp_image_undo_push_link_layer (gimp_item_get_image (GIMP_ITEM (layer)),
|
|
_("Monitor Link"), layer);
|
|
|
|
gimp_link_thaw (layer->p->link);
|
|
gimp_matrix3_identity (&layer->p->matrix);
|
|
gimp_link_layer_render_link (layer);
|
|
}
|
|
|
|
gboolean
|
|
gimp_link_layer_is_monitored (GimpLinkLayer *layer)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), FALSE);
|
|
|
|
return (GIMP_LINK_LAYER (layer)->p->link &&
|
|
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;
|
|
}
|
|
|
|
gboolean
|
|
gimp_item_is_link_layer (GimpItem *item)
|
|
{
|
|
g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);
|
|
|
|
return (GIMP_IS_LINK_LAYER (item) &&
|
|
gimp_link_layer_is_monitored (GIMP_LINK_LAYER (item)));
|
|
}
|
|
|
|
void
|
|
gimp_link_layer_set_xcf_flags (GimpLinkLayer *layer,
|
|
guint32 flags)
|
|
{
|
|
g_return_if_fail (GIMP_IS_LINK_LAYER (layer));
|
|
|
|
g_object_set (layer,
|
|
"auto-rename", (flags & LINK_LAYER_XCF_DONT_AUTO_RENAME) == 0,
|
|
NULL);
|
|
|
|
if ((flags & LINK_LAYER_XCF_MODIFIED) != 0)
|
|
gimp_link_freeze (layer->p->link);
|
|
}
|
|
|
|
guint32
|
|
gimp_link_layer_get_xcf_flags (GimpLinkLayer *link_layer)
|
|
{
|
|
guint flags = 0;
|
|
|
|
g_return_val_if_fail (GIMP_IS_LINK_LAYER (link_layer), 0);
|
|
|
|
if (! link_layer->p->auto_rename)
|
|
flags |= LINK_LAYER_XCF_DONT_AUTO_RENAME;
|
|
|
|
if (! gimp_link_is_monitored (link_layer->p->link))
|
|
flags |= LINK_LAYER_XCF_MODIFIED;
|
|
|
|
return flags;
|
|
}
|
|
|
|
|
|
/* private functions */
|
|
|
|
static void
|
|
gimp_link_layer_render_full (GimpLinkLayer *layer)
|
|
{
|
|
gimp_link_layer_render_link (layer);
|
|
if (! gimp_matrix3_is_identity (&layer->p->matrix))
|
|
{
|
|
Gimp *gimp = (gimp_item_get_image (GIMP_ITEM (layer)))->gimp;
|
|
|
|
gimp_item_set_offset (GIMP_ITEM (layer), layer->p->offset_x, layer->p->offset_y);
|
|
|
|
layer->p->keep_monitoring = TRUE;
|
|
GIMP_ITEM_CLASS (parent_class)->transform (GIMP_ITEM (layer),
|
|
gimp_get_user_context (gimp),
|
|
&layer->p->matrix,
|
|
GIMP_TRANSFORM_FORWARD,
|
|
layer->p->interpolation,
|
|
GIMP_TRANSFORM_RESIZE_ADJUST,
|
|
NULL, FALSE);
|
|
layer->p->keep_monitoring = FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gimp_link_layer_render_link (GimpLinkLayer *layer)
|
|
{
|
|
GimpDrawable *drawable;
|
|
GimpItem *item;
|
|
GimpImage *image;
|
|
GeglBuffer *buffer;
|
|
gdouble xres;
|
|
gdouble yres;
|
|
gint width;
|
|
gint height;
|
|
|
|
if (! layer->p->link)
|
|
return FALSE;
|
|
|
|
drawable = GIMP_DRAWABLE (layer);
|
|
item = GIMP_ITEM (layer);
|
|
image = gimp_item_get_image (item);
|
|
|
|
gimp_image_get_resolution (image, &xres, &yres);
|
|
/* TODO: I could imagine a GimpBusyBox (to be made as GimpProgress) in
|
|
* the later list showing layer update.
|
|
*/
|
|
buffer = gimp_link_get_buffer (layer->p->link);
|
|
|
|
if (! buffer)
|
|
return FALSE;
|
|
|
|
width = gegl_buffer_get_width (buffer);
|
|
height = gegl_buffer_get_height (buffer);
|
|
|
|
g_object_freeze_notify (G_OBJECT (drawable));
|
|
|
|
if ((width != gimp_item_get_width (item) ||
|
|
height != gimp_item_get_height (item)))
|
|
{
|
|
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_CLASS (parent_class)->set_buffer (drawable,
|
|
FALSE, NULL,
|
|
new_buffer, &bounds);
|
|
g_object_unref (new_buffer);
|
|
|
|
if (gimp_layer_get_mask (GIMP_LAYER (layer)))
|
|
{
|
|
GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (layer));
|
|
|
|
static GimpContext *unused_eek = NULL;
|
|
|
|
if (! unused_eek)
|
|
unused_eek = gimp_context_new (image->gimp, "eek", NULL);
|
|
|
|
gimp_item_resize (GIMP_ITEM (mask),
|
|
unused_eek, GIMP_FILL_TRANSPARENT,
|
|
width, height, 0, 0);
|
|
}
|
|
}
|
|
|
|
if (layer->p->auto_rename)
|
|
{
|
|
GimpItem *item = GIMP_ITEM (layer);
|
|
gchar *name = NULL;
|
|
|
|
if (layer->p->link)
|
|
{
|
|
name = g_strdup (gimp_object_get_name (layer->p->link));
|
|
}
|
|
|
|
if (! name || ! name[0])
|
|
{
|
|
g_free (name);
|
|
name = g_strdup (_("Link Layer"));
|
|
}
|
|
|
|
if (gimp_item_is_attached (item))
|
|
{
|
|
gimp_item_tree_rename_item (gimp_item_get_tree (item), item,
|
|
name, FALSE, NULL);
|
|
g_free (name);
|
|
}
|
|
else
|
|
{
|
|
gimp_object_take_name (GIMP_OBJECT (layer), name);
|
|
}
|
|
}
|
|
|
|
gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE,
|
|
gimp_drawable_get_buffer (drawable), NULL);
|
|
|
|
g_object_thaw_notify (G_OBJECT (drawable));
|
|
|
|
gimp_drawable_update (drawable, 0, 0, width, height);
|
|
gimp_image_flush (image);
|
|
|
|
return (width > 0 && height > 0);
|
|
}
|
|
|
|
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] < EPSILON && matrix->coeff[1][1] > 0.0 &&
|
|
matrix->coeff[2][0] < EPSILON && matrix->coeff[2][1] < EPSILON && 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[1][1]);
|
|
|
|
*new_offset_x = (gint) (offset_x * matrix->coeff[0][0] + matrix->coeff[0][2]);
|
|
*new_offset_y = (gint) (offset_y * matrix->coeff[1][1] + matrix->coeff[1][2]);
|
|
}
|
|
|
|
return is_scaling;
|
|
}
|