/* 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 . */ #include "config.h" #include #include #include #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, ©_x, ©_y, ©_width, ©_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; }