1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  *
16  * See the COPYING file for license information.
17  *
18  * Guillaume Chazarain <guichaz@gmail.com>
19  */
20 
21 /***********************************
22  * Loading and unloading of images *
23  ***********************************/
24 
25 #include <stdio.h>              /* FILE, fopen(), fread(), fclose(), perror() */
26 #include <signal.h>             /* signal(), SIG_DFL, SIGSEGV */
27 #include <string.h>             /* strlen(), strrchr() */
28 
29 #include "gliv.h"
30 #include "loading.h"
31 #include "options.h"
32 #include "gliv-image.h"
33 #include "messages.h"
34 #include "str_utils.h"
35 #include "formats.h"
36 #include "params.h"
37 #include "files_list.h"
38 #include "textures.h"
39 #include "math_floats.h"
40 #include "thread.h"
41 #include "decompression.h"
42 #include "thumbnails.h"
43 #include "rendering.h"
44 #include "tree_browser.h"
45 
46 extern rt_struct *rt;
47 extern options_struct *options;
48 extern GlivImage *current_image;
49 
50 /* Used to know at any time if we are loading an image. */
51 static gchar *loading_filename = NULL;
52 
53 /*
54  * Used to be quiet when we try to load files
55  * not explicitly requested by the user.
56  */
57 static gint quiet = 0;
58 
start_quiet(void)59 void start_quiet(void)
60 {
61     quiet++;
62 }
63 
stop_quiet(void)64 void stop_quiet(void)
65 {
66     quiet--;
67 }
68 
currently_loading(void)69 const gchar *currently_loading(void)
70 {
71     return loading_filename;
72 }
73 
segv_handler(gint sig)74 static void segv_handler(gint sig)
75 {
76     const gchar *sig_str;
77 
78     signal(sig, SIG_DFL);
79     sig_str = g_strsignal(sig);
80 
81     if (loading_filename == NULL)
82         g_printerr(_("%s not while loading an image\n"), sig_str);
83     else
84         g_printerr(_("%s while loading %s\n"), sig_str, loading_filename);
85 
86     raise(sig);
87 }
88 
89 /* Called when the first image is loaded. */
install_segv_handler(void)90 void install_segv_handler(void)
91 {
92     signal(SIGSEGV, segv_handler);
93 }
94 
fill_ident(GlivImage * im)95 void fill_ident(GlivImage * im)
96 {
97     gchar *alpha_str = NULL;
98     gint im_nr;
99 
100     g_free(im->ident);
101 
102     if (im->has_alpha)
103         alpha_str = g_strconcat(" (", _("alpha channel"), ")", NULL);
104     else
105         alpha_str = g_strdup("");
106 
107     im_nr = get_image_number(im);
108     im->ident = g_strdup_printf("%s (%u/%u)%s",
109                                 im->node ?
110                                 filename_to_utf8(im->node->data) : "",
111                                 im_nr + 1, get_list_length(), alpha_str);
112 
113     g_free(alpha_str);
114 }
115 
get_extension(const gchar * filename)116 const gchar *get_extension(const gchar * filename)
117 {
118     const gchar *ext;
119     const gchar *basename;
120 
121     basename = strrchr(filename, '/');
122     if (basename == NULL)
123         basename = filename;
124     else
125         basename++;
126 
127     ext = strrchr(basename, '.');
128     if (ext != NULL)
129         /* Skip the '.' */
130         ext++;
131 
132     return ext;
133 }
134 
135 #define LOADING_ERROR if (!quiet) g_printerr
136 
get_decompressor(const gchar * filename,gint ext_len)137 static loader_t get_decompressor(const gchar * filename, gint ext_len)
138 {
139     const struct format *res;
140 
141     /* We want the image extension between image_ext_start and image_ext_end. */
142     const gchar *image_ext_end = filename + strlen(filename) - ext_len - 1;
143     const gchar *image_ext_start = image_ext_end - 1;
144 
145     gint image_ext_length = 0;
146     gchar *image_ext;
147 
148     while (image_ext_start > filename && *image_ext_start != '.') {
149         image_ext_length++;
150         image_ext_start--;
151     }
152 
153     image_ext = g_strndup(image_ext_start + 1, image_ext_length);
154     res = ext_to_loader(image_ext, image_ext_length);
155 
156     g_free(image_ext);
157 
158     if (res == NULL) {
159         LOADING_ERROR(_("%s: unknown decompressed extension\n"),
160                       filename_to_utf8(filename));
161         return LOADER_NONE;
162     }
163 
164     switch (res->loader) {
165     case LOADER_DECOMP:
166         LOADING_ERROR(_("%s: image cannot be compressed twice\n"),
167                       filename_to_utf8(filename));
168         return LOADER_NONE;
169 
170     case LOADER_PIXBUF:
171         return LOADER_DECOMP_PIXBUF;
172 
173     case LOADER_DOT_GLIV:
174         return LOADER_DECOMP_DOT_GLIV;
175 
176     default:
177         return LOADER_NONE;
178     }
179 }
180 
get_loader(const gchar * filename)181 loader_t get_loader(const gchar * filename)
182 {
183     const struct format *res;
184     const gchar *ext;
185     gint ext_len;
186 
187     ext = get_extension(filename);
188     if (ext == NULL) {
189         LOADING_ERROR(_("%s: unknown extension (none)\n"),
190                       filename_to_utf8(filename));
191         return LOADER_NONE;
192     }
193 
194     ext_len = strlen(ext);
195     res = ext_to_loader(ext, ext_len);
196 
197     if (res == NULL) {
198         LOADING_ERROR(_("%s: unknown extension\n"), filename_to_utf8(filename));
199         return LOADER_NONE;
200     }
201 
202     if (res->loader == LOADER_DECOMP)
203         return get_decompressor(filename, ext_len);
204 
205     return res->loader;
206 }
207 
208 #if 0
209 /*
210  * We used to blacken image pixels with alpha == 0.
211  * These pixels are invisible but may alter the bilinear filtering.
212  * We have stopped doing it since the time lost is too big for the benefit.
213  */
214 static void clean_alpha(GdkPixbuf * pixbuf)
215 {
216     guchar *pixel, *end;
217 
218     pixel = gdk_pixbuf_get_pixels(pixbuf);
219     end = pixel + pixels_size(pixels_size);
220 
221     for (; pixel < end; pixel += 4) {
222         if (pixel[3] == 0)
223             /*
224              * alpha == 0
225              * This is not valid C since a cast cannot be a lvalue but...
226              */
227             *((guint32 *) pixel) = 0;
228     }
229 }
230 #endif
231 
set_loading(const gchar * filename)232 void set_loading(const gchar * filename)
233 {
234     /* Used if there is a segfault. */
235     g_free(loading_filename);
236     loading_filename = g_strdup(filename);
237     update_current_image_status(FALSE);
238 
239     if (filename == NULL)
240         /*
241          * Do it, now that we are no more in
242          * the currently_loading() case
243          */
244         load_later();
245 }
246 
247 /* Wrapper: two args -> one arg. */
248 typedef struct {
249     const gchar *filename;
250     GError **error;
251 } fname_error;
252 
load_gdk_pixbuf(fname_error * param)253 static GdkPixbuf *load_gdk_pixbuf(fname_error * param)
254 {
255     GdkPixbuf *pixbuf = NULL;
256     loader_t loader;
257 
258     loader = get_loader(param->filename);
259     if (loader == LOADER_DECOMP_PIXBUF)
260         pixbuf = load_compressed_pixbuf(param->filename, param->error);
261 
262     if (*param->error)
263         return pixbuf;
264 
265     if (pixbuf == NULL)
266         pixbuf = gdk_pixbuf_new_from_file(param->filename, param->error);
267 
268     return pixbuf;
269 }
270 
load_gdk_pixbuf_threaded(const gchar * filename,GError ** error)271 static GdkPixbuf *load_gdk_pixbuf_threaded(const gchar * filename,
272                                            GError ** error)
273 {
274     fname_error *param;
275     GdkPixbuf *pixbuf;
276 
277     param = g_new(fname_error, 1);
278     param->filename = filename;
279     param->error = (error == NULL) ? g_new(GError *, 1) : error;
280 
281     *param->error = NULL;
282     pixbuf = do_threaded((GThreadFunc) load_gdk_pixbuf, param);
283 
284     if (pixbuf == NULL) {
285         if (error == NULL) {
286             /*
287              * The caller won't handle the error, handle it here
288              */
289             if (*param->error != NULL)
290                 DIALOG_MSG("%s", (*param->error)->message);
291             else
292                 DIALOG_MSG(_("Cannot load %s"), filename);
293         }
294     } else
295         add_thumbnail(filename, pixbuf);
296 
297     g_free(param);
298     return pixbuf;
299 }
300 
pixels_size(GdkPixbuf * pixbuf)301 gint pixels_size(GdkPixbuf * pixbuf)
302 {
303     /* The last rowstride may not be full. */
304     gint w = gdk_pixbuf_get_width(pixbuf);
305     gint h = gdk_pixbuf_get_height(pixbuf);
306     gint rs = gdk_pixbuf_get_rowstride(pixbuf);
307     gint nb_chan = gdk_pixbuf_get_n_channels(pixbuf);
308     gint bps = gdk_pixbuf_get_bits_per_sample(pixbuf);
309 
310     return ((h - 1) * rs + w * ((nb_chan * bps + 7) / 8));
311 }
312 
nb_maps(gint dim)313 G_GNUC_PURE static gint nb_maps(gint dim)
314 {
315     gint ret = 1;
316 
317     while (dim > rt->max_texture_size) {
318         dim *= MIPMAP_RATIO;
319         ret++;
320     }
321 
322     return ret;
323 }
324 
parse_exif_orientation(GdkPixbuf * pixbuf,GlivImage * im)325 static void parse_exif_orientation(GdkPixbuf * pixbuf, GlivImage *im)
326 {
327     const gchar *orientation_str = gdk_pixbuf_get_option(pixbuf, "orientation");
328 
329     if (orientation_str)
330         switch (orientation_str[0]) {
331             case '2':
332                 im->initial_angle = PI;
333                 im->initial_h_flip = TRUE;
334                 break;
335 
336             case '3':
337                 im->initial_angle = PI;
338                 break;
339 
340             case '4':
341                 im->initial_h_flip = TRUE;
342                 break;
343 
344             case '5':
345                 im->initial_angle = PI / 2.0;
346                 im->initial_h_flip = TRUE;
347                 break;
348 
349             case '6':
350                 im->initial_angle = -PI / 2.0;
351                 break;
352 
353             case '7':
354                 im->initial_angle = -PI / 2.0;
355                 im->initial_h_flip = TRUE;
356                 break;
357 
358             case '8':
359                 im->initial_angle = PI / 2.0;
360                 break;
361         }
362 }
363 
make_gliv_image(GdkPixbuf * pixbuf)364 GlivImage *make_gliv_image(GdkPixbuf * pixbuf)
365 {
366     GlivImage *im;
367 
368     im = gliv_image_new();
369     im->ident = NULL;
370     im->number = -1;
371     im->node = NULL;
372     im->first_image = (current_image == NULL);
373 
374     im->width = gdk_pixbuf_get_width(pixbuf);
375     im->height = gdk_pixbuf_get_height(pixbuf);
376     im->has_alpha = gdk_pixbuf_get_has_alpha(pixbuf);
377     parse_exif_orientation(pixbuf, im);
378 
379     if (options->mipmap) {
380         gint nb_w = nb_maps(im->width);
381         gint nb_h = nb_maps(im->height);
382         im->nb_maps = MIN(nb_w, nb_h);
383     } else
384         im->nb_maps = 1;
385 
386     im->maps = g_new(texture_map, im->nb_maps);
387     im->maps[0].pixbuf = pixbuf;
388 
389 #if 0
390     if (im->has_alpha)
391         clean_alpha(pixbuf);
392 #endif
393 
394     create_maps(im);
395     return im;
396 }
397 
load_file(const gchar * filename,GError ** error)398 GlivImage *load_file(const gchar * filename, GError ** error)
399 {
400     GdkPixbuf *loaded_image;
401     GlivImage *im = NULL;
402 
403     set_loading(filename_to_utf8(filename));
404 
405     loaded_image = load_gdk_pixbuf_threaded(filename, error);
406     if (loaded_image != NULL)
407         im = make_gliv_image(loaded_image);
408 
409     set_loading(NULL);
410 
411     return im;
412 }
413