X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fdisplay%2Fsp-canvas.cpp;h=9cc37a1a0c5b6b1c5a885b3a33d737a1b828164b;hb=8f2afbd99dcbbe962be70bc574e356b33c8b9537;hp=39ebc17d1d2fb7f0f0c20e2b75eff179332ad141;hpb=68a136703d96e42fba5a2464e2eb09eb3fee5815;p=inkscape.git diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp index 39ebc17d1..9cc37a1a0 100644 --- a/src/display/sp-canvas.cpp +++ b/src/display/sp-canvas.cpp @@ -34,14 +34,22 @@ #include #include #include "prefs-utils.h" +#include "inkscape.h" +#include "sodipodi-ctrlrect.h" +#if ENABLE_LCMS +#include "color-profile-fns.h" +#endif // ENABLE_LCMS +#include "display/rendermode.h" -enum { - RENDERMODE_NORMAL, - RENDERMODE_NOAA, - RENDERMODE_OUTLINE -}; +// Define this to visualize the regions to be redrawn +//#define DEBUG_REDRAW 1; + +// 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 16 -const gint sp_canvas_update_priority = G_PRIORITY_HIGH_IDLE; +static gint const sp_canvas_update_priority = G_PRIORITY_HIGH_IDLE; #define SP_CANVAS_WINDOW(c) (((GtkWidget *) (c))->window) @@ -87,7 +95,7 @@ static void sp_canvas_request_update (SPCanvas *canvas); static void sp_canvas_item_class_init (SPCanvasItemClass *klass); static void sp_canvas_item_init (SPCanvasItem *item); static void sp_canvas_item_dispose (GObject *object); -static void sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar *first_arg_name, va_list args); +static void sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, gchar const *first_arg_name, va_list args); static int emit_event (SPCanvas *canvas, GdkEvent *event); @@ -103,7 +111,7 @@ sp_canvas_item_get_type (void) { static GType type = 0; if (!type) { - static const GTypeInfo info = { + static GTypeInfo const info = { sizeof (SPCanvasItemClass), NULL, NULL, (GClassInitFunc) sp_canvas_item_class_init, @@ -133,7 +141,7 @@ sp_canvas_item_class_init (SPCanvasItemClass *klass) item_signals[ITEM_EVENT] = g_signal_new ("event", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (SPCanvasItemClass, event), + ((glong)((guint8*)&(klass->event) - (guint8*)klass)), NULL, NULL, sp_marshal_BOOLEAN__POINTER, G_TYPE_BOOLEAN, 1, @@ -156,7 +164,7 @@ sp_canvas_item_init (SPCanvasItem *item) * Constructs new SPCanvasItem on SPCanvasGroup. */ SPCanvasItem * -sp_canvas_item_new (SPCanvasGroup *parent, GtkType type, const gchar *first_arg_name, ...) +sp_canvas_item_new (SPCanvasGroup *parent, GtkType type, gchar const *first_arg_name, ...) { va_list args; @@ -179,7 +187,7 @@ sp_canvas_item_new (SPCanvasGroup *parent, GtkType type, const gchar *first_arg_ * We make it static for encapsulation reasons since it was nowhere used. */ static void -sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar *first_arg_name, va_list args) +sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, gchar const *first_arg_name, va_list args) { g_return_if_fail (SP_IS_CANVAS_GROUP (parent)); g_return_if_fail (SP_IS_CANVAS_ITEM (item)); @@ -192,8 +200,6 @@ sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar group_add (SP_CANVAS_GROUP (item->parent), item); sp_canvas_item_request_update (item); - sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1)); - item->canvas->need_repick = TRUE; } /** @@ -203,7 +209,14 @@ static void redraw_if_visible (SPCanvasItem *item) { if (item->flags & SP_CANVAS_ITEM_VISIBLE) { - sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1)); + int x0 = (int)(item->x1); + int x1 = (int)(item->x2); + int y0 = (int)(item->y1); + int y1 = (int)(item->y2); + + if (x0 !=0 || x1 !=0 || y0 !=0 || y1 !=0) { + sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1)); + } } } @@ -215,7 +228,15 @@ sp_canvas_item_dispose (GObject *object) { SPCanvasItem *item = SP_CANVAS_ITEM (object); - redraw_if_visible (item); + // Hack: if this is a ctrlrect, move it to 0,0; + // this redraws only the stroke of the rect to be deleted, + // avoiding redraw of the entire area + if (SP_IS_CTRLRECT(item)) { + SP_CTRLRECT(object)->setRectangle(NR::Rect(NR::Point(0,0),NR::Point(0,0))); + SP_CTRLRECT(object)->update(item->xform, 0); + } else { + redraw_if_visible (item); + } item->flags &= ~SP_CANVAS_ITEM_VISIBLE; if (item == item->canvas->current_item) { @@ -272,11 +293,11 @@ sp_canvas_item_invoke_update (SPCanvasItem *item, NR::Matrix const &affine, unsi GTK_OBJECT_UNSET_FLAGS (item, SP_CANVAS_ITEM_NEED_AFFINE); } -/** - * Helper function to invoke the point method of the item. +/** + * Helper function to invoke the point method of the item. * - * The argument x, y should be in the parent's item-relative coordinate - * system. This routine applies the inverse of the item's transform, + * The argument x, y should be in the parent's item-relative coordinate + * system. This routine applies the inverse of the item's transform, * maintaining the affine invariant. */ static double @@ -291,12 +312,12 @@ sp_canvas_item_invoke_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **act /** * Makes the item's affine transformation matrix be equal to the specified * matrix. - * + * * @item: A canvas item. * @affine: An affine transformation matrix. */ void -sp_canvas_item_affine_absolute (SPCanvasItem *item, NR::Matrix const& affine) +sp_canvas_item_affine_absolute (SPCanvasItem *item, NR::Matrix const &affine) { item->xform = affine; @@ -313,7 +334,7 @@ sp_canvas_item_affine_absolute (SPCanvasItem *item, NR::Matrix const& affine) } /** - * Convenience function to reorder items in a group's child list. + * Convenience function to reorder items in a group's child list. * * This puts the specified link after the "before" link. */ @@ -369,7 +390,7 @@ put_item_after (GList *link, GList *before) /** * Raises the item in its parent's stack by the specified number of positions. - * + * * \param item A canvas item. * \param positions Number of steps to raise the item. * @@ -454,8 +475,15 @@ sp_canvas_item_show (SPCanvasItem *item) item->flags |= SP_CANVAS_ITEM_VISIBLE; - sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1)); - item->canvas->need_repick = TRUE; + int x0 = (int)(item->x1); + int x1 = (int)(item->x2); + int y0 = (int)(item->y1); + int y1 = (int)(item->y2); + + if (x0 !=0 || x1 !=0 || y0 !=0 || y1 !=0) { + sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1)); + item->canvas->need_repick = TRUE; + } } /** @@ -472,13 +500,20 @@ sp_canvas_item_hide (SPCanvasItem *item) item->flags &= ~SP_CANVAS_ITEM_VISIBLE; - sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)(item->x2 + 1), (int)(item->y2 + 1)); - item->canvas->need_repick = TRUE; + int x0 = (int)(item->x1); + int x1 = (int)(item->x2); + int y0 = (int)(item->y1); + int y1 = (int)(item->y2); + + if (x0 !=0 || x1 !=0 || y0 !=0 || y1 !=0) { + sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)(item->x2 + 1), (int)(item->y2 + 1)); + item->canvas->need_repick = TRUE; + } } /** * Grab item under cursor. - * + * * \pre !canvas->grabbed_item && item->flags & SP_CANVAS_ITEM_VISIBLE */ int @@ -511,7 +546,7 @@ sp_canvas_item_grab (SPCanvasItem *item, guint event_mask, GdkCursor *cursor, gu /** * Ungrabs the item, which must have been grabbed in the canvas, and ungrabs the * mouse. - * + * * \param item A canvas item that holds a grab. * \param etime The timestamp for ungrabbing the mouse. */ @@ -598,7 +633,7 @@ sp_canvas_item_grab_focus (SPCanvasItem *item) /** * Requests that the canvas queue an update for the specified item. - * + * * To be used only by item implementations. */ void @@ -641,25 +676,25 @@ static SPCanvasItemClass *group_parent_class; /** * Registers SPCanvasGroup class with Gtk and returns its type number. */ -GtkType -sp_canvas_group_get_type (void) +GType sp_canvas_group_get_type(void) { - static GtkType group_type = 0; - - if (!group_type) { - static const GtkTypeInfo group_info = { - "SPCanvasGroup", - sizeof (SPCanvasGroup), - sizeof (SPCanvasGroupClass), - (GtkClassInitFunc) sp_canvas_group_class_init, - (GtkObjectInitFunc) sp_canvas_group_init, - NULL, NULL, NULL + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPCanvasGroupClass), + 0, // base_init + 0, // base_finalize + (GClassInitFunc)sp_canvas_group_class_init, + 0, // class_finalize + 0, // class_data + sizeof(SPCanvasGroup), + 0, // n_preallocs + (GInstanceInitFunc)sp_canvas_group_init, + 0 // value_table }; - - group_type = gtk_type_unique (sp_canvas_item_get_type (), &group_info); + type = g_type_register_static(sp_canvas_item_get_type(), "SPCanvasGroup", &info, static_cast(0)); } - - return group_type; + return type; } /** @@ -690,7 +725,7 @@ sp_canvas_group_init (SPCanvasGroup */*group*/) } /** - * Callback that destroys all items in group and calls group's virtual + * Callback that destroys all items in group and calls group's virtual * destroy() function. */ static void @@ -699,7 +734,7 @@ sp_canvas_group_destroy (GtkObject *object) g_return_if_fail (object != NULL); g_return_if_fail (SP_IS_CANVAS_GROUP (object)); - const SPCanvasGroup *group = SP_CANVAS_GROUP (object); + SPCanvasGroup const *group = SP_CANVAS_GROUP (object); GList *list = group->items; while (list) { @@ -719,7 +754,7 @@ sp_canvas_group_destroy (GtkObject *object) static void sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags) { - const SPCanvasGroup *group = SP_CANVAS_GROUP (item); + SPCanvasGroup const *group = SP_CANVAS_GROUP (item); NR::ConvexHull corners(NR::Point(0, 0)); bool empty=true; @@ -739,11 +774,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; + } } /** @@ -752,9 +792,9 @@ sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned i static double sp_canvas_group_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item) { - const SPCanvasGroup *group = SP_CANVAS_GROUP (item); - const double x = p[NR::X]; - const double y = p[NR::Y]; + SPCanvasGroup const *group = SP_CANVAS_GROUP (item); + double const x = p[NR::X]; + double const y = p[NR::Y]; int x1 = (int)(x - item->canvas->close_enough); int y1 = (int)(y - item->canvas->close_enough); int x2 = (int)(x + item->canvas->close_enough); @@ -794,7 +834,7 @@ sp_canvas_group_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_it static void sp_canvas_group_render (SPCanvasItem *item, SPCanvasBuf *buf) { - const SPCanvasGroup *group = SP_CANVAS_GROUP (item); + SPCanvasGroup const *group = SP_CANVAS_GROUP (item); for (GList *list = group->items; list; list = list->next) { SPCanvasItem *child = (SPCanvasItem *)list->data; @@ -829,7 +869,7 @@ group_add (SPCanvasGroup *group, SPCanvasItem *item) sp_canvas_item_request_update (item); } -/** +/** * Removes an item from a canvas group */ static void @@ -881,8 +921,9 @@ static gint sp_canvas_focus_out (GtkWidget *widget, GdkEventFocus *event); 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 void sp_canvas_resize_tiles(SPCanvas* canvas, int nl, int nt, int nr, int nb); +static void sp_canvas_dirty_rect(SPCanvas* canvas, int nl, int nt, int nr, int nb); +static void sp_canvas_mark_rect(SPCanvas* canvas, int nl, int nt, int nr, int nb, uint8_t val); static int do_update (SPCanvas *canvas); /** @@ -891,25 +932,25 @@ static int do_update (SPCanvas *canvas); * * \return The type ID of the SPCanvas class. **/ -GtkType -sp_canvas_get_type (void) -{ - static GtkType canvas_type = 0; - - if (!canvas_type) { - static const GtkTypeInfo canvas_info = { - "SPCanvas", - sizeof (SPCanvas), - sizeof (SPCanvasClass), - (GtkClassInitFunc) sp_canvas_class_init, - (GtkObjectInitFunc) sp_canvas_init, - NULL, NULL, NULL +GType sp_canvas_get_type(void) +{ + static GType type = 0; + if (!type) { + GTypeInfo info = { + sizeof(SPCanvasClass), + 0, // base_init + 0, // base_finalize + (GClassInitFunc)sp_canvas_class_init, + 0, // class_finalize + 0, // class_data + sizeof(SPCanvas), + 0, // n_preallocs + (GInstanceInitFunc)sp_canvas_init, + 0 // value_table }; - - canvas_type = gtk_type_unique (GTK_TYPE_WIDGET, &canvas_info); + type = g_type_register_static(GTK_TYPE_WIDGET, "SPCanvas", &info, static_cast(0)); } - - return canvas_type; + return type; } /** @@ -942,7 +983,7 @@ sp_canvas_class_init (SPCanvasClass *klass) widget_class->focus_out_event = sp_canvas_focus_out; } -/** +/** * Callback: object initialization for SPCanvas. */ static void @@ -972,17 +1013,16 @@ sp_canvas_init (SPCanvas *canvas) 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; +#if ENABLE_LCMS + canvas->enable_cms_display_adj = false; + canvas->cms_key = new Glib::ustring(""); +#endif // ENABLE_LCMS + + canvas->is_scrolling = false; + } /** @@ -1106,6 +1146,10 @@ sp_canvas_unrealize (GtkWidget *widget) { SPCanvas *canvas = SP_CANVAS (widget); + canvas->current_item = NULL; + canvas->grabbed_item = NULL; + canvas->focused_item = NULL; + shutdown_transients (canvas); gdk_gc_destroy (canvas->pixmap_gc); @@ -1153,7 +1197,7 @@ sp_canvas_size_allocate (GtkWidget *widget, GtkAllocation *allocation) } widget->allocation = *allocation; - + if (GTK_WIDGET_REALIZED (widget)) { gdk_window_move_resize (widget->window, widget->allocation.x, widget->allocation.y, @@ -1162,7 +1206,7 @@ sp_canvas_size_allocate (GtkWidget *widget, GtkAllocation *allocation) } /** - * Helper that emits an event for an item in the canvas, be it the current + * Helper that emits an event for an item in the canvas, be it the current * item, grabbed item, or focused item, as appropriate. */ static int @@ -1269,7 +1313,7 @@ emit_event (SPCanvas *canvas, GdkEvent *event) } /** - * Helper that re-picks the current item in the canvas, based on the event's + * Helper that re-picks the current item in the canvas, based on the event's * coordinates and emits enter/leave events for items as appropriate. */ static int @@ -1420,7 +1464,7 @@ sp_canvas_button (GtkWidget *widget, GdkEventButton *event) /* dispatch normally regardless of the event's window if an item has has a pointer grab in effect */ - if (!canvas->grabbed_item && + if (!canvas->grabbed_item && event->window != SP_CANVAS_WINDOW (canvas)) return retval; @@ -1499,6 +1543,9 @@ sp_canvas_motion (GtkWidget *widget, GdkEventMotion *event) if (event->window != SP_CANVAS_WINDOW (canvas)) return FALSE; + if (canvas->pixmap_gc == NULL) // canvas being deleted + return FALSE; + if (canvas->grabbed_event_mask & GDK_POINTER_MOTION_HINT_MASK) { gint x, y; gdk_window_get_pointer (widget->window, &x, &y, NULL); @@ -1518,12 +1565,15 @@ sp_canvas_paint_single_buffer (SPCanvas *canvas, int x0, int y0, int x1, int y1, GtkWidget *widget = GTK_WIDGET (canvas); SPCanvasBuf buf; - if (canvas->rendermode != RENDERMODE_OUTLINE) { + if (canvas->rendermode != Inkscape::RENDERMODE_OUTLINE) { buf.buf = nr_pixelstore_256K_new (FALSE, 0); } else { buf.buf = nr_pixelstore_1M_new (FALSE, 0); } + // Mark the region clean + sp_canvas_mark_rect(canvas, x0, y0, x1, y1, 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; @@ -1538,12 +1588,27 @@ sp_canvas_paint_single_buffer (SPCanvas *canvas, int x0, int y0, int x1, int y1, | (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 ENABLE_LCMS + cmsHTRANSFORM transf = 0; + long long int fromDisplay = prefs_get_int_attribute_limited( "options.displayprofile", "from_display", 0, 0, 1 ); + if ( fromDisplay ) { + transf = Inkscape::colorprofile_get_display_per( canvas->cms_key ? *(canvas->cms_key) : "" ); + } else { + transf = Inkscape::colorprofile_get_display_transform(); + } +#endif // ENABLE_LCMS + if (buf.is_empty) { +#if ENABLE_LCMS + if ( transf && canvas->enable_cms_display_adj ) { + cmsDoTransform( transf, &buf.bg_color, &buf.bg_color, 1 ); + } +#endif // ENABLE_LCMS gdk_rgb_gc_set_foreground (canvas->pixmap_gc, buf.bg_color); gdk_draw_rectangle (SP_CANVAS_WINDOW (canvas), canvas->pixmap_gc, @@ -1552,7 +1617,13 @@ sp_canvas_paint_single_buffer (SPCanvas *canvas, int x0, int y0, int x1, int y1, x1 - x0, y1 - y0); } else { /* - CAIRO FIXME: after SPCanvasBuf is made 32bpp throughout, this rgb_draw below can be replaced with: +// 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 @@ -1562,12 +1633,20 @@ sp_canvas_paint_single_buffer (SPCanvas *canvas, int x0, int y0, int x1, int y1, 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); */ +#if ENABLE_LCMS + if ( transf && canvas->enable_cms_display_adj ) { + for ( gint yy = 0; yy < (y1 - y0); yy++ ) { + guchar* p = buf.buf + (sw * 3) * yy; + cmsDoTransform( transf, p, p, (x1 - x0) ); + } + } +#endif // ENABLE_LCMS + gdk_draw_rgb_image_dithalign (SP_CANVAS_WINDOW (canvas), canvas->pixmap_gc, x0 - canvas->x0, y0 - canvas->y0, @@ -1578,209 +1657,143 @@ sp_canvas_paint_single_buffer (SPCanvas *canvas, int x0, int y0, int x1, int y1, x0 - canvas->x0, y0 - canvas->y0); } - if (canvas->rendermode != RENDERMODE_OUTLINE) { + if (canvas->rendermode != Inkscape::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) +struct PaintRectSetup { + SPCanvas* canvas; + NRRectL big_rect; + GTimeVal start_time; + int max_pixels; + NR::Point mouse_loc; +}; + +/** + * Paint the given rect, recursively subdividing the region until it is the size of a single + * buffer. + * + * @return true if the drawing completes */ 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; +sp_canvas_paint_rect_internal (PaintRectSetup const *setup, NRRectL this_rect) +{ + GTimeVal now; + g_get_current_time (&now); + + glong elapsed = (now.tv_sec - setup->start_time.tv_sec) * 1000000 + + (now.tv_usec - setup->start_time.tv_usec); + + // Allow only very fast buffers to be run together; + // as soon as the total redraw time exceeds 1ms, cancel; + // this returns control to the idle loop and allows Inkscape to process user input + // (potentially interrupting the redraw); as soon as Inkscape has some more idle time, + // it will get back and finish painting what remains to paint. + if (elapsed > 1000) { + + // Interrupting redraw isn't always good. + // For example, when you drag one node of a big path, only the buffer containing + // the mouse cursor will be redrawn again and again, and the rest of the path + // will remain stale because Inkscape never has enough idle time to redraw all + // of the screen. To work around this, such operations set a forced_redraw_limit > 0. + // If this limit is set, and if we have aborted redraw more times than is allowed, + // interrupting is blocked and we're forced to redraw full screen once + // (after which we can again interrupt forced_redraw_limit times). + if (setup->canvas->forced_redraw_limit < 0 || + setup->canvas->forced_redraw_count < setup->canvas->forced_redraw_limit) { + + if (setup->canvas->forced_redraw_limit != -1) { + setup->canvas->forced_redraw_count++; + } - // Here we'll store the time it took to draw the slowest buffer of this paint. - glong slowest_buffer = 0; + return false; + } + } // Find the optimal buffer dimensions - int bw = draw_x2 - draw_x1; - int bh = draw_y2 - draw_y1; + int bw = this_rect.x1 - this_rect.x0; + int bh = this_rect.y1 - this_rect.y0; if ((bw < 1) || (bh < 1)) return 0; - 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 - // We can go with single buffer - sw = bw; - sh = bh; - } else if (bw <= (16 * 341)) { - // Go with row buffer - sw = bw; - sh = 87381 / bw; - } else if (bh <= (16 * 256)) { - // Go with column buffer - sw = 87381 / bh; - sh = bh; - } else { - sw = 341; - sh = 256; - } - } else { // paths only, so 1M works faster - /* 1M is the cached buffer and we need 3 channels */ - if (bw * bh < 349525) { // 1M/3 - // We can go with single buffer - sw = bw; - sh = bh; - } else if (bw <= (16 * 682)) { - // Go with row buffer - sw = bw; - sh = 349525 / bw; - } else if (bh <= (16 * 512)) { - // Go with column buffer - sw = 349525 / bh; - sh = bh; - } else { - sw = 682; - 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; + if (bw * bh < setup->max_pixels) { + // We are small enough + sp_canvas_paint_single_buffer (setup->canvas, + this_rect.x0, this_rect.y0, + this_rect.x1, this_rect.y1, + setup->big_rect.x0, setup->big_rect.y0, + setup->big_rect.x1, setup->big_rect.y1, bw); + return 1; } - // 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); - - 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 (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); - } + NRRectL lo = this_rect; + NRRectL hi = this_rect; - // OPTIMIZATION IDEA: if drawing is really slow (as measured by canvas->slowest - // buffer), process some events even BEFORE we do any buffers? - - // 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; } - } - } +/* +This test determines the redraw strategy: - 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; } - } - } +bw < bh (strips mode) splits across the smaller dimension of the rect and therefore (on +horizontally-stretched windows) results in redrawing in horizontal strips (from cursor point, in +both directions if the cursor is in the middle). This is traditional for Inkscape since old days, +and seems to be faster for drawings with many smaller objects at zoom-out. - // 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); - } +bw > bh (chunks mode) splits across the larger dimension of the rect and therefore paints in +almost-square chunks, again from the cursor point. It's sometimes faster for drawings with few slow +(e.g. blurred) objects crossing the entire screen. It also appears to be somewhat psychologically +faster. - 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 - } - } +The default for now is the strips mode. +*/ + if (bw < bh || bh < 2 * TILE_SIZE) { + // to correctly calculate the mean of two ints, we need to sum them into a larger int type + int mid = ((long long) this_rect.x0 + (long long) this_rect.x1) / 2; + // Make sure that mid lies on a tile boundary + mid = (mid / TILE_SIZE) * TILE_SIZE; + + lo.x1 = mid; + hi.x0 = mid; + + if (setup->mouse_loc[NR::X] < mid) { + // Always paint towards the mouse first + return sp_canvas_paint_rect_internal(setup, lo) + && sp_canvas_paint_rect_internal(setup, hi); + } else { + return sp_canvas_paint_rect_internal(setup, hi) + && sp_canvas_paint_rect_internal(setup, lo); + } + } else { + // to correctly calculate the mean of two ints, we need to sum them into a larger int type + int mid = ((long long) this_rect.y0 + (long long) this_rect.y1) / 2; + // Make sure that mid lies on a tile boundary + mid = (mid / TILE_SIZE) * TILE_SIZE; + + lo.y1 = mid; + hi.y0 = mid; + + if (setup->mouse_loc[NR::Y] < mid) { + // Always paint towards the mouse first + return sp_canvas_paint_rect_internal(setup, lo) + && sp_canvas_paint_rect_internal(setup, hi); + } else { + return sp_canvas_paint_rect_internal(setup, hi) + && sp_canvas_paint_rect_internal(setup, lo); } } - - // 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. + * + * @return true if the rectangle painting succeeds. */ -static void +static bool 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++; + g_return_val_if_fail (!canvas->need_update, false); NRRectL rect; rect.x0 = xx0; @@ -1794,96 +1807,42 @@ sp_canvas_paint_rect (SPCanvas *canvas, int xx0, int yy0, int xx1, int yy1) 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 { - second = ▭ - first = &aborted; - } +#ifdef DEBUG_REDRAW + // paint the area to redraw yellow + gdk_rgb_gc_set_foreground (canvas->pixmap_gc, 0xFFFF00); + gdk_draw_rectangle (SP_CANVAS_WINDOW (canvas), + canvas->pixmap_gc, + TRUE, + rect.x0 - canvas->x0, rect.y0 - canvas->y0, + rect.x1 - rect.x0, rect.y1 - rect.y0); +#endif - NRRectL second_minus_first; - nr_rect_l_subtract (&second_minus_first, second, first); + PaintRectSetup setup; - // paint the first rect; - if (sp_canvas_paint_rect_internal (canvas, first, &(second_minus_first.x0), &(second_minus_first.y0))) { - // aborted! - return; - } + setup.canvas = canvas; + setup.big_rect = rect; - // 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 - } + // Save the mouse location + gint x, y; + gdk_window_get_pointer (GTK_WIDGET(canvas)->window, &x, &y, NULL); + setup.mouse_loc = sp_canvas_window_to_world (canvas, NR::Point(x,y)); - } 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 - } - } + // CAIRO FIXME: the sw/sh calculations below all assume 24bpp, need fixing for 32bpp + if (canvas->rendermode != Inkscape::RENDERMODE_OUTLINE) { + // use 256K as a compromise to not slow down gradients + // 256K is the cached buffer and we need 3 channels + setup.max_pixels = 87381; // 256K/3 } 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 - } + // paths only, so 1M works faster + // 1M is the cached buffer and we need 3 channels + setup.max_pixels = 349525; } - // we've had a full unaborted redraw, reset the full redraw counter - if (canvas->forced_redraw_limit != -1) { - canvas->forced_redraw_count = 0; - } + // Start the clock + g_get_current_time(&(setup.start_time)); + + // Go + return sp_canvas_paint_rect_internal(&setup, rect); } /** @@ -1892,7 +1851,7 @@ sp_canvas_paint_rect (SPCanvas *canvas, int xx0, int yy0, int xx1, int yy1) 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; } @@ -1915,7 +1874,7 @@ sp_canvas_expose (GtkWidget *widget, GdkEventExpose *event) { SPCanvas *canvas = SP_CANVAS (widget); - if (!GTK_WIDGET_DRAWABLE (widget) || + if (!GTK_WIDGET_DRAWABLE (widget) || (event->window != SP_CANVAS_WINDOW (canvas))) return FALSE; @@ -1925,7 +1884,7 @@ sp_canvas_expose (GtkWidget *widget, GdkEventExpose *event) for (int i = 0; i < n_rects; i++) { NRRectL rect; - + rect.x0 = rects[i].x + canvas->x0; rect.y0 = rects[i].y + canvas->y0; rect.x1 = rect.x0 + rects[i].width; @@ -1999,6 +1958,8 @@ sp_canvas_focus_out (GtkWidget *widget, GdkEventFocus *event) /** * Helper that repaints the areas in the canvas that need it. + * + * @return true if all the dirty parts have been redrawn */ static int paint (SPCanvas *canvas) @@ -2011,47 +1972,41 @@ paint (SPCanvas *canvas) if (!canvas->need_redraw) return TRUE; - GtkWidget const *widget = GTK_WIDGET(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; - - 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); - } + Gdk::Region to_paint; + 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) + to_paint.union_with_rect(Gdk::Rectangle(i*TILE_SIZE, j*TILE_SIZE, + TILE_SIZE, TILE_SIZE)); } + + } + } + + if (!to_paint.empty()) { + Glib::ArrayHandle rect = to_paint.get_rectangles(); + typedef Glib::ArrayHandle::const_iterator Iter; + for (Iter i=rect.begin(); i != rect.end(); ++i) { + int x0 = (*i).get_x(); + int y0 = (*i).get_y(); + int x1 = x0 + (*i).get_width(); + int y1 = y0 + (*i).get_height(); + if (!sp_canvas_paint_rect(canvas, x0, y0, x1, y1)) { + // Aborted + return FALSE; + }; } } canvas->need_redraw = FALSE; - sp_canvas_paint_rect (canvas, topaint.x0, topaint.y0, topaint.x1, topaint.y1); + + // we've had a full unaborted redraw, reset the full redraw counter + if (canvas->forced_redraw_limit != -1) { + canvas->forced_redraw_count = 0; + } return TRUE; } @@ -2062,7 +2017,7 @@ paint (SPCanvas *canvas) static int do_update (SPCanvas *canvas) { - if (!canvas->root) // canvas may have already be destroyed by closing desktop durring interrupted display! + if (!canvas->root || !canvas->pixmap_gc) // canvas may have already be destroyed by closing desktop durring interrupted display! return TRUE; /* Cause the update if necessary */ @@ -2095,7 +2050,7 @@ idle_handler (gpointer data) SPCanvas *canvas = SP_CANVAS (data); - const int ret = do_update (canvas); + int const ret = do_update (canvas); if (ret) { /* Reset idle id */ @@ -2132,45 +2087,32 @@ sp_canvas_root (SPCanvas *canvas) } /** - * Scrolls canvas to specific position. + * Scrolls canvas to specific position (cx and cy are measured in screen pixels) */ 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)); - int ix = (int) (cx + 0.5); - int iy = (int) (cy + 0.5); - int dx = ix - canvas->x0; - int dy = iy - canvas->y0; + int ix = (int) round(cx); // ix and iy are the new canvas coordinates (integer screen pixels) + int iy = (int) round(cy); // cx might be negative, so (int)(cx + 0.5) will not do! + int dx = ix - canvas->x0; // dx and dy specify the displacement (scroll) of the + int dy = iy - canvas->y0; // canvas w.r.t its previous position - canvas->dx0 = cx; + canvas->dx0 = cx; // here the 'd' stands for double, not delta! canvas->dy0 = cy; 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 { @@ -2178,7 +2120,7 @@ sp_canvas_scroll_to (SPCanvas *canvas, double cx, double cy, unsigned int clear) } } -/** +/** * Updates canvas if necessary. */ void @@ -2191,7 +2133,6 @@ sp_canvas_update_now (SPCanvas *canvas) canvas->need_redraw)) return; - remove_idle (canvas); do_update (canvas); } @@ -2301,30 +2242,39 @@ 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 { GtkWidget const *w = GTK_WIDGET(this); - return NR::Rect(NR::Point(dx0, dy0), NR::Point(dx0 + w->allocation.width, dy0 + w->allocation.height)); } +/** + * Return canvas window coordinates as IRect (a rectangle defined by integers). + */ +NR::IRect SPCanvas::getViewboxIntegers() const +{ + GtkWidget const *w = GTK_WIDGET(this); + return NR::IRect(NR::IPoint(x0, y0), + NR::IPoint(x0 + w->allocation.width, y0 + w->allocation.height)); +} + 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) +static void sp_canvas_resize_tiles(SPCanvas* canvas, int nl, int nt, int nr, int nb) { if ( nl >= nr || nt >= nb ) { if ( canvas->tiles ) g_free(canvas->tiles); @@ -2338,15 +2288,15 @@ 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*)g_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 } } } @@ -2360,10 +2310,19 @@ void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb) canvas->tileV=nv; } +/* + * Helper that queues a canvas rectangle for redraw + */ +static void sp_canvas_dirty_rect(SPCanvas* canvas, int nl, int nt, int nr, int nb) { + canvas->need_redraw = TRUE; + + sp_canvas_mark_rect(canvas, nl, nt, nr, nb, 1); +} + /** - * Helper that marks specific canvas rectangle for redraw. + * Helper that marks specific canvas rectangle as clean (val == 0) or dirty (otherwise) */ -void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb) +void sp_canvas_mark_rect(SPCanvas* canvas, int nl, int nt, int nr, int nb, uint8_t val) { if ( nl >= nr || nt >= nb ) { return; @@ -2378,11 +2337,9 @@ void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb) if ( tt < canvas->tTop ) tt=canvas->tTop; if ( tb > canvas->tBottom ) tb=canvas->tBottom; - 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] = val; } } } @@ -2397,4 +2354,4 @@ void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb) fill-column:99 End: */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :