/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
#include <config.h>
#include "hippo-canvas-theme-image.h"
#ifdef HAVE_LIBRSVG
#include <librsvg/rsvg.h>
#include <librsvg/rsvg-cairo.h>
#endif
typedef enum {
THEME_IMAGE_SURFACE,
THEME_IMAGE_SVG
} ThemeImageType;
struct _HippoCanvasThemeImage {
GObject parent;
ThemeImageType type;
union {
#ifdef HAVE_LIBRSVG
RsvgHandle *svg;
#endif
cairo_surface_t *surface;
} u;
int border_top;
int border_right;
int border_bottom;
int border_left;
};
struct _HippoCanvasThemeImageClass {
GObjectClass parent_class;
};
G_DEFINE_TYPE(HippoCanvasThemeImage, hippo_canvas_theme_image, G_TYPE_OBJECT)
GQuark
hippo_canvas_theme_image_error_quark (void)
{
return g_quark_from_static_string ("hippo-canvas-theme-image-error-quark");
}
static void
hippo_canvas_theme_image_finalize(GObject *object)
{
HippoCanvasThemeImage *image = HIPPO_CANVAS_THEME_IMAGE(object);
switch (image->type) {
case THEME_IMAGE_SURFACE:
if (image->u.surface) {
cairo_surface_destroy(image->u.surface);
image->u.surface = NULL;
}
break;
case THEME_IMAGE_SVG:
#ifdef HAVE_LIBRSVG
if (image->u.svg) {
g_object_unref(image->u.svg);
image->u.svg = NULL;
}
#endif
break;
}
G_OBJECT_CLASS(hippo_canvas_theme_image_parent_class)->finalize(object);
}
static void
hippo_canvas_theme_image_class_init(HippoCanvasThemeImageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = hippo_canvas_theme_image_finalize;
}
static void
hippo_canvas_theme_image_init(HippoCanvasThemeImage *image)
{
}
HippoCanvasThemeImage *
hippo_canvas_theme_image_new (const char *filename,
int border_top,
int border_right,
int border_bottom,
int border_left,
GError **error)
{
HippoCanvasThemeImage *image;
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
image = g_object_new(HIPPO_TYPE_CANVAS_THEME_IMAGE, NULL);
#ifdef HAVE_LIBRSVG
if (g_str_has_suffix(filename, ".svg") || g_str_has_suffix(filename, ".SVG")) {
image->type = THEME_IMAGE_SVG;
image->u.svg = rsvg_handle_new_from_file(filename, error);
if (image->u.svg == NULL) {
/* librsvg does a horrible job setting error, it's probably NULL */
if (*error == NULL)
g_set_error(error,
HIPPO_CANVAS_THEME_IMAGE_ERROR,
HIPPO_CANVAS_THEME_IMAGE_ERROR_FAILED,
"Failed to read SVG image '%s'", filename);
goto fail;
}
} else
#endif
if (g_str_has_suffix(filename, ".png") || g_str_has_suffix(filename, ".PNG")) {
image->type = THEME_IMAGE_SURFACE;
image->u.surface = cairo_image_surface_create_from_png(filename);
if (image->u.surface == NULL) {
g_set_error(error,
HIPPO_CANVAS_THEME_IMAGE_ERROR,
HIPPO_CANVAS_THEME_IMAGE_ERROR_FAILED,
"Failed to read PNG image '%s'", filename);
goto fail;
}
} else {
g_set_error(error,
HIPPO_CANVAS_THEME_IMAGE_ERROR,
HIPPO_CANVAS_THEME_IMAGE_ERROR_FAILED,
"Unknown filetype for image '%s'", filename);
goto fail;
}
image->border_top = border_top;
image->border_right = border_right;
image->border_bottom = border_bottom;
image->border_left = border_left;
return image;
fail:
g_object_unref(image);
return NULL;
}
void
hippo_canvas_theme_image_render (HippoCanvasThemeImage *image,
cairo_t *cr,
int x,
int y,
int width,
int height)
{
int source_width = 0;
int source_height = 0;
int source_x1 = 0, source_x2 = 0, source_y1 = 0, source_y2 = 0;
int dest_x1 = 0, dest_x2 = 0, dest_y1 = 0, dest_y2 = 0;
int i, j;
/* To draw a theme image, we divide the source and destination into 9
* pieces and draw each piece separately. (Some pieces may not exist
* if we have 0-width borders, in which case they'll be skipped)
*
* i=0 i=1 i=2
* border_left border_right
* +------------+--------------------+--------------+
* j=0: border_top | | | |
* +------------+--------------------+--------------+
* j=1 | | | |
* +------------+--------------------+--------------+
* j=2: border_bottom | | | |
* +------------+--------------------+--------------+
*
*/
switch (image->type) {
case THEME_IMAGE_SURFACE:
source_width = cairo_image_surface_get_width(image->u.surface);
source_height = cairo_image_surface_get_height(image->u.surface);
break;
case THEME_IMAGE_SVG:
{
#ifdef HAVE_LIBRSVG
RsvgDimensionData dimensions;
rsvg_handle_get_dimensions(image->u.svg, &dimensions);
source_width = dimensions.width;
source_height = dimensions.height;
#endif
break;
}
}
for (j = 0; j < 3; j++) {
switch (j) {
case 0:
source_y1 = 0;
source_y2 = image->border_top;
dest_y1 = y;
dest_y2 = y + image->border_top;
break;
case 1:
source_y1 = image->border_top;
source_y2 = source_height - image->border_bottom;
dest_y1 = y + image->border_top;
dest_y2 = y + height - image->border_bottom;
break;
case 2:
source_y1 = source_height - image->border_bottom;
source_y2 = source_height;
dest_y1 = y + height - image->border_bottom;
dest_y2 = y + height;
break;
}
if (dest_y2 <= dest_y1)
continue;
/* pixbuf-theme-engine has a nice interpretation of source_y2 == source_y1,
* dest_y2 != dest_y1, which is to linearly interpolate between the surrounding
* areas. We could do that for the surface case by setting
*
* source_y1 == y - 0.5
* source_y2 == y + 0.5
*
* but it's hard for the SVG case. source_y2 < source_y1 is pathological ... someone
* specified borders that sum up larger than the image.
*/
if (source_y2 <= source_y1)
continue;
for (i = 0; i < 3; i++) {
switch (i) {
case 0:
source_x1 = 0;
source_x2 = image->border_left;
dest_x1 = x;
dest_x2 = x + image->border_left;
break;
case 1:
source_x1 = image->border_left;
source_x2 = source_width - image->border_right;
dest_x1 = x + image->border_left;
dest_x2 = x + width - image->border_right;
break;
case 2:
source_x1 = source_width - image->border_right;
source_x2 = source_width;
dest_x1 = x + width - image->border_right;
dest_x2 = x + width;
break;
}
if (dest_x2 <= dest_x1)
continue;
if (source_x2 <= source_x1)
continue;
cairo_save(cr);
cairo_rectangle(cr, dest_x1, dest_y1, dest_x2 - dest_x1, dest_y2 - dest_y1);
cairo_clip(cr);
cairo_translate(cr, dest_x1, dest_y1);
cairo_scale(cr,
(double)(dest_x2 - dest_x1) / (source_x2 - source_x1),
(double)(dest_y2 - dest_y1) / (source_y2 - source_y1));
switch (image->type) {
case THEME_IMAGE_SURFACE:
cairo_set_source_surface(cr, image->u.surface, - source_x1, - source_y1);
cairo_paint(cr);
break;
case THEME_IMAGE_SVG:
#ifdef HAVE_LIBRSVG
cairo_translate(cr, - source_x1, - source_y1);
rsvg_handle_render_cairo(image->u.svg, cr);
#endif
break;
}
cairo_restore(cr);
}
}
}