/* -*- mode: C; c-file-style: "linux" -*- */ /* GdkPixbuf library - Main loading interface. * * Copyright (C) 1999 The Free Software Foundation * * Authors: Miguel de Icaza * Federico Mena-Quintero * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include "gdk-pixbuf-private.h" #include "gdk-pixbuf-io.h" #ifdef G_OS_WIN32 #define STRICT #include #undef STRICT #endif static gint format_check (GdkPixbufModule *module, guchar *buffer, int size) { int j; gchar m; GdkPixbufModulePattern *pattern; for (pattern = module->info->signature; pattern->prefix; pattern++) { for (j = 0; j < size && pattern->prefix[j] != 0; j++) { m = pattern->mask ? pattern->mask[j] : ' '; if (m == ' ') { if (buffer[j] != pattern->prefix[j]) break; } else if (m == '!') { if (buffer[j] == pattern->prefix[j]) break; } else if (m == 'z') { if (buffer[j] != 0) break; } else if (m == 'n') { if (buffer[j] == 0) break; } } if (pattern->prefix[j] == 0) return pattern->relevance; } return 0; } static GSList *file_formats = NULL; static void gdk_pixbuf_io_init (); static GSList * get_file_formats () { if (file_formats == NULL) gdk_pixbuf_io_init (); return file_formats; } #ifdef USE_GMODULE static gboolean scan_string (const char **pos, GString *out) { const char *p = *pos, *q = *pos; char *tmp, *tmp2; gboolean quoted; while (g_ascii_isspace (*p)) p++; if (!*p) return FALSE; else if (*p == '"') { p++; quoted = FALSE; for (q = p; (*q != '"') || quoted; q++) { if (!*q) return FALSE; quoted = (*q == '\\') && !quoted; } tmp = g_strndup (p, q - p); tmp2 = g_strcompress (tmp); g_string_truncate (out, 0); g_string_append (out, tmp2); g_free (tmp); g_free (tmp2); } q++; *pos = q; return TRUE; } static gboolean scan_int (const char **pos, int *out) { int i = 0; char buf[32]; const char *p = *pos; while (g_ascii_isspace (*p)) p++; if (*p < '0' || *p > '9') return FALSE; while ((*p >= '0') && (*p <= '9') && i < sizeof (buf)) { buf[i] = *p; i++; p++; } if (i == sizeof (buf)) return FALSE; else buf[i] = '\0'; *out = atoi (buf); *pos = p; return TRUE; } static gboolean skip_space (const char **pos) { const char *p = *pos; while (g_ascii_isspace (*p)) p++; *pos = p; return !(*p == '\0'); } static gchar * gdk_pixbuf_get_module_file (void) { gchar *result = g_strdup (g_getenv ("GDK_PIXBUF_MODULE_FILE")); if (!result) result = g_build_filename (GTK_SYSCONFDIR, "gtk-2.0", "gdk-pixbuf.loaders", NULL); return result; } static void gdk_pixbuf_io_init () { GIOChannel *channel; gchar *line_buf; gsize term; GString *tmp_buf = g_string_new (NULL); gboolean have_error = FALSE; GdkPixbufModule *module = NULL; gchar *filename = gdk_pixbuf_get_module_file (); int flags; int n_patterns = 0; GdkPixbufModulePattern *pattern; GError *error = NULL; channel = g_io_channel_new_file (filename, "r", &error); if (!channel) { g_warning ("Can not open pixbuf loader module file '%s': %s", filename, error->message); return; } while (!have_error && g_io_channel_read_line (channel, &line_buf, NULL, &term, NULL) == G_IO_STATUS_NORMAL) { const char *p; p = line_buf; line_buf[term] = 0; if (!skip_space (&p)) { /* Blank line marking the end of a module */ if (module && *p != '#') { file_formats = g_slist_prepend (file_formats, module); module = NULL; } goto next_line; } if (*p == '#') goto next_line; if (!module) { /* Read a module location */ module = g_new0 (GdkPixbufModule, 1); n_patterns = 0; if (!scan_string (&p, tmp_buf)) { g_warning ("Error parsing loader info in '%s'\n %s", filename, line_buf); have_error = TRUE; } module->module_path = g_strdup (tmp_buf->str); } else if (!module->module_name) { module->info = g_new0 (GdkPixbufFormat, 1); if (!scan_string (&p, tmp_buf)) { g_warning ("Error parsing loader info in '%s'\n %s", filename, line_buf); have_error = TRUE; } module->info->name = g_strdup (tmp_buf->str); module->module_name = module->info->name; if (!scan_int (&p, &flags)) { g_warning ("Error parsing loader info in '%s'\n %s", filename, line_buf); have_error = TRUE; } module->info->flags = flags; if (!scan_string (&p, tmp_buf)) { g_warning ("Error parsing loader info in '%s'\n %s", filename, line_buf); have_error = TRUE; } if (tmp_buf->str[0] != 0) module->info->domain = g_strdup (tmp_buf->str); if (!scan_string (&p, tmp_buf)) { g_warning ("Error parsing loader info in '%s'\n %s", filename, line_buf); have_error = TRUE; } module->info->description = g_strdup (tmp_buf->str); } else if (!module->info->mime_types) { int n = 1; module->info->mime_types = g_new0 (gchar*, 1); while (scan_string (&p, tmp_buf)) { if (tmp_buf->str[0] != 0) { module->info->mime_types = g_realloc (module->info->mime_types, (n + 1) * sizeof (gchar*)); module->info->mime_types[n - 1] = g_strdup (tmp_buf->str); module->info->mime_types[n] = 0; n++; } } } else if (!module->info->extensions) { int n = 1; module->info->extensions = g_new0 (gchar*, 1); while (scan_string (&p, tmp_buf)) { if (tmp_buf->str[0] != 0) { module->info->extensions = g_realloc (module->info->extensions, (n + 1) * sizeof (gchar*)); module->info->extensions[n - 1] = g_strdup (tmp_buf->str); module->info->extensions[n] = 0; n++; } } } else { n_patterns++; module->info->signature = (GdkPixbufModulePattern *) g_realloc (module->info->signature, (n_patterns + 1) * sizeof (GdkPixbufModulePattern)); pattern = module->info->signature + n_patterns; pattern->prefix = NULL; pattern->mask = NULL; pattern->relevance = 0; pattern--; if (!scan_string (&p, tmp_buf)) goto context_error; pattern->prefix = g_strdup (tmp_buf->str); if (!scan_string (&p, tmp_buf)) goto context_error; if (*tmp_buf->str) pattern->mask = g_strdup (tmp_buf->str); else pattern->mask = NULL; if (!scan_int (&p, &pattern->relevance)) goto context_error; goto next_line; context_error: g_free (pattern->prefix); g_free (pattern->mask); g_free (pattern); g_warning ("Error parsing loader info in '%s'\n %s", filename, line_buf); have_error = TRUE; } next_line: g_free (line_buf); } g_string_free (tmp_buf, TRUE); g_io_channel_unref (channel); g_free (filename); } #ifdef G_OS_WIN32 /* DllMain function needed to tuck away the gdk-pixbuf DLL name */ G_WIN32_DLLMAIN_FOR_DLL_NAME (static, dll_name) static char * get_libdir (void) { static char *libdir = NULL; if (libdir == NULL) libdir = g_win32_get_package_installation_subdirectory (GETTEXT_PACKAGE, dll_name, "lib\\gtk-2.0\\" GTK_BINARY_VERSION "\\loaders"); return libdir; } #undef PIXBUF_LIBDIR #define PIXBUF_LIBDIR get_libdir () #endif /* actually load the image handler - gdk_pixbuf_get_module only get a */ /* reference to the module to load, it doesn't actually load it */ /* perhaps these actions should be combined in one function */ gboolean _gdk_pixbuf_load_module (GdkPixbufModule *image_module, GError **error) { char *path; GModule *module; gpointer sym; g_return_val_if_fail (image_module->module == NULL, FALSE); path = image_module->module_path; module = g_module_open (path, G_MODULE_BIND_LAZY); if (!module) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, _("Unable to load image-loading module: %s: %s"), path, g_module_error ()); return FALSE; } image_module->module = module; if (g_module_symbol (module, "fill_vtable", &sym)) { GdkPixbufModuleFillVtableFunc func = (GdkPixbufModuleFillVtableFunc) sym; (* func) (image_module); return TRUE; } else { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, _("Image-loading module %s does not export the proper interface; perhaps it's from a different GTK version?"), path); return FALSE; } } #else #define module(type) \ extern void MODULE_ENTRY (type, fill_info) (GdkPixbufFormat *info); \ extern void MODULE_ENTRY (type, fill_vtable) (GdkPixbufModule *module) module (png); module (bmp); module (wbmp); module (gif); module (ico); module (ani); module (jpeg); module (pnm); module (ras); module (tiff); module (xpm); module (xbm); module (tga); module (pcx); gboolean _gdk_pixbuf_load_module (GdkPixbufModule *image_module, GError **error) { GdkPixbufModuleFillInfoFunc fill_info = NULL; GdkPixbufModuleFillVtableFunc fill_vtable = NULL; image_module->module = (void *) 1; if (FALSE) { /* Ugly hack so we can use else if unconditionally below ;-) */ } #ifdef INCLUDE_png else if (strcmp (image_module->module_name, "png") == 0) { fill_info = MODULE_ENTRY (png, fill_info); fill_vtable = MODULE_ENTRY (png, fill_vtable); } #endif #ifdef INCLUDE_bmp else if (strcmp (image_module->module_name, "bmp") == 0) { fill_info = MODULE_ENTRY (bmp, fill_info); fill_vtable = MODULE_ENTRY (bmp, fill_vtable); } #endif #ifdef INCLUDE_wbmp else if (strcmp (image_module->module_name, "wbmp") == 0) { fill_info = MODULE_ENTRY (wbmp, fill_info); fill_vtable = MODULE_ENTRY (wbmp, fill_vtable); } #endif #ifdef INCLUDE_gif else if (strcmp (image_module->module_name, "gif") == 0) { fill_info = MODULE_ENTRY (gif, fill_info); fill_vtable = MODULE_ENTRY (gif, fill_vtable); } #endif #ifdef INCLUDE_ico else if (strcmp (image_module->module_name, "ico") == 0) { fill_info = MODULE_ENTRY (ico, fill_info); fill_vtable = MODULE_ENTRY (ico, fill_vtable); } #endif #ifdef INCLUDE_ani else if (strcmp (image_module->module_name, "ani") == 0) { fill_info = MODULE_ENTRY (ani, fill_info); fill_vtable = MODULE_ENTRY (ani, fill_vtable); } #endif #ifdef INCLUDE_jpeg else if (strcmp (image_module->module_name, "jpeg") == 0) { fill_info = MODULE_ENTRY (jpeg, fill_info); fill_vtable = MODULE_ENTRY (jpeg, fill_vtable); } #endif #ifdef INCLUDE_pnm else if (strcmp (image_module->module_name, "pnm") == 0) { fill_info = MODULE_ENTRY (pnm, fill_info); fill_vtable = MODULE_ENTRY (pnm, fill_vtable); } #endif #ifdef INCLUDE_ras else if (strcmp (image_module->module_name, "ras") == 0) { fill_info = MODULE_ENTRY (ras, fill_info); fill_vtable = MODULE_ENTRY (ras, fill_vtable); } #endif #ifdef INCLUDE_tiff else if (strcmp (image_module->module_name, "tiff") == 0) { fill_info = MODULE_ENTRY (tiff, fill_info); fill_vtable = MODULE_ENTRY (tiff, fill_vtable); } #endif #ifdef INCLUDE_xpm else if (strcmp (image_module->module_name, "xpm") == 0) { fill_info = MODULE_ENTRY (xpm, fill_info); fill_vtable = MODULE_ENTRY (xpm, fill_vtable); } #endif #ifdef INCLUDE_xbm else if (strcmp (image_module->module_name, "xbm") == 0) { fill_info = MODULE_ENTRY (xbm, fill_info); fill_vtable = MODULE_ENTRY (xbm, fill_vtable); } #endif #ifdef INCLUDE_tga else if (strcmp (image_module->module_name, "tga") == 0) { fill_info = MODULE_ENTRY (tga, fill_info); fill_vtable = MODULE_ENTRY (tga, fill_vtable); } #endif #ifdef INCLUDE_pcx else if (strcmp (image_module->module_name, "pcx") == 0) { fill_info = MODULE_ENTRY (pcx, fill_info); fill_vtable = MODULE_ENTRY (pcx, fill_vtable); } #endif if (fill_vtable) { (* fill_vtable) (image_module); image_module->info = g_new0 (GdkPixbufFormat, 1); (* fill_info) (image_module->info); return TRUE; } else { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNKNOWN_TYPE, _("Image type '%s' is not supported"), image_module->module_name); return FALSE; } } static void gdk_pixbuf_io_init () { gchar *included_formats[] = { "ani", "png", "bmp", "wbmp", "gif", "ico", "jpeg", "pnm", "ras", "tiff", "xpm", "xbm", "tga", "pcx", NULL }; gchar **name; GdkPixbufModule *module = NULL; for (name = included_formats; *name; name++) { module = g_new0 (GdkPixbufModule, 1); module->module_name = *name; if (_gdk_pixbuf_load_module (module, NULL)) file_formats = g_slist_prepend (file_formats, module); else g_free (module); } } #endif GdkPixbufModule * _gdk_pixbuf_get_named_module (const char *name, GError **error) { GSList *modules; for (modules = get_file_formats (); modules; modules = g_slist_next (modules)) { GdkPixbufModule *module = (GdkPixbufModule *)modules->data; if (!strcmp (name, module->module_name)) return module; } g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNKNOWN_TYPE, _("Image type '%s' is not supported"), name); return NULL; } GdkPixbufModule * _gdk_pixbuf_get_module (guchar *buffer, guint size, const gchar *filename, GError **error) { GSList *modules; gint score, best = 0; GdkPixbufModule *selected = NULL; for (modules = get_file_formats (); modules; modules = g_slist_next (modules)) { GdkPixbufModule *module = (GdkPixbufModule *)modules->data; score = format_check (module, buffer, size); if (score > best) { best = score; selected = module; } if (score >= 100) break; } if (selected != NULL) return selected; if (filename) g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNKNOWN_TYPE, _("Couldn't recognize the image file format for file '%s'"), filename); else g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNKNOWN_TYPE, _("Unrecognized image file format")); return NULL; } static void prepared_notify (GdkPixbuf *pixbuf, GdkPixbufAnimation *anim, gpointer user_data) { if (pixbuf != NULL) g_object_ref (pixbuf); *((GdkPixbuf **)user_data) = pixbuf; } GdkPixbuf * _gdk_pixbuf_generic_image_load (GdkPixbufModule *module, FILE *f, GError **error) { guchar buffer[4096]; size_t length; GdkPixbuf *pixbuf = NULL; GdkPixbufAnimation *animation = NULL; gpointer context; if (module->load != NULL) return (* module->load) (f, error); if (module->begin_load != NULL) { context = module->begin_load (NULL, prepared_notify, NULL, &pixbuf, error); if (!context) return NULL; while (!feof (f)) { length = fread (buffer, 1, sizeof (buffer), f); if (length > 0) if (!module->load_increment (context, buffer, length, error)) { module->stop_load (context, NULL); if (pixbuf != NULL) g_object_unref (pixbuf); return NULL; } } if (!module->stop_load (context, error)) { if (pixbuf != NULL) g_object_unref (pixbuf); return NULL; } return pixbuf; } if (module->load_animation != NULL) { animation = (* module->load_animation) (f, error); if (animation != NULL) { pixbuf = gdk_pixbuf_animation_get_static_image (animation); g_object_ref (pixbuf); g_object_unref (animation); return pixbuf; } } return NULL; } /** * gdk_pixbuf_new_from_file: * @filename: Name of file to load. * @error: Return location for an error * * Creates a new pixbuf by loading an image from a file. The file format is * detected automatically. If %NULL is returned, then @error will be set. * Possible errors are in the #GDK_PIXBUF_ERROR and #G_FILE_ERROR domains. * * Return value: A newly-created pixbuf with a reference count of 1, or %NULL if * any of several error conditions occurred: the file could not be opened, * there was no loader for the file's format, there was not enough memory to * allocate the image buffer, or the image file contained invalid data. **/ GdkPixbuf * gdk_pixbuf_new_from_file (const char *filename, GError **error) { GdkPixbuf *pixbuf; int size; FILE *f; guchar buffer [128]; GdkPixbufModule *image_module; g_return_val_if_fail (filename != NULL, NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); f = fopen (filename, "rb"); if (!f) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Failed to open file '%s': %s"), filename, g_strerror (errno)); return NULL; } size = fread (&buffer, 1, sizeof (buffer), f); if (size == 0) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE, _("Image file '%s' contains no data"), filename); fclose (f); return NULL; } image_module = _gdk_pixbuf_get_module (buffer, size, filename, error); if (image_module == NULL) { fclose (f); return NULL; } if (image_module->module == NULL) if (!_gdk_pixbuf_load_module (image_module, error)) { fclose (f); return NULL; } fseek (f, 0, SEEK_SET); pixbuf = _gdk_pixbuf_generic_image_load (image_module, f, error); fclose (f); if (pixbuf == NULL && error != NULL && *error == NULL) { /* I don't trust these crufty longjmp()'ing image libs * to maintain proper error invariants, and I don't * want user code to segfault as a result. We need to maintain * the invariastable/gdk-pixbuf/nt that error gets set if NULL is returned. */ g_warning ("Bug! gdk-pixbuf loader '%s' didn't set an error on failure.", image_module->module_name); g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, _("Failed to load image '%s': reason not known, probably a corrupt image file"), filename); } else if (error != NULL && *error != NULL) { /* Add the filename to the error message */ GError *e = *error; gchar *old; old = e->message; e->message = g_strdup_printf (_("Failed to load image '%s': %s"), filename, old); g_free (old); } return pixbuf; } static void size_prepared_cb (GdkPixbufLoader *loader, int width, int height, gpointer data) { struct { int width; int height; } *info = data; g_return_if_fail (width > 0 && height > 0); if ((double)height * (double)info->width > (double)width * (double)info->height) { width = 0.5 + (double)width * (double)info->height / (double)height; height = info->height; } else { height = 0.5 + (double)height * (double)info->width / (double)width; width = info->width; } gdk_pixbuf_loader_set_size (loader, width, height); } /** * gdk_pixbuf_new_from_file_at_size: * @filename: Name of file to load. * @width: The width the image should have * @height: The height the image should have * @error: Return location for an error * * Creates a new pixbuf by loading an image from a file. The file format is * detected automatically. If %NULL is returned, then @error will be set. * Possible errors are in the #GDK_PIXBUF_ERROR and #G_FILE_ERROR domains. * The image will be scaled to fit in the requested size, preserving its aspect ratio. * * Return value: A newly-created pixbuf with a reference count of 1, or %NULL if * any of several error conditions occurred: the file could not be opened, * there was no loader for the file's format, there was not enough memory to * allocate the image buffer, or the image file contained invalid data. * * Since: 2.4 **/ GdkPixbuf * gdk_pixbuf_new_from_file_at_size (const char *filename, int width, int height, GError **error) { GdkPixbufLoader *loader; GdkPixbuf *pixbuf; guchar buffer [4096]; int length; FILE *f; struct { gint width; gint height; } info; g_return_val_if_fail (filename != NULL, NULL); g_return_val_if_fail (width > 0 && height > 0, NULL); f = fopen (filename, "rb"); if (!f) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Failed to open file '%s': %s"), filename, g_strerror (errno)); return NULL; } loader = gdk_pixbuf_loader_new (); info.width = width; info.height = height; g_signal_connect (loader, "size-prepared", G_CALLBACK (size_prepared_cb), &info); while (!feof (f)) { length = fread (buffer, 1, sizeof (buffer), f); if (length > 0) if (!gdk_pixbuf_loader_write (loader, buffer, length, error)) { gdk_pixbuf_loader_close (loader, NULL); fclose (f); g_object_unref (G_OBJECT (loader)); return NULL; } } fclose (f); if (!gdk_pixbuf_loader_close (loader, error)) { g_object_unref (G_OBJECT (loader)); return NULL; } pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); if (!pixbuf) { g_object_unref (G_OBJECT (loader)); g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, _("Failed to load image '%s': reason not known, probably a corrupt image file"), filename); return NULL; } g_object_ref (pixbuf); g_object_unref (G_OBJECT (loader)); return pixbuf; } static void info_cb (GdkPixbufLoader *loader, int width, int height, gpointer data) { struct { GdkPixbufFormat *format; int width; int height; } *info = data; g_return_if_fail (width > 0 && height > 0); info->format = gdk_pixbuf_loader_get_format (loader); info->width = width; info->height = height; gdk_pixbuf_loader_set_size (loader, 0, 0); } /** * gdk_pixbuf_get_file_info: * @filename: The name of the file to identify. * @width: Return location for the width of the image, or %NULL * @height: Return location for the height of the image, or %NULL * * Parses an image file far enough to determine its format and size. * * Returns: A #GdkPixbufFormat describing the image format of the file * or %NULL if the image format wasn't recognized. The return value * is owned by GdkPixbuf and should not be freed. * * Since: 2.4 **/ GdkPixbufFormat * gdk_pixbuf_get_file_info (const gchar *filename, gint *width, gint *height) { GdkPixbufLoader *loader; GError *temp = NULL; guchar buffer [4096]; int length; FILE *f; struct { GdkPixbufFormat *format; gint width; gint height; } info; g_return_val_if_fail (filename != NULL, NULL); f = fopen (filename, "rb"); if (!f) return NULL; loader = gdk_pixbuf_loader_new (); info.format = NULL; info.width = -1; info.height = -1; g_signal_connect (loader, "size-prepared", G_CALLBACK (info_cb), &info); while (!feof (f)) { length = fread (buffer, 1, sizeof (buffer), f); if (length > 0) { if (!gdk_pixbuf_loader_write (loader, buffer, length, NULL)) break; } if (info.format != NULL) break; } fclose (f); gdk_pixbuf_loader_close (loader, NULL); g_object_unref (G_OBJECT (loader)); if (width) *width = info.width; if (height) *height = info.height; return info.format; } /** * gdk_pixbuf_new_from_xpm_data: * @data: Pointer to inline XPM data. * * Creates a new pixbuf by parsing XPM data in memory. This data is commonly * the result of including an XPM file into a program's C source. * * Return value: A newly-created pixbuf with a reference count of 1. **/ GdkPixbuf * gdk_pixbuf_new_from_xpm_data (const char **data) { GdkPixbuf *(* load_xpm_data) (const char **data); GdkPixbuf *pixbuf; GError *error = NULL; GdkPixbufModule *xpm_module = _gdk_pixbuf_get_named_module ("xpm", &error); if (xpm_module == NULL) { g_warning ("Error loading XPM image loader: %s", error->message); g_error_free (error); return NULL; } if (xpm_module->module == NULL) { if (!_gdk_pixbuf_load_module (xpm_module, &error)) { g_warning ("Error loading XPM image loader: %s", error->message); g_error_free (error); return NULL; } } if (xpm_module->load_xpm_data == NULL) { g_warning ("gdk-pixbuf XPM module lacks XPM data capability"); return NULL; } else load_xpm_data = xpm_module->load_xpm_data; pixbuf = (* load_xpm_data) (data); return pixbuf; } static void collect_save_options (va_list opts, gchar ***keys, gchar ***vals) { gchar *key; gchar *val; gchar *next; gint count; count = 0; *keys = NULL; *vals = NULL; next = va_arg (opts, gchar*); while (next) { key = next; val = va_arg (opts, gchar*); ++count; /* woo, slow */ *keys = g_realloc (*keys, sizeof(gchar*) * (count + 1)); *vals = g_realloc (*vals, sizeof(gchar*) * (count + 1)); (*keys)[count-1] = g_strdup (key); (*vals)[count-1] = g_strdup (val); (*keys)[count] = NULL; (*vals)[count] = NULL; next = va_arg (opts, gchar*); } } static gboolean save_to_file_callback (const gchar *buf, gsize count, GError **error, gpointer data) { FILE *filehandle = data; gsize n; n = fwrite (buf, 1, count, filehandle); if (n != count) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Error writing to image file: %s"), g_strerror (errno)); return FALSE; } return TRUE; } static gboolean gdk_pixbuf_real_save (GdkPixbuf *pixbuf, FILE *filehandle, const char *type, gchar **keys, gchar **values, GError **error) { GdkPixbufModule *image_module = NULL; image_module = _gdk_pixbuf_get_named_module (type, error); if (image_module == NULL) return FALSE; if (image_module->module == NULL) if (!_gdk_pixbuf_load_module (image_module, error)) return FALSE; if (image_module->save) { /* save normally */ return (* image_module->save) (filehandle, pixbuf, keys, values, error); } else if (image_module->save_to_callback) { /* save with simple callback */ return (* image_module->save_to_callback) (save_to_file_callback, filehandle, pixbuf, keys, values, error); } else { /* can't save */ g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNSUPPORTED_OPERATION, _("This build of gdk-pixbuf does not support saving the image format: %s"), type); return FALSE; } } #define TMP_FILE_BUF_SIZE 4096 static gboolean save_to_callback_with_tmp_file (GdkPixbufModule *image_module, GdkPixbuf *pixbuf, GdkPixbufSaveFunc save_func, gpointer user_data, gchar **keys, gchar **values, GError **error) { int fd; FILE *f = NULL; gboolean retval = FALSE; gchar *buf = NULL; gsize n; gchar *filename = NULL; buf = g_try_malloc (TMP_FILE_BUF_SIZE); if (buf == NULL) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Insufficient memory to save image to callback")); goto end; } fd = g_file_open_tmp ("gdkpixbuf-save-tmp.XXXXXX", &filename, error); if (fd == -1) goto end; f = fdopen (fd, "wb+"); if (f == NULL) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Failed to open temporary file")); goto end; } if (!(* image_module->save) (f, pixbuf, keys, values, error)) goto end; rewind (f); for (;;) { n = fread (buf, 1, TMP_FILE_BUF_SIZE, f); if (n > 0) { if (!save_func (buf, n, error, user_data)) goto end; } if (n != TMP_FILE_BUF_SIZE) break; } if (ferror (f)) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Failed to read from temporary file")); goto end; } retval = TRUE; end: /* cleanup and return retval */ if (f) fclose (f); if (filename) { unlink (filename); g_free (filename); } g_free (buf); return retval; } static gboolean gdk_pixbuf_real_save_to_callback (GdkPixbuf *pixbuf, GdkPixbufSaveFunc save_func, gpointer user_data, const char *type, gchar **keys, gchar **values, GError **error) { GdkPixbufModule *image_module = NULL; image_module = _gdk_pixbuf_get_named_module (type, error); if (image_module == NULL) return FALSE; if (image_module->module == NULL) if (!_gdk_pixbuf_load_module (image_module, error)) return FALSE; if (image_module->save_to_callback) { /* save normally */ return (* image_module->save_to_callback) (save_func, user_data, pixbuf, keys, values, error); } else if (image_module->save) { /* use a temporary file */ return save_to_callback_with_tmp_file (image_module, pixbuf, save_func, user_data, keys, values, error); } else { /* can't save */ g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNSUPPORTED_OPERATION, _("This build of gdk-pixbuf does not support saving the image format: %s"), type); return FALSE; } } /** * gdk_pixbuf_save: * @pixbuf: a #GdkPixbuf. * @filename: name of file to save. * @type: name of file format. * @error: return location for error, or %NULL * @Varargs: list of key-value save options * * Saves pixbuf to a file in format @type. By default, "jpeg", "png" and * "ico" are possible file formats to save in, but more formats may be * installed. The list of all writable formats can be determined in the * following way: * * * void add_if_writable (GdkPixbufFormat *data, GSList **list) * { * if (gdk_pixbuf_format_is_writable (data)) * *list = g_slist_prepend (*list, data); * } * * GSList *formats = gdk_pixbuf_get_formats (); * GSList *writable_formats = NULL; * g_slist_foreach (formats, add_if_writable, &writable_formats); * g_slist_free (formats); * * * If @error is set, %FALSE will be returned. Possible errors include * those in the #GDK_PIXBUF_ERROR domain and those in the #G_FILE_ERROR domain. * * The variable argument list should be %NULL-terminated; if not empty, * it should contain pairs of strings that modify the save * parameters. For example: * * gdk_pixbuf_save (pixbuf, handle, "jpeg", &error, * "quality", "100", NULL); * * * Currently only few parameters exist. JPEG images can be saved with a * "quality" parameter; its value should be in the range [0,100]. * Text chunks can be attached to PNG images by specifying parameters of * the form "tEXt::key", where key is an ASCII string of length 1-79. * The values are UTF-8 encoded strings. * ICO images can be saved in depth 16, 24, or 32, by using the "depth" * parameter. When the ICO saver is given "x_hot" and "y_hot" parameters, * it produces a CUR instead of an ICO. * * Return value: whether an error was set **/ gboolean gdk_pixbuf_save (GdkPixbuf *pixbuf, const char *filename, const char *type, GError **error, ...) { gchar **keys = NULL; gchar **values = NULL; va_list args; gboolean result; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); va_start (args, error); collect_save_options (args, &keys, &values); va_end (args); result = gdk_pixbuf_savev (pixbuf, filename, type, keys, values, error); g_strfreev (keys); g_strfreev (values); return result; } /** * gdk_pixbuf_savev: * @pixbuf: a #GdkPixbuf. * @filename: name of file to save. * @type: name of file format. * @option_keys: name of options to set, %NULL-terminated * @option_values: values for named options * @error: return location for error, or %NULL * * Saves pixbuf to a file in @type, which is currently "jpeg", "png" or "ico". * If @error is set, %FALSE will be returned. * See gdk_pixbuf_save () for more details. * * Return value: whether an error was set **/ gboolean gdk_pixbuf_savev (GdkPixbuf *pixbuf, const char *filename, const char *type, char **option_keys, char **option_values, GError **error) { FILE *f = NULL; gboolean result; g_return_val_if_fail (filename != NULL, FALSE); g_return_val_if_fail (type != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); f = fopen (filename, "wb"); if (f == NULL) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Failed to open '%s' for writing: %s"), filename, g_strerror (errno)); return FALSE; } result = gdk_pixbuf_real_save (pixbuf, f, type, option_keys, option_values, error); if (!result) { g_return_val_if_fail (error == NULL || *error != NULL, FALSE); fclose (f); return FALSE; } if (fclose (f) < 0) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Failed to close '%s' while writing image, all data may not have been saved: %s"), filename, g_strerror (errno)); return FALSE; } return TRUE; } /** * gdk_pixbuf_save_to_callback: * @pixbuf: a #GdkPixbuf. * @save_func: a function that is called to save each block of data that * the save routine generates. * @user_data: user data to pass to the save function. * @type: name of file format. * @error: return location for error, or %NULL * @Varargs: list of key-value save options * * Saves pixbuf in format @type by feeding the produced data to a * callback. Can be used when you want to store the image to something * other than a file, such as an in-memory buffer or a socket. * If @error is set, %FALSE will be returned. Possible errors * include those in the #GDK_PIXBUF_ERROR domain and whatever the save * function generates. * * See gdk_pixbuf_save() for more details. * * Return value: whether an error was set * * Since: 2.4 **/ gboolean gdk_pixbuf_save_to_callback (GdkPixbuf *pixbuf, GdkPixbufSaveFunc save_func, gpointer user_data, const char *type, GError **error, ...) { gchar **keys = NULL; gchar **values = NULL; va_list args; gboolean result; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); va_start (args, error); collect_save_options (args, &keys, &values); va_end (args); result = gdk_pixbuf_save_to_callbackv (pixbuf, save_func, user_data, type, keys, values, error); g_strfreev (keys); g_strfreev (values); return result; } /** * gdk_pixbuf_save_to_callbackv: * @pixbuf: a #GdkPixbuf. * @save_func: a function that is called to save each block of data that * the save routine generates. * @user_data: user data to pass to the save function. * @type: name of file format. * @option_keys: name of options to set, %NULL-terminated * @option_values: values for named options * @error: return location for error, or %NULL * * Saves pixbuf to a callback in format @type, which is currently "jpeg", * "png" or "ico". If @error is set, %FALSE will be returned. See * gdk_pixbuf_save_to_callback () for more details. * * Return value: whether an error was set * * Since: 2.4 **/ gboolean gdk_pixbuf_save_to_callbackv (GdkPixbuf *pixbuf, GdkPixbufSaveFunc save_func, gpointer user_data, const char *type, char **option_keys, char **option_values, GError **error) { gboolean result; g_return_val_if_fail (save_func != NULL, FALSE); g_return_val_if_fail (type != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); result = gdk_pixbuf_real_save_to_callback (pixbuf, save_func, user_data, type, option_keys, option_values, error); if (!result) { g_return_val_if_fail (error == NULL || *error != NULL, FALSE); return FALSE; } return TRUE; } /** * gdk_pixbuf_save_to_buffer: * @pixbuf: a #GdkPixbuf. * @buffer: location to receive a pointer to the new buffer. * @buffer_size: location to receive the size of the new buffer. * @type: name of file format. * @error: return location for error, or %NULL * @Varargs: list of key-value save options * * Saves pixbuf to a new buffer in format @type, which is currently "jpeg", * "png" or "ico". This is a convenience function that uses * gdk_pixbuf_save_to_callback() to do the real work. Note that the buffer * is not nul-terminated and may contain embedded nuls. * If @error is set, %FALSE will be returned and @string will be set to * %NULL. Possible errors include those in the #GDK_PIXBUF_ERROR * domain. * * See gdk_pixbuf_save() for more details. * * Return value: whether an error was set * * Since: 2.4 **/ gboolean gdk_pixbuf_save_to_buffer (GdkPixbuf *pixbuf, gchar **buffer, gsize *buffer_size, const char *type, GError **error, ...) { gchar **keys = NULL; gchar **values = NULL; va_list args; gboolean result; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); va_start (args, error); collect_save_options (args, &keys, &values); va_end (args); result = gdk_pixbuf_save_to_bufferv (pixbuf, buffer, buffer_size, type, keys, values, error); g_strfreev (keys); g_strfreev (values); return result; } struct SaveToBufferData { gchar *buffer; gsize len, max; }; static gboolean save_to_buffer_callback (const gchar *data, gsize count, GError **error, gpointer user_data) { struct SaveToBufferData *sdata = user_data; gchar *new_buffer; gsize new_max; if (sdata->len + count > sdata->max) { new_max = MAX (sdata->max*2, sdata->len + count); new_buffer = g_try_realloc (sdata->buffer, new_max); if (!new_buffer) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Insufficient memory to save image into a buffer")); return FALSE; } sdata->buffer = new_buffer; sdata->max = new_max; } memcpy (sdata->buffer + sdata->len, data, count); sdata->len += count; return TRUE; } /** * gdk_pixbuf_save_to_bufferv: * @pixbuf: a #GdkPixbuf. * @buffer: location to receive a pointer to the new buffer. * @buffer_size: location to receive the size of the new buffer. * @type: name of file format. * @option_keys: name of options to set, %NULL-terminated * @option_values: values for named options * @error: return location for error, or %NULL * * Saves pixbuf to a new buffer in format @type, which is currently "jpeg", * "png" or "ico". See gdk_pixbuf_save_to_buffer() for more details. * * Return value: whether an error was set * * Since: 2.4 **/ gboolean gdk_pixbuf_save_to_bufferv (GdkPixbuf *pixbuf, gchar **buffer, gsize *buffer_size, const char *type, char **option_keys, char **option_values, GError **error) { static const gint initial_max = 1024; struct SaveToBufferData sdata; *buffer = NULL; *buffer_size = 0; sdata.buffer = g_try_malloc (initial_max); sdata.max = initial_max; sdata.len = 0; if (!sdata.buffer) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Insufficient memory to save image into a buffer")); return FALSE; } if (!gdk_pixbuf_save_to_callbackv (pixbuf, save_to_buffer_callback, &sdata, type, option_keys, option_values, error)) { g_free (sdata.buffer); return FALSE; } *buffer = sdata.buffer; *buffer_size = sdata.len; return TRUE; } /** * gdk_pixbuf_format_get_name: * @format: a #GdkPixbufFormat * * Returns the name of the format. * * Return value: the name of the format. * * Since: 2.2 */ gchar * gdk_pixbuf_format_get_name (GdkPixbufFormat *format) { g_return_val_if_fail (format != NULL, NULL); return g_strdup (format->name); } /** * gdk_pixbuf_format_get_description: * @format: a #GdkPixbufFormat * * Returns a description of the format. * * Return value: a description of the format. * * Since: 2.2 */ gchar * gdk_pixbuf_format_get_description (GdkPixbufFormat *format) { gchar *domain; gchar *description; g_return_val_if_fail (format != NULL, NULL); if (format->domain != NULL) domain = format->domain; else domain = GETTEXT_PACKAGE; description = dgettext (domain, format->description); return g_strdup (description); } /** * gdk_pixbuf_format_get_mime_types: * @format: a #GdkPixbufFormat * * Returns the mime types supported by the format. * * Return value: a %NULL-terminated array of mime types which must be freed with * g_strfreev() when it is no longer needed. * * Since: 2.2 */ gchar ** gdk_pixbuf_format_get_mime_types (GdkPixbufFormat *format) { g_return_val_if_fail (format != NULL, NULL); return g_strdupv (format->mime_types); } /** * gdk_pixbuf_format_get_extensions: * @format: a #GdkPixbufFormat * * Returns the filename extensions typically used for files in the * given format. * * Return value: a %NULL-terminated array of filename extensions which must be * freed with g_strfreev() when it is no longer needed. * * Since: 2.2 */ gchar ** gdk_pixbuf_format_get_extensions (GdkPixbufFormat *format) { g_return_val_if_fail (format != NULL, NULL); return g_strdupv (format->extensions); } /** * gdk_pixbuf_format_is_writable: * @format: a #GdkPixbufFormat * * Returns whether pixbufs can be saved in the given format. * * Return value: whether pixbufs can be saved in the given format. * * Since: 2.2 */ gboolean gdk_pixbuf_format_is_writable (GdkPixbufFormat *format) { g_return_val_if_fail (format != NULL, FALSE); return (format->flags & GDK_PIXBUF_FORMAT_WRITABLE) != 0; } GdkPixbufFormat * _gdk_pixbuf_get_format (GdkPixbufModule *module) { g_return_val_if_fail (module != NULL, NULL); return module->info; } /** * gdk_pixbuf_get_formats: * * Obtains the available information about the image formats supported * by GdkPixbuf. * * Returns: A list of #GdkPixbufFormats describing the supported * image formats. The list should be freed when it is no longer needed, * but the structures themselves are owned by #GdkPixbuf and should not be * freed. * * Since: 2.2 */ GSList * gdk_pixbuf_get_formats (void) { GSList *result = NULL; GSList *modules; for (modules = get_file_formats (); modules; modules = g_slist_next (modules)) { GdkPixbufModule *module = (GdkPixbufModule *)modules->data; GdkPixbufFormat *info = _gdk_pixbuf_get_format (module); result = g_slist_prepend (result, info); } return result; }