Render SVG scaled to default icon size
authorRicardo Mones <ricardo@mones.org>
Mon, 30 Jan 2017 22:22:20 +0000 (23:22 +0100)
committerRicardo Mones <ricardo@mones.org>
Tue, 7 Feb 2017 19:02:58 +0000 (20:02 +0100)
Default icon sizes are parsed from icon data, which is suboptimal
since these sizes can be known at compile time.

src/stock_pixmap.c

index 947b2e6088de8f4f900b96b3db904ba4c36af531..3a59a36dc9cdd6d0a600808bc2292064cb9b0050 100644 (file)
@@ -494,6 +494,163 @@ GtkWidget *stock_pixmap_widget(StockPixmap icon)
        return NULL;
 }
 
+#ifdef HAVE_SVG
+/*
+ * Renders a SVG into a Cairo context at the given dimensions keeping
+ * the aspect ratio.
+ *
+ * Adapted from https://developer.gnome.org/rsvg/2.40/RsvgHandle.html
+ * #rsvg-handle-set-size-callback
+ */
+void render_scaled_proportionally(RsvgHandle *handle, cairo_t *cr, int width, int height)
+{
+       RsvgDimensionData dimensions;
+       double x_factor, y_factor;
+       double scale_factor;
+
+       rsvg_handle_get_dimensions(handle, &dimensions);
+
+       x_factor = (double) width / dimensions.width;
+       y_factor = (double) height / dimensions.height;
+
+       scale_factor = MIN(x_factor, y_factor);
+
+       cairo_scale(cr, scale_factor, scale_factor);
+
+       rsvg_handle_render_cairo(handle, cr);
+}
+
+/*
+ * Generates a new Pixbuf from a Cairo context of the given dimensions.
+ *
+ * Adapted from https://gist.github.com/bert/985903
+ */
+GdkPixbuf *pixbuf_from_cairo(cairo_t *cr, gboolean alpha, int width, int height)
+{
+       gint p_stride, /* Pixbuf stride value */
+            p_n_channels, /* RGB -> 3, RGBA -> 4 */
+            s_stride; /* Surface stride value */
+       guchar *p_pixels, /* Pixbuf's pixel data */
+              *s_pixels; /* Surface's pixel data */
+       cairo_surface_t *surface; /* Temporary image surface */
+       GdkPixbuf *pixbuf; /* Returned pixbuf */
+
+       /* Create pixbuf */
+       pixbuf = gdk_pixbuf_new
+                       (GDK_COLORSPACE_RGB, alpha, 8, width, height);
+       if (pixbuf == NULL) {
+               g_warning("failed to create a new %d x %d pixbuf", width, height);
+               return NULL;
+       }
+       /* Obtain surface from where pixel values will be copied */
+       surface = cairo_get_target(cr);
+       if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
+               g_warning("invalid cairo surface for copying");
+               return NULL;
+       }
+       /* Inspect pixbuf */
+       g_object_get(G_OBJECT(pixbuf),
+               "rowstride", &p_stride,
+               "n-channels", &p_n_channels,
+               "pixels", &p_pixels,
+               NULL);
+       /* and surface */
+       s_stride = cairo_image_surface_get_stride(surface);
+       s_pixels = cairo_image_surface_get_data(surface);
+
+       /* Copy pixel data from surface to pixbuf */
+       while (height--) {
+               gint i;
+               guchar *p_iter = p_pixels, *s_iter = s_pixels;
+               for (i = 0; i < width; i++) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+                       /* Pixbuf: RGB(A) - Surface: BGRA */
+                       gdouble alpha_factor = (gdouble)0xff / s_iter[3];
+                       p_iter[0] = (guchar)( s_iter[2] * alpha_factor + .5 );
+                       p_iter[1] = (guchar)( s_iter[1] * alpha_factor + .5 );
+                       p_iter[2] = (guchar)( s_iter[0] * alpha_factor + .5 );
+                       if (p_n_channels == 4)
+                                p_iter[3] = s_iter[3];
+#elif G_BYTE_ORDER == G_BIG_ENDIAN
+                       /* Pixbuf: RGB(A) - Surface: ARGB */
+                       gdouble alpha_factor = (gdouble)0xff / s_iter[0];
+                       p_iter[0] = (guchar)( s_iter[1] * alpha_factor + .5 );
+                       p_iter[1] = (guchar)( s_iter[2] * alpha_factor + .5 );
+                       p_iter[2] = (guchar)( s_iter[3] * alpha_factor + .5 );
+                       if (p_n_channels == 4)
+                               p_iter[3] = s_iter[0];
+#else /* PDP endianness */
+                       /* Pixbuf: RGB(A) - Surface: RABG */
+                       gdouble alpha_factor = (gdouble)0xff / s_iter[1];
+                       p_iter[0] = (guchar)( s_iter[0] * alpha_factor + .5 );
+                       p_iter[1] = (guchar)( s_iter[3] * alpha_factor + .5 );
+                       p_iter[2] = (guchar)( s_iter[2] * alpha_factor + .5 );
+                       if (p_n_channels == 4)
+                               p_iter[3] = s_iter[1];
+#endif
+                       s_iter += 4;
+                       p_iter += p_n_channels;
+               }
+               s_pixels += s_stride;
+               p_pixels += p_stride;
+       }
+       /* Destroy context */
+       cairo_destroy(cr);
+
+       return pixbuf;
+}
+
+/*
+ * Renders a SVG file into a pixbuf with the dimensions of the
+ * given pixmap data (optionally with alpha channel).
+ */
+GdkPixbuf *pixbuf_from_svg_like_icon(char *filename, GError **error, StockPixmapData *icondata, gboolean alpha)
+{
+       int width, height;
+       cairo_surface_t *surface;
+       cairo_t *context;
+       RsvgHandle *handle;
+
+       cm_return_val_if_fail(filename != NULL, NULL);
+       cm_return_val_if_fail(icondata != NULL, NULL);
+
+       if (sscanf((icondata->data)[0], "%d %d ", &width, &height) != 2) {
+               g_warning("failed reading icondata width and height");
+               return NULL;
+       }
+
+       /* load SVG file */
+       handle = rsvg_handle_new_from_file(filename, error);
+       if (handle == NULL) {
+               g_warning("failed loading SVG '%s': %s (%d)", filename,
+                               (*error)->message, (*error)->code);
+               return NULL;
+       }
+       /* create drawing context */
+       surface = cairo_image_surface_create(
+                       alpha? CAIRO_FORMAT_ARGB32: CAIRO_FORMAT_RGB24,
+                       width, height);
+       if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
+               g_warning("failed to create a cairo surface: %s",
+                               cairo_status_to_string(cairo_surface_status(surface)));
+               g_object_unref(handle);
+               return NULL;
+       }
+       context = cairo_create(surface);
+       cairo_surface_destroy(surface);
+       if (cairo_status(context) != CAIRO_STATUS_SUCCESS) {
+               g_warning("failed to create a cairo context: %s",
+                               cairo_status_to_string(cairo_status(context)));
+               cairo_destroy(context);
+               return NULL;
+       }
+       /* render SVG */
+       render_scaled_proportionally(handle, context, width, height);
+       /* build result and destroy context */
+       return pixbuf_from_cairo(context, alpha, width, height);
+}
+#endif
+
 /*!
  *\brief
  */
@@ -532,7 +689,7 @@ try_next_extension:
                                        GError *err = NULL;
 #ifdef HAVE_SVG
                                        if (!strncmp(extension[i], ".svg", 4)) {
-                                               pix = rsvg_pixbuf_from_file(icon_file_name, &err);
+                                               pix = pixbuf_from_svg_like_icon(icon_file_name, &err, pix_d, TRUE);
                                        } else {
                                                pix = gdk_pixbuf_new_from_file(icon_file_name, &err);
                                        }