Fix missing translation
[geeqie.git] / src / image-load.cc
1 /*
2  * Copyright (C) 2004 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-load.h"
23
24 #include <config.h>
25
26 #include "debug.h"
27 #include "exif.h"
28 #include "filedata.h"
29 #include "gq-marshal.h"
30 #include "image-load-collection.h"
31 #include "image-load-dds.h"
32 #include "image-load-external.h"
33 #include "image-load-gdk.h"
34 #include "image-load-libraw.h"
35 #include "image-load-psd.h"
36 #include "image-load-svgz.h"
37 #include "image-load-webp.h"
38 #include "image-load-zxscr.h"
39 #include "misc.h"
40 #include "options.h"
41 #include "ui-fileops.h"
42
43 #ifdef HAVE_DJVU
44         #include "image-load-djvu.h"
45 #endif
46 #ifdef HAVE_FFMPEGTHUMBNAILER
47         #include "image-load-ffmpegthumbnailer.h"
48 #endif
49 #ifdef HAVE_HEIF
50         #include "image-load-heif.h"
51 #endif
52 #ifdef HAVE_J2K
53         #include "image-load-j2k.h"
54 #endif
55 #ifdef HAVE_JPEG
56         #include "image-load-cr3.h"
57         #include "image-load-jpeg.h"
58 #endif
59 #ifdef HAVE_JPEGXL
60         #include "image-load-jpegxl.h"
61 #endif
62 #ifdef HAVE_PDF
63         #include "image-load-pdf.h"
64 #endif
65 #ifdef HAVE_TIFF
66         #include "image-load-tiff.h"
67 #endif
68
69 #include <fcntl.h>
70 #include <sys/mman.h>
71
72 enum {
73         IMAGE_LOADER_READ_BUFFER_SIZE_DEFAULT =         4096,
74         IMAGE_LOADER_IDLE_READ_LOOP_COUNT_DEFAULT =     1
75 };
76
77 /* image loader class */
78
79
80 enum {
81         SIGNAL_AREA_READY = 0,
82         SIGNAL_ERROR,
83         SIGNAL_DONE,
84         SIGNAL_PERCENT,
85         SIGNAL_SIZE,
86         SIGNAL_COUNT
87 };
88
89 static guint signals[SIGNAL_COUNT] = { 0 };
90
91 static void image_loader_init(GTypeInstance *instance, gpointer g_class);
92 static void image_loader_class_init_wrapper(void *data, void *user_data);
93 static void image_loader_class_init(ImageLoaderClass *loader_class);
94 static void image_loader_finalize(GObject *object);
95 static void image_loader_stop(ImageLoader *il);
96
97 GType image_loader_get_type()
98 {
99         static GType type = 0;
100         if (type == 0)
101                 {
102                 static const GTypeInfo info = {
103                         sizeof(ImageLoaderClass),
104                         nullptr,   /* base_init */
105                         nullptr,   /* base_finalize */
106                         static_cast<GClassInitFunc>(image_loader_class_init_wrapper), /* class_init */
107                         nullptr,   /* class_finalize */
108                         nullptr,   /* class_data */
109                         sizeof(ImageLoader),
110                         0,      /* n_preallocs */
111                         static_cast<GInstanceInitFunc>(image_loader_init), /* instance_init */
112                         nullptr /* value_table */
113                         };
114                 type = g_type_register_static(G_TYPE_OBJECT, "ImageLoaderType", &info, static_cast<GTypeFlags>(0));
115                 }
116         return type;
117 }
118
119 static void image_loader_init(GTypeInstance *instance, gpointer)
120 {
121         auto il = reinterpret_cast<ImageLoader *>(instance);
122
123         il->pixbuf = nullptr;
124         il->idle_id = 0;
125         il->idle_priority = G_PRIORITY_DEFAULT_IDLE;
126         il->done = FALSE;
127         il->loader = nullptr;
128
129         il->bytes_read = 0;
130         il->bytes_total = 0;
131
132         il->idle_done_id = 0;
133
134         il->idle_read_loop_count = IMAGE_LOADER_IDLE_READ_LOOP_COUNT_DEFAULT;
135         il->read_buffer_size = IMAGE_LOADER_READ_BUFFER_SIZE_DEFAULT;
136         il->mapped_file = nullptr;
137         il->preview = IMAGE_LOADER_PREVIEW_NONE;
138
139         il->requested_width = 0;
140         il->requested_height = 0;
141         il->actual_width = 0;
142         il->actual_height = 0;
143         il->shrunk = FALSE;
144
145         il->can_destroy = TRUE;
146
147         il->data_mutex = g_new(GMutex, 1);
148         g_mutex_init(il->data_mutex);
149         il->can_destroy_cond = g_new(GCond, 1);
150         g_cond_init(il->can_destroy_cond);
151
152         DEBUG_1("new image loader %p, bufsize=%" G_GSIZE_FORMAT " idle_loop=%u", (void *)il, il->read_buffer_size, il->idle_read_loop_count);
153 }
154
155 static void image_loader_class_init_wrapper(void *data, void *)
156 {
157         image_loader_class_init(static_cast<ImageLoaderClass *>(data));
158 }
159
160 static void image_loader_class_init(ImageLoaderClass *loader_class)
161 {
162         GObjectClass *gobject_class = G_OBJECT_CLASS (loader_class);
163
164         gobject_class->finalize = image_loader_finalize;
165
166
167         signals[SIGNAL_AREA_READY] =
168                 g_signal_new("area_ready",
169                              G_OBJECT_CLASS_TYPE(gobject_class),
170                              G_SIGNAL_RUN_LAST,
171                              G_STRUCT_OFFSET(ImageLoaderClass, area_ready),
172                              nullptr, nullptr,
173                              gq_marshal_VOID__INT_INT_INT_INT,
174                              G_TYPE_NONE, 4,
175                              G_TYPE_INT,
176                              G_TYPE_INT,
177                              G_TYPE_INT,
178                              G_TYPE_INT);
179
180         signals[SIGNAL_ERROR] =
181                 g_signal_new("error",
182                              G_OBJECT_CLASS_TYPE(gobject_class),
183                              G_SIGNAL_RUN_LAST,
184                              G_STRUCT_OFFSET(ImageLoaderClass, error),
185                              nullptr, nullptr,
186                              g_cclosure_marshal_VOID__VOID,
187                              G_TYPE_NONE, 0);
188
189         signals[SIGNAL_DONE] =
190                 g_signal_new("done",
191                              G_OBJECT_CLASS_TYPE(gobject_class),
192                              G_SIGNAL_RUN_LAST,
193                              G_STRUCT_OFFSET(ImageLoaderClass, done),
194                              nullptr, nullptr,
195                              g_cclosure_marshal_VOID__VOID,
196                              G_TYPE_NONE, 0);
197
198         signals[SIGNAL_PERCENT] =
199                 g_signal_new("percent",
200                              G_OBJECT_CLASS_TYPE(gobject_class),
201                              G_SIGNAL_RUN_LAST,
202                              G_STRUCT_OFFSET(ImageLoaderClass, percent),
203                              nullptr, nullptr,
204                              g_cclosure_marshal_VOID__DOUBLE,
205                              G_TYPE_NONE, 1,
206                              G_TYPE_DOUBLE);
207
208         signals[SIGNAL_SIZE] =
209                 g_signal_new("size_prepared",
210                              G_OBJECT_CLASS_TYPE(gobject_class),
211                              G_SIGNAL_RUN_LAST,
212                              G_STRUCT_OFFSET(ImageLoaderClass, area_ready),
213                              nullptr, nullptr,
214                              gq_marshal_VOID__INT_INT,
215                              G_TYPE_NONE, 2,
216                              G_TYPE_INT,
217                              G_TYPE_INT);
218
219 }
220
221 static void image_loader_finalize(GObject *object)
222 {
223         auto il = reinterpret_cast<ImageLoader *>(object);
224
225         image_loader_stop(il);
226
227         if (il->error) DEBUG_1("%s", image_loader_get_error(il));
228
229         DEBUG_1("freeing image loader %p bytes_read=%" G_GSIZE_FORMAT, (void *)il, il->bytes_read);
230
231         if (il->idle_done_id)
232                 {
233                 g_source_remove(il->idle_done_id);
234                 il->idle_done_id = 0;
235                 }
236
237         while (g_source_remove_by_user_data(il))
238                 {
239                 DEBUG_2("pending signals detected");
240                 }
241
242         while (il->area_param_list)
243                 {
244                 DEBUG_1("pending area_ready signals detected");
245                 while (g_source_remove_by_user_data(il->area_param_list->data)) {}
246                 g_free(il->area_param_list->data);
247                 il->area_param_list = g_list_delete_link(il->area_param_list, il->area_param_list);
248                 }
249
250         while (il->area_param_delayed_list)
251                 {
252                 g_free(il->area_param_delayed_list->data);
253                 il->area_param_delayed_list = g_list_delete_link(il->area_param_delayed_list, il->area_param_delayed_list);
254                 }
255
256         if (il->pixbuf) g_object_unref(il->pixbuf);
257
258         if (il->error) g_error_free(il->error);
259
260         file_data_unref(il->fd);
261
262         g_mutex_clear(il->data_mutex);
263         g_free(il->data_mutex);
264         g_cond_clear(il->can_destroy_cond);
265         g_free(il->can_destroy_cond);
266 }
267
268 void image_loader_free(ImageLoader *il)
269 {
270         if (!il) return;
271         g_object_unref(G_OBJECT(il));
272 }
273
274
275 ImageLoader *image_loader_new(FileData *fd)
276 {
277         ImageLoader *il;
278
279         if (!fd) return nullptr;
280
281         il = static_cast<ImageLoader *>(g_object_new(TYPE_IMAGE_LOADER, nullptr));
282
283         il->fd = file_data_ref(fd);
284
285         return il;
286 }
287
288 /**************************************************************************************/
289 /* send signals via idle calbacks - the callback are executed in the main thread */
290
291 struct ImageLoaderAreaParam {
292         ImageLoader *il;
293         guint x;
294         guint y;
295         guint w;
296         guint h;
297 };
298
299
300 static gboolean image_loader_emit_area_ready_cb(gpointer data)
301 {
302         auto par = static_cast<ImageLoaderAreaParam *>(data);
303         ImageLoader *il = par->il;
304         guint x;
305         guint y;
306         guint w;
307         guint h;
308         g_mutex_lock(il->data_mutex);
309         il->area_param_list = g_list_remove(il->area_param_list, par);
310         x = par->x;
311         y = par->y;
312         w = par->w;
313         h = par->h;
314         g_free(par);
315         g_mutex_unlock(il->data_mutex);
316
317         g_signal_emit(il, signals[SIGNAL_AREA_READY], 0, x, y, w, h);
318
319         return G_SOURCE_REMOVE;
320 }
321
322 static gboolean image_loader_emit_done_cb(gpointer data)
323 {
324         auto il = static_cast<ImageLoader *>(data);
325         g_signal_emit(il, signals[SIGNAL_DONE], 0);
326         return G_SOURCE_REMOVE;
327 }
328
329 static gboolean image_loader_emit_error_cb(gpointer data)
330 {
331         auto il = static_cast<ImageLoader *>(data);
332         g_signal_emit(il, signals[SIGNAL_ERROR], 0);
333         return G_SOURCE_REMOVE;
334 }
335
336 static gboolean image_loader_emit_percent_cb(gpointer data)
337 {
338         auto il = static_cast<ImageLoader *>(data);
339         g_signal_emit(il, signals[SIGNAL_PERCENT], 0, image_loader_get_percent(il));
340         return G_SOURCE_REMOVE;
341 }
342
343 static gboolean image_loader_emit_size_cb(gpointer data)
344 {
345         gint width;
346         gint height;
347         auto il = static_cast<ImageLoader *>(data);
348         g_mutex_lock(il->data_mutex);
349         width = il->actual_width;
350         height = il->actual_height;
351         g_mutex_unlock(il->data_mutex);
352         g_signal_emit(il, signals[SIGNAL_SIZE], 0, width, height);
353         return G_SOURCE_REMOVE;
354 }
355
356
357 /* DONE and ERROR are emitted only once, thus they can have normal priority
358    PERCENT and AREA_READY should be processed ASAP
359 */
360
361 static void image_loader_emit_done(ImageLoader *il)
362 {
363         g_idle_add_full(il->idle_priority, image_loader_emit_done_cb, il, nullptr);
364 }
365
366 static void image_loader_emit_error(ImageLoader *il)
367 {
368         g_idle_add_full(il->idle_priority, image_loader_emit_error_cb, il, nullptr);
369 }
370
371 static void image_loader_emit_percent(ImageLoader *il)
372 {
373         g_idle_add_full(G_PRIORITY_HIGH, image_loader_emit_percent_cb, il, nullptr);
374 }
375
376 static void image_loader_emit_size(ImageLoader *il)
377 {
378         g_idle_add_full(G_PRIORITY_HIGH, image_loader_emit_size_cb, il, nullptr);
379 }
380
381 static ImageLoaderAreaParam *image_loader_queue_area_ready(ImageLoader *il, GList **list, guint x, guint y, guint w, guint h)
382 {
383         if (*list)
384                 {
385                 auto prev_par = static_cast<ImageLoaderAreaParam *>((*list)->data);
386                 if (prev_par->x == x && prev_par->w == w &&
387                     prev_par->y + prev_par->h == y)
388                         {
389                         /* we can merge the notifications */
390                         prev_par->h += h;
391                         return nullptr;
392                         }
393                 if (prev_par->x == x && prev_par->w == w &&
394                     y + h == prev_par->y)
395                         {
396                         /* we can merge the notifications */
397                         prev_par->h += h;
398                         prev_par->y = y;
399                         return nullptr;
400                         }
401                 if (prev_par->y == y && prev_par->h == h &&
402                     prev_par->x + prev_par->w == x)
403                         {
404                         /* we can merge the notifications */
405                         prev_par->w += w;
406                         return nullptr;
407                         }
408                 if (prev_par->y == y && prev_par->h == h &&
409                     x + w == prev_par->x)
410                         {
411                         /* we can merge the notifications */
412                         prev_par->w += w;
413                         prev_par->x = x;
414                         return nullptr;
415                         }
416                 }
417
418         auto par = g_new0(ImageLoaderAreaParam, 1);
419         par->il = il;
420         par->x = x;
421         par->y = y;
422         par->w = w;
423         par->h = h;
424
425         *list = g_list_prepend(*list, par);
426         return par;
427 }
428
429 /* this function expects that il->data_mutex is locked by caller */
430 static void image_loader_emit_area_ready(ImageLoader *il, guint x, guint y, guint w, guint h)
431 {
432         ImageLoaderAreaParam *par = image_loader_queue_area_ready(il, &il->area_param_list, x, y, w, h);
433
434         if (par)
435                 {
436                 g_idle_add_full(G_PRIORITY_HIGH, image_loader_emit_area_ready_cb, par, nullptr);
437                 }
438 }
439
440 /**************************************************************************************/
441 /* the following functions may be executed in separate thread */
442
443 /* this function expects that il->data_mutex is locked by caller */
444 static void image_loader_queue_delayed_area_ready(ImageLoader *il, guint x, guint y, guint w, guint h)
445 {
446         image_loader_queue_area_ready(il, &il->area_param_delayed_list, x, y, w, h);
447 }
448
449
450
451 static gboolean image_loader_get_stopping(ImageLoader *il)
452 {
453         gboolean ret;
454         if (!il) return FALSE;
455
456         g_mutex_lock(il->data_mutex);
457         ret = il->stopping;
458         g_mutex_unlock(il->data_mutex);
459
460         return ret;
461 }
462
463
464 static void image_loader_sync_pixbuf(ImageLoader *il)
465 {
466         GdkPixbuf *pb;
467
468         g_mutex_lock(il->data_mutex);
469
470         if (!il->loader)
471                 {
472                 g_mutex_unlock(il->data_mutex);
473                 return;
474                 }
475
476         pb = il->backend.get_pixbuf(il->loader);
477
478         if (pb == il->pixbuf)
479                 {
480                 g_mutex_unlock(il->data_mutex);
481                 return;
482                 }
483
484         if (g_ascii_strcasecmp(".jps", il->fd->extension) == 0)
485                 {
486                 g_object_set_data(G_OBJECT(pb), "stereo_data", GINT_TO_POINTER(STEREO_PIXBUF_CROSS));
487                 }
488
489         if (il->pixbuf) g_object_unref(il->pixbuf);
490
491         il->pixbuf = pb;
492         if (il->pixbuf) g_object_ref(il->pixbuf);
493
494         g_mutex_unlock(il->data_mutex);
495 }
496
497 static void image_loader_area_updated_cb(gpointer,
498                                  guint x, guint y, guint w, guint h,
499                                  gpointer data)
500 {
501         auto il = static_cast<ImageLoader *>(data);
502
503         if (!image_loader_get_pixbuf(il))
504                 {
505                 image_loader_sync_pixbuf(il);
506                 if (!image_loader_get_pixbuf(il))
507                         {
508                         log_printf("critical: area_ready signal with NULL pixbuf (out of mem?)\n");
509                         }
510                 }
511
512         g_mutex_lock(il->data_mutex);
513         if (il->delay_area_ready)
514                 image_loader_queue_delayed_area_ready(il, x, y, w, h);
515         else
516                 image_loader_emit_area_ready(il, x, y, w, h);
517
518         if (il->stopping) il->backend.abort(il->loader);
519
520         g_mutex_unlock(il->data_mutex);
521 }
522
523 static void image_loader_area_prepared_cb(gpointer loader, gpointer data)
524 {
525         auto il = static_cast<ImageLoader *>(data);
526         GdkPixbuf *pb;
527         guchar *pix;
528         size_t h;
529         size_t rs;
530
531         /* a workaround for
532            https://bugzilla.gnome.org/show_bug.cgi?id=547669
533            https://bugzilla.gnome.org/show_bug.cgi?id=589334
534         */
535         gchar *format = il->backend.get_format_name(loader);
536         if (strcmp(format, "svg") == 0 ||
537             strcmp(format, "xpm") == 0)
538                 {
539                 g_free(format);
540                 return;
541                 }
542
543         g_free(format);
544
545         pb = il->backend.get_pixbuf(loader);
546
547         h = gdk_pixbuf_get_height(pb);
548         rs = gdk_pixbuf_get_rowstride(pb);
549         pix = gdk_pixbuf_get_pixels(pb);
550
551         memset(pix, 0, rs * h); /*this should be faster than pixbuf_fill */
552
553 }
554
555 static void image_loader_size_cb(gpointer loader,
556                                  gint width, gint height, gpointer data)
557 {
558         auto il = static_cast<ImageLoader *>(data);
559         gchar **mime_types;
560         gboolean scale = FALSE;
561         gint n;
562
563         g_mutex_lock(il->data_mutex);
564         il->actual_width = width;
565         il->actual_height = height;
566         if (il->requested_width < 1 || il->requested_height < 1)
567                 {
568                 g_mutex_unlock(il->data_mutex);
569                 image_loader_emit_size(il);
570                 return;
571                 }
572         g_mutex_unlock(il->data_mutex);
573
574 #ifdef HAVE_FFMPEGTHUMBNAILER
575         if (il->fd->format_class == FORMAT_CLASS_VIDEO)
576                 scale = TRUE;
577 #endif
578         mime_types = il->backend.get_format_mime_types(loader);
579         n = 0;
580         while (mime_types[n] && !scale)
581                 {
582                 if (strstr(mime_types[n], "jpeg")) scale = TRUE;
583                 n++;
584                 }
585         g_strfreev(mime_types);
586
587         if (!scale)
588                 {
589                 image_loader_emit_size(il);
590                 return;
591                 }
592
593         g_mutex_lock(il->data_mutex);
594
595         gint nw;
596         gint nh;
597         if (width > il->requested_width || height > il->requested_height)
598                 {
599
600                 if ((static_cast<gdouble>(il->requested_width) / width) < (static_cast<gdouble>(il->requested_height) / height))
601                         {
602                         nw = il->requested_width;
603                         nh = static_cast<gdouble>(nw) / width * height;
604                         if (nh < 1) nh = 1;
605                         }
606                 else
607                         {
608                         nh = il->requested_height;
609                         nw = static_cast<gdouble>(nh) / height * width;
610                         if (nw < 1) nw = 1;
611                         }
612
613                 il->actual_width = nw;
614                 il->actual_height = nh;
615                 il->backend.set_size(loader, nw, nh);
616                 il->shrunk = TRUE;
617                 }
618
619         g_mutex_unlock(il->data_mutex);
620         image_loader_emit_size(il);
621 }
622
623 static void image_loader_stop_loader(ImageLoader *il)
624 {
625         if (!il) return;
626
627         if (il->loader)
628                 {
629                 /* some loaders do not have a pixbuf till close, order is important here */
630                 il->backend.close(il->loader, il->error ? nullptr : &il->error); /* we are interested in the first error only */
631                 image_loader_sync_pixbuf(il);
632                 il->backend.free(il->loader);
633                 il->loader = nullptr;
634                 }
635         g_mutex_lock(il->data_mutex);
636         il->done = TRUE;
637         g_mutex_unlock(il->data_mutex);
638 }
639
640 static void image_loader_setup_loader(ImageLoader *il)
641 {
642 #if defined HAVE_TIFF || defined HAVE_PDF || defined HAVE_HEIF || defined HAVE_DJVU
643         gchar *format;
644 #endif
645
646         gint external_preview = 1;
647
648         g_mutex_lock(il->data_mutex);
649
650         if (options->external_preview.enable)
651                 {
652                 gchar *cmd_line;
653                 gchar *tilde_filename;
654
655                 tilde_filename = expand_tilde(options->external_preview.select);
656
657                 cmd_line = g_strdup_printf("\"%s\" \"%s\"" , tilde_filename, il->fd->path);
658
659                 external_preview = runcmd(cmd_line);
660                 g_free(cmd_line);
661                 g_free(tilde_filename);
662                 }
663
664         if (external_preview == 0)
665                 {
666                 DEBUG_1("Using custom external loader");
667                 image_loader_backend_set_external(&il->backend);
668                 }
669         else
670                 {
671 #ifdef HAVE_FFMPEGTHUMBNAILER
672                 if (il->fd->format_class == FORMAT_CLASS_VIDEO)
673                         {
674                         DEBUG_1("Using custom ffmpegthumbnailer loader");
675                         image_loader_backend_set_ft(&il->backend);
676                         }
677                 else
678 #endif
679 #ifdef HAVE_PDF
680                 if (il->bytes_total >= 4 &&
681                     (memcmp(il->mapped_file + 0, "%PDF", 4) == 0))
682                         {
683                         DEBUG_1("Using custom pdf loader");
684                         image_loader_backend_set_pdf(&il->backend);
685                         }
686                 else
687 #endif
688 #ifdef HAVE_HEIF
689                 if (il->bytes_total >= 12 &&
690                         ((memcmp(il->mapped_file + 4, "ftypheic", 8) == 0) ||
691                         (memcmp(il->mapped_file + 4, "ftypheix", 8) == 0) ||
692                         (memcmp(il->mapped_file + 4, "ftypmsf1", 8) == 0) ||
693                         (memcmp(il->mapped_file + 4, "ftypmif1", 8) == 0) ||
694                         (memcmp(il->mapped_file + 4, "ftypavif", 8) == 0)))
695                         {
696                         DEBUG_1("Using custom heif loader");
697                         image_loader_backend_set_heif(&il->backend);
698                         }
699                 else
700         #endif
701         #ifdef HAVE_WEBP
702                 if (il->bytes_total >= 12 &&
703                         (memcmp(il->mapped_file, "RIFF", 4) == 0) &&
704                         (memcmp(il->mapped_file + 8, "WEBP", 4) == 0))
705                         {
706                         DEBUG_1("Using custom webp loader");
707                         image_loader_backend_set_webp(&il->backend);
708                         }
709                 else
710 #endif
711 #ifdef HAVE_DJVU
712                 if (il->bytes_total >= 16 &&
713                         (memcmp(il->mapped_file, "AT&TFORM", 8) == 0) &&
714                         (memcmp(il->mapped_file + 12, "DJV", 3) == 0))
715                         {
716                         DEBUG_1("Using custom djvu loader");
717                         image_loader_backend_set_djvu(&il->backend);
718                         }
719                 else
720 #endif
721 #ifdef HAVE_JPEG
722                 if (il->bytes_total >= 2 && il->mapped_file[0] == 0xff && il->mapped_file[1] == 0xd8)
723                         {
724                         DEBUG_1("Using custom jpeg loader");
725                         image_loader_backend_set_jpeg(&il->backend);
726                         }
727 #ifndef HAVE_RAW
728                 else
729                 if (il->bytes_total >= 11 &&
730                         (memcmp(il->mapped_file + 4, "ftypcrx", 7) == 0) &&
731                         (memcmp(il->mapped_file + 64, "CanonCR3", 8) == 0))
732                         {
733                         DEBUG_1("Using custom cr3 loader");
734                         image_loader_backend_set_cr3(&il->backend);
735                         }
736 #endif
737                 else
738 #endif
739 #ifdef HAVE_TIFF
740                 if (il->bytes_total >= 10 &&
741                     (memcmp(il->mapped_file, "MM\0*", 4) == 0 ||
742                      memcmp(il->mapped_file, "MM\0+\0\x08\0\0", 8) == 0 ||
743                      memcmp(il->mapped_file, "II+\0\x08\0\0\0", 8) == 0 ||
744                      memcmp(il->mapped_file, "II*\0", 4) == 0))
745                         {
746                         DEBUG_1("Using custom tiff loader");
747                         image_loader_backend_set_tiff(&il->backend);
748                         }
749                 else
750 #endif
751                 if (il->bytes_total >= 3 && il->mapped_file[0] == 0x44 && il->mapped_file[1] == 0x44 && il->mapped_file[2] == 0x53)
752                         {
753                         DEBUG_1("Using dds loader");
754                         image_loader_backend_set_dds(&il->backend);
755                         }
756                 else
757                 if (il->bytes_total >= 6 &&
758                         (memcmp(il->mapped_file, "8BPS\0\x01", 6) == 0))
759                         {
760                         DEBUG_1("Using custom psd loader");
761                         image_loader_backend_set_psd(&il->backend);
762                         }
763                 else
764 #ifdef HAVE_J2K
765                 if (il->bytes_total >= 12 &&
766                         (memcmp(il->mapped_file, "\0\0\0\x0CjP\x20\x20\x0D\x0A\x87\x0A", 12) == 0))
767                         {
768                         DEBUG_1("Using custom j2k loader");
769                         image_loader_backend_set_j2k(&il->backend);
770                         }
771                 else
772 #endif
773 #ifdef HAVE_JPEGXL
774                 if (il->bytes_total >= 12 &&
775                         (memcmp(il->mapped_file, "\0\0\0\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", 12) == 0))
776                         {
777                         DEBUG_1("Using custom jpeg xl loader");
778                         image_loader_backend_set_jpegxl(&il->backend);
779                         }
780                 else
781                 if (il->bytes_total >= 2 &&
782                         (memcmp(il->mapped_file, "\xFF\x0A", 2) == 0))
783                         {
784                         DEBUG_1("Using custom jpeg xl loader");
785                         image_loader_backend_set_jpegxl(&il->backend);
786                         }
787                 else
788 #endif
789                 if ((il->bytes_total == 6144 || il->bytes_total == 6912) &&
790                         (file_extension_match(il->fd->path, ".scr")))
791                         {
792                         DEBUG_1("Using custom zxscr loader");
793                         image_loader_backend_set_zxscr(&il->backend);
794                         }
795                 else
796                 if (il->fd->format_class == FORMAT_CLASS_COLLECTION)
797                         {
798                         DEBUG_1("Using custom collection loader");
799                         image_loader_backend_set_collection(&il->backend);
800                         }
801                 else
802                 if (g_strcmp0(strrchr(il->fd->path, '.'), ".svgz") == 0)
803                         {
804                         DEBUG_1("Using custom svgz loader");
805                         image_loader_backend_set_svgz(&il->backend);
806                         }
807                 else
808                         image_loader_backend_set_default(&il->backend);
809                 }
810
811         il->loader = static_cast<void **>(il->backend.loader_new(image_loader_area_updated_cb, image_loader_size_cb, image_loader_area_prepared_cb, il));
812
813 #ifdef HAVE_TIFF
814         format = il->backend.get_format_name(il->loader);
815         if (g_strcmp0(format, "tiff") == 0)
816                 {
817                 il->backend.set_page_num(il->loader, il->fd->page_num);
818                 }
819         g_free(format);
820 #endif
821
822 #ifdef HAVE_PDF
823         format = il->backend.get_format_name(il->loader);
824         if (g_strcmp0(format, "pdf") == 0)
825                 {
826                 il->backend.set_page_num(il->loader, il->fd->page_num);
827                 }
828         g_free(format);
829 #endif
830
831 #ifdef HAVE_HEIF
832         format = il->backend.get_format_name(il->loader);
833         if (g_strcmp0(format, "heif") == 0)
834                 {
835                 il->backend.set_page_num(il->loader, il->fd->page_num);
836                 }
837         g_free(format);
838 #endif
839
840 #ifdef HAVE_DJVU
841         format = il->backend.get_format_name(il->loader);
842         if (g_strcmp0(format, "djvu") == 0)
843                 {
844                 il->backend.set_page_num(il->loader, il->fd->page_num);
845                 }
846         g_free(format);
847 #endif
848
849         il->fd->format_name = il->backend.get_format_name(il->loader);
850
851         g_mutex_unlock(il->data_mutex);
852 }
853
854
855 static void image_loader_done(ImageLoader *il)
856 {
857         image_loader_stop_loader(il);
858
859         image_loader_emit_done(il);
860 }
861
862 static void image_loader_error(ImageLoader *il)
863 {
864         image_loader_stop_loader(il);
865
866         DEBUG_1("pixbuf_loader reported load error for: %s", il->fd->path);
867
868         image_loader_emit_error(il);
869 }
870
871 static gboolean image_loader_continue(ImageLoader *il)
872 {
873         gint b;
874         gint c;
875
876         if (!il) return G_SOURCE_REMOVE;
877
878         c = il->idle_read_loop_count ? il->idle_read_loop_count : 1;
879         while (c > 0 && !image_loader_get_stopping(il))
880                 {
881                 b = MIN(il->read_buffer_size, il->bytes_total - il->bytes_read);
882
883                 if (b == 0)
884                         {
885                         image_loader_done(il);
886                         return G_SOURCE_REMOVE;
887                         }
888
889                 if (b < 0 || (b > 0 && !il->backend.write(il->loader, il->mapped_file + il->bytes_read, b, &il->error)))
890                         {
891                         image_loader_error(il);
892                         return G_SOURCE_REMOVE;
893                         }
894
895                 il->bytes_read += b;
896
897                 c--;
898                 }
899
900         if (il->bytes_total > 0)
901                 {
902                 image_loader_emit_percent(il);
903                 }
904
905         return G_SOURCE_CONTINUE;
906 }
907
908 static gboolean image_loader_begin(ImageLoader *il)
909 {
910 #if defined HAVE_TIFF || defined HAVE_PDF || defined HAVE_HEIF || defined HAVE_DJVU
911         gchar *format;
912 #endif
913         gssize b;
914
915         if (il->pixbuf) return FALSE;
916
917         b = MIN(il->read_buffer_size, il->bytes_total - il->bytes_read);
918         if (b < 1) return FALSE;
919
920         image_loader_setup_loader(il);
921
922         g_assert(il->bytes_read == 0);
923         if (il->backend.load) {
924                 b = il->bytes_total;
925                 if (!il->backend.load(il->loader, il->mapped_file, b, &il->error))
926                         {
927                         image_loader_stop_loader(il);
928                         return FALSE;
929                         }
930         }
931         else if (!il->backend.write(il->loader, il->mapped_file, b, &il->error))
932                 {
933                 image_loader_stop_loader(il);
934                 return FALSE;
935                 }
936
937 #ifdef HAVE_PDF
938         format = il->backend.get_format_name(il->loader);
939         if (g_strcmp0(format, "pdf") == 0)
940                 {
941                 gint i = il->backend.get_page_total(il->loader);
942                 file_data_set_page_total(il->fd, i);
943                 }
944         g_free(format);
945 #endif
946 #ifdef HAVE_HEIF
947         format = il->backend.get_format_name(il->loader);
948         if (g_strcmp0(format, "heif") == 0)
949                 {
950                 gint i = il->backend.get_page_total(il->loader);
951                 file_data_set_page_total(il->fd, i);
952                 }
953         g_free(format);
954 #endif
955 #ifdef HAVE_DJVU
956         format = il->backend.get_format_name(il->loader);
957         if (g_strcmp0(format, "djvu") == 0)
958                 {
959                 gint i = il->backend.get_page_total(il->loader);
960                 file_data_set_page_total(il->fd, i);
961                 }
962         g_free(format);
963 #endif
964 #ifdef HAVE_TIFF
965         format = il->backend.get_format_name(il->loader);
966         if (g_strcmp0(format, "tiff") == 0)
967                 {
968                 gint i = il->backend.get_page_total(il->loader);
969                 file_data_set_page_total(il->fd, i);
970                 }
971         g_free(format);
972 #endif
973
974         il->bytes_read += b;
975
976         /* read until size is known */
977         while (il->loader && !il->backend.get_pixbuf(il->loader) && b > 0 && !image_loader_get_stopping(il))
978                 {
979                 b = MIN(il->read_buffer_size, il->bytes_total - il->bytes_read);
980                 if (b < 0 || (b > 0 && !il->backend.write(il->loader, il->mapped_file + il->bytes_read, b, &il->error)))
981                         {
982                         image_loader_stop_loader(il);
983                         return FALSE;
984                         }
985                 il->bytes_read += b;
986                 }
987         if (!il->pixbuf) image_loader_sync_pixbuf(il);
988
989         if (il->bytes_read == il->bytes_total || b < 1)
990                 {
991                 /* done, handle (broken) loaders that do not have pixbuf till close */
992                 image_loader_stop_loader(il);
993
994                 if (!il->pixbuf) return FALSE;
995
996                 image_loader_done(il);
997                 return TRUE;
998                 }
999
1000         if (!il->pixbuf)
1001                 {
1002                 image_loader_stop_loader(il);
1003                 return FALSE;
1004                 }
1005
1006         return TRUE;
1007 }
1008
1009 /**************************************************************************************/
1010 /* the following functions are always executed in the main thread */
1011
1012
1013 static gboolean image_loader_setup_source(ImageLoader *il)
1014 {
1015         struct stat st;
1016         gchar *pathl;
1017
1018         if (!il || il->loader || il->mapped_file) return FALSE;
1019
1020         il->mapped_file = nullptr;
1021
1022         if (il->fd)
1023                 {
1024                 ExifData *exif = exif_read_fd(il->fd);
1025
1026                 if (options->thumbnails.use_exif)
1027                         {
1028                         il->mapped_file = exif_get_preview(exif, reinterpret_cast<guint *>(&il->bytes_total), il->requested_width, il->requested_height);
1029
1030                         if (il->mapped_file)
1031                                 {
1032                                 il->preview = IMAGE_LOADER_PREVIEW_EXIF;
1033                                 }
1034                         }
1035                 else
1036                         {
1037                         il->mapped_file = libraw_get_preview(il, reinterpret_cast<guint *>(&il->bytes_total));
1038
1039                         if (il->mapped_file)
1040                                 {
1041                                 /* Both exiv2 and libraw sometimes return a pointer to a file
1042                                  * section that is not a jpeg */
1043                                 if (il->mapped_file[0] != 0xFF || il->mapped_file[1] != 0xD8)
1044                                         {
1045                                         il->mapped_file = nullptr;
1046                                         }
1047                                 else
1048                                         {
1049                                         il->preview = IMAGE_LOADER_PREVIEW_LIBRAW;
1050                                         }
1051                                 }
1052                         }
1053
1054                 /* If libraw does not find a thumbnail, try exiv2 */
1055                 if (!il->mapped_file)
1056                         {
1057                         il->mapped_file = exif_get_preview(exif, reinterpret_cast<guint *>(&il->bytes_total), 0, 0); /* get the largest available preview image or NULL for normal images*/
1058
1059                         if (il->mapped_file)
1060                                 {
1061                                 /* Both exiv2 and libraw sometimes return a pointer to a file
1062                                  * section that is not a jpeg */
1063                                 if (il->mapped_file[0] != 0xFF || il->mapped_file[1] != 0xD8)
1064                                         {
1065                                         il->mapped_file = nullptr;
1066                                         }
1067                                 else
1068                                         {
1069                                         il->preview = IMAGE_LOADER_PREVIEW_EXIF;
1070                                         }
1071                                 }
1072                         }
1073
1074                 if (il->mapped_file)
1075                         {
1076                         DEBUG_1("Usable reduced size (preview) image loaded from file %s", il->fd->path);
1077                         }
1078                 exif_free_fd(il->fd, exif);
1079                 }
1080
1081
1082         if (!il->mapped_file)
1083                 {
1084                 /* normal file */
1085                 gint load_fd;
1086
1087                 pathl = path_from_utf8(il->fd->path);
1088                 load_fd = open(pathl, O_RDONLY | O_NONBLOCK);
1089                 g_free(pathl);
1090                 if (load_fd == -1) return FALSE;
1091
1092                 if (fstat(load_fd, &st) == 0)
1093                         {
1094                         il->bytes_total = st.st_size;
1095                         }
1096                 else
1097                         {
1098                         close(load_fd);
1099                         return FALSE;
1100                         }
1101
1102                 il->mapped_file = static_cast<guchar *>(mmap(nullptr, il->bytes_total, PROT_READ|PROT_WRITE, MAP_PRIVATE, load_fd, 0));
1103                 close(load_fd);
1104                 if (il->mapped_file == MAP_FAILED)
1105                         {
1106                         il->mapped_file = nullptr;
1107                         return FALSE;
1108                         }
1109                 il->preview = IMAGE_LOADER_PREVIEW_NONE;
1110                 }
1111
1112         return TRUE;
1113 }
1114
1115 static void image_loader_stop_source(ImageLoader *il)
1116 {
1117         if (!il) return;
1118
1119         if (il->mapped_file)
1120                 {
1121                 if (il->preview == IMAGE_LOADER_PREVIEW_EXIF)
1122                         {
1123                         exif_free_preview(il->mapped_file);
1124                         }
1125                 else if (il->preview == IMAGE_LOADER_PREVIEW_LIBRAW)
1126                         {
1127                         libraw_free_preview(il->mapped_file);
1128                         }
1129                 else
1130                         {
1131                         munmap(il->mapped_file, il->bytes_total);
1132                         }
1133                 il->mapped_file = nullptr;
1134                 }
1135 }
1136
1137 static void image_loader_stop(ImageLoader *il)
1138 {
1139         if (!il) return;
1140
1141         if (il->idle_id)
1142                 {
1143                 g_source_remove(il->idle_id);
1144                 il->idle_id = 0;
1145                 }
1146
1147         if (il->thread)
1148                 {
1149                 /* stop loader in the other thread */
1150                 g_mutex_lock(il->data_mutex);
1151                 il->stopping = TRUE;
1152                 while (!il->can_destroy) g_cond_wait(il->can_destroy_cond, il->data_mutex);
1153                 g_mutex_unlock(il->data_mutex);
1154                 }
1155
1156         image_loader_stop_loader(il);
1157         image_loader_stop_source(il);
1158
1159 }
1160
1161 void image_loader_delay_area_ready(ImageLoader *il, gboolean enable)
1162 {
1163         g_mutex_lock(il->data_mutex);
1164         il->delay_area_ready = enable;
1165         if (!enable)
1166                 {
1167                 /* send delayed */
1168                 GList *list;
1169                 GList *work;
1170                 list = g_list_reverse(il->area_param_delayed_list);
1171                 il->area_param_delayed_list = nullptr;
1172                 g_mutex_unlock(il->data_mutex);
1173
1174                 work = list;
1175
1176                 while (work)
1177                         {
1178                         auto par = static_cast<ImageLoaderAreaParam *>(work->data);
1179                         work = work->next;
1180
1181                         g_signal_emit(il, signals[SIGNAL_AREA_READY], 0, par->x, par->y, par->w, par->h);
1182                         }
1183                 g_list_free_full(list, g_free);
1184                 }
1185         else
1186                 {
1187                 /* just unlock */
1188                 g_mutex_unlock(il->data_mutex);
1189                 }
1190 }
1191
1192
1193 /**************************************************************************************/
1194 /* execution via idle calls */
1195
1196 static gboolean image_loader_idle_cb(gpointer data)
1197 {
1198         gboolean ret = G_SOURCE_REMOVE;
1199         auto il = static_cast<ImageLoader *>(data);
1200
1201         if (il->idle_id)
1202                 {
1203                 ret = image_loader_continue(il);
1204                 }
1205
1206         if (!ret)
1207                 {
1208                 image_loader_stop_source(il);
1209                 }
1210
1211         return ret;
1212 }
1213
1214 static gboolean image_loader_start_idle(ImageLoader *il)
1215 {
1216         gboolean ret;
1217
1218         if (!il) return FALSE;
1219
1220         if (!il->fd) return FALSE;
1221
1222         if (!image_loader_setup_source(il)) return FALSE;
1223
1224         ret = image_loader_begin(il);
1225
1226         if (ret && !il->done) il->idle_id = g_idle_add_full(il->idle_priority, image_loader_idle_cb, il, nullptr);
1227         return ret;
1228 }
1229
1230 /**************************************************************************************/
1231 /* execution via thread */
1232
1233 static GThreadPool *image_loader_thread_pool = nullptr;
1234
1235 static GCond *image_loader_prio_cond = nullptr;
1236 static GMutex *image_loader_prio_mutex = nullptr;
1237 static gint image_loader_prio_num = 0;
1238
1239
1240 static void image_loader_thread_enter_high()
1241 {
1242         g_mutex_lock(image_loader_prio_mutex);
1243         image_loader_prio_num++;
1244         g_mutex_unlock(image_loader_prio_mutex);
1245 }
1246
1247 static void image_loader_thread_leave_high()
1248 {
1249         g_mutex_lock(image_loader_prio_mutex);
1250         image_loader_prio_num--;
1251         if (image_loader_prio_num == 0) g_cond_broadcast(image_loader_prio_cond); /* wake up all low prio threads */
1252         g_mutex_unlock(image_loader_prio_mutex);
1253 }
1254
1255 static void image_loader_thread_wait_high()
1256 {
1257         g_mutex_lock(image_loader_prio_mutex);
1258         while (image_loader_prio_num)
1259                 {
1260                 g_cond_wait(image_loader_prio_cond, image_loader_prio_mutex);
1261                 }
1262
1263         g_mutex_unlock(image_loader_prio_mutex);
1264 }
1265
1266
1267 static void image_loader_thread_run(gpointer data, gpointer)
1268 {
1269         auto il = static_cast<ImageLoader *>(data);
1270         gboolean cont;
1271         gboolean err;
1272
1273         if (il->idle_priority > G_PRIORITY_DEFAULT_IDLE)
1274                 {
1275                 /* low prio, wait until high prio tasks finishes */
1276                 image_loader_thread_wait_high();
1277                 }
1278         else
1279                 {
1280                 /* high prio */
1281                 image_loader_thread_enter_high();
1282                 }
1283
1284         err = !image_loader_begin(il);
1285
1286         if (err)
1287                 {
1288                 /*
1289                 loader failed, we have to send signal
1290                 (idle mode returns the image_loader_begin return value directly)
1291                 (success is always reported indirectly from image_loader_begin)
1292                 */
1293                 image_loader_emit_error(il);
1294                 }
1295
1296         cont = !err;
1297
1298         while (cont && !image_loader_get_is_done(il) && !image_loader_get_stopping(il))
1299                 {
1300                 if (il->idle_priority > G_PRIORITY_DEFAULT_IDLE)
1301                         {
1302                         /* low prio, wait until high prio tasks finishes */
1303                         image_loader_thread_wait_high();
1304                         }
1305                 cont = image_loader_continue(il);
1306                 }
1307         image_loader_stop_loader(il);
1308
1309         if (il->idle_priority <= G_PRIORITY_DEFAULT_IDLE)
1310                 {
1311                 /* high prio */
1312                 image_loader_thread_leave_high();
1313                 }
1314
1315         g_mutex_lock(il->data_mutex);
1316         il->can_destroy = TRUE;
1317         g_cond_signal(il->can_destroy_cond);
1318         g_mutex_unlock(il->data_mutex);
1319
1320 }
1321
1322
1323 static gboolean image_loader_start_thread(ImageLoader *il)
1324 {
1325         if (!il) return FALSE;
1326
1327         if (!il->fd) return FALSE;
1328
1329         il->thread = TRUE;
1330
1331         if (!image_loader_setup_source(il)) return FALSE;
1332
1333         if (!image_loader_thread_pool)
1334                 {
1335                 image_loader_thread_pool = g_thread_pool_new(image_loader_thread_run, nullptr, -1, FALSE, nullptr);
1336                 if (!image_loader_prio_cond) image_loader_prio_cond = g_new(GCond, 1);
1337                 g_cond_init(image_loader_prio_cond);
1338                 if (!image_loader_prio_mutex) image_loader_prio_mutex = g_new(GMutex, 1);
1339                 g_mutex_init(image_loader_prio_mutex);
1340                 }
1341
1342         il->can_destroy = FALSE; /* ImageLoader can't be freed until image_loader_thread_run finishes */
1343
1344         g_thread_pool_push(image_loader_thread_pool, il, nullptr);
1345         DEBUG_1("Thread pool num threads: %d", g_thread_pool_get_num_threads(image_loader_thread_pool));
1346
1347         return TRUE;
1348 }
1349
1350
1351 /**************************************************************************************/
1352 /* public interface */
1353
1354
1355 gboolean image_loader_start(ImageLoader *il)
1356 {
1357         if (!il) return FALSE;
1358
1359         if (!il->fd) return FALSE;
1360
1361         return image_loader_start_thread(il);
1362 }
1363
1364
1365 /* don't forget to gdk_pixbuf_ref() it if you want to use it after image_loader_free() */
1366 GdkPixbuf *image_loader_get_pixbuf(ImageLoader *il)
1367 {
1368         GdkPixbuf *ret;
1369         if (!il) return nullptr;
1370
1371         g_mutex_lock(il->data_mutex);
1372         ret = il->pixbuf;
1373         g_mutex_unlock(il->data_mutex);
1374         return ret;
1375 }
1376
1377 void image_loader_set_requested_size(ImageLoader *il, gint width, gint height)
1378 {
1379         if (!il) return;
1380
1381         g_mutex_lock(il->data_mutex);
1382         il->requested_width = width;
1383         il->requested_height = height;
1384         g_mutex_unlock(il->data_mutex);
1385 }
1386
1387 void image_loader_set_buffer_size(ImageLoader *il, guint count)
1388 {
1389         if (!il) return;
1390
1391         g_mutex_lock(il->data_mutex);
1392         il->idle_read_loop_count = count ? count : 1;
1393         g_mutex_unlock(il->data_mutex);
1394 }
1395
1396 void image_loader_set_priority(ImageLoader *il, gint priority)
1397 {
1398         if (!il) return;
1399
1400         if (il->thread) return; /* can't change prio if the thread already runs */
1401         il->idle_priority = priority;
1402 }
1403
1404
1405 gdouble image_loader_get_percent(ImageLoader *il)
1406 {
1407         gdouble ret;
1408         if (!il) return 0.0;
1409
1410         g_mutex_lock(il->data_mutex);
1411         if (il->bytes_total == 0)
1412                 {
1413                 ret = 0.0;
1414                 }
1415         else
1416                 {
1417                 ret = static_cast<gdouble>(il->bytes_read) / il->bytes_total;
1418                 }
1419         g_mutex_unlock(il->data_mutex);
1420         return ret;
1421 }
1422
1423 gboolean image_loader_get_is_done(ImageLoader *il)
1424 {
1425         gboolean ret;
1426         if (!il) return FALSE;
1427
1428         g_mutex_lock(il->data_mutex);
1429         ret = il->done;
1430         g_mutex_unlock(il->data_mutex);
1431
1432         return ret;
1433 }
1434
1435 FileData *image_loader_get_fd(ImageLoader *il)
1436 {
1437         FileData *ret;
1438         if (!il) return nullptr;
1439
1440         g_mutex_lock(il->data_mutex);
1441         ret = il->fd;
1442         g_mutex_unlock(il->data_mutex);
1443
1444         return ret;
1445 }
1446
1447 gboolean image_loader_get_shrunk(ImageLoader *il)
1448 {
1449         gboolean ret;
1450         if (!il) return FALSE;
1451
1452         g_mutex_lock(il->data_mutex);
1453         ret = il->shrunk;
1454         g_mutex_unlock(il->data_mutex);
1455         return ret;
1456 }
1457
1458 const gchar *image_loader_get_error(ImageLoader *il)
1459 {
1460         const gchar *ret = nullptr;
1461         if (!il) return nullptr;
1462         g_mutex_lock(il->data_mutex);
1463         if (il->error) ret = il->error->message;
1464         g_mutex_unlock(il->data_mutex);
1465         return ret;
1466 }
1467
1468
1469 /**
1470  *  @FIXME this can be rather slow and blocks until the size is known
1471  */
1472 gboolean image_load_dimensions(FileData *fd, gint *width, gint *height)
1473 {
1474         ImageLoader *il;
1475         gboolean success;
1476
1477         il = image_loader_new(fd);
1478
1479         success = image_loader_start_idle(il);
1480
1481         if (success && il->pixbuf)
1482                 {
1483                 if (width) *width = gdk_pixbuf_get_width(il->pixbuf);
1484                 if (height) *height = gdk_pixbuf_get_height(il->pixbuf);;
1485                 }
1486         else
1487                 {
1488                 if (width) *width = -1;
1489                 if (height) *height = -1;
1490                 }
1491
1492         image_loader_free(il);
1493
1494         return success;
1495 }
1496 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */