Mercurial > hg > forks > geeqie
view src/pixbuf-renderer.c @ 1916:69ab167a50f5
do not mix autodetected and user-defined stereo mode
author | Vladimir Nadvornik <nadvornik@suse.cz> |
---|---|
date | Sat, 14 May 2011 21:53:45 +0200 |
parents | 25f23890dabe |
children | 465b904a7e05 |
line wrap: on
line source
/* * Geeqie * (C) 2006 John Ellis * Copyright (C) 2008 - 2010 The Geeqie Team * * Author: John Ellis * * This software is released under the GNU General Public License (GNU GPL). * Please read the included file COPYING for more information. * This software comes with no warranty of any kind, use at your own risk! */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> #include "main.h" #include "pixbuf-renderer.h" #include "renderer-tiles.h" #include "intl.h" #include "layout.h" #include <gtk/gtk.h> /* comment this out if not using this from within Geeqie * defining GQ_BUILD does these things: * - Sets the shift-click scroller pixbuf to a nice icon instead of a black box */ #define GQ_BUILD 1 #ifdef GQ_BUILD #include "main.h" #include "pixbuf_util.h" #include "exif.h" #else typedef enum { EXIF_ORIENTATION_UNKNOWN = 0, EXIF_ORIENTATION_TOP_LEFT = 1, EXIF_ORIENTATION_TOP_RIGHT = 2, EXIF_ORIENTATION_BOTTOM_RIGHT = 3, EXIF_ORIENTATION_BOTTOM_LEFT = 4, EXIF_ORIENTATION_LEFT_TOP = 5, EXIF_ORIENTATION_RIGHT_TOP = 6, EXIF_ORIENTATION_RIGHT_BOTTOM = 7, EXIF_ORIENTATION_LEFT_BOTTOM = 8 } ExifOrientationType; #endif /* default min and max zoom */ #define PR_ZOOM_MIN -32.0 #define PR_ZOOM_MAX 32.0 /* distance to drag mouse to disable image flip */ #define PR_DRAG_SCROLL_THRESHHOLD 4 /* increase pan rate when holding down shift */ #define PR_PAN_SHIFT_MULTIPLIER 6 /* scroller config */ #define PR_SCROLLER_UPDATES_PER_SEC 30 #define PR_SCROLLER_DEAD_ZONE 6 /* when scaling image to below this size, use nearest pixel for scaling * (below about 4, the other scale types become slow generating their conversion tables) */ #define PR_MIN_SCALE_SIZE 8 enum { SIGNAL_ZOOM = 0, SIGNAL_CLICKED, SIGNAL_SCROLL_NOTIFY, SIGNAL_RENDER_COMPLETE, SIGNAL_DRAG, SIGNAL_UPDATE_PIXEL, SIGNAL_COUNT }; enum { PROP_0, PROP_ZOOM_MIN, PROP_ZOOM_MAX, PROP_ZOOM_QUALITY, PROP_ZOOM_2PASS, PROP_ZOOM_EXPAND, PROP_DITHER_QUALITY, PROP_SCROLL_RESET, PROP_DELAY_FLIP, PROP_LOADING, PROP_COMPLETE, PROP_CACHE_SIZE_DISPLAY, PROP_CACHE_SIZE_TILES, PROP_WINDOW_FIT, PROP_WINDOW_LIMIT, PROP_WINDOW_LIMIT_VALUE, PROP_AUTOFIT_LIMIT, PROP_AUTOFIT_LIMIT_VALUE }; typedef enum { PR_ZOOM_NONE = 0, PR_ZOOM_FORCE = 1 << 0, PR_ZOOM_NEW = 1 << 1, PR_ZOOM_CENTER = 1 << 2, PR_ZOOM_INVALIDATE = 1 << 3, PR_ZOOM_LAZY = 1 << 4 /* wait with redraw for pixbuf_renderer_area_changed */ } PrZoomFlags; static guint signals[SIGNAL_COUNT] = { 0 }; static GtkEventBoxClass *parent_class = NULL; static void pixbuf_renderer_class_init(PixbufRendererClass *class); static void pixbuf_renderer_init(PixbufRenderer *pr); static void pixbuf_renderer_finalize(GObject *object); static void pixbuf_renderer_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void pixbuf_renderer_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static gboolean pixbuf_renderer_expose(GtkWidget *widget, GdkEventExpose *event); static void pr_scroller_timer_set(PixbufRenderer *pr, gboolean start); static void pr_source_tile_free_all(PixbufRenderer *pr); static void pr_zoom_sync(PixbufRenderer *pr, gdouble zoom, PrZoomFlags flags, gint px, gint py); static void pr_signals_connect(PixbufRenderer *pr); static void pr_size_cb(GtkWidget *widget, GtkAllocation *allocation, gpointer data); static void pixbuf_renderer_paint(PixbufRenderer *pr, GdkRectangle *area); static void pr_stereo_temp_disable(PixbufRenderer *pr, gboolean disable); /* *------------------------------------------------------------------- * Pixbuf Renderer object *------------------------------------------------------------------- */ GType pixbuf_renderer_get_type(void) { static GType pixbuf_renderer_type = 0; if (!pixbuf_renderer_type) { static const GTypeInfo pixbuf_renderer_info = { sizeof(PixbufRendererClass), /* class_size */ NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc)pixbuf_renderer_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof(PixbufRenderer), /* instance_size */ 0, /* n_preallocs */ (GInstanceInitFunc)pixbuf_renderer_init, /* instance_init */ NULL, /* value_table */ }; pixbuf_renderer_type = g_type_register_static(GTK_TYPE_EVENT_BOX, "PixbufRenderer", &pixbuf_renderer_info, 0); } return pixbuf_renderer_type; } static void pixbuf_renderer_class_init(PixbufRendererClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS(class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class); parent_class = g_type_class_peek_parent(class); gobject_class->set_property = pixbuf_renderer_set_property; gobject_class->get_property = pixbuf_renderer_get_property; gobject_class->finalize = pixbuf_renderer_finalize; widget_class->expose_event = pixbuf_renderer_expose; g_object_class_install_property(gobject_class, PROP_ZOOM_MIN, g_param_spec_double("zoom_min", "Zoom minimum", NULL, -1000.0, 1000.0, PR_ZOOM_MIN, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_ZOOM_MAX, g_param_spec_double("zoom_max", "Zoom maximum", NULL, -1000.0, 1000.0, PR_ZOOM_MIN, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_ZOOM_QUALITY, g_param_spec_uint("zoom_quality", "Zoom quality", NULL, GDK_INTERP_NEAREST, GDK_INTERP_HYPER, GDK_INTERP_BILINEAR, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_ZOOM_2PASS, g_param_spec_boolean("zoom_2pass", "2 pass zoom", NULL, TRUE, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_ZOOM_EXPAND, g_param_spec_boolean("zoom_expand", "Expand image in autozoom.", NULL, FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_DITHER_QUALITY, g_param_spec_uint("dither_quality", "Dither quality", NULL, GDK_RGB_DITHER_NONE, GDK_RGB_DITHER_MAX, GDK_RGB_DITHER_NORMAL, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_SCROLL_RESET, g_param_spec_uint("scroll_reset", "New image scroll reset", NULL, PR_SCROLL_RESET_TOPLEFT, PR_SCROLL_RESET_NOCHANGE, PR_SCROLL_RESET_TOPLEFT, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_DELAY_FLIP, g_param_spec_boolean("delay_flip", "Delay image update", NULL, FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_LOADING, g_param_spec_boolean("loading", "Image actively loading", NULL, FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_COMPLETE, g_param_spec_boolean("complete", "Image rendering complete", NULL, FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_CACHE_SIZE_DISPLAY, g_param_spec_uint("cache_display", "Display cache size MB", NULL, 0, 128, PR_CACHE_SIZE_DEFAULT, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_CACHE_SIZE_TILES, g_param_spec_uint("cache_tiles", "Tile cache count", "Number of tiles to retain in memory at any one time.", 0, 256, PR_CACHE_SIZE_DEFAULT, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_WINDOW_FIT, g_param_spec_boolean("window_fit", "Fit window to image size", NULL, FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_WINDOW_LIMIT, g_param_spec_boolean("window_limit", "Limit size of parent window", NULL, FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_WINDOW_LIMIT_VALUE, g_param_spec_uint("window_limit_value", "Size limit of parent window", NULL, 10, 150, 100, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_AUTOFIT_LIMIT, g_param_spec_boolean("autofit_limit", "Limit size of image when autofitting", NULL, FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(gobject_class, PROP_AUTOFIT_LIMIT_VALUE, g_param_spec_uint("autofit_limit_value", "Size limit of image when autofitting", NULL, 10, 150, 100, G_PARAM_READABLE | G_PARAM_WRITABLE)); signals[SIGNAL_ZOOM] = g_signal_new("zoom", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(PixbufRendererClass, zoom), NULL, NULL, g_cclosure_marshal_VOID__DOUBLE, G_TYPE_NONE, 1, G_TYPE_DOUBLE); signals[SIGNAL_CLICKED] = g_signal_new("clicked", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(PixbufRendererClass, clicked), NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, GDK_TYPE_EVENT); signals[SIGNAL_SCROLL_NOTIFY] = g_signal_new("scroll-notify", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(PixbufRendererClass, scroll_notify), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[SIGNAL_RENDER_COMPLETE] = g_signal_new("render-complete", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(PixbufRendererClass, render_complete), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[SIGNAL_DRAG] = g_signal_new("drag", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(PixbufRendererClass, drag), NULL, NULL, g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, GDK_TYPE_EVENT); signals[SIGNAL_UPDATE_PIXEL] = g_signal_new("update-pixel", G_OBJECT_CLASS_TYPE(gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(PixbufRendererClass, update_pixel), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void pixbuf_renderer_init(PixbufRenderer *pr) { GtkWidget *box; box = GTK_WIDGET(pr); pr->zoom_min = PR_ZOOM_MIN; pr->zoom_max = PR_ZOOM_MAX; pr->zoom_quality = GDK_INTERP_BILINEAR; pr->zoom_2pass = FALSE; pr->zoom = 1.0; pr->scale = 1.0; pr->aspect_ratio = 1.0; pr->dither_quality = GDK_RGB_DITHER_NORMAL; pr->scroll_reset = PR_SCROLL_RESET_TOPLEFT; pr->scroller_id = 0; pr->scroller_overlay = -1; pr->x_mouse = -1; pr->y_mouse = -1; pr->source_tiles_enabled = FALSE; pr->source_tiles = NULL; pr->orientation = 1; pr->norm_center_x = 0.5; pr->norm_center_y = 0.5; pr->stereo_mode = PR_STEREO_NONE; pr->renderer = (void *)renderer_tiles_new(pr); pr->renderer2 = NULL; gtk_widget_set_double_buffered(box, FALSE); g_signal_connect_after(G_OBJECT(box), "size_allocate", G_CALLBACK(pr_size_cb), pr); pr_signals_connect(pr); } static void pixbuf_renderer_finalize(GObject *object) { PixbufRenderer *pr; pr = PIXBUF_RENDERER(object); pr->renderer->free(pr->renderer); if (pr->renderer2) pr->renderer2->free(pr->renderer2); if (pr->pixbuf) g_object_unref(pr->pixbuf); pr_scroller_timer_set(pr, FALSE); pr_source_tile_free_all(pr); } PixbufRenderer *pixbuf_renderer_new(void) { return g_object_new(TYPE_PIXBUF_RENDERER, NULL); } static void pixbuf_renderer_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { PixbufRenderer *pr; pr = PIXBUF_RENDERER(object); switch (prop_id) { case PROP_ZOOM_MIN: pr->zoom_min = g_value_get_double(value); break; case PROP_ZOOM_MAX: pr->zoom_max = g_value_get_double(value); break; case PROP_ZOOM_QUALITY: pr->zoom_quality = g_value_get_uint(value); break; case PROP_ZOOM_2PASS: pr->zoom_2pass = g_value_get_boolean(value); break; case PROP_ZOOM_EXPAND: pr->zoom_expand = g_value_get_boolean(value); break; case PROP_DITHER_QUALITY: pr->dither_quality = g_value_get_uint(value); break; case PROP_SCROLL_RESET: pr->scroll_reset = g_value_get_uint(value); break; case PROP_DELAY_FLIP: pr->delay_flip = g_value_get_boolean(value); break; case PROP_LOADING: pr->loading = g_value_get_boolean(value); break; case PROP_COMPLETE: pr->complete = g_value_get_boolean(value); break; case PROP_CACHE_SIZE_DISPLAY: // pr->tile_cache_max = g_value_get_uint(value); break; case PROP_CACHE_SIZE_TILES: pr->source_tiles_cache_size = g_value_get_uint(value); break; case PROP_WINDOW_FIT: pr->window_fit = g_value_get_boolean(value); break; case PROP_WINDOW_LIMIT: pr->window_limit = g_value_get_boolean(value); break; case PROP_WINDOW_LIMIT_VALUE: pr->window_limit_size = g_value_get_uint(value); break; case PROP_AUTOFIT_LIMIT: pr->autofit_limit = g_value_get_boolean(value); break; case PROP_AUTOFIT_LIMIT_VALUE: pr->autofit_limit_size = g_value_get_uint(value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void pixbuf_renderer_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { PixbufRenderer *pr; pr = PIXBUF_RENDERER(object); switch (prop_id) { case PROP_ZOOM_MIN: g_value_set_double(value, pr->zoom_min); break; case PROP_ZOOM_MAX: g_value_set_double(value, pr->zoom_max); break; case PROP_ZOOM_QUALITY: g_value_set_uint(value, pr->zoom_quality); break; case PROP_ZOOM_2PASS: g_value_set_boolean(value, pr->zoom_2pass); break; case PROP_ZOOM_EXPAND: g_value_set_boolean(value, pr->zoom_expand); break; case PROP_DITHER_QUALITY: g_value_set_uint(value, pr->dither_quality); break; case PROP_SCROLL_RESET: g_value_set_uint(value, pr->scroll_reset); break; case PROP_DELAY_FLIP: g_value_set_boolean(value, pr->delay_flip); break; case PROP_LOADING: g_value_set_boolean(value, pr->loading); break; case PROP_COMPLETE: g_value_set_boolean(value, pr->complete); break; case PROP_CACHE_SIZE_DISPLAY: // g_value_set_uint(value, pr->tile_cache_max); break; case PROP_CACHE_SIZE_TILES: g_value_set_uint(value, pr->source_tiles_cache_size); break; case PROP_WINDOW_FIT: g_value_set_boolean(value, pr->window_fit); break; case PROP_WINDOW_LIMIT: g_value_set_boolean(value, pr->window_limit); break; case PROP_WINDOW_LIMIT_VALUE: g_value_set_uint(value, pr->window_limit_size); break; case PROP_AUTOFIT_LIMIT: g_value_set_boolean(value, pr->autofit_limit); break; case PROP_AUTOFIT_LIMIT_VALUE: g_value_set_uint(value, pr->autofit_limit_size); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static gboolean pixbuf_renderer_expose(GtkWidget *widget, GdkEventExpose *event) { #if GTK_CHECK_VERSION(2,20,0) if (gtk_widget_is_drawable(widget)) #else if (GTK_WIDGET_DRAWABLE(widget)) #endif { #if GTK_CHECK_VERSION(2,20,0) if (gtk_widget_get_has_window(widget)) #else if (!GTK_WIDGET_NO_WINDOW(widget)) #endif { if (event->window != widget->window) { GdkRectangle area; gdk_window_get_position(event->window, &area.x, &area.y); area.x += event->area.x; area.y += event->area.y; area.width = event->area.width; area.height = event->area.height; pixbuf_renderer_paint(PIXBUF_RENDERER(widget), &area); } else { pixbuf_renderer_paint(PIXBUF_RENDERER(widget), &event->area); } } } return FALSE; } /* *------------------------------------------------------------------- * misc utilities *------------------------------------------------------------------- */ static void widget_set_cursor(GtkWidget *widget, gint icon) { GdkCursor *cursor; if (!widget->window) return; if (icon == -1) { cursor = NULL; } else { cursor = gdk_cursor_new(icon); } gdk_window_set_cursor(widget->window, cursor); if (cursor) gdk_cursor_unref(cursor); } gboolean pr_clip_region(gint x, gint y, gint w, gint h, gint clip_x, gint clip_y, gint clip_w, gint clip_h, gint *rx, gint *ry, gint *rw, gint *rh) { if (clip_x + clip_w <= x || clip_x >= x + w || clip_y + clip_h <= y || clip_y >= y + h) { return FALSE; } *rx = MAX(x, clip_x); *rw = MIN((x + w), (clip_x + clip_w)) - *rx; *ry = MAX(y, clip_y); *rh = MIN((y + h), (clip_y + clip_h)) - *ry; return TRUE; } static gboolean pr_parent_window_sizable(PixbufRenderer *pr) { GdkWindowState state; if (!pr->parent_window) return FALSE; if (!pr->window_fit) return FALSE; if (!GTK_WIDGET(pr)->window) return FALSE; if (!pr->parent_window->window) return FALSE; state = gdk_window_get_state(pr->parent_window->window); if (state & GDK_WINDOW_STATE_MAXIMIZED) return FALSE; return TRUE; } static gboolean pr_parent_window_resize(PixbufRenderer *pr, gint w, gint h) { GtkWidget *widget; GtkWidget *parent; gint ww, wh; if (!pr_parent_window_sizable(pr)) return FALSE; if (pr->window_limit) { gint sw = gdk_screen_width() * pr->window_limit_size / 100; gint sh = gdk_screen_height() * pr->window_limit_size / 100; if (w > sw) w = sw; if (h > sh) h = sh; } widget = GTK_WIDGET(pr); parent = GTK_WIDGET(pr->parent_window); w += (parent->allocation.width - widget->allocation.width); h += (parent->allocation.height - widget->allocation.height); gdk_drawable_get_size(parent->window, &ww, &wh); if (w == ww && h == wh) return FALSE; gdk_window_resize(parent->window, w, h); return TRUE; } void pixbuf_renderer_set_parent(PixbufRenderer *pr, GtkWindow *window) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); g_return_if_fail(window == NULL || GTK_IS_WINDOW(window)); pr->parent_window = GTK_WIDGET(window); } GtkWindow *pixbuf_renderer_get_parent(PixbufRenderer *pr) { g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), NULL); return GTK_WINDOW(pr->parent_window); } /* *------------------------------------------------------------------- * overlays *------------------------------------------------------------------- */ gint pixbuf_renderer_overlay_add(PixbufRenderer *pr, GdkPixbuf *pixbuf, gint x, gint y, OverlayRendererFlags flags) { /* let's assume both renderers returns the same value */ if (pr->renderer2) pr->renderer2->overlay_add(pr->renderer2, pixbuf, x, y, flags); return pr->renderer->overlay_add(pr->renderer, pixbuf, x, y, flags); } void pixbuf_renderer_overlay_set(PixbufRenderer *pr, gint id, GdkPixbuf *pixbuf, gint x, gint y) { pr->renderer->overlay_set(pr->renderer, id, pixbuf, x, y); if (pr->renderer2) pr->renderer2->overlay_set(pr->renderer2, id, pixbuf, x, y); } gboolean pixbuf_renderer_overlay_get(PixbufRenderer *pr, gint id, GdkPixbuf **pixbuf, gint *x, gint *y) { if (pr->renderer2) pr->renderer2->overlay_get(pr->renderer2, id, pixbuf, x, y); return pr->renderer->overlay_get(pr->renderer, id, pixbuf, x, y); } void pixbuf_renderer_overlay_remove(PixbufRenderer *pr, gint id) { pr->renderer->overlay_set(pr->renderer, id, NULL, 0, 0); if (pr->renderer2) pr->renderer2->overlay_set(pr->renderer2, id, NULL, 0, 0); } /* *------------------------------------------------------------------- * scroller overlay *------------------------------------------------------------------- */ static gboolean pr_scroller_update_cb(gpointer data) { PixbufRenderer *pr = data; gint x, y; gint xinc, yinc; /* this was a simple scroll by difference between scroller and mouse position, * but all this math results in a smoother result and accounts for a dead zone. */ if (abs(pr->scroller_xpos - pr->scroller_x) < PR_SCROLLER_DEAD_ZONE) { x = 0; } else { gint shift = PR_SCROLLER_DEAD_ZONE / 2 * PR_SCROLLER_UPDATES_PER_SEC; x = (pr->scroller_xpos - pr->scroller_x) / 2 * PR_SCROLLER_UPDATES_PER_SEC; x += (x > 0) ? -shift : shift; } if (abs(pr->scroller_ypos - pr->scroller_y) < PR_SCROLLER_DEAD_ZONE) { y = 0; } else { gint shift = PR_SCROLLER_DEAD_ZONE / 2 * PR_SCROLLER_UPDATES_PER_SEC; y = (pr->scroller_ypos - pr->scroller_y) / 2 * PR_SCROLLER_UPDATES_PER_SEC; y += (y > 0) ? -shift : shift; } if (abs(x) < PR_SCROLLER_DEAD_ZONE * PR_SCROLLER_UPDATES_PER_SEC) { xinc = x; } else { xinc = pr->scroller_xinc; if (x >= 0) { if (xinc < 0) xinc = 0; if (x < xinc) xinc = x; if (x > xinc) xinc = MIN(xinc + x / PR_SCROLLER_UPDATES_PER_SEC, x); } else { if (xinc > 0) xinc = 0; if (x > xinc) xinc = x; if (x < xinc) xinc = MAX(xinc + x / PR_SCROLLER_UPDATES_PER_SEC, x); } } if (abs(y) < PR_SCROLLER_DEAD_ZONE * PR_SCROLLER_UPDATES_PER_SEC) { yinc = y; } else { yinc = pr->scroller_yinc; if (y >= 0) { if (yinc < 0) yinc = 0; if (y < yinc) yinc = y; if (y > yinc) yinc = MIN(yinc + y / PR_SCROLLER_UPDATES_PER_SEC, y); } else { if (yinc > 0) yinc = 0; if (y > yinc) yinc = y; if (y < yinc) yinc = MAX(yinc + y / PR_SCROLLER_UPDATES_PER_SEC, y); } } pr->scroller_xinc = xinc; pr->scroller_yinc = yinc; xinc = xinc / PR_SCROLLER_UPDATES_PER_SEC; yinc = yinc / PR_SCROLLER_UPDATES_PER_SEC; pixbuf_renderer_scroll(pr, xinc, yinc); return TRUE; } static void pr_scroller_timer_set(PixbufRenderer *pr, gboolean start) { if (pr->scroller_id) { g_source_remove(pr->scroller_id); pr->scroller_id = 0; } if (start) { pr->scroller_id = g_timeout_add(1000 / PR_SCROLLER_UPDATES_PER_SEC, pr_scroller_update_cb, pr); } } static void pr_scroller_start(PixbufRenderer *pr, gint x, gint y) { if (pr->scroller_overlay == -1) { GdkPixbuf *pixbuf; gint w, h; #ifdef GQ_BUILD pixbuf = pixbuf_inline(PIXBUF_INLINE_SCROLLER); #else pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 32, 32); gdk_pixbuf_fill(pixbuf, 0x000000ff); #endif w = gdk_pixbuf_get_width(pixbuf); h = gdk_pixbuf_get_height(pixbuf); pr->scroller_overlay = pixbuf_renderer_overlay_add(pr, pixbuf, x - w / 2, y - h / 2, OVL_NORMAL); g_object_unref(pixbuf); } pr->scroller_x = x; pr->scroller_y = y; pr->scroller_xpos = x; pr->scroller_ypos = y; pr_scroller_timer_set(pr, TRUE); } static void pr_scroller_stop(PixbufRenderer *pr) { if (!pr->scroller_id) return; pixbuf_renderer_overlay_remove(pr, pr->scroller_overlay); pr->scroller_overlay = -1; pr_scroller_timer_set(pr, FALSE); } /* *------------------------------------------------------------------- * borders *------------------------------------------------------------------- */ static void pr_border_clear(PixbufRenderer *pr) { pr->renderer->border_clear(pr->renderer); if (pr->renderer2) pr->renderer2->border_clear(pr->renderer2); } void pixbuf_renderer_set_color(PixbufRenderer *pr, GdkColor *color) { GtkStyle *style; GtkWidget *widget; g_return_if_fail(IS_PIXBUF_RENDERER(pr)); widget = GTK_WIDGET(pr); if (color) { GdkColor *slot; style = gtk_style_copy(gtk_widget_get_style(widget)); slot = &style->bg[GTK_STATE_NORMAL]; slot->red = color->red; slot->green = color->green; slot->blue = color->blue; } else { style = gtk_style_copy(gtk_widget_get_default_style()); } gtk_widget_set_style(widget, style); #if GTK_CHECK_VERSION(2,20,0) if (gtk_widget_get_visible(widget)) pr_border_clear(pr); #else if (GTK_WIDGET_VISIBLE(widget)) pr_border_clear(pr); #endif } static void pr_redraw(PixbufRenderer *pr, gboolean new_data) { pr->renderer->queue_clear(pr->renderer); pr->renderer->redraw(pr->renderer, 0, 0, pr->width, pr->height, TRUE, TILE_RENDER_ALL, new_data, FALSE); if (pr->renderer2) { pr->renderer2->queue_clear(pr->renderer2); pr->renderer2->redraw(pr->renderer2, 0, 0, pr->width, pr->height, TRUE, TILE_RENDER_ALL, new_data, FALSE); } } /* *------------------------------------------------------------------- * source tiles *------------------------------------------------------------------- */ static void pr_source_tile_free(SourceTile *st) { if (!st) return; if (st->pixbuf) g_object_unref(st->pixbuf); g_free(st); } static void pr_source_tile_free_all(PixbufRenderer *pr) { GList *work; work = pr->source_tiles; while (work) { SourceTile *st; st = work->data; work = work->next; pr_source_tile_free(st); } g_list_free(pr->source_tiles); pr->source_tiles = NULL; } static void pr_source_tile_unset(PixbufRenderer *pr) { pr_source_tile_free_all(pr); pr->source_tiles_enabled = FALSE; } static gboolean pr_source_tile_visible(PixbufRenderer *pr, SourceTile *st) { gint x1, y1, x2, y2; if (!st) return FALSE; // x1 = ROUND_DOWN(pr->x_scroll, pr->tile_width); // y1 = ROUND_DOWN(pr->y_scroll, pr->tile_height); // x2 = ROUND_UP(pr->x_scroll + pr->vis_width, pr->tile_width); // y2 = ROUND_UP(pr->y_scroll + pr->vis_height, pr->tile_height); x1 = pr->x_scroll; y1 = pr->y_scroll; x2 = pr->x_scroll + pr->vis_width; y2 = pr->y_scroll + pr->vis_height; return !((gdouble)st->x * pr->scale > (gdouble)x2 || (gdouble)(st->x + pr->source_tile_width) * pr->scale < (gdouble)x1 || (gdouble)st->y * pr->scale > (gdouble)y2 || (gdouble)(st->y + pr->source_tile_height) * pr->scale < (gdouble)y1); } static SourceTile *pr_source_tile_new(PixbufRenderer *pr, gint x, gint y) { SourceTile *st = NULL; gint count; g_return_val_if_fail(pr->source_tile_width >= 1 && pr->source_tile_height >= 1, NULL); if (pr->source_tiles_cache_size < 4) pr->source_tiles_cache_size = 4; count = g_list_length(pr->source_tiles); if (count >= pr->source_tiles_cache_size) { GList *work; work = g_list_last(pr->source_tiles); while (work && count >= pr->source_tiles_cache_size) { SourceTile *needle; needle = work->data; work = work->prev; if (!pr_source_tile_visible(pr, needle)) { pr->source_tiles = g_list_remove(pr->source_tiles, needle); if (pr->func_tile_dispose) { pr->func_tile_dispose(pr, needle->x, needle->y, pr->source_tile_width, pr->source_tile_height, needle->pixbuf, pr->func_tile_data); } if (!st) { st = needle; } else { pr_source_tile_free(needle); } count--; } } } if (!st) { st = g_new0(SourceTile, 1); st->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, pr->source_tile_width, pr->source_tile_height); } st->x = ROUND_DOWN(x, pr->source_tile_width); st->y = ROUND_DOWN(y, pr->source_tile_height); st->blank = TRUE; pr->source_tiles = g_list_prepend(pr->source_tiles, st); return st; } static SourceTile *pr_source_tile_request(PixbufRenderer *pr, gint x, gint y) { SourceTile *st; st = pr_source_tile_new(pr, x, y); if (!st) return NULL; if (pr->func_tile_request && pr->func_tile_request(pr, st->x, st->y, pr->source_tile_width, pr->source_tile_height, st->pixbuf, pr->func_tile_data)) { st->blank = FALSE; } pr->renderer->invalidate_region(pr->renderer, st->x * pr->scale, st->y * pr->scale, pr->source_tile_width * pr->scale, pr->source_tile_height * pr->scale); if (pr->renderer2) pr->renderer2->invalidate_region(pr->renderer2, st->x * pr->scale, st->y * pr->scale, pr->source_tile_width * pr->scale, pr->source_tile_height * pr->scale); return st; } static SourceTile *pr_source_tile_find(PixbufRenderer *pr, gint x, gint y) { GList *work; work = pr->source_tiles; while (work) { SourceTile *st = work->data; if (x >= st->x && x < st->x + pr->source_tile_width && y >= st->y && y < st->y + pr->source_tile_height) { if (work != pr->source_tiles) { pr->source_tiles = g_list_remove_link(pr->source_tiles, work); pr->source_tiles = g_list_concat(work, pr->source_tiles); } return st; } work = work->next; } return NULL; } GList *pr_source_tile_compute_region(PixbufRenderer *pr, gint x, gint y, gint w, gint h, gboolean request) { gint x1, y1; GList *list = NULL; gint sx, sy; if (x < 0) x = 0; if (y < 0) y = 0; if (w > pr->image_width) w = pr->image_width; if (h > pr->image_height) h = pr->image_height; sx = ROUND_DOWN(x, pr->source_tile_width); sy = ROUND_DOWN(y, pr->source_tile_height); for (x1 = sx; x1 < x + w; x1+= pr->source_tile_width) { for (y1 = sy; y1 < y + h; y1 += pr->source_tile_height) { SourceTile *st; st = pr_source_tile_find(pr, x1, y1); if (!st && request) st = pr_source_tile_request(pr, x1, y1); if (st) list = g_list_prepend(list, st); } } return g_list_reverse(list); } static void pr_source_tile_changed(PixbufRenderer *pr, gint x, gint y, gint width, gint height) { GList *work; if (width < 1 || height < 1) return; work = pr->source_tiles; while (work) { SourceTile *st; gint rx, ry, rw, rh; st = work->data; work = work->next; if (pr_clip_region(st->x, st->y, pr->source_tile_width, pr->source_tile_height, x, y, width, height, &rx, &ry, &rw, &rh)) { GdkPixbuf *pixbuf; pixbuf = gdk_pixbuf_new_subpixbuf(st->pixbuf, rx - st->x, ry - st->y, rw, rh); if (pr->func_tile_request && pr->func_tile_request(pr, rx, ry, rw, rh, pixbuf, pr->func_tile_data)) { pr->renderer->invalidate_region(pr->renderer, rx * pr->scale, ry * pr->scale, rw * pr->scale, rh * pr->scale); if (pr->renderer2) pr->renderer2->invalidate_region(pr->renderer2, rx * pr->scale, ry * pr->scale, rw * pr->scale, rh * pr->scale); } g_object_unref(pixbuf); } } } void pixbuf_renderer_set_tiles(PixbufRenderer *pr, gint width, gint height, gint tile_width, gint tile_height, gint cache_size, PixbufRendererTileRequestFunc func_request, PixbufRendererTileDisposeFunc func_dispose, gpointer user_data, gdouble zoom) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); g_return_if_fail(tile_width >= 32 && tile_width >= 32); g_return_if_fail(width >= 32 && height > 32); g_return_if_fail(func_request != NULL); if (pr->pixbuf) g_object_unref(pr->pixbuf); pr->pixbuf = NULL; pr_source_tile_unset(pr); if (cache_size < 4) cache_size = 4; pr->source_tiles_enabled = TRUE; pr->source_tiles_cache_size = cache_size; pr->source_tile_width = tile_width; pr->source_tile_height = tile_height; pr->image_width = width; pr->image_height = height; pr->func_tile_request = func_request; pr->func_tile_dispose = func_dispose; pr->func_tile_data = user_data; pr_zoom_sync(pr, zoom, PR_ZOOM_FORCE | PR_ZOOM_NEW, 0, 0); pr_redraw(pr, TRUE); } void pixbuf_renderer_set_tiles_size(PixbufRenderer *pr, gint width, gint height) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); g_return_if_fail(width >= 32 && height > 32); if (!pr->source_tiles_enabled) return; if (pr->image_width == width && pr->image_height == height) return; pr->image_width = width; pr->image_height = height; pr_zoom_sync(pr, pr->zoom, PR_ZOOM_FORCE, 0, 0); } gint pixbuf_renderer_get_tiles(PixbufRenderer *pr) { g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE); return pr->source_tiles_enabled; } static void pr_zoom_adjust_real(PixbufRenderer *pr, gdouble increment, PrZoomFlags flags, gint x, gint y) { gdouble zoom = pr->zoom; if (increment == 0.0) return; if (zoom == 0.0) { if (pr->scale < 1.0) { zoom = 0.0 - 1.0 / pr->scale; } else { zoom = pr->scale; } } if (increment < 0.0) { if (zoom >= 1.0 && zoom + increment < 1.0) { zoom = zoom + increment - 2.0; } else { zoom = zoom + increment; } } else { if (zoom <= -1.0 && zoom + increment > -1.0) { zoom = zoom + increment + 2.0; } else { zoom = zoom + increment; } } pr_zoom_sync(pr, zoom, flags, x, y); } /* *------------------------------------------------------------------- * signal emission *------------------------------------------------------------------- */ static void pr_update_signal(PixbufRenderer *pr) { #if 0 log_printf("FIXME: send updated signal\n"); #endif DEBUG_1("%s pixbuf renderer updated - started drawing %p, img: %dx%d", get_exec_time(), pr, pr->image_width, pr->image_height); pr->debug_updated = TRUE; } static void pr_zoom_signal(PixbufRenderer *pr) { g_signal_emit(pr, signals[SIGNAL_ZOOM], 0, pr->zoom); } static void pr_clicked_signal(PixbufRenderer *pr, GdkEventButton *bevent) { g_signal_emit(pr, signals[SIGNAL_CLICKED], 0, bevent); } static void pr_scroll_notify_signal(PixbufRenderer *pr) { g_signal_emit(pr, signals[SIGNAL_SCROLL_NOTIFY], 0); } void pr_render_complete_signal(PixbufRenderer *pr) { if (!pr->complete) { g_signal_emit(pr, signals[SIGNAL_RENDER_COMPLETE], 0); g_object_set(G_OBJECT(pr), "complete", TRUE, NULL); } if (pr->debug_updated) { DEBUG_1("%s pixbuf renderer done %p", get_exec_time(), pr); pr->debug_updated = FALSE; } } static void pr_drag_signal(PixbufRenderer *pr, GdkEventButton *bevent) { g_signal_emit(pr, signals[SIGNAL_DRAG], 0, bevent); } static void pr_update_pixel_signal(PixbufRenderer *pr) { g_signal_emit(pr, signals[SIGNAL_UPDATE_PIXEL], 0); } /* *------------------------------------------------------------------- * sync and clamp *------------------------------------------------------------------- */ void pr_tile_coords_map_orientation(gint orientation, gdouble tile_x, gdouble tile_y, /* coordinates of the tile */ gdouble image_w, gdouble image_h, gdouble tile_w, gdouble tile_h, gdouble *res_x, gdouble *res_y) { *res_x = tile_x; *res_y = tile_y; switch (orientation) { case EXIF_ORIENTATION_TOP_LEFT: /* normal -- nothing to do */ break; case EXIF_ORIENTATION_TOP_RIGHT: /* mirrored */ *res_x = image_w - tile_x - tile_w; break; case EXIF_ORIENTATION_BOTTOM_RIGHT: /* upside down */ *res_x = image_w - tile_x - tile_w; *res_y = image_h - tile_y - tile_h; break; case EXIF_ORIENTATION_BOTTOM_LEFT: /* flipped */ *res_y = image_h - tile_y - tile_h; break; case EXIF_ORIENTATION_LEFT_TOP: *res_x = tile_y; *res_y = tile_x; break; case EXIF_ORIENTATION_RIGHT_TOP: /* rotated -90 (270) */ *res_x = tile_y; *res_y = image_w - tile_x - tile_w; break; case EXIF_ORIENTATION_RIGHT_BOTTOM: *res_x = image_h - tile_y - tile_h; *res_y = image_w - tile_x - tile_w; break; case EXIF_ORIENTATION_LEFT_BOTTOM: /* rotated 90 */ *res_x = image_h - tile_y - tile_h; *res_y = tile_x; break; default: /* The other values are out of range */ break; } // log_printf("tile coord y:%f, ih:%d, th:%f ry:%f\n", tile_y, image_h, tile_h, *res_x); } void pr_tile_region_map_orientation(gint orientation, gint area_x, gint area_y, /* coordinates of the area inside tile */ gint tile_w, gint tile_h, gint area_w, gint area_h, gint *res_x, gint *res_y, gint *res_w, gint *res_h) { *res_x = area_x; *res_y = area_y; *res_w = area_w; *res_h = area_h; switch (orientation) { case EXIF_ORIENTATION_TOP_LEFT: /* normal -- nothing to do */ break; case EXIF_ORIENTATION_TOP_RIGHT: /* mirrored */ *res_x = tile_w - area_x - area_w; break; case EXIF_ORIENTATION_BOTTOM_RIGHT: /* upside down */ *res_x = tile_w - area_x - area_w; *res_y = tile_h - area_y - area_h; break; case EXIF_ORIENTATION_BOTTOM_LEFT: /* flipped */ *res_y = tile_h - area_y - area_h; break; case EXIF_ORIENTATION_LEFT_TOP: *res_x = area_y; *res_y = area_x; *res_w = area_h; *res_h = area_w; break; case EXIF_ORIENTATION_RIGHT_TOP: /* rotated -90 (270) */ *res_x = area_y; *res_y = tile_w - area_x - area_w; *res_w = area_h; *res_h = area_w; break; case EXIF_ORIENTATION_RIGHT_BOTTOM: *res_x = tile_h - area_y - area_h; *res_y = tile_w - area_x - area_w; *res_w = area_h; *res_h = area_w; break; case EXIF_ORIENTATION_LEFT_BOTTOM: /* rotated 90 */ *res_x = tile_h - area_y - area_h; *res_y = area_x; *res_w = area_h; *res_h = area_w; break; default: /* The other values are out of range */ break; } // log_printf("inside y:%d, th:%d, ah:%d ry:%d\n", area_y, tile_h, area_h, *res_x); } void pr_coords_map_orientation_reverse(gint orientation, gint area_x, gint area_y, gint tile_w, gint tile_h, gint area_w, gint area_h, gint *res_x, gint *res_y, gint *res_w, gint *res_h) { *res_x = area_x; *res_y = area_y; *res_w = area_w; *res_h = area_h; switch (orientation) { case EXIF_ORIENTATION_TOP_LEFT: /* normal -- nothing to do */ break; case EXIF_ORIENTATION_TOP_RIGHT: /* mirrored */ *res_x = tile_w - area_x - area_w; break; case EXIF_ORIENTATION_BOTTOM_RIGHT: /* upside down */ *res_x = tile_w - area_x - area_w; *res_y = tile_h - area_y - area_h; break; case EXIF_ORIENTATION_BOTTOM_LEFT: /* flipped */ *res_y = tile_h - area_y - area_h; break; case EXIF_ORIENTATION_LEFT_TOP: *res_x = area_y; *res_y = area_x; *res_w = area_h; *res_h = area_w; break; case EXIF_ORIENTATION_RIGHT_TOP: /* rotated -90 (270) */ *res_x = tile_w - area_y - area_h; *res_y = area_x; *res_w = area_h; *res_h = area_w; break; case EXIF_ORIENTATION_RIGHT_BOTTOM: *res_x = tile_w - area_y - area_h; *res_y = tile_h - area_x - area_w; *res_w = area_h; *res_h = area_w; break; case EXIF_ORIENTATION_LEFT_BOTTOM: /* rotated 90 */ *res_x = area_y; *res_y = tile_h - area_x - area_w; *res_w = area_h; *res_h = area_w; break; default: /* The other values are out of range */ break; } } static void pixbuf_renderer_sync_scroll_center(PixbufRenderer *pr) { gint src_x, src_y; if (!pr->width || !pr->height) return; /* * Update norm_center only if the image is bigger than the window. * With this condition the stored center survives also a temporary display * of the "broken image" icon. */ if (pr->width > pr->viewport_width) { src_x = pr->x_scroll + pr->vis_width / 2; pr->norm_center_x = (gdouble)src_x / pr->width; } if (pr->height > pr->viewport_height) { src_y = pr->y_scroll + pr->vis_height / 2; pr->norm_center_y = (gdouble)src_y / pr->height; } } static gboolean pr_scroll_clamp(PixbufRenderer *pr) { gint old_xs; gint old_ys; if (pr->zoom == 0.0) { pr->x_scroll = 0; pr->y_scroll = 0; return FALSE; } old_xs = pr->x_scroll; old_ys = pr->y_scroll; if (pr->x_offset > 0) { pr->x_scroll = 0; } else { pr->x_scroll = CLAMP(pr->x_scroll, 0, pr->width - pr->vis_width); } if (pr->y_offset > 0) { pr->y_scroll = 0; } else { pr->y_scroll = CLAMP(pr->y_scroll, 0, pr->height - pr->vis_height); } pixbuf_renderer_sync_scroll_center(pr); return (old_xs != pr->x_scroll || old_ys != pr->y_scroll); } static gboolean pr_size_clamp(PixbufRenderer *pr) { gint old_vw, old_vh; old_vw = pr->vis_width; old_vh = pr->vis_height; if (pr->width < pr->viewport_width) { pr->vis_width = pr->width; pr->x_offset = (pr->viewport_width - pr->width) / 2; } else { pr->vis_width = pr->viewport_width; pr->x_offset = 0; } if (pr->height < pr->viewport_height) { pr->vis_height = pr->height; pr->y_offset = (pr->viewport_height - pr->height) / 2; } else { pr->vis_height = pr->viewport_height; pr->y_offset = 0; } pixbuf_renderer_sync_scroll_center(pr); return (old_vw != pr->vis_width || old_vh != pr->vis_height); } static gboolean pr_zoom_clamp(PixbufRenderer *pr, gdouble zoom, PrZoomFlags flags, gboolean *redrawn) { gint w, h; gdouble scale; gboolean invalid; gboolean force = !!(flags & PR_ZOOM_FORCE); gboolean new = !!(flags & PR_ZOOM_NEW); gboolean invalidate = !!(flags & PR_ZOOM_INVALIDATE); gboolean lazy = !!(flags & PR_ZOOM_LAZY); zoom = CLAMP(zoom, pr->zoom_min, pr->zoom_max); if (pr->zoom == zoom && !force) return FALSE; w = pr->image_width; h = pr->image_height; if (zoom == 0.0 && !pr->pixbuf) { scale = 1.0; } else if (zoom == 0.0) { gint max_w; gint max_h; gboolean sizeable; sizeable = (new && pr_parent_window_sizable(pr)); if (sizeable) { max_w = gdk_screen_width(); max_h = gdk_screen_height(); if (pr->window_limit) { max_w = max_w * pr->window_limit_size / 100; max_h = max_h * pr->window_limit_size / 100; } } else { max_w = pr->viewport_width; max_h = pr->viewport_height; } if ((pr->zoom_expand && !sizeable) || w > max_w || h > max_h) { if ((gdouble)max_w / w > (gdouble)max_h / h / pr->aspect_ratio) { scale = (gdouble)max_h / h / pr->aspect_ratio; h = max_h; w = w * scale + 0.5; if (w > max_w) w = max_w; } else { scale = (gdouble)max_w / w; w = max_w; h = h * scale * pr->aspect_ratio + 0.5; if (h > max_h) h = max_h; } if (pr->autofit_limit) { gdouble factor = (gdouble)pr->autofit_limit_size / 100; w = w * factor + 0.5; h = h * factor + 0.5; scale = scale * factor; } if (w < 1) w = 1; if (h < 1) h = 1; } else { scale = 1.0; } } else if (zoom > 0.0) /* zoom orig, in */ { scale = zoom; w = w * scale; h = h * scale * pr->aspect_ratio; } else /* zoom out */ { scale = 1.0 / (0.0 - zoom); w = w * scale; h = h * scale * pr->aspect_ratio; } invalid = (pr->width != w || pr->height != h); pr->zoom = zoom; pr->width = w; pr->height = h; pr->scale = scale; if (invalidate || invalid) { pr->renderer->invalidate_all(pr->renderer); if (pr->renderer2) pr->renderer2->invalidate_all(pr->renderer2); if (!lazy) pr_redraw(pr, TRUE); } if (redrawn) *redrawn = (invalidate || invalid); pixbuf_renderer_sync_scroll_center(pr); return TRUE; } static void pr_zoom_sync(PixbufRenderer *pr, gdouble zoom, PrZoomFlags flags, gint px, gint py) { gdouble old_scale; gint old_cx, old_cy; gboolean clamped; gboolean sized; gboolean redrawn = FALSE; gboolean center_point = !!(flags & PR_ZOOM_CENTER); gboolean force = !!(flags & PR_ZOOM_FORCE); gboolean new = !!(flags & PR_ZOOM_NEW); gboolean lazy = !!(flags & PR_ZOOM_LAZY); PrZoomFlags clamp_flags = flags; gdouble old_center_x = pr->norm_center_x; gdouble old_center_y = pr->norm_center_y; old_scale = pr->scale; if (center_point) { px = CLAMP(px, 0, pr->width); py = CLAMP(py, 0, pr->height); old_cx = pr->x_scroll + (px - pr->x_offset); old_cy = pr->y_scroll + (py - pr->y_offset); } else { px = py = 0; old_cx = pr->x_scroll + pr->vis_width / 2; old_cy = pr->y_scroll + pr->vis_height / 2; } if (force) clamp_flags |= PR_ZOOM_INVALIDATE; if (lazy) clamp_flags |= PR_ZOOM_LAZY; if (!pr_zoom_clamp(pr, zoom, clamp_flags, &redrawn)) return; clamped = pr_size_clamp(pr); sized = pr_parent_window_resize(pr, pr->width, pr->height); if (force && new) { switch (pr->scroll_reset) { case PR_SCROLL_RESET_NOCHANGE: /* maintain old scroll position */ pr->x_scroll = ((gdouble)pr->image_width * old_center_x * pr->scale) - pr->vis_width / 2; pr->y_scroll = ((gdouble)pr->image_height * old_center_y * pr->scale * pr->aspect_ratio) - pr->vis_height / 2; break; case PR_SCROLL_RESET_CENTER: /* center new image */ pr->x_scroll = ((gdouble)pr->image_width / 2.0 * pr->scale) - pr->vis_width / 2; pr->y_scroll = ((gdouble)pr->image_height / 2.0 * pr->scale * pr->aspect_ratio) - pr->vis_height / 2; break; case PR_SCROLL_RESET_TOPLEFT: default: /* reset to upper left */ pr->x_scroll = 0; pr->y_scroll = 0; break; } } else { /* user zoom does not force, so keep visible center point */ if (center_point) { pr->x_scroll = old_cx / old_scale * pr->scale - (px - pr->x_offset); pr->y_scroll = old_cy / old_scale * pr->scale * pr->aspect_ratio - (py - pr->y_offset); } else { pr->x_scroll = old_cx / old_scale * pr->scale - (pr->vis_width / 2); pr->y_scroll = old_cy / old_scale * pr->scale * pr->aspect_ratio - (pr->vis_height / 2); } } pr_scroll_clamp(pr); /* If the window was not sized, redraw the image - we know there will be no size/expose signal. * But even if a size is claimed, there is no guarantee that the window manager will allow it, * so redraw the window anyway :/ */ if (sized || clamped) pr_border_clear(pr); if (lazy) { pr->renderer->queue_clear(pr->renderer); if (pr->renderer2) pr->renderer2->queue_clear(pr->renderer2); } else { pr_redraw(pr, redrawn); } pr_scroll_notify_signal(pr); pr_zoom_signal(pr); pr_update_signal(pr); } static void pr_size_sync(PixbufRenderer *pr, gint new_width, gint new_height) { gboolean zoom_changed = FALSE; gint new_viewport_width = new_width; gint new_viewport_height = new_height; if (!pr->stereo_temp_disable) { if (pr->stereo_mode & PR_STEREO_HORIZ) { new_viewport_width = new_width / 2; } else if (pr->stereo_mode & PR_STEREO_VERT) { new_viewport_height = new_height / 2; } else if (pr->stereo_mode & PR_STEREO_FIXED) { new_viewport_width = pr->stereo_fixed_width; new_viewport_height = pr->stereo_fixed_height; } } if (pr->window_width == new_width && pr->window_height == new_height && pr->viewport_width == new_viewport_width && pr->viewport_height == new_viewport_height) return; pr->window_width = new_width; pr->window_height = new_height; pr->viewport_width = new_viewport_width; pr->viewport_height = new_viewport_height; if (pr->zoom == 0.0) { gdouble old_scale = pr->scale; pr_zoom_clamp(pr, 0.0, PR_ZOOM_FORCE, NULL); zoom_changed = (old_scale != pr->scale); } pr_size_clamp(pr); pr_scroll_clamp(pr); pr->renderer->update_sizes(pr->renderer); if (pr->renderer2) pr->renderer2->update_sizes(pr->renderer2); /* ensure scroller remains visible */ if (pr->scroller_overlay != -1) { gboolean update = FALSE; if (pr->scroller_x > new_width) { pr->scroller_x = new_width; pr->scroller_xpos = new_width; update = TRUE; } if (pr->scroller_y > new_height) { pr->scroller_y = new_height; pr->scroller_ypos = new_height; update = TRUE; } if (update) { GdkPixbuf *pixbuf; if (pixbuf_renderer_overlay_get(pr, pr->scroller_overlay, &pixbuf, NULL, NULL)) { gint w, h; w = gdk_pixbuf_get_width(pixbuf); h = gdk_pixbuf_get_height(pixbuf); pixbuf_renderer_overlay_set(pr, pr->scroller_overlay, pixbuf, pr->scroller_x - w / 2, pr->scroller_y - h / 2); } } } pr_border_clear(pr); pr_scroll_notify_signal(pr); if (zoom_changed) pr_zoom_signal(pr); pr_update_signal(pr); } static void pr_size_cb(GtkWidget *widget, GtkAllocation *allocation, gpointer data) { PixbufRenderer *pr = data; pr_size_sync(pr, allocation->width, allocation->height); } static void pixbuf_renderer_paint(PixbufRenderer *pr, GdkRectangle *area) { gint x, y; pr->renderer->redraw(pr->renderer, area->x, area->y, area->width, area->height, FALSE, TILE_RENDER_ALL, FALSE, FALSE); if (pr->renderer2) { pr->renderer2->redraw(pr->renderer2, area->x, area->y, area->width, area->height, FALSE, TILE_RENDER_ALL, FALSE, FALSE); } } /* *------------------------------------------------------------------- * scrolling *------------------------------------------------------------------- */ void pixbuf_renderer_scroll(PixbufRenderer *pr, gint x, gint y) { gint old_x, old_y; gint x_off, y_off; g_return_if_fail(IS_PIXBUF_RENDERER(pr)); if (!pr->pixbuf && !pr->source_tiles_enabled) return; old_x = pr->x_scroll; old_y = pr->y_scroll; pr->x_scroll += x; pr->y_scroll += y; pr_scroll_clamp(pr); pixbuf_renderer_sync_scroll_center(pr); if (pr->x_scroll == old_x && pr->y_scroll == old_y) return; pr_scroll_notify_signal(pr); x_off = pr->x_scroll - old_x; y_off = pr->y_scroll - old_y; pr->renderer->scroll(pr->renderer, x_off, y_off); if (pr->renderer2) pr->renderer2->scroll(pr->renderer2, x_off, y_off); } void pixbuf_renderer_scroll_to_point(PixbufRenderer *pr, gint x, gint y, gdouble x_align, gdouble y_align) { gint px, py; gint ax, ay; x_align = CLAMP(x_align, 0.0, 1.0); y_align = CLAMP(y_align, 0.0, 1.0); ax = (gdouble)pr->vis_width * x_align; ay = (gdouble)pr->vis_height * y_align; px = (gdouble)x * pr->scale - (pr->x_scroll + ax); py = (gdouble)y * pr->scale * pr->aspect_ratio - (pr->y_scroll + ay); pixbuf_renderer_scroll(pr, px, py); } /* get or set coordinates of viewport center in the image, in range 0.0 - 1.0 */ void pixbuf_renderer_get_scroll_center(PixbufRenderer *pr, gdouble *x, gdouble *y) { *x = pr->norm_center_x; *y = pr->norm_center_y; } void pixbuf_renderer_set_scroll_center(PixbufRenderer *pr, gdouble x, gdouble y) { gdouble dst_x, dst_y; dst_x = x * pr->width - pr->vis_width / 2 - pr->x_scroll + CLAMP(pr->subpixel_x_scroll, -1.0, 1.0); dst_y = y * pr->height - pr->vis_height / 2 - pr->y_scroll + CLAMP(pr->subpixel_y_scroll, -1.0, 1.0); pr->subpixel_x_scroll = dst_x - (gint)dst_x; pr->subpixel_y_scroll = dst_y - (gint)dst_y; pixbuf_renderer_scroll(pr, (gint)dst_x, (gint)dst_y); } /* *------------------------------------------------------------------- * mouse *------------------------------------------------------------------- */ static gboolean pr_mouse_motion_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data) { PixbufRenderer *pr; gint accel; /* This is a hack, but work far the best, at least for single pointer systems. * See http://bugzilla.gnome.org/show_bug.cgi?id=587714 for more. */ gint x, y; gdk_window_get_pointer (bevent->window, &x, &y, NULL); bevent->x = x; bevent->y = y; pr = PIXBUF_RENDERER(widget); if (pr->scroller_id) { pr->scroller_xpos = bevent->x; pr->scroller_ypos = bevent->y; } pr->x_mouse = bevent->x; pr->y_mouse = bevent->y; pr_update_pixel_signal(pr); if (!pr->in_drag || !gdk_pointer_is_grabbed()) return FALSE; if (pr->drag_moved < PR_DRAG_SCROLL_THRESHHOLD) { pr->drag_moved++; } else { widget_set_cursor(widget, GDK_FLEUR); } if (bevent->state & GDK_CONTROL_MASK) { accel = PR_PAN_SHIFT_MULTIPLIER; } else { accel = 1; } /* do the scroll */ pixbuf_renderer_scroll(pr, (pr->drag_last_x - bevent->x) * accel, (pr->drag_last_y - bevent->y) * accel); pr_drag_signal(pr, bevent); pr->drag_last_x = bevent->x; pr->drag_last_y = bevent->y; /* This is recommended by the GTK+ documentation, but does not work properly. * Use deprecated way until GTK+ gets a solution for correct motion hint handling: * http://bugzilla.gnome.org/show_bug.cgi?id=587714 */ /* gdk_event_request_motions (bevent); */ return FALSE; } static gboolean pr_leave_notify_cb(GtkWidget *widget, GdkEventCrossing *cevent, gpointer data) { PixbufRenderer *pr; pr = PIXBUF_RENDERER(widget); pr->x_mouse = -1; pr->y_mouse = -1; pr_update_pixel_signal(pr); return FALSE; } static gboolean pr_mouse_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data) { PixbufRenderer *pr; GtkWidget *parent; pr = PIXBUF_RENDERER(widget); if (pr->scroller_id) return TRUE; switch (bevent->button) { case MOUSE_BUTTON_LEFT: pr->in_drag = TRUE; pr->drag_last_x = bevent->x; pr->drag_last_y = bevent->y; pr->drag_moved = 0; gdk_pointer_grab(widget->window, FALSE, GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_RELEASE_MASK, NULL, NULL, bevent->time); gtk_grab_add(widget); break; case MOUSE_BUTTON_MIDDLE: pr->drag_moved = 0; break; case MOUSE_BUTTON_RIGHT: pr_clicked_signal(pr, bevent); break; default: break; } parent = gtk_widget_get_parent(widget); #if GTK_CHECK_VERSION(2,20,0) if (widget && gtk_widget_get_can_focus(parent)) #else if (widget && GTK_WIDGET_CAN_FOCUS(parent)) #endif { gtk_widget_grab_focus(parent); } return FALSE; } static gboolean pr_mouse_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data) { PixbufRenderer *pr; pr = PIXBUF_RENDERER(widget); if (pr->scroller_id) { pr_scroller_stop(pr); return TRUE; } #if GTK_CHECK_VERSION(2,20,0) if (gdk_pointer_is_grabbed() && gtk_widget_has_grab(GTK_WIDGET(pr))) #else if (gdk_pointer_is_grabbed() && GTK_WIDGET_HAS_GRAB(pr)) #endif { gtk_grab_remove(widget); gdk_pointer_ungrab(bevent->time); widget_set_cursor(widget, -1); } if (pr->drag_moved < PR_DRAG_SCROLL_THRESHHOLD) { if (bevent->button == MOUSE_BUTTON_LEFT && (bevent->state & GDK_CONTROL_MASK)) { pr_scroller_start(pr, bevent->x, bevent->y); } else if (bevent->button == MOUSE_BUTTON_LEFT || bevent->button == MOUSE_BUTTON_MIDDLE) { pr_clicked_signal(pr, bevent); } } pr->in_drag = FALSE; return FALSE; } static gboolean pr_mouse_leave_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer data) { PixbufRenderer *pr; pr = PIXBUF_RENDERER(widget); if (pr->scroller_id) { pr->scroller_xpos = pr->scroller_x; pr->scroller_ypos = pr->scroller_y; pr->scroller_xinc = 0; pr->scroller_yinc = 0; } return FALSE; } static void pr_mouse_drag_cb(GtkWidget *widget, GdkDragContext *context, gpointer data) { PixbufRenderer *pr; pr = PIXBUF_RENDERER(widget); pr->drag_moved = PR_DRAG_SCROLL_THRESHHOLD; } static void pr_signals_connect(PixbufRenderer *pr) { g_signal_connect(G_OBJECT(pr), "motion_notify_event", G_CALLBACK(pr_mouse_motion_cb), pr); g_signal_connect(G_OBJECT(pr), "button_press_event", G_CALLBACK(pr_mouse_press_cb), pr); g_signal_connect(G_OBJECT(pr), "button_release_event", G_CALLBACK(pr_mouse_release_cb), pr); g_signal_connect(G_OBJECT(pr), "leave_notify_event", G_CALLBACK(pr_mouse_leave_cb), pr); g_signal_connect(G_OBJECT(pr), "leave_notify_event", G_CALLBACK(pr_leave_notify_cb), pr); gtk_widget_set_events(GTK_WIDGET(pr), GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK); g_signal_connect(G_OBJECT(pr), "drag_begin", G_CALLBACK(pr_mouse_drag_cb), pr); } /* *------------------------------------------------------------------- * stereo support *------------------------------------------------------------------- */ #define COLOR_BYTES 3 /* rgb */ static void pr_create_anaglyph_RC(GdkPixbuf *pixbuf, GdkPixbuf *right, gint x, gint y, gint w, gint h) { gint srs, drs; guchar *s_pix, *d_pix; guchar *sp, *dp; guchar *spi, *dpi; gint i, j; srs = gdk_pixbuf_get_rowstride(right); s_pix = gdk_pixbuf_get_pixels(right); spi = s_pix + (x * COLOR_BYTES); drs = gdk_pixbuf_get_rowstride(pixbuf); d_pix = gdk_pixbuf_get_pixels(pixbuf); dpi = d_pix + x * COLOR_BYTES; for (i = y; i < y + h; i++) { sp = spi + (i * srs); dp = dpi + (i * drs); for (j = 0; j < w; j++) { *dp = *sp; /* copy red channel */ sp += COLOR_BYTES; dp += COLOR_BYTES; } } } static void pr_create_anaglyph_gray(GdkPixbuf *pixbuf, GdkPixbuf *right, gint x, gint y, gint w, gint h) { gint srs, drs; guchar *s_pix, *d_pix; guchar *sp, *dp; guchar *spi, *dpi; gint i, j; const double gc[3] = {0.299, 0.587, 0.114}; srs = gdk_pixbuf_get_rowstride(right); s_pix = gdk_pixbuf_get_pixels(right); spi = s_pix + (x * COLOR_BYTES); drs = gdk_pixbuf_get_rowstride(pixbuf); d_pix = gdk_pixbuf_get_pixels(pixbuf); dpi = d_pix + x * COLOR_BYTES; for (i = y; i < y + h; i++) { sp = spi + (i * srs); dp = dpi + (i * drs); for (j = 0; j < w; j++) { guchar g1 = dp[0] * gc[0] + dp[1] * gc[1] + dp[2] * gc[2]; guchar g2 = sp[0] * gc[0] + sp[1] * gc[1] + sp[2] * gc[2]; dp[0] = g2; /* red channel from sp */ dp[1] = g1; /* green and blue from dp */ dp[2] = g1; sp += COLOR_BYTES; dp += COLOR_BYTES; } } } const double pr_dubois_matrix[3][6] = { { 0.456, 0.500, 0.176, -0.043, -0.088, -0.002}, {-0.040, -0.038, -0.016, 0.378, 0.734, -0.018}, {-0.015, -0.021, -0.005, -0.072, -0.113, 1.226} }; static void pr_create_anaglyph_dubois(GdkPixbuf *pixbuf, GdkPixbuf *right, gint x, gint y, gint w, gint h) { gint srs, drs; guchar *s_pix, *d_pix; guchar *sp, *dp; guchar *spi, *dpi; gint i, j, k; srs = gdk_pixbuf_get_rowstride(right); s_pix = gdk_pixbuf_get_pixels(right); spi = s_pix + (x * COLOR_BYTES); drs = gdk_pixbuf_get_rowstride(pixbuf); d_pix = gdk_pixbuf_get_pixels(pixbuf); dpi = d_pix + x * COLOR_BYTES; for (i = y; i < y + h; i++) { sp = spi + (i * srs); dp = dpi + (i * drs); for (j = 0; j < w; j++) { double res[3]; for (k = 0; k < 3; k++) { double *m = pr_dubois_matrix[k]; res[k] = sp[0] * m[0] + sp[1] * m[1] + sp[2] * m[2] + dp[0] * m[3] + dp[1] * m[4] + dp[2] * m[5]; if (res[k] < 0.0) res[k] = 0; if (res[k] > 255.0) res[k] = 255.0; } dp[0] = res[0]; dp[1] = res[1]; dp[2] = res[2]; sp += COLOR_BYTES; dp += COLOR_BYTES; } } } void pr_create_anaglyph(guint mode, GdkPixbuf *pixbuf, GdkPixbuf *right, gint x, gint y, gint w, gint h) { if (mode & PR_STEREO_ANAGLYPH_RC) pr_create_anaglyph_RC(pixbuf, right, x, y, w, h); else if (mode & PR_STEREO_ANAGLYPH_GRAY) pr_create_anaglyph_gray(pixbuf, right, x, y, w, h); else if (mode & PR_STEREO_ANAGLYPH_DB) pr_create_anaglyph_dubois(pixbuf, right, x, y, w, h); } /* *------------------------------------------------------------------- * public *------------------------------------------------------------------- */ static void pr_pixbuf_size_sync(PixbufRenderer *pr) { pr->stereo_pixbuf_offset_left = 0; pr->stereo_pixbuf_offset_right = 0; if (!pr->pixbuf) return; switch (pr->orientation) { case EXIF_ORIENTATION_LEFT_TOP: case EXIF_ORIENTATION_RIGHT_TOP: case EXIF_ORIENTATION_RIGHT_BOTTOM: case EXIF_ORIENTATION_LEFT_BOTTOM: pr->image_width = gdk_pixbuf_get_height(pr->pixbuf); pr->image_height = gdk_pixbuf_get_width(pr->pixbuf); if (pr->stereo_data == STEREO_PIXBUF_SBS) { pr->image_height /= 2; pr->stereo_pixbuf_offset_right = pr->image_height; } else if (pr->stereo_data == STEREO_PIXBUF_CROSS) { pr->image_height /= 2; pr->stereo_pixbuf_offset_left = pr->image_height; } break; default: pr->image_width = gdk_pixbuf_get_width(pr->pixbuf); pr->image_height = gdk_pixbuf_get_height(pr->pixbuf); if (pr->stereo_data == STEREO_PIXBUF_SBS) { pr->image_width /= 2; pr->stereo_pixbuf_offset_right = pr->image_width; } else if (pr->stereo_data == STEREO_PIXBUF_CROSS) { pr->image_width /= 2; pr->stereo_pixbuf_offset_left = pr->image_width; } } } static void pr_set_pixbuf(PixbufRenderer *pr, GdkPixbuf *pixbuf, gdouble zoom, PrZoomFlags flags) { if (pixbuf) g_object_ref(pixbuf); if (pr->pixbuf) g_object_unref(pr->pixbuf); pr->pixbuf = pixbuf; if (!pr->pixbuf) { GtkWidget *box; /* no pixbuf so just clear the window */ pr->image_width = 0; pr->image_height = 0; pr->scale = 1.0; pr->zoom = zoom; /* don't throw away the zoom value, it is set by pixbuf_renderer_move, among others, and used for pixbuf_renderer_zoom_get */ box = GTK_WIDGET(pr); #if GTK_CHECK_VERSION(2,20,0) if (gtk_widget_get_realized(box)) #else if (GTK_WIDGET_REALIZED(box)) #endif { gdk_window_clear(box->window); pr->renderer->overlay_draw(pr->renderer, 0, 0, pr->viewport_width, pr->viewport_height); if (pr->renderer2) pr->renderer2->overlay_draw(pr->renderer2, 0, 0, pr->viewport_width, pr->viewport_height); } pr_update_signal(pr); return; } if (pr->stereo_mode & PR_STEREO_TEMP_DISABLE) { gint disable = !pr->pixbuf || ! pr->stereo_data; pr_stereo_temp_disable(pr, disable); } pr_pixbuf_size_sync(pr); pr_zoom_sync(pr, zoom, flags | PR_ZOOM_FORCE | PR_ZOOM_NEW, 0, 0); } void pixbuf_renderer_set_pixbuf(PixbufRenderer *pr, GdkPixbuf *pixbuf, gdouble zoom) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); pr_source_tile_unset(pr); pr_set_pixbuf(pr, pixbuf, zoom, 0); pr_update_signal(pr); } void pixbuf_renderer_set_pixbuf_lazy(PixbufRenderer *pr, GdkPixbuf *pixbuf, gdouble zoom, gint orientation, StereoPixbufData stereo_data) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); pr_source_tile_unset(pr); pr->orientation = orientation; pr->stereo_data = stereo_data; pr_set_pixbuf(pr, pixbuf, zoom, PR_ZOOM_LAZY); pr_update_signal(pr); } GdkPixbuf *pixbuf_renderer_get_pixbuf(PixbufRenderer *pr) { g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), NULL); return pr->pixbuf; } void pixbuf_renderer_set_orientation(PixbufRenderer *pr, gint orientation) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); pr->orientation = orientation; pr_pixbuf_size_sync(pr); pr_zoom_sync(pr, pr->zoom, PR_ZOOM_FORCE, 0, 0); } gint pixbuf_renderer_get_orientation(PixbufRenderer *pr) { if (!pr) return 1; return pr->orientation; } void pixbuf_renderer_set_stereo_data(PixbufRenderer *pr, StereoPixbufData stereo_data) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); pr->stereo_data = stereo_data; pr_pixbuf_size_sync(pr); pr_zoom_sync(pr, pr->zoom, PR_ZOOM_FORCE, 0, 0); } void pixbuf_renderer_set_post_process_func(PixbufRenderer *pr, PixbufRendererPostProcessFunc func, gpointer user_data, gboolean slow) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); pr->func_post_process = func; pr->post_process_user_data = user_data; pr->post_process_slow = func && slow; } void pixbuf_renderer_move(PixbufRenderer *pr, PixbufRenderer *source) { GObject *object; PixbufRendererScrollResetType scroll_reset; g_return_if_fail(IS_PIXBUF_RENDERER(pr)); g_return_if_fail(IS_PIXBUF_RENDERER(source)); if (pr == source) return; object = G_OBJECT(pr); g_object_set(object, "zoom_min", source->zoom_min, NULL); g_object_set(object, "zoom_max", source->zoom_max, NULL); g_object_set(object, "loading", source->loading, NULL); pr->complete = source->complete; pr->x_scroll = source->x_scroll; pr->y_scroll = source->y_scroll; pr->x_mouse = source->x_mouse; pr->y_mouse = source->y_mouse; scroll_reset = pr->scroll_reset; pr->scroll_reset = PR_SCROLL_RESET_NOCHANGE; pr->func_post_process = source->func_post_process; pr->post_process_user_data = source->post_process_user_data; pr->post_process_slow = source->post_process_slow; pr->orientation = source->orientation; pr->stereo_data = source->stereo_data; if (source->source_tiles_enabled) { pr_source_tile_unset(pr); pr->source_tiles_enabled = source->source_tiles_enabled; pr->source_tiles_cache_size = source->source_tiles_cache_size; pr->source_tile_width = source->source_tile_width; pr->source_tile_height = source->source_tile_height; pr->image_width = source->image_width; pr->image_height = source->image_height; pr->func_tile_request = source->func_tile_request; pr->func_tile_dispose = source->func_tile_dispose; pr->func_tile_data = source->func_tile_data; pr->source_tiles = source->source_tiles; source->source_tiles = NULL; pr_zoom_sync(pr, source->zoom, PR_ZOOM_FORCE | PR_ZOOM_NEW, 0, 0); pr_redraw(pr, TRUE); } else { pixbuf_renderer_set_pixbuf(pr, source->pixbuf, source->zoom); } pr->scroll_reset = scroll_reset; pixbuf_renderer_set_pixbuf(source, NULL, source->zoom); // pr_queue_clear(source); // pr_tile_free_all(source); } void pixbuf_renderer_area_changed(PixbufRenderer *pr, gint x, gint y, gint w, gint h) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); if (pr->source_tiles_enabled) { pr_source_tile_changed(pr, x, y, w, h); } pr->renderer->area_changed(pr->renderer, x, y, w, h); if (pr->renderer2) pr->renderer2->area_changed(pr->renderer2, x, y, w, h); } void pixbuf_renderer_zoom_adjust(PixbufRenderer *pr, gdouble increment) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); pr_zoom_adjust_real(pr, increment, PR_ZOOM_NONE, 0, 0); } void pixbuf_renderer_zoom_adjust_at_point(PixbufRenderer *pr, gdouble increment, gint x, gint y) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); pr_zoom_adjust_real(pr, increment, PR_ZOOM_CENTER, x, y); } void pixbuf_renderer_zoom_set(PixbufRenderer *pr, gdouble zoom) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); pr_zoom_sync(pr, zoom, PR_ZOOM_NONE, 0, 0); } gdouble pixbuf_renderer_zoom_get(PixbufRenderer *pr) { g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), 1.0); return pr->zoom; } gdouble pixbuf_renderer_zoom_get_scale(PixbufRenderer *pr) { g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), 1.0); return pr->scale; } void pixbuf_renderer_zoom_set_limits(PixbufRenderer *pr, gdouble min, gdouble max) { g_return_if_fail(IS_PIXBUF_RENDERER(pr)); if (min > 1.0 || max < 1.0) return; if (min < 1.0 && min > -1.0) return; if (min < -200.0 || max > 200.0) return; if (pr->zoom_min != min) { pr->zoom_min = min; g_object_notify(G_OBJECT(pr), "zoom_min"); } if (pr->zoom_max != max) { pr->zoom_max = max; g_object_notify(G_OBJECT(pr), "zoom_max"); } } static void pr_stereo_set(PixbufRenderer *pr) { if (!pr->renderer) pr->renderer = (void *)renderer_tiles_new(pr); pr->renderer->stereo_set(pr->renderer, pr->stereo_mode & ~PR_STEREO_MIRROR_RIGHT & ~PR_STEREO_FLIP_RIGHT); if (pr->stereo_mode & (PR_STEREO_HORIZ | PR_STEREO_VERT | PR_STEREO_FIXED)) { if (!pr->renderer2) pr->renderer2 = (void *)renderer_tiles_new(pr); pr->renderer2->stereo_set(pr->renderer2, (pr->stereo_mode & ~PR_STEREO_MIRROR_LEFT & ~PR_STEREO_FLIP_LEFT) | PR_STEREO_RIGHT); } else { if (pr->renderer2) pr->renderer2->free(pr->renderer2); pr->renderer2 = NULL; } if (pr->stereo_mode & PR_STEREO_HALF) { if (pr->stereo_mode & PR_STEREO_HORIZ) pr->aspect_ratio = 2.0; else if (pr->stereo_mode & PR_STEREO_VERT) pr->aspect_ratio = 0.5; else pr->aspect_ratio = 1.0; } else { pr->aspect_ratio = 1.0; } } void pixbuf_renderer_stereo_set(PixbufRenderer *pr, gint stereo_mode) { gboolean redraw = !(pr->stereo_mode == stereo_mode) || pr->stereo_temp_disable; pr->stereo_mode = stereo_mode; if ((stereo_mode & PR_STEREO_TEMP_DISABLE) && pr->stereo_temp_disable) return; pr->stereo_temp_disable = FALSE; pr_stereo_set(pr); if (redraw) { pr_size_sync(pr, pr->window_width, pr->window_height); /* recalculate new viewport */ pr_zoom_sync(pr, pr->zoom, PR_ZOOM_FORCE | PR_ZOOM_NEW, 0, 0); } } void pixbuf_renderer_stereo_fixed_set(PixbufRenderer *pr, gint width, gint height, gint x1, gint y1, gint x2, gint y2) { pr->stereo_fixed_width = width; pr->stereo_fixed_height = height; pr->stereo_fixed_x_left = x1; pr->stereo_fixed_y_left = y1; pr->stereo_fixed_x_right = x2; pr->stereo_fixed_y_right = y2; } gint pixbuf_renderer_stereo_get(PixbufRenderer *pr) { return pr->stereo_mode; } static void pr_stereo_temp_disable(PixbufRenderer *pr, gboolean disable) { if (pr->stereo_temp_disable == disable) return; pr->stereo_temp_disable = disable; if (disable) { if (!pr->renderer) pr->renderer = (void *)renderer_tiles_new(pr); pr->renderer->stereo_set(pr->renderer, PR_STEREO_NONE); if (pr->renderer2) pr->renderer2->free(pr->renderer2); pr->renderer2 = NULL; pr->aspect_ratio = 1.0; } else { pr_stereo_set(pr); } pr_size_sync(pr, pr->window_width, pr->window_height); /* recalculate new viewport */ } gboolean pixbuf_renderer_get_pixel_colors(PixbufRenderer *pr, gint x_pixel, gint y_pixel, gint *r_mouse, gint *g_mouse, gint *b_mouse) { GdkPixbuf *pb = pr->pixbuf; gint p_alpha, prs; guchar *p_pix, *pp; gint map_x, map_y, map_w, map_h; g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE); g_return_val_if_fail(r_mouse != NULL && g_mouse != NULL && b_mouse != NULL, FALSE); if (!pr->pixbuf && !pr->source_tiles_enabled) { *r_mouse = -1; *g_mouse = -1; *b_mouse = -1; return FALSE; } if (!pb) return FALSE; pr_tile_region_map_orientation(pr->orientation, x_pixel, y_pixel, pr->image_width, pr->image_height, 1, 1, /*single pixel */ &map_x, &map_y, &map_w, &map_h); if (map_x < 0 || map_x > gdk_pixbuf_get_width(pr->pixbuf) - 1) return FALSE; if (map_y < 0 || map_y > gdk_pixbuf_get_height(pr->pixbuf) - 1) return FALSE; p_alpha = gdk_pixbuf_get_has_alpha(pb); prs = gdk_pixbuf_get_rowstride(pb); p_pix = gdk_pixbuf_get_pixels(pb); pp = p_pix + map_y * prs + (map_x * (p_alpha ? 4 : 3)); *r_mouse = *pp; pp++; *g_mouse = *pp; pp++; *b_mouse = *pp; return TRUE; } gboolean pixbuf_renderer_get_mouse_position(PixbufRenderer *pr, gint *x_pixel_return, gint *y_pixel_return) { gint x_pixel, y_pixel, x_pixel_clamped, y_pixel_clamped; g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE); g_return_val_if_fail(x_pixel_return != NULL && y_pixel_return != NULL, FALSE); if (!pr->pixbuf && !pr->source_tiles_enabled) { *x_pixel_return = -1; *y_pixel_return = -1; return FALSE; } x_pixel = floor((gdouble)(pr->x_mouse - pr->x_offset + pr->x_scroll) / pr->scale); y_pixel = floor((gdouble)(pr->y_mouse - pr->y_offset + pr->y_scroll) / pr->scale / pr->aspect_ratio); x_pixel_clamped = CLAMP(x_pixel, 0, pr->image_width - 1); y_pixel_clamped = CLAMP(y_pixel, 0, pr->image_height - 1); if(x_pixel != x_pixel_clamped || y_pixel != y_pixel_clamped) { /* mouse is not on pr */ x_pixel = y_pixel = -1; } *x_pixel_return = x_pixel; *y_pixel_return = y_pixel; return TRUE; } gboolean pixbuf_renderer_get_image_size(PixbufRenderer *pr, gint *width, gint *height) { g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE); g_return_val_if_fail(width != NULL && height != NULL, FALSE); if (!pr->pixbuf && !pr->source_tiles_enabled && (!pr->image_width || !pr->image_height)) { *width = 0; *height = 0; return FALSE; } *width = pr->image_width; *height = pr->image_height; return TRUE; } gboolean pixbuf_renderer_get_scaled_size(PixbufRenderer *pr, gint *width, gint *height) { g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE); g_return_val_if_fail(width != NULL && height != NULL, FALSE); if (!pr->pixbuf && !pr->source_tiles_enabled && (!pr->image_width || !pr->image_height)) { *width = 0; *height = 0; return FALSE; } *width = pr->width; *height = pr->height; return TRUE; } gboolean pixbuf_renderer_get_visible_rect(PixbufRenderer *pr, GdkRectangle *rect) { g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE); g_return_val_if_fail(rect != NULL, FALSE); if ((!pr->pixbuf && !pr->source_tiles_enabled) || !pr->scale) { rect->x = 0; rect->y = 0; rect->width = 0; rect->height = 0; return FALSE; } rect->x = (gint)((gdouble)pr->x_scroll / pr->scale); rect->y = (gint)((gdouble)pr->y_scroll / pr->scale / pr->aspect_ratio); rect->width = (gint)((gdouble)pr->vis_width / pr->scale); rect->height = (gint)((gdouble)pr->vis_height / pr->scale / pr->aspect_ratio); return TRUE; } gboolean pixbuf_renderer_get_virtual_rect(PixbufRenderer *pr, GdkRectangle *rect) { g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE); g_return_val_if_fail(rect != NULL, FALSE); if ((!pr->pixbuf && !pr->source_tiles_enabled)) { rect->x = 0; rect->y = 0; rect->width = 0; rect->height = 0; return FALSE; } rect->x = pr->x_scroll; rect->y = pr->y_scroll; rect->width = pr->vis_width; rect->height = pr->vis_height; return TRUE; } void pixbuf_renderer_set_size_early(PixbufRenderer *pr, guint width, guint height) { gdouble zoom; gint w, h; zoom = pixbuf_renderer_zoom_get(pr); pr->image_width = width; pr->image_height = height; pr_zoom_clamp(pr, zoom, PR_ZOOM_FORCE, NULL); //w = width; //h = height; //pr->width = width; //pr->height = height; } /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */