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