/* 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 * * pikaimageproxy.c * Copyright (C) 2019 Ell * * 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 "core-types.h" #include "gegl/pika-babl.h" #include "gegl/pika-gegl-loops.h" #include "pikaimage.h" #include "pikaimage-color-profile.h" #include "pikaimage-preview.h" #include "pikaimageproxy.h" #include "pikapickable.h" #include "pikaprojectable.h" #include "pikatempbuf.h" enum { PROP_0, PROP_IMAGE, PROP_SHOW_ALL, PROP_BUFFER }; struct _PikaImageProxyPrivate { PikaImage *image; gboolean show_all; GeglRectangle bounding_box; gboolean frozen; }; /* local function prototypes */ static void pika_image_proxy_pickable_iface_init (PikaPickableInterface *iface); static void pika_image_proxy_color_managed_iface_init (PikaColorManagedInterface *iface); static void pika_image_proxy_finalize (GObject *object); static void pika_image_proxy_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_image_proxy_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static gboolean pika_image_proxy_get_size (PikaViewable *viewable, gint *width, gint *height); static void pika_image_proxy_get_preview_size (PikaViewable *viewable, gint size, gboolean is_popup, gboolean dot_for_dot, gint *width, gint *height); static gboolean pika_image_proxy_get_popup_size (PikaViewable *viewable, gint width, gint height, gboolean dot_for_dot, gint *popup_width, gint *popup_height); static PikaTempBuf * pika_image_proxy_get_new_preview (PikaViewable *viewable, PikaContext *context, gint width, gint height); static GdkPixbuf * pika_image_proxy_get_new_pixbuf (PikaViewable *viewable, PikaContext *context, gint width, gint height); static gchar * pika_image_proxy_get_description (PikaViewable *viewable, gchar **tooltip); static void pika_image_proxy_flush (PikaPickable *pickable); static const Babl * pika_image_proxy_get_format (PikaPickable *pickable); static const Babl * pika_image_proxy_get_format_with_alpha (PikaPickable *pickable); static GeglBuffer * pika_image_proxy_get_buffer (PikaPickable *pickable); static gboolean pika_image_proxy_get_pixel_at (PikaPickable *pickable, gint x, gint y, const Babl *format, gpointer pixel); static gdouble pika_image_proxy_get_opacity_at (PikaPickable *pickable, gint x, gint y); static void pika_image_proxy_get_pixel_average (PikaPickable *pickable, const GeglRectangle *rect, const Babl *format, gpointer pixel); static void pika_image_proxy_pixel_to_rgb (PikaPickable *pickable, const Babl *format, gpointer pixel, PikaRGB *color); static void pika_image_proxy_rgb_to_pixel (PikaPickable *pickable, const PikaRGB *color, const Babl *format, gpointer pixel); static const guint8 * pika_image_proxy_get_icc_profile (PikaColorManaged *managed, gsize *len); static PikaColorProfile * pika_image_proxy_get_color_profile (PikaColorManaged *managed); static void pika_image_proxy_profile_changed (PikaColorManaged *managed); static void pika_image_proxy_image_frozen_notify (PikaImage *image, const GParamSpec *pspec, PikaImageProxy *image_proxy); static void pika_image_proxy_image_invalidate_preview (PikaImage *image, PikaImageProxy *image_proxy); static void pika_image_proxy_image_size_changed (PikaImage *image, PikaImageProxy *image_proxy); static void pika_image_proxy_image_bounds_changed (PikaImage *image, gint old_x, gint old_y, PikaImageProxy *image_proxy); static void pika_image_proxy_image_profile_changed (PikaImage *image, PikaImageProxy *image_proxy); static void pika_image_proxy_set_image (PikaImageProxy *image_proxy, PikaImage *image); static PikaPickable * pika_image_proxy_get_pickable (PikaImageProxy *image_proxy); static void pika_image_proxy_update_bounding_box (PikaImageProxy *image_proxy); static void pika_image_proxy_update_frozen (PikaImageProxy *image_proxy); G_DEFINE_TYPE_WITH_CODE (PikaImageProxy, pika_image_proxy, PIKA_TYPE_VIEWABLE, G_ADD_PRIVATE (PikaImageProxy) G_IMPLEMENT_INTERFACE (PIKA_TYPE_PICKABLE, pika_image_proxy_pickable_iface_init) G_IMPLEMENT_INTERFACE (PIKA_TYPE_COLOR_MANAGED, pika_image_proxy_color_managed_iface_init)) #define parent_class pika_image_proxy_parent_class /* private functions */ static void pika_image_proxy_class_init (PikaImageProxyClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass); object_class->finalize = pika_image_proxy_finalize; object_class->set_property = pika_image_proxy_set_property; object_class->get_property = pika_image_proxy_get_property; viewable_class->default_icon_name = "pika-image"; viewable_class->get_size = pika_image_proxy_get_size; viewable_class->get_preview_size = pika_image_proxy_get_preview_size; viewable_class->get_popup_size = pika_image_proxy_get_popup_size; viewable_class->get_new_preview = pika_image_proxy_get_new_preview; viewable_class->get_new_pixbuf = pika_image_proxy_get_new_pixbuf; viewable_class->get_description = pika_image_proxy_get_description; g_object_class_install_property (object_class, PROP_IMAGE, g_param_spec_object ("image", NULL, NULL, PIKA_TYPE_IMAGE, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_SHOW_ALL, g_param_spec_boolean ("show-all", NULL, NULL, FALSE, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_override_property (object_class, PROP_BUFFER, "buffer"); } static void pika_image_proxy_pickable_iface_init (PikaPickableInterface *iface) { iface->flush = pika_image_proxy_flush; iface->get_image = (gpointer) pika_image_proxy_get_image; iface->get_format = pika_image_proxy_get_format; iface->get_format_with_alpha = pika_image_proxy_get_format_with_alpha; iface->get_buffer = pika_image_proxy_get_buffer; iface->get_pixel_at = pika_image_proxy_get_pixel_at; iface->get_opacity_at = pika_image_proxy_get_opacity_at; iface->get_pixel_average = pika_image_proxy_get_pixel_average; iface->pixel_to_rgb = pika_image_proxy_pixel_to_rgb; iface->rgb_to_pixel = pika_image_proxy_rgb_to_pixel; } static void pika_image_proxy_color_managed_iface_init (PikaColorManagedInterface *iface) { iface->get_icc_profile = pika_image_proxy_get_icc_profile; iface->get_color_profile = pika_image_proxy_get_color_profile; iface->profile_changed = pika_image_proxy_profile_changed; } static void pika_image_proxy_init (PikaImageProxy *image_proxy) { image_proxy->priv = pika_image_proxy_get_instance_private (image_proxy); } static void pika_image_proxy_finalize (GObject *object) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (object); pika_image_proxy_set_image (image_proxy, NULL); G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_image_proxy_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (object); switch (property_id) { case PROP_IMAGE: pika_image_proxy_set_image (image_proxy, g_value_get_object (value)); break; case PROP_SHOW_ALL: pika_image_proxy_set_show_all (image_proxy, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_image_proxy_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (object); switch (property_id) { case PROP_IMAGE: g_value_set_object (value, pika_image_proxy_get_image (image_proxy)); break; case PROP_SHOW_ALL: g_value_set_boolean (value, pika_image_proxy_get_show_all (image_proxy)); break; case PROP_BUFFER: g_value_set_object (value, pika_pickable_get_buffer ( PIKA_PICKABLE (image_proxy))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static gboolean pika_image_proxy_get_size (PikaViewable *viewable, gint *width, gint *height) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (viewable); *width = image_proxy->priv->bounding_box.width; *height = image_proxy->priv->bounding_box.height; return TRUE; } static void pika_image_proxy_get_preview_size (PikaViewable *viewable, gint size, gboolean is_popup, gboolean dot_for_dot, gint *width, gint *height) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (viewable); PikaImage *image = image_proxy->priv->image; gdouble xres; gdouble yres; gint viewable_width; gint viewable_height; pika_image_get_resolution (image, &xres, &yres); pika_viewable_get_size (viewable, &viewable_width, &viewable_height); pika_viewable_calc_preview_size (viewable_width, viewable_height, size, size, dot_for_dot, xres, yres, width, height, NULL); } static gboolean pika_image_proxy_get_popup_size (PikaViewable *viewable, gint width, gint height, gboolean dot_for_dot, gint *popup_width, gint *popup_height) { gint viewable_width; gint viewable_height; pika_viewable_get_size (viewable, &viewable_width, &viewable_height); if (viewable_width > width || viewable_height > height) { gboolean scaling_up; pika_viewable_calc_preview_size (viewable_width, viewable_height, width * 2, height * 2, dot_for_dot, 1.0, 1.0, popup_width, popup_height, &scaling_up); if (scaling_up) { *popup_width = viewable_width; *popup_height = viewable_height; } return TRUE; } return FALSE; } static PikaTempBuf * pika_image_proxy_get_new_preview (PikaViewable *viewable, PikaContext *context, gint width, gint height) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (viewable); PikaImage *image = image_proxy->priv->image; PikaPickable *pickable; const Babl *format; GeglRectangle bounding_box; PikaTempBuf *buf; gdouble scale_x; gdouble scale_y; gdouble scale; pickable = pika_image_proxy_get_pickable (image_proxy); bounding_box = pika_image_proxy_get_bounding_box (image_proxy); scale_x = (gdouble) width / (gdouble) bounding_box.width; scale_y = (gdouble) height / (gdouble) bounding_box.height; scale = MIN (scale_x, scale_y); format = pika_image_get_preview_format (image); buf = pika_temp_buf_new (width, height, format); gegl_buffer_get (pika_pickable_get_buffer (pickable), GEGL_RECTANGLE (bounding_box.x * scale, bounding_box.y * scale, width, height), scale, pika_temp_buf_get_format (buf), pika_temp_buf_get_data (buf), GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP); return buf; } static GdkPixbuf * pika_image_proxy_get_new_pixbuf (PikaViewable *viewable, PikaContext *context, gint width, gint height) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (viewable); PikaImage *image = image_proxy->priv->image; PikaPickable *pickable; GeglRectangle bounding_box; GdkPixbuf *pixbuf; gdouble scale_x; gdouble scale_y; gdouble scale; PikaColorTransform *transform; pickable = pika_image_proxy_get_pickable (image_proxy); bounding_box = pika_image_proxy_get_bounding_box (image_proxy); scale_x = (gdouble) width / (gdouble) bounding_box.width; scale_y = (gdouble) height / (gdouble) bounding_box.height; scale = MIN (scale_x, scale_y); pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height); transform = pika_image_get_color_transform_to_srgb_u8 (image); if (transform) { PikaTempBuf *temp_buf; GeglBuffer *src_buf; GeglBuffer *dest_buf; temp_buf = pika_temp_buf_new (width, height, pika_pickable_get_format (pickable)); gegl_buffer_get (pika_pickable_get_buffer (pickable), GEGL_RECTANGLE (bounding_box.x * scale, bounding_box.y * scale, width, height), scale, pika_temp_buf_get_format (temp_buf), pika_temp_buf_get_data (temp_buf), GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP); src_buf = pika_temp_buf_create_buffer (temp_buf); dest_buf = pika_pixbuf_create_buffer (pixbuf); pika_temp_buf_unref (temp_buf); pika_color_transform_process_buffer (transform, src_buf, GEGL_RECTANGLE (0, 0, width, height), dest_buf, GEGL_RECTANGLE (0, 0, 0, 0)); g_object_unref (src_buf); g_object_unref (dest_buf); } else { gegl_buffer_get (pika_pickable_get_buffer (pickable), GEGL_RECTANGLE (bounding_box.x * scale, bounding_box.y * scale, width, height), scale, pika_pixbuf_get_format (pixbuf), gdk_pixbuf_get_pixels (pixbuf), gdk_pixbuf_get_rowstride (pixbuf), GEGL_ABYSS_CLAMP); } return pixbuf; } static gchar * pika_image_proxy_get_description (PikaViewable *viewable, gchar **tooltip) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (viewable); PikaImage *image = image_proxy->priv->image; if (tooltip) *tooltip = g_strdup (pika_image_get_display_path (image)); return g_strdup_printf ("%s-%d", pika_image_get_display_name (image), pika_image_get_id (image)); } static void pika_image_proxy_flush (PikaPickable *pickable) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable); PikaPickable *proxy_pickable; proxy_pickable = pika_image_proxy_get_pickable (image_proxy); pika_pickable_flush (proxy_pickable); } static const Babl * pika_image_proxy_get_format (PikaPickable *pickable) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable); PikaPickable *proxy_pickable; proxy_pickable = pika_image_proxy_get_pickable (image_proxy); return pika_pickable_get_format (proxy_pickable); } static const Babl * pika_image_proxy_get_format_with_alpha (PikaPickable *pickable) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable); PikaPickable *proxy_pickable; proxy_pickable = pika_image_proxy_get_pickable (image_proxy); return pika_pickable_get_format_with_alpha (proxy_pickable); } static GeglBuffer * pika_image_proxy_get_buffer (PikaPickable *pickable) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable); PikaPickable *proxy_pickable; proxy_pickable = pika_image_proxy_get_pickable (image_proxy); return pika_pickable_get_buffer (proxy_pickable); } static gboolean pika_image_proxy_get_pixel_at (PikaPickable *pickable, gint x, gint y, const Babl *format, gpointer pixel) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable); PikaPickable *proxy_pickable; proxy_pickable = pika_image_proxy_get_pickable (image_proxy); return pika_pickable_get_pixel_at (proxy_pickable, x, y, format, pixel); } static gdouble pika_image_proxy_get_opacity_at (PikaPickable *pickable, gint x, gint y) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable); PikaPickable *proxy_pickable; proxy_pickable = pika_image_proxy_get_pickable (image_proxy); return pika_pickable_get_opacity_at (proxy_pickable, x, y); } static void pika_image_proxy_get_pixel_average (PikaPickable *pickable, const GeglRectangle *rect, const Babl *format, gpointer pixel) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable); PikaPickable *proxy_pickable; proxy_pickable = pika_image_proxy_get_pickable (image_proxy); pika_pickable_get_pixel_average (proxy_pickable, rect, format, pixel); } static void pika_image_proxy_pixel_to_rgb (PikaPickable *pickable, const Babl *format, gpointer pixel, PikaRGB *color) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable); PikaPickable *proxy_pickable; proxy_pickable = pika_image_proxy_get_pickable (image_proxy); pika_pickable_pixel_to_rgb (proxy_pickable, format, pixel, color); } static void pika_image_proxy_rgb_to_pixel (PikaPickable *pickable, const PikaRGB *color, const Babl *format, gpointer pixel) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (pickable); PikaPickable *proxy_pickable; proxy_pickable = pika_image_proxy_get_pickable (image_proxy); pika_pickable_rgb_to_pixel (proxy_pickable, color, format, pixel); } static const guint8 * pika_image_proxy_get_icc_profile (PikaColorManaged *managed, gsize *len) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (managed); return pika_color_managed_get_icc_profile ( PIKA_COLOR_MANAGED (image_proxy->priv->image), len); } static PikaColorProfile * pika_image_proxy_get_color_profile (PikaColorManaged *managed) { PikaImageProxy *image_proxy = PIKA_IMAGE_PROXY (managed); return pika_color_managed_get_color_profile ( PIKA_COLOR_MANAGED (image_proxy->priv->image)); } static void pika_image_proxy_profile_changed (PikaColorManaged *managed) { pika_viewable_invalidate_preview (PIKA_VIEWABLE (managed)); } static void pika_image_proxy_image_frozen_notify (PikaImage *image, const GParamSpec *pspec, PikaImageProxy *image_proxy) { pika_image_proxy_update_frozen (image_proxy); } static void pika_image_proxy_image_invalidate_preview (PikaImage *image, PikaImageProxy *image_proxy) { pika_viewable_invalidate_preview (PIKA_VIEWABLE (image_proxy)); } static void pika_image_proxy_image_size_changed (PikaImage *image, PikaImageProxy *image_proxy) { pika_image_proxy_update_bounding_box (image_proxy); } static void pika_image_proxy_image_bounds_changed (PikaImage *image, gint old_x, gint old_y, PikaImageProxy *image_proxy) { pika_image_proxy_update_bounding_box (image_proxy); } static void pika_image_proxy_image_profile_changed (PikaImage *image, PikaImageProxy *image_proxy) { pika_color_managed_profile_changed (PIKA_COLOR_MANAGED (image_proxy)); } static void pika_image_proxy_set_image (PikaImageProxy *image_proxy, PikaImage *image) { if (image_proxy->priv->image) { g_signal_handlers_disconnect_by_func ( image_proxy->priv->image, pika_image_proxy_image_frozen_notify, image_proxy); g_signal_handlers_disconnect_by_func ( image_proxy->priv->image, pika_image_proxy_image_invalidate_preview, image_proxy); g_signal_handlers_disconnect_by_func ( image_proxy->priv->image, pika_image_proxy_image_size_changed, image_proxy); g_signal_handlers_disconnect_by_func ( image_proxy->priv->image, pika_image_proxy_image_bounds_changed, image_proxy); g_signal_handlers_disconnect_by_func ( image_proxy->priv->image, pika_image_proxy_image_profile_changed, image_proxy); g_object_unref (image_proxy->priv->image); } image_proxy->priv->image = image; if (image_proxy->priv->image) { g_object_ref (image_proxy->priv->image); g_signal_connect ( image_proxy->priv->image, "notify::frozen", G_CALLBACK (pika_image_proxy_image_frozen_notify), image_proxy); g_signal_connect ( image_proxy->priv->image, "invalidate-preview", G_CALLBACK (pika_image_proxy_image_invalidate_preview), image_proxy); g_signal_connect ( image_proxy->priv->image, "size-changed", G_CALLBACK (pika_image_proxy_image_size_changed), image_proxy); g_signal_connect ( image_proxy->priv->image, "bounds-changed", G_CALLBACK (pika_image_proxy_image_bounds_changed), image_proxy); g_signal_connect ( image_proxy->priv->image, "profile-changed", G_CALLBACK (pika_image_proxy_image_profile_changed), image_proxy); pika_image_proxy_update_bounding_box (image_proxy); pika_image_proxy_update_frozen (image_proxy); pika_viewable_invalidate_preview (PIKA_VIEWABLE (image_proxy)); } } static PikaPickable * pika_image_proxy_get_pickable (PikaImageProxy *image_proxy) { PikaImage *image = image_proxy->priv->image; if (! image_proxy->priv->show_all) return PIKA_PICKABLE (image); else return PIKA_PICKABLE (pika_image_get_projection (image)); } static void pika_image_proxy_update_bounding_box (PikaImageProxy *image_proxy) { PikaImage *image = image_proxy->priv->image; GeglRectangle bounding_box; if (pika_viewable_preview_is_frozen (PIKA_VIEWABLE (image_proxy))) return; if (! image_proxy->priv->show_all) { bounding_box.x = 0; bounding_box.y = 0; bounding_box.width = pika_image_get_width (image); bounding_box.height = pika_image_get_height (image); } else { bounding_box = pika_projectable_get_bounding_box ( PIKA_PROJECTABLE (image)); } if (! gegl_rectangle_equal (&bounding_box, &image_proxy->priv->bounding_box)) { image_proxy->priv->bounding_box = bounding_box; pika_viewable_size_changed (PIKA_VIEWABLE (image_proxy)); } } static void pika_image_proxy_update_frozen (PikaImageProxy *image_proxy) { gboolean frozen; frozen = pika_viewable_preview_is_frozen ( PIKA_VIEWABLE (image_proxy->priv->image)); if (frozen != image_proxy->priv->frozen) { image_proxy->priv->frozen = frozen; if (frozen) { pika_viewable_preview_freeze (PIKA_VIEWABLE (image_proxy)); } else { pika_viewable_preview_thaw (PIKA_VIEWABLE (image_proxy)); pika_image_proxy_update_bounding_box (image_proxy); } } } /* public functions */ PikaImageProxy * pika_image_proxy_new (PikaImage *image) { g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); return g_object_new (PIKA_TYPE_IMAGE_PROXY, "image", image, NULL); } PikaImage * pika_image_proxy_get_image (PikaImageProxy *image_proxy) { g_return_val_if_fail (PIKA_IS_IMAGE_PROXY (image_proxy), NULL); return image_proxy->priv->image; } void pika_image_proxy_set_show_all (PikaImageProxy *image_proxy, gboolean show_all) { g_return_if_fail (PIKA_IS_IMAGE_PROXY (image_proxy)); if (show_all != image_proxy->priv->show_all) { image_proxy->priv->show_all = show_all; pika_image_proxy_update_bounding_box (image_proxy); } } gboolean pika_image_proxy_get_show_all (PikaImageProxy *image_proxy) { g_return_val_if_fail (PIKA_IS_IMAGE_PROXY (image_proxy), FALSE); return image_proxy->priv->show_all; } GeglRectangle pika_image_proxy_get_bounding_box (PikaImageProxy *image_proxy) { g_return_val_if_fail (PIKA_IS_IMAGE_PROXY (image_proxy), *GEGL_RECTANGLE (0, 0, 0, 0)); return image_proxy->priv->bounding_box; }