merge gnomecanvas into ardour tree, so that we can fix our own bugs and not wait...
[ardour.git] / libs / gnomecanvas / libgnomecanvas / gnome-canvas-clipgroup.c
1 #define GNOME_CANVAS_CLIPGROUP_C
2
3 /* Clipping group for GnomeCanvas
4  *
5  * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget.  Tk is
6  * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties.
7  *
8  * Copyright (C) 1998,1999 The Free Software Foundation
9  *
10  * Author:
11  *          Lauris Kaplinski <lauris@ximian.com>
12  */
13
14 /* These includes are set up for standalone compile. If/when this codebase
15    is integrated into libgnomeui, the includes will need to change. */
16
17 #include <math.h>
18 #include <string.h>
19
20 #include <gtk/gtk.h>
21
22 #include <libart_lgpl/art_misc.h>
23 #include <libart_lgpl/art_rect.h>
24 #include <libart_lgpl/art_vpath.h>
25 #include <libart_lgpl/art_bpath.h>
26 #include <libart_lgpl/art_vpath.h>
27 #include <libart_lgpl/art_vpath_bpath.h>
28 #include <libart_lgpl/art_svp.h>
29 #include <libart_lgpl/art_svp_vpath.h>
30 #include <libart_lgpl/art_rect_svp.h>
31 #include <libart_lgpl/art_gray_svp.h>
32 #include <libart_lgpl/art_svp_intersect.h>
33 #include <libart_lgpl/art_svp_ops.h>
34
35 #include "gnome-canvas.h"
36 #include "gnome-canvas-util.h"
37 #include "gnome-canvas-clipgroup.h"
38
39 enum {
40         PROP_0,
41         PROP_PATH,
42         PROP_WIND
43 };
44
45 static void gnome_canvas_clipgroup_class_init      (GnomeCanvasClipgroupClass *klass);
46 static void gnome_canvas_clipgroup_init            (GnomeCanvasClipgroup      *clipgroup);
47 static void gnome_canvas_clipgroup_destroy         (GtkObject                 *object);
48 static void gnome_canvas_clipgroup_set_property    (GObject                   *object,
49                                                     guint                      param_id,
50                                                     const GValue              *value,
51                                                     GParamSpec                *pspec);
52 static void gnome_canvas_clipgroup_get_property    (GObject                   *object,
53                                                     guint                      param_id,
54                                                     GValue                    *value,
55                                                     GParamSpec                *pspec);
56 static void gnome_canvas_clipgroup_update          (GnomeCanvasItem           *item,
57                                                     double                    *affine,
58                                                     ArtSVP                    *clip_path,
59                                                     int                        flags);
60
61 /*
62  * Generic clipping stuff
63  *
64  * This is somewhat slow and memory-hungry - we add extra
65  * composition, extra SVP render and allocate 65536
66  * bytes for each clip level. It could be done more
67  * efficently per-object basis - but to make clipping
68  * universal, there is no alternative to double
69  * buffering (although it should be done into RGBA
70  * buffer by other method than ::render to make global
71  * opacity possible).
72  * Using art-render could possibly optimize that a bit,
73  * although I am not sure.
74  */
75
76 #define GCG_BUF_WIDTH 128
77 #define GCG_BUF_HEIGHT 128
78 #define GCG_BUF_PIXELS (GCG_BUF_WIDTH * GCG_BUF_HEIGHT)
79 #define GCG_BUF_SIZE (GCG_BUF_WIDTH * GCG_BUF_HEIGHT * 3)
80
81 #define noSHOW_SHADOW
82
83 static guchar *gcg_buf_new (void);
84 static void gcg_buf_free (guchar *buf);
85 static guchar *gcg_mask_new (void);
86 static void gcg_mask_free (guchar *mask);
87
88 static void gnome_canvas_clipgroup_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf);
89
90 static GnomeCanvasGroupClass *parent_class;
91
92 GType
93 gnome_canvas_clipgroup_get_type (void)
94 {
95         static GType clipgroup_type;
96
97         if (!clipgroup_type) {
98                 const GTypeInfo object_info = {
99                         sizeof (GnomeCanvasClipgroupClass),
100                         (GBaseInitFunc) NULL,
101                         (GBaseFinalizeFunc) NULL,
102                         (GClassInitFunc) gnome_canvas_clipgroup_class_init,
103                         (GClassFinalizeFunc) NULL,
104                         NULL,                   /* class_data */
105                         sizeof (GnomeCanvasClipgroup),
106                         0,                      /* n_preallocs */
107                         (GInstanceInitFunc) gnome_canvas_clipgroup_init,
108                         NULL                    /* value_table */
109                 };
110
111                 clipgroup_type = g_type_register_static (GNOME_TYPE_CANVAS_GROUP, "GnomeCanvasClipgroup",
112                                                          &object_info, 0);
113         }
114
115         return clipgroup_type;
116 }
117
118 static void
119 gnome_canvas_clipgroup_class_init (GnomeCanvasClipgroupClass *klass)
120 {
121         GObjectClass *gobject_class;
122         GtkObjectClass *object_class;
123         GnomeCanvasItemClass *item_class;
124
125         gobject_class = (GObjectClass *) klass;
126         object_class = (GtkObjectClass *) klass;
127         item_class = (GnomeCanvasItemClass *) klass;
128         parent_class = g_type_class_ref (GNOME_TYPE_CANVAS_GROUP);
129
130         object_class->destroy       = gnome_canvas_clipgroup_destroy;
131         gobject_class->set_property = gnome_canvas_clipgroup_set_property;
132         gobject_class->get_property = gnome_canvas_clipgroup_get_property;
133         item_class->update          = gnome_canvas_clipgroup_update;
134         item_class->render          = gnome_canvas_clipgroup_render;
135
136         g_object_class_install_property (gobject_class,
137                                          PROP_PATH,
138                                          g_param_spec_pointer ("path", NULL, NULL,
139                                                                (G_PARAM_READABLE | G_PARAM_WRITABLE)));
140         g_object_class_install_property (gobject_class,
141                                          PROP_WIND,
142                                          g_param_spec_uint ("wind", NULL, NULL,
143                                                             0, G_MAXUINT, 0,
144                                                             (G_PARAM_READABLE | G_PARAM_WRITABLE)));
145 }
146
147 static void
148 gnome_canvas_clipgroup_init (GnomeCanvasClipgroup *clipgroup)
149 {
150         clipgroup->path = NULL;
151         clipgroup->wind = ART_WIND_RULE_NONZERO; /* default winding rule */
152         clipgroup->svp = NULL;
153 }
154
155 static void
156 gnome_canvas_clipgroup_destroy (GtkObject *object)
157 {
158         GnomeCanvasClipgroup *clipgroup;
159
160         g_return_if_fail (object != NULL);
161         g_return_if_fail (GNOME_IS_CANVAS_CLIPGROUP (object));
162
163         clipgroup = GNOME_CANVAS_CLIPGROUP (object);
164
165         if (clipgroup->path) {
166                 gnome_canvas_path_def_unref (clipgroup->path);
167                 clipgroup->path = NULL;
168         }
169         
170         if (clipgroup->svp) {
171                 art_svp_free (clipgroup->svp);
172                 clipgroup->svp = NULL;
173         }
174
175         if (GTK_OBJECT_CLASS (parent_class)->destroy)
176                 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
177 }
178
179
180 static void
181 gnome_canvas_clipgroup_set_property (GObject      *object,
182                                      guint         param_id,
183                                      const GValue *value,
184                                      GParamSpec   *pspec)
185 {
186         GnomeCanvasItem *item;
187         GnomeCanvasClipgroup *cgroup;
188         GnomeCanvasPathDef *gpp;
189
190         item = GNOME_CANVAS_ITEM (object);
191         cgroup = GNOME_CANVAS_CLIPGROUP (object);
192
193         switch (param_id) {
194         case PROP_PATH:
195                 gpp = g_value_get_pointer (value);
196
197                 if (cgroup->path) {
198                         gnome_canvas_path_def_unref (cgroup->path);
199                         cgroup->path = NULL;
200                 }
201                 if (gpp != NULL) {
202                         cgroup->path = gnome_canvas_path_def_closed_parts (gpp);
203                 }
204
205                 gnome_canvas_item_request_update (item);
206                 break;
207
208         case PROP_WIND:
209                 cgroup->wind = g_value_get_uint (value);
210                 gnome_canvas_item_request_update (item);
211                 break;
212
213         default:
214                 break;
215         }
216 }
217
218 static void
219 gnome_canvas_clipgroup_get_property (GObject    *object,
220                                      guint       param_id,
221                                      GValue     *value,
222                                      GParamSpec *pspec)
223 {
224         GnomeCanvasClipgroup * cgroup;
225
226         cgroup = GNOME_CANVAS_CLIPGROUP (object);
227
228         switch (param_id) {
229         case PROP_PATH:
230                 g_value_set_pointer (value, cgroup->path);
231                 break;
232
233         case PROP_WIND:
234                 g_value_set_uint (value, cgroup->wind);
235                 break;
236
237         default:
238                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
239                 break;
240         }
241 }
242
243 static void
244 gnome_canvas_clipgroup_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags)
245 {
246         GnomeCanvasClipgroup *clipgroup;
247         ArtSvpWriter *swr;
248         ArtBpath *bp;
249         ArtBpath *bpath;
250         ArtVpath *vpath;
251         ArtSVP *svp, *svp1, *svp2;
252
253         clipgroup = GNOME_CANVAS_CLIPGROUP (item);
254
255         if (clipgroup->svp) {
256                 art_svp_free (clipgroup->svp);
257                 clipgroup->svp = NULL;
258         }
259
260         if (clipgroup->path) {
261                 bp = gnome_canvas_path_def_bpath (clipgroup->path);
262                 bpath = art_bpath_affine_transform (bp, affine);
263
264                 vpath = art_bez_path_to_vec (bpath, 0.25);
265                 art_free (bpath);
266
267                 svp1 = art_svp_from_vpath (vpath);
268                 art_free (vpath);
269                 
270                 swr = art_svp_writer_rewind_new (clipgroup->wind);
271                 art_svp_intersector (svp1, swr);
272
273                 svp2 = art_svp_writer_rewind_reap (swr);
274                 art_svp_free (svp1);
275                 
276                 if (clip_path != NULL) {
277                         svp = art_svp_intersect (svp2, clip_path);
278                         art_svp_free (svp2);
279                 } else {
280                         svp = svp2;
281                 }
282
283                 clipgroup->svp = svp;
284         }
285
286         if (GNOME_CANVAS_ITEM_CLASS (parent_class)->update)
287                 (GNOME_CANVAS_ITEM_CLASS (parent_class)->update) (item, affine, NULL, flags);
288
289         if (clipgroup->svp) {
290                 ArtDRect cbox;
291                 art_drect_svp (&cbox, clipgroup->svp);
292                 item->x1 = MAX (item->x1, cbox.x0 - 1.0);
293                 item->y1 = MAX (item->y1, cbox.y0 - 1.0);
294                 item->x2 = MIN (item->x2, cbox.x1 + 1.0);
295                 item->y2 = MIN (item->y2, cbox.y1 + 1.0);
296         }
297 }
298
299 /* non-premultiplied composition into RGB */
300
301 #define COMPOSEN11(fc,fa,bc) (((255 - (guint) (fa)) * (guint) (bc) + (guint) (fc) * (guint) (fa) + 127) / 255)
302
303 static void
304 gnome_canvas_clipgroup_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf)
305 {
306         GnomeCanvasClipgroup *cg;
307         GnomeCanvasBuf lbuf;
308         guchar *mask;
309
310         cg = GNOME_CANVAS_CLIPGROUP (item);
311
312         if (cg->svp) {
313                 gint bw, bh, sw, sh;
314                 gint x, y;
315
316                 /* fixme: We could optimize background handling (lauris) */
317
318                 if (buf->is_bg) {
319                         gnome_canvas_buf_ensure_buf (buf);
320                         buf->is_bg = FALSE;
321                         buf->is_buf = TRUE;
322                 }
323
324                 bw = buf->rect.x1 - buf->rect.x0;
325                 bh = buf->rect.y1 - buf->rect.y0;
326                 if ((bw < 1) || (bh < 1)) return;
327
328                 if (bw * bh <= GCG_BUF_PIXELS) {
329                         /* We can go with single buffer */
330                         sw = bw;
331                         sh = bh;
332                 } else if (bw <= (GCG_BUF_PIXELS >> 3)) {
333                         /* Go with row buffer */
334                         sw = bw;
335                         sh =  GCG_BUF_PIXELS / bw;
336                 } else if (bh <= (GCG_BUF_PIXELS >> 3)) {
337                         /* Go with column buffer */
338                         sw = GCG_BUF_PIXELS / bh;
339                         sh = bh;
340                 } else {
341                         /* Tile buffer */
342                         sw = GCG_BUF_WIDTH;
343                         sh = GCG_BUF_HEIGHT;
344                 }
345
346                 /* Set up local buffer */
347                 lbuf.buf = gcg_buf_new ();
348                 lbuf.bg_color = buf->bg_color;
349                 lbuf.is_bg = FALSE;
350                 lbuf.is_buf = TRUE;
351                 /* Allocate mask */
352                 mask = gcg_mask_new ();
353
354                 for (y = buf->rect.y0; y < buf->rect.y1; y += sh) {
355                         for (x = buf->rect.x0; x < buf->rect.x1; x += sw) {
356                                 gint r, xx, yy;
357                                 /* Set up local buffer */
358                                 lbuf.rect.x0 = x;
359                                 lbuf.rect.y0 = y;
360                                 lbuf.rect.x1 = MIN (x + sw, buf->rect.x1);
361                                 lbuf.rect.y1 = MIN (y + sh, buf->rect.y1);
362                                 lbuf.buf_rowstride = 3 * (lbuf.rect.x1 - lbuf.rect.x0);
363                                 /* Copy background */
364                                 for (r = lbuf.rect.y0; r < lbuf.rect.y1; r++) {
365                                         memcpy (lbuf.buf + (r - lbuf.rect.y0) * lbuf.buf_rowstride,
366                                                 buf->buf + (r - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3,
367                                                 (lbuf.rect.x1 - lbuf.rect.x0) * 3);
368                                 }
369                                 /* Invoke render method */
370                                 if (((GnomeCanvasItemClass *) parent_class)->render)
371                                         ((GnomeCanvasItemClass *) parent_class)->render (item, &lbuf);
372                                 /* Render mask */
373                                 art_gray_svp_aa (cg->svp, lbuf.rect.x0, lbuf.rect.y0, lbuf.rect.x1, lbuf.rect.y1,
374                                                  mask, lbuf.rect.x1 - lbuf.rect.x0);
375                                 /* Combine */
376                                 for (yy = lbuf.rect.y0; yy < lbuf.rect.y1; yy++) {
377                                         guchar *s, *m, *d;
378                                         s = lbuf.buf + (yy - lbuf.rect.y0) * lbuf.buf_rowstride;
379                                         m = mask + (yy - lbuf.rect.y0) * (lbuf.rect.x1 - lbuf.rect.x0);
380                                         d = buf->buf + (yy - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3;
381                                         for (xx = lbuf.rect.x0; xx < lbuf.rect.x1; xx++) {
382 #ifndef SHOW_SHADOW
383                                                 d[0] = COMPOSEN11 (s[0], m[0], d[0]);
384                                                 d[1] = COMPOSEN11 (s[1], m[0], d[1]);
385                                                 d[2] = COMPOSEN11 (s[2], m[0], d[2]);
386 #else
387                                                 d[0] = COMPOSEN11 (s[0], m[0] | 0x7f, d[0]);
388                                                 d[1] = COMPOSEN11 (s[1], m[0] | 0x7f, d[1]);
389                                                 d[2] = COMPOSEN11 (s[2], m[0] | 0x7f, d[2]);
390 #endif
391                                                 s += 3;
392                                                 m += 1;
393                                                 d += 3;
394                                         }
395                                 }
396                         }
397                 }
398                 /* Free buffers */
399                 gcg_mask_free (mask);
400                 gcg_buf_free (lbuf.buf);
401         } else {
402                 if (((GnomeCanvasItemClass *) parent_class)->render)
403                         ((GnomeCanvasItemClass *) parent_class)->render (item, buf);
404         }
405 }
406
407 static GSList *gcg_buffers = NULL;
408 static GSList *gcg_masks = NULL;
409
410 static guchar *
411 gcg_buf_new (void)
412 {
413         guchar *buf;
414
415         if (!gcg_buffers) {
416                 buf = g_new (guchar, GCG_BUF_SIZE);
417         } else {
418                 buf = (guchar *) gcg_buffers->data;
419                 gcg_buffers = g_slist_remove (gcg_buffers, buf);
420         }
421
422         return buf;
423 }
424
425 static void
426 gcg_buf_free (guchar *buf)
427 {
428         gcg_buffers = g_slist_prepend (gcg_buffers, buf);
429 }
430
431 static guchar *
432 gcg_mask_new (void)
433 {
434         guchar *mask;
435
436         if (!gcg_masks) {
437                 mask = g_new (guchar, GCG_BUF_PIXELS);
438         } else {
439                 mask = (guchar *) gcg_masks->data;
440                 gcg_masks = g_slist_remove (gcg_masks, mask);
441         }
442
443         return mask;
444 }
445
446 static void
447 gcg_mask_free (guchar *mask)
448 {
449         gcg_masks = g_slist_prepend (gcg_masks, mask);
450 }