diff options
Diffstat (limited to 'app/widgets/gimpviewrenderer.c')
-rw-r--r-- | app/widgets/gimpviewrenderer.c | 1336 |
1 files changed, 1336 insertions, 0 deletions
diff --git a/app/widgets/gimpviewrenderer.c b/app/widgets/gimpviewrenderer.c new file mode 100644 index 0000000..d67ea05 --- /dev/null +++ b/app/widgets/gimpviewrenderer.c @@ -0,0 +1,1336 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpviewrenderer.c + * Copyright (C) 2003 Michael Natterer <mitch@gimp.org> + * Copyright (C) 2007 Sven Neumann <sven@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <gegl.h> +#include <gtk/gtk.h> + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpconfig/gimpconfig.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "config/gimpcoreconfig.h" + +#include "gegl/gimp-gegl-loops.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpmarshal.h" +#include "core/gimptempbuf.h" +#include "core/gimpviewable.h" + +#include "gimprender.h" +#include "gimpviewrenderer.h" +#include "gimpviewrenderer-utils.h" +#include "gimpwidgets-utils.h" + +#include "gimp-priorities.h" + + +#define RGB_EPSILON 1e-6 + +enum +{ + UPDATE, + LAST_SIGNAL +}; + + +struct _GimpViewRendererPrivate +{ + cairo_pattern_t *pattern; + GdkPixbuf *pixbuf; + gchar *bg_icon_name; + + GimpColorConfig *color_config; + GimpColorTransform *profile_transform; + + gboolean needs_render; + guint idle_id; +}; + + +static void gimp_view_renderer_dispose (GObject *object); +static void gimp_view_renderer_finalize (GObject *object); + +static gboolean gimp_view_renderer_idle_update (GimpViewRenderer *renderer); +static void gimp_view_renderer_real_set_context (GimpViewRenderer *renderer, + GimpContext *context); +static void gimp_view_renderer_real_invalidate (GimpViewRenderer *renderer); +static void gimp_view_renderer_real_draw (GimpViewRenderer *renderer, + GtkWidget *widget, + cairo_t *cr, + gint available_width, + gint available_height); +static void gimp_view_renderer_real_render (GimpViewRenderer *renderer, + GtkWidget *widget); + +static void gimp_view_renderer_size_changed (GimpViewRenderer *renderer, + GimpViewable *viewable); +static void gimp_view_renderer_profile_changed (GimpViewRenderer *renderer, + GimpViewable *viewable); +static void gimp_view_renderer_config_notify (GObject *config, + const GParamSpec *pspec, + GimpViewRenderer *renderer); + +static void gimp_view_render_temp_buf_to_surface (GimpViewRenderer *renderer, + GtkWidget *widget, + GimpTempBuf *temp_buf, + gint temp_buf_x, + gint temp_buf_y, + gint channel, + GimpViewBG inside_bg, + GimpViewBG outside_bg, + cairo_surface_t *surface, + gint dest_width, + gint dest_height); + +static cairo_pattern_t * + gimp_view_renderer_create_background (GimpViewRenderer *renderer, + GtkWidget *widget); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpViewRenderer, gimp_view_renderer, G_TYPE_OBJECT) + +#define parent_class gimp_view_renderer_parent_class + +static guint renderer_signals[LAST_SIGNAL] = { 0 }; + +static GimpRGB black_color; +static GimpRGB white_color; +static GimpRGB green_color; +static GimpRGB red_color; + + +static void +gimp_view_renderer_class_init (GimpViewRendererClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + renderer_signals[UPDATE] = + g_signal_new ("update", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpViewRendererClass, update), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->dispose = gimp_view_renderer_dispose; + object_class->finalize = gimp_view_renderer_finalize; + + klass->update = NULL; + klass->set_context = gimp_view_renderer_real_set_context; + klass->invalidate = gimp_view_renderer_real_invalidate; + klass->draw = gimp_view_renderer_real_draw; + klass->render = gimp_view_renderer_real_render; + + klass->frame = NULL; + klass->frame_left = 0; + klass->frame_right = 0; + klass->frame_top = 0; + klass->frame_bottom = 0; + + gimp_rgba_set (&black_color, 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&white_color, 1.0, 1.0, 1.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&green_color, 0.0, 0.94, 0.0, GIMP_OPACITY_OPAQUE); + gimp_rgba_set (&red_color, 1.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE); +} + +static void +gimp_view_renderer_init (GimpViewRenderer *renderer) +{ + renderer->priv = gimp_view_renderer_get_instance_private (renderer); + + renderer->viewable = NULL; + + renderer->dot_for_dot = TRUE; + + renderer->border_type = GIMP_VIEW_BORDER_BLACK; + renderer->border_color = black_color; + + renderer->size = -1; + + renderer->priv->needs_render = TRUE; +} + +static void +gimp_view_renderer_dispose (GObject *object) +{ + GimpViewRenderer *renderer = GIMP_VIEW_RENDERER (object); + + if (renderer->viewable) + gimp_view_renderer_set_viewable (renderer, NULL); + + if (renderer->context) + gimp_view_renderer_set_context (renderer, NULL); + + if (renderer->priv->color_config) + gimp_view_renderer_set_color_config (renderer, NULL); + + gimp_view_renderer_remove_idle (renderer); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gimp_view_renderer_finalize (GObject *object) +{ + GimpViewRenderer *renderer = GIMP_VIEW_RENDERER (object); + + g_clear_pointer (&renderer->priv->pattern, cairo_pattern_destroy); + g_clear_pointer (&renderer->surface, cairo_surface_destroy); + g_clear_object (&renderer->priv->pixbuf); + g_clear_pointer (&renderer->priv->bg_icon_name, g_free); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GimpViewRenderer * +gimp_view_renderer_new_internal (GimpContext *context, + GType viewable_type, + gboolean is_popup) +{ + GimpViewRenderer *renderer; + + renderer = g_object_new (gimp_view_renderer_type_from_viewable_type (viewable_type), + NULL); + + renderer->viewable_type = viewable_type; + renderer->is_popup = is_popup ? TRUE : FALSE; + + if (context) + gimp_view_renderer_set_context (renderer, context); + + return renderer; +} + + +/* public functions */ + +GimpViewRenderer * +gimp_view_renderer_new (GimpContext *context, + GType viewable_type, + gint size, + gint border_width, + gboolean is_popup) +{ + GimpViewRenderer *renderer; + + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (g_type_is_a (viewable_type, GIMP_TYPE_VIEWABLE), NULL); + g_return_val_if_fail (size > 0 && + size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (border_width >= 0 && + border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, NULL); + + renderer = gimp_view_renderer_new_internal (context, viewable_type, + is_popup); + + gimp_view_renderer_set_size (renderer, size, border_width); + gimp_view_renderer_remove_idle (renderer); + + return renderer; +} + +GimpViewRenderer * +gimp_view_renderer_new_full (GimpContext *context, + GType viewable_type, + gint width, + gint height, + gint border_width, + gboolean is_popup) +{ + GimpViewRenderer *renderer; + + g_return_val_if_fail (context == NULL || GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (g_type_is_a (viewable_type, GIMP_TYPE_VIEWABLE), NULL); + g_return_val_if_fail (width > 0 && + width <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (height > 0 && + height <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE, NULL); + g_return_val_if_fail (border_width >= 0 && + border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, NULL); + + renderer = gimp_view_renderer_new_internal (context, viewable_type, + is_popup); + + gimp_view_renderer_set_size_full (renderer, width, height, border_width); + gimp_view_renderer_remove_idle (renderer); + + return renderer; +} + +void +gimp_view_renderer_set_context (GimpViewRenderer *renderer, + GimpContext *context) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (context == NULL || GIMP_IS_CONTEXT (context)); + + if (context != renderer->context) + { + GIMP_VIEW_RENDERER_GET_CLASS (renderer)->set_context (renderer, + context); + + if (renderer->viewable) + gimp_view_renderer_invalidate (renderer); + } +} + +static void +gimp_view_renderer_weak_notify (GimpViewRenderer *renderer, + GimpViewable *viewable) +{ + renderer->viewable = NULL; + + gimp_view_renderer_update_idle (renderer); +} + +void +gimp_view_renderer_set_viewable (GimpViewRenderer *renderer, + GimpViewable *viewable) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (viewable == NULL || GIMP_IS_VIEWABLE (viewable)); + + if (viewable) + g_return_if_fail (g_type_is_a (G_TYPE_FROM_INSTANCE (viewable), + renderer->viewable_type)); + + if (viewable == renderer->viewable) + return; + + g_clear_pointer (&renderer->surface, cairo_surface_destroy); + g_clear_object (&renderer->priv->pixbuf); + + gimp_view_renderer_free_color_transform (renderer); + + if (renderer->viewable) + { + g_object_weak_unref (G_OBJECT (renderer->viewable), + (GWeakNotify) gimp_view_renderer_weak_notify, + renderer); + + g_signal_handlers_disconnect_by_func (renderer->viewable, + G_CALLBACK (gimp_view_renderer_invalidate), + renderer); + + g_signal_handlers_disconnect_by_func (renderer->viewable, + G_CALLBACK (gimp_view_renderer_size_changed), + renderer); + + if (GIMP_IS_COLOR_MANAGED (renderer->viewable)) + g_signal_handlers_disconnect_by_func (renderer->viewable, + G_CALLBACK (gimp_view_renderer_profile_changed), + renderer); + } + + renderer->viewable = viewable; + + if (renderer->viewable) + { + g_object_weak_ref (G_OBJECT (renderer->viewable), + (GWeakNotify) gimp_view_renderer_weak_notify, + renderer); + + g_signal_connect_swapped (renderer->viewable, + "invalidate-preview", + G_CALLBACK (gimp_view_renderer_invalidate), + renderer); + + g_signal_connect_swapped (renderer->viewable, + "size-changed", + G_CALLBACK (gimp_view_renderer_size_changed), + renderer); + + if (GIMP_IS_COLOR_MANAGED (renderer->viewable)) + g_signal_connect_swapped (renderer->viewable, + "profile-changed", + G_CALLBACK (gimp_view_renderer_profile_changed), + renderer); + + if (renderer->size != -1) + gimp_view_renderer_set_size (renderer, renderer->size, + renderer->border_width); + + gimp_view_renderer_invalidate (renderer); + } + else + { + gimp_view_renderer_update_idle (renderer); + } +} + +void +gimp_view_renderer_set_size (GimpViewRenderer *renderer, + gint view_size, + gint border_width) +{ + gint width; + gint height; + + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE); + g_return_if_fail (border_width >= 0 && + border_width <= GIMP_VIEW_MAX_BORDER_WIDTH); + + renderer->size = view_size; + + if (renderer->viewable) + { + gimp_viewable_get_preview_size (renderer->viewable, + view_size, + renderer->is_popup, + renderer->dot_for_dot, + &width, &height); + } + else + { + width = view_size; + height = view_size; + } + + gimp_view_renderer_set_size_full (renderer, width, height, border_width); +} + +void +gimp_view_renderer_set_size_full (GimpViewRenderer *renderer, + gint width, + gint height, + gint border_width) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (width > 0 && + width <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE); + g_return_if_fail (height > 0 && + height <= GIMP_VIEWABLE_MAX_PREVIEW_SIZE); + g_return_if_fail (border_width >= 0 && + border_width <= GIMP_VIEW_MAX_BORDER_WIDTH); + + if (width != renderer->width || + height != renderer->height || + border_width != renderer->border_width) + { + renderer->width = width; + renderer->height = height; + renderer->border_width = border_width; + + g_clear_pointer (&renderer->surface, cairo_surface_destroy); + + if (renderer->viewable) + gimp_view_renderer_invalidate (renderer); + } +} + +void +gimp_view_renderer_set_dot_for_dot (GimpViewRenderer *renderer, + gboolean dot_for_dot) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + if (dot_for_dot != renderer->dot_for_dot) + { + renderer->dot_for_dot = dot_for_dot ? TRUE: FALSE; + + if (renderer->size != -1) + gimp_view_renderer_set_size (renderer, renderer->size, + renderer->border_width); + + gimp_view_renderer_invalidate (renderer); + } +} + +void +gimp_view_renderer_set_border_type (GimpViewRenderer *renderer, + GimpViewBorderType border_type) +{ + GimpRGB *border_color = &black_color; + + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + renderer->border_type = border_type; + + switch (border_type) + { + case GIMP_VIEW_BORDER_BLACK: + border_color = &black_color; + break; + case GIMP_VIEW_BORDER_WHITE: + border_color = &white_color; + break; + case GIMP_VIEW_BORDER_GREEN: + border_color = &green_color; + break; + case GIMP_VIEW_BORDER_RED: + border_color = &red_color; + break; + } + + gimp_view_renderer_set_border_color (renderer, border_color); +} + +void +gimp_view_renderer_set_border_color (GimpViewRenderer *renderer, + const GimpRGB *color) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (color != NULL); + + if (gimp_rgb_distance (&renderer->border_color, color) > RGB_EPSILON) + { + renderer->border_color = *color; + + gimp_view_renderer_update_idle (renderer); + } +} + +void +gimp_view_renderer_set_background (GimpViewRenderer *renderer, + const gchar *icon_name) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + if (renderer->priv->bg_icon_name) + g_free (renderer->priv->bg_icon_name); + + renderer->priv->bg_icon_name = g_strdup (icon_name); + + g_clear_object (&renderer->priv->pattern); +} + +void +gimp_view_renderer_set_color_config (GimpViewRenderer *renderer, + GimpColorConfig *color_config) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (color_config == NULL || GIMP_IS_COLOR_CONFIG (color_config)); + + if (color_config != renderer->priv->color_config) + { + if (renderer->priv->color_config) + g_signal_handlers_disconnect_by_func (renderer->priv->color_config, + gimp_view_renderer_config_notify, + renderer); + + g_set_object (&renderer->priv->color_config, color_config); + + if (renderer->priv->color_config) + g_signal_connect (renderer->priv->color_config, "notify", + G_CALLBACK (gimp_view_renderer_config_notify), + renderer); + + gimp_view_renderer_config_notify (G_OBJECT (renderer->priv->color_config), + NULL, renderer); + } +} + +void +gimp_view_renderer_invalidate (GimpViewRenderer *renderer) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + if (renderer->priv->idle_id) + { + g_source_remove (renderer->priv->idle_id); + renderer->priv->idle_id = 0; + } + + GIMP_VIEW_RENDERER_GET_CLASS (renderer)->invalidate (renderer); + + renderer->priv->idle_id = + g_idle_add_full (GIMP_PRIORITY_VIEWABLE_IDLE, + (GSourceFunc) gimp_view_renderer_idle_update, + renderer, NULL); +} + +void +gimp_view_renderer_update (GimpViewRenderer *renderer) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + if (renderer->priv->idle_id) + { + g_source_remove (renderer->priv->idle_id); + renderer->priv->idle_id = 0; + } + + g_signal_emit (renderer, renderer_signals[UPDATE], 0); +} + +void +gimp_view_renderer_update_idle (GimpViewRenderer *renderer) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + if (renderer->priv->idle_id) + g_source_remove (renderer->priv->idle_id); + + renderer->priv->idle_id = + g_idle_add_full (GIMP_PRIORITY_VIEWABLE_IDLE, + (GSourceFunc) gimp_view_renderer_idle_update, + renderer, NULL); +} + +void +gimp_view_renderer_remove_idle (GimpViewRenderer *renderer) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + if (renderer->priv->idle_id) + { + g_source_remove (renderer->priv->idle_id); + renderer->priv->idle_id = 0; + } +} + +void +gimp_view_renderer_draw (GimpViewRenderer *renderer, + GtkWidget *widget, + cairo_t *cr, + gint available_width, + gint available_height) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (cr != NULL); + + if (G_UNLIKELY (renderer->context == NULL)) + g_warning ("%s: renderer->context is NULL", G_STRFUNC); + + if (! gtk_widget_is_drawable (widget)) + return; + + if (renderer->viewable) + { + cairo_save (cr); + + GIMP_VIEW_RENDERER_GET_CLASS (renderer)->draw (renderer, widget, cr, + available_width, + available_height); + + cairo_restore (cr); + } + else + { + GimpViewableClass *viewable_class; + + viewable_class = g_type_class_ref (renderer->viewable_type); + + gimp_view_renderer_render_icon (renderer, + widget, + viewable_class->default_icon_name); + renderer->priv->needs_render = FALSE; + + g_type_class_unref (viewable_class); + + gimp_view_renderer_real_draw (renderer, widget, cr, + available_width, + available_height); + } + + if (renderer->border_width > 0) + { + gint width = renderer->width + renderer->border_width; + gint height = renderer->height + renderer->border_width; + gdouble x, y; + + cairo_set_line_width (cr, renderer->border_width); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + gimp_cairo_set_source_rgb (cr, &renderer->border_color); + + x = (available_width - width) / 2.0; + y = (available_height - height) / 2.0; + + cairo_rectangle (cr, x, y, width, height); + cairo_stroke (cr); + } +} + + +/* private functions */ + +static gboolean +gimp_view_renderer_idle_update (GimpViewRenderer *renderer) +{ + renderer->priv->idle_id = 0; + + gimp_view_renderer_update (renderer); + + return FALSE; +} + +static void +gimp_view_renderer_real_set_context (GimpViewRenderer *renderer, + GimpContext *context) +{ + if (renderer->context && + renderer->priv->color_config == + renderer->context->gimp->config->color_management) + { + gimp_view_renderer_set_color_config (renderer, NULL); + } + + g_set_object (&renderer->context, context); + + if (renderer->context && + renderer->priv->color_config == NULL) + { + gimp_view_renderer_set_color_config (renderer, + renderer->context->gimp->config->color_management); + } +} + +static void +gimp_view_renderer_real_invalidate (GimpViewRenderer *renderer) +{ + renderer->priv->needs_render = TRUE; +} + +static void +gimp_view_renderer_real_draw (GimpViewRenderer *renderer, + GtkWidget *widget, + cairo_t *cr, + gint available_width, + gint available_height) +{ + if (renderer->priv->needs_render) + { + GIMP_VIEW_RENDERER_GET_CLASS (renderer)->render (renderer, widget); + + renderer->priv->needs_render = FALSE; + } + + if (renderer->priv->pixbuf) + { + gint width = gdk_pixbuf_get_width (renderer->priv->pixbuf); + gint height = gdk_pixbuf_get_height (renderer->priv->pixbuf); + gint x, y; + + if (renderer->priv->bg_icon_name) + { + if (! renderer->priv->pattern) + { + renderer->priv->pattern = + gimp_view_renderer_create_background (renderer, widget); + } + + cairo_set_source (cr, renderer->priv->pattern); + cairo_paint (cr); + } + + x = (available_width - width) / 2; + y = (available_height - height) / 2; + + gdk_cairo_set_source_pixbuf (cr, renderer->priv->pixbuf, x, y); + cairo_rectangle (cr, x, y, width, height); + cairo_fill (cr); + } + else if (renderer->surface) + { + cairo_content_t content = cairo_surface_get_content (renderer->surface); + gint width = renderer->width; + gint height = renderer->height; + gint offset_x = (available_width - width) / 2; + gint offset_y = (available_height - height) / 2; + + cairo_translate (cr, offset_x, offset_y); + + cairo_rectangle (cr, 0, 0, width, height); + + if (content == CAIRO_CONTENT_COLOR_ALPHA) + { + if (! renderer->priv->pattern) + renderer->priv->pattern = + gimp_cairo_checkerboard_create (cr, GIMP_CHECK_SIZE_SM, + gimp_render_light_check_color (), + gimp_render_dark_check_color ()); + + cairo_set_source (cr, renderer->priv->pattern); + cairo_fill_preserve (cr); + } + + cairo_set_source_surface (cr, renderer->surface, 0, 0); + cairo_fill (cr); + + cairo_translate (cr, - offset_x, - offset_y); + } +} + +static void +gimp_view_renderer_real_render (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + GdkPixbuf *pixbuf; + GimpTempBuf *temp_buf; + const gchar *icon_name; + + pixbuf = gimp_viewable_get_pixbuf (renderer->viewable, + renderer->context, + renderer->width, + renderer->height); + if (pixbuf) + { + gimp_view_renderer_render_pixbuf (renderer, widget, pixbuf); + return; + } + + temp_buf = gimp_viewable_get_preview (renderer->viewable, + renderer->context, + renderer->width, + renderer->height); + if (temp_buf) + { + gimp_view_renderer_render_temp_buf_simple (renderer, widget, temp_buf); + return; + } + + icon_name = gimp_viewable_get_icon_name (renderer->viewable); + gimp_view_renderer_render_icon (renderer, widget, icon_name); +} + +static void +gimp_view_renderer_size_changed (GimpViewRenderer *renderer, + GimpViewable *viewable) +{ + if (renderer->size != -1) + gimp_view_renderer_set_size (renderer, renderer->size, + renderer->border_width); + + gimp_view_renderer_invalidate (renderer); +} + +static void +gimp_view_renderer_profile_changed (GimpViewRenderer *renderer, + GimpViewable *viewable) +{ + gimp_view_renderer_free_color_transform (renderer); +} + +static void +gimp_view_renderer_config_notify (GObject *config, + const GParamSpec *pspec, + GimpViewRenderer *renderer) +{ + gimp_view_renderer_free_color_transform (renderer); +} + + +/* protected functions */ + +void +gimp_view_renderer_render_temp_buf_simple (GimpViewRenderer *renderer, + GtkWidget *widget, + GimpTempBuf *temp_buf) +{ + gint temp_buf_x = 0; + gint temp_buf_y = 0; + gint temp_buf_width; + gint temp_buf_height; + + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (temp_buf != NULL); + + temp_buf_width = gimp_temp_buf_get_width (temp_buf); + temp_buf_height = gimp_temp_buf_get_height (temp_buf); + + if (temp_buf_width < renderer->width) + temp_buf_x = (renderer->width - temp_buf_width) / 2; + + if (temp_buf_height < renderer->height) + temp_buf_y = (renderer->height - temp_buf_height) / 2; + + gimp_view_renderer_render_temp_buf (renderer, widget, temp_buf, + temp_buf_x, temp_buf_y, + -1, + GIMP_VIEW_BG_CHECKS, + GIMP_VIEW_BG_WHITE); +} + +void +gimp_view_renderer_render_temp_buf (GimpViewRenderer *renderer, + GtkWidget *widget, + GimpTempBuf *temp_buf, + gint temp_buf_x, + gint temp_buf_y, + gint channel, + GimpViewBG inside_bg, + GimpViewBG outside_bg) +{ + g_clear_object (&renderer->priv->pixbuf); + + if (! renderer->surface) + renderer->surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, + renderer->width, + renderer->height); + + gimp_view_render_temp_buf_to_surface (renderer, + widget, + temp_buf, + temp_buf_x, + temp_buf_y, + channel, + inside_bg, + outside_bg, + renderer->surface, + renderer->width, + renderer->height); +} + + +void +gimp_view_renderer_render_pixbuf (GimpViewRenderer *renderer, + GtkWidget *widget, + GdkPixbuf *pixbuf) +{ + GimpColorTransform *transform; + const Babl *format; + + g_clear_pointer (&renderer->surface, cairo_surface_destroy); + + format = gimp_pixbuf_get_format (pixbuf); + + transform = gimp_view_renderer_get_color_transform (renderer, widget, + format, format); + + if (transform) + { + GdkPixbuf *new; + gint width = gdk_pixbuf_get_width (pixbuf); + gint height = gdk_pixbuf_get_height (pixbuf); + gsize src_stride = gdk_pixbuf_get_rowstride (pixbuf); + guchar *src = gdk_pixbuf_get_pixels (pixbuf); + gsize dest_stride; + guchar *dest; + gint i; + + new = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + gdk_pixbuf_get_has_alpha (pixbuf), + 8, width, height); + + dest_stride = gdk_pixbuf_get_rowstride (new); + dest = gdk_pixbuf_get_pixels (new); + + for (i = 0; i < height; i++) + { + gimp_color_transform_process_pixels (transform, + format, src, + format, dest, + width); + + src += src_stride; + dest += dest_stride; + } + + g_clear_object (&renderer->priv->pixbuf); + renderer->priv->pixbuf = new; + } + else + { + g_set_object (&renderer->priv->pixbuf, pixbuf); + } +} + +void +gimp_view_renderer_render_icon (GimpViewRenderer *renderer, + GtkWidget *widget, + const gchar *icon_name) +{ + GdkPixbuf *pixbuf; + gint width; + gint height; + + + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (icon_name != NULL); + + g_clear_object (&renderer->priv->pixbuf); + g_clear_pointer (&renderer->surface, cairo_surface_destroy); + + pixbuf = gimp_widget_load_icon (widget, icon_name, + MIN (renderer->width, renderer->height)); + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + if (width > renderer->width || height > renderer->height) + { + GdkPixbuf *scaled_pixbuf; + + gimp_viewable_calc_preview_size (width, height, + renderer->width, renderer->height, + TRUE, 1.0, 1.0, + &width, &height, + NULL); + + scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf, + width, height, + GDK_INTERP_BILINEAR); + + g_object_unref (pixbuf); + pixbuf = scaled_pixbuf; + } + + renderer->priv->pixbuf = pixbuf; +} + +GimpColorTransform * +gimp_view_renderer_get_color_transform (GimpViewRenderer *renderer, + GtkWidget *widget, + const Babl *src_format, + const Babl *dest_format) +{ + GimpColorProfile *profile; + + g_return_val_if_fail (GIMP_IS_VIEW_RENDERER (renderer), NULL); + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + g_return_val_if_fail (src_format != NULL, NULL); + g_return_val_if_fail (dest_format != NULL, NULL); + + if (renderer->priv->profile_transform) + return renderer->priv->profile_transform; + + if (! renderer->priv->color_config) + { + g_printerr ("EEK\n"); + return NULL; + } + + if (GIMP_IS_COLOR_MANAGED (renderer->viewable)) + { + GimpColorManaged *managed = GIMP_COLOR_MANAGED (renderer->viewable); + + profile = gimp_color_managed_get_color_profile (managed); + } + else + { + static GimpColorProfile *srgb_profile = NULL; + + if (G_UNLIKELY (! srgb_profile)) + srgb_profile = gimp_color_profile_new_rgb_srgb (); + + profile = srgb_profile; + } + + renderer->priv->profile_transform = + gimp_widget_get_color_transform (widget, + renderer->priv->color_config, + profile, + src_format, + dest_format); + + return renderer->priv->profile_transform; +} + +void +gimp_view_renderer_free_color_transform (GimpViewRenderer *renderer) +{ + g_return_if_fail (GIMP_IS_VIEW_RENDERER (renderer)); + + g_clear_object (&renderer->priv->profile_transform); + + gimp_view_renderer_invalidate (renderer); +} + +/* private functions */ + +static void +gimp_view_render_temp_buf_to_surface (GimpViewRenderer *renderer, + GtkWidget *widget, + GimpTempBuf *temp_buf, + gint temp_buf_x, + gint temp_buf_y, + gint channel, + GimpViewBG inside_bg, + GimpViewBG outside_bg, + cairo_surface_t *surface, + gint surface_width, + gint surface_height) +{ + cairo_t *cr; + gint x, y; + gint width, height; + const Babl *temp_buf_format; + gint temp_buf_width; + gint temp_buf_height; + + g_return_if_fail (temp_buf != NULL); + g_return_if_fail (surface != NULL); + + temp_buf_format = gimp_temp_buf_get_format (temp_buf); + temp_buf_width = gimp_temp_buf_get_width (temp_buf); + temp_buf_height = gimp_temp_buf_get_height (temp_buf); + + /* Here are the different cases this functions handles correctly: + * 1) Offset temp_buf which does not necessarily cover full image area + * 2) Color conversion of temp_buf if it is gray and image is color + * 3) Background check buffer for transparent temp_bufs + * 4) Using the optional "channel" argument, one channel can be extracted + * from a multi-channel temp_buf and composited as a grayscale + * Prereqs: + * 1) Grayscale temp_bufs have bytes == {1, 2} + * 2) Color temp_bufs have bytes == {3, 4} + * 3) If image is gray, then temp_buf should have bytes == {1, 2} + */ + + cr = cairo_create (surface); + + if (outside_bg == GIMP_VIEW_BG_CHECKS || + inside_bg == GIMP_VIEW_BG_CHECKS) + { + if (! renderer->priv->pattern) + renderer->priv->pattern = + gimp_cairo_checkerboard_create (cr, GIMP_CHECK_SIZE_SM, + gimp_render_light_check_color (), + gimp_render_dark_check_color ()); + } + + switch (outside_bg) + { + case GIMP_VIEW_BG_CHECKS: + cairo_set_source (cr, renderer->priv->pattern); + break; + + case GIMP_VIEW_BG_WHITE: + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + break; + } + + cairo_paint (cr); + + if (! gimp_rectangle_intersect (0, 0, + surface_width, surface_height, + temp_buf_x, temp_buf_y, + temp_buf_width, temp_buf_height, + &x, &y, + &width, &height)) + { + cairo_destroy (cr); + return; + } + + if (inside_bg != outside_bg && + babl_format_has_alpha (temp_buf_format) && channel == -1) + { + cairo_rectangle (cr, x, y, width, height); + + switch (inside_bg) + { + case GIMP_VIEW_BG_CHECKS: + cairo_set_source (cr, renderer->priv->pattern); + break; + + case GIMP_VIEW_BG_WHITE: + cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); + break; + } + + cairo_fill (cr); + } + + if (babl_format_has_alpha (temp_buf_format) && channel == -1) + { + GimpColorTransform *transform; + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + cairo_surface_t *alpha_surface; + + alpha_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + width, height); + + src_buffer = gimp_temp_buf_create_buffer (temp_buf); + dest_buffer = gimp_cairo_surface_create_buffer (alpha_surface); + + transform = + gimp_view_renderer_get_color_transform (renderer, widget, + gegl_buffer_get_format (src_buffer), + gegl_buffer_get_format (dest_buffer)); + + if (transform) + { + gimp_color_transform_process_buffer (transform, + src_buffer, + GEGL_RECTANGLE (x - temp_buf_x, + y - temp_buf_y, + width, height), + dest_buffer, + GEGL_RECTANGLE (0, 0, 0, 0)); + } + else + { + gimp_gegl_buffer_copy (src_buffer, + GEGL_RECTANGLE (x - temp_buf_x, + y - temp_buf_y, + width, height), + GEGL_ABYSS_NONE, + dest_buffer, + GEGL_RECTANGLE (0, 0, 0, 0)); + } + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + cairo_surface_mark_dirty (alpha_surface); + + cairo_translate (cr, x, y); + cairo_rectangle (cr, 0, 0, width, height); + cairo_set_source_surface (cr, alpha_surface, 0, 0); + cairo_fill (cr); + + cairo_surface_destroy (alpha_surface); + } + else if (channel == -1) + { + GimpColorTransform *transform; + GeglBuffer *src_buffer; + GeglBuffer *dest_buffer; + + cairo_surface_flush (surface); + + src_buffer = gimp_temp_buf_create_buffer (temp_buf); + dest_buffer = gimp_cairo_surface_create_buffer (surface); + + transform = + gimp_view_renderer_get_color_transform (renderer, widget, + gegl_buffer_get_format (src_buffer), + gegl_buffer_get_format (dest_buffer)); + + if (transform) + { + gimp_color_transform_process_buffer (transform, + src_buffer, + GEGL_RECTANGLE (x - temp_buf_x, + y - temp_buf_y, + width, height), + dest_buffer, + GEGL_RECTANGLE (x, y, 0, 0)); + } + else + { + gimp_gegl_buffer_copy (src_buffer, + GEGL_RECTANGLE (x - temp_buf_x, + y - temp_buf_y, + width, height), + GEGL_ABYSS_NONE, + dest_buffer, + GEGL_RECTANGLE (x, y, 0, 0)); + } + + g_object_unref (src_buffer); + g_object_unref (dest_buffer); + + cairo_surface_mark_dirty (surface); + } + else + { + const Babl *fish; + const guchar *src; + guchar *dest; + gint dest_stride; + gint bytes; + gint rowstride; + gint i; + + cairo_surface_flush (surface); + + bytes = babl_format_get_bytes_per_pixel (temp_buf_format); + rowstride = temp_buf_width * bytes; + + src = gimp_temp_buf_get_data (temp_buf) + ((y - temp_buf_y) * rowstride + + (x - temp_buf_x) * bytes); + + dest = cairo_image_surface_get_data (surface); + dest_stride = cairo_image_surface_get_stride (surface); + + dest += y * dest_stride + x * 4; + + fish = babl_fish (temp_buf_format, + babl_format ("cairo-RGB24")); + + for (i = y; i < (y + height); i++) + { + const guchar *s = src; + guchar *d = dest; + gint j; + + for (j = x; j < (x + width); j++, d += 4, s += bytes) + { + if (bytes > 2) + { + guchar pixel[4] = { s[channel], s[channel], s[channel], 255 }; + + babl_process (fish, pixel, d, 1); + } + else + { + guchar pixel[2] = { s[channel], 255 }; + + babl_process (fish, pixel, d, 1); + } + } + + src += rowstride; + dest += dest_stride; + } + + cairo_surface_mark_dirty (surface); + } + + cairo_destroy (cr); +} + +/* This function creates a background pattern from a named icon + * if renderer->priv->bg_icon_name is set. + */ +static cairo_pattern_t * +gimp_view_renderer_create_background (GimpViewRenderer *renderer, + GtkWidget *widget) +{ + cairo_pattern_t *pattern = NULL; + + if (renderer->priv->bg_icon_name) + { + cairo_surface_t *surface; + GdkPixbuf *pixbuf; + + pixbuf = gimp_widget_load_icon (widget, + renderer->priv->bg_icon_name, + 64); + surface = gimp_cairo_surface_create_from_pixbuf (pixbuf); + g_object_unref (pixbuf); + + pattern = cairo_pattern_create_for_surface (surface); + cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT); + + cairo_surface_destroy (surface); + } + + return pattern; +} |