/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * See the COPYING file for license information. * * Guillaume Chazarain */ /*********************************** * Loading and unloading of images * ***********************************/ #include /* FILE, fopen(), fread(), fclose(), perror() */ #include /* signal(), SIG_DFL, SIGSEGV */ #include /* strlen(), strrchr() */ #include "gliv.h" #include "loading.h" #include "options.h" #include "gliv-image.h" #include "messages.h" #include "str_utils.h" #include "formats.h" #include "params.h" #include "files_list.h" #include "textures.h" #include "math_floats.h" #include "thread.h" #include "decompression.h" #include "thumbnails.h" #include "rendering.h" #include "tree_browser.h" extern rt_struct *rt; extern options_struct *options; extern GlivImage *current_image; /* Used to know at any time if we are loading an image. */ static gchar *loading_filename = NULL; /* * Used to be quiet when we try to load files * not explicitly requested by the user. */ static gint quiet = 0; void start_quiet(void) { quiet++; } void stop_quiet(void) { quiet--; } const gchar *currently_loading(void) { return loading_filename; } static void segv_handler(gint sig) { const gchar *sig_str; signal(sig, SIG_DFL); sig_str = g_strsignal(sig); if (loading_filename == NULL) g_printerr(_("%s not while loading an image\n"), sig_str); else g_printerr(_("%s while loading %s\n"), sig_str, loading_filename); raise(sig); } /* Called when the first image is loaded. */ void install_segv_handler(void) { signal(SIGSEGV, segv_handler); } void fill_ident(GlivImage * im) { gchar *alpha_str = NULL; gint im_nr; g_free(im->ident); if (im->has_alpha) alpha_str = g_strconcat(" (", _("alpha channel"), ")", NULL); else alpha_str = g_strdup(""); im_nr = get_image_number(im); im->ident = g_strdup_printf("%s (%u/%u)%s", im->node ? filename_to_utf8(im->node->data) : "", im_nr + 1, get_list_length(), alpha_str); g_free(alpha_str); } const gchar *get_extension(const gchar * filename) { const gchar *ext; const gchar *basename; basename = strrchr(filename, '/'); if (basename == NULL) basename = filename; else basename++; ext = strrchr(basename, '.'); if (ext != NULL) /* Skip the '.' */ ext++; return ext; } #define LOADING_ERROR if (!quiet) g_printerr static loader_t get_decompressor(const gchar * filename, gint ext_len) { const struct format *res; /* We want the image extension between image_ext_start and image_ext_end. */ const gchar *image_ext_end = filename + strlen(filename) - ext_len - 1; const gchar *image_ext_start = image_ext_end - 1; gint image_ext_length = 0; gchar *image_ext; while (image_ext_start > filename && *image_ext_start != '.') { image_ext_length++; image_ext_start--; } image_ext = g_strndup(image_ext_start + 1, image_ext_length); res = ext_to_loader(image_ext, image_ext_length); g_free(image_ext); if (res == NULL) { LOADING_ERROR(_("%s: unknown decompressed extension\n"), filename_to_utf8(filename)); return LOADER_NONE; } switch (res->loader) { case LOADER_DECOMP: LOADING_ERROR(_("%s: image cannot be compressed twice\n"), filename_to_utf8(filename)); return LOADER_NONE; case LOADER_PIXBUF: return LOADER_DECOMP_PIXBUF; case LOADER_DOT_GLIV: return LOADER_DECOMP_DOT_GLIV; default: return LOADER_NONE; } } loader_t get_loader(const gchar * filename) { const struct format *res; const gchar *ext; gint ext_len; ext = get_extension(filename); if (ext == NULL) { LOADING_ERROR(_("%s: unknown extension (none)\n"), filename_to_utf8(filename)); return LOADER_NONE; } ext_len = strlen(ext); res = ext_to_loader(ext, ext_len); if (res == NULL) { LOADING_ERROR(_("%s: unknown extension\n"), filename_to_utf8(filename)); return LOADER_NONE; } if (res->loader == LOADER_DECOMP) return get_decompressor(filename, ext_len); return res->loader; } #if 0 /* * We used to blacken image pixels with alpha == 0. * These pixels are invisible but may alter the bilinear filtering. * We have stopped doing it since the time lost is too big for the benefit. */ static void clean_alpha(GdkPixbuf * pixbuf) { guchar *pixel, *end; pixel = gdk_pixbuf_get_pixels(pixbuf); end = pixel + pixels_size(pixels_size); for (; pixel < end; pixel += 4) { if (pixel[3] == 0) /* * alpha == 0 * This is not valid C since a cast cannot be a lvalue but... */ *((guint32 *) pixel) = 0; } } #endif void set_loading(const gchar * filename) { /* Used if there is a segfault. */ g_free(loading_filename); loading_filename = g_strdup(filename); update_current_image_status(FALSE); if (filename == NULL) /* * Do it, now that we are no more in * the currently_loading() case */ load_later(); } /* Wrapper: two args -> one arg. */ typedef struct { const gchar *filename; GError **error; } fname_error; static GdkPixbuf *load_gdk_pixbuf(fname_error * param) { GdkPixbuf *pixbuf = NULL; loader_t loader; loader = get_loader(param->filename); if (loader == LOADER_DECOMP_PIXBUF) pixbuf = load_compressed_pixbuf(param->filename, param->error); if (*param->error) return pixbuf; if (pixbuf == NULL) pixbuf = gdk_pixbuf_new_from_file(param->filename, param->error); return pixbuf; } static GdkPixbuf *load_gdk_pixbuf_threaded(const gchar * filename, GError ** error) { fname_error *param; GdkPixbuf *pixbuf; param = g_new(fname_error, 1); param->filename = filename; param->error = (error == NULL) ? g_new(GError *, 1) : error; *param->error = NULL; pixbuf = do_threaded((GThreadFunc) load_gdk_pixbuf, param); if (pixbuf == NULL) { if (error == NULL) { /* * The caller won't handle the error, handle it here */ if (*param->error != NULL) DIALOG_MSG("%s", (*param->error)->message); else DIALOG_MSG(_("Cannot load %s"), filename); } } else add_thumbnail(filename, pixbuf); g_free(param); return pixbuf; } gint pixels_size(GdkPixbuf * pixbuf) { /* The last rowstride may not be full. */ gint w = gdk_pixbuf_get_width(pixbuf); gint h = gdk_pixbuf_get_height(pixbuf); gint rs = gdk_pixbuf_get_rowstride(pixbuf); gint nb_chan = gdk_pixbuf_get_n_channels(pixbuf); gint bps = gdk_pixbuf_get_bits_per_sample(pixbuf); return ((h - 1) * rs + w * ((nb_chan * bps + 7) / 8)); } G_GNUC_PURE static gint nb_maps(gint dim) { gint ret = 1; while (dim > rt->max_texture_size) { dim *= MIPMAP_RATIO; ret++; } return ret; } static void parse_exif_orientation(GdkPixbuf * pixbuf, GlivImage *im) { const gchar *orientation_str = gdk_pixbuf_get_option(pixbuf, "orientation"); if (orientation_str) switch (orientation_str[0]) { case '2': im->initial_angle = PI; im->initial_h_flip = TRUE; break; case '3': im->initial_angle = PI; break; case '4': im->initial_h_flip = TRUE; break; case '5': im->initial_angle = PI / 2.0; im->initial_h_flip = TRUE; break; case '6': im->initial_angle = -PI / 2.0; break; case '7': im->initial_angle = -PI / 2.0; im->initial_h_flip = TRUE; break; case '8': im->initial_angle = PI / 2.0; break; } } GlivImage *make_gliv_image(GdkPixbuf * pixbuf) { GlivImage *im; im = gliv_image_new(); im->ident = NULL; im->number = -1; im->node = NULL; im->first_image = (current_image == NULL); im->width = gdk_pixbuf_get_width(pixbuf); im->height = gdk_pixbuf_get_height(pixbuf); im->has_alpha = gdk_pixbuf_get_has_alpha(pixbuf); parse_exif_orientation(pixbuf, im); if (options->mipmap) { gint nb_w = nb_maps(im->width); gint nb_h = nb_maps(im->height); im->nb_maps = MIN(nb_w, nb_h); } else im->nb_maps = 1; im->maps = g_new(texture_map, im->nb_maps); im->maps[0].pixbuf = pixbuf; #if 0 if (im->has_alpha) clean_alpha(pixbuf); #endif create_maps(im); return im; } GlivImage *load_file(const gchar * filename, GError ** error) { GdkPixbuf *loaded_image; GlivImage *im = NULL; set_loading(filename_to_utf8(filename)); loaded_image = load_gdk_pixbuf_threaded(filename, error); if (loaded_image != NULL) im = make_gliv_image(loaded_image); set_loading(NULL); return im; }