/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * go-image.c: Image formats * * Copyright (C) 2004, 2005 Jody Goldberg (jody@gnome.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 * USA */ #include #include #include #include #include #include #include #include #include static GOImageFormatInfo *pixbuf_image_format_infos = NULL; static GHashTable *pixbuf_mimes = NULL; static unsigned pixbuf_format_nbr = 0; static gboolean pixbuf_format_done = FALSE; #define PIXBUF_IMAGE_FORMAT_OFFSET (1+GO_IMAGE_FORMAT_UNKNOWN) static void go_image_build_pixbuf_format_infos (void); /** * go_mime_to_image_format: * @mime_type: a mime type string * * returns: file extension for the given mime type. **/ char * go_mime_to_image_format (char const *mime_type) { guint i; const char* exceptions[] = { "image/svg", "svg", "image/svg+xml", "svg", "image/svg-xml", "svg", "image/vnd.adobe.svg+xml", "svg", "text/xml-svg", "svg", "image/x-wmf", "wmf", "image/x-emf", "emf", "application/pdf", "pdf", "application/postscript", "ps", "image/x-eps", "eps", }; for (i = 0; i < G_N_ELEMENTS (exceptions); i += 2) if (strcmp (mime_type, exceptions[i]) == 0) return g_strdup (exceptions[i + 1]); go_image_build_pixbuf_format_infos (); return g_strdup (g_hash_table_lookup (pixbuf_mimes, mime_type)); } /** * go_image_format_to_mime: * @format: a file extension string * * returns: corresponding mime type. **/ char * go_image_format_to_mime (char const *format) { char *ret = NULL; guint i; #ifdef GOFFICE_WITH_GTK GSList *ptr, *pixbuf_fmts; #endif static const char* const formats[] = { "svg", "image/svg,image/svg+xml", "wmf", "image/x-wmf", "emf", "image/x-emf", "pdf", "application/pdf", "ps", "application/postscript", "eps", "image/x-eps", }; if (format == NULL) return NULL; for (i = 0; i < G_N_ELEMENTS (formats); i += 2) if (strcmp (format, formats[i]) == 0) return g_strdup (formats[i + 1]); #ifdef GOFFICE_WITH_GTK /* Not a format we have special knowledge about - ask gdk-pixbuf */ pixbuf_fmts = gdk_pixbuf_get_formats (); for (ptr = pixbuf_fmts; ptr != NULL; ptr = ptr->next) { GdkPixbufFormat *pfmt = ptr->data; gchar *name = gdk_pixbuf_format_get_name (pfmt); int cmp = strcmp (format, name); g_free (name); if (cmp == 0) { gchar **mimes = gdk_pixbuf_format_get_mime_types (pfmt); ret = g_strjoinv (",", mimes); g_strfreev (mimes); break; } } g_slist_free (pixbuf_fmts); #endif return ret; } static GOImageFormatInfo const image_format_infos[GO_IMAGE_FORMAT_UNKNOWN] = { {GO_IMAGE_FORMAT_SVG, (char *) "svg", (char *) N_("SVG (vector graphics)"), (char *) "svg", FALSE, FALSE, TRUE}, {GO_IMAGE_FORMAT_PNG, (char *) "png", (char *) N_("PNG (raster graphics)"), (char *) "png", TRUE, TRUE, TRUE}, {GO_IMAGE_FORMAT_JPG, (char *) "jpeg", (char *) N_("JPEG (photograph)"), (char *) "jpg", TRUE, TRUE, FALSE}, {GO_IMAGE_FORMAT_PDF, (char *) "pdf", (char *) N_("PDF (portable document format)"), (char *) "pdf", FALSE, FALSE, TRUE}, {GO_IMAGE_FORMAT_PS, (char *) "ps", (char *) N_("PS (postscript)"), (char *) "ps", FALSE, TRUE, TRUE}, {GO_IMAGE_FORMAT_EMF, (char *) "emf", (char *) N_("EMF (extended metafile)"), (char *) "emf", FALSE, FALSE, TRUE}, {GO_IMAGE_FORMAT_WMF, (char *) "wmf", (char *) N_("WMF (windows metafile)"), (char *) "wmf", FALSE, FALSE, TRUE}, {GO_IMAGE_FORMAT_EPS, (char *) "eps", (char *) N_("EPS (encapsulated postscript)"), (char *) "eps", FALSE, TRUE, TRUE}, }; static void go_image_build_pixbuf_format_infos (void) { #ifdef GOFFICE_WITH_GTK GdkPixbufFormat *fmt; GSList *l, *pixbuf_fmts; GOImageFormatInfo *format_info; gchar **exts, **mimes; unsigned i, j; if (pixbuf_format_done) return; pixbuf_fmts = gdk_pixbuf_get_formats (); pixbuf_format_nbr = g_slist_length (pixbuf_fmts); if (pixbuf_format_nbr > 0) { pixbuf_image_format_infos = g_new (GOImageFormatInfo, pixbuf_format_nbr); pixbuf_mimes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); for (l = pixbuf_fmts, i = 1, format_info = pixbuf_image_format_infos; l != NULL; l = l->next, i++, format_info++) { fmt = (GdkPixbufFormat *)l->data; format_info->format = GO_IMAGE_FORMAT_UNKNOWN + i; format_info->name = gdk_pixbuf_format_get_name (fmt); format_info->desc = gdk_pixbuf_format_get_description (fmt); exts = gdk_pixbuf_format_get_extensions (fmt); format_info->ext = g_strdup (exts[0]); if (format_info->ext == NULL) format_info->ext = format_info->name; g_strfreev (exts); format_info->has_pixbuf_saver = gdk_pixbuf_format_is_writable (fmt); format_info->is_dpi_useful = FALSE; format_info->alpha_support = FALSE; mimes = gdk_pixbuf_format_get_mime_types (fmt); for (j = 0; mimes[j]; j++) { g_hash_table_insert (pixbuf_mimes, g_strdup((gpointer) mimes[j]), g_strdup((gpointer) format_info->name)); } g_strfreev (mimes); } } g_slist_free (pixbuf_fmts); #endif /* GOFFICE_WITH_GTK */ pixbuf_format_done = TRUE; } /** * go_image_get_format_info: * @format: a #GOImageFormat * * Retrieves infromation associated to @format. * * returns: a #GOImageFormatInfo struct. **/ GOImageFormatInfo const * go_image_get_format_info (GOImageFormat format) { if (format > GO_IMAGE_FORMAT_UNKNOWN) go_image_build_pixbuf_format_infos (); g_return_val_if_fail (format != GO_IMAGE_FORMAT_UNKNOWN && format <= GO_IMAGE_FORMAT_UNKNOWN + pixbuf_format_nbr, NULL); if (format < GO_IMAGE_FORMAT_UNKNOWN) return &image_format_infos[format]; return &pixbuf_image_format_infos[format - PIXBUF_IMAGE_FORMAT_OFFSET]; } /** * go_image_get_format_from_name: * @name: a string * * returns: corresponding #GOImageFormat. **/ GOImageFormat go_image_get_format_from_name (char const *name) { unsigned i; go_image_build_pixbuf_format_infos (); for (i = 0; i < GO_IMAGE_FORMAT_UNKNOWN; i++) { if (strcmp (name, image_format_infos[i].name) == 0) return image_format_infos[i].format; } for (i = 0; i < pixbuf_format_nbr; i++) { if (strcmp (name, pixbuf_image_format_infos[i].name) == 0) return pixbuf_image_format_infos[i].format; } g_warning ("[GOImage::get_format_from_name] Unknown format name (%s)", name); return GO_IMAGE_FORMAT_UNKNOWN; } /** * go_image_get_formats_with_pixbuf_saver: * * returns: a list of #GOImageFormat that can be created from a pixbuf. **/ GSList * go_image_get_formats_with_pixbuf_saver (void) { GSList *list = NULL; unsigned i; for (i = 0; i < GO_IMAGE_FORMAT_UNKNOWN; i++) if (image_format_infos[i].has_pixbuf_saver) list = g_slist_prepend (list, GUINT_TO_POINTER (i)); /* TODO: before enabling this code, we must remove duplicate in pixbuf_image_format_infos */ #if 0 go_image_build_pixbuf_format_infos (); for (i = 0; i < pixbuf_format_nbr; i++) { if (pixbuf_image_format_infos[i].has_pixbuf_saver) list = g_slist_prepend (list, GUINT_TO_POINTER (i + PIXBUF_IMAGE_FORMAT_OFFSET)); } #endif return list; } /********************************* * GOImage object implementation * *********************************/ static GObjectClass *parent_klass; struct _GOImage { GObject parent; guint8 *data; guint width, height, rowstride; gboolean target_cairo; cairo_t *cairo; #ifdef GOFFICE_WITH_GTK GdkPixbuf *pixbuf, *thumbnail; #else void *pixbuf; #endif char *name; }; enum { IMAGE_PROP_0, IMAGE_PROP_WIDTH, IMAGE_PROP_HEIGHT, #ifdef GOFFICE_WITH_GTK IMAGE_PROP_PIXBUF, #endif }; static void pixbuf_to_cairo (GOImage *image) { #ifdef GOFFICE_WITH_GTK unsigned char *src, *dst; g_return_if_fail (GO_IS_IMAGE (image) && image->data && image->pixbuf); src = gdk_pixbuf_get_pixels (image->pixbuf); dst = image->data; g_return_if_fail (gdk_pixbuf_get_rowstride (image->pixbuf) == (int) image->rowstride); go_cairo_convert_data_from_pixbuf (dst, src, image->width, image->height, image->rowstride); #endif } #ifdef GOFFICE_WITH_GTK static void cairo_to_pixbuf (GOImage *image) { unsigned char *src, *dst; g_return_if_fail (GO_IS_IMAGE (image) && image->data && image->pixbuf); dst = gdk_pixbuf_get_pixels (image->pixbuf); src = image->data; g_return_if_fail (gdk_pixbuf_get_rowstride (image->pixbuf) == (int) image->rowstride); go_cairo_convert_data_to_pixbuf (dst, src, image->width, image->height, image->rowstride); } #endif static void go_image_set_property (GObject *obj, guint param_id, GValue const *value, GParamSpec *pspec) { GOImage *image = GO_IMAGE (obj); gboolean size_changed = FALSE; guint n; switch (param_id) { case IMAGE_PROP_WIDTH: n = g_value_get_uint (value); if (n != image->width) { image->width = n; size_changed = TRUE; } break; case IMAGE_PROP_HEIGHT: n = g_value_get_uint (value); if (n != image->height) { image->height = n; size_changed = TRUE; } break; #ifdef GOFFICE_WITH_GTK case IMAGE_PROP_PIXBUF: { GdkPixbuf *pixbuf = GDK_PIXBUF (g_value_get_object (value)); if (!GDK_IS_PIXBUF (pixbuf)) break; if (!gdk_pixbuf_get_has_alpha (pixbuf)) pixbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); else g_object_ref (pixbuf); if (image->pixbuf) g_object_unref (image->pixbuf); image->pixbuf = pixbuf; g_free (image->data); image->data = NULL; image->width = gdk_pixbuf_get_width (pixbuf); image->height = gdk_pixbuf_get_height (pixbuf); image->rowstride = gdk_pixbuf_get_rowstride (pixbuf); image->target_cairo = FALSE; if (image->thumbnail) { g_object_unref (image->thumbnail); image->thumbnail = NULL; } } break; #endif default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec); return; /* NOTE : RETURN */ } if (size_changed) { if (image->pixbuf) { g_object_unref (image->pixbuf); image->pixbuf = NULL; } g_free (image->data); /* GOImage only supports pixbuf with alpha values at the moment */ image->rowstride = image->width * 4; image->data = g_new0 (guint8, image->height * image->rowstride); image->target_cairo = TRUE; } } static void go_image_get_property (GObject *obj, guint param_id, GValue *value, GParamSpec *pspec) { GOImage *image = GO_IMAGE (obj); switch (param_id) { case IMAGE_PROP_WIDTH: g_value_set_uint (value, image->width); break; case IMAGE_PROP_HEIGHT: g_value_set_uint (value, image->height); break; #ifdef GOFFICE_WITH_GTK case IMAGE_PROP_PIXBUF: if (image->target_cairo && image->pixbuf) { cairo_to_pixbuf (image); image->target_cairo = FALSE; } g_value_set_object (value, image->pixbuf); break; #endif default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec); return; /* NOTE : RETURN */ } } static void go_image_finalize (GObject *obj) { GOImage *image = GO_IMAGE (obj); g_free (image->data); #ifdef GOFFICE_WITH_GTK if (image->pixbuf) g_object_unref (image->pixbuf); if (image->thumbnail) g_object_unref (image->thumbnail); #endif g_free (image->name); (parent_klass->finalize) (obj); } typedef GObjectClass GOImageClass; static void go_image_class_init (GOImageClass *klass) { klass->finalize = go_image_finalize; klass->set_property = go_image_set_property; klass->get_property = go_image_get_property; parent_klass = g_type_class_peek_parent (klass); g_object_class_install_property (klass, IMAGE_PROP_WIDTH, g_param_spec_uint ("width", _("Width"), _("Image width in pixels"), 0, G_MAXUINT16, 0, G_PARAM_READWRITE)); g_object_class_install_property (klass, IMAGE_PROP_HEIGHT, g_param_spec_uint ("height", _("Height"), _("Image height in pixels"), 0, G_MAXUINT16, 0, G_PARAM_READWRITE)); #ifdef GOFFICE_WITH_GTK g_object_class_install_property (klass, IMAGE_PROP_PIXBUF, g_param_spec_object ("pixbuf", _("Pixbuf"), _("GdkPixbuf object from which the GOImage is built"), GDK_TYPE_PIXBUF, G_PARAM_READWRITE)); #endif } GSF_CLASS (GOImage, go_image, go_image_class_init, NULL, G_TYPE_OBJECT) cairo_t * go_image_get_cairo (GOImage *image) { cairo_surface_t *surface ; cairo_t *cairo; g_return_val_if_fail (GO_IS_IMAGE (image), NULL); if (image->data == NULL && image->pixbuf == NULL) return NULL; if (image->data == NULL) { /* image built from a pixbuf */ image->data = g_new0 (guint8, image->height * image->rowstride); } if (!image->target_cairo) { pixbuf_to_cairo (image); image->target_cairo = TRUE; } surface = cairo_image_surface_create_for_data ( image->data, CAIRO_FORMAT_ARGB32, image->width, image->height, image->rowstride); cairo = cairo_create (surface); cairo_surface_destroy (surface); image->target_cairo = TRUE; return cairo; } static void cb_surface_destroyed (void *data) { GOImage *image = GO_IMAGE (data); /* We no longer need image->data. */ g_object_unref (image); } /** * go_image_create_cairo_pattern: * @image: a GOImage. * * returns: a cairo_pattern usable for cairo_set_source. * * Note: this function has lifespan issues. The resulting pattern in only * valid until (a) a pixbuf is set for the, or (b) a pixbuf is _read_ from * the image. In either of these cases, the pattern must have been * destroyed beforehand. In particular, if the pattern has been attached * to a surface, that surface must either be finished itself, or have had * a new pattern attached. See #632439. */ cairo_pattern_t * go_image_create_cairo_pattern (GOImage *image) { cairo_surface_t *surface ; cairo_pattern_t *pat; static const cairo_user_data_key_t key; g_return_val_if_fail (GO_IS_IMAGE (image), NULL); if (image->data == NULL && image->pixbuf == NULL) return NULL; if (image->data == NULL) { /* image built from a pixbuf */ image->data = g_new0 (guint8, image->height * image->rowstride); } if (!image->target_cairo) { pixbuf_to_cairo (image); image->target_cairo = TRUE; } surface = cairo_image_surface_create_for_data (image->data, CAIRO_FORMAT_ARGB32, image->width, image->height, image->rowstride); g_object_ref (image); cairo_surface_set_user_data (surface, &key, image, cb_surface_destroyed); pat = cairo_pattern_create_for_surface (surface); cairo_surface_destroy (surface); return pat; } #ifdef GOFFICE_WITH_GTK GOImage * go_image_new_from_pixbuf (GdkPixbuf *pixbuf) { return g_object_new (GO_TYPE_IMAGE, "pixbuf", pixbuf, NULL); } GdkPixbuf * go_image_get_pixbuf (GOImage *image) { g_return_val_if_fail (image != NULL, NULL); if (!image->pixbuf) { if (image->width == 0 || image->height == 0 || image->data == NULL) return NULL; image->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, image->width, image->height); } if (image->target_cairo) { cairo_to_pixbuf (image); image->target_cairo = FALSE; } return image->pixbuf; } #define THUMBNAIL_SIZE 64 GdkPixbuf * go_image_get_thumbnail (GOImage *image) { g_return_val_if_fail (image != NULL, NULL); if (!image->pixbuf) go_image_get_pixbuf (image); if (!image->pixbuf) return NULL; if (!image->thumbnail) { int w, h; if (image->width <= THUMBNAIL_SIZE && image->height <= THUMBNAIL_SIZE) return image->pixbuf; if (image->width >= image->height) { w = THUMBNAIL_SIZE; h = THUMBNAIL_SIZE * image->height / image->width; } else { h = THUMBNAIL_SIZE; w = THUMBNAIL_SIZE * image->width / image->height; } image->thumbnail = gdk_pixbuf_scale_simple (image->pixbuf, w, h, GDK_INTERP_HYPER); } return image->thumbnail; } #endif GOImage * go_image_new_from_file (const char *filename, GError **error) { #ifdef GOFFICE_WITH_GTK GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file (filename, error); if (pixbuf) { GOImage *image = g_object_new (GO_TYPE_IMAGE, "pixbuf", pixbuf, NULL); g_object_unref (pixbuf); image->target_cairo = FALSE; return image; } #else g_warning ("go_image_new_from_file not implemented!"); #endif return NULL; } guint8 * go_image_get_pixels (GOImage *image) { g_return_val_if_fail (image, NULL); return image->data; } int go_image_get_rowstride (GOImage *image) { g_return_val_if_fail (image, 0); return image->rowstride; } void go_image_fill (GOImage *image, GOColor color) { guint32 val; guint8 *dst; unsigned i, j; g_return_if_fail (image); dst = image->data; if (image->target_cairo) val = (GO_COLOR_UINT_R (color) << 8) + (GO_COLOR_UINT_G (color) << 16) + (GO_COLOR_UINT_B (color) << 24) + GO_COLOR_UINT_A (color); else val = color; for (i = 0; i < image->height; i++) { for (j = 0; j < image->width; j++) *((guint32*) dst) = val; dst += image->rowstride - image->width * 4; } } void go_image_set_name (GOImage *image, char const *name) { g_free (image->name); image->name = (name)? g_strdup (name): NULL; } char const * go_image_get_name (GOImage *image) { g_return_val_if_fail (GO_IS_IMAGE (image), NULL); return image->name; } gboolean go_image_same_pixbuf (GOImage *first, GOImage *second) { #ifdef GOFFICE_WITH_GTK void *pixels1, *pixels2; int size; g_return_val_if_fail (GO_IS_IMAGE (first), FALSE); g_return_val_if_fail (GO_IS_IMAGE (second), FALSE); if (!first->pixbuf) go_image_get_pixbuf (first); if (!second->pixbuf) go_image_get_pixbuf (second); if (!first->pixbuf || !second->pixbuf) return FALSE; if (gdk_pixbuf_get_n_channels (first->pixbuf) != gdk_pixbuf_get_n_channels (second->pixbuf)) return FALSE; if (gdk_pixbuf_get_colorspace (first->pixbuf) != gdk_pixbuf_get_colorspace (second->pixbuf)) return FALSE; if (gdk_pixbuf_get_bits_per_sample (first->pixbuf) != gdk_pixbuf_get_bits_per_sample (second->pixbuf)) return FALSE; if (gdk_pixbuf_get_has_alpha (first->pixbuf) != gdk_pixbuf_get_has_alpha (second->pixbuf)) return FALSE; if (gdk_pixbuf_get_width (first->pixbuf) != gdk_pixbuf_get_width (second->pixbuf)) return FALSE; if (gdk_pixbuf_get_height (first->pixbuf) != gdk_pixbuf_get_height (second->pixbuf)) return FALSE; if (gdk_pixbuf_get_rowstride (first->pixbuf) != gdk_pixbuf_get_rowstride (second->pixbuf)) return FALSE; pixels1 = gdk_pixbuf_get_pixels (first->pixbuf); pixels2 = gdk_pixbuf_get_pixels (second->pixbuf); size = gdk_pixbuf_get_rowstride (first->pixbuf) * gdk_pixbuf_get_height (first->pixbuf); return !memcmp (pixels1, pixels2, size); #else return FALSE; #endif } void go_image_save (GOImage *image, GsfXMLOut *output) { g_return_if_fail (GO_IS_IMAGE (image) && image->name); gsf_xml_out_start_element (output, "GOImage"); gsf_xml_out_add_cstr (output, "name", image->name); gsf_xml_out_add_int (output, "width", image->width); gsf_xml_out_add_int (output, "height", image->height); gsf_xml_out_add_int (output, "rowstride", image->rowstride); gsf_xml_out_add_base64 (output, NULL, go_image_get_pixels (image), image->height * image->rowstride); gsf_xml_out_end_element (output); } void go_image_load_attrs (GOImage *image, G_GNUC_UNUSED GsfXMLIn *xin, xmlChar const **attrs) { xmlChar const **attr; g_return_if_fail (image != NULL); for (attr = attrs; attr != NULL && attr[0] && attr[1] ; attr += 2) if (0 == strcmp ((const char*)attr[0], "width")) image->width = strtol ((const char*)attr[1], NULL, 10); else if (0 == strcmp ((const char*)attr[0], "height")) image->height= strtol ((const char*)attr[1], NULL, 10); else if (0 == strcmp ((const char*)attr[0], "rowstride")) image->rowstride = strtol ((const char*)attr[1], NULL, 10); } void go_image_load_data (GOImage *image, GsfXMLIn *xin) { int length; length = gsf_base64_decode_simple ((guint8*) xin->content->str, strlen(xin->content->str)); image->data = g_memdup (xin->content->str, length); image->target_cairo = TRUE; }