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