/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * 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 "libpikabase/pikabase.h" #include "libpikacolor/pikacolor.h" #include "libpikamath/pikamath.h" #include "core-types.h" #include "gegl/pika-babl.h" #include "gegl/pika-gegl-apply-operation.h" #include "gegl/pika-gegl-loops.h" #include "gegl/pika-gegl-utils.h" #include "pika-memsize.h" #include "pika-utils.h" #include "pikachannel.h" #include "pikacontext.h" #include "pikadrawable-combine.h" #include "pikadrawable-fill.h" #include "pikadrawable-floating-selection.h" #include "pikadrawable-preview.h" #include "pikadrawable-private.h" #include "pikadrawable-shadow.h" #include "pikadrawable-transform.h" #include "pikafilterstack.h" #include "pikaimage.h" #include "pikaimage-colormap.h" #include "pikaimage-undo-push.h" #include "pikamarshal.h" #include "pikapickable.h" #include "pikaprogress.h" #include "pika-log.h" #include "pika-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 pika_color_managed_iface_init (PikaColorManagedInterface *iface); static void pika_pickable_iface_init (PikaPickableInterface *iface); static void pika_drawable_dispose (GObject *object); static void pika_drawable_finalize (GObject *object); static void pika_drawable_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_drawable_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static gint64 pika_drawable_get_memsize (PikaObject *object, gint64 *gui_size); static gboolean pika_drawable_get_size (PikaViewable *viewable, gint *width, gint *height); static void pika_drawable_preview_freeze (PikaViewable *viewable); static void pika_drawable_preview_thaw (PikaViewable *viewable); static GeglNode * pika_drawable_get_node (PikaFilter *filter); static void pika_drawable_removed (PikaItem *item); static PikaItem * pika_drawable_duplicate (PikaItem *item, GType new_type); static void pika_drawable_scale (PikaItem *item, gint new_width, gint new_height, gint new_offset_x, gint new_offset_y, PikaInterpolationType interp_type, PikaProgress *progress); static void pika_drawable_resize (PikaItem *item, PikaContext *context, PikaFillType fill_type, gint new_width, gint new_height, gint offset_x, gint offset_y); static void pika_drawable_flip (PikaItem *item, PikaContext *context, PikaOrientationType flip_type, gdouble axis, gboolean clip_result); static void pika_drawable_rotate (PikaItem *item, PikaContext *context, PikaRotationType rotate_type, gdouble center_x, gdouble center_y, gboolean clip_result); static void pika_drawable_transform (PikaItem *item, PikaContext *context, const PikaMatrix3 *matrix, PikaTransformDirection direction, PikaInterpolationType interpolation_type, PikaTransformResize clip_result, PikaProgress *progress); static const guint8 * pika_drawable_get_icc_profile (PikaColorManaged *managed, gsize *len); static PikaColorProfile * pika_drawable_get_color_profile (PikaColorManaged *managed); static void pika_drawable_profile_changed (PikaColorManaged *managed); static gboolean pika_drawable_get_pixel_at (PikaPickable *pickable, gint x, gint y, const Babl *format, gpointer pixel); static void pika_drawable_get_pixel_average (PikaPickable *pickable, const GeglRectangle *rect, const Babl *format, gpointer pixel); static void pika_drawable_real_update (PikaDrawable *drawable, gint x, gint y, gint width, gint height); static gint64 pika_drawable_real_estimate_memsize (PikaDrawable *drawable, PikaComponentType component_type, gint width, gint height); static void pika_drawable_real_update_all (PikaDrawable *drawable); static PikaComponentMask pika_drawable_real_get_active_mask (PikaDrawable *drawable); static gboolean pika_drawable_real_supports_alpha (PikaDrawable *drawable); static void pika_drawable_real_convert_type (PikaDrawable *drawable, PikaImage *dest_image, const Babl *new_format, PikaColorProfile *src_profile, PikaColorProfile *dest_profile, GeglDitherMethod layer_dither_type, GeglDitherMethod mask_dither_type, gboolean push_undo, PikaProgress *progress); static GeglBuffer * pika_drawable_real_get_buffer (PikaDrawable *drawable); static void pika_drawable_real_set_buffer (PikaDrawable *drawable, gboolean push_undo, const gchar *undo_desc, GeglBuffer *buffer, const GeglRectangle *bounds); static GeglRectangle pika_drawable_real_get_bounding_box (PikaDrawable *drawable); static void pika_drawable_real_push_undo (PikaDrawable *drawable, const gchar *undo_desc, GeglBuffer *buffer, gint x, gint y, gint width, gint height); static void pika_drawable_real_swap_pixels (PikaDrawable *drawable, GeglBuffer *buffer, gint x, gint y); static GeglNode * pika_drawable_real_get_source_node (PikaDrawable *drawable); static void pika_drawable_format_changed (PikaDrawable *drawable); static void pika_drawable_alpha_changed (PikaDrawable *drawable); G_DEFINE_TYPE_WITH_CODE (PikaDrawable, pika_drawable, PIKA_TYPE_ITEM, G_ADD_PRIVATE (PikaDrawable) G_IMPLEMENT_INTERFACE (PIKA_TYPE_COLOR_MANAGED, pika_color_managed_iface_init) G_IMPLEMENT_INTERFACE (PIKA_TYPE_PICKABLE, pika_pickable_iface_init)) #define parent_class pika_drawable_parent_class static guint pika_drawable_signals[LAST_SIGNAL] = { 0 }; static void pika_drawable_class_init (PikaDrawableClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass); PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass); PikaFilterClass *filter_class = PIKA_FILTER_CLASS (klass); PikaItemClass *item_class = PIKA_ITEM_CLASS (klass); pika_drawable_signals[UPDATE] = g_signal_new ("update", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaDrawableClass, update), NULL, NULL, pika_marshal_VOID__INT_INT_INT_INT, G_TYPE_NONE, 4, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT); pika_drawable_signals[FORMAT_CHANGED] = g_signal_new ("format-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaDrawableClass, format_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_drawable_signals[ALPHA_CHANGED] = g_signal_new ("alpha-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaDrawableClass, alpha_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_drawable_signals[BOUNDING_BOX_CHANGED] = g_signal_new ("bounding-box-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaDrawableClass, bounding_box_changed), NULL, NULL, NULL, G_TYPE_NONE, 0); object_class->dispose = pika_drawable_dispose; object_class->finalize = pika_drawable_finalize; object_class->set_property = pika_drawable_set_property; object_class->get_property = pika_drawable_get_property; pika_object_class->get_memsize = pika_drawable_get_memsize; viewable_class->get_size = pika_drawable_get_size; viewable_class->get_new_preview = pika_drawable_get_new_preview; viewable_class->get_new_pixbuf = pika_drawable_get_new_pixbuf; viewable_class->preview_freeze = pika_drawable_preview_freeze; viewable_class->preview_thaw = pika_drawable_preview_thaw; filter_class->get_node = pika_drawable_get_node; item_class->removed = pika_drawable_removed; item_class->duplicate = pika_drawable_duplicate; item_class->scale = pika_drawable_scale; item_class->resize = pika_drawable_resize; item_class->flip = pika_drawable_flip; item_class->rotate = pika_drawable_rotate; item_class->transform = pika_drawable_transform; klass->update = pika_drawable_real_update; klass->format_changed = NULL; klass->alpha_changed = NULL; klass->bounding_box_changed = NULL; klass->estimate_memsize = pika_drawable_real_estimate_memsize; klass->update_all = pika_drawable_real_update_all; klass->invalidate_boundary = NULL; klass->get_active_components = NULL; klass->get_active_mask = pika_drawable_real_get_active_mask; klass->supports_alpha = pika_drawable_real_supports_alpha; klass->convert_type = pika_drawable_real_convert_type; klass->apply_buffer = pika_drawable_real_apply_buffer; klass->get_buffer = pika_drawable_real_get_buffer; klass->set_buffer = pika_drawable_real_set_buffer; klass->get_bounding_box = pika_drawable_real_get_bounding_box; klass->push_undo = pika_drawable_real_push_undo; klass->swap_pixels = pika_drawable_real_swap_pixels; klass->get_source_node = pika_drawable_real_get_source_node; g_object_class_override_property (object_class, PROP_BUFFER, "buffer"); } static void pika_drawable_init (PikaDrawable *drawable) { drawable->private = pika_drawable_get_instance_private (drawable); drawable->private->filter_stack = pika_filter_stack_new (PIKA_TYPE_FILTER); } /* sorry for the evil casts */ static void pika_color_managed_iface_init (PikaColorManagedInterface *iface) { iface->get_icc_profile = pika_drawable_get_icc_profile; iface->get_color_profile = pika_drawable_get_color_profile; iface->profile_changed = pika_drawable_profile_changed; } static void pika_pickable_iface_init (PikaPickableInterface *iface) { iface->get_image = (PikaImage * (*) (PikaPickable *pickable)) pika_item_get_image; iface->get_format = (const Babl * (*) (PikaPickable *pickable)) pika_drawable_get_format; iface->get_format_with_alpha = (const Babl * (*) (PikaPickable *pickable)) pika_drawable_get_format_with_alpha; iface->get_buffer = (GeglBuffer * (*) (PikaPickable *pickable)) pika_drawable_get_buffer; iface->get_pixel_at = pika_drawable_get_pixel_at; iface->get_pixel_average = pika_drawable_get_pixel_average; } static void pika_drawable_dispose (GObject *object) { PikaDrawable *drawable = PIKA_DRAWABLE (object); if (pika_drawable_get_floating_sel (drawable)) pika_drawable_detach_floating_sel (drawable); G_OBJECT_CLASS (parent_class)->dispose (object); } static void pika_drawable_finalize (GObject *object) { PikaDrawable *drawable = PIKA_DRAWABLE (object); while (drawable->private->paint_count) pika_drawable_end_paint (drawable); g_clear_object (&drawable->private->buffer); g_clear_object (&drawable->private->format_profile); pika_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 pika_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 pika_drawable_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaDrawable *drawable = PIKA_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 pika_drawable_get_memsize (PikaObject *object, gint64 *gui_size) { PikaDrawable *drawable = PIKA_DRAWABLE (object); gint64 memsize = 0; memsize += pika_gegl_buffer_get_memsize (pika_drawable_get_buffer (drawable)); memsize += pika_gegl_buffer_get_memsize (drawable->private->shadow); return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size); } static gboolean pika_drawable_get_size (PikaViewable *viewable, gint *width, gint *height) { PikaItem *item = PIKA_ITEM (viewable); *width = pika_item_get_width (item); *height = pika_item_get_height (item); return TRUE; } static void pika_drawable_preview_freeze (PikaViewable *viewable) { PikaViewable *parent = pika_viewable_get_parent (viewable); if (! parent && pika_item_is_attached (PIKA_ITEM (viewable))) parent = PIKA_VIEWABLE (pika_item_get_image (PIKA_ITEM (viewable))); if (parent) pika_viewable_preview_freeze (parent); } static void pika_drawable_preview_thaw (PikaViewable *viewable) { PikaViewable *parent = pika_viewable_get_parent (viewable); if (! parent && pika_item_is_attached (PIKA_ITEM (viewable))) parent = PIKA_VIEWABLE (pika_item_get_image (PIKA_ITEM (viewable))); if (parent) pika_viewable_preview_thaw (parent); } static GeglNode * pika_drawable_get_node (PikaFilter *filter) { PikaDrawable *drawable = PIKA_DRAWABLE (filter); GeglNode *node; GeglNode *input; GeglNode *output; node = PIKA_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", "pika:normal", NULL); input = gegl_node_get_input_proxy (node, "input"); output = gegl_node_get_output_proxy (node, "output"); gegl_node_link (input, drawable->private->mode_node); gegl_node_link (drawable->private->mode_node, output); return node; } static void pika_drawable_removed (PikaItem *item) { PikaDrawable *drawable = PIKA_DRAWABLE (item); pika_drawable_free_shadow_buffer (drawable); if (PIKA_ITEM_CLASS (parent_class)->removed) PIKA_ITEM_CLASS (parent_class)->removed (item); } static PikaItem * pika_drawable_duplicate (PikaItem *item, GType new_type) { PikaItem *new_item; g_return_val_if_fail (g_type_is_a (new_type, PIKA_TYPE_DRAWABLE), NULL); new_item = PIKA_ITEM_CLASS (parent_class)->duplicate (item, new_type); if (PIKA_IS_DRAWABLE (new_item)) { PikaDrawable *drawable = PIKA_DRAWABLE (item); PikaDrawable *new_drawable = PIKA_DRAWABLE (new_item); GeglBuffer *new_buffer; new_buffer = pika_gegl_buffer_dup (pika_drawable_get_buffer (drawable)); pika_drawable_set_buffer (new_drawable, FALSE, NULL, new_buffer); g_object_unref (new_buffer); } return new_item; } static void pika_drawable_scale (PikaItem *item, gint new_width, gint new_height, gint new_offset_x, gint new_offset_y, PikaInterpolationType interpolation_type, PikaProgress *progress) { PikaDrawable *drawable = PIKA_DRAWABLE (item); GeglBuffer *new_buffer; new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, new_width, new_height), pika_drawable_get_format (drawable)); pika_gegl_apply_scale (pika_drawable_get_buffer (drawable), progress, C_("undo-type", "Scale"), new_buffer, interpolation_type, ((gdouble) new_width / pika_item_get_width (item)), ((gdouble) new_height / pika_item_get_height (item))); pika_drawable_set_buffer_full (drawable, pika_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 pika_drawable_resize (PikaItem *item, PikaContext *context, PikaFillType fill_type, gint new_width, gint new_height, gint offset_x, gint offset_y) { PikaDrawable *drawable = PIKA_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 == pika_item_get_width (item) && new_height == pika_item_get_height (item) && offset_x == 0 && offset_y == 0) return; new_offset_x = pika_item_get_offset_x (item) - offset_x; new_offset_y = pika_item_get_offset_y (item) - offset_y; intersect = pika_rectangle_intersect (pika_item_get_offset_x (item), pika_item_get_offset_y (item), pika_item_get_width (item), pika_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), pika_drawable_get_format (drawable)); if (! intersect || copy_width != new_width || copy_height != new_height) { /* Clear the new buffer if needed */ PikaRGB color; PikaPattern *pattern; pika_get_fill_params (context, fill_type, &color, &pattern, NULL); pika_drawable_fill_buffer (drawable, new_buffer, &color, pattern, 0, 0); } if (intersect && copy_width && copy_height) { /* Copy the pixels in the intersection */ pika_gegl_buffer_copy ( pika_drawable_get_buffer (drawable), GEGL_RECTANGLE (copy_x - pika_item_get_offset_x (item), copy_y - pika_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)); } pika_drawable_set_buffer_full (drawable, pika_item_is_attached (item) && drawable->private->push_resize_undo, NULL, new_buffer, GEGL_RECTANGLE (new_offset_x, new_offset_y, 0, 0), TRUE); g_object_unref (new_buffer); } static void pika_drawable_flip (PikaItem *item, PikaContext *context, PikaOrientationType flip_type, gdouble axis, gboolean clip_result) { PikaDrawable *drawable = PIKA_DRAWABLE (item); GeglBuffer *buffer; PikaColorProfile *buffer_profile; gint off_x, off_y; gint new_off_x, new_off_y; pika_item_get_offset (item, &off_x, &off_y); buffer = pika_drawable_transform_buffer_flip (drawable, context, pika_drawable_get_buffer (drawable), off_x, off_y, flip_type, axis, clip_result, &buffer_profile, &new_off_x, &new_off_y); if (buffer) { pika_drawable_transform_paste (drawable, buffer, buffer_profile, new_off_x, new_off_y, FALSE); g_object_unref (buffer); } } static void pika_drawable_rotate (PikaItem *item, PikaContext *context, PikaRotationType rotate_type, gdouble center_x, gdouble center_y, gboolean clip_result) { PikaDrawable *drawable = PIKA_DRAWABLE (item); GeglBuffer *buffer; PikaColorProfile *buffer_profile; gint off_x, off_y; gint new_off_x, new_off_y; pika_item_get_offset (item, &off_x, &off_y); buffer = pika_drawable_transform_buffer_rotate (drawable, context, pika_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) { pika_drawable_transform_paste (drawable, buffer, buffer_profile, new_off_x, new_off_y, FALSE); g_object_unref (buffer); } } static void pika_drawable_transform (PikaItem *item, PikaContext *context, const PikaMatrix3 *matrix, PikaTransformDirection direction, PikaInterpolationType interpolation_type, PikaTransformResize clip_result, PikaProgress *progress) { PikaDrawable *drawable = PIKA_DRAWABLE (item); GeglBuffer *buffer; PikaColorProfile *buffer_profile; gint off_x, off_y; gint new_off_x, new_off_y; pika_item_get_offset (item, &off_x, &off_y); buffer = pika_drawable_transform_buffer_affine (drawable, context, pika_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) { pika_drawable_transform_paste (drawable, buffer, buffer_profile, new_off_x, new_off_y, FALSE); g_object_unref (buffer); } } static const guint8 * pika_drawable_get_icc_profile (PikaColorManaged *managed, gsize *len) { PikaColorProfile *profile = pika_color_managed_get_color_profile (managed); return pika_color_profile_get_icc_profile (profile, len); } static PikaColorProfile * pika_drawable_get_color_profile (PikaColorManaged *managed) { PikaDrawable *drawable = PIKA_DRAWABLE (managed); const Babl *format = pika_drawable_get_format (drawable); if (! drawable->private->format_profile) drawable->private->format_profile = pika_babl_format_get_color_profile (format); return drawable->private->format_profile; } static void pika_drawable_profile_changed (PikaColorManaged *managed) { pika_viewable_invalidate_preview (PIKA_VIEWABLE (managed)); } static gboolean pika_drawable_get_pixel_at (PikaPickable *pickable, gint x, gint y, const Babl *format, gpointer pixel) { PikaDrawable *drawable = PIKA_DRAWABLE (pickable); /* do not make this a g_return_if_fail() */ if (x < 0 || x >= pika_item_get_width (PIKA_ITEM (drawable)) || y < 0 || y >= pika_item_get_height (PIKA_ITEM (drawable))) return FALSE; gegl_buffer_sample (pika_drawable_get_buffer (drawable), x, y, NULL, pixel, format, GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE); return TRUE; } static void pika_drawable_get_pixel_average (PikaPickable *pickable, const GeglRectangle *rect, const Babl *format, gpointer pixel) { PikaDrawable *drawable = PIKA_DRAWABLE (pickable); return pika_gegl_average_color (pika_drawable_get_buffer (drawable), rect, TRUE, GEGL_ABYSS_NONE, format, pixel); } static void pika_drawable_real_update (PikaDrawable *drawable, gint x, gint y, gint width, gint height) { pika_viewable_invalidate_preview (PIKA_VIEWABLE (drawable)); } static gint64 pika_drawable_real_estimate_memsize (PikaDrawable *drawable, PikaComponentType component_type, gint width, gint height) { PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable)); PikaTRCType trc = pika_drawable_get_trc (drawable); const Babl *format; format = pika_image_get_format (image, pika_drawable_get_base_type (drawable), pika_babl_precision (component_type, trc), pika_drawable_has_alpha (drawable), NULL); return (gint64) babl_format_get_bytes_per_pixel (format) * width * height; } static void pika_drawable_real_update_all (PikaDrawable *drawable) { pika_drawable_update (drawable, 0, 0, -1, -1); } static PikaComponentMask pika_drawable_real_get_active_mask (PikaDrawable *drawable) { /* Return all, because that skips the component mask op when painting */ return PIKA_COMPONENT_MASK_ALL; } static gboolean pika_drawable_real_supports_alpha (PikaDrawable *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 pika_drawable_real_convert_type (PikaDrawable *drawable, PikaImage *dest_image, const Babl *new_format, PikaColorProfile *src_profile, PikaColorProfile *dest_profile, GeglDitherMethod layer_dither_type, GeglDitherMethod mask_dither_type, gboolean push_undo, PikaProgress *progress) { GeglBuffer *dest_buffer; dest_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, pika_item_get_width (PIKA_ITEM (drawable)), pika_item_get_height (PIKA_ITEM (drawable))), new_format); pika_gegl_buffer_copy (pika_drawable_get_buffer (drawable), NULL, GEGL_ABYSS_NONE, dest_buffer, NULL); pika_drawable_set_buffer (drawable, push_undo, NULL, dest_buffer); g_object_unref (dest_buffer); } static GeglBuffer * pika_drawable_real_get_buffer (PikaDrawable *drawable) { return drawable->private->buffer; } static void pika_drawable_real_set_buffer (PikaDrawable *drawable, gboolean push_undo, const gchar *undo_desc, GeglBuffer *buffer, const GeglRectangle *bounds) { PikaItem *item = PIKA_ITEM (drawable); const Babl *old_format = NULL; gint old_has_alpha = -1; g_object_freeze_notify (G_OBJECT (drawable)); pika_drawable_invalidate_boundary (drawable); if (push_undo) pika_image_undo_push_drawable_mod (pika_item_get_image (item), undo_desc, drawable, FALSE); if (drawable->private->buffer) { old_format = pika_drawable_get_format (drawable); old_has_alpha = pika_drawable_has_alpha (drawable); } g_set_object (&drawable->private->buffer, buffer); if (pika_drawable_is_painting (drawable)) g_set_object (&drawable->private->paint_buffer, buffer); g_clear_object (&drawable->private->format_profile); if (drawable->private->buffer_source_node) gegl_node_set (drawable->private->buffer_source_node, "buffer", pika_drawable_get_buffer (drawable), NULL); pika_item_set_offset (item, bounds->x, bounds->y); pika_item_set_size (item, bounds->width ? bounds->width : gegl_buffer_get_width (buffer), bounds->height ? bounds->height : gegl_buffer_get_height (buffer)); pika_drawable_update_bounding_box (drawable); if (pika_drawable_get_format (drawable) != old_format) pika_drawable_format_changed (drawable); if (pika_drawable_has_alpha (drawable) != old_has_alpha) pika_drawable_alpha_changed (drawable); g_object_notify (G_OBJECT (drawable), "buffer"); g_object_thaw_notify (G_OBJECT (drawable)); } static GeglRectangle pika_drawable_real_get_bounding_box (PikaDrawable *drawable) { return gegl_node_get_bounding_box (pika_drawable_get_source_node (drawable)); } static void pika_drawable_real_push_undo (PikaDrawable *drawable, const gchar *undo_desc, GeglBuffer *buffer, gint x, gint y, gint width, gint height) { PikaImage *image; if (! buffer) { GeglBuffer *drawable_buffer = pika_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), pika_drawable_get_format (drawable)); pika_gegl_buffer_copy ( drawable_buffer, &drawable_rect, GEGL_ABYSS_NONE, buffer, GEGL_RECTANGLE (0, 0, 0, 0)); } else { g_object_ref (buffer); } image = pika_item_get_image (PIKA_ITEM (drawable)); pika_image_undo_push_drawable (image, undo_desc, drawable, buffer, x, y); g_object_unref (buffer); } static void pika_drawable_real_swap_pixels (PikaDrawable *drawable, GeglBuffer *buffer, gint x, gint y) { GeglBuffer *tmp; gint width = gegl_buffer_get_width (buffer); gint height = gegl_buffer_get_height (buffer); tmp = pika_gegl_buffer_dup (buffer); pika_gegl_buffer_copy (pika_drawable_get_buffer (drawable), GEGL_RECTANGLE (x, y, width, height), GEGL_ABYSS_NONE, buffer, GEGL_RECTANGLE (0, 0, 0, 0)); pika_gegl_buffer_copy (tmp, GEGL_RECTANGLE (0, 0, width, height), GEGL_ABYSS_NONE, pika_drawable_get_buffer (drawable), GEGL_RECTANGLE (x, y, 0, 0)); g_object_unref (tmp); pika_drawable_update (drawable, x, y, width, height); } static GeglNode * pika_drawable_real_get_source_node (PikaDrawable *drawable) { g_warn_if_fail (drawable->private->buffer_source_node == NULL); drawable->private->buffer_source_node = gegl_node_new_child (NULL, "operation", "pika:buffer-source-validate", "buffer", pika_drawable_get_buffer (drawable), NULL); return g_object_ref (drawable->private->buffer_source_node); } static void pika_drawable_format_changed (PikaDrawable *drawable) { g_signal_emit (drawable, pika_drawable_signals[FORMAT_CHANGED], 0); } static void pika_drawable_alpha_changed (PikaDrawable *drawable) { g_signal_emit (drawable, pika_drawable_signals[ALPHA_CHANGED], 0); } /* public functions */ PikaDrawable * pika_drawable_new (GType type, PikaImage *image, const gchar *name, gint offset_x, gint offset_y, gint width, gint height, const Babl *format) { PikaDrawable *drawable; GeglBuffer *buffer; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); g_return_val_if_fail (g_type_is_a (type, PIKA_TYPE_DRAWABLE), NULL); g_return_val_if_fail (width > 0 && height > 0, NULL); g_return_val_if_fail (format != NULL, NULL); drawable = PIKA_DRAWABLE (pika_item_new (type, image, name, offset_x, offset_y, width, height)); buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height), format); pika_drawable_set_buffer (drawable, FALSE, NULL, buffer); g_object_unref (buffer); pika_drawable_enable_resize_undo (drawable); return drawable; } gint64 pika_drawable_estimate_memsize (PikaDrawable *drawable, PikaComponentType component_type, gint width, gint height) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), 0); return PIKA_DRAWABLE_GET_CLASS (drawable)->estimate_memsize (drawable, component_type, width, height); } void pika_drawable_update (PikaDrawable *drawable, gint x, gint y, gint width, gint height) { g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); if (width < 0) { GeglRectangle bounding_box; bounding_box = pika_drawable_get_bounding_box (drawable); x = bounding_box.x; width = bounding_box.width; } if (height < 0) { GeglRectangle bounding_box; bounding_box = pika_drawable_get_bounding_box (drawable); y = bounding_box.y; height = bounding_box.height; } if (drawable->private->paint_count == 0) { g_signal_emit (drawable, pika_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, pika_item_get_width (PIKA_ITEM (drawable)), pika_item_get_height (PIKA_ITEM (drawable))))) { GeglRectangle aligned_rect; gegl_rectangle_align_to_buffer (&aligned_rect, &rect, pika_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 pika_drawable_update_all (PikaDrawable *drawable) { g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); PIKA_DRAWABLE_GET_CLASS (drawable)->update_all (drawable); } void pika_drawable_invalidate_boundary (PikaDrawable *drawable) { PikaDrawableClass *drawable_class; g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); drawable_class = PIKA_DRAWABLE_GET_CLASS (drawable); if (drawable_class->invalidate_boundary) drawable_class->invalidate_boundary (drawable); } void pika_drawable_get_active_components (PikaDrawable *drawable, gboolean *active) { PikaDrawableClass *drawable_class; g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (active != NULL); drawable_class = PIKA_DRAWABLE_GET_CLASS (drawable); if (drawable_class->get_active_components) drawable_class->get_active_components (drawable, active); } PikaComponentMask pika_drawable_get_active_mask (PikaDrawable *drawable) { PikaComponentMask mask; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), 0); mask = PIKA_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 (! pika_drawable_has_alpha (drawable)) { if (mask & ~PIKA_COMPONENT_MASK_ALPHA) mask |= PIKA_COMPONENT_MASK_ALPHA; else mask &= ~PIKA_COMPONENT_MASK_ALPHA; } return mask; } gboolean pika_drawable_supports_alpha (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE); return PIKA_DRAWABLE_GET_CLASS (drawable)->supports_alpha (drawable); } void pika_drawable_convert_type (PikaDrawable *drawable, PikaImage *dest_image, PikaImageBaseType new_base_type, PikaPrecision new_precision, gboolean new_has_alpha, PikaColorProfile *src_profile, PikaColorProfile *dest_profile, GeglDitherMethod layer_dither_type, GeglDitherMethod mask_dither_type, gboolean push_undo, PikaProgress *progress) { const Babl *old_format; const Babl *new_format; gint old_bits; gint new_bits; g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (PIKA_IS_IMAGE (dest_image)); g_return_if_fail (new_base_type != pika_drawable_get_base_type (drawable) || new_precision != pika_drawable_get_precision (drawable) || new_has_alpha != pika_drawable_has_alpha (drawable) || dest_profile); g_return_if_fail (src_profile == NULL || PIKA_IS_COLOR_PROFILE (src_profile)); g_return_if_fail (dest_profile == NULL || PIKA_IS_COLOR_PROFILE (dest_profile)); g_return_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress)); if (! pika_item_is_attached (PIKA_ITEM (drawable))) push_undo = FALSE; old_format = pika_drawable_get_format (drawable); new_format = pika_image_get_format (dest_image, new_base_type, new_precision, new_has_alpha, NULL /* handled by layer */); 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; } PIKA_DRAWABLE_GET_CLASS (drawable)->convert_type (drawable, dest_image, new_format, src_profile, dest_profile, layer_dither_type, mask_dither_type, push_undo, progress); if (progress) pika_progress_set_value (progress, 1.0); } void pika_drawable_apply_buffer (PikaDrawable *drawable, GeglBuffer *buffer, const GeglRectangle *buffer_region, gboolean push_undo, const gchar *undo_desc, gdouble opacity, PikaLayerMode mode, PikaLayerColorSpace blend_space, PikaLayerColorSpace composite_space, PikaLayerCompositeMode composite_mode, GeglBuffer *base_buffer, gint base_x, gint base_y) { g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (pika_item_is_attached (PIKA_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)); PIKA_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 * pika_drawable_get_buffer (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); if (drawable->private->paint_count == 0) return PIKA_DRAWABLE_GET_CLASS (drawable)->get_buffer (drawable); else return drawable->private->paint_buffer; } void pika_drawable_set_buffer (PikaDrawable *drawable, gboolean push_undo, const gchar *undo_desc, GeglBuffer *buffer) { g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (GEGL_IS_BUFFER (buffer)); if (! pika_item_is_attached (PIKA_ITEM (drawable))) push_undo = FALSE; pika_drawable_set_buffer_full (drawable, push_undo, undo_desc, buffer, NULL, TRUE); } void pika_drawable_set_buffer_full (PikaDrawable *drawable, gboolean push_undo, const gchar *undo_desc, GeglBuffer *buffer, const GeglRectangle *bounds, gboolean update) { PikaItem *item; GeglRectangle curr_bounds; g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (GEGL_IS_BUFFER (buffer)); item = PIKA_ITEM (drawable); if (! pika_item_is_attached (PIKA_ITEM (drawable))) push_undo = FALSE; if (! bounds) { pika_item_get_offset (PIKA_ITEM (drawable), &curr_bounds.x, &curr_bounds.y); curr_bounds.width = 0; curr_bounds.height = 0; bounds = &curr_bounds; } if (update && pika_drawable_get_buffer (drawable)) { GeglBuffer *old_buffer = pika_drawable_get_buffer (drawable); GeglRectangle old_extent; GeglRectangle new_extent; old_extent = *gegl_buffer_get_extent (old_buffer); old_extent.x += pika_item_get_offset_x (item); old_extent.y += pika_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)) pika_drawable_update (drawable, 0, 0, -1, -1); } g_object_freeze_notify (G_OBJECT (drawable)); PIKA_DRAWABLE_GET_CLASS (drawable)->set_buffer (drawable, push_undo, undo_desc, buffer, bounds); g_object_thaw_notify (G_OBJECT (drawable)); if (update) pika_drawable_update (drawable, 0, 0, -1, -1); } void pika_drawable_steal_buffer (PikaDrawable *drawable, PikaDrawable *src_drawable) { GeglBuffer *buffer; GeglBuffer *replacement_buffer; g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (PIKA_IS_DRAWABLE (src_drawable)); buffer = pika_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)); pika_drawable_set_buffer (src_drawable, FALSE, NULL, replacement_buffer); pika_drawable_set_buffer (drawable, FALSE, NULL, buffer); g_object_unref (replacement_buffer); g_object_unref (buffer); } void pika_drawable_set_format (PikaDrawable *drawable, const Babl *format, gboolean copy_buffer, gboolean push_undo) { PikaItem *item; GeglBuffer *buffer; g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (format != NULL); g_return_if_fail (format != pika_drawable_get_format (drawable)); g_return_if_fail (pika_babl_format_get_base_type (format) == pika_drawable_get_base_type (drawable)); g_return_if_fail (pika_babl_format_get_component_type (format) == pika_drawable_get_component_type (drawable)); g_return_if_fail (babl_format_has_alpha (format) == pika_drawable_has_alpha (drawable)); g_return_if_fail (push_undo == FALSE || copy_buffer == TRUE); item = PIKA_ITEM (drawable); if (! pika_item_is_attached (item)) push_undo = FALSE; if (push_undo) pika_image_undo_push_drawable_format (pika_item_get_image (item), NULL, drawable); buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, pika_item_get_width (item), pika_item_get_height (item)), format); if (copy_buffer) { gegl_buffer_set_format (buffer, pika_drawable_get_format (drawable)); pika_gegl_buffer_copy (pika_drawable_get_buffer (drawable), NULL, GEGL_ABYSS_NONE, buffer, NULL); gegl_buffer_set_format (buffer, NULL); } pika_drawable_set_buffer (drawable, FALSE, NULL, buffer); g_object_unref (buffer); } GeglNode * pika_drawable_get_source_node (PikaDrawable *drawable) { GeglNode *input; GeglNode *source; GeglNode *filter; GeglNode *output; g_return_val_if_fail (PIKA_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 = PIKA_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_link (input, source); } filter = pika_filter_stack_get_graph (PIKA_FILTER_STACK (drawable->private->filter_stack)); gegl_node_add_child (drawable->private->source_node, filter); gegl_node_link (source, filter); output = gegl_node_get_output_proxy (drawable->private->source_node, "output"); gegl_node_link (filter, output); if (pika_drawable_get_floating_sel (drawable)) _pika_drawable_add_floating_sel_filter (drawable); return drawable->private->source_node; } GeglNode * pika_drawable_get_mode_node (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); if (! drawable->private->mode_node) pika_filter_get_node (PIKA_FILTER (drawable)); return drawable->private->mode_node; } GeglRectangle pika_drawable_get_bounding_box (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), *GEGL_RECTANGLE (0, 0, 0, 0)); if (gegl_rectangle_is_empty (&drawable->private->bounding_box)) pika_drawable_update_bounding_box (drawable); return drawable->private->bounding_box; } gboolean pika_drawable_update_bounding_box (PikaDrawable *drawable) { GeglRectangle bounding_box; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE); bounding_box = PIKA_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++) { pika_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, pika_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++) { pika_drawable_update (drawable, diff_rects[i].x, diff_rects[i].y, diff_rects[i].width, diff_rects[i].height); } return TRUE; } return FALSE; } void pika_drawable_swap_pixels (PikaDrawable *drawable, GeglBuffer *buffer, gint x, gint y) { g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (GEGL_IS_BUFFER (buffer)); PIKA_DRAWABLE_GET_CLASS (drawable)->swap_pixels (drawable, buffer, x, y); } void pika_drawable_push_undo (PikaDrawable *drawable, const gchar *undo_desc, GeglBuffer *buffer, gint x, gint y, gint width, gint height) { PikaItem *item; g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); g_return_if_fail (buffer == NULL || GEGL_IS_BUFFER (buffer)); item = PIKA_ITEM (drawable); g_return_if_fail (pika_item_is_attached (item)); if (! buffer && ! pika_rectangle_intersect (x, y, width, height, 0, 0, pika_item_get_width (item), pika_item_get_height (item), &x, &y, &width, &height)) { g_warning ("%s: tried to push empty region", G_STRFUNC); return; } PIKA_DRAWABLE_GET_CLASS (drawable)->push_undo (drawable, undo_desc, buffer, x, y, width, height); } void pika_drawable_disable_resize_undo (PikaDrawable *drawable) { g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); drawable->private->push_resize_undo = FALSE; } void pika_drawable_enable_resize_undo (PikaDrawable *drawable) { g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); drawable->private->push_resize_undo = TRUE; } const Babl * pika_drawable_get_space (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); return babl_format_get_space (pika_drawable_get_format (drawable)); } const Babl * pika_drawable_get_format (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); return gegl_buffer_get_format (drawable->private->buffer); } const Babl * pika_drawable_get_format_with_alpha (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); return pika_image_get_format (pika_item_get_image (PIKA_ITEM (drawable)), pika_drawable_get_base_type (drawable), pika_drawable_get_precision (drawable), TRUE, pika_drawable_get_space (drawable)); } const Babl * pika_drawable_get_format_without_alpha (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); return pika_image_get_format (pika_item_get_image (PIKA_ITEM (drawable)), pika_drawable_get_base_type (drawable), pika_drawable_get_precision (drawable), FALSE, pika_drawable_get_space (drawable)); } PikaTRCType pika_drawable_get_trc (PikaDrawable *drawable) { const Babl *format; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE); format = gegl_buffer_get_format (drawable->private->buffer); return pika_babl_format_get_trc (format); } gboolean pika_drawable_has_alpha (PikaDrawable *drawable) { const Babl *format; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE); format = gegl_buffer_get_format (drawable->private->buffer); return babl_format_has_alpha (format); } PikaImageBaseType pika_drawable_get_base_type (PikaDrawable *drawable) { const Babl *format; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), -1); format = gegl_buffer_get_format (drawable->private->buffer); return pika_babl_format_get_base_type (format); } PikaComponentType pika_drawable_get_component_type (PikaDrawable *drawable) { const Babl *format; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), -1); format = gegl_buffer_get_format (drawable->private->buffer); return pika_babl_format_get_component_type (format); } PikaPrecision pika_drawable_get_precision (PikaDrawable *drawable) { const Babl *format; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), -1); format = gegl_buffer_get_format (drawable->private->buffer); return pika_babl_format_get_precision (format); } gboolean pika_drawable_is_rgb (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE); return (pika_drawable_get_base_type (drawable) == PIKA_RGB); } gboolean pika_drawable_is_gray (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE); return (pika_drawable_get_base_type (drawable) == PIKA_GRAY); } gboolean pika_drawable_is_indexed (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE); return (pika_drawable_get_base_type (drawable) == PIKA_INDEXED); } const Babl * pika_drawable_get_component_format (PikaDrawable *drawable, PikaChannelType channel) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); switch (channel) { case PIKA_CHANNEL_RED: return pika_babl_component_format (PIKA_RGB, pika_drawable_get_precision (drawable), RED); case PIKA_CHANNEL_GREEN: return pika_babl_component_format (PIKA_RGB, pika_drawable_get_precision (drawable), GREEN); case PIKA_CHANNEL_BLUE: return pika_babl_component_format (PIKA_RGB, pika_drawable_get_precision (drawable), BLUE); case PIKA_CHANNEL_ALPHA: return pika_babl_component_format (PIKA_RGB, pika_drawable_get_precision (drawable), ALPHA); case PIKA_CHANNEL_GRAY: return pika_babl_component_format (PIKA_GRAY, pika_drawable_get_precision (drawable), GRAY); case PIKA_CHANNEL_INDEXED: return babl_format ("Y u8"); /* will extract grayscale, the best * we can do here */ } return NULL; } gint pika_drawable_get_component_index (PikaDrawable *drawable, PikaChannelType channel) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), -1); switch (channel) { case PIKA_CHANNEL_RED: return RED; case PIKA_CHANNEL_GREEN: return GREEN; case PIKA_CHANNEL_BLUE: return BLUE; case PIKA_CHANNEL_GRAY: return GRAY; case PIKA_CHANNEL_INDEXED: return INDEXED; case PIKA_CHANNEL_ALPHA: switch (pika_drawable_get_base_type (drawable)) { case PIKA_RGB: return ALPHA; case PIKA_GRAY: return ALPHA_G; case PIKA_INDEXED: return ALPHA_I; } } return -1; } guchar * pika_drawable_get_colormap (PikaDrawable *drawable) { PikaImage *image; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL); image = pika_item_get_image (PIKA_ITEM (drawable)); return image ? pika_image_get_colormap (image) : NULL; } void pika_drawable_start_paint (PikaDrawable *drawable) { g_return_if_fail (PIKA_IS_DRAWABLE (drawable)); if (drawable->private->paint_count == 0) { GeglBuffer *buffer = pika_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 = pika_gegl_buffer_dup (buffer); } drawable->private->paint_count++; } gboolean pika_drawable_end_paint (PikaDrawable *drawable) { gboolean result = FALSE; g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE); g_return_val_if_fail (drawable->private->paint_count > 0, FALSE); if (drawable->private->paint_count == 1) { result = pika_drawable_flush_paint (drawable); g_clear_object (&drawable->private->paint_buffer); } drawable->private->paint_count--; return result; } gboolean pika_drawable_flush_paint (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_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 = PIKA_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); pika_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, pika_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 pika_drawable_is_painting (PikaDrawable *drawable) { g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE); return drawable->private->paint_count > 0; }