Code

blind fix for endianness, needs testing by someone on a big-endian machine
[inkscape.git] / src / display / canvas-arena.cpp
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)
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);
112 static void
113 sp_canvas_arena_destroy (GtkObject *object)
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);
138 static void
139 sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
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         }
186 static void
187 sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf)
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);
199   
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         }
340 static double
341 sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
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;
361 static gint
362 sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event)
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;
437 static gint
438 sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event)
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;
448 static void
449 sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data)
451         sp_canvas_item_request_update (SP_CANVAS_ITEM (data));
454 static void
455 sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data)
457         sp_canvas_request_redraw (SP_CANVAS_ITEM (data)->canvas, area->x0, area->y0, area->x1, area->y1);
460 void
461 sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta)
463         g_return_if_fail (ca != NULL);
464         g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
466         /* fixme: repick? */
467         ca->delta = delta;
470 void
471 sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky)
473         g_return_if_fail (ca != NULL);
474         g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
476         /* fixme: repick? */
477         ca->sticky = sticky;
480 void
481 sp_canvas_arena_render_pixblock (SPCanvasArena *ca, NRPixBlock *pb)
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);