GTK2 no longer supported
[geeqie.git] / src / renderer-tiles.c
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2021 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <math.h>
26
27 #include "main.h"
28 #include "pixbuf-renderer.h"
29 #include "renderer-tiles.h"
30
31 #include "intl.h"
32 #include "layout.h"
33
34 #include <gtk/gtk.h>
35
36
37 /* comment this out if not using this from within Geeqie
38  * defining GQ_BUILD does these things:
39  *   - Sets the shift-click scroller pixbuf to a nice icon instead of a black box
40  */
41 #define GQ_BUILD 1
42
43 #ifdef GQ_BUILD
44 #include "main.h"
45 #include "pixbuf_util.h"
46 #include "exif.h"
47 #else
48 typedef enum {
49         EXIF_ORIENTATION_UNKNOWN        = 0,
50         EXIF_ORIENTATION_TOP_LEFT       = 1,
51         EXIF_ORIENTATION_TOP_RIGHT      = 2,
52         EXIF_ORIENTATION_BOTTOM_RIGHT   = 3,
53         EXIF_ORIENTATION_BOTTOM_LEFT    = 4,
54         EXIF_ORIENTATION_LEFT_TOP       = 5,
55         EXIF_ORIENTATION_RIGHT_TOP      = 6,
56         EXIF_ORIENTATION_RIGHT_BOTTOM   = 7,
57         EXIF_ORIENTATION_LEFT_BOTTOM    = 8
58 } ExifOrientationType;
59 #endif
60
61 typedef struct _ImageTile ImageTile;
62 typedef struct _QueueData QueueData;
63
64 struct _ImageTile
65 {
66         cairo_surface_t *surface;       /* off screen buffer */
67         GdkPixbuf *pixbuf;      /* pixbuf area for zooming */
68         gint x;                 /* x offset into image */
69         gint y;                 /* y offset into image */
70         gint w;                 /* width that is visible (may be less if at edge of image) */
71         gint h;                 /* height '' */
72
73         gboolean blank;
74
75 /* render_todo: (explanation)
76         NONE    do nothing
77         AREA    render area of tile, usually only used when loading an image
78                 note: will jump to an ALL if render_done is not ALL.
79         ALL     render entire tile, if never done before w/ ALL, for expose events *only*
80 */
81
82         ImageRenderType render_todo;    /* what to do (see above) */
83         ImageRenderType render_done;    /* highest that has been done before on tile */
84
85         QueueData *qd;
86         QueueData *qd2;
87
88         guint size;             /* est. memory used by pixmap and pixbuf */
89 };
90
91 struct _QueueData
92 {
93         ImageTile *it;
94         gint x;
95         gint y;
96         gint w;
97         gint h;
98         gboolean new_data;
99 };
100
101 typedef struct _OverlayData OverlayData;
102 struct _OverlayData
103 {
104         gint id;
105
106         GdkPixbuf *pixbuf;
107         GdkWindow *window;
108
109         gint x;
110         gint y;
111
112         OverlayRendererFlags flags;
113 };
114
115 typedef struct _RendererTiles RendererTiles;
116
117 struct _RendererTiles
118 {
119         RendererFuncs f;
120         PixbufRenderer *pr;
121
122         gint tile_cache_max;            /* max MiB to use for offscreen buffer */
123
124         gint tile_width;
125         gint tile_height;
126         gint tile_cols;         /* count of tile columns */
127         GList *tiles;           /* list of buffer tiles */
128         gint tile_cache_size;   /* allocated size of pixmaps/pixbufs */
129         GList *draw_queue;      /* list of areas to redraw */
130         GList *draw_queue_2pass;/* list when 2 pass is enabled */
131
132         GList *overlay_list;
133         cairo_surface_t *overlay_buffer;
134         cairo_surface_t *surface;
135
136         guint draw_idle_id; /* event source id */
137
138         GdkPixbuf *spare_tile;
139
140         gint stereo_mode;
141         gint stereo_off_x;
142         gint stereo_off_y;
143
144         gint x_scroll;  /* allow local adjustment and mirroring */
145         gint y_scroll;
146
147         gint hidpi_scale;
148 };
149
150
151
152 static void rt_border_draw(RendererTiles *rt, gint x, gint y, gint w, gint h);
153 static void rt_overlay_draw(RendererTiles *rt, gint x, gint y, gint w, gint h, ImageTile *it);
154
155
156 static void rt_tile_free_all(RendererTiles *rt);
157 static void rt_tile_invalidate_region(RendererTiles *rt, gint x, gint y, gint w, gint h);
158 static gboolean rt_tile_is_visible(RendererTiles *rt, ImageTile *it);
159 static void rt_queue_clear(RendererTiles *rt);
160 static void rt_queue_merge(QueueData *parent, QueueData *qd);
161 static void rt_queue(RendererTiles *rt, gint x, gint y, gint w, gint h,
162                      gint clamp, ImageRenderType render, gboolean new_data, gboolean only_existing);
163
164 static void rt_hierarchy_changed_cb(GtkWidget *widget, GtkWidget *previous_toplevel, gpointer data);
165 static gint rt_queue_draw_idle_cb(gpointer data);
166
167 #define GET_RIGHT_PIXBUF_OFFSET(rt) \
168         (( (rt->stereo_mode & PR_STEREO_RIGHT) && !(rt->stereo_mode & PR_STEREO_SWAP)) || \
169          (!(rt->stereo_mode & PR_STEREO_RIGHT) &&  (rt->stereo_mode & PR_STEREO_SWAP)) ?  \
170           rt->pr->stereo_pixbuf_offset_right : rt->pr->stereo_pixbuf_offset_left )
171
172 #define GET_LEFT_PIXBUF_OFFSET(rt) \
173         ((!(rt->stereo_mode & PR_STEREO_RIGHT) && !(rt->stereo_mode & PR_STEREO_SWAP)) || \
174          ( (rt->stereo_mode & PR_STEREO_RIGHT) &&  (rt->stereo_mode & PR_STEREO_SWAP)) ?  \
175           rt->pr->stereo_pixbuf_offset_right : rt->pr->stereo_pixbuf_offset_left )
176
177
178 static void rt_sync_scroll(RendererTiles *rt)
179 {
180         PixbufRenderer *pr = rt->pr;
181
182         rt->x_scroll = (rt->stereo_mode & PR_STEREO_MIRROR) ?
183                        pr->width - pr->vis_width - pr->x_scroll
184                        : pr->x_scroll;
185
186         rt->y_scroll = (rt->stereo_mode & PR_STEREO_FLIP) ?
187                        pr->height - pr->vis_height - pr->y_scroll
188                        : pr->y_scroll;
189 }
190
191 /*
192  *-------------------------------------------------------------------
193  * borders
194  *-------------------------------------------------------------------
195  */
196
197 static void rt_border_draw(RendererTiles *rt, gint x, gint y, gint w, gint h)
198 {
199         PixbufRenderer *pr = rt->pr;
200         GtkWidget *box;
201         GdkWindow *window;
202         gint rx, ry, rw, rh;
203         cairo_t *cr;
204
205         box = GTK_WIDGET(pr);
206         window = gtk_widget_get_window(box);
207
208         if (!window) return;
209
210         cr = cairo_create(rt->surface);
211
212         if (!pr->pixbuf && !pr->source_tiles_enabled)
213                 {
214                 if (pr_clip_region(x, y, w, h,
215                                    0, 0,
216                                    pr->viewport_width, pr->viewport_height,
217                                    &rx, &ry, &rw, &rh))
218                         {
219                         cairo_set_source_rgb(cr, (double)pr->color.red/65535, (double)pr->color.green/65535, (double)pr->color.blue/65535);
220                         cairo_rectangle(cr, rx + rt->stereo_off_x, ry + rt->stereo_off_y, rw, rh);
221                         cairo_fill(cr);
222                         rt_overlay_draw(rt, rx, ry, rw, rh, NULL);
223                         }
224                 cairo_destroy(cr);
225                 return;
226                 }
227
228         if (pr->vis_width < pr->viewport_width)
229                 {
230                 if (pr->x_offset > 0 &&
231                     pr_clip_region(x, y, w, h,
232                                    0, 0,
233                                    pr->x_offset, pr->viewport_height,
234                                    &rx, &ry, &rw, &rh))
235                         {
236                         cairo_set_source_rgb(cr, (double)pr->color.red/65535, (double)pr->color.green/65535, (double)pr->color.blue/65535);
237                         cairo_rectangle(cr, rx + rt->stereo_off_x, ry + rt->stereo_off_y, rw, rh);
238                         cairo_fill(cr);
239                         rt_overlay_draw(rt, rx, ry, rw, rh, NULL);
240                         }
241                 if (pr->viewport_width - pr->vis_width - pr->x_offset > 0 &&
242                     pr_clip_region(x, y, w, h,
243                                    pr->x_offset + pr->vis_width, 0,
244                                    pr->viewport_width - pr->vis_width - pr->x_offset, pr->viewport_height,
245                                    &rx, &ry, &rw, &rh))
246                         {
247                         cairo_set_source_rgb(cr, (double)pr->color.red/65535, (double)pr->color.green/65535, (double)pr->color.blue/65535);
248                         cairo_rectangle(cr, rx + rt->stereo_off_x, ry + rt->stereo_off_y, rw, rh);
249                         cairo_fill(cr);
250                         rt_overlay_draw(rt, rx, ry, rw, rh, NULL);
251                         }
252                 }
253         if (pr->vis_height < pr->viewport_height)
254                 {
255                 if (pr->y_offset > 0 &&
256                     pr_clip_region(x, y, w, h,
257                                    pr->x_offset, 0,
258                                    pr->vis_width, pr->y_offset,
259                                    &rx, &ry, &rw, &rh))
260                         {
261                         cairo_set_source_rgb(cr, (double)pr->color.red/65535, (double)pr->color.green/65535, (double)pr->color.blue/65535);
262                         cairo_rectangle(cr, rx + rt->stereo_off_x, ry + rt->stereo_off_y, rw, rh);
263                         cairo_fill(cr);
264                         rt_overlay_draw(rt, rx, ry, rw, rh, NULL);
265                         }
266                 if (pr->viewport_height - pr->vis_height - pr->y_offset > 0 &&
267                     pr_clip_region(x, y, w, h,
268                                    pr->x_offset, pr->y_offset + pr->vis_height,
269                                    pr->vis_width, pr->viewport_height - pr->vis_height - pr->y_offset,
270                                    &rx, &ry, &rw, &rh))
271                         {
272                         cairo_set_source_rgb(cr, (double)pr->color.red/65535, (double)pr->color.green/65535, (double)pr->color.blue/65535);
273                         cairo_rectangle(cr, rx + rt->stereo_off_x, ry + rt->stereo_off_y, rw, rh);
274                         cairo_fill(cr);
275                         rt_overlay_draw(rt, rx, ry, rw, rh, NULL);
276                         }
277                 }
278         cairo_destroy(cr);
279 }
280
281 static void rt_border_clear(RendererTiles *rt)
282 {
283         PixbufRenderer *pr = rt->pr;
284         rt_border_draw(rt, 0, 0, pr->viewport_width, pr->viewport_height);
285 }
286
287
288 /*
289  *-------------------------------------------------------------------
290  * display tiles
291  *-------------------------------------------------------------------
292  */
293
294 static ImageTile *rt_tile_new(gint x, gint y, gint width, gint height)
295 {
296         ImageTile *it;
297
298         it = g_new0(ImageTile, 1);
299
300         it->x = x;
301         it->y = y;
302         it->w = width;
303         it->h = height;
304
305         it->render_done = TILE_RENDER_NONE;
306
307         return it;
308 }
309
310 static void rt_tile_free(ImageTile *it)
311 {
312         if (!it) return;
313
314         if (it->pixbuf) g_object_unref(it->pixbuf);
315         if (it->surface) cairo_surface_destroy(it->surface);
316
317         g_free(it);
318 }
319
320 static void rt_tile_free_all(RendererTiles *rt)
321 {
322         GList *work;
323
324         work = rt->tiles;
325         while (work)
326                 {
327                 ImageTile *it;
328
329                 it = work->data;
330                 work = work->next;
331
332                 rt_tile_free(it);
333                 }
334
335         g_list_free(rt->tiles);
336         rt->tiles = NULL;
337         rt->tile_cache_size = 0;
338 }
339
340 static ImageTile *rt_tile_add(RendererTiles *rt, gint x, gint y)
341 {
342         PixbufRenderer *pr = rt->pr;
343         ImageTile *it;
344
345         it = rt_tile_new(x, y, rt->tile_width, rt->tile_height);
346
347         if (it->x + it->w > pr->width) it->w = pr->width - it->x;
348         if (it->y + it->h > pr->height) it->h = pr->height - it->y;
349
350         rt->tiles = g_list_prepend(rt->tiles, it);
351         rt->tile_cache_size += it->size;
352
353         return it;
354 }
355
356 static void rt_tile_remove(RendererTiles *rt, ImageTile *it)
357 {
358         if (it->qd)
359                 {
360                 QueueData *qd = it->qd;
361
362                 it->qd = NULL;
363                 rt->draw_queue = g_list_remove(rt->draw_queue, qd);
364                 g_free(qd);
365                 }
366
367         if (it->qd2)
368                 {
369                 QueueData *qd = it->qd2;
370
371                 it->qd2 = NULL;
372                 rt->draw_queue_2pass = g_list_remove(rt->draw_queue_2pass, qd);
373                 g_free(qd);
374                 }
375
376         rt->tiles = g_list_remove(rt->tiles, it);
377         rt->tile_cache_size -= it->size;
378
379         rt_tile_free(it);
380 }
381
382 static void rt_tile_free_space(RendererTiles *rt, guint space, ImageTile *it)
383 {
384         PixbufRenderer *pr = rt->pr;
385         GList *work;
386         guint tile_max;
387
388         work = g_list_last(rt->tiles);
389
390         if (pr->source_tiles_enabled && pr->scale < 1.0)
391                 {
392                 gint tiles;
393
394                 tiles = (pr->vis_width / rt->tile_width + 1) * (pr->vis_height / rt->tile_height + 1);
395                 tile_max = MAX(tiles * rt->tile_width * rt->tile_height * 3,
396                                (gint)((gdouble)rt->tile_cache_max * 1048576.0 * pr->scale));
397                 }
398         else
399                 {
400                 tile_max = rt->tile_cache_max * 1048576;
401                 }
402
403         while (work && rt->tile_cache_size + space > tile_max)
404                 {
405                 ImageTile *needle;
406
407                 needle = work->data;
408                 work = work->prev;
409                 if (needle != it &&
410                     ((!needle->qd && !needle->qd2) || !rt_tile_is_visible(rt, needle))) rt_tile_remove(rt, needle);
411                 }
412 }
413
414 static void rt_tile_invalidate_all(RendererTiles *rt)
415 {
416         PixbufRenderer *pr = rt->pr;
417         GList *work;
418
419         work = rt->tiles;
420         while (work)
421                 {
422                 ImageTile *it;
423
424                 it = work->data;
425                 work = work->next;
426
427                 it->render_done = TILE_RENDER_NONE;
428                 it->render_todo = TILE_RENDER_ALL;
429                 it->blank = FALSE;
430
431                 it->w = MIN(rt->tile_width, pr->width - it->x);
432                 it->h = MIN(rt->tile_height, pr->height - it->y);
433                 }
434 }
435
436 static void rt_tile_invalidate_region(RendererTiles *rt, gint x, gint y, gint w, gint h)
437 {
438         gint x1, x2;
439         gint y1, y2;
440         GList *work;
441
442         x1 = ROUND_DOWN(x, rt->tile_width);
443         x2 = ROUND_UP(x + w, rt->tile_width);
444
445         y1 = ROUND_DOWN(y, rt->tile_height);
446         y2 = ROUND_UP(y + h, rt->tile_height);
447
448         work = rt->tiles;
449         while (work)
450                 {
451                 ImageTile *it;
452
453                 it = work->data;
454                 work = work->next;
455
456                 if (it->x < x2 && it->x + it->w > x1 &&
457                     it->y < y2 && it->y + it->h > y1)
458                         {
459                         it->render_done = TILE_RENDER_NONE;
460                         it->render_todo = TILE_RENDER_ALL;
461                         }
462                 }
463 }
464
465 static ImageTile *rt_tile_get(RendererTiles *rt, gint x, gint y, gboolean only_existing)
466 {
467         GList *work;
468
469         work = rt->tiles;
470         while (work)
471                 {
472                 ImageTile *it;
473
474                 it = work->data;
475                 if (it->x == x && it->y == y)
476                         {
477                         rt->tiles = g_list_delete_link(rt->tiles, work);
478                         rt->tiles = g_list_prepend(rt->tiles, it);
479                         return it;
480                         }
481
482                 work = work->next;
483                 }
484
485         if (only_existing) return NULL;
486
487         return rt_tile_add(rt, x, y);
488 }
489
490 static gint pixmap_calc_size(cairo_surface_t *UNUSED(surface))
491 {
492 //      gint w, h, d;
493
494 //      d = gdk_drawable_get_depth(pixmap);
495 //      gdk_drawable_get_size(pixmap, &w, &h);
496         return options->image.tile_size * options->image.tile_size * 4 / 8;
497 }
498
499 static void rt_hidpi_aware_draw(
500         RendererTiles *rt,
501         cairo_t *cr,
502         GdkPixbuf *pixbuf,
503         double x,
504         double y)
505 {
506         cairo_surface_t *surface;
507         surface = gdk_cairo_surface_create_from_pixbuf(pixbuf, rt->hidpi_scale, NULL);
508         cairo_set_source_surface(cr, surface, x, y);
509         cairo_fill(cr);
510         cairo_surface_destroy(surface);
511 }
512
513 static void rt_tile_prepare(RendererTiles *rt, ImageTile *it)
514 {
515         PixbufRenderer *pr = rt->pr;
516         if (!it->surface)
517                 {
518                 cairo_surface_t *surface;
519                 guint size;
520
521                 surface = gdk_window_create_similar_surface(gtk_widget_get_window((GtkWidget *)pr),
522                                                             CAIRO_CONTENT_COLOR,
523                                                             rt->tile_width, rt->tile_height);
524
525                 size = pixmap_calc_size(surface) * rt->hidpi_scale * rt->hidpi_scale;
526                 rt_tile_free_space(rt, size, it);
527
528                 it->surface = surface;
529                 it->size += size;
530                 rt->tile_cache_size += size;
531                 }
532
533         if (!it->pixbuf)
534                 {
535                 GdkPixbuf *pixbuf;
536                 guint size;
537                 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, rt->hidpi_scale * rt->tile_width, rt->hidpi_scale * rt->tile_height);
538
539                 size = gdk_pixbuf_get_rowstride(pixbuf) * rt->tile_height * rt->hidpi_scale;
540                 rt_tile_free_space(rt, size, it);
541
542                 it->pixbuf = pixbuf;
543                 it->size += size;
544                 rt->tile_cache_size += size;
545                 }
546 }
547
548 /*
549  *-------------------------------------------------------------------
550  * overlays
551  *-------------------------------------------------------------------
552  */
553
554 static void rt_overlay_get_position(RendererTiles *rt, OverlayData *od,
555                                     gint *x, gint *y, gint *w, gint *h)
556 {
557         PixbufRenderer *pr = rt->pr;
558         gint px, py, pw, ph;
559
560         pw = gdk_pixbuf_get_width(od->pixbuf);
561         ph = gdk_pixbuf_get_height(od->pixbuf);
562         px = od->x;
563         py = od->y;
564
565         if (od->flags & OVL_RELATIVE)
566                 {
567                 if (px < 0) px = pr->viewport_width - pw + px;
568                 if (py < 0) py = pr->viewport_height - ph + py;
569                 }
570
571         if (x) *x = px;
572         if (y) *y = py;
573         if (w) *w = pw;
574         if (h) *h = ph;
575 }
576
577 static void rt_overlay_init_window(RendererTiles *rt, OverlayData *od)
578 {
579         PixbufRenderer *pr = rt->pr;
580         gint px, py, pw, ph;
581         GdkWindowAttr attributes;
582         gint attributes_mask;
583
584         rt_overlay_get_position(rt, od, &px, &py, &pw, &ph);
585
586         attributes.window_type = GDK_WINDOW_CHILD;
587         attributes.wclass = GDK_INPUT_OUTPUT;
588         attributes.width = pw;
589         attributes.height = ph;
590         attributes.event_mask = GDK_EXPOSURE_MASK;
591         attributes_mask = 0;
592
593         od->window = gdk_window_new(gtk_widget_get_window(GTK_WIDGET(pr)), &attributes, attributes_mask);
594         gdk_window_set_user_data(od->window, pr);
595         gdk_window_move(od->window, px + rt->stereo_off_x, py + rt->stereo_off_y);
596         gdk_window_show(od->window);
597 }
598
599 static void rt_overlay_draw(RendererTiles *rt, gint x, gint y, gint w, gint h,
600                             ImageTile *it)
601 {
602         PixbufRenderer *pr = rt->pr;
603         GList *work;
604
605         work = rt->overlay_list;
606         while (work)
607                 {
608                 OverlayData *od;
609                 gint px, py, pw, ph;
610                 gint rx, ry, rw, rh;
611
612                 od = work->data;
613                 work = work->next;
614
615                 if (!od->window) rt_overlay_init_window(rt, od);
616
617                 rt_overlay_get_position(rt, od, &px, &py, &pw, &ph);
618                 if (pr_clip_region(x, y, w, h, px, py, pw, ph, &rx, &ry, &rw, &rh))
619                         {
620                         if (!rt->overlay_buffer)
621                                 {
622                                 rt->overlay_buffer = gdk_window_create_similar_surface(gtk_widget_get_window((GtkWidget *)pr),
623                                                             CAIRO_CONTENT_COLOR,
624                                                             rt->tile_width, rt->tile_height);
625                                 }
626
627                         if (it)
628                                 {
629                                 cairo_t *cr;
630
631                                 cr = cairo_create(rt->overlay_buffer);
632                                 cairo_set_source_surface(cr, it->surface, (pr->x_offset + (it->x - rt->x_scroll)) - rx, (pr->y_offset + (it->y - rt->y_scroll)) - ry);
633                                 cairo_rectangle(cr, 0, 0, rw, rh);
634                                 cairo_fill_preserve(cr);
635
636                                 gdk_cairo_set_source_pixbuf(cr, od->pixbuf, px - rx, py - ry);
637                                 cairo_fill(cr);
638                                 cairo_destroy (cr);
639
640                                 cr = gdk_cairo_create(od->window);
641                                 cairo_set_source_surface(cr, rt->overlay_buffer, rx - px, ry - py);
642                                 cairo_rectangle (cr, rx - px, ry - py, rw, rh);
643                                 cairo_fill (cr);
644                                 cairo_destroy (cr);
645                                 }
646                         else
647                                 {
648                                 /* no ImageTile means region may be larger than our scratch buffer */
649                                 gint sx, sy;
650
651                                 for (sx = rx; sx < rx + rw; sx += rt->tile_width)
652                                     for (sy = ry; sy < ry + rh; sy += rt->tile_height)
653                                         {
654                                         gint sw, sh;
655                                         cairo_t *cr;
656
657                                         sw = MIN(rx + rw - sx, rt->tile_width);
658                                         sh = MIN(ry + rh - sy, rt->tile_height);
659
660                                         cr = cairo_create(rt->overlay_buffer);
661                                         cairo_set_source_rgb(cr, 0, 0, 0);
662                                         cairo_rectangle(cr, 0, 0, sw, sh);
663                                         cairo_fill_preserve(cr);
664
665                                         gdk_cairo_set_source_pixbuf(cr, od->pixbuf, px - sx, py - sy);
666                                         cairo_fill (cr);
667                                         cairo_destroy (cr);
668
669                                         cr = gdk_cairo_create(od->window);
670                                         cairo_set_source_surface(cr, rt->overlay_buffer, sx - px, sy - py);
671                                         cairo_rectangle (cr, sx - px, sy - py, sw, sh);
672                                         cairo_fill(cr);
673                                         cairo_destroy(cr);
674                                         }
675                                 }
676                         }
677                 }
678 }
679
680 static void rt_overlay_queue_draw(RendererTiles *rt, OverlayData *od, gint x1, gint y1, gint x2, gint y2)
681 {
682         PixbufRenderer *pr = rt->pr;
683         gint x, y, w, h;
684
685         rt_overlay_get_position(rt, od, &x, &y, &w, &h);
686
687         /* add borders */
688         x -= x1;
689         y -= y1;
690         w += x1 + x2;
691         h += y1 + y2;
692
693         rt_queue(rt, rt->x_scroll - pr->x_offset + x,
694                  rt->y_scroll - pr->y_offset + y,
695                  w, h,
696                  FALSE, TILE_RENDER_ALL, FALSE, FALSE);
697
698         rt_border_draw(rt, x, y, w, h);
699 }
700
701 static void rt_overlay_queue_all(RendererTiles *rt, gint x1, gint y1, gint x2, gint y2)
702 {
703         GList *work;
704
705         work = rt->overlay_list;
706         while (work)
707                 {
708                 OverlayData *od = work->data;
709                 work = work->next;
710
711                 rt_overlay_queue_draw(rt, od, x1, y1, x2, y2);
712                 }
713 }
714
715 static void rt_overlay_update_sizes(RendererTiles *rt)
716 {
717         GList *work;
718
719         work = rt->overlay_list;
720         while (work)
721                 {
722                 OverlayData *od = work->data;
723                 work = work->next;
724
725                 if (!od->window) rt_overlay_init_window(rt, od);
726
727                 if (od->flags & OVL_RELATIVE)
728                         {
729                         gint x, y, w, h;
730
731                         rt_overlay_get_position(rt, od, &x, &y, &w, &h);
732                         gdk_window_move_resize(od->window, x + rt->stereo_off_x, y + rt->stereo_off_y, w, h);
733                         }
734                 }
735 }
736
737 static OverlayData *rt_overlay_find(RendererTiles *rt, gint id)
738 {
739         GList *work;
740
741         work = rt->overlay_list;
742         while (work)
743                 {
744                 OverlayData *od = work->data;
745                 work = work->next;
746
747                 if (od->id == id) return od;
748                 }
749
750         return NULL;
751 }
752
753
754 gint renderer_tiles_overlay_add(void *renderer, GdkPixbuf *pixbuf, gint x, gint y,
755                                  OverlayRendererFlags flags)
756 {
757         RendererTiles *rt = (RendererTiles *) renderer;
758         PixbufRenderer *pr = rt->pr;
759         OverlayData *od;
760         gint id;
761
762         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), -1);
763         g_return_val_if_fail(pixbuf != NULL, -1);
764
765         id = 1;
766         while (rt_overlay_find(rt, id)) id++;
767
768         od = g_new0(OverlayData, 1);
769         od->id = id;
770         od->pixbuf = pixbuf;
771         g_object_ref(G_OBJECT(od->pixbuf));
772         od->x = x;
773         od->y = y;
774         od->flags = flags;
775
776         rt_overlay_init_window(rt, od);
777
778         rt->overlay_list = g_list_append(rt->overlay_list, od);
779
780         gtk_widget_queue_draw(GTK_WIDGET(rt->pr));
781
782         return od->id;
783 }
784
785 static void rt_overlay_free(RendererTiles *rt, OverlayData *od)
786 {
787         rt->overlay_list = g_list_remove(rt->overlay_list, od);
788
789         if (od->pixbuf) g_object_unref(G_OBJECT(od->pixbuf));
790         if (od->window) gdk_window_destroy(od->window);
791         g_free(od);
792
793         if (!rt->overlay_list && rt->overlay_buffer)
794                 {
795                 cairo_surface_destroy(rt->overlay_buffer);
796                 rt->overlay_buffer = NULL;
797                 }
798 }
799
800 static void rt_overlay_list_clear(RendererTiles *rt)
801 {
802         while (rt->overlay_list)
803                 {
804                 OverlayData *od;
805
806                 od = rt->overlay_list->data;
807                 rt_overlay_free(rt, od);
808                 }
809 }
810
811 static void rt_overlay_list_reset_window(RendererTiles *rt)
812 {
813         GList *work;
814
815         if (rt->overlay_buffer) cairo_surface_destroy(rt->overlay_buffer);
816         rt->overlay_buffer = NULL;
817
818         work = rt->overlay_list;
819         while (work)
820                 {
821                 OverlayData *od = work->data;
822                 work = work->next;
823                 if (od->window) gdk_window_destroy(od->window);
824                 od->window = NULL;
825                 }
826 }
827
828 void renderer_tiles_overlay_set(void *renderer, gint id, GdkPixbuf *pixbuf, gint UNUSED(x), gint UNUSED(y))
829 {
830         RendererTiles *rc = (RendererTiles *)renderer;
831         PixbufRenderer *pr = rc->pr;
832         OverlayData *od;
833
834         g_return_if_fail(IS_PIXBUF_RENDERER(pr));
835
836         od = rt_overlay_find(rc, id);
837         if (!od) return;
838
839         if (pixbuf)
840                 {
841                 g_object_ref(G_OBJECT(pixbuf));
842                 g_object_unref(G_OBJECT(od->pixbuf));
843                 od->pixbuf = pixbuf;
844                 }
845         else
846                 {
847                 rt_overlay_free(rc, od);
848                 }
849
850         gtk_widget_queue_draw(GTK_WIDGET(rc->pr));
851 }
852
853 gboolean renderer_tiles_overlay_get(void *renderer, gint id, GdkPixbuf **pixbuf, gint *x, gint *y)
854 {
855         RendererTiles *rt = (RendererTiles *) renderer;
856         PixbufRenderer *pr = rt->pr;
857         OverlayData *od;
858
859         g_return_val_if_fail(IS_PIXBUF_RENDERER(pr), FALSE);
860
861         od = rt_overlay_find(rt, id);
862         if (!od) return FALSE;
863
864         if (pixbuf) *pixbuf = od->pixbuf;
865         if (x) *x = od->x;
866         if (y) *y = od->y;
867
868         return TRUE;
869 }
870
871 static void rt_hierarchy_changed_cb(GtkWidget *UNUSED(widget), GtkWidget *UNUSED(previous_toplevel), gpointer data)
872 {
873         RendererTiles *rt = data;
874         rt_overlay_list_reset_window(rt);
875 }
876
877 /*
878  *-------------------------------------------------------------------
879  * drawing
880  *-------------------------------------------------------------------
881  */
882
883 static GdkPixbuf *rt_get_spare_tile(RendererTiles *rt)
884 {
885         if (!rt->spare_tile) rt->spare_tile = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, rt->tile_width * rt->hidpi_scale, rt->tile_height * rt->hidpi_scale);
886         return rt->spare_tile;
887 }
888
889 #define COLOR_BYTES 3   /* rgb */
890
891 static void rt_tile_rotate_90_clockwise(RendererTiles *rt, GdkPixbuf **tile, gint x, gint y, gint w, gint h)
892 {
893         GdkPixbuf *src = *tile;
894         GdkPixbuf *dest;
895         gint srs, drs;
896         guchar *s_pix, *d_pix;
897         guchar *sp, *dp;
898         guchar *ip, *spi, *dpi;
899         gint i, j;
900         gint tw = rt->tile_width * rt->hidpi_scale;
901
902         srs = gdk_pixbuf_get_rowstride(src);
903         s_pix = gdk_pixbuf_get_pixels(src);
904         spi = s_pix + (x * COLOR_BYTES);
905
906         dest = rt_get_spare_tile(rt);
907         drs = gdk_pixbuf_get_rowstride(dest);
908         d_pix = gdk_pixbuf_get_pixels(dest);
909         dpi = d_pix + (tw - 1) * COLOR_BYTES;
910
911         for (i = y; i < y + h; i++)
912                 {
913                 sp = spi + (i * srs);
914                 ip = dpi - (i * COLOR_BYTES);
915                 for (j = x; j < x + w; j++)
916                         {
917                         dp = ip + (j * drs);
918                         memcpy(dp, sp, COLOR_BYTES);
919                         sp += COLOR_BYTES;
920                         }
921                 }
922
923         rt->spare_tile = src;
924         *tile = dest;
925 }
926
927 static void rt_tile_rotate_90_counter_clockwise(RendererTiles *rt, GdkPixbuf **tile, gint x, gint y, gint w, gint h)
928 {
929         GdkPixbuf *src = *tile;
930         GdkPixbuf *dest;
931         gint srs, drs;
932         guchar *s_pix, *d_pix;
933         guchar *sp, *dp;
934         guchar *ip, *spi, *dpi;
935         gint i, j;
936         gint th = rt->tile_height * rt->hidpi_scale;
937
938         srs = gdk_pixbuf_get_rowstride(src);
939         s_pix = gdk_pixbuf_get_pixels(src);
940         spi = s_pix + (x * COLOR_BYTES);
941
942         dest = rt_get_spare_tile(rt);
943         drs = gdk_pixbuf_get_rowstride(dest);
944         d_pix = gdk_pixbuf_get_pixels(dest);
945         dpi = d_pix + (th - 1) * drs;
946
947         for (i = y; i < y + h; i++)
948                 {
949                 sp = spi + (i * srs);
950                 ip = dpi + (i * COLOR_BYTES);
951                 for (j = x; j < x + w; j++)
952                         {
953                         dp = ip - (j * drs);
954                         memcpy(dp, sp, COLOR_BYTES);
955                         sp += COLOR_BYTES;
956                         }
957                 }
958
959         rt->spare_tile = src;
960         *tile = dest;
961 }
962
963 static void rt_tile_mirror_only(RendererTiles *rt, GdkPixbuf **tile, gint x, gint y, gint w, gint h)
964 {
965         GdkPixbuf *src = *tile;
966         GdkPixbuf *dest;
967         gint srs, drs;
968         guchar *s_pix, *d_pix;
969         guchar *sp, *dp;
970         guchar *spi, *dpi;
971         gint i, j;
972
973         gint tw = rt->tile_width * rt->hidpi_scale;
974
975         srs = gdk_pixbuf_get_rowstride(src);
976         s_pix = gdk_pixbuf_get_pixels(src);
977         spi = s_pix + (x * COLOR_BYTES);
978
979         dest = rt_get_spare_tile(rt);
980         drs = gdk_pixbuf_get_rowstride(dest);
981         d_pix = gdk_pixbuf_get_pixels(dest);
982         dpi =  d_pix + (tw - x - 1) * COLOR_BYTES;
983
984         for (i = y; i < y + h; i++)
985                 {
986                 sp = spi + (i * srs);
987                 dp = dpi + (i * drs);
988                 for (j = 0; j < w; j++)
989                         {
990                         memcpy(dp, sp, COLOR_BYTES);
991                         sp += COLOR_BYTES;
992                         dp -= COLOR_BYTES;
993                         }
994                 }
995
996         rt->spare_tile = src;
997         *tile = dest;
998 }
999
1000 static void rt_tile_mirror_and_flip(RendererTiles *rt, GdkPixbuf **tile, gint x, gint y, gint w, gint h)
1001 {
1002         GdkPixbuf *src = *tile;
1003         GdkPixbuf *dest;
1004         gint srs, drs;
1005         guchar *s_pix, *d_pix;
1006         guchar *sp, *dp;
1007         guchar *dpi;
1008         gint i, j;
1009         gint tw = rt->tile_width * rt->hidpi_scale;
1010         gint th = rt->tile_height * rt->hidpi_scale;
1011
1012         srs = gdk_pixbuf_get_rowstride(src);
1013         s_pix = gdk_pixbuf_get_pixels(src);
1014
1015         dest = rt_get_spare_tile(rt);
1016         drs = gdk_pixbuf_get_rowstride(dest);
1017         d_pix = gdk_pixbuf_get_pixels(dest);
1018         dpi = d_pix + (th - 1) * drs + (tw - 1) * COLOR_BYTES;
1019
1020         for (i = y; i < y + h; i++)
1021                 {
1022                 sp = s_pix + (i * srs) + (x * COLOR_BYTES);
1023                 dp = dpi - (i * drs) - (x * COLOR_BYTES);
1024                 for (j = 0; j < w; j++)
1025                         {
1026                         memcpy(dp, sp, COLOR_BYTES);
1027                         sp += COLOR_BYTES;
1028                         dp -= COLOR_BYTES;
1029                         }
1030                 }
1031
1032         rt->spare_tile = src;
1033         *tile = dest;
1034 }
1035
1036 static void rt_tile_flip_only(RendererTiles *rt, GdkPixbuf **tile, gint x, gint y, gint w, gint h)
1037 {
1038         GdkPixbuf *src = *tile;
1039         GdkPixbuf *dest;
1040         gint srs, drs;
1041         guchar *s_pix, *d_pix;
1042         guchar *sp, *dp;
1043         guchar *spi, *dpi;
1044         gint i;
1045         gint th = rt->tile_height * rt->hidpi_scale;
1046
1047         srs = gdk_pixbuf_get_rowstride(src);
1048         s_pix = gdk_pixbuf_get_pixels(src);
1049         spi = s_pix + (x * COLOR_BYTES);
1050
1051         dest = rt_get_spare_tile(rt);
1052         drs = gdk_pixbuf_get_rowstride(dest);
1053         d_pix = gdk_pixbuf_get_pixels(dest);
1054         dpi = d_pix + (th - 1) * drs + (x * COLOR_BYTES);
1055
1056         for (i = y; i < y + h; i++)
1057                 {
1058                 sp = spi + (i * srs);
1059                 dp = dpi - (i * drs);
1060                 memcpy(dp, sp, w * COLOR_BYTES);
1061                 }
1062
1063         rt->spare_tile = src;
1064         *tile = dest;
1065 }
1066
1067 static void rt_tile_apply_orientation(RendererTiles *rt, gint orientation, GdkPixbuf **pixbuf, gint x, gint y, gint w, gint h)
1068 {
1069         switch (orientation)
1070                 {
1071                 case EXIF_ORIENTATION_TOP_LEFT:
1072                         /* normal -- nothing to do */
1073                         break;
1074                 case EXIF_ORIENTATION_TOP_RIGHT:
1075                         /* mirrored */
1076                         {
1077                                 rt_tile_mirror_only(rt, pixbuf, x, y, w, h);
1078                         }
1079                         break;
1080                 case EXIF_ORIENTATION_BOTTOM_RIGHT:
1081                         /* upside down */
1082                         {
1083                                 rt_tile_mirror_and_flip(rt, pixbuf, x, y, w, h);
1084                         }
1085                         break;
1086                 case EXIF_ORIENTATION_BOTTOM_LEFT:
1087                         /* flipped */
1088                         {
1089                                 rt_tile_flip_only(rt, pixbuf, x, y, w, h);
1090                         }
1091                         break;
1092                 case EXIF_ORIENTATION_LEFT_TOP:
1093                         {
1094                                 rt_tile_flip_only(rt, pixbuf, x, y, w, h);
1095                                 rt_tile_rotate_90_clockwise(rt, pixbuf, x, rt->tile_height - y - h, w, h);
1096                         }
1097                         break;
1098                 case EXIF_ORIENTATION_RIGHT_TOP:
1099                         /* rotated -90 (270) */
1100                         {
1101                                 rt_tile_rotate_90_clockwise(rt, pixbuf, x, y, w, h);
1102                         }
1103                         break;
1104                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
1105                         {
1106                                 rt_tile_flip_only(rt, pixbuf, x, y, w, h);
1107                                 rt_tile_rotate_90_counter_clockwise(rt, pixbuf, x, rt->tile_height - y - h, w, h);
1108                         }
1109                         break;
1110                 case EXIF_ORIENTATION_LEFT_BOTTOM:
1111                         /* rotated 90 */
1112                         {
1113                                 rt_tile_rotate_90_counter_clockwise(rt, pixbuf, x, y, w, h);
1114                         }
1115                         break;
1116                 default:
1117                         /* The other values are out of range */
1118                         break;
1119                 }
1120 }
1121
1122 static gboolean rt_source_tile_render(RendererTiles *rt, ImageTile *it,
1123                                       gint x, gint y, gint w, gint h,
1124                                       gboolean UNUSED(new_data), gboolean fast)
1125 {
1126         PixbufRenderer *pr = rt->pr;
1127         GList *list;
1128         GList *work;
1129         gboolean draw = FALSE;
1130
1131         if (pr->zoom == 1.0 || pr->scale == 1.0)
1132                 {
1133                 list = pr_source_tile_compute_region(pr, it->x + x, it->y + y, w, h, TRUE);
1134                 work = list;
1135                 while (work)
1136                         {
1137                         SourceTile *st;
1138                         gint rx, ry, rw, rh;
1139
1140                         st = work->data;
1141                         work = work->next;
1142
1143                         if (pr_clip_region(st->x, st->y, pr->source_tile_width, pr->source_tile_height,
1144                                            it->x + x, it->y + y, w, h,
1145                                            &rx, &ry, &rw, &rh))
1146                                 {
1147                                 cairo_t *cr;
1148                                 cr = cairo_create(it->surface);
1149                                 cairo_rectangle (cr, rx - it->x, ry - it->y, rw, rh);
1150
1151                                 if (st->blank)
1152                                         {
1153                                         cairo_set_source_rgb(cr, 0, 0, 0);
1154                                         cairo_fill (cr);
1155                                         }
1156                                 else /* (pr->zoom == 1.0 || pr->scale == 1.0) */
1157                                         {
1158                                         rt_hidpi_aware_draw(rt, cr, st->pixbuf, -it->x + st->x, -it->y + st->y);
1159                                         }
1160                                 cairo_destroy (cr);
1161                                 }
1162                         }
1163                 }
1164         else
1165                 {
1166                 gdouble scale_x, scale_y;
1167                 gint sx, sy, sw, sh;
1168
1169                 if (pr->image_width == 0 || pr->image_height == 0) return FALSE;
1170                 scale_x = (gdouble)pr->width / pr->image_width;
1171                 scale_y = (gdouble)pr->height / pr->image_height;
1172
1173                 sx = (gdouble)(it->x + x) / scale_x;
1174                 sy = (gdouble)(it->y + y) / scale_y;
1175                 sw = (gdouble)w / scale_x;
1176                 sh = (gdouble)h / scale_y;
1177
1178                 if (pr->width < PR_MIN_SCALE_SIZE || pr->height < PR_MIN_SCALE_SIZE) fast = TRUE;
1179
1180 #if 0
1181                 /* draws red over draw region, to check for leaks (regions not filled) */
1182                 pixbuf_set_rect_fill(it->pixbuf, x, y, w, h, 255, 0, 0, 255);
1183 #endif
1184
1185                 list = pr_source_tile_compute_region(pr, sx, sy, sw, sh, TRUE);
1186                 work = list;
1187                 while (work)
1188                         {
1189                         SourceTile *st;
1190                         gint rx, ry, rw, rh;
1191                         gint stx, sty, stw, sth;
1192
1193                         st = work->data;
1194                         work = work->next;
1195
1196                         stx = floor((gdouble)st->x * scale_x);
1197                         sty = floor((gdouble)st->y * scale_y);
1198                         stw = ceil((gdouble)(st->x + pr->source_tile_width) * scale_x) - stx;
1199                         sth = ceil((gdouble)(st->y + pr->source_tile_height) * scale_y) - sty;
1200
1201                         if (pr_clip_region(stx, sty, stw, sth,
1202                                            it->x + x, it->y + y, w, h,
1203                                            &rx, &ry, &rw, &rh))
1204                                 {
1205
1206                                 if (st->blank)
1207                                         {
1208                                         cairo_t *cr;
1209                                         cr = cairo_create(it->surface);
1210                                         cairo_rectangle (cr, rx - st->x, ry - st->y, rw, rh);
1211                                         cairo_set_source_rgb(cr, 0, 0, 0);
1212                                         cairo_fill (cr);
1213                                         cairo_destroy (cr);
1214                                         }
1215                                 else
1216                                         {
1217                                         gdouble offset_x;
1218                                         gdouble offset_y;
1219
1220                                         /* may need to use unfloored stx,sty values here */
1221                                         offset_x = (gdouble)(stx - it->x);
1222                                         offset_y = (gdouble)(sty - it->y);
1223
1224                                         gdk_pixbuf_scale(st->pixbuf, it->pixbuf, rx - it->x, ry - it->y, rw, rh,
1225                                                  (gdouble) 0.0 + offset_x,
1226                                                  (gdouble) 0.0 + offset_y,
1227                                                  scale_x, scale_y,
1228                                                  (fast) ? GDK_INTERP_NEAREST : pr->zoom_quality);
1229                                         draw = TRUE;
1230                                         }
1231                                 }
1232                         }
1233                 }
1234
1235         g_list_free(list);
1236
1237         return draw;
1238 }
1239
1240 /**
1241  * @brief 
1242  * @param has_alpha 
1243  * @param ignore_alpha 
1244  * @param src 
1245  * @param dest 
1246  * @param pb_x 
1247  * @param pb_y 
1248  * @param pb_w 
1249  * @param pb_h 
1250  * @param offset_x 
1251  * @param offset_y 
1252  * @param scale_x 
1253  * @param scale_y 
1254  * @param interp_type 
1255  * @param check_x 
1256  * @param check_y 
1257  * @param wide_image Used as a work-around for a GdkPixbuf problem. Set when image width is > 32767. Problem exhibited with gdk_pixbuf_copy_area() and GDK_INTERP_NEAREST. See #772 on GitHub Geeqie
1258  * 
1259  * 
1260  */
1261 static void rt_tile_get_region(gboolean has_alpha, gboolean ignore_alpha,
1262                                const GdkPixbuf *src, GdkPixbuf *dest,
1263                                int pb_x, int pb_y, int pb_w, int pb_h,
1264                                double offset_x, double offset_y, double scale_x, double scale_y,
1265                                GdkInterpType interp_type,
1266                                int check_x, int check_y, gboolean wide_image)
1267 {
1268         GdkPixbuf* tmppixbuf;
1269         gint srs;
1270         gint drs;
1271         gint x;
1272         gint y;
1273         gint c;
1274         guchar *psrc;
1275         guchar *pdst;
1276
1277         if (!has_alpha)
1278                 {
1279                 if (scale_x == 1.0 && scale_y == 1.0)
1280                         {
1281                         if (wide_image)
1282                                 {
1283                                 srs = gdk_pixbuf_get_rowstride(src);
1284                                 drs = gdk_pixbuf_get_rowstride(dest);
1285                                 psrc = gdk_pixbuf_get_pixels(src);
1286                                 pdst = gdk_pixbuf_get_pixels(dest);
1287                                 for (y = 0; y < pb_h; y++)
1288                                         {
1289                                         for (x = 0; x < pb_w; x++)
1290                                                 {
1291                                                 for (c = 0; c < 3; c++)
1292                                                         {
1293                                                         pdst[(y * drs) + x*3 + c] = psrc[(-(int)offset_y + pb_y + y) * srs + (-(int)offset_x + pb_x+x)*3 + c];
1294                                                         }
1295                                                 }
1296                                         }
1297                                 }
1298                         else
1299                                 {
1300                                 gdk_pixbuf_copy_area(src,
1301                                                                         -offset_x + pb_x, -offset_y + pb_y,
1302                                                                         pb_w, pb_h,
1303                                                                         dest,
1304                                                                         pb_x, pb_y);
1305                                         }
1306                         }
1307                 else
1308                         {
1309                         gdk_pixbuf_scale(src, dest,
1310                                                         pb_x, pb_y, pb_w, pb_h,
1311                                                         offset_x,
1312                                                         offset_y,
1313                                                         scale_x, scale_y,
1314                                                         (wide_image && interp_type == GDK_INTERP_NEAREST) ? GDK_INTERP_TILES : interp_type);
1315                         }
1316                 }
1317         else
1318                 {
1319                 if (ignore_alpha)
1320                         {
1321                         tmppixbuf = gdk_pixbuf_add_alpha(src, FALSE, 0, 0, 0);
1322
1323                         pixbuf_ignore_alpha_rect(tmppixbuf, 0, 0, gdk_pixbuf_get_width(src), gdk_pixbuf_get_height(src));
1324
1325                         gdk_pixbuf_composite_color(tmppixbuf, dest,
1326                                         pb_x, pb_y, pb_w, pb_h,
1327                                         offset_x,
1328                                         offset_y,
1329                                         scale_x, scale_y,
1330                                         (scale_x == 1.0 && scale_y == 1.0) ? GDK_INTERP_NEAREST : interp_type,
1331                                         255, check_x, check_y,
1332                                         PR_ALPHA_CHECK_SIZE,
1333                                         ((options->image.alpha_color_1.red << 8 & 0x00FF0000) +
1334                                         (options->image.alpha_color_1.green & 0x00FF00) +
1335                                         (options->image.alpha_color_1.blue >> 8 & 0x00FF)),
1336                                         ((options->image.alpha_color_2.red << 8 & 0x00FF0000) +
1337                                         (options->image.alpha_color_2.green & 0x00FF00) +
1338                                         (options->image.alpha_color_2.blue >> 8 & 0x00FF)));
1339                         g_object_unref(tmppixbuf);
1340                         }
1341                 else
1342                         {
1343                         gdk_pixbuf_composite_color(src, dest,
1344                                         pb_x, pb_y, pb_w, pb_h,
1345                                         offset_x,
1346                                         offset_y,
1347                                         scale_x, scale_y,
1348                                         (scale_x == 1.0 && scale_y == 1.0) ? GDK_INTERP_NEAREST : interp_type,
1349                                         255, check_x, check_y,
1350                                         PR_ALPHA_CHECK_SIZE,
1351                                         ((options->image.alpha_color_1.red << 8 & 0x00FF0000) +
1352                                         (options->image.alpha_color_1.green & 0x00FF00) +
1353                                         (options->image.alpha_color_1.blue >> 8 & 0x00FF)),
1354                                         ((options->image.alpha_color_2.red << 8 & 0x00FF0000) +
1355                                         (options->image.alpha_color_2.green & 0x00FF00) +
1356                                         (options->image.alpha_color_2.blue >> 8 & 0x00FF)));
1357                         }
1358                 }
1359 }
1360
1361
1362 static gint rt_get_orientation(RendererTiles *rt)
1363 {
1364         PixbufRenderer *pr = rt->pr;
1365
1366         gint orientation = pr->orientation;
1367         static const gint mirror[]       = {1,   2, 1, 4, 3, 6, 5, 8, 7};
1368         static const gint flip[]         = {1,   4, 3, 2, 1, 8, 7, 6, 5};
1369
1370         if (rt->stereo_mode & PR_STEREO_MIRROR) orientation = mirror[orientation];
1371         if (rt->stereo_mode & PR_STEREO_FLIP) orientation = flip[orientation];
1372         return orientation;
1373 }
1374
1375
1376 static void rt_tile_render(RendererTiles *rt, ImageTile *it,
1377                            gint x, gint y, gint w, gint h,
1378                            gboolean new_data, gboolean fast)
1379 {
1380         PixbufRenderer *pr = rt->pr;
1381         gboolean has_alpha;
1382         gboolean draw = FALSE;
1383         gint orientation = rt_get_orientation(rt);
1384         gboolean wide_image = FALSE;
1385
1386         if (it->render_todo == TILE_RENDER_NONE && it->surface && !new_data) return;
1387
1388         if (it->render_done != TILE_RENDER_ALL)
1389                 {
1390                 x = 0;
1391                 y = 0;
1392                 w = it->w;
1393                 h = it->h;
1394                 if (!fast) it->render_done = TILE_RENDER_ALL;
1395                 }
1396         else if (it->render_todo != TILE_RENDER_AREA)
1397                 {
1398                 if (!fast) it->render_todo = TILE_RENDER_NONE;
1399                 return;
1400                 }
1401
1402         if (!fast) it->render_todo = TILE_RENDER_NONE;
1403
1404         if (new_data) it->blank = FALSE;
1405
1406         rt_tile_prepare(rt, it);
1407         has_alpha = (pr->pixbuf && gdk_pixbuf_get_has_alpha(pr->pixbuf));
1408
1409         /** @FIXME checker colors for alpha should be configurable,
1410          * also should be drawn for blank = TRUE
1411          */
1412
1413         if (it->blank)
1414                 {
1415                 /* no data, do fast rect fill */
1416                 cairo_t *cr;
1417                 cr = cairo_create(it->surface);
1418                 cairo_rectangle (cr, 0, 0, it->w, it->h);
1419                 cairo_set_source_rgb(cr, 0, 0, 0);
1420                 cairo_fill (cr);
1421                 cairo_destroy (cr);
1422                 }
1423         else if (pr->source_tiles_enabled)
1424                 {
1425                 draw = rt_source_tile_render(rt, it, x, y, w, h, new_data, fast);
1426                 }
1427         else
1428                 {
1429                 gdouble scale_x, scale_y;
1430                 gdouble src_x, src_y;
1431                 gint pb_x, pb_y;
1432                 gint pb_w, pb_h;
1433
1434                 if (pr->image_width == 0 || pr->image_height == 0) return;
1435
1436                 scale_x = rt->hidpi_scale * (gdouble)pr->width / pr->image_width;
1437                 scale_y = rt->hidpi_scale * (gdouble)pr->height / pr->image_height;
1438
1439                 pr_tile_coords_map_orientation(orientation, it->x, it->y,
1440                                             pr->width, pr->height,
1441                                             rt->tile_width, rt->tile_height,
1442                                             &src_x, &src_y);
1443                 pr_tile_region_map_orientation(orientation, x, y,
1444                                             rt->tile_width, rt->tile_height,
1445                                             w, h,
1446                                             &pb_x, &pb_y,
1447                                             &pb_w, &pb_h);
1448
1449                 src_x *= rt->hidpi_scale;
1450                 src_y *= rt->hidpi_scale;
1451                 pb_x *= rt->hidpi_scale;
1452                 pb_y *= rt->hidpi_scale;
1453                 pb_w *= rt->hidpi_scale;
1454                 pb_h *= rt->hidpi_scale;
1455
1456                 switch (orientation)
1457                         {
1458                         gdouble tmp;
1459                         case EXIF_ORIENTATION_LEFT_TOP:
1460                         case EXIF_ORIENTATION_RIGHT_TOP:
1461                         case EXIF_ORIENTATION_RIGHT_BOTTOM:
1462                         case EXIF_ORIENTATION_LEFT_BOTTOM:
1463                                 tmp = scale_x;
1464                                 scale_x = scale_y;
1465                                 scale_y = tmp;
1466                                 break;
1467                         default:
1468                                 /* nothing to do */
1469                                 break;
1470                         }
1471
1472                 /* HACK: The pixbuf scalers get kinda buggy(crash) with extremely
1473                  * small sizes for anything but GDK_INTERP_NEAREST
1474                  */
1475                 if (pr->width < PR_MIN_SCALE_SIZE || pr->height < PR_MIN_SCALE_SIZE) fast = TRUE;
1476                 if (pr->image_width > 32767) wide_image = TRUE;
1477
1478                 rt_tile_get_region(has_alpha, pr->ignore_alpha,
1479                                    pr->pixbuf, it->pixbuf, pb_x, pb_y, pb_w, pb_h,
1480                                    (gdouble) 0.0 - src_x - GET_RIGHT_PIXBUF_OFFSET(rt) * scale_x,
1481                                    (gdouble) 0.0 - src_y,
1482                                    scale_x, scale_y,
1483                                    (fast) ? GDK_INTERP_NEAREST : pr->zoom_quality,
1484                                    it->x + pb_x, it->y + pb_y, wide_image);
1485                 if (rt->stereo_mode & PR_STEREO_ANAGLYPH &&
1486                     (pr->stereo_pixbuf_offset_right > 0 || pr->stereo_pixbuf_offset_left > 0))
1487                         {
1488                         GdkPixbuf *right_pb = rt_get_spare_tile(rt);
1489                         rt_tile_get_region(has_alpha, pr->ignore_alpha,
1490                                            pr->pixbuf, right_pb, pb_x, pb_y, pb_w, pb_h,
1491                                            (gdouble) 0.0 - src_x - GET_LEFT_PIXBUF_OFFSET(rt) * scale_x,
1492                                            (gdouble) 0.0 - src_y,
1493                                            scale_x, scale_y,
1494                                            (fast) ? GDK_INTERP_NEAREST : pr->zoom_quality,
1495                                            it->x + pb_x, it->y + pb_y, wide_image);
1496                         pr_create_anaglyph(rt->stereo_mode, it->pixbuf, right_pb, pb_x, pb_y, pb_w, pb_h);
1497                         /* do not care about freeing spare_tile, it will be reused */
1498                         }
1499                 rt_tile_apply_orientation(rt, orientation, &it->pixbuf, pb_x, pb_y, pb_w, pb_h);
1500                 draw = TRUE;
1501                 }
1502
1503         if (draw && it->pixbuf && !it->blank)
1504                 {
1505                 cairo_t *cr;
1506
1507                 if (pr->func_post_process && !(pr->post_process_slow && fast))
1508                         pr->func_post_process(pr, &it->pixbuf, x, y, w, h, pr->post_process_user_data);
1509
1510                 cr = cairo_create(it->surface);
1511                 cairo_rectangle (cr, x, y, w, h);
1512                 rt_hidpi_aware_draw(rt, cr, it->pixbuf, 0, 0);
1513                 cairo_destroy (cr);
1514                 }
1515 }
1516
1517
1518 static void rt_tile_expose(RendererTiles *rt, ImageTile *it,
1519                            gint x, gint y, gint w, gint h,
1520                            gboolean new_data, gboolean fast)
1521 {
1522         PixbufRenderer *pr = rt->pr;
1523         //~ GtkWidget *box;
1524         //~ GdkWindow *window;
1525         cairo_t *cr;
1526
1527         /* clamp to visible */
1528         if (it->x + x < rt->x_scroll)
1529                 {
1530                 w -= rt->x_scroll - it->x - x;
1531                 x = rt->x_scroll - it->x;
1532                 }
1533         if (it->x + x + w > rt->x_scroll + pr->vis_width)
1534                 {
1535                 w = rt->x_scroll + pr->vis_width - it->x - x;
1536                 }
1537         if (w < 1) return;
1538         if (it->y + y < rt->y_scroll)
1539                 {
1540                 h -= rt->y_scroll - it->y - y;
1541                 y = rt->y_scroll - it->y;
1542                 }
1543         if (it->y + y + h > rt->y_scroll + pr->vis_height)
1544                 {
1545                 h = rt->y_scroll + pr->vis_height - it->y - y;
1546                 }
1547         if (h < 1) return;
1548
1549         rt_tile_render(rt, it, x, y, w, h, new_data, fast);
1550
1551         //~ box = GTK_WIDGET(pr);
1552         //~ window = gtk_widget_get_window(box);
1553
1554         cr = cairo_create(rt->surface);
1555         cairo_set_source_surface(cr, it->surface, pr->x_offset + (it->x - rt->x_scroll) + rt->stereo_off_x, pr->y_offset + (it->y - rt->y_scroll) + rt->stereo_off_y);
1556         cairo_rectangle (cr, pr->x_offset + (it->x - rt->x_scroll) + x + rt->stereo_off_x, pr->y_offset + (it->y - rt->y_scroll) + y + rt->stereo_off_y, w, h);
1557         cairo_fill (cr);
1558         cairo_destroy (cr);
1559
1560         if (rt->overlay_list)
1561                 {
1562                 rt_overlay_draw(rt, pr->x_offset + (it->x - rt->x_scroll) + x,
1563                                 pr->y_offset + (it->y - rt->y_scroll) + y,
1564                                 w, h,
1565                                 it);
1566                 }
1567
1568         gtk_widget_queue_draw(GTK_WIDGET(rt->pr));
1569 }
1570
1571
1572 static gboolean rt_tile_is_visible(RendererTiles *rt, ImageTile *it)
1573 {
1574         PixbufRenderer *pr = rt->pr;
1575         return (it->x + it->w >= rt->x_scroll && it->x < rt->x_scroll + pr->vis_width &&
1576                 it->y + it->h >= rt->y_scroll && it->y < rt->y_scroll + pr->vis_height);
1577 }
1578
1579 /*
1580  *-------------------------------------------------------------------
1581  * draw queue
1582  *-------------------------------------------------------------------
1583  */
1584
1585 static gint rt_get_queued_area(GList *work)
1586 {
1587         gint area = 0;
1588
1589         while (work)
1590                 {
1591                 QueueData *qd = work->data;
1592                 area += qd->w * qd->h;
1593                 work = work->next;
1594                 }
1595         return area;
1596 }
1597
1598
1599 static gboolean rt_queue_schedule_next_draw(RendererTiles *rt, gboolean force_set)
1600 {
1601         PixbufRenderer *pr = rt->pr;
1602         gfloat percent;
1603         gint visible_area = pr->vis_width * pr->vis_height;
1604
1605         if (!pr->loading)
1606                 {
1607                 /* 2pass prio */
1608                 DEBUG_2("redraw priority: 2pass");
1609                 rt->draw_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, rt_queue_draw_idle_cb, rt, NULL);
1610                 return FALSE;
1611                 }
1612
1613         if (visible_area == 0)
1614                 {
1615                 /* not known yet */
1616                 percent = 100.0;
1617                 }
1618         else
1619                 {
1620                 percent = 100.0 * rt_get_queued_area(rt->draw_queue) / visible_area;
1621                 }
1622
1623         if (percent > 10.0)
1624                 {
1625                 /* we have enough data for starting intensive redrawing */
1626                 DEBUG_2("redraw priority: high %.2f %%", percent);
1627                 rt->draw_idle_id = g_idle_add_full(GDK_PRIORITY_REDRAW, rt_queue_draw_idle_cb, rt, NULL);
1628                 return FALSE;
1629                 }
1630
1631         if (percent < 1.0 || force_set)
1632                 {
1633                 /* queue is (almost) empty, wait  50 ms*/
1634                 DEBUG_2("redraw priority: wait %.2f %%", percent);
1635                 rt->draw_idle_id = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 50, rt_queue_draw_idle_cb, rt, NULL);
1636                 return FALSE;
1637                 }
1638
1639         /* keep the same priority as before */
1640         DEBUG_2("redraw priority: no change %.2f %%", percent);
1641         return TRUE;
1642 }
1643
1644
1645 static gboolean rt_queue_draw_idle_cb(gpointer data)
1646 {
1647         RendererTiles *rt = data;
1648         PixbufRenderer *pr = rt->pr;
1649         QueueData *qd;
1650         gboolean fast;
1651
1652
1653         if ((!pr->pixbuf && !pr->source_tiles_enabled) ||
1654             (!rt->draw_queue && !rt->draw_queue_2pass) ||
1655             !rt->draw_idle_id)
1656                 {
1657                 pr_render_complete_signal(pr);
1658
1659                 rt->draw_idle_id = 0;
1660                 return FALSE;
1661                 }
1662
1663         if (rt->draw_queue)
1664                 {
1665                 qd = rt->draw_queue->data;
1666                 fast = (pr->zoom_2pass && ((pr->zoom_quality != GDK_INTERP_NEAREST && pr->scale != 1.0) || pr->post_process_slow));
1667                 }
1668         else
1669                 {
1670                 if (pr->loading)
1671                         {
1672                         /* still loading, wait till done (also drops the higher priority) */
1673
1674                         return rt_queue_schedule_next_draw(rt, FALSE);
1675                         }
1676
1677                 qd = rt->draw_queue_2pass->data;
1678                 fast = FALSE;
1679                 }
1680
1681         if (gtk_widget_get_realized(GTK_WIDGET(pr)))
1682                 {
1683                 if (rt_tile_is_visible(rt, qd->it))
1684                         {
1685                         rt_tile_expose(rt, qd->it, qd->x, qd->y, qd->w, qd->h, qd->new_data, fast);
1686                         }
1687                 else if (qd->new_data)
1688                         {
1689                         /* if new pixel data, and we already have a pixmap, update the tile */
1690                         qd->it->blank = FALSE;
1691                         if (qd->it->surface && qd->it->render_done == TILE_RENDER_ALL)
1692                                 {
1693                                 rt_tile_render(rt, qd->it, qd->x, qd->y, qd->w, qd->h, qd->new_data, fast);
1694                                 }
1695                         }
1696                 }
1697
1698         if (rt->draw_queue)
1699                 {
1700                 qd->it->qd = NULL;
1701                 rt->draw_queue = g_list_remove(rt->draw_queue, qd);
1702                 if (fast)
1703                         {
1704                         if (qd->it->qd2)
1705                                 {
1706                                 rt_queue_merge(qd->it->qd2, qd);
1707                                 g_free(qd);
1708                                 }
1709                         else
1710                                 {
1711                                 qd->it->qd2 = qd;
1712                                 rt->draw_queue_2pass = g_list_append(rt->draw_queue_2pass, qd);
1713                                 }
1714                         }
1715                 else
1716                         {
1717                         g_free(qd);
1718                         }
1719                 }
1720         else
1721                 {
1722                 qd->it->qd2 = NULL;
1723                 rt->draw_queue_2pass = g_list_remove(rt->draw_queue_2pass, qd);
1724                 g_free(qd);
1725                 }
1726
1727         if (!rt->draw_queue && !rt->draw_queue_2pass)
1728                 {
1729                 pr_render_complete_signal(pr);
1730
1731                 rt->draw_idle_id = 0;
1732                 return FALSE;
1733                 }
1734
1735                 return rt_queue_schedule_next_draw(rt, FALSE);
1736 }
1737
1738 static void rt_queue_list_free(GList *list)
1739 {
1740         GList *work;
1741
1742         work = list;
1743         while (work)
1744                 {
1745                 QueueData *qd;
1746
1747                 qd = work->data;
1748                 work = work->next;
1749
1750                 qd->it->qd = NULL;
1751                 qd->it->qd2 = NULL;
1752                 g_free(qd);
1753                 }
1754
1755         g_list_free(list);
1756 }
1757
1758 static void rt_queue_clear(RendererTiles *rt)
1759 {
1760         rt_queue_list_free(rt->draw_queue);
1761         rt->draw_queue = NULL;
1762
1763         rt_queue_list_free(rt->draw_queue_2pass);
1764         rt->draw_queue_2pass = NULL;
1765
1766         if (rt->draw_idle_id)
1767                 {
1768                 g_source_remove(rt->draw_idle_id);
1769                 rt->draw_idle_id = 0;
1770                 }
1771         rt_sync_scroll(rt);
1772 }
1773
1774 static void rt_queue_merge(QueueData *parent, QueueData *qd)
1775 {
1776         if (parent->x + parent->w < qd->x + qd->w)
1777                 {
1778                 parent->w += (qd->x + qd->w) - (parent->x + parent->w);
1779                 }
1780         if (parent->x > qd->x)
1781                 {
1782                 parent->w += parent->x - qd->x;
1783                 parent->x = qd->x;
1784                 }
1785
1786         if (parent->y + parent->h < qd->y + qd->h)
1787                 {
1788                 parent->h += (qd->y + qd->h) - (parent->y + parent->h);
1789                 }
1790         if (parent->y > qd->y)
1791                 {
1792                 parent->h += parent->y - qd->y;
1793                 parent->y = qd->y;
1794                 }
1795
1796         parent->new_data |= qd->new_data;
1797 }
1798
1799 static gboolean rt_clamp_to_visible(RendererTiles *rt, gint *x, gint *y, gint *w, gint *h)
1800 {
1801         PixbufRenderer *pr = rt->pr;
1802         gint nx, ny;
1803         gint nw, nh;
1804         gint vx, vy;
1805         gint vw, vh;
1806
1807         vw = pr->vis_width;
1808         vh = pr->vis_height;
1809
1810         vx = rt->x_scroll;
1811         vy = rt->y_scroll;
1812
1813         if (*x + *w < vx || *x > vx + vw || *y + *h < vy || *y > vy + vh) return FALSE;
1814
1815         /* now clamp it */
1816         nx = CLAMP(*x, vx, vx + vw);
1817         nw = CLAMP(*w - (nx - *x), 1, vw);
1818
1819         ny = CLAMP(*y, vy, vy + vh);
1820         nh = CLAMP(*h - (ny - *y), 1, vh);
1821
1822         *x = nx;
1823         *y = ny;
1824         *w = nw;
1825         *h = nh;
1826
1827         return TRUE;
1828 }
1829
1830 static gboolean rt_queue_to_tiles(RendererTiles *rt, gint x, gint y, gint w, gint h,
1831                                   gboolean clamp, ImageRenderType render,
1832                                   gboolean new_data, gboolean only_existing)
1833 {
1834         PixbufRenderer *pr = rt->pr;
1835         gint i, j;
1836         gint x1, x2;
1837         gint y1, y2;
1838
1839         if (clamp && !rt_clamp_to_visible(rt, &x, &y, &w, &h)) return FALSE;
1840
1841         x1 = ROUND_DOWN(x, rt->tile_width);
1842         x2 = ROUND_UP(x + w, rt->tile_width);
1843
1844         y1 = ROUND_DOWN(y, rt->tile_height);
1845         y2 = ROUND_UP(y + h, rt->tile_height);
1846
1847         for (j = y1; j <= y2; j += rt->tile_height)
1848                 {
1849                 for (i = x1; i <= x2; i += rt->tile_width)
1850                         {
1851                         ImageTile *it;
1852
1853                         it = rt_tile_get(rt, i, j,
1854                                          (only_existing &&
1855                                           (i + rt->tile_width < rt->x_scroll ||
1856                                            i > rt->x_scroll + pr->vis_width ||
1857                                            j + rt->tile_height < rt->y_scroll ||
1858                                            j > rt->y_scroll + pr->vis_height)));
1859                         if (it)
1860                                 {
1861                                 QueueData *qd;
1862
1863                                 if ((render == TILE_RENDER_ALL && it->render_done != TILE_RENDER_ALL) ||
1864                                     (render == TILE_RENDER_AREA && it->render_todo != TILE_RENDER_ALL))
1865                                         {
1866                                         it->render_todo = render;
1867                                         }
1868
1869                                 qd = g_new(QueueData, 1);
1870                                 qd->it = it;
1871                                 qd->new_data = new_data;
1872
1873                                 if (i < x)
1874                                         {
1875                                         qd->x = x - i;
1876                                         }
1877                                 else
1878                                         {
1879                                         qd->x = 0;
1880                                         }
1881                                 qd->w = x + w - i - qd->x;
1882                                 if (qd->x + qd->w > rt->tile_width) qd->w = rt->tile_width - qd->x;
1883
1884                                 if (j < y)
1885                                         {
1886                                         qd->y = y - j;
1887                                         }
1888                                 else
1889                                         {
1890                                         qd->y = 0;
1891                                         }
1892                                 qd->h = y + h - j - qd->y;
1893                                 if (qd->y + qd->h > rt->tile_height) qd->h = rt->tile_height - qd->y;
1894
1895                                 if (qd->w < 1 || qd->h < 1)
1896                                         {
1897                                         g_free(qd);
1898                                         }
1899                                 else if (it->qd)
1900                                         {
1901                                         rt_queue_merge(it->qd, qd);
1902                                         g_free(qd);
1903                                         }
1904                                 else
1905                                         {
1906                                         it->qd = qd;
1907                                         rt->draw_queue = g_list_append(rt->draw_queue, qd);
1908                                         }
1909                                 }
1910                         }
1911                 }
1912
1913         return TRUE;
1914 }
1915
1916 static void rt_queue(RendererTiles *rt, gint x, gint y, gint w, gint h,
1917                      gboolean clamp, ImageRenderType render,
1918                      gboolean new_data, gboolean only_existing)
1919 {
1920         PixbufRenderer *pr = rt->pr;
1921         gint nx, ny;
1922
1923         rt_sync_scroll(rt);
1924
1925         nx = CLAMP(x, 0, pr->width - 1);
1926         ny = CLAMP(y, 0, pr->height - 1);
1927         w -= (nx - x);
1928         h -= (ny - y);
1929         w = CLAMP(w, 0, pr->width - nx);
1930         h = CLAMP(h, 0, pr->height - ny);
1931         if (w < 1 || h < 1) return;
1932
1933         if (rt_queue_to_tiles(rt, nx, ny, w, h, clamp, render, new_data, only_existing) &&
1934             ((!rt->draw_queue && !rt->draw_queue_2pass) || !rt->draw_idle_id))
1935                 {
1936                 if (rt->draw_idle_id)
1937                         {
1938                         g_source_remove(rt->draw_idle_id);
1939                         rt->draw_idle_id = 0;
1940                         }
1941                 rt_queue_schedule_next_draw(rt, TRUE);
1942                 }
1943 }
1944
1945 static void rt_scroll(void *renderer, gint x_off, gint y_off)
1946 {
1947         RendererTiles *rt = (RendererTiles *) renderer;
1948         PixbufRenderer *pr = rt->pr;
1949
1950         rt_sync_scroll(rt);
1951         if (rt->stereo_mode & PR_STEREO_MIRROR) x_off = -x_off;
1952         if (rt->stereo_mode & PR_STEREO_FLIP) y_off = -y_off;
1953
1954         gint w = pr->vis_width - abs(x_off);
1955         gint h = pr->vis_height - abs(y_off);
1956
1957         if (w < 1 || h < 1)
1958                 {
1959                 /* scrolled completely to new material */
1960                 rt_queue(rt, 0, 0, pr->width, pr->height, TRUE, TILE_RENDER_ALL, FALSE, FALSE);
1961                 return;
1962                 }
1963         else
1964                 {
1965                 gint x1, y1;
1966                 gint x2, y2;
1967                 cairo_t *cr;
1968                 cairo_surface_t *surface;
1969
1970                 if (x_off < 0)
1971                         {
1972                         x1 = abs(x_off);
1973                         x2 = 0;
1974                         }
1975                 else
1976                         {
1977                         x1 = 0;
1978                         x2 = abs(x_off);
1979                         }
1980
1981                 if (y_off < 0)
1982                         {
1983                         y1 = abs(y_off);
1984                         y2 = 0;
1985                         }
1986                 else
1987                         {
1988                         y1 = 0;
1989                         y2 = abs(y_off);
1990                         }
1991
1992                 cr = cairo_create(rt->surface);
1993                 surface = rt->surface;
1994
1995                 /* clipping restricts the intermediate surface's size, so it's a good idea
1996                  * to use it. */
1997                 cairo_rectangle(cr, x1 + pr->x_offset + rt->stereo_off_x, y1 + pr->y_offset + rt->stereo_off_y, w, h);
1998                 cairo_clip (cr);
1999                 /* Now push a group to change the target */
2000                 cairo_push_group (cr);
2001                 cairo_set_source_surface(cr, surface, x1 - x2, y1 - y2);
2002                 cairo_paint(cr);
2003                 /* Now copy the intermediate target back */
2004                 cairo_pop_group_to_source(cr);
2005                 cairo_paint(cr);
2006                 cairo_destroy(cr);
2007
2008                 rt_overlay_queue_all(rt, x2, y2, x1, y1);
2009
2010                 w = pr->vis_width - w;
2011                 h = pr->vis_height - h;
2012
2013                 if (w > 0)
2014                         {
2015                         rt_queue(rt,
2016                                     x_off > 0 ? rt->x_scroll + (pr->vis_width - w) : rt->x_scroll, rt->y_scroll,
2017                                     w, pr->vis_height, TRUE, TILE_RENDER_ALL, FALSE, FALSE);
2018                         }
2019                 if (h > 0)
2020                         {
2021                         /** @FIXME to optimize this, remove overlap */
2022                         rt_queue(rt,
2023                                     rt->x_scroll, y_off > 0 ? rt->y_scroll + (pr->vis_height - h) : rt->y_scroll,
2024                                     pr->vis_width, h, TRUE, TILE_RENDER_ALL, FALSE, FALSE);
2025                         }
2026                 }
2027 }
2028
2029 static void renderer_area_changed(void *renderer, gint src_x, gint src_y, gint src_w, gint src_h)
2030 {
2031         RendererTiles *rt = (RendererTiles *)renderer;
2032         PixbufRenderer *pr = rt->pr;
2033         gint x, y, width, height,  x1, y1, x2, y2;
2034
2035         gint orientation = rt_get_orientation(rt);
2036         pr_coords_map_orientation_reverse(orientation,
2037                                      src_x - GET_RIGHT_PIXBUF_OFFSET(rt), src_y,
2038                                      pr->image_width, pr->image_height,
2039                                      src_w, src_h,
2040                                      &x, &y,
2041                                      &width, &height);
2042
2043         if (pr->scale != 1.0 && pr->zoom_quality != GDK_INTERP_NEAREST)
2044                 {
2045                 /* increase region when using a zoom quality that may access surrounding pixels */
2046                 y -= 1;
2047                 height += 2;
2048                 }
2049
2050         x1 = (gint)floor((gdouble)x * pr->scale);
2051         y1 = (gint)floor((gdouble)y * pr->scale * pr->aspect_ratio);
2052         x2 = (gint)ceil((gdouble)(x + width) * pr->scale);
2053         y2 = (gint)ceil((gdouble)(y + height) * pr->scale * pr->aspect_ratio);
2054
2055         rt_queue(rt, x1, y1, x2 - x1, y2 - y1, FALSE, TILE_RENDER_AREA, TRUE, TRUE);
2056 }
2057
2058 static void renderer_redraw(RendererTiles *rt, gint x, gint y, gint w, gint h,
2059                      gint clamp, ImageRenderType render, gboolean new_data, gboolean only_existing)
2060 {
2061         PixbufRenderer *pr = rt->pr;
2062
2063         x -= rt->stereo_off_x;
2064         y -= rt->stereo_off_y;
2065
2066         rt_border_draw(rt, x, y, w, h);
2067
2068         x = MAX(0, x - pr->x_offset + pr->x_scroll);
2069         y = MAX(0, y - pr->y_offset + pr->y_scroll);
2070
2071         rt_queue(rt,
2072                  x, y,
2073                  MIN(w, pr->width - x),
2074                  MIN(h, pr->height - y),
2075                  clamp, render, new_data, only_existing);
2076 }
2077
2078 static void renderer_update_pixbuf(void *renderer, gboolean UNUSED(lazy))
2079 {
2080         rt_queue_clear((RendererTiles *)renderer);
2081 }
2082
2083 static void renderer_update_zoom(void *renderer, gboolean lazy)
2084 {
2085         RendererTiles *rt = (RendererTiles *)renderer;
2086         PixbufRenderer *pr = rt->pr;
2087
2088         rt_tile_invalidate_all((RendererTiles *)renderer);
2089         if (!lazy)
2090                 {
2091                 renderer_redraw(renderer, 0, 0, pr->width, pr->height, TRUE, TILE_RENDER_ALL, TRUE, FALSE);
2092                 }
2093         rt_border_clear(rt);
2094 }
2095
2096 static void renderer_invalidate_region(void *renderer, gint x, gint y, gint w, gint h)
2097 {
2098         rt_tile_invalidate_region((RendererTiles *)renderer, x, y, w, h);
2099 }
2100
2101 static void renderer_update_viewport(void *renderer)
2102 {
2103         RendererTiles *rt = (RendererTiles *)renderer;
2104
2105         rt->stereo_off_x = 0;
2106         rt->stereo_off_y = 0;
2107
2108         if (rt->stereo_mode & PR_STEREO_RIGHT)
2109                 {
2110                 if (rt->stereo_mode & PR_STEREO_HORIZ)
2111                         {
2112                         rt->stereo_off_x = rt->pr->viewport_width;
2113                         }
2114                 else if (rt->stereo_mode & PR_STEREO_VERT)
2115                         {
2116                         rt->stereo_off_y = rt->pr->viewport_height;
2117                         }
2118                 else if (rt->stereo_mode & PR_STEREO_FIXED)
2119                         {
2120                         rt->stereo_off_x = rt->pr->stereo_fixed_x_right;
2121                         rt->stereo_off_y = rt->pr->stereo_fixed_y_right;
2122                         }
2123                 }
2124         else
2125                 {
2126                 if (rt->stereo_mode & PR_STEREO_FIXED)
2127                         {
2128                         rt->stereo_off_x = rt->pr->stereo_fixed_x_left;
2129                         rt->stereo_off_y = rt->pr->stereo_fixed_y_left;
2130                         }
2131                 }
2132         DEBUG_1("update size: %p  %d %d   %d %d", (void *)rt, rt->stereo_off_x, rt->stereo_off_y, rt->pr->viewport_width, rt->pr->viewport_height);
2133         rt_sync_scroll(rt);
2134         rt_overlay_update_sizes(rt);
2135         rt_border_clear(rt);
2136 }
2137
2138 static void renderer_stereo_set(void *renderer, gint stereo_mode)
2139 {
2140         RendererTiles *rt = (RendererTiles *)renderer;
2141
2142         rt->stereo_mode = stereo_mode;
2143 }
2144
2145 static void renderer_free(void *renderer)
2146 {
2147         RendererTiles *rt = (RendererTiles *)renderer;
2148         rt_queue_clear(rt);
2149         rt_tile_free_all(rt);
2150         if (rt->spare_tile) g_object_unref(rt->spare_tile);
2151         if (rt->overlay_buffer) g_object_unref(rt->overlay_buffer);
2152         rt_overlay_list_clear(rt);
2153         /* disconnect "hierarchy-changed" */
2154         g_signal_handlers_disconnect_matched(G_OBJECT(rt->pr), G_SIGNAL_MATCH_DATA,
2155                                                      0, 0, 0, NULL, rt);
2156         g_free(rt);
2157 }
2158
2159 static gboolean rt_realize_cb(GtkWidget *widget, gpointer data)
2160 {
2161         RendererTiles *rt = (RendererTiles *)data;
2162         cairo_t *cr;
2163
2164         if (!rt->surface)
2165                 {
2166                 rt->surface = gdk_window_create_similar_surface(gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR, gtk_widget_get_allocated_width(widget), gtk_widget_get_allocated_height(widget));
2167
2168                 cr = cairo_create(rt->surface);
2169                 cairo_set_source_rgb(cr, (gdouble)rt->pr->color.red / 65535, (gdouble)rt->pr->color.green / 65535, (gdouble)rt->pr->color.blue / 65535);
2170                 cairo_paint(cr);
2171                 cairo_destroy(cr);
2172                 }
2173
2174         return FALSE;
2175 }
2176
2177 static gboolean rt_size_allocate_cb(GtkWidget *widget,  GdkRectangle *allocation, gpointer data)
2178 {
2179         RendererTiles *rt = data;
2180         cairo_t *cr;
2181         cairo_surface_t *old_surface;
2182
2183         if (gtk_widget_get_realized(GTK_WIDGET(rt->pr)))
2184                 {
2185                 old_surface = rt->surface;
2186                 rt->surface = gdk_window_create_similar_surface(gtk_widget_get_window(widget), CAIRO_CONTENT_COLOR, allocation->width, allocation->height);
2187
2188                 cr = cairo_create(rt->surface);
2189
2190                 cairo_set_source_rgb(cr, (gdouble)options->image.border_color.red / 65535, (gdouble)options->image.border_color.green / 65535, (gdouble)options->image.border_color.blue / 65535);
2191                 cairo_paint(cr);
2192                 cairo_set_source_surface(cr, old_surface, 0, 0);
2193                 cairo_paint(cr);
2194                 cairo_destroy(cr);
2195                 cairo_surface_destroy(old_surface);
2196
2197                 renderer_redraw(rt, allocation->x, allocation->y, allocation->width, allocation->height, FALSE, TILE_RENDER_ALL, FALSE, FALSE);
2198         }
2199
2200         return FALSE;
2201 }
2202
2203 static gboolean rt_draw_cb(GtkWidget *UNUSED(widget), cairo_t *cr, gpointer data)
2204 {
2205         RendererTiles *rt = (RendererTiles *)data;
2206         GList *work;
2207         OverlayData *od;
2208
2209         if (rt->stereo_mode & (PR_STEREO_HORIZ | PR_STEREO_VERT))
2210                 {
2211                 cairo_push_group(cr);
2212                 cairo_set_source_rgb(cr, (double)rt->pr->color.red / 65535, (double)rt->pr->color.green / 65535, (double)rt->pr->color.blue / 65535);
2213
2214                 if (rt->stereo_mode & PR_STEREO_HORIZ)
2215                         {
2216                         cairo_rectangle(cr, rt->stereo_off_x, 0, rt->pr->viewport_width, rt->pr->viewport_height);
2217                         }
2218                 else
2219                         {
2220                         cairo_rectangle(cr, 0, rt->stereo_off_y, rt->pr->viewport_width, rt->pr->viewport_height);
2221                         }
2222                 cairo_clip(cr);
2223                 cairo_paint(cr);
2224
2225                 cairo_rectangle(cr, rt->pr->x_offset + rt->stereo_off_x, rt->pr->y_offset + rt->stereo_off_y, rt->pr->vis_width, rt->pr->vis_height);
2226                 cairo_clip(cr);
2227                 cairo_set_source_surface(cr, rt->surface, 0, 0);
2228                 cairo_paint(cr);
2229
2230                 cairo_pop_group_to_source(cr);
2231                 cairo_paint(cr);
2232                 }
2233         else
2234                 {
2235                 cairo_set_source_surface(cr, rt->surface, 0, 0);
2236                 cairo_paint(cr);
2237                 }
2238
2239         work = rt->overlay_list;
2240         while (work)
2241                 {
2242                 od = work->data;
2243                 gint px, py, pw, ph;
2244                 pw = gdk_pixbuf_get_width(od->pixbuf);
2245                 ph = gdk_pixbuf_get_height(od->pixbuf);
2246                 px = od->x;
2247                 py = od->y;
2248
2249                 if (od->flags & OVL_RELATIVE)
2250                         {
2251                         if (px < 0) px = rt->pr->viewport_width - pw + px;
2252                         if (py < 0) py = rt->pr->viewport_height - ph + py;
2253                         }
2254
2255                 gdk_cairo_set_source_pixbuf(cr, od->pixbuf, px, py);
2256                 cairo_paint(cr);
2257                 work = work->next;
2258                 }
2259
2260         return FALSE;
2261 }
2262
2263 RendererFuncs *renderer_tiles_new(PixbufRenderer *pr)
2264 {
2265         RendererTiles *rt = g_new0(RendererTiles, 1);
2266
2267         rt->pr = pr;
2268
2269         rt->f.area_changed = renderer_area_changed;
2270         rt->f.update_pixbuf = renderer_update_pixbuf;
2271         rt->f.free = renderer_free;
2272         rt->f.update_zoom = renderer_update_zoom;
2273         rt->f.invalidate_region = renderer_invalidate_region;
2274         rt->f.scroll = rt_scroll;
2275         rt->f.update_viewport = renderer_update_viewport;
2276
2277
2278         rt->f.overlay_add = renderer_tiles_overlay_add;
2279         rt->f.overlay_set = renderer_tiles_overlay_set;
2280         rt->f.overlay_get = renderer_tiles_overlay_get;
2281
2282         rt->f.stereo_set = renderer_stereo_set;
2283
2284         rt->tile_width = options->image.tile_size;
2285         rt->tile_height = options->image.tile_size;
2286
2287         rt->tiles = NULL;
2288         rt->tile_cache_size = 0;
2289
2290         rt->tile_cache_max = PR_CACHE_SIZE_DEFAULT;
2291
2292         rt->draw_idle_id = 0;
2293
2294         rt->stereo_mode = 0;
2295         rt->stereo_off_x = 0;
2296         rt->stereo_off_y = 0;
2297
2298         rt->hidpi_scale = gtk_widget_get_scale_factor(GTK_WIDGET(rt->pr));
2299
2300         g_signal_connect(G_OBJECT(pr), "hierarchy-changed",
2301                          G_CALLBACK(rt_hierarchy_changed_cb), rt);
2302
2303         g_signal_connect(G_OBJECT(pr), "draw",
2304                          G_CALLBACK(rt_draw_cb), rt);
2305         g_signal_connect(G_OBJECT(pr), "realize", G_CALLBACK(rt_realize_cb), rt);
2306         g_signal_connect(G_OBJECT(pr), "size-allocate", G_CALLBACK(rt_size_allocate_cb), rt);
2307
2308         return (RendererFuncs *) rt;
2309 }
2310
2311
2312 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */