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