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