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->root = NRArenaGroup::create(arena->arena);
105 nr_arena_group_set_transparent (NR_ARENA_GROUP (arena->root), TRUE);
107 arena->active = NULL;
109 nr_active_object_add_listener ((NRActiveObject *) arena->arena, (NRObjectEventVector *) &carenaev, sizeof (carenaev), arena);
110 }
112 static void
113 sp_canvas_arena_destroy (GtkObject *object)
114 {
115 SPCanvasArena *arena = SP_CANVAS_ARENA (object);
117 if (arena->active) {
118 nr_object_unref ((NRObject *) arena->active);
119 arena->active = NULL;
120 }
122 if (arena->root) {
123 nr_arena_item_unref (arena->root);
124 arena->root = NULL;
125 }
127 if (arena->arena) {
128 nr_active_object_remove_listener_by_data ((NRActiveObject *) arena->arena, arena);
130 nr_object_unref ((NRObject *) arena->arena);
131 arena->arena = NULL;
132 }
134 if (GTK_OBJECT_CLASS (parent_class)->destroy)
135 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
136 }
138 static void
139 sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
140 {
141 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
143 if (((SPCanvasItemClass *) parent_class)->update)
144 (* ((SPCanvasItemClass *) parent_class)->update) (item, affine, flags);
146 arena->gc.transform = affine;
148 guint reset;
149 reset = (flags & SP_CANVAS_UPDATE_AFFINE)? NR_ARENA_ITEM_STATE_ALL : NR_ARENA_ITEM_STATE_NONE;
151 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_ALL, reset);
153 item->x1 = arena->root->bbox.x0 - 1;
154 item->y1 = arena->root->bbox.y0 - 1;
155 item->x2 = arena->root->bbox.x1 + 1;
156 item->y2 = arena->root->bbox.y1 + 1;
158 if (arena->cursor) {
159 /* Mess with enter/leave notifiers */
160 NRArenaItem *new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
161 if (new_arena != arena->active) {
162 GdkEventCrossing ec;
163 ec.window = GTK_WIDGET (item->canvas)->window;
164 ec.send_event = TRUE;
165 ec.subwindow = ec.window;
166 ec.time = GDK_CURRENT_TIME;
167 ec.x = arena->c[NR::X];
168 ec.y = arena->c[NR::Y];
169 /* fixme: */
170 if (arena->active) {
171 ec.type = GDK_LEAVE_NOTIFY;
172 sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
173 }
174 /* fixme: This is not optimal - better track ::destroy (Lauris) */
175 if (arena->active) nr_object_unref ((NRObject *) arena->active);
176 arena->active = new_arena;
177 if (arena->active) nr_object_ref ((NRObject *) arena->active);
178 if (arena->active) {
179 ec.type = GDK_ENTER_NOTIFY;
180 sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
181 }
182 }
183 }
184 }
186 static void
187 sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf)
188 {
189 gint bw, bh, sw, sh;
190 gint x, y;
192 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
194 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc,
195 NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_RENDER,
196 NR_ARENA_ITEM_STATE_NONE);
198 sp_canvas_prepare_buffer(buf);
200 bw = buf->rect.x1 - buf->rect.x0;
201 bh = buf->rect.y1 - buf->rect.y0;
202 if ((bw < 1) || (bh < 1)) return;
204 // FIXME: currently this function is a huge waste. It receives a buffer but creates a new one and loops
205 // within the large one, doing arena painting in several blocks. This just makes no sense because the
206 // buf that we are given is already only a strip of the screen, created by one iteration of a loop in
207 // sp_canvas_paint_rect_internal. With the current numbers, this function's buffer is always 1/4
208 // smaller than the one we get, because they both are the same number of bytes but
209 // buf uses 3 bytes per pixel (24bpp, packed) while the pixblock created here uses 4 bytes (32bpp).
210 // Eventually I want to switch buf to using 4 bytes (see comment in canvas.cpp) and then remove
211 // from here the sw/sh calculation, the loop, and creating the intermediate buffer, allowing arena
212 // just render into buf in one go.
214 if (arena->arena->rendermode != RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients
215 /* 256K is the cached buffer and we need 4 channels */
216 if (bw * bh < 65536) { // 256K/4
217 /* We can go with single buffer */
218 sw = bw;
219 sh = bh;
220 } else if (bw <= 4096) {
221 /* Go with row buffer */
222 sw = bw;
223 sh = 65536 / bw;
224 } else if (bh <= 4096) {
225 /* Go with column buffer */
226 sw = 65536 / bh;
227 sh = bh;
228 } else {
229 sw = 256;
230 sh = 256;
231 }
232 } else { // paths only, so 1M works faster
233 /* 1M is the cached buffer and we need 4 channels */
234 if (bw * bh < 262144) { // 1M/4
235 /* We can go with single buffer */
236 sw = bw;
237 sh = bh;
238 } else if (bw <= 8192) {
239 /* Go with row buffer */
240 sw = bw;
241 sh = 262144 / bw;
242 } else if (bh <= 8192) {
243 /* Go with column buffer */
244 sw = 262144 / bh;
245 sh = bh;
246 } else {
247 sw = 512;
248 sh = 512;
249 }
250 }
252 /*
253 This define chooses between two modes: When on, arena renders into a temporary
254 32bpp buffer, and the result is then squished into the SPCanvasBuf. When off, arena
255 renders directly to SPCanvasBuf. However currently this gives no speed advantage,
256 perhaps because the lack of squishing is offset by the need for arena items to render
257 to the inconvenient (and probably slower) 24bpp buffer. When SPCanvasBuf is
258 switched to 32bpp and cairo drawing, however, this define should be removed to
259 streamline rendering.
260 */
261 #define STRICT_RGBA
263 for (y = buf->rect.y0; y < buf->rect.y1; y += sh) {
264 for (x = buf->rect.x0; x < buf->rect.x1; x += sw) {
265 NRRectL area;
266 #ifdef STRICT_RGBA
267 NRPixBlock pb;
268 #endif
269 NRPixBlock cb;
271 area.x0 = x;
272 area.y0 = y;
273 area.x1 = MIN (x + sw, buf->rect.x1);
274 area.y1 = MIN (y + sh, buf->rect.y1);
276 #ifdef STRICT_RGBA
277 nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8A8P, area.x0, area.y0, area.x1, area.y1, TRUE);
278 #endif
280 // CAIRO FIXME: switch this to R8G8B8A8P and 4 * ...
281 nr_pixblock_setup_extern (&cb, NR_PIXBLOCK_MODE_R8G8B8, area.x0, area.y0, area.x1, area.y1,
282 buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + 3 * (x - buf->rect.x0),
283 buf->buf_rowstride,
284 FALSE, FALSE);
286 #ifdef STRICT_RGBA
287 pb.visible_area = buf->visible_rect;
289 if (pb.data.px != NULL) {
290 cairo_t *ct = nr_create_cairo_context (&area, &pb);
292 nr_arena_item_invoke_render (ct, arena->root, &area, &pb, 0);
294 if (pb.empty == FALSE) {
296 if (arena->arena->rendermode == RENDERMODE_OUTLINE) {
297 // currently we only use cairo in outline mode
299 // ENDIANNESS FIX
300 // Inkscape and GTK use fixed byte order in their buffers: r, g, b, a.
301 // Cairo reads/writes buffer values as in32s and therefore depends on the hardware byte order
302 // (little-endian vs big-endian).
303 // Until we move ALL of inkscape rendering and screen display to cairo,
304 // we must reverse the order for big-endian architectures (e.g. PowerPC).
305 if (G_BYTE_ORDER == G_BIG_ENDIAN) {
306 unsigned char *start = NR_PIXBLOCK_PX(&pb);
307 unsigned char *end = start + pb.rs * (pb.area.y1 - pb.area.y0);
308 for (unsigned char *i = start; i < end; i += 4) {
309 unsigned char tmp0 = i[0];
310 unsigned char tmp1 = i[1];
311 i[0] = i[3];
312 i[1] = i[2];
313 i[2] = tmp1;
314 i[3] = tmp0;
315 }
316 }
317 }
319 // this does the 32->24 squishing, using an assembler routine:
320 nr_blit_pixblock_pixblock (&cb, &pb);
321 }
323 cairo_surface_t *cst = cairo_get_target(ct);
324 cairo_destroy (ct);
325 cairo_surface_finish (cst);
326 cairo_surface_destroy (cst);
327 }
329 nr_pixblock_release (&pb);
330 #else
331 cb.visible_area = buf->visible_rect;
332 nr_arena_item_invoke_render (NULL, arena->root, &area, &cb, 0);
333 #endif
335 nr_pixblock_release (&cb);
336 }
337 }
338 }
340 static double
341 sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
342 {
343 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
345 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc,
346 NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_PICK,
347 NR_ARENA_ITEM_STATE_NONE);
349 NRArenaItem *picked = nr_arena_item_invoke_pick (arena->root, p, arena->arena->delta, arena->sticky);
351 arena->picked = picked;
353 if (picked) {
354 *actual_item = item;
355 return 0.0;
356 }
358 return 1e18;
359 }
361 static gint
362 sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event)
363 {
364 NRArenaItem *new_arena;
365 /* fixme: This sucks, we have to handle enter/leave notifiers */
367 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
369 gint ret = FALSE;
371 switch (event->type) {
372 case GDK_ENTER_NOTIFY:
373 if (!arena->cursor) {
374 if (arena->active) {
375 //g_warning ("Cursor entered to arena with already active item");
376 nr_object_unref ((NRObject *) arena->active);
377 }
378 arena->cursor = TRUE;
380 /* TODO ... event -> arena transform? */
381 arena->c = NR::Point(event->crossing.x, event->crossing.y);
383 /* fixme: Not sure abut this, but seems the right thing (Lauris) */
384 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE);
385 arena->active = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
386 if (arena->active) nr_object_ref ((NRObject *) arena->active);
387 ret = sp_canvas_arena_send_event (arena, event);
388 }
389 break;
390 case GDK_LEAVE_NOTIFY:
391 if (arena->cursor) {
392 ret = sp_canvas_arena_send_event (arena, event);
393 if (arena->active) nr_object_unref ((NRObject *) arena->active);
394 arena->active = NULL;
395 arena->cursor = FALSE;
396 }
397 break;
398 case GDK_MOTION_NOTIFY:
399 /* TODO ... event -> arena transform? */
400 arena->c = NR::Point(event->motion.x, event->motion.y);
402 /* fixme: Not sure abut this, but seems the right thing (Lauris) */
403 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE);
404 new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
405 if (new_arena != arena->active) {
406 GdkEventCrossing ec;
407 ec.window = event->motion.window;
408 ec.send_event = event->motion.send_event;
409 ec.subwindow = event->motion.window;
410 ec.time = event->motion.time;
411 ec.x = event->motion.x;
412 ec.y = event->motion.y;
413 /* fixme: */
414 if (arena->active) {
415 ec.type = GDK_LEAVE_NOTIFY;
416 ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
417 }
418 if (arena->active) nr_object_unref ((NRObject *) arena->active);
419 arena->active = new_arena;
420 if (arena->active) nr_object_ref ((NRObject *) arena->active);
421 if (arena->active) {
422 ec.type = GDK_ENTER_NOTIFY;
423 ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
424 }
425 }
426 ret = sp_canvas_arena_send_event (arena, event);
427 break;
428 default:
429 /* Just send event */
430 ret = sp_canvas_arena_send_event (arena, event);
431 break;
432 }
434 return ret;
435 }
437 static gint
438 sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event)
439 {
440 gint ret = FALSE;
442 /* Send event to arena */
443 gtk_signal_emit (GTK_OBJECT (arena), signals[ARENA_EVENT], arena->active, event, &ret);
445 return ret;
446 }
448 static void
449 sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data)
450 {
451 sp_canvas_item_request_update (SP_CANVAS_ITEM (data));
452 }
454 static void
455 sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data)
456 {
457 sp_canvas_request_redraw (SP_CANVAS_ITEM (data)->canvas, area->x0, area->y0, area->x1, area->y1);
458 }
460 void
461 sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta)
462 {
463 g_return_if_fail (ca != NULL);
464 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
466 /* fixme: repick? */
467 ca->delta = delta;
468 }
470 void
471 sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky)
472 {
473 g_return_if_fail (ca != NULL);
474 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
476 /* fixme: repick? */
477 ca->sticky = sticky;
478 }
480 void
481 sp_canvas_arena_render_pixblock (SPCanvasArena *ca, NRPixBlock *pb)
482 {
483 NRRectL area;
485 g_return_if_fail (ca != NULL);
486 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
488 /* fixme: */
489 pb->empty = FALSE;
491 area.x0 = pb->area.x0;
492 area.y0 = pb->area.y0;
493 area.x1 = pb->area.x1;
494 area.y1 = pb->area.y1;
496 nr_arena_item_invoke_render (NULL, ca->root, &area, pb, 0);
497 }