clang-tidy: readability-isolate-declaration
[geeqie.git] / src / image.cc
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 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 "main.h"
23 #include "image.h"
24
25 #include "collect-table.h"
26 #include "color-man.h"
27 #include "exif.h"
28 #include "metadata.h"
29 #include "history-list.h"
30 #include "image-load.h"
31 #include "layout.h"
32 #include "layout-image.h"
33 #include "pixbuf-renderer.h"
34 #include "pixbuf-util.h"
35 #include "ui-fileops.h"
36 #include "ui-misc.h"
37 #include "filecache.h"
38
39 #include <cmath>
40
41 static GList *image_list = nullptr;
42
43 static void image_read_ahead_start(ImageWindow *imd);
44 static void image_cache_set(ImageWindow *imd, FileData *fd);
45
46 // For draw rectangle function
47 static gint pixbuf_start_x;
48 static gint pixbuf_start_y;
49 static gint image_start_x;
50 static gint image_start_y;
51 static gint rect_x1, rect_x2, rect_y1, rect_y2;
52 static gint rect_id = 0;
53
54 /*
55  *-------------------------------------------------------------------
56  * 'signals'
57  *-------------------------------------------------------------------
58  */
59
60 static void image_click_cb(PixbufRenderer *, GdkEventButton *event, gpointer data)
61 {
62         auto imd = static_cast<ImageWindow *>(data);
63         if (!options->image_lm_click_nav && event->button == MOUSE_BUTTON_MIDDLE)
64                 {
65                 imd->mouse_wheel_mode = !imd->mouse_wheel_mode;
66                 }
67
68         if (imd->func_button)
69                 {
70                 imd->func_button(imd, event, imd->data_button);
71                 }
72 }
73
74 static void switch_coords_orientation(ImageWindow *imd, gint x, gint y, gint width, gint height)
75 {
76         switch (imd->orientation)
77                 {
78                 case EXIF_ORIENTATION_TOP_LEFT:
79                         /* normal -- nothing to do */
80                         rect_x1 = image_start_x;
81                         rect_y1 = image_start_y;
82                         rect_x2 = x;
83                         rect_y2 = y;
84                         break;
85                 case EXIF_ORIENTATION_TOP_RIGHT:
86                         /* mirrored */
87                         rect_x1 = width - x;
88                         rect_y1 = image_start_y;
89                         rect_x2 = width - image_start_x;
90                         rect_y2 = y;
91                         break;
92                 case EXIF_ORIENTATION_BOTTOM_RIGHT:
93                         /* upside down */
94                         rect_x1 = width - x;
95                         rect_y1 = height - y;
96                         rect_x2 = width - image_start_x;
97                         rect_y2 = height - image_start_y;
98                         break;
99                 case EXIF_ORIENTATION_BOTTOM_LEFT:
100                         /* flipped */
101                         rect_x1 = image_start_x;
102                         rect_y1 = height - y;
103                         rect_x2 = x;
104                         rect_y2 = height - image_start_y;
105                         break;
106                 case EXIF_ORIENTATION_LEFT_TOP:
107                         /* left mirrored */
108                         rect_x1 = image_start_y;
109                         rect_y1 = image_start_x;
110                         rect_x2 = y;
111                         rect_y2 = x;
112                         break;
113                 case EXIF_ORIENTATION_RIGHT_TOP:
114                         /* rotated -90 (270) */
115                         rect_x1 = image_start_y;
116                         rect_y1 = width - x;
117                         rect_x2 = y;
118                         rect_y2 = width - image_start_x;
119                         break;
120                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
121                         /* right mirrored */
122                         rect_x1 = height - y;
123                         rect_y1 = width - x;
124                         rect_x2 = height - image_start_y;
125                         rect_y2 = width - image_start_x;
126                         break;
127                 case EXIF_ORIENTATION_LEFT_BOTTOM:
128                         /* rotated 90 */
129                         rect_x1 = height - y;
130                         rect_y1 = image_start_x;
131                         rect_x2 = height - image_start_y;
132                         rect_y2 = x;
133                         break;
134                 default:
135                         /* The other values are out of range */
136                         break;
137                 }
138 }
139
140 static void image_press_cb(PixbufRenderer *pr, GdkEventButton *event, gpointer data)
141 {
142         auto imd = static_cast<ImageWindow *>(data);
143         LayoutWindow *lw;
144         gint x_pixel;
145         gint y_pixel;
146
147         if(options->draw_rectangle)
148                 {
149                 pixbuf_renderer_get_mouse_position(pr, &x_pixel, &y_pixel);
150
151                 pixbuf_start_x = event->x;
152                 pixbuf_start_y = event->y;
153
154                 if (x_pixel == -1)
155                         {
156                         image_start_x = 0;
157                         }
158                 else
159                         {
160                         image_start_x = x_pixel;
161                         }
162
163                 if (y_pixel == -1)
164                         {
165                         image_start_y = 0;
166                         }
167                 else
168                         {
169                         image_start_y = y_pixel;
170                         }
171                 }
172
173         if (rect_id)
174                 {
175                 pixbuf_renderer_overlay_remove(reinterpret_cast<PixbufRenderer *>(imd->pr), rect_id);
176                 }
177
178         lw = layout_find_by_image(imd);
179         if (!lw)
180                 {
181                 layout_valid(&lw);
182                 }
183
184         if (lw && event->button == MOUSE_BUTTON_LEFT && event->type == GDK_2BUTTON_PRESS
185                                                                                                 && !options->image_lm_click_nav)
186                 {
187                 layout_image_full_screen_toggle(lw);
188                 }
189 }
190
191 static void image_release_cb(PixbufRenderer *, GdkEventButton *event, gpointer data)
192 {
193         auto imd = static_cast<ImageWindow *>(data);
194         LayoutWindow *lw;
195
196         lw = layout_find_by_image(imd);
197         if (!lw)
198                 {
199                 layout_valid(&lw);
200                 }
201
202         defined_mouse_buttons(nullptr, event, lw);
203 }
204
205 static void image_drag_cb(PixbufRenderer *pr, GdkEventMotion *event, gpointer data)
206 {
207         auto imd = static_cast<ImageWindow *>(data);
208         gint width;
209         gint height;
210         gint rect_width;
211         gint rect_height;
212         GdkPixbuf *rect_pixbuf;
213         gint x_pixel;
214         gint y_pixel;
215         gint image_x_pixel;
216         gint image_y_pixel;
217
218         if (options->draw_rectangle)
219                 {
220                 pixbuf_renderer_get_image_size(pr, &width, &height);
221                 pixbuf_renderer_get_mouse_position(pr, &x_pixel, &y_pixel);
222
223                 if (x_pixel == -1)
224                         {
225                         image_x_pixel = width;
226                         }
227                 else
228                         {
229                         image_x_pixel = x_pixel;
230                         }
231
232                 if (y_pixel == -1)
233                         {
234                         image_y_pixel = height;
235                         }
236                 else
237                         {
238                         image_y_pixel = y_pixel;
239                         }
240
241                 switch_coords_orientation(imd, image_x_pixel, image_y_pixel, width, height);
242                 if (rect_id)
243                         {
244                         pixbuf_renderer_overlay_remove(reinterpret_cast<PixbufRenderer *>(imd->pr), rect_id);
245                         }
246
247                 rect_width = pr->drag_last_x - pixbuf_start_x;
248                 if (rect_width <= 0)
249                         {
250                         rect_width = 1;
251                         }
252                 rect_height = pr->drag_last_y - pixbuf_start_y;
253                 if (rect_height <= 0)
254                         {
255                         rect_height = 1;
256                         }
257
258                 rect_pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, rect_width, rect_height);
259                 pixbuf_set_rect_fill(rect_pixbuf, 0, 0, rect_width, rect_height, 255, 255, 255, 0);
260                 pixbuf_set_rect(rect_pixbuf, 1, 1, rect_width-2, rect_height - 2, 0, 0, 0, 255, 1, 1, 1, 1);
261                 pixbuf_set_rect(rect_pixbuf, 2, 2, rect_width-4, rect_height - 4, 255, 255, 255, 255, 1, 1, 1, 1);
262
263                 rect_id = pixbuf_renderer_overlay_add(reinterpret_cast<PixbufRenderer *>(imd->pr), rect_pixbuf, pixbuf_start_x, pixbuf_start_y, OVL_NORMAL);
264                 }
265
266         pixbuf_renderer_get_scaled_size(pr, &width, &height);
267
268         if (imd->func_drag)
269                 {
270                 imd->func_drag(imd, event,
271                                static_cast<gfloat>(pr->drag_last_x - event->x) / width,
272                                static_cast<gfloat>(pr->drag_last_y - event->y) / height,
273                                imd->data_button);
274                 }
275 }
276
277 static void image_scroll_notify_cb(PixbufRenderer *pr, gpointer data)
278 {
279         auto imd = static_cast<ImageWindow *>(data);
280
281         if (imd->func_scroll_notify && pr->scale)
282                 {
283                 imd->func_scroll_notify(imd,
284                                         static_cast<gint>(static_cast<gdouble>(pr->x_scroll) / pr->scale),
285                                         static_cast<gint>(static_cast<gdouble>(pr->y_scroll) / pr->scale),
286                                         static_cast<gint>(static_cast<gdouble>(pr->image_width) - pr->vis_width / pr->scale),
287                                         static_cast<gint>(static_cast<gdouble>(pr->image_height) - pr->vis_height / pr->scale),
288                                         imd->data_scroll_notify);
289                 }
290 }
291
292 static void image_update_util(ImageWindow *imd)
293 {
294         if (imd->func_update) imd->func_update(imd, imd->data_update);
295 }
296
297
298 static void image_complete_util(ImageWindow *imd, gboolean preload)
299 {
300         if (imd->il && image_get_pixbuf(imd) != image_loader_get_pixbuf(imd->il)) return;
301
302         DEBUG_1("%s image load completed \"%s\" (%s)", get_exec_time(),
303                           (preload) ? (imd->read_ahead_fd ? imd->read_ahead_fd->path : "null") :
304                                       (imd->image_fd ? imd->image_fd->path : "null"),
305                           (preload) ? "preload" : "current");
306
307         if (!preload) imd->completed = TRUE;
308         if (imd->func_complete) imd->func_complete(imd, preload, imd->data_complete);
309 }
310
311 static void image_render_complete_cb(PixbufRenderer *, gpointer data)
312 {
313         auto imd = static_cast<ImageWindow *>(data);
314
315         image_complete_util(imd, FALSE);
316 }
317
318 static void image_state_set(ImageWindow *imd, ImageState state)
319 {
320         if (state == IMAGE_STATE_NONE)
321                 {
322                 imd->state = state;
323                 }
324         else
325                 {
326                 imd->state = static_cast<ImageState>(imd->state | state);
327                 }
328         if (imd->func_state) imd->func_state(imd, state, imd->data_state);
329 }
330
331 static void image_state_unset(ImageWindow *imd, ImageState state)
332 {
333         imd->state = static_cast<ImageState>(imd->state & ~state);
334         if (imd->func_state) imd->func_state(imd, state, imd->data_state);
335 }
336
337 static void image_zoom_cb(PixbufRenderer *, gdouble, gpointer data)
338 {
339         auto imd = static_cast<ImageWindow *>(data);
340
341         if (imd->title_show_zoom) image_update_title(imd);
342         image_state_set(imd, IMAGE_STATE_IMAGE);
343         image_update_util(imd);
344 }
345
346 /*
347  *-------------------------------------------------------------------
348  * misc
349  *-------------------------------------------------------------------
350  */
351
352 void image_update_title(ImageWindow *imd)
353 {
354         gchar *title = nullptr;
355         gchar *zoom = nullptr;
356         gchar *collection = nullptr;
357         LayoutWindow *lw;
358         gchar *lw_ident = nullptr;
359
360         if (!imd->top_window) return;
361
362         if (imd->collection && collection_to_number(imd->collection) >= 0)
363                 {
364                 const gchar *name = imd->collection->name;
365                 if (!name) name = _("Untitled");
366                 collection = g_strdup_printf(_(" (Collection %s)"), name);
367                 }
368
369         if (imd->title_show_zoom)
370                 {
371                 gchar *buf = image_zoom_get_as_text(imd);
372                 zoom = g_strconcat(" [", buf, "]", NULL);
373                 g_free(buf);
374                 }
375
376         lw = layout_find_by_image(imd);
377         if (lw)
378                 {
379                 lw_ident = g_strconcat(" (", lw->options.id, ")", NULL);
380                 }
381
382         title = g_strdup_printf("%s%s%s%s%s%s%s",
383                 imd->title ? imd->title : "",
384                 imd->image_fd ? imd->image_fd->name : "",
385                 zoom ? zoom : "",
386                 collection ? collection : "",
387                 imd->image_fd ? " - " : "",
388                 imd->title_right ? imd->title_right : "",
389                 options->show_window_ids ? (lw_ident ? lw_ident : "") : ""
390                 );
391         if (lw_ident)
392                 {
393                 g_free(lw_ident);
394                 }
395
396         gtk_window_set_title(GTK_WINDOW(imd->top_window), title);
397
398         g_free(title);
399         g_free(zoom);
400         g_free(collection);
401 }
402
403 /*
404  *-------------------------------------------------------------------
405  * rotation, flip, etc.
406  *-------------------------------------------------------------------
407  */
408 static gboolean image_get_x11_screen_profile(ImageWindow *imd, guchar **screen_profile, gint *screen_profile_len)
409 {
410         GdkScreen *screen = gtk_widget_get_screen(imd->widget);;
411         GdkAtom    type   = GDK_NONE;
412         gint       format = 0;
413
414         return (gdk_property_get(gdk_screen_get_root_window(screen),
415                                  gdk_atom_intern ("_ICC_PROFILE", FALSE),
416                                  GDK_NONE,
417                                  0, 64 * 1024 * 1024, FALSE,
418                                  &type, &format, screen_profile_len, screen_profile) && *screen_profile_len > 0);
419 }
420
421 static gboolean image_post_process_color(ImageWindow *imd, gint start_row, gboolean run_in_bg)
422 {
423         ColorMan *cm;
424         ColorManProfileType input_type;
425         ColorManProfileType screen_type;
426         const gchar *input_file = nullptr;
427         const gchar *screen_file = nullptr;
428         guchar *profile = nullptr;
429         guint profile_len;
430         guchar *screen_profile = nullptr;
431         gint screen_profile_len;
432         ExifData *exif;
433
434         if (imd->cm) return FALSE;
435
436         if (imd->color_profile_input >= COLOR_PROFILE_FILE &&
437             imd->color_profile_input <  COLOR_PROFILE_FILE + COLOR_PROFILE_INPUTS)
438                 {
439                 const gchar *file = options->color_profile.input_file[imd->color_profile_input - COLOR_PROFILE_FILE];
440
441                 if (!is_readable_file(file)) return FALSE;
442
443                 input_type = COLOR_PROFILE_FILE;
444                 input_file = file;
445                 }
446         else if (imd->color_profile_input >= COLOR_PROFILE_SRGB &&
447                  imd->color_profile_input <  COLOR_PROFILE_FILE)
448                 {
449                 input_type = static_cast<ColorManProfileType>(imd->color_profile_input);
450                 input_file = nullptr;
451                 }
452         else
453                 {
454                 return FALSE;
455                 }
456
457         if (options->color_profile.use_x11_screen_profile &&
458             image_get_x11_screen_profile(imd, &screen_profile, &screen_profile_len))
459                 {
460                 screen_type = COLOR_PROFILE_MEM;
461                 DEBUG_1("Using X11 screen profile, length: %d", screen_profile_len);
462                 }
463         else if (options->color_profile.screen_file &&
464             is_readable_file(options->color_profile.screen_file))
465                 {
466                 screen_type = COLOR_PROFILE_FILE;
467                 screen_file = options->color_profile.screen_file;
468                 }
469         else
470                 {
471                 screen_type = COLOR_PROFILE_SRGB;
472                 screen_file = nullptr;
473                 }
474
475
476         imd->color_profile_from_image = COLOR_PROFILE_NONE;
477
478         exif = exif_read_fd(imd->image_fd);
479
480         if (exif)
481                 {
482                 if (g_strcmp0(imd->image_fd->format_name, "heif") == 0)
483                         {
484                         profile = heif_color_profile(imd->image_fd, &profile_len);
485                         }
486
487                 if (!profile)
488                         {
489                         profile = exif_get_color_profile(exif, &profile_len);
490                         }
491
492                 if (profile)
493                         {
494                         if (!imd->color_profile_use_image)
495                                 {
496                                 g_free(profile);
497                                 profile = nullptr;
498                                 }
499                         DEBUG_1("Found embedded color profile");
500                         imd->color_profile_from_image = COLOR_PROFILE_MEM;
501                         }
502                 else
503                         {
504                         gchar *interop_index = exif_get_data_as_text(exif, "Exif.Iop.InteroperabilityIndex");
505
506                         if (interop_index)
507                                 {
508                                 /* Exif 2.21 specification */
509                                 if (!strcmp(interop_index, "R98"))
510                                         {
511                                         imd->color_profile_from_image = COLOR_PROFILE_SRGB;
512                                         DEBUG_1("Found EXIF 2.21 ColorSpace of sRGB");
513                                         }
514                                 else if (!strcmp(interop_index, "R03"))
515                                         {
516                                         imd->color_profile_from_image = COLOR_PROFILE_ADOBERGB;
517                                         DEBUG_1("Found EXIF 2.21 ColorSpace of AdobeRGB");
518                                         }
519                                 g_free(interop_index);
520                                 }
521                         else
522                                 {
523                                 gint cs;
524
525                                 /* ColorSpace == 1 specifies sRGB per EXIF 2.2 */
526                                 if (!exif_get_integer(exif, "Exif.Photo.ColorSpace", &cs)) cs = 0;
527                                 if (cs == 1)
528                                         {
529                                         imd->color_profile_from_image = COLOR_PROFILE_SRGB;
530                                         DEBUG_1("Found EXIF 2.2 ColorSpace of sRGB");
531                                         }
532                                 else if (cs == 2)
533                                         {
534                                         /* non-standard way of specifying AdobeRGB (used by some software) */
535                                         imd->color_profile_from_image = COLOR_PROFILE_ADOBERGB;
536                                         DEBUG_1("Found EXIF 2.2 ColorSpace of AdobeRGB");
537                                         }
538                                 }
539
540                         if (imd->color_profile_use_image && imd->color_profile_from_image != COLOR_PROFILE_NONE)
541                                {
542                                input_type = static_cast<ColorManProfileType>(imd->color_profile_from_image);
543                                input_file = nullptr;
544                                }
545                         }
546
547                 exif_free_fd(imd->image_fd, exif);
548                 }
549
550
551         if (profile)
552                 {
553                 cm = color_man_new_embedded(run_in_bg ? imd : nullptr, nullptr,
554                                             profile, profile_len,
555                                             screen_type, screen_file, screen_profile, screen_profile_len);
556                 g_free(profile);
557                 }
558         else
559                 {
560                 cm = color_man_new(run_in_bg ? imd : nullptr, nullptr,
561                                    input_type, input_file,
562                                    screen_type, screen_file, screen_profile, screen_profile_len);
563                 }
564
565         if (cm)
566                 {
567                 if (start_row > 0)
568                         {
569                         cm->row = start_row;
570                         cm->incremental_sync = TRUE;
571                         }
572
573                 imd->cm = cm;
574                 }
575
576         image_update_util(imd);
577
578         if (screen_profile)
579                 {
580                 g_free(screen_profile);
581                 screen_profile = nullptr;
582                 }
583
584         return !!cm;
585 }
586
587
588 static void image_post_process_tile_color_cb(PixbufRenderer *, GdkPixbuf **pixbuf, gint x, gint y, gint w, gint h, gpointer data)
589 {
590         auto imd = static_cast<ImageWindow *>(data);
591         if (imd->cm) color_man_correct_region(static_cast<ColorMan *>(imd->cm), *pixbuf, x, y, w, h);
592         if (imd->desaturate) pixbuf_desaturate_rect(*pixbuf, x, y, w, h);
593         if (imd->overunderexposed) pixbuf_highlight_overunderexposed(*pixbuf, x, y, w, h);
594 }
595
596 void image_alter_orientation(ImageWindow *imd, FileData *fd_n, AlterType type)
597 {
598         static const gint rotate_90[]    = {1,   6, 7, 8, 5, 2, 3, 4, 1};
599         static const gint rotate_90_cc[] = {1,   8, 5, 6, 7, 4, 1, 2, 3};
600         static const gint rotate_180[]   = {1,   3, 4, 1, 2, 7, 8, 5, 6};
601         static const gint mirror[]       = {1,   2, 1, 4, 3, 6, 5, 8, 7};
602         static const gint flip[]         = {1,   4, 3, 2, 1, 8, 7, 6, 5};
603
604         gint orientation;
605
606         if (!imd || !imd->pr || !imd->image_fd || !fd_n) return;
607
608         orientation = EXIF_ORIENTATION_TOP_LEFT;
609         {
610         if (fd_n->user_orientation)
611                 {
612                 orientation = fd_n->user_orientation;
613                 }
614         else
615                 if (options->metadata.write_orientation)
616                         {
617                         if (g_strcmp0(imd->image_fd->format_name, "heif") == 0)
618                                 {
619                                 orientation = EXIF_ORIENTATION_TOP_LEFT;
620                                 }
621                         else
622                                 {
623                                 orientation = metadata_read_int(fd_n, ORIENTATION_KEY, EXIF_ORIENTATION_TOP_LEFT);
624                                 }
625                         }
626         }
627
628         switch (type)
629                 {
630                 case ALTER_ROTATE_90:
631                         orientation = rotate_90[orientation];
632                         break;
633                 case ALTER_ROTATE_90_CC:
634                         orientation = rotate_90_cc[orientation];
635                         break;
636                 case ALTER_ROTATE_180:
637                         orientation = rotate_180[orientation];
638                         break;
639                 case ALTER_MIRROR:
640                         orientation = mirror[orientation];
641                         break;
642                 case ALTER_FLIP:
643                         orientation = flip[orientation];
644                         break;
645                 case ALTER_NONE:
646                         orientation = fd_n->exif_orientation ? fd_n->exif_orientation : 1;
647                         break;
648                 default:
649                         return;
650                         break;
651                 }
652
653         if (orientation != (fd_n->exif_orientation ? fd_n->exif_orientation : 1))
654                 {
655                 if (g_strcmp0(fd_n->format_name, "heif") != 0)
656                         {
657                         if (!options->metadata.write_orientation)
658                                 {
659                                 /* user_orientation does not work together with options->metadata.write_orientation,
660                                    use either one or the other.
661                                    we must however handle switching metadata.write_orientation on and off, therefore
662                                    we just disable referencing new fd's, not unreferencing the old ones
663                                 */
664                                 if (fd_n->user_orientation == 0) file_data_ref(fd_n);
665                                 fd_n->user_orientation = orientation;
666                                 }
667                         }
668                 else
669                         {
670                         if (fd_n->user_orientation == 0) file_data_ref(fd_n);
671                         fd_n->user_orientation = orientation;
672                         }
673                 }
674         else
675                 {
676                 if (fd_n->user_orientation != 0) file_data_unref(fd_n);
677                 fd_n->user_orientation = 0;
678                 }
679
680         if (g_strcmp0(fd_n->format_name, "heif") != 0)
681                 {
682                 if (options->metadata.write_orientation)
683                         {
684                         if (type == ALTER_NONE)
685                                 {
686                                 metadata_write_revert(fd_n, ORIENTATION_KEY);
687                                 }
688                         else
689                                 {
690                                 metadata_write_int(fd_n, ORIENTATION_KEY, orientation);
691                                 }
692                         }
693                 }
694
695         if (imd->image_fd == fd_n && (!options->metadata.write_orientation || options->image.exif_rotate_enable))
696                 {
697                 imd->orientation = orientation;
698                 pixbuf_renderer_set_orientation(reinterpret_cast<PixbufRenderer *>(imd->pr), orientation);
699                 }
700 }
701
702 void image_set_desaturate(ImageWindow *imd, gboolean desaturate)
703 {
704         imd->desaturate = desaturate;
705         if (imd->cm || imd->desaturate || imd->overunderexposed)
706                 pixbuf_renderer_set_post_process_func(reinterpret_cast<PixbufRenderer *>(imd->pr), image_post_process_tile_color_cb, imd, (imd->cm != nullptr) );
707         else
708                 pixbuf_renderer_set_post_process_func(reinterpret_cast<PixbufRenderer *>(imd->pr), nullptr, nullptr, TRUE);
709         pixbuf_renderer_set_orientation(reinterpret_cast<PixbufRenderer *>(imd->pr), imd->orientation);
710 }
711
712 gboolean image_get_desaturate(ImageWindow *imd)
713 {
714         return imd->desaturate;
715 }
716
717 void image_set_overunderexposed(ImageWindow *imd, gboolean overunderexposed)
718 {
719         imd->overunderexposed = overunderexposed;
720         if (imd->cm || imd->desaturate || imd->overunderexposed)
721                 pixbuf_renderer_set_post_process_func(reinterpret_cast<PixbufRenderer *>(imd->pr), image_post_process_tile_color_cb, imd, (imd->cm != nullptr) );
722         else
723                 pixbuf_renderer_set_post_process_func(reinterpret_cast<PixbufRenderer *>(imd->pr), nullptr, nullptr, TRUE);
724         pixbuf_renderer_set_orientation(reinterpret_cast<PixbufRenderer *>(imd->pr), imd->orientation);
725 }
726
727 void image_set_ignore_alpha(ImageWindow *imd, gboolean ignore_alpha)
728 {
729    pixbuf_renderer_set_ignore_alpha(reinterpret_cast<PixbufRenderer *>(imd->pr), ignore_alpha);
730 }
731
732 /*
733  *-------------------------------------------------------------------
734  * read ahead (prebuffer)
735  *-------------------------------------------------------------------
736  */
737
738 static void image_read_ahead_cancel(ImageWindow *imd)
739 {
740         DEBUG_1("%s read ahead cancelled for :%s", get_exec_time(), imd->read_ahead_fd ? imd->read_ahead_fd->path : "null");
741
742         image_loader_free(imd->read_ahead_il);
743         imd->read_ahead_il = nullptr;
744
745         file_data_unref(imd->read_ahead_fd);
746         imd->read_ahead_fd = nullptr;
747 }
748
749 static void image_read_ahead_done_cb(ImageLoader *, gpointer data)
750 {
751         auto imd = static_cast<ImageWindow *>(data);
752
753         if (!imd->read_ahead_fd || !imd->read_ahead_il) return;
754
755         DEBUG_1("%s read ahead done for :%s", get_exec_time(), imd->read_ahead_fd->path);
756
757         if (!imd->read_ahead_fd->pixbuf)
758                 {
759                 imd->read_ahead_fd->pixbuf = image_loader_get_pixbuf(imd->read_ahead_il);
760                 if (imd->read_ahead_fd->pixbuf)
761                         {
762                         g_object_ref(imd->read_ahead_fd->pixbuf);
763                         image_cache_set(imd, imd->read_ahead_fd);
764                         }
765                 }
766         image_loader_free(imd->read_ahead_il);
767         imd->read_ahead_il = nullptr;
768
769         image_complete_util(imd, TRUE);
770 }
771
772 static void image_read_ahead_error_cb(ImageLoader *il, gpointer data)
773 {
774         /* we even treat errors as success, maybe at least some of the file was ok */
775         image_read_ahead_done_cb(il, data);
776 }
777
778 static void image_read_ahead_start(ImageWindow *imd)
779 {
780         /* already started ? */
781         if (!imd->read_ahead_fd || imd->read_ahead_il || imd->read_ahead_fd->pixbuf) return;
782
783         /* still loading ?, do later */
784         if (imd->il /*|| imd->cm*/) return;
785
786         DEBUG_1("%s read ahead started for :%s", get_exec_time(), imd->read_ahead_fd->path);
787
788         imd->read_ahead_il = image_loader_new(imd->read_ahead_fd);
789
790         image_loader_delay_area_ready(imd->read_ahead_il, TRUE); /* we will need the area_ready signals later */
791
792         g_signal_connect(G_OBJECT(imd->read_ahead_il), "error", (GCallback)image_read_ahead_error_cb, imd);
793         g_signal_connect(G_OBJECT(imd->read_ahead_il), "done", (GCallback)image_read_ahead_done_cb, imd);
794
795         if (!image_loader_start(imd->read_ahead_il))
796                 {
797                 image_read_ahead_cancel(imd);
798                 image_complete_util(imd, TRUE);
799                 }
800 }
801
802 static void image_read_ahead_set(ImageWindow *imd, FileData *fd)
803 {
804         if (imd->read_ahead_fd && fd && imd->read_ahead_fd == fd) return;
805
806         image_read_ahead_cancel(imd);
807
808         imd->read_ahead_fd = file_data_ref(fd);
809
810         DEBUG_1("read ahead set to :%s", imd->read_ahead_fd->path);
811
812         image_read_ahead_start(imd);
813 }
814
815 /*
816  *-------------------------------------------------------------------
817  * post buffering
818  *-------------------------------------------------------------------
819  */
820
821 static void image_cache_release_cb(FileData *fd)
822 {
823         g_object_unref(fd->pixbuf);
824         fd->pixbuf = nullptr;
825 }
826
827 static FileCacheData *image_get_cache()
828 {
829         static FileCacheData *cache = nullptr;
830         if (!cache) cache = file_cache_new(image_cache_release_cb, 1);
831         file_cache_set_max_size(cache, static_cast<gulong>(options->image.image_cache_max) * 1048576); /* update from options */
832         return cache;
833 }
834
835 static void image_cache_set(ImageWindow *, FileData *fd)
836 {
837         g_assert(fd->pixbuf);
838
839         file_cache_put(image_get_cache(), fd, static_cast<gulong>(gdk_pixbuf_get_rowstride(fd->pixbuf)) * static_cast<gulong>(gdk_pixbuf_get_height(fd->pixbuf)));
840         file_data_send_notification(fd, NOTIFY_PIXBUF); /* to update histogram */
841 }
842
843 static gint image_cache_get(ImageWindow *imd)
844 {
845         gint success;
846
847         success = file_cache_get(image_get_cache(), imd->image_fd);
848         if (success)
849                 {
850                 g_assert(imd->image_fd->pixbuf);
851                 image_change_pixbuf(imd, imd->image_fd->pixbuf, image_zoom_get(imd), FALSE);
852                 }
853
854         return success;
855 }
856
857 /*
858  *-------------------------------------------------------------------
859  * loading
860  *-------------------------------------------------------------------
861  */
862
863 static void image_load_pixbuf_ready(ImageWindow *imd)
864 {
865         if (image_get_pixbuf(imd) || !imd->il) return;
866
867         image_change_pixbuf(imd, image_loader_get_pixbuf(imd->il), image_zoom_get(imd), FALSE);
868 }
869
870 static void image_load_area_cb(ImageLoader *il, guint x, guint y, guint w, guint h, gpointer data)
871 {
872         auto imd = static_cast<ImageWindow *>(data);
873         PixbufRenderer *pr;
874
875         pr = reinterpret_cast<PixbufRenderer *>(imd->pr);
876
877         if (imd->delay_flip &&
878             pr->pixbuf != image_loader_get_pixbuf(il))
879                 {
880                 return;
881                 }
882
883         if (!pr->pixbuf) image_change_pixbuf(imd, image_loader_get_pixbuf(imd->il), image_zoom_get(imd), TRUE);
884
885         pixbuf_renderer_area_changed(pr, x, y, w, h);
886 }
887
888 static void image_load_done_cb(ImageLoader *, gpointer data)
889 {
890         auto imd = static_cast<ImageWindow *>(data);
891
892         DEBUG_1("%s image done", get_exec_time());
893
894         if (options->image.enable_read_ahead && imd->image_fd && !imd->image_fd->pixbuf && image_loader_get_pixbuf(imd->il))
895                 {
896                 imd->image_fd->pixbuf = static_cast<GdkPixbuf*>(g_object_ref(image_loader_get_pixbuf(imd->il)));
897                 image_cache_set(imd, imd->image_fd);
898                 }
899         /* call the callback triggered by image_state after fd->pixbuf is set */
900         g_object_set(G_OBJECT(imd->pr), "loading", FALSE, NULL);
901         image_state_unset(imd, IMAGE_STATE_LOADING);
902
903         if (!image_loader_get_pixbuf(imd->il))
904                 {
905                 GdkPixbuf *pixbuf = pixbuf_fallback(imd->image_fd, 0, 0);
906
907                 image_change_pixbuf(imd, pixbuf, image_zoom_get(imd), FALSE);
908                 g_object_unref(pixbuf);
909
910                 imd->unknown = TRUE;
911                 }
912         else if (imd->delay_flip &&
913             image_get_pixbuf(imd) != image_loader_get_pixbuf(imd->il))
914                 {
915                 g_object_set(G_OBJECT(imd->pr), "complete", FALSE, NULL);
916                 image_change_pixbuf(imd, image_loader_get_pixbuf(imd->il), image_zoom_get(imd), FALSE);
917                 }
918
919         image_loader_free(imd->il);
920         imd->il = nullptr;
921
922         image_read_ahead_start(imd);
923 }
924
925 static void image_load_size_cb(ImageLoader *, guint width, guint height, gpointer data)
926 {
927         auto imd = static_cast<ImageWindow *>(data);
928
929         DEBUG_1("image_load_size_cb: %dx%d", width, height);
930         pixbuf_renderer_set_size_early(reinterpret_cast<PixbufRenderer *>(imd->pr), width, height);
931 }
932
933 static void image_load_error_cb(ImageLoader *il, gpointer data)
934 {
935         DEBUG_1("%s image error", get_exec_time());
936
937         /* even on error handle it like it was done,
938          * since we have a pixbuf with _something_ */
939
940         image_load_done_cb(il, data);
941 }
942
943 static void image_load_set_signals(ImageWindow *imd, gboolean override_old_signals)
944 {
945         g_assert(imd->il);
946         if (override_old_signals)
947                 {
948                 /* override the old signals */
949                 g_signal_handlers_disconnect_matched(G_OBJECT(imd->il), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, imd);
950                 }
951
952         g_signal_connect(G_OBJECT(imd->il), "area_ready", (GCallback)image_load_area_cb, imd);
953         g_signal_connect(G_OBJECT(imd->il), "error", (GCallback)image_load_error_cb, imd);
954         g_signal_connect(G_OBJECT(imd->il), "done", (GCallback)image_load_done_cb, imd);
955         g_signal_connect(G_OBJECT(imd->il), "size_prepared", (GCallback)image_load_size_cb, imd);
956 }
957
958 /* this read ahead is located here merely for the callbacks, above */
959
960 static gboolean image_read_ahead_check(ImageWindow *imd)
961 {
962         if (!imd->read_ahead_fd) return FALSE;
963         if (imd->il) return FALSE;
964
965         if (!imd->image_fd || imd->read_ahead_fd != imd->image_fd)
966                 {
967                 image_read_ahead_cancel(imd);
968                 return FALSE;
969                 }
970
971         if (imd->read_ahead_il)
972                 {
973                 imd->il = imd->read_ahead_il;
974                 imd->read_ahead_il = nullptr;
975
976                 image_load_set_signals(imd, TRUE);
977
978                 g_object_set(G_OBJECT(imd->pr), "loading", TRUE, NULL);
979                 image_state_set(imd, IMAGE_STATE_LOADING);
980
981                 if (!imd->delay_flip)
982                         {
983                         image_change_pixbuf(imd, image_loader_get_pixbuf(imd->il), image_zoom_get(imd), TRUE);
984                         }
985
986                 image_loader_delay_area_ready(imd->il, FALSE); /* send the delayed area_ready signals */
987
988                 file_data_unref(imd->read_ahead_fd);
989                 imd->read_ahead_fd = nullptr;
990                 return TRUE;
991                 }
992         if (imd->read_ahead_fd->pixbuf)
993                 {
994                 image_change_pixbuf(imd, imd->read_ahead_fd->pixbuf, image_zoom_get(imd), FALSE);
995
996                 file_data_unref(imd->read_ahead_fd);
997                 imd->read_ahead_fd = nullptr;
998
999                 return TRUE;
1000                 }
1001
1002         image_read_ahead_cancel(imd);
1003         return FALSE;
1004 }
1005
1006 static gboolean image_load_begin(ImageWindow *imd, FileData *fd)
1007 {
1008         DEBUG_1("%s image begin", get_exec_time());
1009
1010         if (imd->il) return FALSE;
1011
1012         imd->completed = FALSE;
1013         g_object_set(G_OBJECT(imd->pr), "complete", FALSE, NULL);
1014
1015         if (image_cache_get(imd))
1016                 {
1017                 DEBUG_1("from cache: %s", imd->image_fd->path);
1018                 return TRUE;
1019                 }
1020
1021         if (image_read_ahead_check(imd))
1022                 {
1023                 DEBUG_1("from read ahead buffer: %s", imd->image_fd->path);
1024                 return TRUE;
1025                 }
1026
1027         if (!imd->delay_flip && image_get_pixbuf(imd))
1028                 {
1029                 PixbufRenderer *pr;
1030
1031                 pr = PIXBUF_RENDERER(imd->pr);
1032                 if (pr->pixbuf) g_object_unref(pr->pixbuf);
1033                 pr->pixbuf = nullptr;
1034                 }
1035
1036         g_object_set(G_OBJECT(imd->pr), "loading", TRUE, NULL);
1037
1038         imd->il = image_loader_new(fd);
1039
1040         image_load_set_signals(imd, FALSE);
1041
1042         if (!image_loader_start(imd->il))
1043                 {
1044                 DEBUG_1("image start error");
1045
1046                 g_object_set(G_OBJECT(imd->pr), "loading", FALSE, NULL);
1047
1048                 image_loader_free(imd->il);
1049                 imd->il = nullptr;
1050
1051                 image_complete_util(imd, FALSE);
1052
1053                 return FALSE;
1054                 }
1055
1056         image_state_set(imd, IMAGE_STATE_LOADING);
1057
1058 /*
1059         if (!imd->delay_flip && !image_get_pixbuf(imd) && image_loader_get_pixbuf(imd->il))
1060                 {
1061                 image_change_pixbuf(imd, image_loader_get_pixbuf(imd->il), image_zoom_get(imd), TRUE);
1062                 }
1063 */
1064         return TRUE;
1065 }
1066
1067 static void image_reset(ImageWindow *imd)
1068 {
1069         /* stops anything currently being done */
1070
1071         DEBUG_1("%s image reset", get_exec_time());
1072
1073         g_object_set(G_OBJECT(imd->pr), "loading", FALSE, NULL);
1074
1075         image_loader_free(imd->il);
1076         imd->il = nullptr;
1077
1078         color_man_free(static_cast<ColorMan *>(imd->cm));
1079         imd->cm = nullptr;
1080
1081         imd->delay_alter_type = ALTER_NONE;
1082
1083         image_state_set(imd, IMAGE_STATE_NONE);
1084 }
1085
1086 /*
1087  *-------------------------------------------------------------------
1088  * image changer
1089  *-------------------------------------------------------------------
1090  */
1091
1092 static void image_change_complete(ImageWindow *imd, gdouble zoom)
1093 {
1094         image_reset(imd);
1095         imd->unknown = TRUE;
1096
1097         /** @FIXME Might be improved when the wepb animation changes happen */
1098         g_object_set(G_OBJECT(imd->pr), "zoom_2pass", options->image.zoom_2pass, NULL);
1099         g_object_set(G_OBJECT(imd->pr), "zoom_quality", options->image.zoom_quality, NULL);
1100
1101         if (!imd->image_fd)
1102                 {
1103                 image_change_pixbuf(imd, nullptr, zoom, FALSE);
1104                 }
1105         else
1106                 {
1107
1108                 if (is_readable_file(imd->image_fd->path))
1109                         {
1110                         PixbufRenderer *pr;
1111
1112                         pr = PIXBUF_RENDERER(imd->pr);
1113                         pr->zoom = zoom;        /* store the zoom, needed by the loader */
1114
1115                         /* Disable 2-pass for GIFs. Animated GIFs can flicker when enabled
1116                          * Reduce quality to worst but fastest to avoid dropped frames */
1117                         if (g_ascii_strcasecmp(imd->image_fd->extension, ".GIF") == 0)
1118                                 {
1119                                 g_object_set(G_OBJECT(imd->pr), "zoom_2pass", FALSE, NULL);
1120                                 g_object_set(G_OBJECT(imd->pr), "zoom_quality", GDK_INTERP_NEAREST, NULL);
1121                                 }
1122
1123
1124                         if (image_load_begin(imd, imd->image_fd))
1125                                 {
1126                                 imd->unknown = FALSE;
1127                                 }
1128                         }
1129
1130                 if (imd->unknown == TRUE)
1131                         {
1132                         GdkPixbuf *pixbuf;
1133
1134                         pixbuf = pixbuf_inline(PIXBUF_INLINE_BROKEN);
1135                         image_change_pixbuf(imd, pixbuf, zoom, FALSE);
1136                         g_object_unref(pixbuf);
1137                         }
1138                 }
1139
1140         image_update_util(imd);
1141 }
1142
1143 static void image_change_real(ImageWindow *imd, FileData *fd,
1144                               CollectionData *cd, CollectInfo *info, gdouble zoom)
1145 {
1146
1147         imd->collection = cd;
1148         imd->collection_info = info;
1149
1150         if (imd->auto_refresh && imd->image_fd)
1151                 file_data_unregister_real_time_monitor(imd->image_fd);
1152
1153         file_data_unref(imd->image_fd);
1154         imd->image_fd = file_data_ref(fd);
1155
1156
1157         image_change_complete(imd, zoom);
1158
1159         image_update_title(imd);
1160         image_state_set(imd, IMAGE_STATE_IMAGE);
1161
1162         if (imd->auto_refresh && imd->image_fd)
1163                 file_data_register_real_time_monitor(imd->image_fd);
1164 }
1165
1166 /*
1167  *-------------------------------------------------------------------
1168  * focus stuff
1169  *-------------------------------------------------------------------
1170  */
1171
1172 static gboolean image_focus_in_cb(GtkWidget *, GdkEventFocus *, gpointer data)
1173 {
1174         auto imd = static_cast<ImageWindow *>(data);
1175
1176         if (imd->func_focus_in)
1177                 {
1178                 imd->func_focus_in(imd, imd->data_focus_in);
1179                 }
1180
1181         return TRUE;
1182 }
1183
1184 static gboolean image_scroll_cb(GtkWidget *, GdkEventScroll *event, gpointer data)
1185 {
1186         auto imd = static_cast<ImageWindow *>(data);
1187         gboolean in_lw = FALSE;
1188         gint i = 0;
1189         LayoutWindow *lw = nullptr;
1190
1191         if (imd->func_scroll && event && event->type == GDK_SCROLL)
1192                 {
1193                 layout_valid(&lw);
1194                 /* check if the image is in a layout window */
1195                 for (i = 0; i < MAX_SPLIT_IMAGES; i++)
1196                         {
1197                         if (imd == lw->split_images[i])
1198                                 {
1199                                 in_lw = TRUE;
1200                                 break;
1201                                 }
1202                         }
1203
1204                 if (in_lw)
1205                         {
1206                         if (lw->options.split_pane_sync)
1207                                 {
1208                                 for (i = 0; i < MAX_SPLIT_IMAGES; i++)
1209                                         {
1210                                         if (lw->split_images[i])
1211                                                 {
1212                                                 layout_image_activate(lw, i, FALSE);
1213                                                 imd->func_scroll(lw->split_images[i], event, lw->split_images[i]->data_scroll);
1214                                                 }
1215                                         }
1216                                 }
1217                         else
1218                                 {
1219                                 imd->func_scroll(imd, event, imd->data_scroll);
1220                                 }
1221                         return TRUE;
1222                         }
1223
1224                 imd->func_scroll(imd, event, imd->data_scroll);
1225                 return TRUE;
1226                 }
1227
1228         return FALSE;
1229 }
1230
1231 /*
1232  *-------------------------------------------------------------------
1233  * public interface
1234  *-------------------------------------------------------------------
1235  */
1236
1237 void image_attach_window(ImageWindow *imd, GtkWidget *window,
1238                          const gchar *title, const gchar *title_right, gboolean show_zoom)
1239 {
1240         LayoutWindow *lw;
1241
1242         imd->top_window = window;
1243         g_free(imd->title);
1244         imd->title = g_strdup(title);
1245         g_free(imd->title_right);
1246         imd->title_right = g_strdup(title_right);
1247         imd->title_show_zoom = show_zoom;
1248
1249         lw = layout_find_by_image(imd);
1250
1251         if (!(options->image.fit_window_to_image && lw && (lw->options.tools_float || lw->options.tools_hidden))) window = nullptr;
1252
1253         pixbuf_renderer_set_parent(reinterpret_cast<PixbufRenderer *>(imd->pr), reinterpret_cast<GtkWindow *>(window));
1254
1255         image_update_title(imd);
1256 }
1257
1258 void image_set_update_func(ImageWindow *imd,
1259                            void (*func)(ImageWindow *imd, gpointer data),
1260                            gpointer data)
1261 {
1262         imd->func_update = func;
1263         imd->data_update = data;
1264 }
1265
1266 void image_set_complete_func(ImageWindow *imd,
1267                              void (*func)(ImageWindow *imd, gboolean preload, gpointer data),
1268                              gpointer data)
1269 {
1270         imd->func_complete = func;
1271         imd->data_complete = data;
1272 }
1273
1274 void image_set_state_func(ImageWindow *imd,
1275                         void (*func)(ImageWindow *imd, ImageState state, gpointer data),
1276                         gpointer data)
1277 {
1278         imd->func_state = func;
1279         imd->data_state = data;
1280 }
1281
1282
1283 void image_set_button_func(ImageWindow *imd,
1284                            void (*func)(ImageWindow *, GdkEventButton *event, gpointer),
1285                            gpointer data)
1286 {
1287         imd->func_button = func;
1288         imd->data_button = data;
1289 }
1290
1291 void image_set_drag_func(ImageWindow *imd,
1292                            void (*func)(ImageWindow *, GdkEventMotion *event, gdouble dx, gdouble dy, gpointer),
1293                            gpointer data)
1294 {
1295         imd->func_drag = func;
1296         imd->data_drag = data;
1297 }
1298
1299 void image_set_scroll_func(ImageWindow *imd,
1300                            void (*func)(ImageWindow *, GdkEventScroll *event, gpointer),
1301                            gpointer data)
1302 {
1303         imd->func_scroll = func;
1304         imd->data_scroll = data;
1305 }
1306
1307 #pragma GCC diagnostic push
1308 #pragma GCC diagnostic ignored "-Wunused-function"
1309 void image_set_scroll_notify_func_unused(ImageWindow *imd,
1310                                   void (*func)(ImageWindow *imd, gint x, gint y, gint width, gint height, gpointer data),
1311                                   gpointer data)
1312 {
1313         imd->func_scroll_notify = func;
1314         imd->data_scroll_notify = data;
1315 }
1316 #pragma GCC diagnostic pop
1317
1318 void image_set_focus_in_func(ImageWindow *imd,
1319                            void (*func)(ImageWindow *, gpointer),
1320                            gpointer data)
1321 {
1322         imd->func_focus_in = func;
1323         imd->data_focus_in = data;
1324 }
1325
1326 /* path, name */
1327
1328 const gchar *image_get_path(ImageWindow *imd)
1329 {
1330         if (imd->image_fd == nullptr) return nullptr;
1331         return imd->image_fd->path;
1332 }
1333
1334 const gchar *image_get_name(ImageWindow *imd)
1335 {
1336         if (imd->image_fd == nullptr) return nullptr;
1337         return imd->image_fd->name;
1338 }
1339
1340 FileData *image_get_fd(ImageWindow *imd)
1341 {
1342         return imd->image_fd;
1343 }
1344
1345 /* merely changes path string, does not change the image! */
1346 void image_set_fd(ImageWindow *imd, FileData *fd)
1347 {
1348         if (imd->auto_refresh && imd->image_fd)
1349                 file_data_unregister_real_time_monitor(imd->image_fd);
1350
1351         file_data_unref(imd->image_fd);
1352         imd->image_fd = file_data_ref(fd);
1353
1354         image_update_title(imd);
1355         image_state_set(imd, IMAGE_STATE_IMAGE);
1356
1357         if (imd->auto_refresh && imd->image_fd)
1358                 file_data_register_real_time_monitor(imd->image_fd);
1359 }
1360
1361 /* load a new image */
1362
1363 void image_change_fd(ImageWindow *imd, FileData *fd, gdouble zoom)
1364 {
1365         if (imd->image_fd == fd) return;
1366
1367         image_change_real(imd, fd, nullptr, nullptr, zoom);
1368 }
1369
1370 gboolean image_get_image_size(ImageWindow *imd, gint *width, gint *height)
1371 {
1372         return pixbuf_renderer_get_image_size(PIXBUF_RENDERER(imd->pr), width, height);
1373 }
1374
1375 GdkPixbuf *image_get_pixbuf(ImageWindow *imd)
1376 {
1377         return pixbuf_renderer_get_pixbuf(reinterpret_cast<PixbufRenderer *>(imd->pr));
1378 }
1379
1380 void image_change_pixbuf(ImageWindow *imd, GdkPixbuf *pixbuf, gdouble zoom, gboolean lazy)
1381 {
1382         LayoutWindow *lw;
1383         StereoPixbufData stereo_data = STEREO_PIXBUF_DEFAULT;
1384         /* read_exif and similar functions can actually notice that the file has changed and trigger
1385            a notification that removes the pixbuf from cache and unrefs it. Therefore we must ref it
1386            here before it is taken over by the renderer. */
1387         if (pixbuf) g_object_ref(pixbuf);
1388
1389         imd->orientation = EXIF_ORIENTATION_TOP_LEFT;
1390         if (imd->image_fd)
1391                 {
1392                 if (imd->image_fd->user_orientation)
1393                         {
1394                         imd->orientation = imd->image_fd->user_orientation;
1395                         }
1396                 else if (options->image.exif_rotate_enable)
1397                         {
1398                         if (g_strcmp0(imd->image_fd->format_name, "heif") == 0)
1399                                 {
1400                                 imd->orientation = EXIF_ORIENTATION_TOP_LEFT;
1401                                 imd->image_fd->exif_orientation = imd->orientation;
1402                                 }
1403                         else
1404                                 {
1405                                 imd->orientation = metadata_read_int(imd->image_fd, ORIENTATION_KEY, EXIF_ORIENTATION_TOP_LEFT);
1406                                 imd->image_fd->exif_orientation = imd->orientation;
1407                                 }
1408                         }
1409                 }
1410
1411         if (pixbuf)
1412                 {
1413                 stereo_data = static_cast<StereoPixbufData>(imd->user_stereo);
1414                 if (stereo_data == STEREO_PIXBUF_DEFAULT)
1415                         {
1416                         stereo_data = static_cast<StereoPixbufData>(GPOINTER_TO_INT(g_object_get_data(G_OBJECT(pixbuf), "stereo_data")));
1417                         }
1418                 }
1419
1420         pixbuf_renderer_set_post_process_func(reinterpret_cast<PixbufRenderer *>(imd->pr), nullptr, nullptr, FALSE);
1421         if (imd->cm)
1422                 {
1423                 color_man_free(static_cast<ColorMan *>(imd->cm));
1424                 imd->cm = nullptr;
1425                 }
1426
1427         if (lazy)
1428                 {
1429                 pixbuf_renderer_set_pixbuf_lazy(reinterpret_cast<PixbufRenderer *>(imd->pr), pixbuf, zoom, imd->orientation, stereo_data);
1430                 }
1431         else
1432                 {
1433                 pixbuf_renderer_set_pixbuf(reinterpret_cast<PixbufRenderer *>(imd->pr), pixbuf, zoom);
1434                 pixbuf_renderer_set_orientation(reinterpret_cast<PixbufRenderer *>(imd->pr), imd->orientation);
1435                 pixbuf_renderer_set_stereo_data(reinterpret_cast<PixbufRenderer *>(imd->pr), stereo_data);
1436                 }
1437
1438         if (pixbuf) g_object_unref(pixbuf);
1439
1440         /* Color correction takes too much time for an animated gif */
1441         lw = layout_find_by_image(imd);
1442         if (imd->color_profile_enable && lw && !lw->animation)
1443                 {
1444                 image_post_process_color(imd, 0, FALSE); /** @todo error handling */
1445                 }
1446
1447         if (imd->cm || imd->desaturate || imd->overunderexposed)
1448                 pixbuf_renderer_set_post_process_func(reinterpret_cast<PixbufRenderer *>(imd->pr), image_post_process_tile_color_cb, imd, (imd->cm != nullptr) );
1449
1450         image_state_set(imd, IMAGE_STATE_IMAGE);
1451 }
1452
1453 void image_change_from_collection(ImageWindow *imd, CollectionData *cd, CollectInfo *info, gdouble zoom)
1454 {
1455         CollectWindow *cw;
1456
1457         if (!cd || !info || !g_list_find(cd->list, info)) return;
1458
1459         image_change_real(imd, info->fd, cd, info, zoom);
1460         cw = collection_window_find(cd);
1461         if (cw)
1462                 {
1463                 collection_table_set_focus(cw->table, info);
1464                 collection_table_unselect_all(cw->table);
1465                 collection_table_select(cw->table,info);
1466                 }
1467
1468         if (info->fd)
1469                 {
1470                 image_chain_append_end(info->fd->path);
1471                 }
1472 }
1473
1474 CollectionData *image_get_collection(ImageWindow *imd, CollectInfo **info)
1475 {
1476         if (collection_to_number(imd->collection) >= 0)
1477                 {
1478                 if (g_list_find(imd->collection->list, imd->collection_info) != nullptr)
1479                         {
1480                         if (info) *info = imd->collection_info;
1481                         }
1482                 else
1483                         {
1484                         if (info) *info = nullptr;
1485                         }
1486                 return imd->collection;
1487                 }
1488
1489         if (info) *info = nullptr;
1490         return nullptr;
1491 }
1492
1493 static void image_loader_sync_read_ahead_data(ImageLoader *il, gpointer old_data, gpointer data)
1494 {
1495         if (g_signal_handlers_disconnect_by_func(G_OBJECT(il), (gpointer)image_read_ahead_error_cb, old_data))
1496                 g_signal_connect(G_OBJECT(il), "error", G_CALLBACK(image_read_ahead_error_cb), data);
1497
1498         if (g_signal_handlers_disconnect_by_func(G_OBJECT(il), (gpointer)image_read_ahead_done_cb, old_data))
1499                 g_signal_connect(G_OBJECT(il), "done", G_CALLBACK(image_read_ahead_done_cb), data);
1500 }
1501
1502 static void image_loader_sync_data(ImageLoader *il, gpointer old_data, gpointer data)
1503 {
1504         if (g_signal_handlers_disconnect_by_func(G_OBJECT(il), (gpointer)image_load_area_cb, old_data))
1505                 g_signal_connect(G_OBJECT(il), "area_ready", G_CALLBACK(image_load_area_cb), data);
1506
1507         if (g_signal_handlers_disconnect_by_func(G_OBJECT(il), (gpointer)image_load_error_cb, old_data))
1508                 g_signal_connect(G_OBJECT(il), "error", G_CALLBACK(image_load_error_cb), data);
1509
1510         if (g_signal_handlers_disconnect_by_func(G_OBJECT(il), (gpointer)image_load_done_cb, old_data))
1511                 g_signal_connect(G_OBJECT(il), "done", G_CALLBACK(image_load_done_cb), data);
1512 }
1513
1514 /* this is more like a move function
1515  * it moves most data from source to imd
1516  */
1517 void image_move_from_image(ImageWindow *imd, ImageWindow *source)
1518 {
1519         if (imd == source) return;
1520
1521         imd->unknown = source->unknown;
1522
1523         imd->collection = source->collection;
1524         imd->collection_info = source->collection_info;
1525
1526         image_loader_free(imd->il);
1527         imd->il = nullptr;
1528
1529         image_set_fd(imd, image_get_fd(source));
1530
1531
1532         if (source->il)
1533                 {
1534                 imd->il = source->il;
1535                 source->il = nullptr;
1536
1537                 image_loader_sync_data(imd->il, source, imd);
1538
1539                 imd->delay_alter_type = source->delay_alter_type;
1540                 source->delay_alter_type = ALTER_NONE;
1541                 }
1542
1543         imd->color_profile_enable = source->color_profile_enable;
1544         imd->color_profile_input = source->color_profile_input;
1545         imd->color_profile_use_image = source->color_profile_use_image;
1546         color_man_free(static_cast<ColorMan *>(imd->cm));
1547         imd->cm = nullptr;
1548         if (source->cm)
1549                 {
1550                 ColorMan *cm;
1551
1552                 imd->cm = source->cm;
1553                 source->cm = nullptr;
1554
1555                 cm = static_cast<ColorMan *>(imd->cm);
1556                 cm->imd = imd;
1557                 cm->func_done_data = imd;
1558                 }
1559
1560         file_data_unref(imd->read_ahead_fd);
1561         source->read_ahead_fd = nullptr;
1562
1563         imd->orientation = source->orientation;
1564         imd->desaturate = source->desaturate;
1565
1566         imd->user_stereo = source->user_stereo;
1567
1568         pixbuf_renderer_move(PIXBUF_RENDERER(imd->pr), PIXBUF_RENDERER(source->pr));
1569
1570         if (imd->cm || imd->desaturate || imd->overunderexposed)
1571                 pixbuf_renderer_set_post_process_func(reinterpret_cast<PixbufRenderer *>(imd->pr), image_post_process_tile_color_cb, imd, (imd->cm != nullptr) );
1572         else
1573                 pixbuf_renderer_set_post_process_func(reinterpret_cast<PixbufRenderer *>(imd->pr), nullptr, nullptr, TRUE);
1574
1575 }
1576
1577 /* this is  a copy function
1578  * source stays unchanged
1579  */
1580 void image_copy_from_image(ImageWindow *imd, ImageWindow *source)
1581 {
1582         if (imd == source) return;
1583
1584         imd->unknown = source->unknown;
1585
1586         imd->collection = source->collection;
1587         imd->collection_info = source->collection_info;
1588
1589         image_loader_free(imd->il);
1590         imd->il = nullptr;
1591
1592         image_set_fd(imd, image_get_fd(source));
1593
1594
1595         imd->color_profile_enable = source->color_profile_enable;
1596         imd->color_profile_input = source->color_profile_input;
1597         imd->color_profile_use_image = source->color_profile_use_image;
1598         color_man_free(static_cast<ColorMan *>(imd->cm));
1599         imd->cm = nullptr;
1600         if (source->cm)
1601                 {
1602                 ColorMan *cm;
1603
1604                 imd->cm = source->cm;
1605                 source->cm = nullptr;
1606
1607                 cm = static_cast<ColorMan *>(imd->cm);
1608                 cm->imd = imd;
1609                 cm->func_done_data = imd;
1610                 }
1611
1612         image_loader_free(imd->read_ahead_il);
1613         imd->read_ahead_il = source->read_ahead_il;
1614         source->read_ahead_il = nullptr;
1615         if (imd->read_ahead_il) image_loader_sync_read_ahead_data(imd->read_ahead_il, source, imd);
1616
1617         file_data_unref(imd->read_ahead_fd);
1618         imd->read_ahead_fd = source->read_ahead_fd;
1619         source->read_ahead_fd = nullptr;
1620
1621         imd->completed = source->completed;
1622         imd->state = source->state;
1623         source->state = IMAGE_STATE_NONE;
1624
1625         imd->orientation = source->orientation;
1626         imd->desaturate = source->desaturate;
1627
1628         imd->user_stereo = source->user_stereo;
1629
1630         pixbuf_renderer_copy(PIXBUF_RENDERER(imd->pr), PIXBUF_RENDERER(source->pr));
1631
1632         if (imd->cm || imd->desaturate || imd->overunderexposed)
1633                 pixbuf_renderer_set_post_process_func(reinterpret_cast<PixbufRenderer *>(imd->pr), image_post_process_tile_color_cb, imd, (imd->cm != nullptr) );
1634         else
1635                 pixbuf_renderer_set_post_process_func(reinterpret_cast<PixbufRenderer *>(imd->pr), nullptr, nullptr, TRUE);
1636
1637 }
1638
1639
1640 /* manipulation */
1641
1642 void image_area_changed(ImageWindow *imd, gint x, gint y, gint width, gint height)
1643 {
1644         pixbuf_renderer_area_changed(reinterpret_cast<PixbufRenderer *>(imd->pr), x, y, width, height);
1645 }
1646
1647 void image_reload(ImageWindow *imd)
1648 {
1649         if (pixbuf_renderer_get_tiles(reinterpret_cast<PixbufRenderer *>(imd->pr))) return;
1650
1651         image_change_complete(imd, image_zoom_get(imd));
1652 }
1653
1654 void image_scroll(ImageWindow *imd, gint x, gint y)
1655 {
1656         pixbuf_renderer_scroll(reinterpret_cast<PixbufRenderer *>(imd->pr), x, y);
1657 }
1658
1659 void image_scroll_to_point(ImageWindow *imd, gint x, gint y,
1660                            gdouble x_align, gdouble y_align)
1661 {
1662         pixbuf_renderer_scroll_to_point(reinterpret_cast<PixbufRenderer *>(imd->pr), x, y, x_align, y_align);
1663 }
1664
1665 void image_get_scroll_center(ImageWindow *imd, gdouble *x, gdouble *y)
1666 {
1667         pixbuf_renderer_get_scroll_center(PIXBUF_RENDERER(imd->pr), x, y);
1668 }
1669
1670 void image_set_scroll_center(ImageWindow *imd, gdouble x, gdouble y)
1671 {
1672         pixbuf_renderer_set_scroll_center(PIXBUF_RENDERER(imd->pr), x, y);
1673 }
1674
1675 void image_zoom_adjust(ImageWindow *imd, gdouble increment)
1676 {
1677         pixbuf_renderer_zoom_adjust(reinterpret_cast<PixbufRenderer *>(imd->pr), increment);
1678 }
1679
1680 void image_zoom_adjust_at_point(ImageWindow *imd, gdouble increment, gint x, gint y)
1681 {
1682         pixbuf_renderer_zoom_adjust_at_point(reinterpret_cast<PixbufRenderer *>(imd->pr), increment, x, y);
1683 }
1684
1685 void image_zoom_set_limits(ImageWindow *imd, gdouble min, gdouble max)
1686 {
1687         pixbuf_renderer_zoom_set_limits(reinterpret_cast<PixbufRenderer *>(imd->pr), min, max);
1688 }
1689
1690 void image_zoom_set(ImageWindow *imd, gdouble zoom)
1691 {
1692         pixbuf_renderer_zoom_set(reinterpret_cast<PixbufRenderer *>(imd->pr), zoom);
1693 }
1694
1695 void image_zoom_set_fill_geometry(ImageWindow *imd, gboolean vertical)
1696 {
1697         PixbufRenderer *pr;
1698         gdouble zoom;
1699         gint width;
1700         gint height;
1701
1702         pr = reinterpret_cast<PixbufRenderer *>(imd->pr);
1703
1704         if (!pixbuf_renderer_get_pixbuf(pr) ||
1705             !pixbuf_renderer_get_image_size(pr, &width, &height)) return;
1706
1707         if (vertical)
1708                 {
1709                 zoom = static_cast<gdouble>(pr->viewport_height) / height;
1710                 }
1711         else
1712                 {
1713                 zoom = static_cast<gdouble>(pr->viewport_width) / width;
1714                 }
1715
1716         if (zoom < 1.0)
1717                 {
1718                 zoom = 0.0 - 1.0 / zoom;
1719                 }
1720
1721         pixbuf_renderer_zoom_set(pr, zoom);
1722 }
1723
1724 gdouble image_zoom_get(ImageWindow *imd)
1725 {
1726         return pixbuf_renderer_zoom_get(reinterpret_cast<PixbufRenderer *>(imd->pr));
1727 }
1728
1729 gdouble image_zoom_get_real(ImageWindow *imd)
1730 {
1731         return pixbuf_renderer_zoom_get_scale(reinterpret_cast<PixbufRenderer *>(imd->pr));
1732 }
1733
1734 gchar *image_zoom_get_as_text(ImageWindow *imd)
1735 {
1736         gdouble zoom;
1737         gdouble scale;
1738         gdouble l = 1.0;
1739         gdouble r = 1.0;
1740         gint pl = 0;
1741         gint pr = 0;
1742         const gchar *approx = " ";
1743
1744         zoom = image_zoom_get(imd);
1745         scale = image_zoom_get_real(imd);
1746
1747         if (zoom > 0.0)
1748                 {
1749                 l = zoom;
1750                 }
1751         else if (zoom < 0.0)
1752                 {
1753                 r = 0.0 - zoom;
1754                 }
1755         else if (zoom == 0.0 && scale != 0.0)
1756                 {
1757                 if (scale >= 1.0)
1758                         {
1759                         l = scale;
1760                         }
1761                 else
1762                         {
1763                         r = 1.0 / scale;
1764                         }
1765                 approx = "~";
1766                 }
1767
1768         if (rint(l) != l) pl = 2;
1769         if (rint(r) != r) pr = 2;
1770
1771         return g_strdup_printf("%.*f :%s%.*f", pl, l, approx, pr, r);
1772 }
1773
1774 gdouble image_zoom_get_default(ImageWindow *imd)
1775 {
1776         gdouble zoom = 1.0;
1777
1778         switch (options->image.zoom_mode)
1779         {
1780         case ZOOM_RESET_ORIGINAL:
1781                 break;
1782         case ZOOM_RESET_FIT_WINDOW:
1783                 zoom = 0.0;
1784                 break;
1785         case ZOOM_RESET_NONE:
1786                 if (imd) zoom = image_zoom_get(imd);
1787                 break;
1788         }
1789
1790         return zoom;
1791 }
1792
1793 /* stereo */
1794
1795 #pragma GCC diagnostic push
1796 #pragma GCC diagnostic ignored "-Wunused-function"
1797 gint image_stereo_get_unused(ImageWindow *imd)
1798 {
1799         return pixbuf_renderer_stereo_get((PixbufRenderer *)imd->pr);
1800 }
1801 #pragma GCC diagnostic pop
1802
1803 void image_stereo_set(ImageWindow *imd, gint stereo_mode)
1804 {
1805         DEBUG_1("Setting stereo mode %04x for imd %p", stereo_mode, (void *)imd);
1806         pixbuf_renderer_stereo_set(reinterpret_cast<PixbufRenderer *>(imd->pr), stereo_mode);
1807 }
1808
1809 #pragma GCC diagnostic push
1810 #pragma GCC diagnostic ignored "-Wunused-function"
1811 void image_stereo_swap_unused(ImageWindow *imd)
1812 {
1813         gint stereo_mode = pixbuf_renderer_stereo_get((PixbufRenderer *)imd->pr);
1814         stereo_mode ^= PR_STEREO_SWAP;
1815         pixbuf_renderer_stereo_set((PixbufRenderer *)imd->pr, stereo_mode);
1816 }
1817 #pragma GCC diagnostic pop
1818
1819 StereoPixbufData image_stereo_pixbuf_get(ImageWindow *imd)
1820 {
1821         return static_cast<StereoPixbufData>(imd->user_stereo);
1822 }
1823
1824 void image_stereo_pixbuf_set(ImageWindow *imd, StereoPixbufData stereo_mode)
1825 {
1826         imd->user_stereo = stereo_mode;
1827         image_reload(imd);
1828 }
1829
1830 /* read ahead */
1831
1832 void image_prebuffer_set(ImageWindow *imd, FileData *fd)
1833 {
1834         if (pixbuf_renderer_get_tiles(reinterpret_cast<PixbufRenderer *>(imd->pr))) return;
1835
1836         if (fd)
1837                 {
1838                 if (!file_cache_get(image_get_cache(), fd))
1839                         {
1840                         image_read_ahead_set(imd, fd);
1841                         }
1842                 }
1843         else
1844                 {
1845                 image_read_ahead_cancel(imd);
1846                 }
1847 }
1848
1849 static void image_notify_cb(FileData *fd, NotifyType type, gpointer data)
1850 {
1851         auto imd = static_cast<ImageWindow *>(data);
1852
1853         if (!imd || !image_get_pixbuf(imd) ||
1854             /* imd->il || */ /* loading in progress - do not check - it should start from the beginning anyway */
1855             !imd->image_fd || /* nothing to reload */
1856             imd->state == IMAGE_STATE_NONE /* loading not started, no need to reload */
1857             ) return;
1858
1859         if ((type & NOTIFY_REREAD) && fd == imd->image_fd)
1860                 {
1861                 /* there is no need to reload on NOTIFY_CHANGE,
1862                    modified files should be detacted anyway and NOTIFY_REREAD should be received
1863                    or they are removed from the filelist completely on "move" and "delete"
1864                 */
1865                 DEBUG_1("Notify image: %s %04x", fd->path, type);
1866                 image_reload(imd);
1867                 }
1868 }
1869
1870 void image_auto_refresh_enable(ImageWindow *imd, gboolean enable)
1871 {
1872         if (!enable && imd->auto_refresh && imd->image_fd)
1873                 file_data_unregister_real_time_monitor(imd->image_fd);
1874
1875         if (enable && !imd->auto_refresh && imd->image_fd)
1876                 file_data_register_real_time_monitor(imd->image_fd);
1877
1878         imd->auto_refresh = enable;
1879 }
1880
1881 void image_top_window_set_sync(ImageWindow *imd, gboolean allow_sync)
1882 {
1883         imd->top_window_sync = allow_sync;
1884
1885         g_object_set(G_OBJECT(imd->pr), "window_fit", allow_sync, NULL);
1886 }
1887
1888 void image_background_set_color(ImageWindow *imd, GdkRGBA *color)
1889 {
1890         pixbuf_renderer_set_color(reinterpret_cast<PixbufRenderer *>(imd->pr), color);
1891 }
1892
1893 void image_background_set_color_from_options(ImageWindow *imd, gboolean fullscreen)
1894 {
1895         GdkRGBA *color = nullptr;
1896         GdkRGBA theme_color;
1897         GdkRGBA bg_color;
1898         GtkStyleContext *style_context;
1899         LayoutWindow *lw = nullptr;
1900
1901         if ((options->image.use_custom_border_color && !fullscreen) ||
1902             (options->image.use_custom_border_color_in_fullscreen && fullscreen))
1903                 {
1904                 color = &options->image.border_color;
1905                 }
1906
1907         else
1908                 {
1909                 if (!layout_valid(&lw)) return;
1910
1911                 style_context = gtk_widget_get_style_context(lw->window);
1912                 gtk_style_context_get_background_color(style_context, GTK_STATE_FLAG_NORMAL, &bg_color);
1913
1914                 theme_color.red = bg_color.red * 1;
1915                 theme_color.green = bg_color.green * 1;
1916                 theme_color.blue = bg_color.blue * 1;
1917
1918                 color = &theme_color;
1919                 }
1920
1921         image_background_set_color(imd, color);
1922 }
1923
1924 void image_color_profile_set(ImageWindow *imd,
1925                              gint input_type,
1926                              gboolean use_image)
1927 {
1928         if (!imd) return;
1929
1930         if (input_type < 0 || input_type >= COLOR_PROFILE_FILE + COLOR_PROFILE_INPUTS)
1931                 {
1932                 return;
1933                 }
1934
1935         imd->color_profile_input = input_type;
1936         imd->color_profile_use_image = use_image;
1937 }
1938
1939 gboolean image_color_profile_get(ImageWindow *imd,
1940                                  gint *input_type,
1941                                  gboolean *use_image)
1942 {
1943         if (!imd) return FALSE;
1944
1945         if (input_type) *input_type = imd->color_profile_input;
1946         if (use_image) *use_image = imd->color_profile_use_image;
1947
1948         return TRUE;
1949 }
1950
1951 void image_color_profile_set_use(ImageWindow *imd, gboolean enable)
1952 {
1953         if (!imd) return;
1954
1955         if (imd->color_profile_enable == enable) return;
1956
1957         imd->color_profile_enable = enable;
1958 }
1959
1960 gboolean image_color_profile_get_use(ImageWindow *imd)
1961 {
1962         if (!imd) return FALSE;
1963
1964         return imd->color_profile_enable;
1965 }
1966
1967 gboolean image_color_profile_get_status(ImageWindow *imd, gchar **image_profile, gchar **screen_profile)
1968 {
1969         ColorMan *cm;
1970         if (!imd) return FALSE;
1971
1972         cm = static_cast<ColorMan *>(imd->cm);
1973         if (!cm) return FALSE;
1974         return color_man_get_status(cm, image_profile, screen_profile);
1975
1976 }
1977
1978 void image_set_delay_flip(ImageWindow *imd, gboolean delay)
1979 {
1980         if (!imd ||
1981             imd->delay_flip == delay) return;
1982
1983         imd->delay_flip = delay;
1984
1985         g_object_set(G_OBJECT(imd->pr), "delay_flip", delay, NULL);
1986
1987         if (!imd->delay_flip && imd->il)
1988                 {
1989                 PixbufRenderer *pr;
1990
1991                 pr = PIXBUF_RENDERER(imd->pr);
1992                 if (pr->pixbuf) g_object_unref(pr->pixbuf);
1993                 pr->pixbuf = nullptr;
1994
1995                 image_load_pixbuf_ready(imd);
1996                 }
1997 }
1998
1999 void image_to_root_window(ImageWindow *, gboolean)
2000 {
2001 }
2002
2003 void image_select(ImageWindow *imd, gboolean select)
2004 {
2005         if (!imd->has_frame) return;
2006
2007         if (select)
2008                 {
2009                 gtk_widget_set_state(imd->widget, GTK_STATE_SELECTED);
2010                 gtk_widget_set_state(imd->pr, GTK_STATE_NORMAL); /* do not propagate */
2011                 }
2012         else
2013                 gtk_widget_set_state(imd->widget, GTK_STATE_NORMAL);
2014 }
2015
2016 void image_set_selectable(ImageWindow *imd, gboolean selectable)
2017 {
2018         if (!imd->has_frame) return;
2019
2020         gq_gtk_frame_set_shadow_type(GTK_FRAME(imd->frame), GTK_SHADOW_NONE);
2021         gtk_container_set_border_width(GTK_CONTAINER(imd->frame), selectable ? 4 : 0);
2022 }
2023
2024 void image_grab_focus(ImageWindow *imd)
2025 {
2026         if (imd->has_frame)
2027                 {
2028                 gtk_widget_grab_focus(imd->frame);
2029                 }
2030         else
2031                 {
2032                 gtk_widget_grab_focus(imd->widget);
2033                 }
2034 }
2035
2036
2037 /*
2038  *-------------------------------------------------------------------
2039  * prefs sync
2040  *-------------------------------------------------------------------
2041  */
2042
2043 static void image_options_set(ImageWindow *imd)
2044 {
2045         g_object_set(G_OBJECT(imd->pr), "zoom_quality", options->image.zoom_quality,
2046                                         "zoom_2pass", options->image.zoom_2pass,
2047                                         "zoom_expand", options->image.zoom_to_fit_allow_expand,
2048                                         "scroll_reset", options->image.scroll_reset_method,
2049                                         "cache_display", options->image.tile_cache_max,
2050                                         "window_fit", (imd->top_window_sync && options->image.fit_window_to_image),
2051                                         "window_limit", options->image.limit_window_size,
2052                                         "window_limit_value", options->image.max_window_size,
2053                                         "autofit_limit", options->image.limit_autofit_size,
2054                                         "autofit_limit_value", options->image.max_autofit_size,
2055                                         "enlargement_limit_value", options->image.max_enlargement_size,
2056
2057                                         NULL);
2058
2059         pixbuf_renderer_set_parent(reinterpret_cast<PixbufRenderer *>(imd->pr), reinterpret_cast<GtkWindow *>(imd->top_window));
2060
2061         image_stereo_set(imd, options->stereo.mode);
2062         pixbuf_renderer_stereo_fixed_set(reinterpret_cast<PixbufRenderer *>(imd->pr),
2063                                         options->stereo.fixed_w, options->stereo.fixed_h,
2064                                         options->stereo.fixed_x1, options->stereo.fixed_y1,
2065                                         options->stereo.fixed_x2, options->stereo.fixed_y2);
2066 }
2067
2068 void image_options_sync()
2069 {
2070         GList *work;
2071
2072         work = image_list;
2073         while (work)
2074                 {
2075                 ImageWindow *imd;
2076
2077                 imd = static_cast<ImageWindow *>(work->data);
2078                 work = work->next;
2079
2080                 image_options_set(imd);
2081                 }
2082 }
2083
2084 /*
2085  *-------------------------------------------------------------------
2086  * init / destroy
2087  *-------------------------------------------------------------------
2088  */
2089
2090 static void image_free(ImageWindow *imd)
2091 {
2092         image_list = g_list_remove(image_list, imd);
2093
2094         if (imd->auto_refresh && imd->image_fd)
2095                 file_data_unregister_real_time_monitor(imd->image_fd);
2096
2097         file_data_unregister_notify_func(image_notify_cb, imd);
2098
2099         image_reset(imd);
2100
2101         image_read_ahead_cancel(imd);
2102
2103         file_data_unref(imd->image_fd);
2104         g_free(imd->title);
2105         g_free(imd->title_right);
2106         g_free(imd);
2107 }
2108
2109 static void image_destroy_cb(GtkWidget *, gpointer data)
2110 {
2111         auto imd = static_cast<ImageWindow *>(data);
2112         image_free(imd);
2113 }
2114
2115 gboolean selectable_frame_draw_cb(GtkWidget *widget, cairo_t *cr, gpointer)
2116 {
2117         GtkAllocation allocation;
2118         gtk_widget_get_allocation(widget, &allocation);
2119
2120         gtk_render_frame(gtk_widget_get_style_context(widget), cr, allocation.x + 3, allocation.y + 3, allocation.width - 6, allocation.height - 6);
2121         gtk_render_background(gtk_widget_get_style_context(widget), cr, allocation.x + 3, allocation.y + 3, allocation.width - 6, allocation.height - 6);
2122
2123         if (gtk_widget_has_focus(widget))
2124                 {
2125                 gtk_render_focus(gtk_widget_get_style_context(widget), cr, allocation.x, allocation.y, allocation.width - 1, allocation.height - 1);
2126                 }
2127         else
2128                 {
2129                 gtk_render_frame(gtk_widget_get_style_context(widget), cr, allocation.x, allocation.y, allocation.width - 1, allocation.height - 1);
2130                 }
2131         return FALSE;
2132 }
2133
2134 void image_set_frame(ImageWindow *imd, gboolean frame)
2135 {
2136         frame = !!frame;
2137
2138         if (frame == imd->has_frame) return;
2139
2140         gtk_widget_hide(imd->pr);
2141
2142         if (frame)
2143                 {
2144                 imd->frame = gtk_frame_new(nullptr);
2145                 DEBUG_NAME(imd->frame);
2146                 g_object_ref(imd->pr);
2147                 if (imd->has_frame != -1) gtk_container_remove(GTK_CONTAINER(imd->widget), imd->pr);
2148                 gq_gtk_container_add(GTK_WIDGET(imd->frame), imd->pr);
2149
2150                 g_object_unref(imd->pr);
2151                 gtk_widget_set_can_focus(imd->frame, TRUE);
2152                 gtk_widget_set_app_paintable(imd->frame, TRUE);
2153
2154                 g_signal_connect(G_OBJECT(imd->frame), "draw",
2155                                  G_CALLBACK(selectable_frame_draw_cb), NULL);
2156                 g_signal_connect(G_OBJECT(imd->frame), "focus_in_event",
2157                                  G_CALLBACK(image_focus_in_cb), imd);
2158
2159                 gq_gtk_box_pack_start(GTK_BOX(imd->widget), imd->frame, TRUE, TRUE, 0);
2160                 gtk_widget_show(imd->frame);
2161                 }
2162         else
2163                 {
2164                 g_object_ref(imd->pr);
2165                 if (imd->frame)
2166                         {
2167                         gtk_container_remove(GTK_CONTAINER(imd->frame), imd->pr);
2168                         g_object_unref(imd->frame);
2169                         imd->frame = nullptr;
2170                         }
2171                 gq_gtk_box_pack_start(GTK_BOX(imd->widget), imd->pr, TRUE, TRUE, 0);
2172
2173                 g_object_unref(imd->pr);
2174                 }
2175
2176         gtk_widget_show(imd->pr);
2177
2178         imd->has_frame = frame;
2179 }
2180
2181 ImageWindow *image_new(gboolean frame)
2182 {
2183         ImageWindow *imd;
2184
2185         imd = g_new0(ImageWindow, 1);
2186
2187         imd->unknown = TRUE;
2188         imd->has_frame = -1; /* not initialized; for image_set_frame */
2189         imd->delay_alter_type = ALTER_NONE;
2190         imd->state = IMAGE_STATE_NONE;
2191         imd->color_profile_from_image = COLOR_PROFILE_NONE;
2192         imd->orientation = 1;
2193
2194         imd->pr = GTK_WIDGET(pixbuf_renderer_new());
2195         DEBUG_NAME(imd->pr);
2196
2197         image_options_set(imd);
2198
2199         imd->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
2200         DEBUG_NAME(imd->widget);
2201
2202         image_set_frame(imd, frame);
2203
2204         image_set_selectable(imd, 0);
2205
2206         g_signal_connect(G_OBJECT(imd->pr), "clicked",
2207                          G_CALLBACK(image_click_cb), imd);
2208         g_signal_connect(G_OBJECT(imd->pr), "button_press_event",
2209                          G_CALLBACK(image_press_cb), imd);
2210         g_signal_connect(G_OBJECT(imd->pr), "button_release_event",
2211                          G_CALLBACK(image_release_cb), imd);
2212         g_signal_connect(G_OBJECT(imd->pr), "scroll_notify",
2213                          G_CALLBACK(image_scroll_notify_cb), imd);
2214
2215         g_signal_connect(G_OBJECT(imd->pr), "scroll_event",
2216                          G_CALLBACK(image_scroll_cb), imd);
2217
2218         g_signal_connect(G_OBJECT(imd->pr), "destroy",
2219                          G_CALLBACK(image_destroy_cb), imd);
2220
2221         g_signal_connect(G_OBJECT(imd->pr), "zoom",
2222                          G_CALLBACK(image_zoom_cb), imd);
2223         g_signal_connect(G_OBJECT(imd->pr), "render_complete",
2224                          G_CALLBACK(image_render_complete_cb), imd);
2225         g_signal_connect(G_OBJECT(imd->pr), "drag",
2226                          G_CALLBACK(image_drag_cb), imd);
2227
2228         file_data_register_notify_func(image_notify_cb, imd, NOTIFY_PRIORITY_LOW);
2229
2230         image_list = g_list_append(image_list, imd);
2231
2232         return imd;
2233 }
2234
2235 void image_get_rectangle(gint *x1, gint *y1, gint *x2, gint *y2)
2236 {
2237         *x1 = rect_x1;
2238         *y1 = rect_y1;
2239         *x2 = rect_x2;
2240         *y2 = rect_y2;
2241 }
2242
2243 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */