X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;ds=sidebyside;f=src%2Fdisplay%2Fsp-canvas.cpp;h=fef2fcc0c1f4653ba54b02a3a3dff92cd079ce11;hb=caa537aad8330d63ee007884befe3568bd42b8d2;hp=b1ed7d3d23fd81537decc9e9656e17bd03b97116;hpb=a43b75210ff4c9e9b45fca2285fbeaf16ce8bd17;p=inkscape.git diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp index b1ed7d3d2..fef2fcc0c 100644 --- a/src/display/sp-canvas.cpp +++ b/src/display/sp-canvas.cpp @@ -8,9 +8,10 @@ * Raph Levien * Lauris Kaplinski * fred + * bbyak * * Copyright (C) 1998 The Free Software Foundation - * Copyright (C) 2002 Lauris Kaplinski + * Copyright (C) 2002-2006 authors * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -24,12 +25,20 @@ #include #include +#include + #include #include #include "display-forward.h" #include #include #include +#include "prefs-utils.h" + +// Tiles are a way to minimize the number of redraws, eliminating too small redraws. +// The canvas stores a 2D array of ints, each representing a TILE_SIZExTILE_SIZE pixels tile. +// If any part of it is dirtied, the entire tile is dirtied (its int is nonzero) and repainted. +#define TILE_SIZE 32 enum { RENDERMODE_NORMAL, @@ -735,11 +744,16 @@ sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned i } } - NR::Rect const &bounds = corners.bounds(); - item->x1 = bounds.min()[NR::X]; - item->y1 = bounds.min()[NR::Y]; - item->x2 = bounds.max()[NR::X]; - item->y2 = bounds.max()[NR::Y]; + NR::Maybe const bounds = corners.bounds(); + if (bounds) { + item->x1 = bounds->min()[NR::X]; + item->y1 = bounds->min()[NR::Y]; + item->x2 = bounds->max()[NR::X]; + item->y2 = bounds->max()[NR::Y]; + } else { + // FIXME ? + item->x1 = item->x2 = item->y1 = item->y2 = 0; + } } /** @@ -879,6 +893,7 @@ static GtkWidgetClass *canvas_parent_class; void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb); void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb); +static int do_update (SPCanvas *canvas); /** * Registers the SPCanvas class if necessary, and returns the type ID @@ -966,6 +981,21 @@ sp_canvas_init (SPCanvas *canvas) canvas->tiles=NULL; canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0; canvas->tileH=canvas->tileV=0; + + canvas->redraw_aborted.x0 = NR_HUGE_L; + canvas->redraw_aborted.x1 = -NR_HUGE_L; + canvas->redraw_aborted.y0 = NR_HUGE_L; + canvas->redraw_aborted.y1 = -NR_HUGE_L; + + canvas->redraw_count = 0; + + canvas->forced_redraw_count = 0; + canvas->forced_redraw_limit = -1; + + canvas->slowest_buffer = 0; + + canvas->is_scrolling = false; + } /** @@ -994,7 +1024,7 @@ shutdown_transients (SPCanvas *canvas) if (canvas->need_redraw) { canvas->need_redraw = FALSE; } - if ( canvas->tiles ) free(canvas->tiles); + if ( canvas->tiles ) g_free(canvas->tiles); canvas->tiles=NULL; canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0; canvas->tileH=canvas->tileV=0; @@ -1070,7 +1100,11 @@ sp_canvas_realize (GtkWidget *widget) widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); gdk_window_set_user_data (widget->window, widget); - gtk_widget_set_events(widget, attributes.event_mask); + + if ( prefs_get_int_attribute ("options.useextinput", "value", 1) ) + gtk_widget_set_events(widget, attributes.event_mask); + + widget->style = gtk_style_attach (widget->style, widget->window); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); @@ -1254,9 +1288,12 @@ emit_event (SPCanvas *canvas, GdkEvent *event) static int pick_current_item (SPCanvas *canvas, GdkEvent *event) { - int button_down; + int button_down = 0; double x, y; + if (!canvas->root) // canvas may have already be destroyed by closing desktop durring interrupted display! + return FALSE; + int retval = FALSE; if (canvas->gen_all_enter_events == false) { @@ -1488,27 +1525,106 @@ sp_canvas_motion (GtkWidget *widget, GdkEventMotion *event) return emit_event (canvas, (GdkEvent *) event); } -/** - * Helper that draws a specific rectangular part of the canvas. - */ static void -sp_canvas_paint_rect (SPCanvas *canvas, int xx0, int yy0, int xx1, int yy1) +sp_canvas_paint_single_buffer (SPCanvas *canvas, int x0, int y0, int x1, int y1, int draw_x1, int draw_y1, int draw_x2, int draw_y2, int sw) { - g_return_if_fail (!canvas->need_update); - GtkWidget *widget = GTK_WIDGET (canvas); - int draw_x1 = MAX (xx0, canvas->x0); - int draw_y1 = MAX (yy0, canvas->y0); - int draw_x2 = MIN (xx1, canvas->x0/*draw_x1*/ + GTK_WIDGET (canvas)->allocation.width); - int draw_y2 = MIN (yy1, canvas->y0/*draw_y1*/ + GTK_WIDGET (canvas)->allocation.height); + SPCanvasBuf buf; + if (canvas->rendermode != RENDERMODE_OUTLINE) { + buf.buf = nr_pixelstore_256K_new (FALSE, 0); + } else { + buf.buf = nr_pixelstore_1M_new (FALSE, 0); + } + + buf.buf_rowstride = sw * 3; // CAIRO FIXME: for cairo output, the buffer must be RGB unpacked, i.e. sw * 4 + buf.rect.x0 = x0; + buf.rect.y0 = y0; + buf.rect.x1 = x1; + buf.rect.y1 = y1; + buf.visible_rect.x0 = draw_x1; + buf.visible_rect.y0 = draw_y1; + buf.visible_rect.x1 = draw_x2; + buf.visible_rect.y1 = draw_y2; + GdkColor *color = &widget->style->bg[GTK_STATE_NORMAL]; + buf.bg_color = (((color->red & 0xff00) << 8) + | (color->green & 0xff00) + | (color->blue >> 8)); + buf.is_empty = true; + + if (canvas->root->flags & SP_CANVAS_ITEM_VISIBLE) { + SP_CANVAS_ITEM_GET_CLASS (canvas->root)->render (canvas->root, &buf); + } + + if (buf.is_empty) { + gdk_rgb_gc_set_foreground (canvas->pixmap_gc, buf.bg_color); + gdk_draw_rectangle (SP_CANVAS_WINDOW (canvas), + canvas->pixmap_gc, + TRUE, + x0 - canvas->x0, y0 - canvas->y0, + x1 - x0, y1 - y0); + } else { +/* +// CAIRO FIXME: after SPCanvasBuf is made 32bpp throughout, this rgb_draw below can be replaced with the below. +// Why this must not be done currently: +// - all canvas items (handles, nodes etc) paint themselves assuming 24bpp +// - cairo assumes bgra, but we have rgba, so r and b get swapped (until we paint all with cairo too) +// - it does not seem to be any faster; in fact since with 32bpp, buf contains less pixels, +// we need more bufs to paint a given area and as a result it's even a bit slower + + cairo_surface_t* cst = cairo_image_surface_create_for_data ( + buf.buf, + CAIRO_FORMAT_RGB24, // unpacked, i.e. 32 bits! one byte is unused + x1 - x0, y1 - y0, + buf.buf_rowstride + ); + cairo_t *ct = gdk_cairo_create(SP_CANVAS_WINDOW (canvas)); + cairo_set_source_surface (ct, cst, x0 - canvas->x0, y0 - canvas->y0); + cairo_paint (ct); + cairo_destroy (ct); + cairo_surface_finish (cst); + cairo_surface_destroy (cst); +*/ + + gdk_draw_rgb_image_dithalign (SP_CANVAS_WINDOW (canvas), + canvas->pixmap_gc, + x0 - canvas->x0, y0 - canvas->y0, + x1 - x0, y1 - y0, + GDK_RGB_DITHER_MAX, + buf.buf, + sw * 3, + x0 - canvas->x0, y0 - canvas->y0); + } + if (canvas->rendermode != RENDERMODE_OUTLINE) { + nr_pixelstore_256K_free (buf.buf); + } else { + nr_pixelstore_1M_free (buf.buf); + } +} + +/* Paint the given rect, while updating canvas->redraw_aborted and running iterations after each + * buffer; make sure canvas->redraw_aborted never goes past aborted_limit (used for 2-rect + * optimized repaint) + */ +static int +sp_canvas_paint_rect_internal (SPCanvas *canvas, NRRectL *rect, NR::ICoord *x_aborted_limit, NR::ICoord *y_aborted_limit) +{ + int draw_x1 = rect->x0; + int draw_x2 = rect->x1; + int draw_y1 = rect->y0; + int draw_y2 = rect->y1; + + // Here we'll store the time it took to draw the slowest buffer of this paint. + glong slowest_buffer = 0; + + // Find the optimal buffer dimensions int bw = draw_x2 - draw_x1; int bh = draw_y2 - draw_y1; if ((bw < 1) || (bh < 1)) - return; + return 0; - int sw, sh; + int sw, sh; // CAIRO FIXME: the sw/sh calculations below all assume 24bpp, need fixing for 32bpp if (canvas->rendermode != RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients /* 256K is the cached buffer and we need 3 channels */ if (bw * bh < 87381) { // 256K/3 @@ -1546,63 +1662,284 @@ sp_canvas_paint_rect (SPCanvas *canvas, int xx0, int yy0, int xx1, int yy1) sh = 512; } } + + // Will this paint require more than one buffer? + bool multiple_buffers = (((draw_y2 - draw_y1) > sh) || ((draw_x2 - draw_x1) > sw)); // or two_rects + + // remember the counter during this paint + long this_count = canvas->redraw_count; + + // Time values to measure each buffer's paint time + GTimeVal tstart, tfinish; + + // paint from the corner nearest the mouse pointer + + gint x, y; + gdk_window_get_pointer (GTK_WIDGET(canvas)->window, &x, &y, NULL); + NR::Point pw = sp_canvas_window_to_world (canvas, NR::Point(x,y)); + + bool reverse_x = (pw[NR::X] > ((draw_x2 + draw_x1) / 2)); + bool reverse_y = (pw[NR::Y] > ((draw_y2 + draw_y1) / 2)); + + if ((bw > bh) && (sh > sw)) { + int t = sw; sw = sh; sh = t; + } - // As we can come from expose, we have to tile here + // This is the main loop which corresponds to the visible left-to-right, top-to-bottom drawing + // of screen blocks (buffers). for (int y0 = draw_y1; y0 < draw_y2; y0 += sh) { int y1 = MIN (y0 + sh, draw_y2); for (int x0 = draw_x1; x0 < draw_x2; x0 += sw) { int x1 = MIN (x0 + sw, draw_x2); - SPCanvasBuf buf; - if (canvas->rendermode != RENDERMODE_OUTLINE) { - buf.buf = nr_pixelstore_256K_new (FALSE, 0); - } else { - buf.buf = nr_pixelstore_1M_new (FALSE, 0); - } - - buf.buf_rowstride = sw * 3; - buf.rect.x0 = x0; - buf.rect.y0 = y0; - buf.rect.x1 = x1; - buf.rect.y1 = y1; - GdkColor *color = &widget->style->bg[GTK_STATE_NORMAL]; - buf.bg_color = (((color->red & 0xff00) << 8) - | (color->green & 0xff00) - | (color->blue >> 8)); - buf.is_empty = true; - - if (canvas->root->flags & SP_CANVAS_ITEM_VISIBLE) { - SP_CANVAS_ITEM_GET_CLASS (canvas->root)->render (canvas->root, &buf); + int dx0 = x0; + int dx1 = x1; + int dy0 = y0; + int dy1 = y1; + + if (reverse_x) { + dx0 = (draw_x2 - (x0 + sw)) + draw_x1; + dx0 = MAX (dx0, draw_x1); + dx1 = (draw_x2 - x0) + draw_x1; + dx1 = MIN (dx1, draw_x2); } - - if (buf.is_empty) { - gdk_rgb_gc_set_foreground (canvas->pixmap_gc, buf.bg_color); - gdk_draw_rectangle (SP_CANVAS_WINDOW (canvas), - canvas->pixmap_gc, - TRUE, - x0 - canvas->x0, y0 - canvas->y0, - x1 - x0, y1 - y0); + if (reverse_y) { + dy0 = (draw_y2 - (y0 + sh)) + draw_y1; + dy0 = MAX (dy0, draw_y1); + dy1 = (draw_y2 - y0) + draw_y1; + dy1 = MIN (dy1, draw_y2); + } + + // SMOOTH SCROLLING: if we are scrolling, process pending events even before doing any rendering. + // This allows for scrolling smoothly without hiccups. Any accumulated redraws will be made + // when scrolling stops. The scrolling flag is set by sp_canvas_scroll_to for each scroll and zeroed + // here for each redraw, to ensure it never gets stuck. + + // OPTIMIZATION IDEA: if drawing is really slow (as measured by canvas->slowest + // buffer), do the same - process some events even before we paint any buffers + + if (canvas->is_scrolling) { + while (Gtk::Main::events_pending()) { // process any events + Gtk::Main::iteration(false); + } + canvas->is_scrolling = false; + if (this_count != canvas->redraw_count) { // if there was redraw, + return 1; // interrupt this one + } + } + + // Paint one buffer; measure how long it takes. + g_get_current_time (&tstart); + sp_canvas_paint_single_buffer (canvas, dx0, dy0, dx1, dy1, draw_x1, draw_y1, draw_x2, draw_y2, sw); + g_get_current_time (&tfinish); + + // Remember the slowest_buffer of this paint. + glong this_buffer = (tfinish.tv_sec - tstart.tv_sec) * 1000000 + (tfinish.tv_usec - tstart.tv_usec); + if (this_buffer > slowest_buffer) + slowest_buffer = this_buffer; + + // After each successful buffer, reduce the rect remaining to redraw by what is already redrawn + if (x1 >= draw_x2) { + if (reverse_y) { + if (canvas->redraw_aborted.y1 > dy0) { canvas->redraw_aborted.y1 = dy0; } + } else { + if (canvas->redraw_aborted.y0 < y1) { canvas->redraw_aborted.y0 = y1; } + } + } + + if (y1 >= draw_y2) { + if (reverse_x) { + if (canvas->redraw_aborted.x1 > dx0) { canvas->redraw_aborted.x1 = dx0; } + } else { + if (canvas->redraw_aborted.x0 < x1) { canvas->redraw_aborted.x0 = x1; } + } + } + + // INTERRUPTIBLE DISPLAY: + // Process events that may have arrived while we were busy drawing; + // only if we're drawing multiple buffers, and only if this one was not very fast, + // and only if we're allowed to interrupt this redraw + bool ok_to_interrupt = (multiple_buffers && this_buffer > 25000); + if (ok_to_interrupt && (canvas->forced_redraw_limit != -1)) { + ok_to_interrupt = (canvas->forced_redraw_count < canvas->forced_redraw_limit); + } + + if (ok_to_interrupt) { + // Run at most max_iterations of the main loop; we cannot process ALL events + // here because some things (e.g. rubberband) flood with dirtying events but will + // not redraw themselves + int max_iterations = 10; + int iterations = 0; + while (Gtk::Main::events_pending() && iterations++ < max_iterations) { + Gtk::Main::iteration(false); + // If one of the iterations has redrawn by itself, abort + if (this_count != canvas->redraw_count) { + canvas->slowest_buffer = slowest_buffer; + if (canvas->forced_redraw_limit != -1) { + canvas->forced_redraw_count++; + } + return 1; // interrupted + } + } + + // If not aborted so far, check if the events set redraw or update flags; + // if so, force update and abort + if (canvas->need_redraw || canvas->need_update) { + canvas->slowest_buffer = slowest_buffer; + if (canvas->forced_redraw_limit != -1) { + canvas->forced_redraw_count++; + } + do_update (canvas); + return 1; // interrupted + } + } + } + } + + // Remember the slowest buffer of this paint in canvas + canvas->slowest_buffer = slowest_buffer; + + return 0; // finished +} + + +/** + * Helper that draws a specific rectangular part of the canvas. + */ +static void +sp_canvas_paint_rect (SPCanvas *canvas, int xx0, int yy0, int xx1, int yy1) +{ + g_return_if_fail (!canvas->need_update); + + // Monotonously increment the canvas-global counter on each paint. This will let us find out + // when a new paint happened in event processing during this paint, so we can abort it. + canvas->redraw_count++; + + NRRectL rect; + rect.x0 = xx0; + rect.x1 = xx1; + rect.y0 = yy0; + rect.y1 = yy1; + + // Clip rect-to-draw by the current visible area + rect.x0 = MAX (rect.x0, canvas->x0); + rect.y0 = MAX (rect.y0, canvas->y0); + rect.x1 = MIN (rect.x1, canvas->x0/*draw_x1*/ + GTK_WIDGET (canvas)->allocation.width); + rect.y1 = MIN (rect.y1, canvas->y0/*draw_y1*/ + GTK_WIDGET (canvas)->allocation.height); + + // Clip rect-aborted-last-time by the current visible area + canvas->redraw_aborted.x0 = MAX (canvas->redraw_aborted.x0, canvas->x0); + canvas->redraw_aborted.y0 = MAX (canvas->redraw_aborted.y0, canvas->y0); + canvas->redraw_aborted.x1 = MIN (canvas->redraw_aborted.x1, canvas->x0/*draw_x1*/ + GTK_WIDGET (canvas)->allocation.width); + canvas->redraw_aborted.y1 = MIN (canvas->redraw_aborted.y1, canvas->y0/*draw_y1*/ + GTK_WIDGET (canvas)->allocation.height); + + if (canvas->redraw_aborted.x0 < canvas->redraw_aborted.x1 && canvas->redraw_aborted.y0 < canvas->redraw_aborted.y1) { + // There was an aborted redraw last time, now we need to redraw BOTH it and the new rect. + + // save the old aborted rect in case we decide to paint it separately (see below) + NRRectL aborted = canvas->redraw_aborted; + + // calculate the rectangle union of the both rects (the smallest rectangle which covers both) + NRRectL nion; + nr_rect_l_union (&nion, &rect, &aborted); + + // subtract one of the rects-to-draw from the other (the smallest rectangle which covers + // all of the first not covered by the second) + NRRectL rect_minus_aborted; + nr_rect_l_subtract (&rect_minus_aborted, &rect, &aborted); + + // Initially, the rect to redraw later (in case we're aborted) is the same as the union of both rects + canvas->redraw_aborted = nion; + + // calculate areas of the three rects + if ((nr_rect_l_area(&rect_minus_aborted) + nr_rect_l_area(&aborted)) * 1.2 < nr_rect_l_area(&nion)) { + // If the summary area of the two rects is significantly (at least by 20%) less than + // the area of their rectangular union, it makes sense to paint the two rects + // separately instead of painting their union. This gives a significant speedup when, + // for example, your current canvas is almost painted, with only a strip at bottom + // left, and at that moment you abort it by scrolling down which reveals a new strip at + // the top. Straightforward painting of the union of the aborted rect and the new rect + // will have to repaint the entire canvas! By contrast, the optimized approach below + // paints the two narrow strips in order which is much faster. + + // find out which rect to draw first - compare them first by y then by x of the top left corners + NRRectL *first; + NRRectL *second; + if (rect.y0 == aborted.y0) { + if (rect.x0 < aborted.x0) { + first = ▭ + second = &aborted; + } else { + second = ▭ + first = &aborted; + } + } else if (rect.y0 < aborted.y0) { + first = ▭ + second = &aborted; } else { - gdk_draw_rgb_image_dithalign (SP_CANVAS_WINDOW (canvas), - canvas->pixmap_gc, - x0 - canvas->x0, y0 - canvas->y0, - x1 - x0, y1 - y0, - GDK_RGB_DITHER_MAX, - buf.buf, - sw * 3, - x0 - canvas->x0, y0 - canvas->y0); + second = ▭ + first = &aborted; + } + + NRRectL second_minus_first; + nr_rect_l_subtract (&second_minus_first, second, first); + + // paint the first rect; + if (sp_canvas_paint_rect_internal (canvas, first, &(second_minus_first.x0), &(second_minus_first.y0))) { + // aborted! + return; } - if (canvas->rendermode != RENDERMODE_OUTLINE) { - nr_pixelstore_256K_free (buf.buf); - } else { - nr_pixelstore_1M_free (buf.buf); - } + // if not aborted, assign (second rect minus first) as the new redraw_aborted and paint the same + canvas->redraw_aborted = second_minus_first; + if (sp_canvas_paint_rect_internal (canvas, &second_minus_first, NULL, NULL)) { + return; // aborted + } + } else { + // no need for separate drawing, just draw the union as one rect + if (sp_canvas_paint_rect_internal (canvas, &nion, NULL, NULL)) { + return; // aborted + } } + } else { + // Nothing was aborted last time, just draw the rect we're given + + // Initially, the rect to redraw later (in case we're aborted) is the same as the one we're going to draw now. + canvas->redraw_aborted = rect; + + if (sp_canvas_paint_rect_internal (canvas, &rect, NULL, NULL)) { + return; // aborted + } + } + + // we've had a full unaborted redraw, reset the full redraw counter + if (canvas->forced_redraw_limit != -1) { + canvas->forced_redraw_count = 0; } } +/** + * Force a full redraw after a specified number of interrupted redraws + */ +void +sp_canvas_force_full_redraw_after_interruptions(SPCanvas *canvas, unsigned int count) { + g_return_if_fail(canvas != NULL); + + canvas->forced_redraw_limit = count; + canvas->forced_redraw_count = 0; +} + +/** + * End forced full redraw requests + */ +void +sp_canvas_end_forced_full_redraws(SPCanvas *canvas) { + g_return_if_fail(canvas != NULL); + + canvas->forced_redraw_limit = -1; +} + /** * The canvas widget's expose callback. */ @@ -1627,12 +1964,7 @@ sp_canvas_expose (GtkWidget *widget, GdkEventExpose *event) rect.x1 = rect.x0 + rects[i].width; rect.y1 = rect.y0 + rects[i].height; - if (canvas->need_update || canvas->need_redraw) { - sp_canvas_request_redraw (canvas, rect.x0, rect.y0, rect.x1, rect.y1); - } else { - /* No pending updates, draw exposed area immediately */ - sp_canvas_paint_rect (canvas, rect.x0, rect.y0, rect.x1, rect.y1); - } + sp_canvas_request_redraw (canvas, rect.x0, rect.y0, rect.x1, rect.y1); } if (n_rects > 0) @@ -1716,44 +2048,41 @@ paint (SPCanvas *canvas) int const canvas_x1 = canvas->x0 + widget->allocation.width; int const canvas_y1 = canvas->y0 + widget->allocation.height; - NRRectL topaint; - topaint.x0 = topaint.y0 = topaint.x1 = topaint.y1 = 0; + bool dirty = false; - for (int j=canvas->tTop&(~3);jtBottom;j+=4) { - for (int i=canvas->tLeft&(~3);itRight;i+=4) { - int mode=0; - - int pl=i+1,pr=i,pt=j+4,pb=j; - for (int l=MAX(j,canvas->tTop);ltBottom);l++) { - for (int k=MAX(i,canvas->tLeft);ktRight);k++) { - if ( canvas->tiles[(k-canvas->tLeft)+(l-canvas->tTop)*canvas->tileH] ) { - mode|=1<<((k-i)+(l-j)*4); - if ( k < pl ) pl=k; - if ( k+1 > pr ) pr=k+1; - if ( l < pt ) pt=l; - if ( l+1 > pb ) pb=l+1; - } - canvas->tiles[(k-canvas->tLeft)+(l-canvas->tTop)*canvas->tileH]=0; - } - } - - if ( mode ) { - NRRectL tile; - tile.x0 = MAX (pl*32, canvas->x0); - tile.y0 = MAX (pt*32, canvas->y0); - tile.x1 = MIN (pr*32, canvas_x1); - tile.y1 = MIN (pb*32, canvas_y1); - if ((tile.x0 < tile.x1) && (tile.y0 < tile.y1)) { - nr_rect_l_union (&topaint, &topaint, &tile); - } + int pl = canvas->tRight, pr = canvas->tLeft, pt = canvas->tBottom, pb = canvas->tTop; // start with "inverted" tile rect + + for (int j=canvas->tTop; jtBottom; j++) { + for (int i=canvas->tLeft; itRight; i++) { + int tile_index = (i - canvas->tLeft) + (j - canvas->tTop)*canvas->tileH; + + if ( canvas->tiles[tile_index] ) { // if this tile is dirtied (nonzero) + dirty = true; + // make (pl..pr)x(pt..pb) the minimal rect covering all dirtied tiles + if ( i < pl ) pl = i; + if ( i+1 > pr ) pr = i+1; + if ( j < pt ) pt = j; + if ( j+1 > pb ) pb = j+1; } + + canvas->tiles[tile_index] = 0; // undirty this tile } } - sp_canvas_paint_rect (canvas, topaint.x0, topaint.y0, topaint.x1, topaint.y1); - canvas->need_redraw = FALSE; + + if ( dirty ) { + NRRectL topaint; + topaint.x0 = MAX (pl*TILE_SIZE, canvas->x0); + topaint.y0 = MAX (pt*TILE_SIZE, canvas->y0); + topaint.x1 = MIN (pr*TILE_SIZE, canvas_x1); + topaint.y1 = MIN (pb*TILE_SIZE, canvas_y1); + if ((topaint.x0 < topaint.x1) && (topaint.y0 < topaint.y1)) { + sp_canvas_paint_rect (canvas, topaint.x0, topaint.y0, topaint.x1, topaint.y1); + } + } + return TRUE; } @@ -1763,6 +2092,9 @@ paint (SPCanvas *canvas) static int do_update (SPCanvas *canvas) { + if (!canvas->root) // canvas may have already be destroyed by closing desktop durring interrupted display! + return TRUE; + /* Cause the update if necessary */ if (canvas->need_update) { sp_canvas_item_invoke_update (canvas->root, NR::identity(), 0); @@ -1833,7 +2165,7 @@ sp_canvas_root (SPCanvas *canvas) * Scrolls canvas to specific position. */ void -sp_canvas_scroll_to (SPCanvas *canvas, double cx, double cy, unsigned int clear) +sp_canvas_scroll_to (SPCanvas *canvas, double cx, double cy, unsigned int clear, bool is_scrolling) { g_return_if_fail (canvas != NULL); g_return_if_fail (SP_IS_CANVAS (canvas)); @@ -1848,27 +2180,14 @@ sp_canvas_scroll_to (SPCanvas *canvas, double cx, double cy, unsigned int clear) canvas->x0 = ix; canvas->y0 = iy; - sp_canvas_resize_tiles(canvas,canvas->x0,canvas->y0,canvas->x0+canvas->widget.allocation.width,canvas->y0+canvas->widget.allocation.height); + sp_canvas_resize_tiles (canvas, canvas->x0, canvas->y0, canvas->x0+canvas->widget.allocation.width, canvas->y0+canvas->widget.allocation.height); if (!clear) { // scrolling without zoom; redraw only the newly exposed areas if ((dx != 0) || (dy != 0)) { - int width, height; - width = canvas->widget.allocation.width; - height = canvas->widget.allocation.height; + canvas->is_scrolling = is_scrolling; if (GTK_WIDGET_REALIZED (canvas)) { gdk_window_scroll (SP_CANVAS_WINDOW (canvas), -dx, -dy); - gdk_window_process_updates (SP_CANVAS_WINDOW (canvas), TRUE); - } - if (dx < 0) { - sp_canvas_request_redraw (canvas, ix + 0, iy + 0, ix - dx, iy + height); - } else if (dx > 0) { - sp_canvas_request_redraw (canvas, ix + width - dx, iy + 0, ix + width, iy + height); - } - if (dy < 0) { - sp_canvas_request_redraw (canvas, ix + 0, iy + 0, ix + width, iy - dy); - } else if (dy > 0) { - sp_canvas_request_redraw (canvas, ix + 0, iy + height - dy, ix + width, iy + height); } } } else { @@ -1931,7 +2250,7 @@ sp_canvas_request_redraw (SPCanvas *canvas, int x0, int y0, int x1, int y1) nr_rect_l_intersect (&clip, &bbox, &visible); - sp_canvas_dirty_rect(canvas,x0,y0,x1,y1); + sp_canvas_dirty_rect(canvas, clip.x0, clip.y0, clip.x1, clip.y1); add_idle (canvas); } @@ -1999,7 +2318,7 @@ bool sp_canvas_world_pt_inside_window(SPCanvas const *canvas, NR::Point const &w } /** - * Return canvas window coordinates as NRRect. + * Return canvas window coordinates as NR::Rect. */ NR::Rect SPCanvas::getViewbox() const { @@ -2011,21 +2330,21 @@ NR::Rect SPCanvas::getViewbox() const inline int sp_canvas_tile_floor(int x) { - return (x&(~31))/32; + return (x & (~(TILE_SIZE - 1))) / TILE_SIZE; } inline int sp_canvas_tile_ceil(int x) { - return ((x+31)&(~31))/32; + return ((x + (TILE_SIZE - 1)) & (~(TILE_SIZE - 1))) / TILE_SIZE; } /** - * Helper that changes tile size for canvas redraw. + * Helper that allocates a new tile array for the canvas, copying overlapping tiles from the old array */ void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb) { if ( nl >= nr || nt >= nb ) { - if ( canvas->tiles ) free(canvas->tiles); + if ( canvas->tiles ) g_free(canvas->tiles); canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0; canvas->tileH=canvas->tileV=0; canvas->tiles=NULL; @@ -2036,19 +2355,19 @@ void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb) int tr=sp_canvas_tile_ceil(nr); int tb=sp_canvas_tile_ceil(nb); - int nh=tr-tl,nv=tb-tt; - uint8_t* ntiles=(uint8_t*)malloc(nh*nv*sizeof(uint8_t)); - for (int i=tl;i= canvas->tLeft && i < canvas->tRight && j >= canvas->tTop && j < canvas->tBottom ) { - ntiles[ind]=canvas->tiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH]; + ntiles[ind]=canvas->tiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH]; // copy from the old tile } else { - ntiles[ind]=0; + ntiles[ind]=0; // newly exposed areas get 0 } } } - if ( canvas->tiles ) free(canvas->tiles); + if ( canvas->tiles ) g_free(canvas->tiles); canvas->tiles=ntiles; canvas->tLeft=tl; canvas->tTop=tt; @@ -2059,7 +2378,7 @@ void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb) } /** - * Helper that marks specific canvas rectangle for redraw. + * Helper that marks specific canvas rectangle for redraw by dirtying its tiles */ void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb) { @@ -2078,9 +2397,9 @@ void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb) canvas->need_redraw = TRUE; - for (int i=tl;itiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH]=1; + for (int i=tl; itiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH] = 1; } } }