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 GTK_SIGNAL_OFFSET (SPCanvasArenaClass, arena_event),
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 != 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 == 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;
391 case GDK_LEAVE_NOTIFY:
392 if (arena->cursor) {
393 ret = sp_canvas_arena_send_event (arena, event);
394 if (arena->active) nr_object_unref ((NRObject *) arena->active);
395 arena->active = NULL;
396 arena->cursor = FALSE;
397 }
398 break;
399 case GDK_MOTION_NOTIFY:
400 /* TODO ... event -> arena transform? */
401 arena->c = NR::Point(event->motion.x, event->motion.y);
403 /* fixme: Not sure abut this, but seems the right thing (Lauris) */
404 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE);
405 new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
406 if (new_arena != arena->active) {
407 GdkEventCrossing ec;
408 ec.window = event->motion.window;
409 ec.send_event = event->motion.send_event;
410 ec.subwindow = event->motion.window;
411 ec.time = event->motion.time;
412 ec.x = event->motion.x;
413 ec.y = event->motion.y;
414 /* fixme: */
415 if (arena->active) {
416 ec.type = GDK_LEAVE_NOTIFY;
417 ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
418 }
419 if (arena->active) nr_object_unref ((NRObject *) arena->active);
420 arena->active = new_arena;
421 if (arena->active) nr_object_ref ((NRObject *) arena->active);
422 if (arena->active) {
423 ec.type = GDK_ENTER_NOTIFY;
424 ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
425 }
426 }
427 ret = sp_canvas_arena_send_event (arena, event);
428 break;
429 default:
430 /* Just send event */
431 ret = sp_canvas_arena_send_event (arena, event);
432 break;
433 }
435 return ret;
436 }
438 static gint
439 sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event)
440 {
441 gint ret = FALSE;
443 /* Send event to arena */
444 gtk_signal_emit (GTK_OBJECT (arena), signals[ARENA_EVENT], arena->active, event, &ret);
446 return ret;
447 }
449 static void
450 sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data)
451 {
452 sp_canvas_item_request_update (SP_CANVAS_ITEM (data));
453 }
455 static void
456 sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data)
457 {
458 sp_canvas_request_redraw (SP_CANVAS_ITEM (data)->canvas, area->x0, area->y0, area->x1, area->y1);
459 }
461 void
462 sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta)
463 {
464 g_return_if_fail (ca != NULL);
465 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
467 /* fixme: repick? */
468 ca->delta = delta;
469 }
471 void
472 sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky)
473 {
474 g_return_if_fail (ca != NULL);
475 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
477 /* fixme: repick? */
478 ca->sticky = sticky;
479 }
481 void
482 sp_canvas_arena_render_pixblock (SPCanvasArena *ca, NRPixBlock *pb)
483 {
484 NRRectL area;
486 g_return_if_fail (ca != NULL);
487 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
489 /* fixme: */
490 pb->empty = FALSE;
492 area.x0 = pb->area.x0;
493 area.y0 = pb->area.y0;
494 area.x1 = pb->area.x1;
495 area.y1 = pb->area.y1;
497 nr_arena_item_invoke_render (NULL, ca->root, &area, pb, 0);
498 }