medit/moo/libfoocanvas/foo-canvas-pixbuf.c
2007-05-26 02:43:55 -05:00

823 lines
20 KiB
C

/* GNOME libraries - GdkPixbuf item for the GNOME canvas
*
* Copyright (C) 1999 The Free Software Foundation
*
* Author: Federico Mena-Quintero <federico@gimp.org>
*
* This library 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 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library 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 <config.h>
#include <math.h>
#include <libfoocanvas/foo-canvas.h>
#include <libfoocanvas/foo-canvas-util.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "foo-canvas-pixbuf.h"
/* Private part of the FooCanvasPixbuf structure */
typedef struct {
/* Our gdk-pixbuf */
GdkPixbuf *pixbuf, *pixbuf_scaled;
/* Width value */
double width;
/* Height value */
double height;
/* X translation */
double x;
/* Y translation */
double y;
/* Whether dimensions are set and whether they are in pixels or units */
guint width_set : 1;
guint width_in_pixels : 1;
guint height_set : 1;
guint height_in_pixels : 1;
guint x_in_pixels : 1;
guint y_in_pixels : 1;
/* Whether the pixbuf has changed */
guint need_pixbuf_update : 1;
/* Whether the transformation or size have changed */
guint need_xform_update : 1;
/* Should the point method ignore transparent areas */
guint point_ignores_alpha : 1;
/* Anchor */
GtkAnchorType anchor;
/* Approximation method used for transformations */
GdkInterpType interp_type;
} PixbufPrivate;
/* Object argument IDs */
enum {
PROP_0,
PROP_PIXBUF,
PROP_WIDTH,
PROP_WIDTH_SET,
PROP_WIDTH_IN_PIXELS,
PROP_HEIGHT,
PROP_HEIGHT_SET,
PROP_HEIGHT_IN_PIXELS,
PROP_X,
PROP_X_IN_PIXELS,
PROP_Y,
PROP_Y_IN_PIXELS,
PROP_ANCHOR,
PROP_INTERP_TYPE,
PROP_POINT_IGNORES_ALPHA
};
static void foo_canvas_pixbuf_class_init (FooCanvasPixbufClass *class);
static void foo_canvas_pixbuf_init (FooCanvasPixbuf *cpb);
static void foo_canvas_pixbuf_destroy (GtkObject *object);
static void foo_canvas_pixbuf_set_property (GObject *object,
guint param_id,
const GValue *value,
GParamSpec *pspec);
static void foo_canvas_pixbuf_get_property (GObject *object,
guint param_id,
GValue *value,
GParamSpec *pspec);
static void foo_canvas_pixbuf_update (FooCanvasItem *item,
double i2w_dx, double i2w_dy,
FooCanvasUpdateFlags flags);
static void foo_canvas_pixbuf_draw (FooCanvasItem *item, GdkDrawable *drawable,
GdkEventExpose *expose);
static double foo_canvas_pixbuf_point (FooCanvasItem *item, double x, double y, int cx, int cy,
FooCanvasItem **actual_item);
static void foo_canvas_pixbuf_translate (FooCanvasItem *item, double dx, double dy);
static void foo_canvas_pixbuf_bounds (FooCanvasItem *item,
double *x1, double *y1, double *x2, double *y2);
G_DEFINE_TYPE(FooCanvasPixbuf, foo_canvas_pixbuf, FOO_TYPE_CANVAS_ITEM)
/* Class initialization function for the pixbuf canvas item */
static void
foo_canvas_pixbuf_class_init (FooCanvasPixbufClass *class)
{
GObjectClass *gobject_class;
GtkObjectClass *object_class;
FooCanvasItemClass *item_class;
gobject_class = (GObjectClass *) class;
object_class = (GtkObjectClass *) class;
item_class = (FooCanvasItemClass *) class;
gobject_class->set_property = foo_canvas_pixbuf_set_property;
gobject_class->get_property = foo_canvas_pixbuf_get_property;
g_object_class_install_property
(gobject_class,
PROP_PIXBUF,
g_param_spec_object ("pixbuf", NULL, NULL,
GDK_TYPE_PIXBUF,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_WIDTH,
g_param_spec_double ("width", NULL, NULL,
-G_MAXDOUBLE, G_MAXDOUBLE, 0,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_WIDTH_SET,
g_param_spec_boolean ("width-set", NULL, NULL,
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_WIDTH_IN_PIXELS,
g_param_spec_boolean ("width-in-pixels", NULL, NULL,
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_HEIGHT,
g_param_spec_double ("height", NULL, NULL,
-G_MAXDOUBLE, G_MAXDOUBLE, 0,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_HEIGHT_SET,
g_param_spec_boolean ("height-set", NULL, NULL,
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_HEIGHT_IN_PIXELS,
g_param_spec_boolean ("height-in-pixels", NULL, NULL,
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_X,
g_param_spec_double ("x", NULL, NULL,
-G_MAXDOUBLE, G_MAXDOUBLE, 0,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_X_IN_PIXELS,
g_param_spec_boolean ("x-in-pixels", NULL, NULL,
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_Y,
g_param_spec_double ("y", NULL, NULL,
-G_MAXDOUBLE, G_MAXDOUBLE, 0,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_Y_IN_PIXELS,
g_param_spec_boolean ("y-in-pixels", NULL, NULL,
FALSE,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_ANCHOR,
g_param_spec_enum ("anchor", NULL, NULL,
GTK_TYPE_ANCHOR_TYPE,
GTK_ANCHOR_NW,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_INTERP_TYPE,
g_param_spec_enum ("interp-type", NULL, NULL,
GDK_TYPE_INTERP_TYPE,
GDK_INTERP_BILINEAR,
G_PARAM_READWRITE));
g_object_class_install_property
(gobject_class,
PROP_POINT_IGNORES_ALPHA,
g_param_spec_boolean ("point-ignores-alpha", NULL, NULL,
FALSE,
G_PARAM_READWRITE));
object_class->destroy = foo_canvas_pixbuf_destroy;
item_class->update = foo_canvas_pixbuf_update;
item_class->draw = foo_canvas_pixbuf_draw;
item_class->point = foo_canvas_pixbuf_point;
item_class->translate = foo_canvas_pixbuf_translate;
item_class->bounds = foo_canvas_pixbuf_bounds;
}
/* Object initialization function for the pixbuf canvas item */
static void
foo_canvas_pixbuf_init (FooCanvasPixbuf *gcp)
{
PixbufPrivate *priv;
priv = g_new0 (PixbufPrivate, 1);
gcp->priv = priv;
priv->width = 0.0;
priv->height = 0.0;
priv->x = 0.0;
priv->y = 0.0;
priv->anchor = GTK_ANCHOR_NW;
priv->interp_type = GDK_INTERP_BILINEAR;
priv->point_ignores_alpha = FALSE;
}
/* Destroy handler for the pixbuf canvas item */
static void
foo_canvas_pixbuf_destroy (GtkObject *object)
{
FooCanvasItem *item;
FooCanvasPixbuf *gcp;
PixbufPrivate *priv;
g_return_if_fail (object != NULL);
g_return_if_fail (FOO_IS_CANVAS_PIXBUF (object));
item = FOO_CANVAS_ITEM (object);
gcp = (FOO_CANVAS_PIXBUF (object));
priv = gcp->priv;
/* remember, destroy can be run multiple times! */
if (priv) {
foo_canvas_item_request_redraw (item);
if (priv->pixbuf)
g_object_unref (priv->pixbuf);
if (priv->pixbuf_scaled)
g_object_unref (priv->pixbuf_scaled);
g_free (priv);
gcp->priv = NULL;
}
if (GTK_OBJECT_CLASS (foo_canvas_pixbuf_parent_class)->destroy)
GTK_OBJECT_CLASS (foo_canvas_pixbuf_parent_class)->destroy (object);
}
/* Set_property handler for the pixbuf canvas item */
static void
foo_canvas_pixbuf_set_property (GObject *object,
guint param_id,
const GValue *value,
GParamSpec *pspec)
{
FooCanvasItem *item;
FooCanvasPixbuf *gcp;
PixbufPrivate *priv;
GdkPixbuf *pixbuf;
double val;
g_return_if_fail (object != NULL);
g_return_if_fail (FOO_IS_CANVAS_PIXBUF (object));
item = FOO_CANVAS_ITEM (object);
gcp = FOO_CANVAS_PIXBUF (object);
priv = gcp->priv;
switch (param_id) {
case PROP_PIXBUF:
if (g_value_get_object (value))
pixbuf = GDK_PIXBUF (g_value_get_object (value));
else
pixbuf = NULL;
if (pixbuf != priv->pixbuf) {
if (pixbuf) {
g_return_if_fail
(gdk_pixbuf_get_colorspace (pixbuf) == GDK_COLORSPACE_RGB);
g_return_if_fail
(gdk_pixbuf_get_n_channels (pixbuf) == 3
|| gdk_pixbuf_get_n_channels (pixbuf) == 4);
g_return_if_fail
(gdk_pixbuf_get_bits_per_sample (pixbuf) == 8);
g_object_ref (pixbuf);
}
if (priv->pixbuf)
g_object_unref (priv->pixbuf);
priv->pixbuf = pixbuf;
if (priv->pixbuf_scaled) {
g_object_unref (priv->pixbuf_scaled);
priv->pixbuf_scaled = NULL;
}
}
priv->need_pixbuf_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_WIDTH:
val = g_value_get_double (value);
g_return_if_fail (val >= 0.0);
priv->width = val;
priv->need_xform_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_WIDTH_SET:
priv->width_set = g_value_get_boolean (value);
priv->need_xform_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_WIDTH_IN_PIXELS:
priv->width_in_pixels = g_value_get_boolean (value);
priv->need_xform_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_HEIGHT:
val = g_value_get_double (value);
g_return_if_fail (val >= 0.0);
priv->height = val;
priv->need_xform_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_HEIGHT_SET:
priv->height_set = g_value_get_boolean (value);
priv->need_xform_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_HEIGHT_IN_PIXELS:
priv->height_in_pixels = g_value_get_boolean (value);
priv->need_xform_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_X:
priv->x = g_value_get_double (value);
priv->need_xform_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_X_IN_PIXELS:
priv->x_in_pixels = g_value_get_boolean (value);
priv->need_xform_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_Y:
priv->y = g_value_get_double (value);
priv->need_xform_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_Y_IN_PIXELS:
priv->y_in_pixels = g_value_get_boolean (value);
priv->need_xform_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_ANCHOR:
priv->anchor = g_value_get_enum (value);
priv->need_xform_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_INTERP_TYPE:
priv->interp_type = g_value_get_enum (value);
priv->need_xform_update = TRUE;
foo_canvas_item_request_update (item);
break;
case PROP_POINT_IGNORES_ALPHA:
priv->point_ignores_alpha = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
break;
}
}
/* Get_property handler for the pixbuf canvasi item */
static void
foo_canvas_pixbuf_get_property (GObject *object,
guint param_id,
GValue *value,
GParamSpec *pspec)
{
FooCanvasPixbuf *gcp;
PixbufPrivate *priv;
g_return_if_fail (object != NULL);
g_return_if_fail (FOO_IS_CANVAS_PIXBUF (object));
gcp = FOO_CANVAS_PIXBUF (object);
priv = gcp->priv;
switch (param_id) {
case PROP_PIXBUF:
g_value_set_object (value, G_OBJECT (priv->pixbuf));
break;
case PROP_WIDTH:
g_value_set_double (value, priv->width);
break;
case PROP_WIDTH_SET:
g_value_set_boolean (value, priv->width_set);
break;
case PROP_WIDTH_IN_PIXELS:
g_value_set_boolean (value, priv->width_in_pixels);
break;
case PROP_HEIGHT:
g_value_set_double (value, priv->height);
break;
case PROP_HEIGHT_SET:
g_value_set_boolean (value, priv->height_set);
break;
case PROP_HEIGHT_IN_PIXELS:
g_value_set_boolean (value, priv->height_in_pixels);
break;
case PROP_X:
g_value_set_double (value, priv->x);
break;
case PROP_X_IN_PIXELS:
g_value_set_boolean (value, priv->x_in_pixels);
break;
case PROP_Y:
g_value_set_double (value, priv->y);
break;
case PROP_Y_IN_PIXELS:
g_value_set_boolean (value, priv->y_in_pixels);
break;
case PROP_ANCHOR:
g_value_set_enum (value, priv->anchor);
break;
case PROP_INTERP_TYPE:
g_value_set_enum (value, priv->interp_type);
break;
case PROP_POINT_IGNORES_ALPHA:
g_value_set_boolean (value, priv->point_ignores_alpha);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
break;
}
}
/* Bounds and utilities */
/* Recomputes the bounding box of a pixbuf canvas item. The horizontal and
* vertical dimensions may be specified in units or pixels, separately, so we
* have to compute the components individually for each dimension.
*
* Returns the coordinates with respect to the parent items coordinates.
*/
static void
compute_bounding_box (FooCanvasPixbuf *gcp,
double i2w_dx, double i2w_dy,
double *bbox_x0, double *bbox_y0,
double *bbox_x1, double *bbox_y1)
{
FooCanvasItem *item;
PixbufPrivate *priv;
double x, y;
double width, height;
item = FOO_CANVAS_ITEM (gcp);
priv = gcp->priv;
if (!priv->pixbuf) {
*bbox_x0 = *bbox_y0 = *bbox_x1 = *bbox_y1 = 0.0;
return;
}
if (priv->x_in_pixels) {
x = i2w_dx + priv->x / item->canvas->pixels_per_unit;
} else {
x = i2w_dx + priv->x;
}
if (priv->y_in_pixels) {
y = i2w_dy + priv->y / item->canvas->pixels_per_unit;
} else {
y = i2w_dy + priv->y;
}
if (priv->width_set) {
width = priv->width;
} else {
width = gdk_pixbuf_get_width (priv->pixbuf);
}
if (priv->width_in_pixels)
width /= item->canvas->pixels_per_unit;
if (priv->height_set) {
height = priv->height;
} else {
height = gdk_pixbuf_get_height (priv->pixbuf);
}
if (priv->height_in_pixels)
height /= item->canvas->pixels_per_unit;
switch (priv->anchor) {
case GTK_ANCHOR_NW:
case GTK_ANCHOR_W:
case GTK_ANCHOR_SW:
break;
case GTK_ANCHOR_N:
case GTK_ANCHOR_CENTER:
case GTK_ANCHOR_S:
x -= width / 2.0;
break;
case GTK_ANCHOR_NE:
case GTK_ANCHOR_E:
case GTK_ANCHOR_SE:
x -= width;
break;
default:
break;
}
switch (priv->anchor) {
case GTK_ANCHOR_NW:
case GTK_ANCHOR_N:
case GTK_ANCHOR_NE:
break;
case GTK_ANCHOR_W:
case GTK_ANCHOR_CENTER:
case GTK_ANCHOR_E:
y -= height / 2.0;
break;
case GTK_ANCHOR_SW:
case GTK_ANCHOR_S:
case GTK_ANCHOR_SE:
y -= height;
break;
default:
break;
}
*bbox_x0 = x;
*bbox_y0 = y;
*bbox_x1 = x + width;
*bbox_y1 = y + height;
}
/* Update sequence */
/* Update handler for the pixbuf canvas item */
static void
foo_canvas_pixbuf_update (FooCanvasItem *item,
double i2w_dx, double i2w_dy,
FooCanvasUpdateFlags flags)
{
FooCanvasPixbuf *gcp;
PixbufPrivate *priv;
double bbox_x0, bbox_y0, bbox_x1, bbox_y1;
int w, h;
gcp = FOO_CANVAS_PIXBUF (item);
priv = gcp->priv;
if (FOO_CANVAS_ITEM_CLASS(foo_canvas_pixbuf_parent_class)->update)
FOO_CANVAS_ITEM_CLASS(foo_canvas_pixbuf_parent_class)->update (item, i2w_dx, i2w_dy, flags);
/* If we need a pixbuf update, or if the item changed visibility to
* shown, recompute the bounding box.
*/
if (priv->need_pixbuf_update || priv->need_xform_update ||
(flags & FOO_CANVAS_UPDATE_DEEP)) {
foo_canvas_item_request_redraw (item);
compute_bounding_box (gcp, i2w_dx, i2w_dy,
&bbox_x0, &bbox_y0,
&bbox_x1, &bbox_y1);
foo_canvas_w2c_d (item->canvas,
bbox_x0, bbox_y0,
&item->x1, &item->y1);
foo_canvas_w2c_d (item->canvas,
bbox_x1, bbox_y1,
&item->x2, &item->y2);
item->x1 = floor (item->x1);
item->y1 = floor (item->y1);
item->x2 = ceil (item->x2);
item->y2 = ceil (item->y2);
#ifdef FOO_CANVAS_PIXBUF_VERBOSE
g_print ("BBox is %g %g %g %g\n", item->x1, item->y1, item->x2, item->y2);
#endif
if (priv->pixbuf) {
w = item->x2 - item->x1;
h = item->y2 - item->y1;
if (priv->pixbuf_scaled)
g_object_unref (priv->pixbuf_scaled);
if (gdk_pixbuf_get_width (priv->pixbuf) != w ||
gdk_pixbuf_get_height (priv->pixbuf) != h)
priv->pixbuf_scaled = gdk_pixbuf_scale_simple (
priv->pixbuf, w, h, priv->interp_type);
else
priv->pixbuf_scaled = g_object_ref (priv->pixbuf);
}
foo_canvas_item_request_redraw (item);
priv->need_pixbuf_update = FALSE;
priv->need_xform_update = FALSE;
}
}
/* Draw handler for the pixbuf canvas item */
static void
foo_canvas_pixbuf_draw (FooCanvasItem *item, GdkDrawable *drawable,
GdkEventExpose *expose)
{
FooCanvasPixbuf *gcp;
PixbufPrivate *priv;
GdkRectangle display_rect, draw_rect;
GdkRegion *draw_region;
int w, h;
gcp = FOO_CANVAS_PIXBUF (item);
priv = gcp->priv;
if (!priv->pixbuf)
return;
/* Compute the area we need to repaint */
w = item->x2 - item->x1;
h = item->y2 - item->y1;
display_rect.x = item->x1;
display_rect.y = item->y1;
display_rect.width = w;
display_rect.height = h;
draw_region = gdk_region_rectangle (&display_rect);
gdk_region_intersect (draw_region, expose->region);
if (!gdk_region_empty (draw_region)) {
gdk_region_get_clipbox (draw_region, &draw_rect);
gdk_draw_pixbuf (drawable, NULL, priv->pixbuf_scaled,
/* pixbuf 0, 0 is at pix_rect.x, pix_rect.y */
draw_rect.x - display_rect.x,
draw_rect.y - display_rect.y,
draw_rect.x,
draw_rect.y,
draw_rect.width,
draw_rect.height,
GDK_RGB_DITHER_NORMAL, 0, 0);
}
gdk_region_destroy (draw_region);
}
/* Point handler for the pixbuf canvas item */
static double
foo_canvas_pixbuf_point (FooCanvasItem *item, double x, double y,
G_GNUC_UNUSED int cx, G_GNUC_UNUSED int cy,
FooCanvasItem **actual_item)
{
FooCanvasPixbuf *gcp;
PixbufPrivate *priv;
double x1, y1, x2, y2;
int px, py;
double no_hit;
guchar *src;
GdkPixbuf *pixbuf;
gcp = FOO_CANVAS_PIXBUF (item);
priv = gcp->priv;
pixbuf = priv->pixbuf;
*actual_item = item;
no_hit = item->canvas->pixels_per_unit * 2 + 10;
if (!priv->pixbuf)
return no_hit;
compute_bounding_box (gcp, 0.0, 0.0,
&x1, &y1, &x2, &y2);
if (x < x1 || x >= x2 ||
y < y1 || y >= y2)
return no_hit;
if (!gdk_pixbuf_get_has_alpha (pixbuf) || priv->point_ignores_alpha)
return 0.0;
px = (x - x1) * gdk_pixbuf_get_width (pixbuf) / (x2 - x1);
py = (y - y1) * gdk_pixbuf_get_height (pixbuf) / (y2 - y1);
src = gdk_pixbuf_get_pixels (pixbuf) +
py * gdk_pixbuf_get_rowstride (pixbuf) +
px * gdk_pixbuf_get_n_channels (pixbuf);
if (src[3] < 128)
return no_hit;
else
return 0.0;
}
static void
foo_canvas_pixbuf_translate (FooCanvasItem *item, double dx, double dy)
{
FooCanvasPixbuf *gcp;
PixbufPrivate *priv;
gcp = FOO_CANVAS_PIXBUF (item);
priv = gcp->priv;
if (priv->x_in_pixels) {
priv->x += dx * item->canvas->pixels_per_unit;
} else {
priv->x += dx;
}
if (priv->y_in_pixels) {
priv->y += dy * item->canvas->pixels_per_unit;
} else {
priv->y += dy;
}
priv->need_xform_update = TRUE;
}
/* Bounds handler for the pixbuf canvas item */
static void
foo_canvas_pixbuf_bounds (FooCanvasItem *item, double *x1, double *y1, double *x2, double *y2)
{
FooCanvasPixbuf *gcp;
PixbufPrivate *priv;
gcp = FOO_CANVAS_PIXBUF (item);
priv = gcp->priv;
if (!priv->pixbuf) {
*x1 = *y1 = *x2 = *y2 = 0.0;
return;
}
compute_bounding_box (gcp, 0.0, 0.0,
x1, y1, x2, y2);
}