1 #define __SP_CANVAS_ARENA_C__
3 /*
4 * RGBA display list system for inkscape
5 *
6 * Author:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 *
9 * Copyright (C) 2001-2002 Lauris Kaplinski
10 * Copyright (C) 2001 Ximian, Inc.
11 *
12 * Released under GNU GPL, read the file 'COPYING' for more information
13 */
15 #include <libnr/nr-blit.h>
16 #include <gtk/gtksignal.h>
18 #include <display/display-forward.h>
19 #include <display/sp-canvas-util.h>
20 #include <helper/sp-marshal.h>
21 #include <display/nr-arena.h>
22 #include <display/nr-arena-group.h>
23 #include <display/canvas-arena.h>
24 #include <display/inkscape-cairo.h>
26 enum {
27 ARENA_EVENT,
28 LAST_SIGNAL
29 };
31 static void sp_canvas_arena_class_init(SPCanvasArenaClass *klass);
32 static void sp_canvas_arena_init(SPCanvasArena *group);
33 static void sp_canvas_arena_destroy(GtkObject *object);
35 static void sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
36 static void sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf);
37 static double sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item);
38 static gint sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event);
40 static gint sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event);
42 static void sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data);
43 static void sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data);
45 NRArenaEventVector carenaev = {
46 {NULL},
47 sp_canvas_arena_request_update,
48 sp_canvas_arena_request_render
49 };
51 static SPCanvasItemClass *parent_class;
52 static guint signals[LAST_SIGNAL] = {0};
54 GtkType
55 sp_canvas_arena_get_type (void)
56 {
57 static GtkType type = 0;
58 if (!type) {
59 GtkTypeInfo info = {
60 "SPCanvasArena",
61 sizeof (SPCanvasArena),
62 sizeof (SPCanvasArenaClass),
63 (GtkClassInitFunc) sp_canvas_arena_class_init,
64 (GtkObjectInitFunc) sp_canvas_arena_init,
65 NULL, NULL, NULL
66 };
67 type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info);
68 }
69 return type;
70 }
72 static void
73 sp_canvas_arena_class_init (SPCanvasArenaClass *klass)
74 {
75 GtkObjectClass *object_class;
76 SPCanvasItemClass *item_class;
78 object_class = (GtkObjectClass *) klass;
79 item_class = (SPCanvasItemClass *) klass;
81 parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM);
83 signals[ARENA_EVENT] = gtk_signal_new ("arena_event",
84 GTK_RUN_LAST,
85 GTK_CLASS_TYPE(object_class),
86 ((glong)((guint8*)&(klass->arena_event) - (guint8*)klass)),
87 sp_marshal_INT__POINTER_POINTER,
88 GTK_TYPE_INT, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER);
90 object_class->destroy = sp_canvas_arena_destroy;
92 item_class->update = sp_canvas_arena_update;
93 item_class->render = sp_canvas_arena_render;
94 item_class->point = sp_canvas_arena_point;
95 item_class->event = sp_canvas_arena_event;
96 }
98 static void
99 sp_canvas_arena_init (SPCanvasArena *arena)
100 {
101 arena->sticky = FALSE;
103 arena->arena = NRArena::create();
104 arena->arena->canvasarena = arena;
105 arena->root = NRArenaGroup::create(arena->arena);
106 nr_arena_group_set_transparent (NR_ARENA_GROUP (arena->root), TRUE);
108 arena->active = NULL;
110 nr_active_object_add_listener ((NRActiveObject *) arena->arena, (NRObjectEventVector *) &carenaev, sizeof (carenaev), arena);
111 }
113 static void
114 sp_canvas_arena_destroy (GtkObject *object)
115 {
116 SPCanvasArena *arena = SP_CANVAS_ARENA (object);
118 if (arena->active) {
119 nr_object_unref ((NRObject *) arena->active);
120 arena->active = NULL;
121 }
123 if (arena->root) {
124 nr_arena_item_unref (arena->root);
125 arena->root = NULL;
126 }
128 if (arena->arena) {
129 nr_active_object_remove_listener_by_data ((NRActiveObject *) arena->arena, arena);
131 nr_object_unref ((NRObject *) arena->arena);
132 arena->arena = NULL;
133 }
135 if (GTK_OBJECT_CLASS (parent_class)->destroy)
136 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
137 }
139 static void
140 sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
141 {
142 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
144 if (((SPCanvasItemClass *) parent_class)->update)
145 (* ((SPCanvasItemClass *) parent_class)->update) (item, affine, flags);
147 arena->gc.transform = affine;
149 guint reset;
150 reset = (flags & SP_CANVAS_UPDATE_AFFINE)? NR_ARENA_ITEM_STATE_ALL : NR_ARENA_ITEM_STATE_NONE;
152 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_ALL, reset);
154 item->x1 = arena->root->bbox.x0 - 1;
155 item->y1 = arena->root->bbox.y0 - 1;
156 item->x2 = arena->root->bbox.x1 + 1;
157 item->y2 = arena->root->bbox.y1 + 1;
159 if (arena->cursor) {
160 /* Mess with enter/leave notifiers */
161 NRArenaItem *new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
162 if (new_arena != arena->active) {
163 GdkEventCrossing ec;
164 ec.window = GTK_WIDGET (item->canvas)->window;
165 ec.send_event = TRUE;
166 ec.subwindow = ec.window;
167 ec.time = GDK_CURRENT_TIME;
168 ec.x = arena->c[NR::X];
169 ec.y = arena->c[NR::Y];
170 /* fixme: */
171 if (arena->active) {
172 ec.type = GDK_LEAVE_NOTIFY;
173 sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
174 }
175 /* fixme: This is not optimal - better track ::destroy (Lauris) */
176 if (arena->active) nr_object_unref ((NRObject *) arena->active);
177 arena->active = new_arena;
178 if (arena->active) nr_object_ref ((NRObject *) arena->active);
179 if (arena->active) {
180 ec.type = GDK_ENTER_NOTIFY;
181 sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
182 }
183 }
184 }
185 }
187 static void
188 sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf)
189 {
190 gint bw, bh, sw, sh;
191 gint x, y;
193 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
195 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc,
196 NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_RENDER,
197 NR_ARENA_ITEM_STATE_NONE);
199 sp_canvas_prepare_buffer(buf);
201 bw = buf->rect.x1 - buf->rect.x0;
202 bh = buf->rect.y1 - buf->rect.y0;
203 if ((bw < 1) || (bh < 1)) return;
205 // FIXME: currently this function is a huge waste. It receives a buffer but creates a new one and loops
206 // within the large one, doing arena painting in several blocks. This just makes no sense because the
207 // buf that we are given is already only a strip of the screen, created by one iteration of a loop in
208 // sp_canvas_paint_rect_internal. With the current numbers, this function's buffer is always 1/4
209 // smaller than the one we get, because they both are the same number of bytes but
210 // buf uses 3 bytes per pixel (24bpp, packed) while the pixblock created here uses 4 bytes (32bpp).
211 // Eventually I want to switch buf to using 4 bytes (see comment in canvas.cpp) and then remove
212 // from here the sw/sh calculation, the loop, and creating the intermediate buffer, allowing arena
213 // just render into buf in one go.
215 if (arena->arena->rendermode != Inkscape::RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients
216 /* 256K is the cached buffer and we need 4 channels */
217 if (bw * bh < 65536) { // 256K/4
218 /* We can go with single buffer */
219 sw = bw;
220 sh = bh;
221 } else if (bw <= 4096) {
222 /* Go with row buffer */
223 sw = bw;
224 sh = 65536 / bw;
225 } else if (bh <= 4096) {
226 /* Go with column buffer */
227 sw = 65536 / bh;
228 sh = bh;
229 } else {
230 sw = 256;
231 sh = 256;
232 }
233 } else { // paths only, so 1M works faster
234 /* 1M is the cached buffer and we need 4 channels */
235 if (bw * bh < 262144) { // 1M/4
236 /* We can go with single buffer */
237 sw = bw;
238 sh = bh;
239 } else if (bw <= 8192) {
240 /* Go with row buffer */
241 sw = bw;
242 sh = 262144 / bw;
243 } else if (bh <= 8192) {
244 /* Go with column buffer */
245 sw = 262144 / bh;
246 sh = bh;
247 } else {
248 sw = 512;
249 sh = 512;
250 }
251 }
253 /*
254 This define chooses between two modes: When on, arena renders into a temporary
255 32bpp buffer, and the result is then squished into the SPCanvasBuf. When off, arena
256 renders directly to SPCanvasBuf. However currently this gives no speed advantage,
257 perhaps because the lack of squishing is offset by the need for arena items to render
258 to the inconvenient (and probably slower) 24bpp buffer. When SPCanvasBuf is
259 switched to 32bpp and cairo drawing, however, this define should be removed to
260 streamline rendering.
261 */
262 #define STRICT_RGBA
264 for (y = buf->rect.y0; y < buf->rect.y1; y += sh) {
265 for (x = buf->rect.x0; x < buf->rect.x1; x += sw) {
266 NRRectL area;
267 #ifdef STRICT_RGBA
268 NRPixBlock pb;
269 #endif
270 NRPixBlock cb;
272 area.x0 = x;
273 area.y0 = y;
274 area.x1 = MIN (x + sw, buf->rect.x1);
275 area.y1 = MIN (y + sh, buf->rect.y1);
277 #ifdef STRICT_RGBA
278 nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8A8P, area.x0, area.y0, area.x1, area.y1, TRUE);
279 #endif
281 // CAIRO FIXME: switch this to R8G8B8A8P and 4 * ...
282 nr_pixblock_setup_extern (&cb, NR_PIXBLOCK_MODE_R8G8B8, area.x0, area.y0, area.x1, area.y1,
283 buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + 3 * (x - buf->rect.x0),
284 buf->buf_rowstride,
285 FALSE, FALSE);
287 #ifdef STRICT_RGBA
288 pb.visible_area = buf->visible_rect;
290 if (pb.data.px != NULL) {
291 cairo_t *ct = nr_create_cairo_context (&area, &pb);
293 nr_arena_item_invoke_render (ct, arena->root, &area, &pb, 0);
295 if (pb.empty == FALSE) {
297 if (arena->arena->rendermode == Inkscape::RENDERMODE_OUTLINE) {
298 // currently we only use cairo in outline mode
300 // ENDIANNESS FIX
301 // Inkscape and GTK use fixed byte order in their buffers: r, g, b, a.
302 // Cairo reads/writes buffer values as in32s and therefore depends on the hardware byte order
303 // (little-endian vs big-endian).
304 // Until we move ALL of inkscape rendering and screen display to cairo,
305 // we must reverse the order for big-endian architectures (e.g. PowerPC).
306 if (G_BYTE_ORDER == G_BIG_ENDIAN) {
307 unsigned char *start = NR_PIXBLOCK_PX(&pb);
308 unsigned char *end = start + pb.rs * (pb.area.y1 - pb.area.y0);
309 for (unsigned char *i = start; i < end; i += 4) {
310 unsigned char tmp0 = i[0];
311 unsigned char tmp1 = i[1];
312 i[0] = i[3];
313 i[1] = i[2];
314 i[2] = tmp1;
315 i[3] = tmp0;
316 }
317 }
318 }
320 // this does the 32->24 squishing, using an assembler routine:
321 nr_blit_pixblock_pixblock (&cb, &pb);
322 }
324 cairo_surface_t *cst = cairo_get_target(ct);
325 cairo_destroy (ct);
326 cairo_surface_finish (cst);
327 cairo_surface_destroy (cst);
328 }
330 nr_pixblock_release (&pb);
331 #else
332 cb.visible_area = buf->visible_rect;
333 nr_arena_item_invoke_render (NULL, arena->root, &area, &cb, 0);
334 #endif
336 nr_pixblock_release (&cb);
337 }
338 }
339 }
341 static double
342 sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
343 {
344 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
346 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc,
347 NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_PICK,
348 NR_ARENA_ITEM_STATE_NONE);
350 NRArenaItem *picked = nr_arena_item_invoke_pick (arena->root, p, arena->arena->delta, arena->sticky);
352 arena->picked = picked;
354 if (picked) {
355 *actual_item = item;
356 return 0.0;
357 }
359 return 1e18;
360 }
362 static gint
363 sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event)
364 {
365 NRArenaItem *new_arena;
366 /* fixme: This sucks, we have to handle enter/leave notifiers */
368 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
370 gint ret = FALSE;
372 switch (event->type) {
373 case GDK_ENTER_NOTIFY:
374 if (!arena->cursor) {
375 if (arena->active) {
376 //g_warning ("Cursor entered to arena with already active item");
377 nr_object_unref ((NRObject *) arena->active);
378 }
379 arena->cursor = TRUE;
381 /* TODO ... event -> arena transform? */
382 arena->c = NR::Point(event->crossing.x, event->crossing.y);
384 /* fixme: Not sure abut this, but seems the right thing (Lauris) */
385 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE);
386 arena->active = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
387 if (arena->active) nr_object_ref ((NRObject *) arena->active);
388 ret = sp_canvas_arena_send_event (arena, event);
389 }
390 break;
392 case GDK_LEAVE_NOTIFY:
393 if (arena->cursor) {
394 ret = sp_canvas_arena_send_event (arena, event);
395 if (arena->active) nr_object_unref ((NRObject *) arena->active);
396 arena->active = NULL;
397 arena->cursor = FALSE;
398 }
399 break;
401 case GDK_MOTION_NOTIFY:
402 /* TODO ... event -> arena transform? */
403 arena->c = NR::Point(event->motion.x, event->motion.y);
405 /* fixme: Not sure abut this, but seems the right thing (Lauris) */
406 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE);
407 new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
408 if (new_arena != arena->active) {
409 GdkEventCrossing ec;
410 ec.window = event->motion.window;
411 ec.send_event = event->motion.send_event;
412 ec.subwindow = event->motion.window;
413 ec.time = event->motion.time;
414 ec.x = event->motion.x;
415 ec.y = event->motion.y;
416 /* fixme: */
417 if (arena->active) {
418 ec.type = GDK_LEAVE_NOTIFY;
419 ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
420 }
421 if (arena->active) nr_object_unref ((NRObject *) arena->active);
422 arena->active = new_arena;
423 if (arena->active) nr_object_ref ((NRObject *) arena->active);
424 if (arena->active) {
425 ec.type = GDK_ENTER_NOTIFY;
426 ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
427 }
428 }
429 ret = sp_canvas_arena_send_event (arena, event);
430 break;
432 default:
433 /* Just send event */
434 ret = sp_canvas_arena_send_event (arena, event);
435 break;
436 }
438 return ret;
439 }
441 static gint
442 sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event)
443 {
444 gint ret = FALSE;
446 /* Send event to arena */
447 gtk_signal_emit (GTK_OBJECT (arena), signals[ARENA_EVENT], arena->active, event, &ret);
449 return ret;
450 }
452 static void
453 sp_canvas_arena_request_update (NRArena */*arena*/, NRArenaItem */*item*/, void *data)
454 {
455 sp_canvas_item_request_update (SP_CANVAS_ITEM (data));
456 }
458 static void
459 sp_canvas_arena_request_render (NRArena */*arena*/, NRRectL *area, void *data)
460 {
461 sp_canvas_request_redraw (SP_CANVAS_ITEM (data)->canvas, area->x0, area->y0, area->x1, area->y1);
462 }
464 void
465 sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta)
466 {
467 g_return_if_fail (ca != NULL);
468 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
470 /* fixme: repick? */
471 ca->delta = delta;
472 }
474 void
475 sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky)
476 {
477 g_return_if_fail (ca != NULL);
478 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
480 /* fixme: repick? */
481 ca->sticky = sticky;
482 }
484 void
485 sp_canvas_arena_render_pixblock (SPCanvasArena *ca, NRPixBlock *pb)
486 {
487 NRRectL area;
489 g_return_if_fail (ca != NULL);
490 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
492 /* fixme: */
493 pb->empty = FALSE;
495 area.x0 = pb->area.x0;
496 area.y0 = pb->area.y0;
497 area.x1 = pb->area.x1;
498 area.y1 = pb->area.y1;
500 nr_arena_item_invoke_render (NULL, ca->root, &area, pb, 0);
501 }
504 /*
505 Local Variables:
506 mode:c++
507 c-file-style:"stroustrup"
508 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
509 indent-tabs-mode:nil
510 fill-column:99
511 End:
512 */
513 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :