added possibility to redraw only the parts of image that are already
[geeqie.git] / src / pixbuf-renderer.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <math.h>
17
18 #include "pixbuf-renderer.h"
19 #include "intl.h"
20
21 #include <gtk/gtk.h>
22
23
24 /* comment this out if not using this from within Geeqie
25  * defining GQ_BUILD does these things:
26  *   - Sets the shift-click scroller pixbuf to a nice icon instead of a black box
27  */
28 #define GQ_BUILD 1
29
30 #ifdef GQ_BUILD
31 #include "main.h"
32 #include "pixbuf_util.h"
33 #include "exif.h"
34 #else
35 typedef enum {
36         EXIF_ORIENTATION_UNKNOWN        = 0,
37         EXIF_ORIENTATION_TOP_LEFT       = 1,
38         EXIF_ORIENTATION_TOP_RIGHT      = 2,
39         EXIF_ORIENTATION_BOTTOM_RIGHT   = 3,
40         EXIF_ORIENTATION_BOTTOM_LEFT    = 4,
41         EXIF_ORIENTATION_LEFT_TOP       = 5,
42         EXIF_ORIENTATION_RIGHT_TOP      = 6,
43         EXIF_ORIENTATION_RIGHT_BOTTOM   = 7,
44         EXIF_ORIENTATION_LEFT_BOTTOM    = 8
45 } ExifOrientationType;
46 #endif
47
48
49 /* size to use when breaking up image pane for rendering */
50 #define PR_TILE_SIZE 128
51
52 /* default size of tile cache (mb) */
53 #define PR_CACHE_SIZE_DEFAULT 8
54
55 /* default min and max zoom */
56 #define PR_ZOOM_MIN -32.0
57 #define PR_ZOOM_MAX 32.0
58
59 /* distance to drag mouse to disable image flip */
60 #define PR_DRAG_SCROLL_THRESHHOLD 4
61
62 /* increase pan rate when holding down shift */
63 #define PR_PAN_SHIFT_MULTIPLIER 6
64
65 /* scroller config */
66 #define PR_SCROLLER_UPDATES_PER_SEC 30
67 #define PR_SCROLLER_DEAD_ZONE 6
68
69 /* alpha channel checkerboard background (same as gimp) */
70 #define PR_ALPHA_CHECK1 0x00999999
71 #define PR_ALPHA_CHECK2 0x00666666
72 #define PR_ALPHA_CHECK_SIZE 16
73
74 /* when scaling image to below this size, use nearest pixel for scaling
75  * (below about 4, the other scale types become slow generating their conversion tables)
76  */
77 #define PR_MIN_SCALE_SIZE 8
78
79 typedef enum {
80         TILE_RENDER_NONE = 0,   /* do nothing */
81         TILE_RENDER_AREA,       /* render an area of the tile */
82         TILE_RENDER_ALL         /* render the whole tile */
83 } ImageTileRenderType;
84
85 typedef struct _ImageTile ImageTile;
86 typedef struct _QueueData QueueData;
87
88 struct _ImageTile
89 {
90         GdkPixmap *pixmap;      /* off screen buffer */
91         GdkPixbuf *pixbuf;      /* pixbuf area for zooming */
92         gint x;                 /* x offset into image */
93         gint y;                 /* y offset into image */
94         gint w;                 /* width that is visible (may be less if at edge of image) */
95         gint h;                 /* height '' */
96
97         gboolean blank;
98
99 /* render_todo: (explanation)
100         NONE    do nothing
101         AREA    render area of tile, usually only used when loading an image
102                 note: will jump to an ALL if render_done is not ALL.
103         ALL     render entire tile, if never done before w/ ALL, for expose events *only*
104 */
105
106         ImageTileRenderType render_todo;        /* what to do (see above) */
107         ImageTileRenderType render_done;        /* highest that has been done before on tile */
108
109         QueueData *qd;
110         QueueData *qd2;
111
112         guint size;             /* est. memory used by pixmap and pixbuf */
113 };
114
115 struct _QueueData
116 {
117         ImageTile *it;
118         gint x;
119         gint y;
120         gint w;
121         gint h;
122         gboolean new_data;
123 };
124
125 typedef struct _SourceTile SourceTile;
126 struct _SourceTile
127 {
128         gint x;
129         gint y;
130         GdkPixbuf *pixbuf;
131         gboolean blank;
132 };
133
134 typedef struct _OverlayData OverlayData;
135 struct _OverlayData
136 {
137         gint id;
138
139         GdkPixbuf *pixbuf;
140         GdkWindow *window;
141
142         gint x;
143         gint y;
144
145         OverlayRendererFlags flags;
146 };
147
148 enum {
149         SIGNAL_ZOOM = 0,
150         SIGNAL_CLICKED,
151         SIGNAL_SCROLL_NOTIFY,
152         SIGNAL_RENDER_COMPLETE,
153         SIGNAL_DRAG,
154         SIGNAL_COUNT
155 };
156
157 enum {
158         PROP_0,
159         PROP_ZOOM_MIN,
160         PROP_ZOOM_MAX,
161         PROP_ZOOM_QUALITY,
162         PROP_ZOOM_2PASS,
163         PROP_ZOOM_EXPAND,
164         PROP_DITHER_QUALITY,
165         PROP_SCROLL_RESET,
166         PROP_DELAY_FLIP,
167         PROP_LOADING,
168         PROP_COMPLETE,
169         PROP_CACHE_SIZE_DISPLAY,
170         PROP_CACHE_SIZE_TILES,
171         PROP_WINDOW_FIT,
172         PROP_WINDOW_LIMIT,
173         PROP_WINDOW_LIMIT_VALUE,
174         PROP_AUTOFIT_LIMIT,
175         PROP_AUTOFIT_LIMIT_VALUE
176 };
177
178 typedef enum {
179         PR_ZOOM_NONE            = 0,
180         PR_ZOOM_FORCE           = 1 << 0,
181         PR_ZOOM_NEW             = 1 << 1,
182         PR_ZOOM_CENTER          = 1 << 2,
183         PR_ZOOM_INVALIDATE      = 1 << 3,
184         PR_ZOOM_LAZY            = 1 << 4  /* wait with redraw for pixbuf_renderer_area_changed */
185 } PrZoomFlags;
186
187 static guint signals[SIGNAL_COUNT] = { 0 };
188 static GtkEventBoxClass *parent_class = NULL;
189
190
191
192 static void pixbuf_renderer_class_init(PixbufRendererClass *class);
193 static void pixbuf_renderer_init(PixbufRenderer *pr);
194 static void pixbuf_renderer_finalize(GObject *object);
195 static void pixbuf_renderer_set_property(GObject *object, guint prop_id,
196                                          const GValue *value, GParamSpec *pspec);
197 static void pixbuf_renderer_get_property(GObject *object, guint prop_id,
198                                          GValue *value, GParamSpec *pspec);
199 static gint pixbuf_renderer_expose(GtkWidget *widget, GdkEventExpose *event);
200
201 static void pr_render_complete_signal(PixbufRenderer *pr);
202
203 static void pr_overlay_list_clear(PixbufRenderer *pr);
204 static void pr_scroller_timer_set(PixbufRenderer *pr, gint start);
205 static void pr_border_draw(PixbufRenderer *pr, gint x, gint y, gint w, gint h);
206
207
208 static void pr_source_tile_free_all(PixbufRenderer *pr);
209 static void pr_tile_free_all(PixbufRenderer *pr);
210 static void pr_tile_invalidate_region(PixbufRenderer *pr, gint x, gint y, gint w, gint h);
211 static gint pr_tile_is_visible(PixbufRenderer *pr, ImageTile *it);
212 static void pr_queue_clear(PixbufRenderer *pr);
213 static void pr_queue_merge(QueueData *parent, QueueData *qd);
214 static void pr_queue(PixbufRenderer *pr, gint x, gint y, gint w, gint h,
215                      gint clamp, ImageTileRenderType render, gint new_data, gint only_existing);
216
217 static void pr_redraw(PixbufRenderer *pr, gint new_data);
218
219 static void pr_zoom_sync(PixbufRenderer *pr, gdouble zoom,
220                          PrZoomFlags flags, gint px, gint py);
221
222 static void pr_signals_connect(PixbufRenderer *pr);
223 static void pr_size_cb(GtkWidget *widget, GtkAllocation *allocation, gpointer data);
224 static void pixbuf_renderer_paint(PixbufRenderer *pr, GdkRectangle *area);
225 static gint pr_queue_draw_idle_cb(gpointer data);
226
227
228 /*
229  *-------------------------------------------------------------------
230  * Pixbuf Renderer object
231  *-------------------------------------------------------------------
232  */
233
234 GType pixbuf_renderer_get_type(void)
235 {
236         static GType pixbuf_renderer_type = 0;
237
238         if (!pixbuf_renderer_type)
239                 {
240                 static const GTypeInfo pixbuf_renderer_info =
241                         {
242                         sizeof(PixbufRendererClass), /* class_size */
243                         NULL,           /* base_init */
244                         NULL,           /* base_finalize */
245                         (GClassInitFunc)pixbuf_renderer_class_init,
246                         NULL,           /* class_finalize */
247                         NULL,           /* class_data */
248                         sizeof(PixbufRenderer), /* instance_size */
249                         0,              /* n_preallocs */
250                         (GInstanceInitFunc)pixbuf_renderer_init, /* instance_init */
251                         NULL,           /* value_table */
252                         };
253
254                 pixbuf_renderer_type = g_type_register_static(GTK_TYPE_EVENT_BOX, "PixbufRenderer",
255                                                               &pixbuf_renderer_info, 0);
256                 }
257
258         return pixbuf_renderer_type;
259 }
260
261 static void pixbuf_renderer_class_init(PixbufRendererClass *class)
262 {
263         GObjectClass *gobject_class = G_OBJECT_CLASS(class);
264         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(class);
265
266         parent_class = g_type_class_peek_parent(class);
267
268         gobject_class->set_property = pixbuf_renderer_set_property;
269         gobject_class->get_property = pixbuf_renderer_get_property;
270
271         gobject_class->finalize = pixbuf_renderer_finalize;
272
273         widget_class->expose_event = pixbuf_renderer_expose;
274
275         g_object_class_install_property(gobject_class,
276                                         PROP_ZOOM_MIN,
277                                         g_param_spec_double("zoom_min",
278                                                             "Zoom minimum",
279                                                             NULL,
280                                                             -1000.0,
281                                                             1000.0,
282                                                             PR_ZOOM_MIN,
283                                                             G_PARAM_READABLE | G_PARAM_WRITABLE));
284
285         g_object_class_install_property(gobject_class,
286                                         PROP_ZOOM_MAX,
287                                         g_param_spec_double("zoom_max",
288                                                             "Zoom maximum",
289                                                             NULL,
290                                                             -1000.0,
291                                                             1000.0,
292                                                             PR_ZOOM_MIN,
293                                                             G_PARAM_READABLE | G_PARAM_WRITABLE));
294
295         g_object_class_install_property(gobject_class,
296                                         PROP_ZOOM_QUALITY,
297                                         g_param_spec_uint("zoom_quality",
298                                                           "Zoom quality",
299                                                           NULL,
300                                                           GDK_INTERP_NEAREST,
301                                                           GDK_INTERP_HYPER,
302                                                           GDK_INTERP_BILINEAR,
303                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
304
305         g_object_class_install_property(gobject_class,
306                                         PROP_ZOOM_2PASS,
307                                         g_param_spec_boolean("zoom_2pass",
308                                                              "2 pass zoom",
309                                                              NULL,
310                                                              TRUE,
311                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
312
313         g_object_class_install_property(gobject_class,
314                                         PROP_ZOOM_EXPAND,
315                                         g_param_spec_boolean("zoom_expand",
316                                                              "Expand image in autozoom.",
317                                                              NULL,
318                                                              FALSE,
319                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
320
321         g_object_class_install_property(gobject_class,
322                                         PROP_DITHER_QUALITY,
323                                         g_param_spec_uint("dither_quality",
324                                                           "Dither quality",
325                                                           NULL,
326                                                           GDK_RGB_DITHER_NONE,
327                                                           GDK_RGB_DITHER_MAX,
328                                                           GDK_RGB_DITHER_NORMAL,
329                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
330
331         g_object_class_install_property(gobject_class,
332                                         PROP_SCROLL_RESET,
333                                         g_param_spec_uint("scroll_reset",
334                                                           "New image scroll reset",
335                                                           NULL,
336                                                           PR_SCROLL_RESET_TOPLEFT,
337                                                           PR_SCROLL_RESET_NOCHANGE,
338                                                           PR_SCROLL_RESET_TOPLEFT,
339                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
340
341         g_object_class_install_property(gobject_class,
342                                         PROP_DELAY_FLIP,
343                                         g_param_spec_boolean("delay_flip",
344                                                              "Delay image update",
345                                                              NULL,
346                                                              FALSE,
347                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
348
349         g_object_class_install_property(gobject_class,
350                                         PROP_LOADING,
351                                         g_param_spec_boolean("loading",
352                                                              "Image actively loading",
353                                                              NULL,
354                                                              FALSE,
355                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
356
357         g_object_class_install_property(gobject_class,
358                                         PROP_COMPLETE,
359                                         g_param_spec_boolean("complete",
360                                                              "Image rendering complete",
361                                                              NULL,
362                                                              FALSE,
363                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
364
365         g_object_class_install_property(gobject_class,
366                                         PROP_CACHE_SIZE_DISPLAY,
367                                         g_param_spec_uint("cache_display",
368                                                           "Display cache size MB",
369                                                           NULL,
370                                                           0,
371                                                           128,
372                                                           PR_CACHE_SIZE_DEFAULT,
373                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
374
375         g_object_class_install_property(gobject_class,
376                                         PROP_CACHE_SIZE_TILES,
377                                         g_param_spec_uint("cache_tiles",
378                                                           "Tile cache count",
379                                                           "Number of tiles to retain in memory at any one time.",
380                                                           0,
381                                                           256,
382                                                           PR_CACHE_SIZE_DEFAULT,
383                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
384
385         g_object_class_install_property(gobject_class,
386                                         PROP_WINDOW_FIT,
387                                         g_param_spec_boolean("window_fit",
388                                                              "Fit window to image size",
389                                                              NULL,
390                                                              FALSE,
391                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
392
393         g_object_class_install_property(gobject_class,
394                                         PROP_WINDOW_LIMIT,
395                                         g_param_spec_boolean("window_limit",
396                                                              "Limit size of parent window",
397                                                              NULL,
398                                                              FALSE,
399                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
400
401         g_object_class_install_property(gobject_class,
402                                         PROP_WINDOW_LIMIT_VALUE,
403                                         g_param_spec_uint("window_limit_value",
404                                                           "Size limit of parent window",
405                                                           NULL,
406                                                           10,
407                                                           150,
408                                                           100,
409                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
410
411         g_object_class_install_property(gobject_class,
412                                         PROP_AUTOFIT_LIMIT,
413                                         g_param_spec_boolean("autofit_limit",
414                                                              "Limit size of image when autofitting",
415                                                              NULL,
416                                                              FALSE,
417                                                              G_PARAM_READABLE | G_PARAM_WRITABLE));
418
419         g_object_class_install_property(gobject_class,
420                                         PROP_AUTOFIT_LIMIT_VALUE,
421                                         g_param_spec_uint("autofit_limit_value",
422                                                           "Size limit of image when autofitting",
423                                                           NULL,
424                                                           10,
425                                                           150,
426                                                           100,
427                                                           G_PARAM_READABLE | G_PARAM_WRITABLE));
428
429
430         signals[SIGNAL_ZOOM] =
431                 g_signal_new("zoom",
432                              G_OBJECT_CLASS_TYPE(gobject_class),
433                              G_SIGNAL_RUN_LAST,
434                              G_STRUCT_OFFSET(PixbufRendererClass, zoom),
435                              NULL, NULL,
436                              g_cclosure_marshal_VOID__DOUBLE,
437                              G_TYPE_NONE, 1,
438                              G_TYPE_DOUBLE);
439
440         signals[SIGNAL_CLICKED] =
441                 g_signal_new("clicked",
442                              G_OBJECT_CLASS_TYPE(gobject_class),
443                              G_SIGNAL_RUN_LAST,
444                              G_STRUCT_OFFSET(PixbufRendererClass, clicked),
445                              NULL, NULL,
446                              g_cclosure_marshal_VOID__BOXED,
447                              G_TYPE_NONE, 1,
448                              GDK_TYPE_EVENT);
449
450         signals[SIGNAL_SCROLL_NOTIFY] =
451                 g_signal_new("scroll-notify",
452                              G_OBJECT_CLASS_TYPE(gobject_class),
453                              G_SIGNAL_RUN_LAST,
454                              G_STRUCT_OFFSET(PixbufRendererClass, scroll_notify),
455                              NULL, NULL,
456                              g_cclosure_marshal_VOID__VOID,
457                              G_TYPE_NONE, 0);
458
459         signals[SIGNAL_RENDER_COMPLETE] =
460                 g_signal_new("render-complete",
461                              G_OBJECT_CLASS_TYPE(gobject_class),
462                              G_SIGNAL_RUN_LAST,
463                              G_STRUCT_OFFSET(PixbufRendererClass, render_complete),
464                              NULL, NULL,
465                              g_cclosure_marshal_VOID__VOID,
466                              G_TYPE_NONE, 0);
467
468         signals[SIGNAL_DRAG] =
469                 g_signal_new("drag",
470                              G_OBJECT_CLASS_TYPE(gobject_class),
471                              G_SIGNAL_RUN_LAST,
472                              G_STRUCT_OFFSET(PixbufRendererClass, drag),
473                              NULL, NULL,
474                              g_cclosure_marshal_VOID__BOXED,
475                              G_TYPE_NONE, 1,
476                              GDK_TYPE_EVENT);
477 }
478
479 static void pixbuf_renderer_init(PixbufRenderer *pr)
480 {
481         GtkWidget *box;
482
483         box = GTK_WIDGET(pr);
484
485         pr->zoom_min = PR_ZOOM_MIN;
486         pr->zoom_max = PR_ZOOM_MAX;
487         pr->zoom_quality = GDK_INTERP_BILINEAR;
488         pr->zoom_2pass = FALSE;
489
490         pr->zoom = 1.0;
491         pr->scale = 1.0;
492
493         pr->dither_quality = GDK_RGB_DITHER_NORMAL;
494
495         pr->scroll_reset = PR_SCROLL_RESET_TOPLEFT;
496
497         pr->draw_idle_id = -1;
498
499         pr->tile_width = PR_TILE_SIZE;
500         pr->tile_height = PR_TILE_SIZE;
501
502         pr->tiles = NULL;
503         pr->tile_cache_size = 0;
504
505         pr->tile_cache_max = PR_CACHE_SIZE_DEFAULT;
506
507         pr->scroller_id = -1;
508         pr->scroller_overlay = -1;
509
510         pr->source_tiles_enabled = FALSE;
511         pr->source_tiles = NULL;
512
513         pr->orientation = 1;
514
515         pr->norm_center_x = 0.5;
516         pr->norm_center_y = 0.5;
517
518         gtk_widget_set_double_buffered(box, FALSE);
519         g_signal_connect_after(G_OBJECT(box), "size_allocate",
520                                G_CALLBACK(pr_size_cb), pr);
521
522         pr_signals_connect(pr);
523 }
524
525 static void pixbuf_renderer_finalize(GObject *object)
526 {
527         PixbufRenderer *pr;
528
529         pr = PIXBUF_RENDERER(object);
530
531         pr_queue_clear(pr);
532         pr_tile_free_all(pr);
533
534
535         if (pr->pixbuf) g_object_unref(pr->pixbuf);
536         if (pr->spare_tile) g_object_unref(pr->spare_tile);
537
538         pr_scroller_timer_set(pr, FALSE);
539         pr_overlay_list_clear(pr);
540
541         pr_source_tile_free_all(pr);
542 }
543
544 PixbufRenderer *pixbuf_renderer_new(void)
545 {
546         return g_object_new(TYPE_PIXBUF_RENDERER, NULL);
547 }
548
549 static void pixbuf_renderer_set_property(GObject *object, guint prop_id,
550                                          const GValue *value, GParamSpec *pspec)
551 {
552         PixbufRenderer *pr;
553
554         pr = PIXBUF_RENDERER(object);
555
556         switch (prop_id)
557                 {
558                 case PROP_ZOOM_MIN:
559                         pr->zoom_min = g_value_get_double(value);
560                         break;
561                 case PROP_ZOOM_MAX:
562                         pr->zoom_max = g_value_get_double(value);
563                         break;
564                 case PROP_ZOOM_QUALITY:
565                         pr->zoom_quality = g_value_get_uint(value);
566                         break;
567                 case PROP_ZOOM_2PASS:
568                         pr->zoom_2pass = g_value_get_boolean(value);
569                         break;
570                 case PROP_ZOOM_EXPAND:
571                         pr->zoom_expand = g_value_get_boolean(value);
572                         break;
573                 case PROP_DITHER_QUALITY:
574                         pr->dither_quality = g_value_get_uint(value);
575                         break;
576                 case PROP_SCROLL_RESET:
577                         pr->scroll_reset = g_value_get_uint(value);
578                         break;
579                 case PROP_DELAY_FLIP:
580                         pr->delay_flip = g_value_get_boolean(value);
581                         break;
582                 case PROP_LOADING:
583                         pr->loading = g_value_get_boolean(value);
584                         break;
585                 case PROP_COMPLETE:
586                         pr->complete = g_value_get_boolean(value);
587                         break;
588                 case PROP_CACHE_SIZE_DISPLAY:
589                         pr->tile_cache_max = g_value_get_uint(value);
590                         break;
591                 case PROP_CACHE_SIZE_TILES:
592                         pr->source_tiles_cache_size = g_value_get_uint(value);
593                         break;
594                 case PROP_WINDOW_FIT:
595                         pr->window_fit = g_value_get_boolean(value);
596                         break;
597                 case PROP_WINDOW_LIMIT:
598                         pr->window_limit = g_value_get_boolean(value);
599                         break;
600                 case PROP_WINDOW_LIMIT_VALUE:
601                         pr->window_limit_size = g_value_get_uint(value);
602                         break;
603                 case PROP_AUTOFIT_LIMIT:
604                         pr->autofit_limit = g_value_get_boolean(value);
605                         break;
606                 case PROP_AUTOFIT_LIMIT_VALUE:
607                         pr->autofit_limit_size = g_value_get_uint(value);
608                         break;
609                 default:
610                         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
611                         break;
612                 }
613 }
614
615 static void pixbuf_renderer_get_property(GObject *object, guint prop_id,
616                                          GValue *value, GParamSpec *pspec)
617 {
618         PixbufRenderer *pr;
619
620         pr = PIXBUF_RENDERER(object);
621
622         switch (prop_id)
623                 {
624                 case PROP_ZOOM_MIN:
625                         g_value_set_double(value, pr->zoom_min);
626                         break;
627                 case PROP_ZOOM_MAX:
628                         g_value_set_double(value, pr->zoom_max);
629                         break;
630                 case PROP_ZOOM_QUALITY:
631                         g_value_set_uint(value, pr->zoom_quality);
632                         break;
633                 case PROP_ZOOM_2PASS:
634                         g_value_set_boolean(value, pr->zoom_2pass);
635                         break;
636                 case PROP_ZOOM_EXPAND:
637                         g_value_set_boolean(value, pr->zoom_expand);
638                         break;
639                 case PROP_DITHER_QUALITY:
640                         g_value_set_uint(value, pr->dither_quality);
641                         break;
642                 case PROP_SCROLL_RESET:
643                         g_value_set_uint(value, pr->scroll_reset);
644                         break;
645                 case PROP_DELAY_FLIP:
646                         g_value_set_boolean(value, pr->delay_flip);
647                         break;
648                 case PROP_LOADING:
649                         g_value_set_boolean(value, pr->loading);
650                         break;
651                 case PROP_COMPLETE:
652                         g_value_set_boolean(value, pr->complete);
653                         break;
654                 case PROP_CACHE_SIZE_DISPLAY:
655                         g_value_set_uint(value, pr->tile_cache_max);
656                         break;
657                 case PROP_CACHE_SIZE_TILES:
658                         g_value_set_uint(value, pr->source_tiles_cache_size);
659                         break;
660                 case PROP_WINDOW_FIT:
661                         g_value_set_boolean(value, pr->window_fit);
662                         break;
663                 case PROP_WINDOW_LIMIT:
664                         g_value_set_boolean(value, pr->window_limit);
665                         break;
666                 case PROP_WINDOW_LIMIT_VALUE:
667                         g_value_set_uint(value, pr->window_limit_size);
668                         break;
669                 case PROP_AUTOFIT_LIMIT:
670                         g_value_set_boolean(value, pr->autofit_limit);
671                         break;
672                 case PROP_AUTOFIT_LIMIT_VALUE:
673                         g_value_set_uint(value, pr->autofit_limit_size);
674                         break;
675                 default:
676                         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
677                         break;
678                 }
679 }
680
681 static gint pixbuf_renderer_expose(GtkWidget *widget, GdkEventExpose *event)
682 {
683         if (GTK_WIDGET_DRAWABLE(widget))
684                 {
685                 if (!GTK_WIDGET_NO_WINDOW(widget))
686                         {
687                         if (event->window != widget->window)
688                                 {
689                                 GdkRectangle area;
690
691                                 gdk_window_get_position(event->window, &area.x, &area.y);
692
693                                 area.x += event->area.x;
694                                 area.y += event->area.y;
695                                 area.width = event->area.width;
696                                 area.height = event->area.height;
697                                 pixbuf_renderer_paint(PIXBUF_RENDERER(widget), &area);
698                                 }
699                         else
700                                 {
701                                 pixbuf_renderer_paint(PIXBUF_RENDERER(widget), &event->area);
702                                 }
703                         }
704                 }
705
706         return FALSE;
707 }
708
709 /*
710  *-------------------------------------------------------------------
711  * misc utilities
712  *-------------------------------------------------------------------
713  */
714
715 static void widget_set_cursor(GtkWidget *widget, gint icon)
716 {
717         GdkCursor *cursor;
718
719         if (!widget->window) return;
720
721         if (icon == -1)
722                 {
723                 cursor = NULL;
724                 }
725         else
726                 {
727                 cursor = gdk_cursor_new(icon);
728                 }
729
730         gdk_window_set_cursor(widget->window, cursor);
731
732         if (cursor) gdk_cursor_unref(cursor);
733 }
734
735 static gint pixmap_calc_size(GdkPixmap *pixmap)
736 {
737         gint w, h, d;
738
739         d = gdk_drawable_get_depth(pixmap);
740         gdk_drawable_get_size(pixmap, &w, &h);
741         return w * h * (d / 8);
742 }
743
744 static gint pr_clip_region(gint x, gint y, gint w, gint h,
745                            gint clip_x, gint clip_y, gint clip_w, gint clip_h,
746                            gint *rx, gint *ry, gint *rw, gint *rh)
747 {
748         if (clip_x + clip_w <= x ||
749             clip_x >= x + w ||
750             clip_y + clip_h <= y ||
751             clip_y >= y + h)
752                 {
753                 return FALSE;
754                 }
755
756         *rx = MAX(x, clip_x);
757         *rw = MIN((x + w), (clip_x + clip_w)) - *rx;
758
759         *ry = MAX(y, clip_y);
760         *rh = MIN((y + h), (clip_y + clip_h)) - *ry;
761
762         return TRUE;
763 }
764
765 static gint pr_parent_window_sizable(PixbufRenderer *pr)
766 {
767         GdkWindowState state;
768
769         if (!pr->parent_window) return FALSE;
770         if (!pr->window_fit) return FALSE;
771         if (!GTK_WIDGET(pr)->window) return FALSE;
772
773         if (!pr->parent_window->window) return FALSE;
774         state = gdk_window_get_state(pr->parent_window->window);
775         if (state & GDK_WINDOW_STATE_MAXIMIZED) return FALSE;
776
777         return TRUE;
778 }
779
780 static gint pr_parent_window_resize(PixbufRenderer *pr, gint w, gint h)
781 {
782         GtkWidget *widget;
783         GtkWidget *parent;
784         gint ww, wh;
785
786         if (!pr_parent_window_sizable(pr)) return FALSE;
787
788         if (pr->window_limit)
789                 {
790                 gint sw = gdk_screen_width() * pr->window_limit_size / 100;
791                 gint sh = gdk_screen_height() * pr->window_limit_size / 100;
792
793                 if (w > sw) w = sw;
794                 if (h > sh) h = sh;
795                 }
796
797         widget = GTK_WIDGET(pr);
798         parent = GTK_WIDGET(pr->parent_window);
799
800         w += (parent->allocation.width - widget->allocation.width);
801         h += (parent->allocation.height - widget->allocation.height);
802
803         gdk_drawable_get_size(parent->window, &ww, &wh);
804         if (w == ww && h == wh) return FALSE;
805
806         gdk_window_resize(parent->window, w, h);
807
808         return TRUE;
809 }
810
811 void pixbuf_renderer_set_parent(PixbufRenderer *pr, GtkWindow *window)
812 {
813         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
814         g_return_if_fail(window == NULL || GTK_IS_WINDOW(window));
815
816         pr->parent_window = GTK_WIDGET(window);
817 }
818
819 GtkWindow *pixbuf_renderer_get_parent(PixbufRenderer *pr)
820 {
821         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), NULL);
822
823         return GTK_WINDOW(pr->parent_window);
824 }
825
826
827 /*
828  *-------------------------------------------------------------------
829  * overlays
830  *-------------------------------------------------------------------
831  */
832
833 static void pr_overlay_get_position(PixbufRenderer *pr, OverlayData *od,
834                                     gint *x, gint *y, gint *w, gint *h)
835 {
836         gint px, py, pw, ph;
837
838         pw = gdk_pixbuf_get_width(od->pixbuf);
839         ph = gdk_pixbuf_get_height(od->pixbuf);
840         px = od->x;
841         py = od->y;
842
843         if (od->flags & OVL_RELATIVE)
844                 {
845                 if (px < 0) px = pr->window_width - pw + px;
846                 if (py < 0) py = pr->window_height - ph + py;
847                 }
848
849         if (x) *x = px;
850         if (y) *y = py;
851         if (w) *w = pw;
852         if (h) *h = ph;
853 }
854
855 static void pr_overlay_draw(PixbufRenderer *pr, gint x, gint y, gint w, gint h,
856                             ImageTile *it)
857 {
858         GtkWidget *box;
859         GList *work;
860
861         box = GTK_WIDGET(pr);
862
863         work = pr->overlay_list;
864         while (work)
865                 {
866                 OverlayData *od;
867                 gint px, py, pw, ph;
868                 gint rx, ry, rw, rh;
869
870                 od = work->data;
871                 work = work->next;
872
873                 pr_overlay_get_position(pr, od, &px, &py, &pw, &ph);
874                 if (pr_clip_region(x, y, w, h, px, py, pw, ph, &rx, &ry, &rw, &rh))
875                         {
876                         if (!pr->overlay_buffer)
877                                 {
878                                 pr->overlay_buffer = gdk_pixmap_new(((GtkWidget *)pr)->window, pr->tile_width, pr->tile_height, -1);
879                                 }
880
881                         if (it)
882                                 {
883                                 gdk_draw_drawable(pr->overlay_buffer, box->style->fg_gc[GTK_WIDGET_STATE(box)],
884                                                   it->pixmap,
885                                                   rx - (pr->x_offset + (it->x - pr->x_scroll)),
886                                                   ry - (pr->y_offset + (it->y - pr->y_scroll)),
887                                                   0, 0, rw, rh);
888                                 gdk_draw_pixbuf(pr->overlay_buffer,
889                                                 box->style->fg_gc[GTK_WIDGET_STATE(box)],
890                                                 od->pixbuf,
891                                                 rx - px, ry - py,
892                                                 0, 0, rw, rh,
893                                                 pr->dither_quality, rx, ry);
894                                 gdk_draw_drawable(od->window, box->style->fg_gc[GTK_WIDGET_STATE(box)],
895                                                   pr->overlay_buffer,
896                                                   0, 0,
897                                                   rx - px, ry - py, rw, rh);
898                                 }
899                         else
900                                 {
901                                 /* no ImageTile means region may be larger than our scratch buffer */
902                                 gint sx, sy;
903
904                                 for (sx = rx; sx < rx + rw; sx += pr->tile_width)
905                                     for (sy = ry; sy < ry + rh; sy += pr->tile_height)
906                                         {
907                                         gint sw, sh;
908
909                                         sw = MIN(rx + rw - sx, pr->tile_width);
910                                         sh = MIN(ry + rh - sy, pr->tile_height);
911
912                                         gdk_draw_rectangle(pr->overlay_buffer,
913                                                            box->style->bg_gc[GTK_WIDGET_STATE(box)], TRUE,
914                                                            0, 0, sw, sh);
915                                         gdk_draw_pixbuf(pr->overlay_buffer,
916                                                         box->style->fg_gc[GTK_WIDGET_STATE(box)],
917                                                         od->pixbuf,
918                                                         sx - px, sy - py,
919                                                         0, 0, sw, sh,
920                                                         pr->dither_quality, sx, sy);
921                                         gdk_draw_drawable(od->window, box->style->fg_gc[GTK_WIDGET_STATE(box)],
922                                                           pr->overlay_buffer,
923                                                           0, 0,
924                                                           sx - px, sy - py, sw, sh);
925                                         }
926                                 }
927                         }
928                 }
929 }
930
931 static void pr_overlay_queue_draw(PixbufRenderer *pr, OverlayData *od)
932 {
933         gint x, y, w, h;
934
935         pr_overlay_get_position(pr, od, &x, &y, &w, &h);
936         pr_queue(pr, pr->x_scroll - pr->x_offset + x,
937                  pr->y_scroll - pr->y_offset + y,
938                  w, h,
939                  FALSE, TILE_RENDER_ALL, FALSE, FALSE);
940
941         pr_border_draw(pr, x, y, w, h);
942 }
943
944 static void pr_overlay_queue_all(PixbufRenderer *pr)
945 {
946         GList *work;
947
948         work = pr->overlay_list;
949         while (work)
950                 {
951                 OverlayData *od = work->data;
952                 work = work->next;
953
954                 pr_overlay_queue_draw(pr, od);
955                 }
956 }
957
958 static void pr_overlay_update_sizes(PixbufRenderer *pr)
959 {
960         GList *work;
961
962         work = pr->overlay_list;
963         while (work)
964                 {
965                 OverlayData *od = work->data;
966                 work = work->next;
967                 
968                 if (!od->window) continue;
969                 
970                 if (od->flags & OVL_RELATIVE)
971                         {
972                         gint x, y, w, h;
973
974                         pr_overlay_get_position(pr, od, &x, &y, &w, &h);
975                         gdk_window_move_resize(od->window, x, y, w, h);
976                         }
977                 }
978 }
979
980 static OverlayData *pr_overlay_find(PixbufRenderer *pr, gint id)
981 {
982         GList *work;
983
984         work = pr->overlay_list;
985         while (work)
986                 {
987                 OverlayData *od = work->data;
988                 work = work->next;
989
990                 if (od->id == id) return od;
991                 }
992
993         return NULL;
994 }
995
996 gint pixbuf_renderer_overlay_add(PixbufRenderer *pr, GdkPixbuf *pixbuf, gint x, gint y,
997                                  OverlayRendererFlags flags)
998 {
999         OverlayData *od;
1000         gint id;
1001         gint px, py, pw, ph;
1002         GdkWindowAttr attributes;
1003         gint attributes_mask;
1004
1005         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), -1);
1006         g_return_val_if_fail(pixbuf != NULL, -1);
1007
1008         id = 1;
1009         while (pr_overlay_find(pr, id)) id++;
1010
1011         od = g_new0(OverlayData, 1);
1012         od->id = id;
1013         od->pixbuf = pixbuf;
1014         g_object_ref(G_OBJECT(od->pixbuf));
1015         od->x = x;
1016         od->y = y;
1017         od->flags = flags;
1018
1019         pr_overlay_get_position(pr, od, &px, &py, &pw, &ph);
1020
1021         attributes.window_type = GDK_WINDOW_CHILD;
1022         attributes.wclass = GDK_INPUT_OUTPUT;
1023         attributes.width = pw;
1024         attributes.height = ph;
1025         attributes.event_mask = GDK_EXPOSURE_MASK;
1026         attributes_mask = 0;
1027
1028         od->window = gdk_window_new(GTK_WIDGET(pr)->window, &attributes, attributes_mask);
1029         gdk_window_set_user_data(od->window, pr);
1030         gdk_window_move(od->window, px, py);
1031         gdk_window_show(od->window);
1032
1033         pr->overlay_list = g_list_append(pr->overlay_list, od);
1034
1035         pr_overlay_queue_draw(pr, od);
1036
1037         return od->id;
1038 }
1039
1040 static void pr_overlay_free(PixbufRenderer *pr, OverlayData *od)
1041 {
1042         pr->overlay_list = g_list_remove(pr->overlay_list, od);
1043
1044         if (od->pixbuf) g_object_unref(G_OBJECT(od->pixbuf));
1045         if (od->window) gdk_window_destroy(od->window);
1046         g_free(od);
1047
1048         if (!pr->overlay_list && pr->overlay_buffer)
1049                 {
1050                 g_object_unref(pr->overlay_buffer);
1051                 pr->overlay_buffer = NULL;
1052                 }
1053 }
1054
1055 static void pr_overlay_list_clear(PixbufRenderer *pr)
1056 {
1057         while (pr->overlay_list)
1058                 {
1059                 OverlayData *od;
1060
1061                 od = pr->overlay_list->data;
1062                 pr_overlay_free(pr, od);
1063                 }
1064 }
1065
1066 void pixbuf_renderer_overlay_set(PixbufRenderer *pr, gint id, GdkPixbuf *pixbuf, gint x, gint y)
1067 {
1068         OverlayData *od;
1069
1070         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
1071
1072         od = pr_overlay_find(pr, id);
1073         if (!od) return;
1074
1075         if (pixbuf)
1076                 {
1077                 gint px, py, pw, ph;
1078
1079                 g_object_ref(G_OBJECT(pixbuf));
1080                 g_object_unref(G_OBJECT(od->pixbuf));
1081                 od->pixbuf = pixbuf;
1082
1083                 od->x = x;
1084                 od->y = y;
1085
1086                 pr_overlay_queue_draw(pr, od);
1087                 pr_overlay_get_position(pr, od, &px, &py, &pw, &ph);
1088                 gdk_window_move_resize(od->window, px, py, pw, ph);
1089                 }
1090         else
1091                 {
1092                 pr_overlay_queue_draw(pr, od);
1093                 pr_overlay_free(pr, od);
1094                 }
1095 }
1096
1097 gint pixbuf_renderer_overlay_get(PixbufRenderer *pr, gint id, GdkPixbuf **pixbuf, gint *x, gint *y)
1098 {
1099         OverlayData *od;
1100
1101         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
1102
1103         od = pr_overlay_find(pr, id);
1104         if (!od) return FALSE;
1105
1106         if (pixbuf) *pixbuf = od->pixbuf;
1107         if (x) *x = od->x;
1108         if (y) *y = od->y;
1109
1110         return TRUE;
1111 }
1112
1113 void pixbuf_renderer_overlay_remove(PixbufRenderer *pr, gint id)
1114 {
1115         pixbuf_renderer_overlay_set(pr, id, NULL, 0, 0);
1116 }
1117
1118 /*
1119  *-------------------------------------------------------------------
1120  * scroller overlay
1121  *-------------------------------------------------------------------
1122  */
1123
1124
1125 static gboolean pr_scroller_update_cb(gpointer data)
1126 {
1127         PixbufRenderer *pr = data;
1128         gint x, y;
1129         gint xinc, yinc;
1130
1131         /* this was a simple scroll by difference between scroller and mouse position,
1132          * but all this math results in a smoother result and accounts for a dead zone.
1133          */
1134
1135         if (abs(pr->scroller_xpos - pr->scroller_x) < PR_SCROLLER_DEAD_ZONE)
1136                 {
1137                 x = 0;
1138                 }
1139         else
1140                 {
1141                 gint shift = PR_SCROLLER_DEAD_ZONE / 2 * PR_SCROLLER_UPDATES_PER_SEC;
1142                 x = (pr->scroller_xpos - pr->scroller_x) / 2 * PR_SCROLLER_UPDATES_PER_SEC;
1143                 x += (x > 0) ? -shift : shift;
1144                 }
1145
1146         if (abs(pr->scroller_ypos - pr->scroller_y) < PR_SCROLLER_DEAD_ZONE)
1147                 {
1148                 y = 0;
1149                 }
1150         else
1151                 {
1152                 gint shift = PR_SCROLLER_DEAD_ZONE / 2 * PR_SCROLLER_UPDATES_PER_SEC;
1153                 y = (pr->scroller_ypos - pr->scroller_y) / 2 * PR_SCROLLER_UPDATES_PER_SEC;
1154                 y += (y > 0) ? -shift : shift;
1155                 }
1156
1157         if (abs(x) < PR_SCROLLER_DEAD_ZONE * PR_SCROLLER_UPDATES_PER_SEC)
1158                 {
1159                 xinc = x;
1160                 }
1161         else
1162                 {
1163                 xinc = pr->scroller_xinc;
1164
1165                 if (x >= 0)
1166                         {
1167                         if (xinc < 0) xinc = 0;
1168                         if (x < xinc) xinc = x;
1169                         if (x > xinc) xinc = MIN(xinc + x / PR_SCROLLER_UPDATES_PER_SEC, x);
1170                         }
1171                 else
1172                         {
1173                         if (xinc > 0) xinc = 0;
1174                         if (x > xinc) xinc = x;
1175                         if (x < xinc) xinc = MAX(xinc + x / PR_SCROLLER_UPDATES_PER_SEC, x);
1176                         }
1177                 }
1178
1179         if (abs(y) < PR_SCROLLER_DEAD_ZONE * PR_SCROLLER_UPDATES_PER_SEC)
1180                 {
1181                 yinc = y;
1182                 }
1183         else
1184                 {
1185                 yinc = pr->scroller_yinc;
1186
1187                 if (y >= 0)
1188                         {
1189                         if (yinc < 0) yinc = 0;
1190                         if (y < yinc) yinc = y;
1191                         if (y > yinc) yinc = MIN(yinc + y / PR_SCROLLER_UPDATES_PER_SEC, y);
1192                         }
1193                 else
1194                         {
1195                         if (yinc > 0) yinc = 0;
1196                         if (y > yinc) yinc = y;
1197                         if (y < yinc) yinc = MAX(yinc + y / PR_SCROLLER_UPDATES_PER_SEC, y);
1198                         }
1199                 }
1200
1201         pr->scroller_xinc = xinc;
1202         pr->scroller_yinc = yinc;
1203
1204         xinc = xinc / PR_SCROLLER_UPDATES_PER_SEC;
1205         yinc = yinc / PR_SCROLLER_UPDATES_PER_SEC;
1206
1207         pixbuf_renderer_scroll(pr, xinc, yinc);
1208
1209         return TRUE;
1210 }
1211
1212 static void pr_scroller_timer_set(PixbufRenderer *pr, gint start)
1213 {
1214         if (pr->scroller_id != -1)
1215                 {
1216                 g_source_remove(pr->scroller_id);
1217                 pr->scroller_id = -1;
1218                 }
1219
1220         if (start)
1221                 {
1222                 pr->scroller_id = g_timeout_add(1000 / PR_SCROLLER_UPDATES_PER_SEC,
1223                                                 pr_scroller_update_cb, pr);
1224                 }
1225 }
1226
1227 static void pr_scroller_start(PixbufRenderer *pr, gint x, gint y)
1228 {
1229         if (pr->scroller_overlay == -1)
1230                 {
1231                 GdkPixbuf *pixbuf;
1232                 gint w, h;
1233
1234 #ifdef GQ_BUILD
1235                 pixbuf = pixbuf_inline(PIXBUF_INLINE_SCROLLER);
1236 #else
1237                 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 32, 32);
1238                 gdk_pixbuf_fill(pixbuf, 0x000000ff);
1239 #endif
1240                 w = gdk_pixbuf_get_width(pixbuf);
1241                 h = gdk_pixbuf_get_height(pixbuf);
1242
1243                 pr->scroller_overlay = pixbuf_renderer_overlay_add(pr, pixbuf, x - w / 2, y - h / 2, OVL_NORMAL);
1244                 g_object_unref(pixbuf);
1245                 }
1246
1247         pr->scroller_x = x;
1248         pr->scroller_y = y;
1249         pr->scroller_xpos = x;
1250         pr->scroller_ypos = y;
1251
1252         pr_scroller_timer_set(pr, TRUE);
1253 }
1254
1255 static void pr_scroller_stop(PixbufRenderer *pr)
1256 {
1257         if (pr->scroller_id == -1) return;
1258
1259         pixbuf_renderer_overlay_remove(pr, pr->scroller_overlay);
1260         pr->scroller_overlay = -1;
1261
1262         pr_scroller_timer_set(pr, FALSE);
1263 }
1264
1265 /*
1266  *-------------------------------------------------------------------
1267  * borders
1268  *-------------------------------------------------------------------
1269  */
1270
1271 static void pr_border_draw(PixbufRenderer *pr, gint x, gint y, gint w, gint h)
1272 {
1273         GtkWidget *box;
1274         gint rx, ry, rw, rh;
1275
1276         box = GTK_WIDGET(pr);
1277
1278         if (!box->window) return;
1279
1280         if (!pr->pixbuf && !pr->source_tiles_enabled)
1281                 {
1282                 if (pr_clip_region(x, y, w, h,
1283                                    0, 0,
1284                                    pr->window_width, pr->window_height,
1285                                    &rx, &ry, &rw, &rh))
1286                         {
1287                         gdk_window_clear_area(box->window, rx, ry, rw, rh);
1288                         pr_overlay_draw(pr, rx, ry, rw, rh, NULL);
1289                         }
1290                 return;
1291                 }
1292
1293         if (pr->vis_width < pr->window_width)
1294                 {
1295                 if (pr->x_offset > 0 &&
1296                     pr_clip_region(x, y, w, h,
1297                                    0, 0,
1298                                    pr->x_offset, pr->window_height,
1299                                    &rx, &ry, &rw, &rh))
1300                         {
1301                         gdk_window_clear_area(box->window, rx, ry, rw, rh);
1302                         pr_overlay_draw(pr, rx, ry, rw, rh, NULL);
1303                         }
1304                 if (pr->window_width - pr->vis_width - pr->x_offset > 0 &&
1305                     pr_clip_region(x, y, w, h,
1306                                    pr->x_offset + pr->vis_width, 0,
1307                                    pr->window_width - pr->vis_width - pr->x_offset, pr->window_height,
1308                                    &rx, &ry, &rw, &rh))
1309                         {
1310                         gdk_window_clear_area(box->window, rx, ry, rw, rh);
1311                         pr_overlay_draw(pr, rx, ry, rw, rh, NULL);
1312                         }
1313                 }
1314         if (pr->vis_height < pr->window_height)
1315                 {
1316                 if (pr->y_offset > 0 &&
1317                     pr_clip_region(x, y, w, h,
1318                                    pr->x_offset, 0,
1319                                    pr->vis_width, pr->y_offset,
1320                                    &rx, &ry, &rw, &rh))
1321                         {
1322                         gdk_window_clear_area(box->window, rx, ry, rw, rh);
1323                         pr_overlay_draw(pr, rx, ry, rw, rh, NULL);
1324                         }
1325                 if (pr->window_height - pr->vis_height - pr->y_offset > 0 &&
1326                     pr_clip_region(x, y, w, h,
1327                                    pr->x_offset, pr->y_offset + pr->vis_height,
1328                                    pr->vis_width, pr->window_height - pr->vis_height - pr->y_offset,
1329                                    &rx, &ry, &rw, &rh))
1330                         {
1331                         gdk_window_clear_area(box->window, rx, ry, rw, rh);
1332                         pr_overlay_draw(pr, rx, ry, rw, rh, NULL);
1333                         }
1334                 }
1335 }
1336
1337 static void pr_border_clear(PixbufRenderer *pr)
1338 {
1339         pr_border_draw(pr, 0, 0, pr->window_width, pr->window_height);
1340 }
1341
1342 void pixbuf_renderer_set_color(PixbufRenderer *pr, GdkColor *color)
1343 {
1344         GtkStyle *style;
1345         GtkWidget *widget;
1346
1347         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
1348
1349         widget = GTK_WIDGET(pr);
1350
1351         if (color) {
1352                 GdkColor *slot;
1353
1354                 style = gtk_style_copy(gtk_widget_get_style(widget));
1355                 slot = &style->bg[GTK_STATE_NORMAL];
1356
1357                 slot->red = color->red;
1358                 slot->green = color->green;
1359                 slot->blue = color->blue;
1360                 }
1361         else {
1362                 style = gtk_style_copy(gtk_widget_get_default_style());
1363         }
1364
1365         gtk_widget_set_style(widget, style);
1366
1367         if (GTK_WIDGET_VISIBLE(widget)) pr_border_clear(pr);
1368 }
1369
1370
1371 /*
1372  *-------------------------------------------------------------------
1373  * source tiles
1374  *-------------------------------------------------------------------
1375  */
1376
1377 static void pr_source_tile_free(SourceTile *st)
1378 {
1379         if (!st) return;
1380
1381         if (st->pixbuf) g_object_unref(st->pixbuf);
1382         g_free(st);
1383 }
1384
1385 static void pr_source_tile_free_all(PixbufRenderer *pr)
1386 {
1387         GList *work;
1388
1389         work = pr->source_tiles;
1390         while (work)
1391                 {
1392                 SourceTile *st;
1393
1394                 st = work->data;
1395                 work = work->next;
1396
1397                 pr_source_tile_free(st);
1398                 }
1399
1400         g_list_free(pr->source_tiles);
1401         pr->source_tiles = NULL;
1402 }
1403
1404 static void pr_source_tile_unset(PixbufRenderer *pr)
1405 {
1406         pr_source_tile_free_all(pr);
1407         pr->source_tiles_enabled = FALSE;
1408 }
1409
1410 static gint pr_source_tile_visible(PixbufRenderer *pr, SourceTile *st)
1411 {
1412         gint x1, y1, x2, y2;
1413
1414         if (!st) return FALSE;
1415
1416         x1 = (pr->x_scroll / pr->tile_width) * pr->tile_width;
1417         y1 = (pr->y_scroll / pr->tile_height) * pr->tile_height;
1418         x2 = ((pr->x_scroll + pr->vis_width) / pr->tile_width) * pr->tile_width + pr->tile_width;
1419         y2 = ((pr->y_scroll + pr->vis_height) / pr->tile_height) * pr->tile_height + pr->tile_height;
1420
1421         return !((gdouble)st->x * pr->scale > (gdouble)x2 ||
1422                  (gdouble)(st->x + pr->source_tile_width) * pr->scale < (gdouble)x1 ||
1423                  (gdouble)st->y * pr->scale > (gdouble)y2 ||
1424                  (gdouble)(st->y + pr->source_tile_height) * pr->scale < (gdouble)y1);
1425 }
1426
1427 static SourceTile *pr_source_tile_new(PixbufRenderer *pr, gint x, gint y)
1428 {
1429         SourceTile *st = NULL;
1430         gint count;
1431
1432         g_return_val_if_fail(pr->source_tile_width >= 1 && pr->source_tile_height >= 1, NULL);
1433
1434         if (pr->source_tiles_cache_size < 4) pr->source_tiles_cache_size = 4;
1435
1436         count = g_list_length(pr->source_tiles);
1437         if (count >= pr->source_tiles_cache_size)
1438                 {
1439                 GList *work;
1440
1441                 work = g_list_last(pr->source_tiles);
1442                 while (work && count >= pr->source_tiles_cache_size)
1443                         {
1444                         SourceTile *needle;
1445
1446                         needle = work->data;
1447                         work = work->prev;
1448
1449                         if (!pr_source_tile_visible(pr, needle))
1450                                 {
1451                                 pr->source_tiles = g_list_remove(pr->source_tiles, needle);
1452
1453                                 if (pr->func_tile_dispose)
1454                                         {
1455                                         pr->func_tile_dispose(pr, needle->x, needle->y,
1456                                                               pr->source_tile_width, pr->source_tile_height,
1457                                                               needle->pixbuf, pr->func_tile_data);
1458                                         }
1459
1460                                 if (!st)
1461                                         {
1462                                         st = needle;
1463                                         }
1464                                 else
1465                                         {
1466                                         pr_source_tile_free(needle);
1467                                         }
1468
1469                                 count--;
1470                                 }
1471                         }
1472                 }
1473
1474         if (!st)
1475                 {
1476                 st = g_new0(SourceTile, 1);
1477                 st->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8,
1478                                             pr->source_tile_width, pr->source_tile_height);
1479                 }
1480
1481         st->x = (x / pr->source_tile_width) * pr->source_tile_width;
1482         st->y = (y / pr->source_tile_height) * pr->source_tile_height;
1483         st->blank = TRUE;
1484
1485         pr->source_tiles = g_list_prepend(pr->source_tiles, st);
1486
1487         return st;
1488 }
1489
1490 static SourceTile *pr_source_tile_request(PixbufRenderer *pr, gint x, gint y)
1491 {
1492         SourceTile *st;
1493
1494         st = pr_source_tile_new(pr, x, y);
1495         if (!st) return NULL;
1496
1497         if (pr->func_tile_request &&
1498             pr->func_tile_request(pr, st->x, st->y,
1499                                    pr->source_tile_width, pr->source_tile_height, st->pixbuf, pr->func_tile_data))
1500                 {
1501                 st->blank = FALSE;
1502                 }
1503
1504         pr_tile_invalidate_region(pr, st->x * pr->scale, st->y * pr->scale,
1505                                   pr->source_tile_width * pr->scale, pr->source_tile_height * pr->scale);
1506
1507         return st;
1508 }
1509
1510 static SourceTile *pr_source_tile_find(PixbufRenderer *pr, gint x, gint y)
1511 {
1512         GList *work;
1513
1514         work = pr->source_tiles;
1515         while (work)
1516                 {
1517                 SourceTile *st = work->data;
1518
1519                 if (x >= st->x && x < st->x + pr->source_tile_width &&
1520                     y >= st->y && y < st->y + pr->source_tile_height)
1521                         {
1522                         if (work != pr->source_tiles)
1523                                 {
1524                                 pr->source_tiles = g_list_remove_link(pr->source_tiles, work);
1525                                 pr->source_tiles = g_list_concat(work, pr->source_tiles);
1526                                 }
1527                         return st;
1528                         }
1529
1530                 work = work->next;
1531                 }
1532
1533         return NULL;
1534 }
1535
1536 static GList *pr_source_tile_compute_region(PixbufRenderer *pr, gint x, gint y, gint w, gint h, gint request)
1537 {
1538         gint x1, y1;
1539         GList *list = NULL;
1540         gint sx, sy;
1541
1542         if (x < 0) x = 0;
1543         if (y < 0) y = 0;
1544         if (w > pr->image_width) w = pr->image_width;
1545         if (h > pr->image_height) h = pr->image_height;
1546
1547         sx = (x / pr->source_tile_width) * pr->source_tile_width;
1548         sy = (y / pr->source_tile_height) * pr->source_tile_height;
1549
1550         for (x1 = sx; x1 < x + w; x1+= pr->source_tile_width)
1551                 {
1552                 for (y1 = sy; y1 < y + h; y1 += pr->source_tile_height)
1553                         {
1554                         SourceTile *st;
1555
1556                         st = pr_source_tile_find(pr, x1, y1);
1557                         if (!st && request) st = pr_source_tile_request(pr, x1, y1);
1558
1559                         if (st) list = g_list_prepend(list, st);
1560                         }
1561                 }
1562
1563         return g_list_reverse(list);
1564 }
1565
1566 static void pr_source_tile_changed(PixbufRenderer *pr, gint x, gint y, gint width, gint height)
1567 {
1568         GList *work;
1569
1570         if (width < 1 || height < 1) return;
1571
1572         work = pr->source_tiles;
1573         while (work)
1574                 {
1575                 SourceTile *st;
1576                 gint rx, ry, rw, rh;
1577
1578                 st = work->data;
1579                 work = work->next;
1580
1581                 if (pr_clip_region(st->x, st->y, pr->source_tile_width, pr->source_tile_height,
1582                                    x, y, width, height,
1583                                    &rx, &ry, &rw, &rh))
1584                         {
1585                         GdkPixbuf *pixbuf;
1586
1587                         pixbuf = gdk_pixbuf_new_subpixbuf(st->pixbuf, rx - st->x, ry - st->y, rw, rh);
1588                         if (pr->func_tile_request &&
1589                             pr->func_tile_request(pr, rx, ry, rw, rh, pixbuf, pr->func_tile_data))
1590                                 {
1591                                 pr_tile_invalidate_region(pr, rx * pr->scale, ry * pr->scale,
1592                                                               rw * pr->scale, rh * pr->scale);
1593                                 }
1594                         g_object_unref(pixbuf);
1595                         }
1596                 }
1597 }
1598
1599 static gint pr_source_tile_render(PixbufRenderer *pr, ImageTile *it,
1600                                   gint x, gint y, gint w, gint h,
1601                                   gint new_data, gint fast)
1602 {
1603         GtkWidget *box;
1604         GList *list;
1605         GList *work;
1606         gint draw = FALSE;
1607
1608         box = GTK_WIDGET(pr);
1609
1610         if (pr->zoom == 1.0 || pr->scale == 1.0)
1611                 {
1612                 list = pr_source_tile_compute_region(pr, it->x + x, it->y + y, w, h, TRUE);
1613                 work = list;
1614                 while (work)
1615                         {
1616                         SourceTile *st;
1617                         gint rx, ry, rw, rh;
1618
1619                         st = work->data;
1620                         work = work->next;
1621
1622                         if (pr_clip_region(st->x, st->y, pr->source_tile_width, pr->source_tile_height,
1623                                            it->x + x, it->y + y, w, h,
1624                                            &rx, &ry, &rw, &rh))
1625                                 {
1626                                 if (st->blank)
1627                                         {
1628                                         gdk_draw_rectangle(it->pixmap, box->style->black_gc, TRUE,
1629                                                            rx - st->x, ry - st->y, rw, rh);
1630                                         }
1631                                 else /* (pr->zoom == 1.0 || pr->scale == 1.0) */
1632                                         {
1633                                         gdk_draw_pixbuf(it->pixmap,
1634                                                         box->style->fg_gc[GTK_WIDGET_STATE(box)],
1635                                                         st->pixbuf,
1636                                                         rx - st->x, ry - st->y,
1637                                                         rx - it->x, ry - it->y,
1638                                                         rw, rh,
1639                                                         pr->dither_quality, rx, ry);
1640                                         }
1641                                 }
1642                         }
1643                 }
1644         else
1645                 {
1646                 gdouble scale_x, scale_y;
1647                 gint sx, sy, sw, sh;
1648
1649                 if (pr->image_width == 0 || pr->image_height == 0) return FALSE;
1650                 scale_x = (gdouble)pr->width / pr->image_width;
1651                 scale_y = (gdouble)pr->height / pr->image_height;
1652
1653                 sx = (gdouble)(it->x + x) / scale_x;
1654                 sy = (gdouble)(it->y + y) / scale_y;
1655                 sw = (gdouble)w / scale_x;
1656                 sh = (gdouble)h / scale_y;
1657
1658                 if (pr->width < PR_MIN_SCALE_SIZE || pr->height < PR_MIN_SCALE_SIZE) fast = TRUE;
1659
1660 #if 0
1661                 /* draws red over draw region, to check for leaks (regions not filled) */
1662                 pixbuf_set_rect_fill(it->pixbuf, x, y, w, h, 255, 0, 0, 255);
1663 #endif
1664
1665                 list = pr_source_tile_compute_region(pr, sx, sy, sw, sh, TRUE);
1666                 work = list;
1667                 while (work)
1668                         {
1669                         SourceTile *st;
1670                         gint rx, ry, rw, rh;
1671                         gint stx, sty, stw, sth;
1672
1673                         st = work->data;
1674                         work = work->next;
1675
1676                         stx = floor((gdouble)st->x * scale_x);
1677                         sty = floor((gdouble)st->y * scale_y);
1678                         stw = ceil((gdouble)(st->x + pr->source_tile_width) * scale_x) - stx;
1679                         sth = ceil((gdouble)(st->y + pr->source_tile_height) * scale_y) - sty;
1680
1681                         if (pr_clip_region(stx, sty, stw, sth,
1682                                            it->x + x, it->y + y, w, h,
1683                                            &rx, &ry, &rw, &rh))
1684                                 {
1685                                 if (st->blank)
1686                                         {
1687                                         gdk_draw_rectangle(it->pixmap, box->style->black_gc, TRUE,
1688                                                            rx - st->x, ry - st->y, rw, rh);
1689                                         }
1690                                 else
1691                                         {
1692                                         gdouble offset_x;
1693                                         gdouble offset_y;
1694
1695                                         /* may need to use unfloored stx,sty values here */
1696                                         offset_x = (gdouble)(stx - it->x);
1697                                         offset_y = (gdouble)(sty - it->y);
1698
1699                                         gdk_pixbuf_scale(st->pixbuf, it->pixbuf, rx - it->x, ry - it->y, rw, rh,
1700                                                  (gdouble) 0.0 + offset_x,
1701                                                  (gdouble) 0.0 + offset_y,
1702                                                  scale_x, scale_y,
1703                                                  (fast) ? GDK_INTERP_NEAREST : pr->zoom_quality);
1704                                         draw = TRUE;
1705                                         }
1706                                 }
1707                         }
1708                 }
1709
1710         g_list_free(list);
1711
1712         return draw;
1713 }
1714
1715 void pixbuf_renderer_set_tiles(PixbufRenderer *pr, gint width, gint height,
1716                                gint tile_width, gint tile_height, gint cache_size,
1717                                PixbufRendererTileRequestFunc func_request,
1718                                PixbufRendererTileDisposeFunc func_dispose,
1719                                gpointer user_data,
1720                                gdouble zoom)
1721 {
1722         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
1723         g_return_if_fail(tile_width >= 32 && tile_width >= 32);
1724         g_return_if_fail(width >= 32 && height > 32);
1725         g_return_if_fail(func_request != NULL);
1726
1727         if (pr->pixbuf) g_object_unref(pr->pixbuf);
1728         pr->pixbuf = NULL;
1729
1730         pr_source_tile_unset(pr);
1731
1732         if (cache_size < 4) cache_size = 4;
1733
1734         pr->source_tiles_enabled = TRUE;
1735         pr->source_tiles_cache_size = cache_size;
1736         pr->source_tile_width = tile_width;
1737         pr->source_tile_height = tile_height;
1738
1739         pr->image_width = width;
1740         pr->image_height = height;
1741
1742         pr->func_tile_request = func_request;
1743         pr->func_tile_dispose = func_dispose;
1744         pr->func_tile_data = user_data;
1745
1746         pr_zoom_sync(pr, zoom, PR_ZOOM_FORCE | PR_ZOOM_NEW, 0, 0);
1747         pr_redraw(pr, TRUE);
1748 }
1749
1750 void pixbuf_renderer_set_tiles_size(PixbufRenderer *pr, gint width, gint height)
1751 {
1752         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
1753         g_return_if_fail(width >= 32 && height > 32);
1754
1755         if (!pr->source_tiles_enabled) return;
1756         if (pr->image_width == width && pr->image_height == height) return;
1757
1758         pr->image_width = width;
1759         pr->image_height = height;
1760
1761         pr_zoom_sync(pr, pr->zoom, PR_ZOOM_FORCE, 0, 0);
1762 }
1763
1764 gint pixbuf_renderer_get_tiles(PixbufRenderer *pr)
1765 {
1766         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
1767
1768         return pr->source_tiles_enabled;
1769 }
1770
1771 static void pr_zoom_adjust_real(PixbufRenderer *pr, gdouble increment,
1772                                 PrZoomFlags flags, gint x, gint y)
1773 {
1774         gdouble zoom = pr->zoom;
1775
1776         if (increment == 0.0) return;
1777
1778         if (zoom == 0.0)
1779                 {
1780                 if (pr->scale < 1.0)
1781                         {
1782                         zoom = 0.0 - 1.0 / pr->scale;
1783                         }
1784                 else
1785                         {
1786                         zoom = pr->scale;
1787                         }
1788                 }
1789
1790         if (increment < 0.0)
1791                 {
1792                 if (zoom >= 1.0 && zoom + increment < 1.0)
1793                         {
1794                         zoom = zoom + increment - 2.0;
1795                         }
1796                 else
1797                         {
1798                         zoom = zoom + increment;
1799                         }
1800                 }
1801         else
1802                 {
1803                 if (zoom <= -1.0 && zoom + increment > -1.0)
1804                         {
1805                         zoom = zoom + increment + 2.0;
1806                         }
1807                 else
1808                         {
1809                         zoom = zoom + increment;
1810                         }
1811                 }
1812
1813         pr_zoom_sync(pr, zoom, flags, x, y);
1814 }
1815
1816 /*
1817  *-------------------------------------------------------------------
1818  * display tiles
1819  *-------------------------------------------------------------------
1820  */
1821
1822 static ImageTile *pr_tile_new(gint x, gint y, gint width, gint height)
1823 {
1824         ImageTile *it;
1825
1826         it = g_new0(ImageTile, 1);
1827
1828         it->x = x;
1829         it->y = y;
1830         it->w = width;
1831         it->h = height;
1832
1833         it->render_done = TILE_RENDER_NONE;
1834
1835         return it;
1836 }
1837
1838 static void pr_tile_free(ImageTile *it)
1839 {
1840         if (!it) return;
1841
1842         if (it->pixbuf) g_object_unref(it->pixbuf);
1843         if (it->pixmap) g_object_unref(it->pixmap);
1844
1845         g_free(it);
1846 }
1847
1848 static void pr_tile_free_all(PixbufRenderer *pr)
1849 {
1850         GList *work;
1851
1852         work = pr->tiles;
1853         while (work)
1854                 {
1855                 ImageTile *it;
1856
1857                 it = work->data;
1858                 work = work->next;
1859
1860                 pr_tile_free(it);
1861                 }
1862
1863         g_list_free(pr->tiles);
1864         pr->tiles = NULL;
1865         pr->tile_cache_size = 0;
1866 }
1867
1868 static ImageTile *pr_tile_add(PixbufRenderer *pr, gint x, gint y)
1869 {
1870         ImageTile *it;
1871
1872         it = pr_tile_new(x, y, pr->tile_width, pr->tile_height);
1873
1874         if (it->x + it->w > pr->width) it->w = pr->width - it->x;
1875         if (it->y + it->h > pr->height) it->h = pr->height - it->y;
1876
1877         pr->tiles = g_list_prepend(pr->tiles, it);
1878         pr->tile_cache_size += it->size;
1879
1880         return it;
1881 }
1882
1883 static void pr_tile_remove(PixbufRenderer *pr, ImageTile *it)
1884 {
1885         if (it->qd)
1886                 {
1887                 QueueData *qd = it->qd;
1888
1889                 it->qd = NULL;
1890                 pr->draw_queue = g_list_remove(pr->draw_queue, qd);
1891                 g_free(qd);
1892                 }
1893
1894         if (it->qd2)
1895                 {
1896                 QueueData *qd = it->qd2;
1897
1898                 it->qd2 = NULL;
1899                 pr->draw_queue_2pass = g_list_remove(pr->draw_queue_2pass, qd);
1900                 g_free(qd);
1901                 }
1902
1903         pr->tiles = g_list_remove(pr->tiles, it);
1904         pr->tile_cache_size -= it->size;
1905
1906         pr_tile_free(it);
1907 }
1908
1909 static void pr_tile_free_space(PixbufRenderer *pr, guint space, ImageTile *it)
1910 {
1911         GList *work;
1912         guint tile_max;
1913
1914         work = g_list_last(pr->tiles);
1915
1916         if (pr->source_tiles_enabled && pr->scale < 1.0)
1917                 {
1918                 gint tiles;
1919
1920                 tiles = (pr->vis_width / pr->tile_width + 1) * (pr->vis_height / pr->tile_height + 1);
1921                 tile_max = MAX(tiles * pr->tile_width * pr->tile_height * 3,
1922                                (gint)((gdouble)pr->tile_cache_max * 1048576.0 * pr->scale));
1923                 }
1924         else
1925                 {
1926                 tile_max = pr->tile_cache_max * 1048576;
1927                 }
1928
1929         while (work && pr->tile_cache_size + space > tile_max)
1930                 {
1931                 ImageTile *needle;
1932
1933                 needle = work->data;
1934                 work = work->prev;
1935                 if (needle != it &&
1936                     ((!needle->qd && !needle->qd2) || !pr_tile_is_visible(pr, needle))) pr_tile_remove(pr, needle);
1937                 }
1938 }
1939
1940 static void pr_tile_invalidate_all(PixbufRenderer *pr)
1941 {
1942         GList *work;
1943
1944         work = pr->tiles;
1945         while (work)
1946                 {
1947                 ImageTile *it;
1948
1949                 it = work->data;
1950                 work = work->next;
1951
1952                 it->render_done = TILE_RENDER_NONE;
1953                 it->render_todo = TILE_RENDER_ALL;
1954                 it->blank = FALSE;
1955
1956                 it->w = MIN(pr->tile_width, pr->width - it->x);
1957                 it->h = MIN(pr->tile_height, pr->height - it->y);
1958                 }
1959 }
1960
1961 static void pr_tile_invalidate_region(PixbufRenderer *pr, gint x, gint y, gint w, gint h)
1962 {
1963         gint x1, x2;
1964         gint y1, y2;
1965         GList *work;
1966
1967         x1 = (gint)floor(x / pr->tile_width) * pr->tile_width;
1968         x2 = (gint)ceil((x + w) / pr->tile_width) * pr->tile_width;
1969
1970         y1 = (gint)floor(y / pr->tile_height) * pr->tile_height;
1971         y2 = (gint)ceil((y + h) / pr->tile_height) * pr->tile_height;
1972
1973         work = pr->tiles;
1974         while (work)
1975                 {
1976                 ImageTile *it;
1977
1978                 it = work->data;
1979                 work = work->next;
1980
1981                 if (it->x < x2 && it->x + it->w > x1 &&
1982                     it->y < y2 && it->y + it->h > y1)
1983                         {
1984                         it->render_done = TILE_RENDER_NONE;
1985                         it->render_todo = TILE_RENDER_ALL;
1986                         }
1987                 }
1988 }
1989
1990 static ImageTile *pr_tile_get(PixbufRenderer *pr, gint x, gint y, gint only_existing)
1991 {
1992         GList *work;
1993
1994         work = pr->tiles;
1995         while (work)
1996                 {
1997                 ImageTile *it;
1998
1999                 it = work->data;
2000                 if (it->x == x && it->y == y)
2001                         {
2002                         pr->tiles = g_list_delete_link(pr->tiles, work);
2003                         pr->tiles = g_list_prepend(pr->tiles, it);
2004                         return it;
2005                         }
2006
2007                 work = work->next;
2008                 }
2009
2010         if (only_existing) return NULL;
2011
2012         return pr_tile_add(pr, x, y);
2013 }
2014
2015 static void pr_tile_prepare(PixbufRenderer *pr, ImageTile *it)
2016 {
2017         if (!it->pixmap)
2018                 {
2019                 GdkPixmap *pixmap;
2020                 guint size;
2021
2022                 pixmap = gdk_pixmap_new(((GtkWidget *)pr)->window, pr->tile_width, pr->tile_height, -1);
2023
2024                 size = pixmap_calc_size(pixmap);
2025                 pr_tile_free_space(pr, size, it);
2026
2027                 it->pixmap = pixmap;
2028                 it->size += size;
2029                 pr->tile_cache_size += size;
2030                 }
2031
2032         if ((pr->zoom != 1.0 || pr->source_tiles_enabled || (pr->pixbuf && gdk_pixbuf_get_has_alpha(pr->pixbuf)) ||
2033              pr->orientation != EXIF_ORIENTATION_TOP_LEFT || pr->func_post_process) && !it->pixbuf)
2034                 {
2035                 GdkPixbuf *pixbuf;
2036                 guint size;
2037 #if 0
2038 /* I don't think that we need a pixbuf with alpha channel here */
2039                 if (pr->pixbuf)
2040                         {
2041                         pixbuf = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(pr->pixbuf),
2042                                                 gdk_pixbuf_get_has_alpha(pr->pixbuf),
2043                                                 gdk_pixbuf_get_bits_per_sample(pr->pixbuf),
2044                                                 pr->tile_width, pr->tile_height);
2045                         }
2046                 else
2047 #endif
2048                         {
2049                         pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, pr->tile_width, pr->tile_height);
2050                         }
2051
2052                 size = gdk_pixbuf_get_rowstride(pixbuf) * pr->tile_height;
2053                 pr_tile_free_space(pr, size, it);
2054
2055                 it->pixbuf = pixbuf;
2056                 it->size += size;
2057                 pr->tile_cache_size += size;
2058                 }
2059 }
2060
2061 /*
2062  *-------------------------------------------------------------------
2063  * drawing
2064  *-------------------------------------------------------------------
2065  */
2066
2067
2068 static void pr_tile_coords_map_orientation(PixbufRenderer *pr,
2069                                      gdouble tile_x, gdouble tile_y, /* coordinates of the tile */
2070                                      gint image_w, gint image_h,
2071                                      gdouble tile_w, gdouble tile_h,
2072                                      gdouble *res_x, gdouble *res_y)
2073 {
2074         *res_x = tile_x;
2075         *res_y = tile_y;
2076         switch (pr->orientation)
2077                 {
2078                 case EXIF_ORIENTATION_TOP_LEFT:
2079                         /* normal -- nothing to do */
2080                         break;
2081                 case EXIF_ORIENTATION_TOP_RIGHT:
2082                         /* mirrored */
2083                         *res_x = image_w - tile_x - tile_w;
2084                         break;
2085                 case EXIF_ORIENTATION_BOTTOM_RIGHT:
2086                         /* upside down */
2087                         *res_x = image_w - tile_x - tile_w;
2088                         *res_y = image_h - tile_y - tile_h;
2089                         break;
2090                 case EXIF_ORIENTATION_BOTTOM_LEFT:
2091                         /* flipped */
2092                         *res_y = image_h - tile_y - tile_h;
2093                         break;
2094                 case EXIF_ORIENTATION_LEFT_TOP:
2095                         *res_x = tile_y;
2096                         *res_y = tile_x;
2097                         break;
2098                 case EXIF_ORIENTATION_RIGHT_TOP:
2099                         /* rotated -90 (270) */
2100                         *res_x = tile_y;
2101                         *res_y = image_w - tile_x - tile_w;
2102                         break;
2103                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
2104                         *res_x = image_h - tile_y - tile_h;
2105                         *res_y = image_w - tile_x - tile_w;
2106                         break;
2107                 case EXIF_ORIENTATION_LEFT_BOTTOM:
2108                         /* rotated 90 */
2109                         *res_x = image_h - tile_y - tile_h;
2110                         *res_y = tile_x;
2111                         break;
2112                 default:
2113                         /* The other values are out of range */
2114                         break;
2115                 }
2116 //      log_printf("tile coord y:%f, ih:%d, th:%f ry:%f\n", tile_y, image_h, tile_h, *res_x);
2117 }
2118
2119 static void pr_tile_region_map_orientation(PixbufRenderer *pr,
2120                                      gint area_x, gint area_y, /* coordinates of the area inside tile */
2121                                      gint tile_w, gint tile_h,
2122                                      gint area_w, gint area_h,
2123                                      gint *res_x, gint *res_y,
2124                                      gint *res_w, gint *res_h)
2125 {
2126         *res_x = area_x;
2127         *res_y = area_y;
2128         *res_w = area_w;
2129         *res_h = area_h;
2130
2131         switch (pr->orientation)
2132                 {
2133                 case EXIF_ORIENTATION_TOP_LEFT:
2134                         /* normal -- nothing to do */
2135                         break;
2136                 case EXIF_ORIENTATION_TOP_RIGHT:
2137                         /* mirrored */
2138                         *res_x = tile_w - area_x - area_w;
2139                         break;
2140                 case EXIF_ORIENTATION_BOTTOM_RIGHT:
2141                         /* upside down */
2142                         *res_x = tile_w - area_x - area_w;
2143                         *res_y = tile_h - area_y - area_h;
2144                         break;
2145                 case EXIF_ORIENTATION_BOTTOM_LEFT:
2146                         /* flipped */
2147                         *res_y = tile_h - area_y - area_h;
2148                         break;
2149                 case EXIF_ORIENTATION_LEFT_TOP:
2150                         *res_x = area_y;
2151                         *res_y = area_x;
2152                         *res_w = area_h;
2153                         *res_h = area_w;
2154                         break;
2155                 case EXIF_ORIENTATION_RIGHT_TOP:
2156                         /* rotated -90 (270) */
2157                         *res_x = area_y;
2158                         *res_y = tile_w - area_x - area_w;
2159                         *res_w = area_h;
2160                         *res_h = area_w;
2161                         break;
2162                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
2163                         *res_x = tile_h - area_y - area_h;
2164                         *res_y = tile_w - area_x - area_w;
2165                         *res_w = area_h;
2166                         *res_h = area_w;
2167                         break;
2168                 case EXIF_ORIENTATION_LEFT_BOTTOM:
2169                         /* rotated 90 */
2170                         *res_x = tile_h - area_y - area_h;
2171                         *res_y = area_x;
2172                         *res_w = area_h;
2173                         *res_h = area_w;
2174                         break;
2175                 default:
2176                         /* The other values are out of range */
2177                         break;
2178                 }
2179 //      log_printf("inside y:%d, th:%d, ah:%d ry:%d\n", area_y, tile_h, area_h, *res_x);
2180 }
2181
2182 static void pr_coords_map_orientation_reverse(PixbufRenderer *pr,
2183                                      gint area_x, gint area_y,
2184                                      gint tile_w, gint tile_h,
2185                                      gint area_w, gint area_h,
2186                                      gint *res_x, gint *res_y,
2187                                      gint *res_w, gint *res_h)
2188 {
2189         *res_x = area_x;
2190         *res_y = area_y;
2191         *res_w = area_w;
2192         *res_h = area_h;
2193
2194         switch (pr->orientation)
2195                 {
2196                 case EXIF_ORIENTATION_TOP_LEFT:
2197                         /* normal -- nothing to do */
2198                         break;
2199                 case EXIF_ORIENTATION_TOP_RIGHT:
2200                         /* mirrored */
2201                         *res_x = tile_w - area_x - area_w;
2202                         break;
2203                 case EXIF_ORIENTATION_BOTTOM_RIGHT:
2204                         /* upside down */
2205                         *res_x = tile_w - area_x - area_w;
2206                         *res_y = tile_h - area_y - area_h;
2207                         break;
2208                 case EXIF_ORIENTATION_BOTTOM_LEFT:
2209                         /* flipped */
2210                         *res_y = tile_h - area_y - area_h;
2211                         break;
2212                 case EXIF_ORIENTATION_LEFT_TOP:
2213                         *res_x = area_y;
2214                         *res_y = area_x;
2215                         *res_w = area_h;
2216                         *res_h = area_w;
2217                         break;
2218                 case EXIF_ORIENTATION_RIGHT_TOP:
2219                         /* rotated -90 (270) */
2220                         *res_x = tile_w - area_y - area_h;
2221                         *res_y = area_x;
2222                         *res_w = area_h;
2223                         *res_h = area_w;
2224                         break;
2225                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
2226                         *res_x = tile_w - area_y - area_h;
2227                         *res_y = tile_h - area_x - area_w;
2228                         *res_w = area_h;
2229                         *res_h = area_w;
2230                         break;
2231                 case EXIF_ORIENTATION_LEFT_BOTTOM:
2232                         /* rotated 90 */
2233                         *res_x = area_y;
2234                         *res_y = tile_h - area_x - area_w;
2235                         *res_w = area_h;
2236                         *res_h = area_w;
2237                         break;
2238                 default:
2239                         /* The other values are out of range */
2240                         break;
2241                 }
2242 }
2243
2244
2245 static GdkPixbuf *pr_get_spare_tile(PixbufRenderer *pr)
2246 {
2247         if (!pr->spare_tile) pr->spare_tile = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, pr->tile_width, pr->tile_height);
2248         return pr->spare_tile;
2249 }
2250
2251 #define COLOR_BYTES 3   /* rgb */
2252
2253 static void pr_tile_rotate_90_clockwise(PixbufRenderer *pr, GdkPixbuf **tile, gint x, gint y, gint w, gint h)
2254 {
2255         GdkPixbuf *src = *tile;
2256         GdkPixbuf *dest;
2257         gint srs, drs;
2258         guchar *s_pix, *d_pix;
2259         guchar *sp, *dp;
2260         guchar *ip, *spi, *dpi;
2261         gint i, j;
2262         gint tw = pr->tile_width;
2263
2264         srs = gdk_pixbuf_get_rowstride(src);
2265         s_pix = gdk_pixbuf_get_pixels(src);
2266         spi = s_pix + (x * COLOR_BYTES);
2267
2268         dest = pr_get_spare_tile(pr);
2269         drs = gdk_pixbuf_get_rowstride(dest);
2270         d_pix = gdk_pixbuf_get_pixels(dest);
2271         dpi = d_pix + (tw - 1) * COLOR_BYTES;
2272
2273         for (i = y; i < y + h; i++)
2274                 {
2275                 sp = spi + (i * srs);
2276                 ip = dpi - (i * COLOR_BYTES);
2277                 for (j = x; j < x + w; j++)
2278                         {
2279                         dp = ip + (j * drs);
2280                         memcpy(dp, sp, COLOR_BYTES);
2281                         sp += COLOR_BYTES;
2282                         }
2283                 }
2284
2285         pr->spare_tile = src;
2286         *tile = dest;
2287 }
2288
2289 static void pr_tile_rotate_90_counter_clockwise(PixbufRenderer *pr, GdkPixbuf **tile, gint x, gint y, gint w, gint h)
2290 {
2291         GdkPixbuf *src = *tile;
2292         GdkPixbuf *dest;
2293         gint srs, drs;
2294         guchar *s_pix, *d_pix;
2295         guchar *sp, *dp;
2296         guchar *ip, *spi, *dpi;
2297         gint i, j;
2298         gint th = pr->tile_height;
2299
2300         srs = gdk_pixbuf_get_rowstride(src);
2301         s_pix = gdk_pixbuf_get_pixels(src);
2302         spi = s_pix + (x * COLOR_BYTES);
2303
2304         dest = pr_get_spare_tile(pr);
2305         drs = gdk_pixbuf_get_rowstride(dest);
2306         d_pix = gdk_pixbuf_get_pixels(dest);
2307         dpi = d_pix + (th - 1) * drs;
2308
2309         for (i = y; i < y + h; i++)
2310                 {
2311                 sp = spi + (i * srs);
2312                 ip = dpi + (i * COLOR_BYTES);
2313                 for (j = x; j < x + w; j++)
2314                         {
2315                         dp = ip - (j * drs);
2316                         memcpy(dp, sp, COLOR_BYTES);
2317                         sp += COLOR_BYTES;
2318                         }
2319                 }
2320
2321         pr->spare_tile = src;
2322         *tile = dest;
2323 }
2324
2325 static void pr_tile_mirror_only(PixbufRenderer *pr, GdkPixbuf **tile, gint x, gint y, gint w, gint h)
2326 {
2327         GdkPixbuf *src = *tile;
2328         GdkPixbuf *dest;
2329         gint srs, drs;
2330         guchar *s_pix, *d_pix;
2331         guchar *sp, *dp;
2332         guchar *spi, *dpi;
2333         gint i, j;
2334
2335         gint tw = pr->tile_width;
2336
2337         srs = gdk_pixbuf_get_rowstride(src);
2338         s_pix = gdk_pixbuf_get_pixels(src);
2339         spi = s_pix + (x * COLOR_BYTES);
2340
2341         dest = pr_get_spare_tile(pr);
2342         drs = gdk_pixbuf_get_rowstride(dest);
2343         d_pix = gdk_pixbuf_get_pixels(dest);
2344         dpi =  d_pix + (tw - x - 1) * COLOR_BYTES;
2345
2346         for (i = y; i < y + h; i++)
2347                 {
2348                 sp = spi + (i * srs);
2349                 dp = dpi + (i * drs);
2350                 for (j = 0; j < w; j++)
2351                         {
2352                         memcpy(dp, sp, COLOR_BYTES);
2353                         sp += COLOR_BYTES;
2354                         dp -= COLOR_BYTES;
2355                         }
2356                 }
2357
2358         pr->spare_tile = src;
2359         *tile = dest;
2360 }
2361
2362 static void pr_tile_mirror_and_flip(PixbufRenderer *pr, GdkPixbuf **tile, gint x, gint y, gint w, gint h)
2363 {
2364         GdkPixbuf *src = *tile;
2365         GdkPixbuf *dest;
2366         gint srs, drs;
2367         guchar *s_pix, *d_pix;
2368         guchar *sp, *dp;
2369         guchar *spi, *dpi;
2370         gint i, j;
2371         gint tw = pr->tile_width;
2372         gint th = pr->tile_height;
2373
2374         srs = gdk_pixbuf_get_rowstride(src);
2375         s_pix = gdk_pixbuf_get_pixels(src);
2376         spi = s_pix + (x * COLOR_BYTES);
2377
2378         dest = pr_get_spare_tile(pr);
2379         drs = gdk_pixbuf_get_rowstride(dest);
2380         d_pix = gdk_pixbuf_get_pixels(dest);
2381         dpi = d_pix + (th - 1) * drs + (tw - 1) * COLOR_BYTES;
2382
2383         for (i = y; i < y + h; i++)
2384                 {
2385                 sp = s_pix + (i * srs) + (x * COLOR_BYTES);
2386                 dp = dpi - (i * drs) - (x * COLOR_BYTES);
2387                 for (j = 0; j < w; j++)
2388                         {
2389                         memcpy(dp, sp, COLOR_BYTES);
2390                         sp += COLOR_BYTES;
2391                         dp -= COLOR_BYTES;
2392                         }
2393                 }
2394
2395         pr->spare_tile = src;
2396         *tile = dest;
2397 }
2398
2399 static void pr_tile_flip_only(PixbufRenderer *pr, GdkPixbuf **tile, gint x, gint y, gint w, gint h)
2400 {
2401         GdkPixbuf *src = *tile;
2402         GdkPixbuf *dest;
2403         gint srs, drs;
2404         guchar *s_pix, *d_pix;
2405         guchar *sp, *dp;
2406         guchar *spi, *dpi;
2407         gint i;
2408         gint th = pr->tile_height;
2409
2410         srs = gdk_pixbuf_get_rowstride(src);
2411         s_pix = gdk_pixbuf_get_pixels(src);
2412         spi = s_pix + (x * COLOR_BYTES);
2413
2414         dest = pr_get_spare_tile(pr);
2415         drs = gdk_pixbuf_get_rowstride(dest);
2416         d_pix = gdk_pixbuf_get_pixels(dest);
2417         dpi = d_pix + (th - 1) * drs + (x * COLOR_BYTES);
2418
2419         for (i = y; i < y + h; i++)
2420                 {
2421                 sp = spi + (i * srs);
2422                 dp = dpi - (i * drs);
2423                 memcpy(dp, sp, w * COLOR_BYTES);
2424                 }
2425
2426         pr->spare_tile = src;
2427         *tile = dest;
2428 }
2429
2430 static void pr_tile_apply_orientation(PixbufRenderer *pr, GdkPixbuf **pixbuf, gint x, gint y, gint w, gint h)
2431 {
2432         switch (pr->orientation)
2433                 {
2434                 case EXIF_ORIENTATION_TOP_LEFT:
2435                         /* normal -- nothing to do */
2436                         break;
2437                 case EXIF_ORIENTATION_TOP_RIGHT:
2438                         /* mirrored */
2439                         {
2440                                 pr_tile_mirror_only(pr, pixbuf, x, y, w, h);
2441                         }
2442                         break;
2443                 case EXIF_ORIENTATION_BOTTOM_RIGHT:
2444                         /* upside down */
2445                         {
2446                                 pr_tile_mirror_and_flip(pr, pixbuf, x, y, w, h);
2447                         }
2448                         break;
2449                 case EXIF_ORIENTATION_BOTTOM_LEFT:
2450                         /* flipped */
2451                         {
2452                                 pr_tile_flip_only(pr, pixbuf, x, y, w, h);
2453                         }
2454                         break;
2455                 case EXIF_ORIENTATION_LEFT_TOP:
2456                         {
2457                                 pr_tile_flip_only(pr, pixbuf, x, y, w, h);
2458                                 pr_tile_rotate_90_clockwise(pr, pixbuf, x, pr->tile_height - y - h, w, h);
2459                         }
2460                         break;
2461                 case EXIF_ORIENTATION_RIGHT_TOP:
2462                         /* rotated -90 (270) */
2463                         {
2464                                 pr_tile_rotate_90_clockwise(pr, pixbuf, x, y, w, h);
2465                         }
2466                         break;
2467                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
2468                         {
2469                                 pr_tile_flip_only(pr, pixbuf, x, y, w, h);
2470                                 pr_tile_rotate_90_counter_clockwise(pr, pixbuf, x, pr->tile_height - y - h, w, h);
2471                         }
2472                         break;
2473                 case EXIF_ORIENTATION_LEFT_BOTTOM:
2474                         /* rotated 90 */
2475                         {
2476                                 pr_tile_rotate_90_counter_clockwise(pr, pixbuf, x, y, w, h);
2477                         }
2478                         break;
2479                 default:
2480                         /* The other values are out of range */
2481                         break;
2482                 }
2483 }
2484
2485
2486 static void pr_tile_render(PixbufRenderer *pr, ImageTile *it,
2487                            gint x, gint y, gint w, gint h,
2488                            gint new_data, gint fast)
2489 {
2490         GtkWidget *box;
2491         gint has_alpha;
2492         gint draw = FALSE;
2493
2494         if (it->render_todo == TILE_RENDER_NONE && it->pixmap && !new_data) return;
2495
2496         if (it->render_done != TILE_RENDER_ALL)
2497                 {
2498                 x = 0;
2499                 y = 0;
2500                 w = it->w;
2501                 h = it->h;
2502                 if (!fast) it->render_done = TILE_RENDER_ALL;
2503                 }
2504         else if (it->render_todo != TILE_RENDER_AREA)
2505                 {
2506                 if (!fast) it->render_todo = TILE_RENDER_NONE;
2507                 return;
2508                 }
2509
2510         if (!fast) it->render_todo = TILE_RENDER_NONE;
2511
2512         if (new_data) it->blank = FALSE;
2513
2514         pr_tile_prepare(pr, it);
2515         has_alpha = (pr->pixbuf && gdk_pixbuf_get_has_alpha(pr->pixbuf));
2516
2517         box = GTK_WIDGET(pr);
2518
2519         /* FIXME checker colors for alpha should be configurable,
2520          * also should be drawn for blank = TRUE
2521          */
2522
2523         if (it->blank)
2524                 {
2525                 /* no data, do fast rect fill */
2526                 gdk_draw_rectangle(it->pixmap, box->style->black_gc, TRUE,
2527                                    0, 0, it->w, it->h);
2528                 }
2529         else if (pr->source_tiles_enabled)
2530                 {
2531                 draw = pr_source_tile_render(pr, it, x, y, w, h, new_data, fast);
2532                 }
2533         else if (pr->zoom == 1.0 || pr->scale == 1.0)
2534                 {
2535
2536                 gdouble src_x, src_y;
2537                 gint pb_x, pb_y;
2538                 gint pb_w, pb_h;
2539                 pr_tile_coords_map_orientation(pr, it->x, it->y,
2540                                             pr->image_width, pr->image_height,
2541                                             pr->tile_width, pr->tile_height,
2542                                             &src_x, &src_y);
2543                 pr_tile_region_map_orientation(pr, x, y,
2544                                             pr->tile_width, pr->tile_height,
2545                                             w, h,
2546                                             &pb_x, &pb_y,
2547                                             &pb_w, &pb_h);
2548
2549                 if (has_alpha)
2550                         {
2551                         gdk_pixbuf_composite_color(pr->pixbuf, it->pixbuf, pb_x, pb_y, pb_w, pb_h,
2552                                          (gdouble) 0.0 - src_x,
2553                                          (gdouble) 0.0 - src_y,
2554                                          1.0, 1.0, GDK_INTERP_NEAREST,
2555                                          255, it->x + pb_x, it->y + pb_y,
2556                                          PR_ALPHA_CHECK_SIZE, PR_ALPHA_CHECK1, PR_ALPHA_CHECK2);
2557                         pr_tile_apply_orientation(pr, &it->pixbuf, pb_x, pb_y, pb_w, pb_h);
2558                         draw = TRUE;
2559                         }
2560                 else
2561                         {
2562
2563
2564                         if (pr->orientation == EXIF_ORIENTATION_TOP_LEFT && !(pr->func_post_process && !(pr->post_process_slow && fast)))
2565                                 {
2566                                 /* faster, simple, base orientation, no postprocessing */
2567                                 gdk_draw_pixbuf(it->pixmap,
2568                                                 box->style->fg_gc[GTK_WIDGET_STATE(box)],
2569                                                 pr->pixbuf,
2570                                                 it->x + x, it->y + y,
2571                                                 x, y,
2572                                                 w, h,
2573                                                 pr->dither_quality, it->x + x, it->y + y);
2574                                 }
2575                         else
2576                                 {
2577                                 gdk_pixbuf_copy_area(pr->pixbuf,
2578                                                      src_x + pb_x, src_y + pb_y,
2579                                                      pb_w, pb_h,
2580                                                      it->pixbuf,
2581                                                      pb_x, pb_y);
2582                                 pr_tile_apply_orientation(pr, &it->pixbuf, pb_x, pb_y, pb_w, pb_h);
2583                                 draw = TRUE;
2584                                 }
2585                         }
2586                 }
2587         else
2588                 {
2589                 gdouble scale_x, scale_y;
2590                 gdouble src_x, src_y;
2591                 gint pb_x, pb_y;
2592                 gint pb_w, pb_h;
2593
2594                 if (pr->image_width == 0 || pr->image_height == 0) return;
2595
2596                 scale_x = (gdouble)pr->width / pr->image_width;
2597                 scale_y = (gdouble)pr->height / pr->image_height;
2598
2599                 pr_tile_coords_map_orientation(pr, it->x / scale_x, it->y /scale_y ,
2600                                             pr->image_width, pr->image_height,
2601                                             pr->tile_width / scale_x , pr->tile_height / scale_y,
2602                                             &src_x, &src_y);
2603                 pr_tile_region_map_orientation(pr, x, y,
2604                                             pr->tile_width, pr->tile_height,
2605                                             w, h,
2606                                             &pb_x, &pb_y,
2607                                             &pb_w, &pb_h);
2608
2609                 /* HACK: The pixbuf scalers get kinda buggy(crash) with extremely
2610                  * small sizes for anything but GDK_INTERP_NEAREST
2611                  */
2612                 if (pr->width < PR_MIN_SCALE_SIZE || pr->height < PR_MIN_SCALE_SIZE) fast = TRUE;
2613
2614                 if (!has_alpha)
2615                         {
2616                         gdk_pixbuf_scale(pr->pixbuf, it->pixbuf, pb_x, pb_y, pb_w, pb_h,
2617                                          (gdouble) 0.0 - src_x * scale_x,
2618                                          (gdouble) 0.0 - src_y * scale_y,
2619                                          scale_x, scale_y,
2620                                          (fast) ? GDK_INTERP_NEAREST : pr->zoom_quality);
2621                         }
2622                 else
2623                         {
2624                         gdk_pixbuf_composite_color(pr->pixbuf, it->pixbuf, pb_x, pb_y, pb_w, pb_h,
2625                                          (gdouble) 0.0 - src_x * scale_x,
2626                                          (gdouble) 0.0 - src_y * scale_y,
2627                                          scale_x, scale_y,
2628                                          (fast) ? GDK_INTERP_NEAREST : pr->zoom_quality,
2629                                          255, it->x + pb_x, it->y + pb_y,
2630                                          PR_ALPHA_CHECK_SIZE, PR_ALPHA_CHECK1, PR_ALPHA_CHECK2);
2631                         }
2632                 pr_tile_apply_orientation(pr, &it->pixbuf, pb_x, pb_y, pb_w, pb_h);
2633                 draw = TRUE;
2634                 }
2635
2636         if (draw && it->pixbuf && !it->blank)
2637                 {
2638
2639                 if (pr->func_post_process && !(pr->post_process_slow && fast))
2640                         pr->func_post_process(pr, &it->pixbuf, x, y, w, h, pr->post_process_user_data);
2641
2642                 gdk_draw_pixbuf(it->pixmap,
2643                                 box->style->fg_gc[GTK_WIDGET_STATE(box)],
2644                                 it->pixbuf,
2645                                 x, y,
2646                                 x, y,
2647                                 w, h,
2648                                 pr->dither_quality, it->x + x, it->y + y);
2649                 }
2650
2651 #if 0
2652         /* enable this line for debugging the edges of tiles */
2653         gdk_draw_rectangle(it->pixmap, box->style->white_gc,
2654                            FALSE, 0, 0, it->w, it->h);
2655         gdk_draw_rectangle(it->pixmap, box->style->white_gc,
2656                            FALSE, x, y, w, h);
2657 #endif
2658 }
2659
2660
2661 static void pr_tile_expose(PixbufRenderer *pr, ImageTile *it,
2662                            gint x, gint y, gint w, gint h,
2663                            gint new_data, gint fast)
2664 {
2665         GtkWidget *box;
2666
2667         pr_tile_render(pr, it, x, y, w, h, new_data, fast);
2668
2669         box = GTK_WIDGET(pr);
2670
2671         gdk_draw_drawable(box->window, box->style->fg_gc[GTK_WIDGET_STATE(box)],
2672                           it->pixmap, x, y,
2673                           pr->x_offset + (it->x - pr->x_scroll) + x, pr->y_offset + (it->y - pr->y_scroll) + y, w, h);
2674
2675         if (pr->overlay_list)
2676                 {
2677                 pr_overlay_draw(pr, pr->x_offset + (it->x - pr->x_scroll) + x,
2678                                 pr->y_offset + (it->y - pr->y_scroll) + y,
2679                                 w, h,
2680                                 it);
2681                 }
2682 }
2683
2684
2685 static gint pr_tile_is_visible(PixbufRenderer *pr, ImageTile *it)
2686 {
2687         return (it->x + it->w >= pr->x_scroll && it->x < pr->x_scroll + pr->vis_width &&
2688                 it->y + it->h >= pr->y_scroll && it->y < pr->y_scroll + pr->vis_height);
2689 }
2690
2691 /*
2692  *-------------------------------------------------------------------
2693  * draw queue
2694  *-------------------------------------------------------------------
2695  */
2696
2697 static gint pr_get_queued_area(GList *work)
2698 {
2699         gint area = 0;
2700         
2701         while (work) 
2702                 {
2703                 QueueData *qd = work->data;
2704                 area += qd->w * qd->h;
2705                 work = work->next;
2706                 }
2707         return area;
2708 }
2709
2710
2711 static gint pr_queue_schedule_next_draw(PixbufRenderer *pr, gboolean force_set)
2712 {
2713         gfloat percent;
2714         gint visible_area = pr->vis_width * pr->vis_height;
2715         
2716         if (!pr->loading)
2717                 {
2718                 /* 2pass prio */ 
2719                 DEBUG_2("redraw priority: 2pass");
2720                 pr->draw_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, pr_queue_draw_idle_cb, pr, NULL);
2721                 return FALSE;
2722                 }
2723         
2724         if (visible_area == 0)
2725                 {
2726                 /* not known yet */
2727                 percent = 100.0;
2728                 }
2729         else
2730                 {
2731                 percent = 100.0 * pr_get_queued_area(pr->draw_queue) / visible_area;
2732                 }
2733         
2734         if (percent > 10.0)
2735                 {
2736                 /* we have enough data for starting intensive redrawing */
2737                 DEBUG_2("redraw priority: high %.2f %%", percent);
2738                 pr->draw_idle_id = g_idle_add_full(GDK_PRIORITY_REDRAW, pr_queue_draw_idle_cb, pr, NULL);
2739                 return FALSE;
2740                 }
2741         
2742         if (percent < 1.0 || force_set)
2743                 {
2744                 /* queue is (almost) empty, wait  50 ms*/
2745                 DEBUG_2("redraw priority: wait %.2f %%", percent);
2746                 pr->draw_idle_id = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 50, pr_queue_draw_idle_cb, pr, NULL);
2747                 return FALSE;
2748                 }
2749         
2750         /* keep the same priority as before */
2751         DEBUG_2("redraw priority: no change %.2f %%", percent);
2752         return TRUE;
2753 }
2754                 
2755
2756 static gint pr_queue_draw_idle_cb(gpointer data)
2757 {
2758         PixbufRenderer *pr = data;
2759         QueueData *qd;
2760         gint fast;
2761
2762
2763         if ((!pr->pixbuf && !pr->source_tiles_enabled) ||
2764             (!pr->draw_queue && !pr->draw_queue_2pass) ||
2765             pr->draw_idle_id == -1)
2766                 {
2767                 pr_render_complete_signal(pr);
2768
2769                 pr->draw_idle_id = -1;
2770                 return FALSE;
2771                 }
2772
2773         if (pr->draw_queue)
2774                 {
2775                 qd = pr->draw_queue->data;
2776                 fast = ((pr->zoom_2pass && pr->zoom_quality != GDK_INTERP_NEAREST && pr->scale != 1.0) || pr->post_process_slow);
2777                 }
2778         else
2779                 {
2780                 if (pr->loading)
2781                         {
2782                         /* still loading, wait till done (also drops the higher priority) */
2783
2784                         return pr_queue_schedule_next_draw(pr, FALSE);
2785                         }
2786
2787                 qd = pr->draw_queue_2pass->data;
2788                 fast = FALSE;
2789                 }
2790
2791         if (GTK_WIDGET_REALIZED(pr))
2792                 {
2793                 if (pr_tile_is_visible(pr, qd->it))
2794                         {
2795                         pr_tile_expose(pr, qd->it, qd->x, qd->y, qd->w, qd->h, qd->new_data, fast);
2796                         }
2797                 else if (qd->new_data)
2798                         {
2799                         /* if new pixel data, and we already have a pixmap, update the tile */
2800                         qd->it->blank = FALSE;
2801                         if (qd->it->pixmap && qd->it->render_done == TILE_RENDER_ALL)
2802                                 {
2803                                 pr_tile_render(pr, qd->it, qd->x, qd->y, qd->w, qd->h, qd->new_data, fast);
2804                                 }
2805                         }
2806                 }
2807
2808         if (pr->draw_queue)
2809                 {
2810                 qd->it->qd = NULL;
2811                 pr->draw_queue = g_list_remove(pr->draw_queue, qd);
2812                 if (fast)
2813                         {
2814                         if (qd->it->qd2)
2815                                 {
2816                                 pr_queue_merge(qd->it->qd2, qd);
2817                                 g_free(qd);
2818                                 }
2819                         else
2820                                 {
2821                                 qd->it->qd2 = qd;
2822                                 pr->draw_queue_2pass = g_list_append(pr->draw_queue_2pass, qd);
2823                                 }
2824                         }
2825                 else
2826                         {
2827                         g_free(qd);
2828                         }
2829                 }
2830         else
2831                 {
2832                 qd->it->qd2 = NULL;
2833                 pr->draw_queue_2pass = g_list_remove(pr->draw_queue_2pass, qd);
2834                 g_free(qd);
2835                 }
2836
2837         if (!pr->draw_queue && !pr->draw_queue_2pass)
2838                 {
2839                 pr_render_complete_signal(pr);
2840
2841                 pr->draw_idle_id = -1;
2842                 return FALSE;
2843                 }
2844
2845                 return pr_queue_schedule_next_draw(pr, FALSE);
2846 }
2847
2848 static void pr_queue_list_free(GList *list)
2849 {
2850         GList *work;
2851
2852         work = list;
2853         while (work)
2854                 {
2855                 QueueData *qd;
2856
2857                 qd = work->data;
2858                 work = work->next;
2859
2860                 qd->it->qd = NULL;
2861                 qd->it->qd2 = NULL;
2862                 g_free(qd);
2863                 }
2864
2865         g_list_free(list);
2866 }
2867
2868 static void pr_queue_clear(PixbufRenderer *pr)
2869 {
2870         pr_queue_list_free(pr->draw_queue);
2871         pr->draw_queue = NULL;
2872
2873         pr_queue_list_free(pr->draw_queue_2pass);
2874         pr->draw_queue_2pass = NULL;
2875
2876         if (pr->draw_idle_id != -1) g_source_remove(pr->draw_idle_id);
2877         pr->draw_idle_id = -1;
2878 }
2879
2880 static void pr_queue_merge(QueueData *parent, QueueData *qd)
2881 {
2882         if (parent->x + parent->w < qd->x + qd->w)
2883                 {
2884                 parent->w += (qd->x + qd->w) - (parent->x + parent->w);
2885                 }
2886         if (parent->x > qd->x)
2887                 {
2888                 parent->w += parent->x - qd->x;
2889                 parent->x = qd->x;
2890                 }
2891
2892         if (parent->y + parent->h < qd->y + qd->h)
2893                 {
2894                 parent->h += (qd->y + qd->h) - (parent->y + parent->h);
2895                 }
2896         if (parent->y > qd->y)
2897                 {
2898                 parent->h += parent->y - qd->y;
2899                 parent->y = qd->y;
2900                 }
2901
2902         parent->new_data |= qd->new_data;
2903 }
2904
2905 static gint pr_clamp_to_visible(PixbufRenderer *pr, gint *x, gint *y, gint *w, gint *h)
2906 {
2907         gint nx, ny;
2908         gint nw, nh;
2909         gint vx, vy;
2910         gint vw, vh;
2911
2912         vw = pr->vis_width;
2913         vh = pr->vis_height;
2914
2915         vx = pr->x_scroll;
2916         vy = pr->y_scroll;
2917
2918         if (*x + *w < vx || *x > vx + vw || *y + *h < vy || *y > vy + vh) return FALSE;
2919
2920         /* now clamp it */
2921         nx = CLAMP(*x, vx, vx + vw);
2922         nw = CLAMP(*w - (nx - *x), 1, vw);
2923
2924         ny = CLAMP(*y, vy, vy + vh);
2925         nh = CLAMP(*h - (ny - *y), 1, vh);
2926
2927         *x = nx;
2928         *y = ny;
2929         *w = nw;
2930         *h = nh;
2931
2932         return TRUE;
2933 }
2934
2935 static gint pr_queue_to_tiles(PixbufRenderer *pr, gint x, gint y, gint w, gint h,
2936                               gint clamp, ImageTileRenderType render, gint new_data, gint only_existing)
2937 {
2938         gint i, j;
2939         gint x1, x2;
2940         gint y1, y2;
2941
2942         if (clamp && !pr_clamp_to_visible(pr, &x, &y, &w, &h)) return FALSE;
2943
2944         x1 = (gint)floor(x / pr->tile_width) * pr->tile_width;
2945         x2 = (gint)ceil((x + w) / pr->tile_width) * pr->tile_width;
2946
2947         y1 = (gint)floor(y / pr->tile_height) * pr->tile_height;
2948         y2 = (gint)ceil((y + h) / pr->tile_height) * pr->tile_height;
2949
2950         for (j = y1; j <= y2; j += pr->tile_height)
2951                 {
2952                 for (i = x1; i <= x2; i += pr->tile_width)
2953                         {
2954                         ImageTile *it;
2955
2956                         it = pr_tile_get(pr, i, j,
2957                                          (only_existing &&
2958                                           (i + pr->tile_width < pr->x_scroll ||
2959                                            i > pr->x_scroll + pr->vis_width ||
2960                                            j + pr->tile_height < pr->y_scroll ||
2961                                            j > pr->y_scroll + pr->vis_height)));
2962                         if (it)
2963                                 {
2964                                 QueueData *qd;
2965
2966                                 if ((render == TILE_RENDER_ALL && it->render_done != TILE_RENDER_ALL) ||
2967                                     (render == TILE_RENDER_AREA && it->render_todo != TILE_RENDER_ALL))
2968                                         {
2969                                         it->render_todo = render;
2970                                         }
2971
2972                                 qd = g_new(QueueData, 1);
2973                                 qd->it = it;
2974                                 qd->new_data = new_data;
2975
2976                                 if (i < x)
2977                                         {
2978                                         qd->x = x - i;
2979                                         }
2980                                 else
2981                                         {
2982                                         qd->x = 0;
2983                                         }
2984                                 qd->w = x + w - i - qd->x;
2985                                 if (qd->x + qd->w > pr->tile_width) qd->w = pr->tile_width - qd->x;
2986
2987                                 if (j < y)
2988                                         {
2989                                         qd->y = y - j;
2990                                         }
2991                                 else
2992                                         {
2993                                         qd->y = 0;
2994                                         }
2995                                 qd->h = y + h - j - qd->y;
2996                                 if (qd->y + qd->h > pr->tile_height) qd->h = pr->tile_height - qd->y;
2997
2998                                 if (qd->w < 1 || qd->h < 1)
2999                                         {
3000                                         g_free(qd);
3001                                         }
3002                                 else if (it->qd)
3003                                         {
3004                                         pr_queue_merge(it->qd, qd);
3005                                         g_free(qd);
3006                                         }
3007                                 else
3008                                         {
3009                                         it->qd = qd;
3010                                         pr->draw_queue = g_list_append(pr->draw_queue, qd);
3011                                         }
3012                                 }
3013                         }
3014                 }
3015
3016         return TRUE;
3017 }
3018
3019 static void pr_queue(PixbufRenderer *pr, gint x, gint y, gint w, gint h,
3020                      gint clamp, ImageTileRenderType render, gint new_data, gint only_existing)
3021 {
3022         gint nx, ny;
3023
3024         nx = CLAMP(x, 0, pr->width - 1);
3025         ny = CLAMP(y, 0, pr->height - 1);
3026         w -= (nx - x);
3027         h -= (ny - y);
3028         w = CLAMP(w, 0, pr->width - nx);
3029         h = CLAMP(h, 0, pr->height - ny);
3030         if (w < 1 || h < 1) return;
3031
3032         if (pr_queue_to_tiles(pr, nx, ny, w, h, clamp, render, new_data, only_existing) &&
3033             ((!pr->draw_queue && !pr->draw_queue_2pass) || pr->draw_idle_id == -1))
3034                 {
3035                 if (pr->draw_idle_id != -1) g_source_remove(pr->draw_idle_id);
3036                 pr_queue_schedule_next_draw(pr, TRUE);
3037                 }
3038 }
3039
3040 static void pr_redraw(PixbufRenderer *pr, gint new_data)
3041 {
3042         pr_queue_clear(pr);
3043         pr_queue(pr, 0, 0, pr->width, pr->height, TRUE, TILE_RENDER_ALL, new_data, FALSE);
3044 }
3045
3046 /*
3047  *-------------------------------------------------------------------
3048  * signal emission
3049  *-------------------------------------------------------------------
3050  */
3051
3052 static void pr_update_signal(PixbufRenderer *pr)
3053 {
3054 #if 0
3055         log_printf("FIXME: send updated signal\n");
3056 #endif
3057         DEBUG_1("%s pixbuf renderer updated - started drawing %p", get_exec_time(), pr);
3058         pr->debug_updated = TRUE;
3059 }
3060
3061 static void pr_zoom_signal(PixbufRenderer *pr)
3062 {
3063         g_signal_emit(pr, signals[SIGNAL_ZOOM], 0, pr->zoom);
3064 }
3065
3066 static void pr_clicked_signal(PixbufRenderer *pr, GdkEventButton *bevent)
3067 {
3068         g_signal_emit(pr, signals[SIGNAL_CLICKED], 0, bevent);
3069 }
3070
3071 static void pr_scroll_notify_signal(PixbufRenderer *pr)
3072 {
3073         g_signal_emit(pr, signals[SIGNAL_SCROLL_NOTIFY], 0);
3074 }
3075
3076 static void pr_render_complete_signal(PixbufRenderer *pr)
3077 {
3078         if (!pr->complete)
3079                 {
3080                 g_signal_emit(pr, signals[SIGNAL_RENDER_COMPLETE], 0);
3081                 g_object_set(G_OBJECT(pr), "complete", TRUE, NULL);
3082                 }
3083         if (pr->debug_updated)
3084                 {
3085                 DEBUG_1("%s pixbuf renderer done %p", get_exec_time(), pr);
3086                 pr->debug_updated = FALSE;
3087                 }
3088 }
3089
3090 static void pr_drag_signal(PixbufRenderer *pr, GdkEventButton *bevent)
3091 {
3092         g_signal_emit(pr, signals[SIGNAL_DRAG], 0, bevent);
3093 }
3094
3095 /*
3096  *-------------------------------------------------------------------
3097  * sync and clamp
3098  *-------------------------------------------------------------------
3099  */
3100
3101 static void pixbuf_renderer_sync_scroll_center(PixbufRenderer *pr)
3102 {
3103         gint src_x, src_y;
3104         if (!pr->width || !pr->height) return;
3105
3106         src_x = pr->x_scroll + pr->vis_width / 2;
3107         src_y = pr->y_scroll + pr->vis_height / 2;
3108
3109         pr->norm_center_x = (gdouble)src_x / pr->width;
3110         pr->norm_center_y = (gdouble)src_y / pr->height;
3111 }
3112
3113
3114 static gint pr_scroll_clamp(PixbufRenderer *pr)
3115 {
3116         gint old_xs;
3117         gint old_ys;
3118
3119         if (pr->zoom == 0.0)
3120                 {
3121                 pr->x_scroll = 0;
3122                 pr->y_scroll = 0;
3123
3124                 return FALSE;
3125                 }
3126
3127         old_xs = pr->x_scroll;
3128         old_ys = pr->y_scroll;
3129
3130         if (pr->x_offset > 0)
3131                 {
3132                 pr->x_scroll = 0;
3133                 }
3134         else
3135                 {
3136                 pr->x_scroll = CLAMP(pr->x_scroll, 0, pr->width - pr->vis_width);
3137                 }
3138
3139         if (pr->y_offset > 0)
3140                 {
3141                 pr->y_scroll = 0;
3142                 }
3143         else
3144                 {
3145                 pr->y_scroll = CLAMP(pr->y_scroll, 0, pr->height - pr->vis_height);
3146                 }
3147
3148         pixbuf_renderer_sync_scroll_center(pr);
3149
3150         return (old_xs != pr->x_scroll || old_ys != pr->y_scroll);
3151 }
3152
3153 static gint pr_size_clamp(PixbufRenderer *pr)
3154 {
3155         gint old_vw, old_vh;
3156
3157         old_vw = pr->vis_width;
3158         old_vh = pr->vis_height;
3159
3160         if (pr->width < pr->window_width)
3161                 {
3162                 pr->vis_width = pr->width;
3163                 pr->x_offset = (pr->window_width - pr->width) / 2;
3164                 }
3165         else
3166                 {
3167                 pr->vis_width = pr->window_width;
3168                 pr->x_offset = 0;
3169                 }
3170
3171         if (pr->height < pr->window_height)
3172                 {
3173                 pr->vis_height = pr->height;
3174                 pr->y_offset = (pr->window_height - pr->height) / 2;
3175                 }
3176         else
3177                 {
3178                 pr->vis_height = pr->window_height;
3179                 pr->y_offset = 0;
3180                 }
3181
3182         pixbuf_renderer_sync_scroll_center(pr);
3183
3184         return (old_vw != pr->vis_width || old_vh != pr->vis_height);
3185 }
3186
3187 static gint pr_zoom_clamp(PixbufRenderer *pr, gdouble zoom,
3188                           PrZoomFlags flags, gboolean *redrawn)
3189 {
3190         gint w, h;
3191         gdouble scale;
3192         gboolean invalid;
3193         gboolean force = !!(flags & PR_ZOOM_FORCE);
3194         gboolean new = !!(flags & PR_ZOOM_NEW);
3195         gboolean invalidate = !!(flags & PR_ZOOM_INVALIDATE);
3196         gboolean lazy = !!(flags & PR_ZOOM_LAZY);
3197
3198         zoom = CLAMP(zoom, pr->zoom_min, pr->zoom_max);
3199
3200         if (pr->zoom == zoom && !force) return FALSE;
3201
3202         w = pr->image_width;
3203         h = pr->image_height;
3204
3205         if (zoom == 0.0 && !pr->pixbuf)
3206                 {
3207                 scale = 1.0;
3208                 }
3209         else if (zoom == 0.0)
3210                 {
3211                 gint max_w;
3212                 gint max_h;
3213                 gboolean sizeable;
3214
3215                 sizeable = (new && pr_parent_window_sizable(pr));
3216
3217                 if (sizeable)
3218                         {
3219                         max_w = gdk_screen_width();
3220                         max_h = gdk_screen_height();
3221
3222                         if (pr->window_limit)
3223                                 {
3224                                 max_w = max_w * pr->window_limit_size / 100;
3225                                 max_h = max_h * pr->window_limit_size / 100;
3226                                 }
3227                         }
3228                 else
3229                         {
3230                         max_w = pr->window_width;
3231                         max_h = pr->window_height;
3232                         }
3233
3234                 if ((pr->zoom_expand && !sizeable) || w > max_w || h > max_h)
3235                         {
3236                         if ((gdouble)max_w / w > (gdouble)max_h / h)
3237                                 {
3238                                 scale = (gdouble)max_h / h;
3239                                 h = max_h;
3240                                 w = w * scale + 0.5;
3241                                 if (w > max_w) w = max_w;
3242                                 }
3243                         else
3244                                 {
3245                                 scale = (gdouble)max_w / w;
3246                                 w = max_w;
3247                                 h = h * scale + 0.5;
3248                                 if (h > max_h) h = max_h;
3249                                 }
3250
3251                         if (pr->autofit_limit)
3252                                 {
3253                                 gdouble factor = (gdouble)pr->autofit_limit_size / 100;
3254                                 w = w * factor + 0.5;
3255                                 h = h * factor + 0.5;
3256                                 scale = scale * factor;
3257                                 }
3258
3259                         if (w < 1) w = 1;
3260                         if (h < 1) h = 1;
3261                         }
3262                 else
3263                         {
3264                         scale = 1.0;
3265                         }
3266                 }
3267         else if (zoom > 0.0) /* zoom orig, in */
3268                 {
3269                 scale = zoom;
3270                 w = w * scale;
3271                 h = h * scale;
3272                 }
3273         else /* zoom out */
3274                 {
3275                 scale = 1.0 / (0.0 - zoom);
3276                 w = w * scale;
3277                 h = h * scale;
3278                 }
3279
3280         invalid = (pr->width != w || pr->height != h);
3281
3282         pr->zoom = zoom;
3283         pr->width = w;
3284         pr->height = h;
3285         pr->scale = scale;
3286
3287         if (invalidate || invalid)
3288                 {
3289                 pr_tile_invalidate_all(pr);
3290                 if (!lazy) pr_redraw(pr, TRUE);
3291                 }
3292         if (redrawn) *redrawn = (invalidate || invalid);
3293
3294         pixbuf_renderer_sync_scroll_center(pr);
3295
3296         return TRUE;
3297 }
3298
3299 static void pr_zoom_sync(PixbufRenderer *pr, gdouble zoom,
3300                          PrZoomFlags flags, gint px, gint py)
3301 {
3302         gdouble old_scale;
3303         gint old_cx, old_cy;
3304         gint clamped;
3305         gint sized;
3306         gboolean redrawn = FALSE;
3307         gboolean center_point = !!(flags & PR_ZOOM_CENTER);
3308         gboolean force = !!(flags & PR_ZOOM_FORCE);
3309         gboolean new = !!(flags & PR_ZOOM_NEW);
3310         gboolean lazy = !!(flags & PR_ZOOM_LAZY);
3311         PrZoomFlags clamp_flags = flags;
3312         gdouble old_center_x = pr->norm_center_x;
3313         gdouble old_center_y = pr->norm_center_y;
3314         
3315         old_scale = pr->scale;
3316         if (center_point)
3317                 {
3318                 px = CLAMP(px, 0, pr->width);
3319                 py = CLAMP(py, 0, pr->height);
3320                 old_cx = pr->x_scroll + (px - pr->x_offset);
3321                 old_cy = pr->y_scroll + (py - pr->y_offset);
3322                 }
3323         else
3324                 {
3325                 px = py = 0;
3326                 old_cx = pr->x_scroll + pr->vis_width / 2;
3327                 old_cy = pr->y_scroll + pr->vis_height / 2;
3328                 }
3329
3330         if (force) clamp_flags |= PR_ZOOM_INVALIDATE;
3331         if (lazy) clamp_flags |= PR_ZOOM_LAZY;
3332         if (!pr_zoom_clamp(pr, zoom, clamp_flags, &redrawn)) return;
3333
3334         clamped = pr_size_clamp(pr);
3335         sized = pr_parent_window_resize(pr, pr->width, pr->height);
3336
3337         if (force && new)
3338                 {
3339                 switch (pr->scroll_reset)
3340                         {
3341                         case PR_SCROLL_RESET_NOCHANGE:
3342                                 /* maintain old scroll position */
3343                                 pr->x_scroll = ((gdouble)pr->image_width * old_center_x * pr->scale) - pr->vis_width / 2;
3344                                 pr->y_scroll = ((gdouble)pr->image_height * old_center_y * pr->scale) - pr->vis_height / 2;
3345                                 break;
3346                         case PR_SCROLL_RESET_CENTER:
3347                                 /* center new image */
3348                                 pr->x_scroll = ((gdouble)pr->image_width / 2.0 * pr->scale) - pr->vis_width / 2;
3349                                 pr->y_scroll = ((gdouble)pr->image_height / 2.0 * pr->scale) - pr->vis_height / 2;
3350                                 break;
3351                         case PR_SCROLL_RESET_TOPLEFT:
3352                         default:
3353                                 /* reset to upper left */
3354                                 pr->x_scroll = 0;
3355                                 pr->y_scroll = 0;
3356                                 break;
3357                         }
3358                 }
3359         else
3360                 {
3361                 /* user zoom does not force, so keep visible center point */
3362                 if (center_point)
3363                         {
3364                         pr->x_scroll = old_cx / old_scale * pr->scale - (px - pr->x_offset);
3365                         pr->y_scroll = old_cy / old_scale * pr->scale - (py - pr->y_offset);
3366                         }
3367                 else
3368                         {
3369                         pr->x_scroll = old_cx / old_scale * pr->scale - (pr->vis_width / 2);
3370                         pr->y_scroll = old_cy / old_scale * pr->scale - (pr->vis_height / 2);
3371                         }
3372                 }
3373
3374         pr_scroll_clamp(pr);
3375
3376         /* If the window was not sized, redraw the image - we know there will be no size/expose signal.
3377          * But even if a size is claimed, there is no guarantee that the window manager will allow it,
3378          * so redraw the window anyway :/
3379          */
3380         if (sized || clamped) pr_border_clear(pr);
3381         
3382         if (lazy)
3383                 {
3384                 pr_queue_clear(pr);
3385                 }
3386         else
3387                 {
3388                 pr_redraw(pr, redrawn);
3389                 }
3390
3391         pr_scroll_notify_signal(pr);
3392         pr_zoom_signal(pr);
3393         pr_update_signal(pr);
3394 }
3395
3396 static void pr_size_sync(PixbufRenderer *pr, gint new_width, gint new_height)
3397 {
3398         gint zoom_changed = FALSE;
3399
3400         if (pr->window_width == new_width && pr->window_height == new_height) return;
3401
3402         pr->window_width = new_width;
3403         pr->window_height = new_height;
3404
3405         if (pr->zoom == 0.0)
3406                 {
3407                 gdouble old_scale = pr->scale;
3408                 pr_zoom_clamp(pr, 0.0, PR_ZOOM_FORCE, NULL);
3409                 zoom_changed = (old_scale != pr->scale);
3410                 }
3411
3412         pr_size_clamp(pr);
3413         pr_scroll_clamp(pr);
3414
3415         pr_overlay_update_sizes(pr);
3416
3417         /* ensure scroller remains visible */
3418         if (pr->scroller_overlay != -1)
3419                 {
3420                 gint update = FALSE;
3421
3422                 if (pr->scroller_x > new_width)
3423                         {
3424                         pr->scroller_x = new_width;
3425                         pr->scroller_xpos = new_width;
3426                         update = TRUE;
3427                         }
3428                 if (pr->scroller_y > new_height)
3429                         {
3430                         pr->scroller_y = new_height;
3431                         pr->scroller_ypos = new_height;
3432                         update = TRUE;
3433                         }
3434
3435                 if (update)
3436                         {
3437                         GdkPixbuf *pixbuf;
3438
3439                         if (pixbuf_renderer_overlay_get(pr, pr->scroller_overlay, &pixbuf, NULL, NULL))
3440                                 {
3441                                 gint w, h;
3442
3443                                 w = gdk_pixbuf_get_width(pixbuf);
3444                                 h = gdk_pixbuf_get_height(pixbuf);
3445                                 pixbuf_renderer_overlay_set(pr, pr->scroller_overlay, pixbuf,
3446                                                             pr->scroller_x - w / 2, pr->scroller_y - h / 2);
3447                                 }
3448                         }
3449                 }
3450
3451         pr_border_clear(pr);
3452
3453         pr_scroll_notify_signal(pr);
3454         if (zoom_changed) pr_zoom_signal(pr);
3455         pr_update_signal(pr);
3456 }
3457
3458 static void pr_size_cb(GtkWidget *widget, GtkAllocation *allocation, gpointer data)
3459 {
3460         PixbufRenderer *pr = data;
3461
3462         pr_size_sync(pr, allocation->width, allocation->height);
3463 }
3464
3465 static void pixbuf_renderer_paint(PixbufRenderer *pr, GdkRectangle *area)
3466 {
3467         gint x, y;
3468
3469         pr_border_draw(pr, area->x, area->y, area->width, area->height);
3470
3471         x = MAX(0, (gint)area->x - pr->x_offset + pr->x_scroll);
3472         y = MAX(0, (gint)area->y - pr->y_offset + pr->y_scroll);
3473
3474         pr_queue(pr, x, y,
3475                  MIN((gint)area->width, pr->width - x),
3476                  MIN((gint)area->height, pr->height - y),
3477                  FALSE, TILE_RENDER_ALL, FALSE, FALSE);
3478 }
3479
3480 /*
3481  *-------------------------------------------------------------------
3482  * scrolling
3483  *-------------------------------------------------------------------
3484  */
3485
3486 void pixbuf_renderer_scroll(PixbufRenderer *pr, gint x, gint y)
3487 {
3488         gint old_x, old_y;
3489         gint x_off, y_off;
3490         gint w, h;
3491
3492         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3493
3494         if (!pr->pixbuf && !pr->source_tiles_enabled) return;
3495
3496         old_x = pr->x_scroll;
3497         old_y = pr->y_scroll;
3498
3499         pr->x_scroll += x;
3500         pr->y_scroll += y;
3501
3502         pr_scroll_clamp(pr);
3503         
3504         pixbuf_renderer_sync_scroll_center(pr);
3505         
3506         if (pr->x_scroll == old_x && pr->y_scroll == old_y) return;
3507
3508         pr_scroll_notify_signal(pr);
3509
3510         x_off = pr->x_scroll - old_x;
3511         y_off = pr->y_scroll - old_y;
3512
3513         w = pr->vis_width - abs(x_off);
3514         h = pr->vis_height - abs(y_off);
3515
3516         if (w < 1 || h < 1)
3517                 {
3518                 /* scrolled completely to new material */
3519                 pr_queue(pr, 0, 0, pr->width, pr->height, TRUE, TILE_RENDER_ALL, FALSE, FALSE);
3520                 return;
3521                 }
3522         else
3523                 {
3524                 gint x1, y1;
3525                 gint x2, y2;
3526                 GtkWidget *box;
3527                 GdkGC *gc;
3528                 GdkEvent *event;
3529
3530                 if (x_off < 0)
3531                         {
3532                         x1 = abs(x_off);
3533                         x2 = 0;
3534                         }
3535                 else
3536                         {
3537                         x1 = 0;
3538                         x2 = abs(x_off);
3539                         }
3540
3541                 if (y_off < 0)
3542                         {
3543                         y1 = abs(y_off);
3544                         y2 = 0;
3545                         }
3546                 else
3547                         {
3548                         y1 = 0;
3549                         y2 = abs(y_off);
3550                         }
3551
3552                 box = GTK_WIDGET(pr);
3553
3554                 gc = gdk_gc_new(box->window);
3555                 gdk_gc_set_exposures(gc, TRUE);
3556                 gdk_draw_drawable(box->window, gc,
3557                                   box->window,
3558                                   x2 + pr->x_offset, y2 + pr->y_offset,
3559                                   x1 + pr->x_offset, y1 + pr->y_offset, w, h);
3560                 g_object_unref(gc);
3561
3562                 if (pr->overlay_list)
3563                         {
3564                         pr_overlay_queue_all(pr);
3565                         }
3566
3567                 w = pr->vis_width - w;
3568                 h = pr->vis_height - h;
3569
3570                 if (w > 0)
3571                         {
3572                         pr_queue(pr,
3573                                  x_off > 0 ? pr->x_scroll + (pr->vis_width - w) : pr->x_scroll, pr->y_scroll,
3574                                  w, pr->vis_height, TRUE, TILE_RENDER_ALL, FALSE, FALSE);
3575                         }
3576                 if (h > 0)
3577                         {
3578                         /* FIXME, to optimize this, remove overlap */
3579                         pr_queue(pr,
3580                                  pr->x_scroll, y_off > 0 ? pr->y_scroll + (pr->vis_height - h) : pr->y_scroll,
3581                                  pr->vis_width, h, TRUE, TILE_RENDER_ALL, FALSE, FALSE);
3582                         }
3583
3584                 /* process exposures here, "expose_event" seems to miss a few with obstructed windows */
3585                 while ((event = gdk_event_get_graphics_expose(box->window)) != NULL)
3586                         {
3587                         pixbuf_renderer_paint(pr, &event->expose.area);
3588
3589                         if (event->expose.count == 0)
3590                                 {
3591                                 gdk_event_free(event);
3592                                 break;
3593                                 }
3594                         gdk_event_free(event);
3595                         }
3596                 }
3597 }
3598
3599 void pixbuf_renderer_scroll_to_point(PixbufRenderer *pr, gint x, gint y,
3600                                      gdouble x_align, gdouble y_align)
3601 {
3602         gint px, py;
3603         gint ax, ay;
3604
3605         x_align = CLAMP(x_align, 0.0, 1.0);
3606         y_align = CLAMP(y_align, 0.0, 1.0);
3607
3608         ax = (gdouble)pr->vis_width * x_align;
3609         ay = (gdouble)pr->vis_height * y_align;
3610
3611         px = (gdouble)x * pr->scale - (pr->x_scroll + ax);
3612         py = (gdouble)y * pr->scale - (pr->y_scroll + ay);
3613
3614         pixbuf_renderer_scroll(pr, px, py);
3615 }
3616
3617 /* get or set coordinates of viewport center in the image, in range 0.0 - 1.0 */
3618
3619 void pixbuf_renderer_get_scroll_center(PixbufRenderer *pr, gdouble *x, gdouble *y)
3620 {
3621         *x = pr->norm_center_x;
3622         *y = pr->norm_center_y;
3623 }
3624
3625 void pixbuf_renderer_set_scroll_center(PixbufRenderer *pr, gdouble x, gdouble y)
3626 {
3627         gdouble dst_x, dst_y;
3628
3629         dst_x = x * pr->width  - pr->vis_width  / 2 - pr->x_scroll + CLAMP(pr->subpixel_x_scroll, -1.0, 1.0);
3630         dst_y = y * pr->height - pr->vis_height / 2 - pr->y_scroll + CLAMP(pr->subpixel_y_scroll, -1.0, 1.0);
3631
3632         pr->subpixel_x_scroll = dst_x - (gint)dst_x;
3633         pr->subpixel_y_scroll = dst_y - (gint)dst_y;
3634
3635         pixbuf_renderer_scroll(pr, (gint)dst_x, (gint)dst_y);
3636 }
3637
3638
3639 /*
3640  *-------------------------------------------------------------------
3641  * mouse
3642  *-------------------------------------------------------------------
3643  */
3644
3645 static gint pr_mouse_motion_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
3646 {
3647         PixbufRenderer *pr;
3648         gint accel;
3649
3650         pr = PIXBUF_RENDERER(widget);
3651
3652         if (pr->scroller_id != -1)
3653                 {
3654                 pr->scroller_xpos = bevent->x;
3655                 pr->scroller_ypos = bevent->y;
3656                 }
3657
3658         if (!pr->in_drag || !gdk_pointer_is_grabbed()) return FALSE;
3659
3660         if (pr->drag_moved < PR_DRAG_SCROLL_THRESHHOLD)
3661                 {
3662                 pr->drag_moved++;
3663                 }
3664         else
3665                 {
3666                 widget_set_cursor(widget, GDK_FLEUR);
3667                 }
3668
3669         if (bevent->state & GDK_SHIFT_MASK)
3670                 {
3671                 accel = PR_PAN_SHIFT_MULTIPLIER;
3672                 }
3673         else
3674                 {
3675                 accel = 1;
3676                 }
3677
3678         /* do the scroll */
3679         pixbuf_renderer_scroll(pr, (pr->drag_last_x - bevent->x) * accel,
3680                                (pr->drag_last_y - bevent->y) * accel);
3681
3682         pr_drag_signal(pr, bevent);
3683
3684         pr->drag_last_x = bevent->x;
3685         pr->drag_last_y = bevent->y;
3686
3687         return FALSE;
3688 }
3689
3690 static gint pr_mouse_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
3691 {
3692         PixbufRenderer *pr;
3693         GtkWidget *parent;
3694
3695         pr = PIXBUF_RENDERER(widget);
3696
3697         if (pr->scroller_id != -1) return TRUE;
3698
3699         switch (bevent->button)
3700                 {
3701                 case MOUSE_BUTTON_LEFT:
3702                         pr->in_drag = TRUE;
3703                         pr->drag_last_x = bevent->x;
3704                         pr->drag_last_y = bevent->y;
3705                         pr->drag_moved = 0;
3706                         gdk_pointer_grab(widget->window, FALSE,
3707                                          GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
3708                                          NULL, NULL, bevent->time);
3709                         gtk_grab_add(widget);
3710                         break;
3711                 case MOUSE_BUTTON_MIDDLE:
3712                         pr->drag_moved = 0;
3713                         break;
3714                 case MOUSE_BUTTON_RIGHT:
3715                         pr_clicked_signal(pr, bevent);
3716                         break;
3717                 default:
3718                         break;
3719                 }
3720
3721         parent = gtk_widget_get_parent(widget);
3722         if (widget && GTK_WIDGET_CAN_FOCUS(parent))
3723                 {
3724                 gtk_widget_grab_focus(parent);
3725                 }
3726
3727         return FALSE;
3728 }
3729
3730 static gint pr_mouse_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
3731 {
3732         PixbufRenderer *pr;
3733
3734         pr = PIXBUF_RENDERER(widget);
3735
3736         if (pr->scroller_id != -1)
3737                 {
3738                 pr_scroller_stop(pr);
3739                 return TRUE;
3740                 }
3741
3742         if (gdk_pointer_is_grabbed() && GTK_WIDGET_HAS_GRAB(pr))
3743                 {
3744                 gtk_grab_remove(widget);
3745                 gdk_pointer_ungrab(bevent->time);
3746                 widget_set_cursor(widget, -1);
3747                 }
3748
3749         if (pr->drag_moved < PR_DRAG_SCROLL_THRESHHOLD)
3750                 {
3751                 if (bevent->button == MOUSE_BUTTON_LEFT && (bevent->state & GDK_SHIFT_MASK))
3752                         {
3753                         pr_scroller_start(pr, bevent->x, bevent->y);
3754                         }
3755                 else if (bevent->button == MOUSE_BUTTON_LEFT || bevent->button == MOUSE_BUTTON_MIDDLE)
3756                         {
3757                         pr_clicked_signal(pr, bevent);
3758                         }
3759                 }
3760
3761         pr->in_drag = FALSE;
3762
3763         return FALSE;
3764 }
3765
3766 static gint pr_mouse_leave_cb(GtkWidget *widget, GdkEventCrossing *event, gpointer data)
3767 {
3768         PixbufRenderer *pr;
3769
3770         pr = PIXBUF_RENDERER(widget);
3771
3772         if (pr->scroller_id != -1)
3773                 {
3774                 pr->scroller_xpos = pr->scroller_x;
3775                 pr->scroller_ypos = pr->scroller_y;
3776                 pr->scroller_xinc = 0;
3777                 pr->scroller_yinc = 0;
3778                 }
3779
3780         return FALSE;
3781 }
3782
3783 static void pr_mouse_drag_cb(GtkWidget *widget, GdkDragContext *context, gpointer data)
3784 {
3785         PixbufRenderer *pr;
3786
3787         pr = PIXBUF_RENDERER(widget);
3788
3789         pr->drag_moved = PR_DRAG_SCROLL_THRESHHOLD;
3790 }
3791
3792 static void pr_signals_connect(PixbufRenderer *pr)
3793 {
3794         g_signal_connect(G_OBJECT(pr), "motion_notify_event",
3795                          G_CALLBACK(pr_mouse_motion_cb), pr);
3796         g_signal_connect(G_OBJECT(pr), "button_press_event",
3797                          G_CALLBACK(pr_mouse_press_cb), pr);
3798         g_signal_connect(G_OBJECT(pr), "button_release_event",
3799                          G_CALLBACK(pr_mouse_release_cb), pr);
3800         g_signal_connect(G_OBJECT(pr), "leave_notify_event",
3801                          G_CALLBACK(pr_mouse_leave_cb), pr);
3802
3803         gtk_widget_set_events(GTK_WIDGET(pr), GDK_POINTER_MOTION_MASK |
3804                                               GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK |
3805                                               GDK_LEAVE_NOTIFY_MASK);
3806
3807         g_signal_connect(G_OBJECT(pr), "drag_begin",
3808                          G_CALLBACK(pr_mouse_drag_cb), pr);
3809
3810 }
3811
3812 /*
3813  *-------------------------------------------------------------------
3814  * public
3815  *-------------------------------------------------------------------
3816  */
3817 static void pr_pixbuf_size_sync(PixbufRenderer *pr)
3818 {
3819         if (!pr->pixbuf) return;
3820         switch (pr->orientation)
3821                 {
3822                 case EXIF_ORIENTATION_LEFT_TOP:
3823                 case EXIF_ORIENTATION_RIGHT_TOP:
3824                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
3825                 case EXIF_ORIENTATION_LEFT_BOTTOM:
3826                         pr->image_width = gdk_pixbuf_get_height(pr->pixbuf);
3827                         pr->image_height = gdk_pixbuf_get_width(pr->pixbuf);
3828                         break;
3829                 default:
3830                         pr->image_width = gdk_pixbuf_get_width(pr->pixbuf);
3831                         pr->image_height = gdk_pixbuf_get_height(pr->pixbuf);
3832                 }
3833 }
3834
3835 static void pr_set_pixbuf(PixbufRenderer *pr, GdkPixbuf *pixbuf, gdouble zoom, PrZoomFlags flags)
3836 {
3837         if (pixbuf) g_object_ref(pixbuf);
3838         if (pr->pixbuf) g_object_unref(pr->pixbuf);
3839         pr->pixbuf = pixbuf;
3840
3841         if (!pr->pixbuf)
3842                 {
3843                 GtkWidget *box;
3844
3845                 /* no pixbuf so just clear the window */
3846                 pr->image_width = 0;
3847                 pr->image_height = 0;
3848                 pr->scale = 1.0;
3849
3850                 box = GTK_WIDGET(pr);
3851
3852                 if (GTK_WIDGET_REALIZED(box))
3853                         {
3854                         gdk_window_clear(box->window);
3855                         pr_overlay_draw(pr, 0, 0, pr->window_width, pr->window_height, NULL);
3856                         }
3857
3858                 pr_update_signal(pr);
3859
3860                 return;
3861                 }
3862
3863         pr_pixbuf_size_sync(pr);
3864         pr_zoom_sync(pr, zoom, flags | PR_ZOOM_FORCE | PR_ZOOM_NEW, 0, 0);
3865 }
3866
3867 void pixbuf_renderer_set_pixbuf(PixbufRenderer *pr, GdkPixbuf *pixbuf, gdouble zoom)
3868 {
3869         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3870
3871         pr_source_tile_unset(pr);
3872
3873         pr_set_pixbuf(pr, pixbuf, zoom, 0);
3874
3875         pr_update_signal(pr);
3876 }
3877
3878 void pixbuf_renderer_set_pixbuf_lazy(PixbufRenderer *pr, GdkPixbuf *pixbuf, gdouble zoom, gint orientation)
3879 {
3880         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3881
3882         pr_source_tile_unset(pr);
3883
3884         pr->orientation = orientation;
3885         pr_set_pixbuf(pr, pixbuf, zoom, PR_ZOOM_LAZY);
3886
3887         pr_update_signal(pr);
3888 }
3889
3890 GdkPixbuf *pixbuf_renderer_get_pixbuf(PixbufRenderer *pr)
3891 {
3892         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), NULL);
3893
3894         return pr->pixbuf;
3895 }
3896
3897 void pixbuf_renderer_set_orientation(PixbufRenderer *pr, gint orientation)
3898 {
3899         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3900
3901         pr->orientation = orientation;
3902
3903         pr_pixbuf_size_sync(pr);
3904         pr_zoom_sync(pr, pr->zoom, PR_ZOOM_FORCE, 0, 0);
3905 }
3906
3907 gint pixbuf_renderer_get_orientation(PixbufRenderer *pr)
3908 {
3909         if (!pr) return 1;
3910         return pr->orientation;
3911 }
3912
3913 void pixbuf_renderer_set_post_process_func(PixbufRenderer *pr, PixbufRendererPostProcessFunc func, gpointer user_data, gint slow)
3914 {
3915         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3916
3917         pr->func_post_process = func;
3918         pr->post_process_user_data = user_data;
3919         pr->post_process_slow = func && slow;
3920
3921 }
3922
3923
3924 void pixbuf_renderer_move(PixbufRenderer *pr, PixbufRenderer *source)
3925 {
3926         GObject *object;
3927         PixbufRendererScrollResetType scroll_reset;
3928
3929         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3930         g_return_if_fail(IS_PIXBUF_RENDERER(source));
3931
3932         if (pr == source) return;
3933
3934         object = G_OBJECT(pr);
3935
3936         g_object_set(object, "zoom_min", source->zoom_min, NULL);
3937         g_object_set(object, "zoom_max", source->zoom_max, NULL);
3938         g_object_set(object, "loading", source->loading, NULL);
3939
3940         pr->complete = source->complete;
3941
3942         pr->x_scroll = source->x_scroll;
3943         pr->y_scroll = source->y_scroll;
3944
3945         scroll_reset = pr->scroll_reset;
3946         pr->scroll_reset = PR_SCROLL_RESET_NOCHANGE;
3947
3948         pr->func_post_process = source->func_post_process;
3949         pr->post_process_user_data = source->post_process_user_data;
3950         pr->post_process_slow = source->post_process_slow;
3951         pr->orientation = source->orientation;
3952
3953         if (source->source_tiles_enabled)
3954                 {
3955                 pr_source_tile_unset(pr);
3956
3957                 pr->source_tiles_enabled = source->source_tiles_enabled;
3958                 pr->source_tiles_cache_size = source->source_tiles_cache_size;
3959                 pr->source_tile_width = source->source_tile_width;
3960                 pr->source_tile_height = source->source_tile_height;
3961                 pr->image_width = source->image_width;
3962                 pr->image_height = source->image_height;
3963
3964                 pr->func_tile_request = source->func_tile_request;
3965                 pr->func_tile_dispose = source->func_tile_dispose;
3966                 pr->func_tile_data = source->func_tile_data;
3967
3968                 pr->source_tiles = source->source_tiles;
3969                 source->source_tiles = NULL;
3970
3971                 pr_zoom_sync(pr, source->zoom, PR_ZOOM_FORCE | PR_ZOOM_NEW, 0, 0);
3972                 pr_redraw(pr, TRUE);
3973                 }
3974         else
3975                 {
3976                 pixbuf_renderer_set_pixbuf(pr, source->pixbuf, source->zoom);
3977                 }
3978
3979         pr->scroll_reset = scroll_reset;
3980
3981         pixbuf_renderer_set_pixbuf(source, NULL, source->zoom);
3982         pr_queue_clear(source);
3983         pr_tile_free_all(source);
3984 }
3985
3986 void pixbuf_renderer_area_changed(PixbufRenderer *pr, gint src_x, gint src_y, gint src_w, gint src_h)
3987 {
3988         gint x, y, width, height,  x1, y1, x2, y2;
3989
3990         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
3991
3992         pr_coords_map_orientation_reverse(pr,
3993                                      src_x, src_y,
3994                                      pr->image_width, pr->image_height,
3995                                      src_w, src_h,
3996                                      &x, &y,
3997                                      &width, &height);
3998
3999         if (pr->source_tiles_enabled)
4000                 {
4001                 pr_source_tile_changed(pr, x, y, width, height);
4002                 }
4003
4004         if (pr->scale != 1.0 && pr->zoom_quality != GDK_INTERP_NEAREST)
4005                 {
4006                 /* increase region when using a zoom quality that may access surrounding pixels */
4007                 y -= 1;
4008                 height += 2;
4009                 }
4010
4011         x1 = (gint)floor((gdouble)x * pr->scale);
4012         y1 = (gint)floor((gdouble)y * pr->scale);
4013         x2 = (gint)ceil((gdouble)(x + width) * pr->scale);
4014         y2 = (gint)ceil((gdouble)(y + height) * pr->scale);
4015
4016         pr_queue(pr, x1, y1, x2 - x1, y2 - y1, FALSE, TILE_RENDER_AREA, TRUE, TRUE);
4017 }
4018
4019 void pixbuf_renderer_zoom_adjust(PixbufRenderer *pr, gdouble increment)
4020 {
4021         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
4022
4023         pr_zoom_adjust_real(pr, increment, PR_ZOOM_NONE, 0, 0);
4024 }
4025
4026 void pixbuf_renderer_zoom_adjust_at_point(PixbufRenderer *pr, gdouble increment, gint x, gint y)
4027 {
4028         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
4029
4030         pr_zoom_adjust_real(pr, increment, PR_ZOOM_CENTER, x, y);
4031 }
4032
4033 void pixbuf_renderer_zoom_set(PixbufRenderer *pr, gdouble zoom)
4034 {
4035         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
4036
4037         pr_zoom_sync(pr, zoom, PR_ZOOM_NONE, 0, 0);
4038 }
4039
4040 gdouble pixbuf_renderer_zoom_get(PixbufRenderer *pr)
4041 {
4042         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), 1.0);
4043
4044         return pr->zoom;
4045 }
4046
4047 gdouble pixbuf_renderer_zoom_get_scale(PixbufRenderer *pr)
4048 {
4049         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), 1.0);
4050
4051         return pr->scale;
4052 }
4053
4054 void pixbuf_renderer_zoom_set_limits(PixbufRenderer *pr, gdouble min, gdouble max)
4055 {
4056         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
4057
4058         if (min > 1.0 || max < 1.0) return;
4059         if (min < 1.0 && min > -1.0) return;
4060         if (min < -200.0 || max > 200.0) return;
4061
4062         if (pr->zoom_min != min)
4063                 {
4064                 pr->zoom_min = min;
4065                 g_object_notify(G_OBJECT(pr), "zoom_min");
4066                 }
4067         if (pr->zoom_max != max)
4068                 {
4069                 pr->zoom_max = max;
4070                 g_object_notify(G_OBJECT(pr), "zoom_max");
4071                 }
4072 }
4073
4074 gint pixbuf_renderer_get_image_size(PixbufRenderer *pr, gint *width, gint *height)
4075 {
4076         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
4077         g_return_val_if_fail(width != NULL && height != NULL, FALSE);
4078
4079         if (!pr->pixbuf && !pr->source_tiles_enabled)
4080                 {
4081                 *width = 0;
4082                 *height = 0;
4083                 return FALSE;
4084                 }
4085
4086         *width = pr->image_width;
4087         *height = pr->image_height;
4088         return TRUE;
4089 }
4090
4091 gint pixbuf_renderer_get_scaled_size(PixbufRenderer *pr, gint *width, gint *height)
4092 {
4093         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
4094         g_return_val_if_fail(width != NULL && height != NULL, FALSE);
4095
4096         if (!pr->pixbuf && !pr->source_tiles_enabled)
4097                 {
4098                 *width = 0;
4099                 *height = 0;
4100                 return FALSE;
4101                 }
4102
4103         *width = pr->width;
4104         *height = pr->height;
4105         return TRUE;
4106 }
4107
4108 gint pixbuf_renderer_get_visible_rect(PixbufRenderer *pr, GdkRectangle *rect)
4109 {
4110         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
4111         g_return_val_if_fail(rect != NULL, FALSE);
4112
4113         if ((!pr->pixbuf && !pr->source_tiles_enabled) ||
4114             !pr->scale)
4115                 {
4116                 rect->x = 0;
4117                 rect->y = 0;
4118                 rect->width = 0;
4119                 rect->height = 0;
4120                 return FALSE;
4121                 }
4122
4123         rect->x = (gint)((gdouble)pr->x_scroll / pr->scale);
4124         rect->y = (gint)((gdouble)pr->y_scroll / pr->scale);
4125         rect->width = (gint)((gdouble)pr->vis_width / pr->scale);
4126         rect->height = (gint)((gdouble)pr->vis_height / pr->scale);
4127         return TRUE;
4128 }
4129
4130 gint pixbuf_renderer_get_virtual_rect(PixbufRenderer *pr, GdkRectangle *rect)
4131 {
4132         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
4133         g_return_val_if_fail(rect != NULL, FALSE);
4134
4135         if ((!pr->pixbuf && !pr->source_tiles_enabled))
4136                 {
4137                 rect->x = 0;
4138                 rect->y = 0;
4139                 rect->width = 0;
4140                 rect->height = 0;
4141                 return FALSE;
4142                 }
4143
4144         rect->x = pr->x_scroll;
4145         rect->y = pr->y_scroll;
4146         rect->width = pr->vis_width;
4147         rect->height = pr->vis_height;
4148         return TRUE;
4149 }