summaryrefslogtreecommitdiff
path: root/librsvg/rsvg-pixbuf.c
diff options
context:
space:
mode:
Diffstat (limited to 'librsvg/rsvg-pixbuf.c')
-rw-r--r--librsvg/rsvg-pixbuf.c465
1 files changed, 465 insertions, 0 deletions
diff --git a/librsvg/rsvg-pixbuf.c b/librsvg/rsvg-pixbuf.c
new file mode 100644
index 00000000..198a5642
--- /dev/null
+++ b/librsvg/rsvg-pixbuf.c
@@ -0,0 +1,465 @@
+/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 sts=4 expandtab: */
+/*
+ rsvg-file-util.c: SAX-based renderer for SVG files into a GdkPixbuf.
+
+ Copyright (C) 2000 Eazel, Inc.
+ Copyright (C) 2002 Dom Lachowicz <cinamod@hotmail.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library 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.
+
+ Author: Raph Levien <raph@artofcode.com>
+*/
+
+/**
+ * SECTION: rsvg-pixbuf
+ * @short_description: How to render SVGs into GdkPixbufs, for easy use in GTK+
+ * applications
+ *
+ * GdkPixbuf is a library for image loading and manipulation. It is part of the
+ * cross-platform GTK+ widget toolkit.
+ */
+
+#include "config.h"
+#include "rsvg-private.h"
+#include "rsvg-io.h"
+#include "rsvg-size-callback.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static GdkPixbuf *
+rsvg_pixbuf_from_stdio_file_with_size_data (const char *data,
+ gsize data_len,
+ struct RsvgSizeCallbackData *cb_data,
+ gchar * base_uri,
+ GError ** error)
+{
+ RsvgHandle *handle;
+ GdkPixbuf *retval;
+
+ handle = rsvg_handle_new ();
+
+ if (!handle) {
+ g_set_error (error, rsvg_error_quark (), 0, _("Error creating SVG reader"));
+ return NULL;
+ }
+
+ rsvg_handle_set_size_callback (handle, _rsvg_size_callback, cb_data, NULL);
+ rsvg_handle_set_base_uri (handle, base_uri);
+
+ if (!rsvg_handle_write (handle, (guchar *) data, data_len, error)) {
+ (void) rsvg_handle_close (handle, NULL);
+ g_object_unref (handle);
+ return NULL;
+ }
+
+ if (!rsvg_handle_close (handle, error)) {
+ g_object_unref (handle);
+ return NULL;
+ }
+
+ retval = rsvg_handle_get_pixbuf (handle);
+ g_object_unref (handle);
+
+ return retval;
+}
+
+static GdkPixbuf *
+rsvg_pixbuf_from_file_with_size_data (const gchar * file_name,
+ struct RsvgSizeCallbackData *cb_data,
+ GError ** error)
+{
+ GdkPixbuf *pixbuf;
+ char *data;
+ gsize data_len;
+ GFile *file;
+ gchar *base_uri;
+
+ file = g_file_new_for_path (file_name);
+ base_uri = g_file_get_uri (file);
+ if (!base_uri) {
+ g_object_unref (file);
+ return NULL;
+ }
+
+ data = _rsvg_io_acquire_data (base_uri, base_uri, NULL, &data_len, NULL, error);
+
+ if (data) {
+ pixbuf = rsvg_pixbuf_from_stdio_file_with_size_data (data, data_len,
+ cb_data, base_uri, error);
+ g_free (data);
+ } else {
+ pixbuf = NULL;
+ }
+
+ g_free (base_uri);
+ g_object_unref (file);
+
+ return pixbuf;
+}
+
+/**
+ * rsvg_pixbuf_from_file:
+ * @file_name: A file name
+ * @error: return location for errors
+ *
+ * Loads a new #GdkPixbuf from @file_name and returns it. The caller must
+ * assume the reference to the reurned pixbuf. If an error occurred, @error is
+ * set and %NULL is returned.
+ *
+ * Return value: A newly allocated #GdkPixbuf, or %NULL
+ * Deprecated: Set up a cairo matrix and use rsvg_handle_new_from_file() + rsvg_handle_render_cairo() instead.
+ **/
+GdkPixbuf *
+rsvg_pixbuf_from_file (const gchar * file_name, GError ** error)
+{
+ return rsvg_pixbuf_from_file_at_size (file_name, -1, -1, error);
+}
+
+/**
+ * rsvg_pixbuf_from_file_at_zoom:
+ * @file_name: A file name
+ * @x_zoom: The horizontal zoom factor
+ * @y_zoom: The vertical zoom factor
+ * @error: return location for errors
+ *
+ * Loads a new #GdkPixbuf from @file_name and returns it. This pixbuf is scaled
+ * from the size indicated by the file by a factor of @x_zoom and @y_zoom. The
+ * caller must assume the reference to the returned pixbuf. If an error
+ * occurred, @error is set and %NULL is returned.
+ *
+ * Return value: A newly allocated #GdkPixbuf, or %NULL
+ * Deprecated: Set up a cairo matrix and use rsvg_handle_new_from_file() + rsvg_handle_render_cairo() instead.
+ **/
+GdkPixbuf *
+rsvg_pixbuf_from_file_at_zoom (const gchar * file_name,
+ double x_zoom, double y_zoom, GError ** error)
+{
+ struct RsvgSizeCallbackData data;
+
+ g_return_val_if_fail (file_name != NULL, NULL);
+ g_return_val_if_fail (x_zoom > 0.0 && y_zoom > 0.0, NULL);
+
+ data.type = RSVG_SIZE_ZOOM;
+ data.x_zoom = x_zoom;
+ data.y_zoom = y_zoom;
+ data.keep_aspect_ratio = FALSE;
+
+ return rsvg_pixbuf_from_file_with_size_data (file_name, &data, error);
+}
+
+/**
+ * rsvg_pixbuf_from_file_at_zoom_with_max:
+ * @file_name: A file name
+ * @x_zoom: The horizontal zoom factor
+ * @y_zoom: The vertical zoom factor
+ * @max_width: The requested max width
+ * @max_height: The requested max heigh
+ * @error: return location for errors
+ *
+ * Loads a new #GdkPixbuf from @file_name and returns it. This pixbuf is scaled
+ * from the size indicated by the file by a factor of @x_zoom and @y_zoom. If the
+ * resulting pixbuf would be larger than max_width/max_heigh it is uniformly scaled
+ * down to fit in that rectangle. The caller must assume the reference to the
+ * returned pixbuf. If an error occurred, @error is set and %NULL is returned.
+ *
+ * Return value: A newly allocated #GdkPixbuf, or %NULL
+ * Deprecated: Set up a cairo matrix and use rsvg_handle_new_from_file() + rsvg_handle_render_cairo() instead.
+ **/
+GdkPixbuf *
+rsvg_pixbuf_from_file_at_zoom_with_max (const gchar * file_name,
+ double x_zoom,
+ double y_zoom,
+ gint max_width, gint max_height, GError ** error)
+{
+ struct RsvgSizeCallbackData data;
+
+ g_return_val_if_fail (file_name != NULL, NULL);
+ g_return_val_if_fail (x_zoom > 0.0 && y_zoom > 0.0, NULL);
+
+ data.type = RSVG_SIZE_ZOOM_MAX;
+ data.x_zoom = x_zoom;
+ data.y_zoom = y_zoom;
+ data.width = max_width;
+ data.height = max_height;
+ data.keep_aspect_ratio = FALSE;
+
+ return rsvg_pixbuf_from_file_with_size_data (file_name, &data, error);
+}
+
+/**
+ * rsvg_pixbuf_from_file_at_size:
+ * @file_name: A file name
+ * @width: The new width, or -1
+ * @height: The new height, or -1
+ * @error: return location for errors
+ *
+ * Loads a new #GdkPixbuf from @file_name and returns it. This pixbuf is scaled
+ * from the size indicated to the new size indicated by @width and @height. If
+ * either of these are -1, then the default size of the image being loaded is
+ * used. The caller must assume the reference to the returned pixbuf. If an
+ * error occurred, @error is set and %NULL is returned.
+ *
+ * Return value: A newly allocated #GdkPixbuf, or %NULL
+ * Deprecated: Set up a cairo matrix and use rsvg_handle_new_from_file() + rsvg_handle_render_cairo() instead.
+ **/
+GdkPixbuf *
+rsvg_pixbuf_from_file_at_size (const gchar * file_name, gint width, gint height, GError ** error)
+{
+ struct RsvgSizeCallbackData data;
+
+ data.type = RSVG_SIZE_WH;
+ data.width = width;
+ data.height = height;
+ data.keep_aspect_ratio = FALSE;
+
+ return rsvg_pixbuf_from_file_with_size_data (file_name, &data, error);
+}
+
+/**
+ * rsvg_pixbuf_from_file_at_max_size:
+ * @file_name: A file name
+ * @max_width: The requested max width
+ * @max_height: The requested max heigh
+ * @error: return location for errors
+ *
+ * Loads a new #GdkPixbuf from @file_name and returns it. This pixbuf is uniformly
+ * scaled so that the it fits into a rectangle of size max_width * max_height. The
+ * caller must assume the reference to the returned pixbuf. If an error occurred,
+ * @error is set and %NULL is returned.
+ *
+ * Return value: A newly allocated #GdkPixbuf, or %NULL
+ * Deprecated: Set up a cairo matrix and use rsvg_handle_new_from_file() + rsvg_handle_render_cairo() instead.
+ **/
+GdkPixbuf *
+rsvg_pixbuf_from_file_at_max_size (const gchar * file_name,
+ gint max_width, gint max_height, GError ** error)
+{
+ struct RsvgSizeCallbackData data;
+
+ data.type = RSVG_SIZE_WH_MAX;
+ data.width = max_width;
+ data.height = max_height;
+ data.keep_aspect_ratio = FALSE;
+
+ return rsvg_pixbuf_from_file_with_size_data (file_name, &data, error);
+}
+
+cairo_surface_t *
+rsvg_cairo_surface_from_pixbuf (const GdkPixbuf *pixbuf)
+{
+ gint width, height, gdk_rowstride, n_channels, cairo_rowstride;
+ guchar *gdk_pixels, *cairo_pixels;
+ cairo_format_t format;
+ cairo_surface_t *surface;
+ int j;
+
+ if (pixbuf == NULL)
+ return NULL;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ gdk_pixels = gdk_pixbuf_get_pixels (pixbuf);
+ gdk_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+
+ if (n_channels == 3)
+ format = CAIRO_FORMAT_RGB24;
+ else
+ format = CAIRO_FORMAT_ARGB32;
+
+ surface = cairo_image_surface_create (format, width, height);
+ if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS) {
+ cairo_surface_destroy (surface);
+ return NULL;
+ }
+
+ cairo_pixels = cairo_image_surface_get_data (surface);
+ cairo_rowstride = cairo_image_surface_get_stride (surface);
+
+ if (n_channels == 3) {
+ for (j = height; j; j--) {
+ guchar *p = gdk_pixels;
+ guchar *q = cairo_pixels;
+ guchar *end = p + 3 * width;
+
+ while (p < end) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ q[0] = p[2];
+ q[1] = p[1];
+ q[2] = p[0];
+#else
+ q[1] = p[0];
+ q[2] = p[1];
+ q[3] = p[2];
+#endif
+ p += 3;
+ q += 4;
+ }
+
+ gdk_pixels += gdk_rowstride;
+ cairo_pixels += cairo_rowstride;
+ }
+ } else {
+ for (j = height; j; j--) {
+ guchar *p = gdk_pixels;
+ guchar *q = cairo_pixels;
+ guchar *end = p + 4 * width;
+ guint t1, t2, t3;
+
+#define MULT(d,c,a,t) G_STMT_START { t = c * a + 0x7f; d = ((t >> 8) + t) >> 8; } G_STMT_END
+
+ while (p < end) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ MULT (q[0], p[2], p[3], t1);
+ MULT (q[1], p[1], p[3], t2);
+ MULT (q[2], p[0], p[3], t3);
+ q[3] = p[3];
+#else
+ q[0] = p[3];
+ MULT (q[1], p[0], p[3], t1);
+ MULT (q[2], p[1], p[3], t2);
+ MULT (q[3], p[2], p[3], t3);
+#endif
+
+ p += 4;
+ q += 4;
+ }
+
+#undef MULT
+ gdk_pixels += gdk_rowstride;
+ cairo_pixels += cairo_rowstride;
+ }
+ }
+
+ cairo_surface_mark_dirty (surface);
+ return surface;
+}
+
+/* Copied from gtk+/gdk/gdkpixbuf-drawable.c, LGPL 2+.
+ *
+ * Copyright (C) 1999 Michael Zucchi
+ *
+ * Authors: Michael Zucchi <zucchi@zedzone.mmc.com.au>
+ * Cody Russell <bratsche@dfw.net>
+ * Federico Mena-Quintero <federico@gimp.org>
+ */
+
+static void
+convert_alpha (guchar *dest_data,
+ int dest_stride,
+ guchar *src_data,
+ int src_stride,
+ int src_x,
+ int src_y,
+ int width,
+ int height)
+{
+ int x, y;
+
+ src_data += src_stride * src_y + src_x * 4;
+
+ for (y = 0; y < height; y++) {
+ guint32 *src = (guint32 *) src_data;
+
+ for (x = 0; x < width; x++) {
+ guint alpha = src[x] >> 24;
+
+ if (alpha == 0) {
+ dest_data[x * 4 + 0] = 0;
+ dest_data[x * 4 + 1] = 0;
+ dest_data[x * 4 + 2] = 0;
+ } else {
+ dest_data[x * 4 + 0] = (((src[x] & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
+ dest_data[x * 4 + 1] = (((src[x] & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha;
+ dest_data[x * 4 + 2] = (((src[x] & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha;
+ }
+ dest_data[x * 4 + 3] = alpha;
+ }
+
+ src_data += src_stride;
+ dest_data += dest_stride;
+ }
+}
+
+static void
+convert_no_alpha (guchar *dest_data,
+ int dest_stride,
+ guchar *src_data,
+ int src_stride,
+ int src_x,
+ int src_y,
+ int width,
+ int height)
+{
+ int x, y;
+
+ src_data += src_stride * src_y + src_x * 4;
+
+ for (y = 0; y < height; y++) {
+ guint32 *src = (guint32 *) src_data;
+
+ for (x = 0; x < width; x++) {
+ dest_data[x * 3 + 0] = src[x] >> 16;
+ dest_data[x * 3 + 1] = src[x] >> 8;
+ dest_data[x * 3 + 2] = src[x];
+ }
+
+ src_data += src_stride;
+ dest_data += dest_stride;
+ }
+}
+
+GdkPixbuf *
+rsvg_cairo_surface_to_pixbuf (cairo_surface_t *surface)
+{
+ cairo_content_t content;
+ GdkPixbuf *dest;
+ int width, height;
+
+ /* General sanity checks */
+ g_assert (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE);
+
+ width = cairo_image_surface_get_width (surface);
+ height = cairo_image_surface_get_height (surface);
+ if (width == 0 || height == 0)
+ return NULL;
+
+ content = cairo_surface_get_content (surface) | CAIRO_CONTENT_COLOR;
+ dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ !!(content & CAIRO_CONTENT_ALPHA),
+ 8,
+ width, height);
+
+ if (gdk_pixbuf_get_has_alpha (dest))
+ convert_alpha (gdk_pixbuf_get_pixels (dest),
+ gdk_pixbuf_get_rowstride (dest),
+ cairo_image_surface_get_data (surface),
+ cairo_image_surface_get_stride (surface),
+ 0, 0,
+ width, height);
+ else
+ convert_no_alpha (gdk_pixbuf_get_pixels (dest),
+ gdk_pixbuf_get_rowstride (dest),
+ cairo_image_surface_get_data (surface),
+ cairo_image_surface_get_stride (surface),
+ 0, 0,
+ width, height);
+
+ return dest;
+}