summaryrefslogtreecommitdiffstats
path: root/app/core/gimpdrawable.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/core/gimpdrawable.c')
-rw-r--r--app/core/gimpdrawable.c1916
1 files changed, 1916 insertions, 0 deletions
diff --git a/app/core/gimpdrawable.c b/app/core/gimpdrawable.c
new file mode 100644
index 0000000..9a85eee
--- /dev/null
+++ b/app/core/gimpdrawable.c
@@ -0,0 +1,1916 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "core-types.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "gimp-memsize.h"
+#include "gimp-utils.h"
+#include "gimpchannel.h"
+#include "gimpcontext.h"
+#include "gimpdrawable-combine.h"
+#include "gimpdrawable-fill.h"
+#include "gimpdrawable-floating-selection.h"
+#include "gimpdrawable-preview.h"
+#include "gimpdrawable-private.h"
+#include "gimpdrawable-shadow.h"
+#include "gimpdrawable-transform.h"
+#include "gimpfilterstack.h"
+#include "gimpimage.h"
+#include "gimpimage-colormap.h"
+#include "gimpimage-undo-push.h"
+#include "gimpmarshal.h"
+#include "gimppickable.h"
+#include "gimpprogress.h"
+
+#include "gimp-log.h"
+
+#include "gimp-intl.h"
+
+
+#define PAINT_UPDATE_CHUNK_WIDTH 32
+#define PAINT_UPDATE_CHUNK_HEIGHT 32
+
+
+enum
+{
+ UPDATE,
+ FORMAT_CHANGED,
+ ALPHA_CHANGED,
+ BOUNDING_BOX_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_BUFFER
+};
+
+
+/* local function prototypes */
+
+static void gimp_color_managed_iface_init (GimpColorManagedInterface *iface);
+static void gimp_pickable_iface_init (GimpPickableInterface *iface);
+
+static void gimp_drawable_dispose (GObject *object);
+static void gimp_drawable_finalize (GObject *object);
+static void gimp_drawable_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_drawable_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gint64 gimp_drawable_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static gboolean gimp_drawable_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height);
+static void gimp_drawable_preview_freeze (GimpViewable *viewable);
+static void gimp_drawable_preview_thaw (GimpViewable *viewable);
+
+static GeglNode * gimp_drawable_get_node (GimpFilter *filter);
+
+static void gimp_drawable_removed (GimpItem *item);
+static GimpItem * gimp_drawable_duplicate (GimpItem *item,
+ GType new_type);
+static void gimp_drawable_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interp_type,
+ GimpProgress *progress);
+static void gimp_drawable_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y);
+static void gimp_drawable_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result);
+static void gimp_drawable_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result);
+static void gimp_drawable_transform (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress);
+
+static const guint8 *
+ gimp_drawable_get_icc_profile (GimpColorManaged *managed,
+ gsize *len);
+static GimpColorProfile *
+ gimp_drawable_get_color_profile (GimpColorManaged *managed);
+static void gimp_drawable_profile_changed (GimpColorManaged *managed);
+
+static gboolean gimp_drawable_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel);
+static void gimp_drawable_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel);
+
+static void gimp_drawable_real_update (GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+
+static gint64 gimp_drawable_real_estimate_memsize (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height);
+
+static void gimp_drawable_real_update_all (GimpDrawable *drawable);
+
+static GimpComponentMask
+ gimp_drawable_real_get_active_mask (GimpDrawable *drawable);
+
+static gboolean gimp_drawable_real_supports_alpha
+ (GimpDrawable *drawable);
+
+static void gimp_drawable_real_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress);
+
+static GeglBuffer * gimp_drawable_real_get_buffer (GimpDrawable *drawable);
+static void gimp_drawable_real_set_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds);
+
+static GeglRectangle gimp_drawable_real_get_bounding_box
+ (GimpDrawable *drawable);
+
+static void gimp_drawable_real_push_undo (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ gint x,
+ gint y,
+ gint width,
+ gint height);
+static void gimp_drawable_real_swap_pixels (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint x,
+ gint y);
+static GeglNode * gimp_drawable_real_get_source_node (GimpDrawable *drawable);
+
+static void gimp_drawable_format_changed (GimpDrawable *drawable);
+static void gimp_drawable_alpha_changed (GimpDrawable *drawable);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpDrawable, gimp_drawable, GIMP_TYPE_ITEM,
+ G_ADD_PRIVATE (GimpDrawable)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_COLOR_MANAGED,
+ gimp_color_managed_iface_init)
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_PICKABLE,
+ gimp_pickable_iface_init))
+
+#define parent_class gimp_drawable_parent_class
+
+static guint gimp_drawable_signals[LAST_SIGNAL] = { 0 };
+
+
+static void
+gimp_drawable_class_init (GimpDrawableClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
+ GimpFilterClass *filter_class = GIMP_FILTER_CLASS (klass);
+ GimpItemClass *item_class = GIMP_ITEM_CLASS (klass);
+
+ gimp_drawable_signals[UPDATE] =
+ g_signal_new ("update",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDrawableClass, update),
+ NULL, NULL,
+ gimp_marshal_VOID__INT_INT_INT_INT,
+ G_TYPE_NONE, 4,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT,
+ G_TYPE_INT);
+
+ gimp_drawable_signals[FORMAT_CHANGED] =
+ g_signal_new ("format-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDrawableClass, format_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_drawable_signals[ALPHA_CHANGED] =
+ g_signal_new ("alpha-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDrawableClass, alpha_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gimp_drawable_signals[BOUNDING_BOX_CHANGED] =
+ g_signal_new ("bounding-box-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpDrawableClass, bounding_box_changed),
+ NULL, NULL,
+ gimp_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->dispose = gimp_drawable_dispose;
+ object_class->finalize = gimp_drawable_finalize;
+ object_class->set_property = gimp_drawable_set_property;
+ object_class->get_property = gimp_drawable_get_property;
+
+ gimp_object_class->get_memsize = gimp_drawable_get_memsize;
+
+ viewable_class->get_size = gimp_drawable_get_size;
+ viewable_class->get_new_preview = gimp_drawable_get_new_preview;
+ viewable_class->get_new_pixbuf = gimp_drawable_get_new_pixbuf;
+ viewable_class->preview_freeze = gimp_drawable_preview_freeze;
+ viewable_class->preview_thaw = gimp_drawable_preview_thaw;
+
+ filter_class->get_node = gimp_drawable_get_node;
+
+ item_class->removed = gimp_drawable_removed;
+ item_class->duplicate = gimp_drawable_duplicate;
+ item_class->scale = gimp_drawable_scale;
+ item_class->resize = gimp_drawable_resize;
+ item_class->flip = gimp_drawable_flip;
+ item_class->rotate = gimp_drawable_rotate;
+ item_class->transform = gimp_drawable_transform;
+
+ klass->update = gimp_drawable_real_update;
+ klass->format_changed = NULL;
+ klass->alpha_changed = NULL;
+ klass->bounding_box_changed = NULL;
+ klass->estimate_memsize = gimp_drawable_real_estimate_memsize;
+ klass->update_all = gimp_drawable_real_update_all;
+ klass->invalidate_boundary = NULL;
+ klass->get_active_components = NULL;
+ klass->get_active_mask = gimp_drawable_real_get_active_mask;
+ klass->supports_alpha = gimp_drawable_real_supports_alpha;
+ klass->convert_type = gimp_drawable_real_convert_type;
+ klass->apply_buffer = gimp_drawable_real_apply_buffer;
+ klass->get_buffer = gimp_drawable_real_get_buffer;
+ klass->set_buffer = gimp_drawable_real_set_buffer;
+ klass->get_bounding_box = gimp_drawable_real_get_bounding_box;
+ klass->push_undo = gimp_drawable_real_push_undo;
+ klass->swap_pixels = gimp_drawable_real_swap_pixels;
+ klass->get_source_node = gimp_drawable_real_get_source_node;
+
+ g_object_class_override_property (object_class, PROP_BUFFER, "buffer");
+}
+
+static void
+gimp_drawable_init (GimpDrawable *drawable)
+{
+ drawable->private = gimp_drawable_get_instance_private (drawable);
+
+ drawable->private->filter_stack = gimp_filter_stack_new (GIMP_TYPE_FILTER);
+}
+
+/* sorry for the evil casts */
+
+static void
+gimp_color_managed_iface_init (GimpColorManagedInterface *iface)
+{
+ iface->get_icc_profile = gimp_drawable_get_icc_profile;
+ iface->get_color_profile = gimp_drawable_get_color_profile;
+ iface->profile_changed = gimp_drawable_profile_changed;
+}
+
+static void
+gimp_pickable_iface_init (GimpPickableInterface *iface)
+{
+ iface->get_image = (GimpImage * (*) (GimpPickable *pickable)) gimp_item_get_image;
+ iface->get_format = (const Babl * (*) (GimpPickable *pickable)) gimp_drawable_get_format;
+ iface->get_format_with_alpha = (const Babl * (*) (GimpPickable *pickable)) gimp_drawable_get_format_with_alpha;
+ iface->get_buffer = (GeglBuffer * (*) (GimpPickable *pickable)) gimp_drawable_get_buffer;
+ iface->get_pixel_at = gimp_drawable_get_pixel_at;
+ iface->get_pixel_average = gimp_drawable_get_pixel_average;
+}
+
+static void
+gimp_drawable_dispose (GObject *object)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (object);
+
+ if (gimp_drawable_get_floating_sel (drawable))
+ gimp_drawable_detach_floating_sel (drawable);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_drawable_finalize (GObject *object)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (object);
+
+ while (drawable->private->paint_count)
+ gimp_drawable_end_paint (drawable);
+
+ g_clear_object (&drawable->private->buffer);
+
+ gimp_drawable_free_shadow_buffer (drawable);
+
+ g_clear_object (&drawable->private->source_node);
+ g_clear_object (&drawable->private->buffer_source_node);
+ g_clear_object (&drawable->private->filter_stack);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_drawable_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ case PROP_BUFFER:
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_drawable_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (object);
+
+ switch (property_id)
+ {
+ case PROP_BUFFER:
+ g_value_set_object (value, drawable->private->buffer);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gint64
+gimp_drawable_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_gegl_buffer_get_memsize (gimp_drawable_get_buffer (drawable));
+ memsize += gimp_gegl_buffer_get_memsize (drawable->private->shadow);
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+static gboolean
+gimp_drawable_get_size (GimpViewable *viewable,
+ gint *width,
+ gint *height)
+{
+ GimpItem *item = GIMP_ITEM (viewable);
+
+ *width = gimp_item_get_width (item);
+ *height = gimp_item_get_height (item);
+
+ return TRUE;
+}
+
+static void
+gimp_drawable_preview_freeze (GimpViewable *viewable)
+{
+ GimpViewable *parent = gimp_viewable_get_parent (viewable);
+
+ if (! parent && gimp_item_is_attached (GIMP_ITEM (viewable)))
+ parent = GIMP_VIEWABLE (gimp_item_get_image (GIMP_ITEM (viewable)));
+
+ if (parent)
+ gimp_viewable_preview_freeze (parent);
+}
+
+static void
+gimp_drawable_preview_thaw (GimpViewable *viewable)
+{
+ GimpViewable *parent = gimp_viewable_get_parent (viewable);
+
+ if (! parent && gimp_item_is_attached (GIMP_ITEM (viewable)))
+ parent = GIMP_VIEWABLE (gimp_item_get_image (GIMP_ITEM (viewable)));
+
+ if (parent)
+ gimp_viewable_preview_thaw (parent);
+}
+
+static GeglNode *
+gimp_drawable_get_node (GimpFilter *filter)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (filter);
+ GeglNode *node;
+ GeglNode *input;
+ GeglNode *output;
+
+ node = GIMP_FILTER_CLASS (parent_class)->get_node (filter);
+
+ g_warn_if_fail (drawable->private->mode_node == NULL);
+
+ drawable->private->mode_node =
+ gegl_node_new_child (node,
+ "operation", "gimp:normal",
+ NULL);
+
+ input = gegl_node_get_input_proxy (node, "input");
+ output = gegl_node_get_output_proxy (node, "output");
+
+ gegl_node_connect_to (input, "output",
+ drawable->private->mode_node, "input");
+ gegl_node_connect_to (drawable->private->mode_node, "output",
+ output, "input");
+
+ return node;
+}
+
+static void
+gimp_drawable_removed (GimpItem *item)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+
+ gimp_drawable_free_shadow_buffer (drawable);
+
+ if (GIMP_ITEM_CLASS (parent_class)->removed)
+ GIMP_ITEM_CLASS (parent_class)->removed (item);
+}
+
+static GimpItem *
+gimp_drawable_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_DRAWABLE (new_item))
+ {
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GimpDrawable *new_drawable = GIMP_DRAWABLE (new_item);
+ GeglBuffer *new_buffer;
+
+ new_buffer = gimp_gegl_buffer_dup (gimp_drawable_get_buffer (drawable));
+
+ gimp_drawable_set_buffer (new_drawable, FALSE, NULL, new_buffer);
+ g_object_unref (new_buffer);
+ }
+
+ return new_item;
+}
+
+static void
+gimp_drawable_scale (GimpItem *item,
+ gint new_width,
+ gint new_height,
+ gint new_offset_x,
+ gint new_offset_y,
+ GimpInterpolationType interpolation_type,
+ GimpProgress *progress)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GeglBuffer *new_buffer;
+
+ new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ new_width, new_height),
+ gimp_drawable_get_format (drawable));
+
+ gimp_gegl_apply_scale (gimp_drawable_get_buffer (drawable),
+ progress, C_("undo-type", "Scale"),
+ new_buffer,
+ interpolation_type,
+ ((gdouble) new_width /
+ gimp_item_get_width (item)),
+ ((gdouble) new_height /
+ gimp_item_get_height (item)));
+
+ gimp_drawable_set_buffer_full (drawable, gimp_item_is_attached (item), NULL,
+ new_buffer,
+ GEGL_RECTANGLE (new_offset_x, new_offset_y,
+ 0, 0),
+ TRUE);
+ g_object_unref (new_buffer);
+}
+
+static void
+gimp_drawable_resize (GimpItem *item,
+ GimpContext *context,
+ GimpFillType fill_type,
+ gint new_width,
+ gint new_height,
+ gint offset_x,
+ gint offset_y)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GeglBuffer *new_buffer;
+ gint new_offset_x;
+ gint new_offset_y;
+ gint copy_x, copy_y;
+ gint copy_width, copy_height;
+ gboolean intersect;
+
+ /* if the size doesn't change, this is a nop */
+ if (new_width == gimp_item_get_width (item) &&
+ new_height == gimp_item_get_height (item) &&
+ offset_x == 0 &&
+ offset_y == 0)
+ return;
+
+ new_offset_x = gimp_item_get_offset_x (item) - offset_x;
+ new_offset_y = gimp_item_get_offset_y (item) - offset_y;
+
+ intersect = gimp_rectangle_intersect (gimp_item_get_offset_x (item),
+ gimp_item_get_offset_y (item),
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ new_offset_x,
+ new_offset_y,
+ new_width,
+ new_height,
+ &copy_x,
+ &copy_y,
+ &copy_width,
+ &copy_height);
+
+ new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ new_width, new_height),
+ gimp_drawable_get_format (drawable));
+
+ if (! intersect ||
+ copy_width != new_width ||
+ copy_height != new_height)
+ {
+ /* Clear the new buffer if needed */
+
+ GimpRGB color;
+ GimpPattern *pattern;
+
+ gimp_get_fill_params (context, fill_type, &color, &pattern, NULL);
+ gimp_drawable_fill_buffer (drawable, new_buffer,
+ &color, pattern, 0, 0);
+ }
+
+ if (intersect && copy_width && copy_height)
+ {
+ /* Copy the pixels in the intersection */
+ gimp_gegl_buffer_copy (
+ gimp_drawable_get_buffer (drawable),
+ GEGL_RECTANGLE (copy_x - gimp_item_get_offset_x (item),
+ copy_y - gimp_item_get_offset_y (item),
+ copy_width,
+ copy_height), GEGL_ABYSS_NONE,
+ new_buffer,
+ GEGL_RECTANGLE (copy_x - new_offset_x,
+ copy_y - new_offset_y, 0, 0));
+ }
+
+ gimp_drawable_set_buffer_full (drawable, gimp_item_is_attached (item), NULL,
+ new_buffer,
+ GEGL_RECTANGLE (new_offset_x, new_offset_y,
+ 0, 0),
+ TRUE);
+ g_object_unref (new_buffer);
+}
+
+static void
+gimp_drawable_flip (GimpItem *item,
+ GimpContext *context,
+ GimpOrientationType flip_type,
+ gdouble axis,
+ gboolean clip_result)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GeglBuffer *buffer;
+ GimpColorProfile *buffer_profile;
+ gint off_x, off_y;
+ gint new_off_x, new_off_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ buffer = gimp_drawable_transform_buffer_flip (drawable, context,
+ gimp_drawable_get_buffer (drawable),
+ off_x, off_y,
+ flip_type, axis,
+ clip_result,
+ &buffer_profile,
+ &new_off_x, &new_off_y);
+
+ if (buffer)
+ {
+ gimp_drawable_transform_paste (drawable, buffer, buffer_profile,
+ new_off_x, new_off_y, FALSE);
+ g_object_unref (buffer);
+ }
+}
+
+static void
+gimp_drawable_rotate (GimpItem *item,
+ GimpContext *context,
+ GimpRotationType rotate_type,
+ gdouble center_x,
+ gdouble center_y,
+ gboolean clip_result)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GeglBuffer *buffer;
+ GimpColorProfile *buffer_profile;
+ gint off_x, off_y;
+ gint new_off_x, new_off_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ buffer = gimp_drawable_transform_buffer_rotate (drawable, context,
+ gimp_drawable_get_buffer (drawable),
+ off_x, off_y,
+ rotate_type, center_x, center_y,
+ clip_result,
+ &buffer_profile,
+ &new_off_x, &new_off_y);
+
+ if (buffer)
+ {
+ gimp_drawable_transform_paste (drawable, buffer, buffer_profile,
+ new_off_x, new_off_y, FALSE);
+ g_object_unref (buffer);
+ }
+}
+
+static void
+gimp_drawable_transform (GimpItem *item,
+ GimpContext *context,
+ const GimpMatrix3 *matrix,
+ GimpTransformDirection direction,
+ GimpInterpolationType interpolation_type,
+ GimpTransformResize clip_result,
+ GimpProgress *progress)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (item);
+ GeglBuffer *buffer;
+ GimpColorProfile *buffer_profile;
+ gint off_x, off_y;
+ gint new_off_x, new_off_y;
+
+ gimp_item_get_offset (item, &off_x, &off_y);
+
+ buffer = gimp_drawable_transform_buffer_affine (drawable, context,
+ gimp_drawable_get_buffer (drawable),
+ off_x, off_y,
+ matrix, direction,
+ interpolation_type,
+ clip_result,
+ &buffer_profile,
+ &new_off_x, &new_off_y,
+ progress);
+
+ if (buffer)
+ {
+ gimp_drawable_transform_paste (drawable, buffer, buffer_profile,
+ new_off_x, new_off_y, FALSE);
+ g_object_unref (buffer);
+ }
+}
+
+static const guint8 *
+gimp_drawable_get_icc_profile (GimpColorManaged *managed,
+ gsize *len)
+{
+ GimpColorProfile *profile = gimp_color_managed_get_color_profile (managed);
+
+ return gimp_color_profile_get_icc_profile (profile, len);
+}
+
+static GimpColorProfile *
+gimp_drawable_get_color_profile (GimpColorManaged *managed)
+{
+ const Babl *format = gimp_drawable_get_format (GIMP_DRAWABLE (managed));
+
+ return gimp_babl_format_get_color_profile (format);
+}
+
+static void
+gimp_drawable_profile_changed (GimpColorManaged *managed)
+{
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (managed));
+}
+
+static gboolean
+gimp_drawable_get_pixel_at (GimpPickable *pickable,
+ gint x,
+ gint y,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (pickable);
+
+ /* do not make this a g_return_if_fail() */
+ if (x < 0 || x >= gimp_item_get_width (GIMP_ITEM (drawable)) ||
+ y < 0 || y >= gimp_item_get_height (GIMP_ITEM (drawable)))
+ return FALSE;
+
+ gegl_buffer_sample (gimp_drawable_get_buffer (drawable),
+ x, y, NULL, pixel, format,
+ GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
+
+ return TRUE;
+}
+
+static void
+gimp_drawable_get_pixel_average (GimpPickable *pickable,
+ const GeglRectangle *rect,
+ const Babl *format,
+ gpointer pixel)
+{
+ GimpDrawable *drawable = GIMP_DRAWABLE (pickable);
+
+ return gimp_gegl_average_color (gimp_drawable_get_buffer (drawable),
+ rect, TRUE, GEGL_ABYSS_NONE, format, pixel);
+}
+
+static void
+gimp_drawable_real_update (GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ gimp_viewable_invalidate_preview (GIMP_VIEWABLE (drawable));
+}
+
+static gint64
+gimp_drawable_real_estimate_memsize (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height)
+{
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ gboolean linear = gimp_drawable_get_linear (drawable);
+ const Babl *format;
+
+ format = gimp_image_get_format (image,
+ gimp_drawable_get_base_type (drawable),
+ gimp_babl_precision (component_type, linear),
+ gimp_drawable_has_alpha (drawable));
+
+ return (gint64) babl_format_get_bytes_per_pixel (format) * width * height;
+}
+
+static void
+gimp_drawable_real_update_all (GimpDrawable *drawable)
+{
+ gimp_drawable_update (drawable, 0, 0, -1, -1);
+}
+
+static GimpComponentMask
+gimp_drawable_real_get_active_mask (GimpDrawable *drawable)
+{
+ /* Return all, because that skips the component mask op when painting */
+ return GIMP_COMPONENT_MASK_ALL;
+}
+
+static gboolean
+gimp_drawable_real_supports_alpha (GimpDrawable *drawable)
+{
+ return FALSE;
+}
+
+/* FIXME: this default impl is currently unused because no subclass
+ * chains up. the goal is to handle the almost identical subclass code
+ * here again.
+ */
+static void
+gimp_drawable_real_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ const Babl *new_format,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress)
+{
+ GeglBuffer *dest_buffer;
+
+ dest_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (GIMP_ITEM (drawable)),
+ gimp_item_get_height (GIMP_ITEM (drawable))),
+ new_format);
+
+ gimp_gegl_buffer_copy (
+ gimp_drawable_get_buffer (drawable), NULL, GEGL_ABYSS_NONE,
+ dest_buffer, NULL);
+
+ gimp_drawable_set_buffer (drawable, push_undo, NULL, dest_buffer);
+ g_object_unref (dest_buffer);
+}
+
+static GeglBuffer *
+gimp_drawable_real_get_buffer (GimpDrawable *drawable)
+{
+ return drawable->private->buffer;
+}
+
+static void
+gimp_drawable_real_set_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds)
+{
+ GimpItem *item = GIMP_ITEM (drawable);
+ const Babl *old_format = NULL;
+ gint old_has_alpha = -1;
+
+ g_object_freeze_notify (G_OBJECT (drawable));
+
+ gimp_drawable_invalidate_boundary (drawable);
+
+ if (push_undo)
+ gimp_image_undo_push_drawable_mod (gimp_item_get_image (item), undo_desc,
+ drawable, FALSE);
+
+ if (drawable->private->buffer)
+ {
+ old_format = gimp_drawable_get_format (drawable);
+ old_has_alpha = gimp_drawable_has_alpha (drawable);
+ }
+
+ g_set_object (&drawable->private->buffer, buffer);
+
+ if (drawable->private->buffer_source_node)
+ gegl_node_set (drawable->private->buffer_source_node,
+ "buffer", gimp_drawable_get_buffer (drawable),
+ NULL);
+
+ gimp_item_set_offset (item, bounds->x, bounds->y);
+ gimp_item_set_size (item,
+ bounds->width ? bounds->width :
+ gegl_buffer_get_width (buffer),
+ bounds->height ? bounds->height :
+ gegl_buffer_get_height (buffer));
+
+ gimp_drawable_update_bounding_box (drawable);
+
+ if (gimp_drawable_get_format (drawable) != old_format)
+ gimp_drawable_format_changed (drawable);
+
+ if (gimp_drawable_has_alpha (drawable) != old_has_alpha)
+ gimp_drawable_alpha_changed (drawable);
+
+ g_object_notify (G_OBJECT (drawable), "buffer");
+
+ g_object_thaw_notify (G_OBJECT (drawable));
+}
+
+static GeglRectangle
+gimp_drawable_real_get_bounding_box (GimpDrawable *drawable)
+{
+ return gegl_node_get_bounding_box (gimp_drawable_get_source_node (drawable));
+}
+
+static void
+gimp_drawable_real_push_undo (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ GimpImage *image;
+
+ if (! buffer)
+ {
+ GeglBuffer *drawable_buffer = gimp_drawable_get_buffer (drawable);
+ GeglRectangle drawable_rect;
+
+ gegl_rectangle_align_to_buffer (
+ &drawable_rect,
+ GEGL_RECTANGLE (x, y, width, height),
+ drawable_buffer,
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ x = drawable_rect.x;
+ y = drawable_rect.y;
+ width = drawable_rect.width;
+ height = drawable_rect.height;
+
+ buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
+ gimp_drawable_get_format (drawable));
+
+ gimp_gegl_buffer_copy (
+ drawable_buffer,
+ &drawable_rect, GEGL_ABYSS_NONE,
+ buffer,
+ GEGL_RECTANGLE (0, 0, 0, 0));
+ }
+ else
+ {
+ g_object_ref (buffer);
+ }
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ gimp_image_undo_push_drawable (image,
+ undo_desc, drawable,
+ buffer, x, y);
+
+ g_object_unref (buffer);
+}
+
+static void
+gimp_drawable_real_swap_pixels (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint x,
+ gint y)
+{
+ GeglBuffer *tmp;
+ gint width = gegl_buffer_get_width (buffer);
+ gint height = gegl_buffer_get_height (buffer);
+
+ tmp = gimp_gegl_buffer_dup (buffer);
+
+ gimp_gegl_buffer_copy (gimp_drawable_get_buffer (drawable),
+ GEGL_RECTANGLE (x, y, width, height), GEGL_ABYSS_NONE,
+ buffer,
+ GEGL_RECTANGLE (0, 0, 0, 0));
+ gimp_gegl_buffer_copy (tmp,
+ GEGL_RECTANGLE (0, 0, width, height), GEGL_ABYSS_NONE,
+ gimp_drawable_get_buffer (drawable),
+ GEGL_RECTANGLE (x, y, 0, 0));
+
+ g_object_unref (tmp);
+
+ gimp_drawable_update (drawable, x, y, width, height);
+}
+
+static GeglNode *
+gimp_drawable_real_get_source_node (GimpDrawable *drawable)
+{
+ g_warn_if_fail (drawable->private->buffer_source_node == NULL);
+
+ drawable->private->buffer_source_node =
+ gegl_node_new_child (NULL,
+ "operation", "gimp:buffer-source-validate",
+ "buffer", gimp_drawable_get_buffer (drawable),
+ NULL);
+
+ return g_object_ref (drawable->private->buffer_source_node);
+}
+
+static void
+gimp_drawable_format_changed (GimpDrawable *drawable)
+{
+ g_signal_emit (drawable, gimp_drawable_signals[FORMAT_CHANGED], 0);
+}
+
+static void
+gimp_drawable_alpha_changed (GimpDrawable *drawable)
+{
+ g_signal_emit (drawable, gimp_drawable_signals[ALPHA_CHANGED], 0);
+}
+
+
+/* public functions */
+
+GimpDrawable *
+gimp_drawable_new (GType type,
+ GimpImage *image,
+ const gchar *name,
+ gint offset_x,
+ gint offset_y,
+ gint width,
+ gint height,
+ const Babl *format)
+{
+ GimpDrawable *drawable;
+ GeglBuffer *buffer;
+
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
+ g_return_val_if_fail (g_type_is_a (type, GIMP_TYPE_DRAWABLE), NULL);
+ g_return_val_if_fail (width > 0 && height > 0, NULL);
+ g_return_val_if_fail (format != NULL, NULL);
+
+ drawable = GIMP_DRAWABLE (gimp_item_new (type,
+ image, name,
+ offset_x, offset_y,
+ width, height));
+
+ buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height), format);
+
+ gimp_drawable_set_buffer (drawable, FALSE, NULL, buffer);
+ g_object_unref (buffer);
+
+ return drawable;
+}
+
+gint64
+gimp_drawable_estimate_memsize (GimpDrawable *drawable,
+ GimpComponentType component_type,
+ gint width,
+ gint height)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), 0);
+
+ return GIMP_DRAWABLE_GET_CLASS (drawable)->estimate_memsize (drawable,
+ component_type,
+ width, height);
+}
+
+void
+gimp_drawable_update (GimpDrawable *drawable,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+
+ if (width < 0)
+ {
+ GeglRectangle bounding_box;
+
+ bounding_box = gimp_drawable_get_bounding_box (drawable);
+
+ x = bounding_box.x;
+ width = bounding_box.width;
+ }
+
+ if (height < 0)
+ {
+ GeglRectangle bounding_box;
+
+ bounding_box = gimp_drawable_get_bounding_box (drawable);
+
+ y = bounding_box.y;
+ height = bounding_box.height;
+ }
+
+ if (drawable->private->paint_count == 0)
+ {
+ g_signal_emit (drawable, gimp_drawable_signals[UPDATE], 0,
+ x, y, width, height);
+ }
+ else
+ {
+ GeglRectangle rect;
+
+ if (gegl_rectangle_intersect (
+ &rect,
+ GEGL_RECTANGLE (x, y, width, height),
+ GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (GIMP_ITEM (drawable)),
+ gimp_item_get_height (GIMP_ITEM (drawable)))))
+ {
+ GeglRectangle aligned_rect;
+
+ gegl_rectangle_align_to_buffer (&aligned_rect, &rect,
+ gimp_drawable_get_buffer (drawable),
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ if (drawable->private->paint_copy_region)
+ {
+ cairo_region_union_rectangle (
+ drawable->private->paint_copy_region,
+ (const cairo_rectangle_int_t *) &aligned_rect);
+ }
+ else
+ {
+ drawable->private->paint_copy_region =
+ cairo_region_create_rectangle (
+ (const cairo_rectangle_int_t *) &aligned_rect);
+ }
+
+ gegl_rectangle_align (&aligned_rect, &rect,
+ GEGL_RECTANGLE (0, 0,
+ PAINT_UPDATE_CHUNK_WIDTH,
+ PAINT_UPDATE_CHUNK_HEIGHT),
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ if (drawable->private->paint_update_region)
+ {
+ cairo_region_union_rectangle (
+ drawable->private->paint_update_region,
+ (const cairo_rectangle_int_t *) &aligned_rect);
+ }
+ else
+ {
+ drawable->private->paint_update_region =
+ cairo_region_create_rectangle (
+ (const cairo_rectangle_int_t *) &aligned_rect);
+ }
+ }
+ }
+}
+
+void
+gimp_drawable_update_all (GimpDrawable *drawable)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+
+ GIMP_DRAWABLE_GET_CLASS (drawable)->update_all (drawable);
+}
+
+void
+gimp_drawable_invalidate_boundary (GimpDrawable *drawable)
+{
+ GimpDrawableClass *drawable_class;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+
+ drawable_class = GIMP_DRAWABLE_GET_CLASS (drawable);
+
+ if (drawable_class->invalidate_boundary)
+ drawable_class->invalidate_boundary (drawable);
+}
+
+void
+gimp_drawable_get_active_components (GimpDrawable *drawable,
+ gboolean *active)
+{
+ GimpDrawableClass *drawable_class;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (active != NULL);
+
+ drawable_class = GIMP_DRAWABLE_GET_CLASS (drawable);
+
+ if (drawable_class->get_active_components)
+ drawable_class->get_active_components (drawable, active);
+}
+
+GimpComponentMask
+gimp_drawable_get_active_mask (GimpDrawable *drawable)
+{
+ GimpComponentMask mask;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), 0);
+
+ mask = GIMP_DRAWABLE_GET_CLASS (drawable)->get_active_mask (drawable);
+
+ /* if the drawable doesn't have an alpha channel, the value of the mask's
+ * alpha-bit doesn't matter, however, we'd like to have a fully-clear or
+ * fully-set mask whenever possible, since it allows us to skip component
+ * masking altogether. we therefore set or clear the alpha bit, depending on
+ * the state of the other bits, so that it never gets in the way of a uniform
+ * mask.
+ */
+ if (! gimp_drawable_has_alpha (drawable))
+ {
+ if (mask & ~GIMP_COMPONENT_MASK_ALPHA)
+ mask |= GIMP_COMPONENT_MASK_ALPHA;
+ else
+ mask &= ~GIMP_COMPONENT_MASK_ALPHA;
+ }
+
+ return mask;
+}
+
+gboolean
+gimp_drawable_supports_alpha (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ return GIMP_DRAWABLE_GET_CLASS (drawable)->supports_alpha (drawable);
+}
+
+void
+gimp_drawable_convert_type (GimpDrawable *drawable,
+ GimpImage *dest_image,
+ GimpImageBaseType new_base_type,
+ GimpPrecision new_precision,
+ gboolean new_has_alpha,
+ GimpColorProfile *dest_profile,
+ GeglDitherMethod layer_dither_type,
+ GeglDitherMethod mask_dither_type,
+ gboolean push_undo,
+ GimpProgress *progress)
+{
+ const Babl *old_format;
+ const Babl *new_format;
+ gint old_bits;
+ gint new_bits;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GIMP_IS_IMAGE (dest_image));
+ g_return_if_fail (new_base_type != gimp_drawable_get_base_type (drawable) ||
+ new_precision != gimp_drawable_get_precision (drawable) ||
+ new_has_alpha != gimp_drawable_has_alpha (drawable) ||
+ dest_profile);
+ g_return_if_fail (dest_profile == NULL || GIMP_IS_COLOR_PROFILE (dest_profile));
+ g_return_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (drawable)))
+ push_undo = FALSE;
+
+ old_format = gimp_drawable_get_format (drawable);
+ new_format = gimp_image_get_format (dest_image,
+ new_base_type,
+ new_precision,
+ new_has_alpha);
+
+ old_bits = (babl_format_get_bytes_per_pixel (old_format) * 8 /
+ babl_format_get_n_components (old_format));
+ new_bits = (babl_format_get_bytes_per_pixel (new_format) * 8 /
+ babl_format_get_n_components (new_format));
+
+ if (old_bits <= new_bits || new_bits > 16)
+ {
+ /* don't dither if we are converting to a higher bit depth,
+ * or to more than 16 bits (gegl:dither only does
+ * 16 bits).
+ */
+ layer_dither_type = GEGL_DITHER_NONE;
+ mask_dither_type = GEGL_DITHER_NONE;
+ }
+
+ GIMP_DRAWABLE_GET_CLASS (drawable)->convert_type (drawable, dest_image,
+ new_format,
+ dest_profile,
+ layer_dither_type,
+ mask_dither_type,
+ push_undo,
+ progress);
+
+ if (progress)
+ gimp_progress_set_value (progress, 1.0);
+}
+
+void
+gimp_drawable_apply_buffer (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ const GeglRectangle *buffer_region,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ gdouble opacity,
+ GimpLayerMode mode,
+ GimpLayerColorSpace blend_space,
+ GimpLayerColorSpace composite_space,
+ GimpLayerCompositeMode composite_mode,
+ GeglBuffer *base_buffer,
+ gint base_x,
+ gint base_y)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+ g_return_if_fail (buffer_region != NULL);
+ g_return_if_fail (base_buffer == NULL || GEGL_IS_BUFFER (base_buffer));
+
+ GIMP_DRAWABLE_GET_CLASS (drawable)->apply_buffer (drawable, buffer,
+ buffer_region,
+ push_undo, undo_desc,
+ opacity, mode,
+ blend_space,
+ composite_space,
+ composite_mode,
+ base_buffer,
+ base_x, base_y);
+}
+
+GeglBuffer *
+gimp_drawable_get_buffer (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ if (drawable->private->paint_count == 0)
+ return GIMP_DRAWABLE_GET_CLASS (drawable)->get_buffer (drawable);
+ else
+ return drawable->private->paint_buffer;
+}
+
+void
+gimp_drawable_set_buffer (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+
+ if (! gimp_item_is_attached (GIMP_ITEM (drawable)))
+ push_undo = FALSE;
+
+ gimp_drawable_set_buffer_full (drawable, push_undo, undo_desc, buffer, NULL,
+ TRUE);
+}
+
+void
+gimp_drawable_set_buffer_full (GimpDrawable *drawable,
+ gboolean push_undo,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ const GeglRectangle *bounds,
+ gboolean update)
+{
+ GimpItem *item;
+ GeglRectangle curr_bounds;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+
+ item = GIMP_ITEM (drawable);
+
+ if (! gimp_item_is_attached (GIMP_ITEM (drawable)))
+ push_undo = FALSE;
+
+ if (! bounds)
+ {
+ gimp_item_get_offset (GIMP_ITEM (drawable),
+ &curr_bounds.x, &curr_bounds.y);
+
+ curr_bounds.width = 0;
+ curr_bounds.height = 0;
+
+ bounds = &curr_bounds;
+ }
+
+ if (update && gimp_drawable_get_buffer (drawable))
+ {
+ GeglBuffer *old_buffer = gimp_drawable_get_buffer (drawable);
+ GeglRectangle old_extent;
+ GeglRectangle new_extent;
+
+ old_extent = *gegl_buffer_get_extent (old_buffer);
+ old_extent.x += gimp_item_get_offset_x (item);
+ old_extent.y += gimp_item_get_offset_x (item);
+
+ new_extent = *gegl_buffer_get_extent (buffer);
+ new_extent.x += bounds->x;
+ new_extent.y += bounds->y;
+
+ if (! gegl_rectangle_equal (&old_extent, &new_extent))
+ gimp_drawable_update (drawable, 0, 0, -1, -1);
+ }
+
+ g_object_freeze_notify (G_OBJECT (drawable));
+
+ GIMP_DRAWABLE_GET_CLASS (drawable)->set_buffer (drawable,
+ push_undo, undo_desc,
+ buffer, bounds);
+
+ g_object_thaw_notify (G_OBJECT (drawable));
+
+ if (update)
+ gimp_drawable_update (drawable, 0, 0, -1, -1);
+}
+
+void
+gimp_drawable_steal_buffer (GimpDrawable *drawable,
+ GimpDrawable *src_drawable)
+{
+ GeglBuffer *buffer;
+ GeglBuffer *replacement_buffer;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GIMP_IS_DRAWABLE (src_drawable));
+
+ buffer = gimp_drawable_get_buffer (src_drawable);
+
+ g_return_if_fail (buffer != NULL);
+
+ g_object_ref (buffer);
+
+ replacement_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, 1, 1),
+ gegl_buffer_get_format (buffer));
+
+ gimp_drawable_set_buffer (src_drawable, FALSE, NULL, replacement_buffer);
+ gimp_drawable_set_buffer (drawable, FALSE, NULL, buffer);
+
+ g_object_unref (replacement_buffer);
+ g_object_unref (buffer);
+}
+
+GeglNode *
+gimp_drawable_get_source_node (GimpDrawable *drawable)
+{
+ GeglNode *input;
+ GeglNode *source;
+ GeglNode *filter;
+ GeglNode *output;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ if (drawable->private->source_node)
+ return drawable->private->source_node;
+
+ drawable->private->source_node = gegl_node_new ();
+
+ input = gegl_node_get_input_proxy (drawable->private->source_node, "input");
+
+ source = GIMP_DRAWABLE_GET_CLASS (drawable)->get_source_node (drawable);
+
+ gegl_node_add_child (drawable->private->source_node, source);
+
+ g_object_unref (source);
+
+ if (gegl_node_has_pad (source, "input"))
+ {
+ gegl_node_connect_to (input, "output",
+ source, "input");
+ }
+
+ filter = gimp_filter_stack_get_graph (GIMP_FILTER_STACK (drawable->private->filter_stack));
+
+ gegl_node_add_child (drawable->private->source_node, filter);
+
+ gegl_node_connect_to (source, "output",
+ filter, "input");
+
+ output = gegl_node_get_output_proxy (drawable->private->source_node, "output");
+
+ gegl_node_connect_to (filter, "output",
+ output, "input");
+
+ if (gimp_drawable_get_floating_sel (drawable))
+ _gimp_drawable_add_floating_sel_filter (drawable);
+
+ return drawable->private->source_node;
+}
+
+GeglNode *
+gimp_drawable_get_mode_node (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ if (! drawable->private->mode_node)
+ gimp_filter_get_node (GIMP_FILTER (drawable));
+
+ return drawable->private->mode_node;
+}
+
+GeglRectangle
+gimp_drawable_get_bounding_box (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable),
+ *GEGL_RECTANGLE (0, 0, 0, 0));
+
+ if (gegl_rectangle_is_empty (&drawable->private->bounding_box))
+ gimp_drawable_update_bounding_box (drawable);
+
+ return drawable->private->bounding_box;
+}
+
+gboolean
+gimp_drawable_update_bounding_box (GimpDrawable *drawable)
+{
+ GeglRectangle bounding_box;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ bounding_box =
+ GIMP_DRAWABLE_GET_CLASS (drawable)->get_bounding_box (drawable);
+
+ if (! gegl_rectangle_equal (&bounding_box, &drawable->private->bounding_box))
+ {
+ GeglRectangle old_bounding_box = drawable->private->bounding_box;
+ GeglRectangle diff_rects[4];
+ gint n_diff_rects;
+ gint i;
+
+ n_diff_rects = gegl_rectangle_subtract (diff_rects,
+ &old_bounding_box,
+ &bounding_box);
+
+ for (i = 0; i < n_diff_rects; i++)
+ {
+ gimp_drawable_update (drawable,
+ diff_rects[i].x,
+ diff_rects[i].y,
+ diff_rects[i].width,
+ diff_rects[i].height);
+ }
+
+ drawable->private->bounding_box = bounding_box;
+
+ g_signal_emit (drawable, gimp_drawable_signals[BOUNDING_BOX_CHANGED], 0);
+
+ n_diff_rects = gegl_rectangle_subtract (diff_rects,
+ &bounding_box,
+ &old_bounding_box);
+
+ for (i = 0; i < n_diff_rects; i++)
+ {
+ gimp_drawable_update (drawable,
+ diff_rects[i].x,
+ diff_rects[i].y,
+ diff_rects[i].width,
+ diff_rects[i].height);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gimp_drawable_swap_pixels (GimpDrawable *drawable,
+ GeglBuffer *buffer,
+ gint x,
+ gint y)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (GEGL_IS_BUFFER (buffer));
+
+ GIMP_DRAWABLE_GET_CLASS (drawable)->swap_pixels (drawable, buffer, x, y);
+}
+
+void
+gimp_drawable_push_undo (GimpDrawable *drawable,
+ const gchar *undo_desc,
+ GeglBuffer *buffer,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
+{
+ GimpItem *item;
+
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (buffer == NULL || GEGL_IS_BUFFER (buffer));
+
+ item = GIMP_ITEM (drawable);
+
+ g_return_if_fail (gimp_item_is_attached (item));
+
+ if (! buffer &&
+ ! gimp_rectangle_intersect (x, y,
+ width, height,
+ 0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item),
+ &x, &y, &width, &height))
+ {
+ g_warning ("%s: tried to push empty region", G_STRFUNC);
+ return;
+ }
+
+ GIMP_DRAWABLE_GET_CLASS (drawable)->push_undo (drawable, undo_desc,
+ buffer,
+ x, y, width, height);
+}
+
+const Babl *
+gimp_drawable_get_format (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ return gegl_buffer_get_format (drawable->private->buffer);
+}
+
+const Babl *
+gimp_drawable_get_format_with_alpha (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ return gimp_image_get_format (gimp_item_get_image (GIMP_ITEM (drawable)),
+ gimp_drawable_get_base_type (drawable),
+ gimp_drawable_get_precision (drawable),
+ TRUE);
+}
+
+const Babl *
+gimp_drawable_get_format_without_alpha (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ return gimp_image_get_format (gimp_item_get_image (GIMP_ITEM (drawable)),
+ gimp_drawable_get_base_type (drawable),
+ gimp_drawable_get_precision (drawable),
+ FALSE);
+}
+
+gboolean
+gimp_drawable_get_linear (GimpDrawable *drawable)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ format = gegl_buffer_get_format (drawable->private->buffer);
+
+ return gimp_babl_format_get_linear (format);
+}
+
+gboolean
+gimp_drawable_has_alpha (GimpDrawable *drawable)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ format = gegl_buffer_get_format (drawable->private->buffer);
+
+ return babl_format_has_alpha (format);
+}
+
+GimpImageBaseType
+gimp_drawable_get_base_type (GimpDrawable *drawable)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), -1);
+
+ format = gegl_buffer_get_format (drawable->private->buffer);
+
+ return gimp_babl_format_get_base_type (format);
+}
+
+GimpComponentType
+gimp_drawable_get_component_type (GimpDrawable *drawable)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), -1);
+
+ format = gegl_buffer_get_format (drawable->private->buffer);
+
+ return gimp_babl_format_get_component_type (format);
+}
+
+GimpPrecision
+gimp_drawable_get_precision (GimpDrawable *drawable)
+{
+ const Babl *format;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), -1);
+
+ format = gegl_buffer_get_format (drawable->private->buffer);
+
+ return gimp_babl_format_get_precision (format);
+}
+
+gboolean
+gimp_drawable_is_rgb (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ return (gimp_drawable_get_base_type (drawable) == GIMP_RGB);
+}
+
+gboolean
+gimp_drawable_is_gray (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ return (gimp_drawable_get_base_type (drawable) == GIMP_GRAY);
+}
+
+gboolean
+gimp_drawable_is_indexed (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ return (gimp_drawable_get_base_type (drawable) == GIMP_INDEXED);
+}
+
+const Babl *
+gimp_drawable_get_component_format (GimpDrawable *drawable,
+ GimpChannelType channel)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ switch (channel)
+ {
+ case GIMP_CHANNEL_RED:
+ return gimp_babl_component_format (GIMP_RGB,
+ gimp_drawable_get_precision (drawable),
+ RED);
+
+ case GIMP_CHANNEL_GREEN:
+ return gimp_babl_component_format (GIMP_RGB,
+ gimp_drawable_get_precision (drawable),
+ GREEN);
+
+ case GIMP_CHANNEL_BLUE:
+ return gimp_babl_component_format (GIMP_RGB,
+ gimp_drawable_get_precision (drawable),
+ BLUE);
+
+ case GIMP_CHANNEL_ALPHA:
+ return gimp_babl_component_format (GIMP_RGB,
+ gimp_drawable_get_precision (drawable),
+ ALPHA);
+
+ case GIMP_CHANNEL_GRAY:
+ return gimp_babl_component_format (GIMP_GRAY,
+ gimp_drawable_get_precision (drawable),
+ GRAY);
+
+ case GIMP_CHANNEL_INDEXED:
+ return babl_format ("Y u8"); /* will extract grayscale, the best
+ * we can do here */
+ }
+
+ return NULL;
+}
+
+gint
+gimp_drawable_get_component_index (GimpDrawable *drawable,
+ GimpChannelType channel)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), -1);
+
+ switch (channel)
+ {
+ case GIMP_CHANNEL_RED: return RED;
+ case GIMP_CHANNEL_GREEN: return GREEN;
+ case GIMP_CHANNEL_BLUE: return BLUE;
+ case GIMP_CHANNEL_GRAY: return GRAY;
+ case GIMP_CHANNEL_INDEXED: return INDEXED;
+ case GIMP_CHANNEL_ALPHA:
+ switch (gimp_drawable_get_base_type (drawable))
+ {
+ case GIMP_RGB: return ALPHA;
+ case GIMP_GRAY: return ALPHA_G;
+ case GIMP_INDEXED: return ALPHA_I;
+ }
+ }
+
+ return -1;
+}
+
+const guchar *
+gimp_drawable_get_colormap (GimpDrawable *drawable)
+{
+ GimpImage *image;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ return image ? gimp_image_get_colormap (image) : NULL;
+}
+
+void
+gimp_drawable_start_paint (GimpDrawable *drawable)
+{
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+
+ if (drawable->private->paint_count == 0)
+ {
+ GeglBuffer *buffer = gimp_drawable_get_buffer (drawable);
+
+ g_return_if_fail (buffer != NULL);
+ g_return_if_fail (drawable->private->paint_buffer == NULL);
+ g_return_if_fail (drawable->private->paint_copy_region == NULL);
+ g_return_if_fail (drawable->private->paint_update_region == NULL);
+
+ drawable->private->paint_buffer = gimp_gegl_buffer_dup (buffer);
+ }
+
+ drawable->private->paint_count++;
+}
+
+gboolean
+gimp_drawable_end_paint (GimpDrawable *drawable)
+{
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (drawable->private->paint_count > 0, FALSE);
+
+ if (drawable->private->paint_count == 1)
+ {
+ result = gimp_drawable_flush_paint (drawable);
+
+ g_clear_object (&drawable->private->paint_buffer);
+ }
+
+ drawable->private->paint_count--;
+
+ return result;
+}
+
+gboolean
+gimp_drawable_flush_paint (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (drawable->private->paint_count > 0, FALSE);
+
+ if (drawable->private->paint_copy_region)
+ {
+ GeglBuffer *buffer;
+ gint n_rects;
+ gint i;
+
+ buffer = GIMP_DRAWABLE_GET_CLASS (drawable)->get_buffer (drawable);
+
+ g_return_val_if_fail (buffer != NULL, FALSE);
+ g_return_val_if_fail (drawable->private->paint_buffer != NULL, FALSE);
+
+ n_rects = cairo_region_num_rectangles (
+ drawable->private->paint_copy_region);
+
+ for (i = 0; i < n_rects; i++)
+ {
+ GeglRectangle rect;
+
+ cairo_region_get_rectangle (drawable->private->paint_copy_region,
+ i, (cairo_rectangle_int_t *) &rect);
+
+ gimp_gegl_buffer_copy (
+ drawable->private->paint_buffer, &rect, GEGL_ABYSS_NONE,
+ buffer, NULL);
+ }
+
+ g_clear_pointer (&drawable->private->paint_copy_region,
+ cairo_region_destroy);
+
+ n_rects = cairo_region_num_rectangles (
+ drawable->private->paint_update_region);
+
+ for (i = 0; i < n_rects; i++)
+ {
+ GeglRectangle rect;
+
+ cairo_region_get_rectangle (drawable->private->paint_update_region,
+ i, (cairo_rectangle_int_t *) &rect);
+
+ g_signal_emit (drawable, gimp_drawable_signals[UPDATE], 0,
+ rect.x, rect.y, rect.width, rect.height);
+ }
+
+ g_clear_pointer (&drawable->private->paint_update_region,
+ cairo_region_destroy);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+gimp_drawable_is_painting (GimpDrawable *drawable)
+{
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+
+ return drawable->private->paint_count > 0;
+}