From 4dd33aa4d5c57706c7f64f63391174954160a308 Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Sat, 6 Aug 2011 14:18:32 +0200 Subject: Rewrite NRArenaItem hierarchy into C++ (bzr r10347.1.21) --- src/display/drawing-item.cpp | 620 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 620 insertions(+) create mode 100644 src/display/drawing-item.cpp (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp new file mode 100644 index 000000000..318ff28e7 --- /dev/null +++ b/src/display/drawing-item.cpp @@ -0,0 +1,620 @@ +/** + * @file + * @brief Canvas item belonging to an SVG drawing element + *//* + * Authors: + * Krzysztof KosiƄski + * + * Copyright (C) 2011 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "display/cairo-utils.h" +#include "display/cairo-templates.h" +#include "display/drawing-context.h" +#include "display/drawing-item.h" +#include "display/drawing-group.h" +#include "display/drawing-surface.h" +#include "nr-arena.h" +#include "nr-filter.h" +#include "preferences.h" +#include "style.h" + +namespace Inkscape { + +DrawingItem::DrawingItem(Drawing *drawing) + : _drawing(drawing) + , _parent(NULL) + , _key(0) + , _opacity(1.0) + , _transform(NULL) + , _clip(NULL) + , _mask(NULL) + , _filter(NULL) + , _user_data(NULL) + , _cache(NULL) + , _state(0) + , _visible(true) + , _sensitive(true) + , _cached(0) + , _propagate(0) +// , _renders_opacity(0) + , _clip_child(0) + , _mask_child(0) + , _pick_children(0) +{ + nr_object_ref(_drawing); +} + +DrawingItem::~DrawingItem() +{ + _drawing->item_deleted.emit(this); + //if (!_children.empty()) { + // g_warning("Removing item with children"); + //} + + // remove from the set of cached items + if (_cached) { + _drawing->cached_items.erase(this); + } + // remove this item from parent's children list + // due to the effect of clearChildren(), this only happens for the top-level deleted item + if (_parent) { + _markForRendering(); + // we cannot call setClip(NULL) or setMask(NULL), + // because that would be an endless loop + if (_clip_child) { + _parent->_clip = NULL; + } else if (_mask_child) { + _parent->_mask = NULL; + } else { + ChildrenList::iterator ithis = _parent->_children.iterator_to(*this); + _parent->_children.erase(ithis); + } + _parent->_markForUpdate(STATE_ALL, false); + } + clearChildren(); + delete _transform; + delete _clip; + delete _mask; + delete _filter; + nr_object_unref(_drawing); +} + +DrawingItem * +DrawingItem::parent() const +{ + //if (_clip_child || _mask_child) + // return NULL; + + return _parent; +} + +void +DrawingItem::appendChild(DrawingItem *item) +{ + item->_parent = this; + _children.push_back(*item); + _markForUpdate(STATE_ALL, false); +} + +void +DrawingItem::prependChild(DrawingItem *item) +{ + item->_parent = this; + _children.push_front(*item); + _markForUpdate(STATE_ALL, false); +} + +void +DrawingItem::clearChildren() +{ + // prevent children from referencing the parent during deletion + // this way, children won't try to remove themselves from a list + // from which they have already been removed by clear_and_dispose + for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) { + i->_parent = NULL; + } + _children.clear_and_dispose(DeleteDisposer()); +} + +void +DrawingItem::setTransform(Geom::Affine const &new_trans) +{ + Geom::Affine current; + if (_transform) { + current = *_transform; + } + + if (!Geom::are_near(current, new_trans, NR_EPSILON)) { + // mark the area where the object was for redraw. + _markForRendering(); + if (new_trans.isIdentity()) { + delete _transform; // delete NULL; is safe + _transform = NULL; + } else { + _transform = new Geom::Affine(new_trans); + } + _markForUpdate(STATE_ALL, true); + } +} + +void +DrawingItem::setOpacity(float opacity) +{ + _opacity = opacity; + _markForRendering(); +} + +void +DrawingItem::setVisible(bool v) +{ + _visible = v; + _markForRendering(); +} + +void +DrawingItem::setSensitive(bool s) +{ + _sensitive = s; +} + +void +DrawingItem::setCached(bool c) +{ + _cached = c; + if (c) { + _drawing->cached_items.insert(this); + } else { + _drawing->cached_items.erase(this); + } + _markForUpdate(STATE_CACHE, false); +} + +void +DrawingItem::setClip(DrawingItem *item) +{ + _markForRendering(); + delete _clip; + _clip = item; + if (item) { + item->_parent = this; + item->_clip_child = true; + } + _markForUpdate(STATE_ALL, true); +} + +void +DrawingItem::setMask(DrawingItem *item) +{ + _markForRendering(); + delete _mask; + _mask = item; + if (item) { + item->_parent = this; + item->_mask_child = true; + } + _markForUpdate(STATE_ALL, true); +} + +void +DrawingItem::setZOrder(unsigned z) +{ + if (!_parent) return; + + ChildrenList::iterator it = _parent->_children.iterator_to(*this); + _parent->_children.erase(it); + + ChildrenList::iterator i = _parent->_children.begin(); + std::advance(i, std::min(z, unsigned(_parent->_children.size()))); + _parent->_children.insert(i, *this); + _markForRendering(); +} + +void +DrawingItem::setItemBounds(Geom::OptRect const &bounds) +{ + _item_bbox = bounds; +} + +void +DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset) +{ + bool render_filters = (_drawing->rendermode == Inkscape::RENDERMODE_NORMAL); + bool outline = (_drawing->rendermode == Inkscape::RENDERMODE_OUTLINE); + + // Set reset flags according to propagation status + if (_propagate) { + reset |= ~_state; + _propagate = FALSE; + } + _state &= ~reset; // reset state of this item + + if ((~_state & flags) == 0) return; // nothing to do + + // TODO this might be wrong + if (_state & STATE_BBOX) { + // we have up-to-date bbox + if (!area.intersects(outline ? _bbox : _drawbox)) return; + } + + UpdateContext child_ctx(ctx); + if (_transform) { + child_ctx.ctm = *_transform * ctx.ctm; + } + /* Remember the transformation matrix */ + Geom::Affine ctm_change = _ctm.inverse() * child_ctx.ctm; + _ctm = child_ctx.ctm; + + // update _bbox + _state = _updateItem(area, child_ctx, flags, reset); + + // compute drawbox + if (_filter && render_filters && _item_bbox) { + _drawbox = _filter->compute_drawbox(this, *_item_bbox); + } else { + _drawbox = _bbox; + } + + // Clipping + if (_clip) { + _clip->update(area, child_ctx, flags, reset); + if (outline) { + _bbox.unionWith(_clip->_bbox); + } else { + _drawbox.intersectWith(_clip->_bbox); + } + } + // masking + if (_mask) { + _mask->update(area, child_ctx, flags, reset); + if (outline) { + _bbox.unionWith(_mask->_bbox); + } else { + // for masking, we need full drawbox of mask + _drawbox.intersectWith(_mask->_drawbox); + } + } + + // update cache if enabled + if (_cached) { + Geom::OptIntRect cl = _drawing->cache_limit; + cl.intersectWith(_drawbox); + if (cl) { + if (_cache) { + // this takes care of invalidation on transform + _cache->resizeAndTransform(*cl, ctm_change); + } else { + _cache = new Inkscape::DrawingCache(*cl); + // the cache is initially dirty + } + } else { + // disable cache for this item - not visible + delete _cache; + _cache = NULL; + } + } + + // now that we know drawbox, dirty the corresponding rect on canvas + // unless filtered, groups do not need to render by themselves, only their members + if (!is_drawing_group(this) || (_filter && render_filters)) { + if (flags & ~STATE_CACHE) { + _markForRendering(); + } + } +} + +struct MaskLuminanceToAlpha { + guint32 operator()(guint32 in) { + EXTRACT_ARGB32(in, a, r, g, b) + // the operation of unpremul -> luminance-to-alpha -> multiply by alpha + // is equivalent to luminance-to-alpha on premultiplied color values + // original computation in double: r*0.2125 + g*0.7154 + b*0.0721 + guint32 ao = r*109 + g*366 + b*37; // coeffs add up to 512 + return ((ao + 256) << 15) & 0xff000000; // equivalent to ((ao + 256) / 512) << 24 + } +}; + +void +DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flags) +{ + bool outline = (_drawing->rendermode == Inkscape::RENDERMODE_OUTLINE); + bool render_filters = (_drawing->rendermode == Inkscape::RENDERMODE_NORMAL); + + /* If we are invisible, just return successfully */ + if (!_visible) return; + + if (outline) { + _renderOutline(ct, area, flags); + return; + } + + // carea is the bounding box for intermediate rendering. + Geom::OptIntRect carea = Geom::intersect(area, _drawbox); + if (!carea) return; + + // render from cache + if (_cached && _cache) { + if (_cache->paintFromCache(ct, *carea)) + return; + } + + // expand carea to contain the dependent area of filters. + if (_filter && render_filters) { + _filter->area_enlarge(*carea, this); + carea.intersectWith(_drawbox); + } + + // determine whether this shape needs intermediate rendering. + bool needs_intermediate_rendering = false; + bool &nir = needs_intermediate_rendering; + bool needs_opacity = (_opacity < 0.995); + + // this item needs an intermediate rendering if: + nir |= (_clip != NULL); // 1. it has a clipping path + nir |= (_mask != NULL); // 2. it has a mask + nir |= (_filter != NULL && render_filters); // 3. it has a filter + nir |= needs_opacity; // 4. it is non-opaque + + /* How the rendering is done. + * + * Clipping, masking and opacity are done by rendering them to a surface + * and then compositing the object's rendering onto it with the IN operator. + * The object itself is rendered to a group. + * + * Opacity is done by rendering the clipping path with an alpha + * value corresponding to the opacity. If there is no clipping path, + * the entire intermediate surface is painted with alpha corresponding + * to the opacity value. + */ + + // short-circuit the simple case. + if (!needs_intermediate_rendering) { + if (_cached && _cache) { + Inkscape::DrawingContext cachect(*_cache); + cachect.rectangle(area); + cachect.clip(); + + { // 1. clear the corresponding part of cache + Inkscape::DrawingContext::Save save(cachect); + cachect.setSource(0,0,0,0); + cachect.setOperator(CAIRO_OPERATOR_SOURCE); + cachect.paint(); + } + // 2. render to cache + _renderItem(cachect, *carea, flags); + // 3. copy from cache to output + Inkscape::DrawingContext::Save save(ct); + ct.rectangle(*carea); + ct.clip(); + ct.setSource(_cache); + ct.paint(); + // 4. mark as clean + _cache->markClean(area); + return; + } else { + _renderItem(ct, *carea, flags); + return; + } + } + + DrawingSurface intermediate(*carea); + DrawingContext ict(intermediate); + + // 1. Render clipping path with alpha = opacity. + ict.setSource(0,0,0,_opacity); + // Since clip can be combined with opacity, the result could be incorrect + // for overlapping clip children. To fix this we use the SOURCE operator + // instead of the default OVER. + ict.setOperator(CAIRO_OPERATOR_SOURCE); + if (_clip) { + _clip->clip(ict, *carea); // fixme: carea or area? + } else { + // if there is no clipping path, fill the entire surface with alpha = opacity. + ict.paint(); + } + ict.setOperator(CAIRO_OPERATOR_OVER); // reset back to default + + // 2. Render the mask if present and compose it with the clipping path + opacity. + if (_mask) { + ict.pushGroup(); + _mask->render(ict, *carea, flags); + + cairo_surface_t *mask_s = ict.rawTarget(); + // Convert mask's luminance to alpha + ink_cairo_surface_filter(mask_s, mask_s, MaskLuminanceToAlpha()); + ict.popGroupToSource(); + ict.setOperator(CAIRO_OPERATOR_IN); + ict.paint(); + ict.setOperator(CAIRO_OPERATOR_OVER); + } + + // 3. Render object itself. + ict.pushGroup(); + _renderItem(ict, *carea, flags); + + // 4. Apply filter. + if (_filter && render_filters) { + _filter->render(this, ct, ict); + // Note that because the object was rendered to a group, + // the internals of the filter need to use cairo_get_group_target() + // instead of cairo_get_target(). + } + + // 5. Render object inside the composited mask + clip + ict.popGroupToSource(); + ict.setOperator(CAIRO_OPERATOR_IN); + ict.paint(); + + // 6. Paint the completed rendering onto the base context (or into cache) + if (_cached && _cache) { + DrawingContext cachect(*_cache); + cachect.rectangle(area); + cachect.clip(); + cachect.setOperator(CAIRO_OPERATOR_SOURCE); + cachect.setSource(&intermediate); + cachect.paint(); + _cache->markClean(area); + } + ct.setSource(&intermediate); + ct.paint(); + ct.setSource(0,0,0,0); + // the call above is to clear a ref on the intermediate surface held by ct +} + +void +DrawingItem::_renderOutline(DrawingContext &ct, Geom::IntRect const &area, unsigned flags) +{ + // intersect with bbox rather than drawbox, as we want to render things outside + // of the clipping path as well + Geom::OptIntRect carea = Geom::intersect(area, _bbox); + if (!carea) return; + + // just render everything: item, clip, mask + // First, render the object itself + _renderItem(ct, *carea, flags); + + // render clip and mask, if any + guint32 saved_rgba = _drawing->outlinecolor; // save current outline color + // render clippath as an object, using a different color + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (_clip) { + _drawing->outlinecolor = prefs->getInt("/options/wireframecolors/clips", 0x00ff00ff); // green clips + _clip->render(ct, *carea, flags); + } + // render mask as an object, using a different color + if (_mask) { + _drawing->outlinecolor = prefs->getInt("/options/wireframecolors/masks", 0x0000ffff); // blue masks + _mask->render(ct, *carea, flags); + } + _drawing->outlinecolor = saved_rgba; // restore outline color +} + +void +DrawingItem::clip(Inkscape::DrawingContext &ct, Geom::IntRect const &area) +{ + // don't bother if the object does not implement clipping (e.g. DrawingImage) + if (!_canClip()) return; + if (!_visible) return; + if (!area.intersects(_bbox)) return; + + // The item used as the clipping path itself has a clipping path. + // Render this item's clipping path onto a temporary surface, then composite it + // with the item using the IN operator + if (_clip) { + ct.pushAlphaGroup(); + { Inkscape::DrawingContext::Save save(ct); + ct.setSource(0,0,0,1); + _clip->clip(ct, area); + } + ct.pushAlphaGroup(); + } + + // rasterize the clipping path + _clipItem(ct, area); + + if (_clip) { + ct.popGroupToSource(); + ct.setOperator(CAIRO_OPERATOR_IN); + ct.paint(); + ct.popGroupToSource(); + ct.setOperator(CAIRO_OPERATOR_SOURCE); + ct.paint(); + } +} + +DrawingItem * +DrawingItem::pick(Geom::Point const &p, double delta, bool sticky) +{ + // Sometimes there's no BBOX in state, reason unknown (bug 992817) + // I made this not an assert to remove the warning + if (!(_state & STATE_BBOX) || !(_state & STATE_PICK)) + return NULL; + + if (!sticky && !(_visible && _sensitive)) + return NULL; + + if (!_bbox) return NULL; + Geom::Rect expanded(*_bbox); + expanded.expandBy(delta); + + if (expanded.contains(p)) { + return _pickItem(p, delta); + } + return NULL; +} + +void +DrawingItem::_markForRendering() +{ + bool outline = (_drawing->rendermode == Inkscape::RENDERMODE_OUTLINE); + Geom::OptIntRect dirty = outline ? _bbox : _drawbox; + if (!dirty) return; + + // dirty the caches of all parents + for (DrawingItem *i = this; i; i = i->_parent) { + if (i->_cached && i->_cache) { + i->_cache->markDirty(*dirty); + } + } + + nr_arena_request_render_rect (_drawing, dirty); +} + +void +DrawingItem::_markForUpdate(unsigned flags, bool propagate) +{ + // here we can't simply assign because a previous markForUpdate call + // could have had propagate=true even if this one has propagate=false + if (propagate) + _propagate = true; + + if (_state & flags) { + _state &= ~flags; + if (_parent) { + _parent->_markForUpdate(flags, false); + } else { + nr_arena_request_update (_drawing, this); + } + } +} + +void +DrawingItem::_setStyleCommon(SPStyle *&_style, SPStyle *style) +{ + if (style) sp_style_ref(style); + if (_style) sp_style_unref(_style); + _style = style; + + // if group has a filter + if (style->filter.set && style->getFilter()) { + if (!_filter) { + int primitives = sp_filter_primitive_count(SP_FILTER(style->getFilter())); + _filter = new Inkscape::Filters::Filter(primitives); + } + sp_filter_build_renderer(SP_FILTER(style->getFilter()), _filter); + } else { + // no filter set for this group + delete _filter; + _filter = NULL; + } + + /* + if (style && style->enable_background.set + && style->enable_background.value == SP_CSS_BACKGROUND_NEW) { + _background_new = true; + }*/ +} + +} // end namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : -- cgit v1.2.3 From 42c8636a2c5814746c41f1452ffa7df99cf21367 Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Sat, 6 Aug 2011 15:38:28 +0200 Subject: Document things figured out during the rewriting (bzr r10347.1.22) --- src/display/drawing-item.cpp | 99 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 4 deletions(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index 318ff28e7..caed08e6f 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -22,6 +22,26 @@ namespace Inkscape { +/** @class DrawingItem + * @brief SVG drawing item for display. + * + * This was previously known as NRArenaItem. It represents the renderable + * portion of the SVG document. Typically this is created by the SP tree, + * in particular the show() virtual function. + * + * @section ObjectLifetime Object Lifetime + * Deleting a DrawingItem will cause all of its children to be deleted as well. + * This can lead to nasty surprises if you hold references to things + * which are children of what is being deleted. Therefore, in the SP tree, + * you always need to delete the item views of children before deleting + * the view of the parent. Do not call delete on things returned from show() + * - this will cause dangling pointers inside the SPItem and lead to a crash. + * Use the corresponing hide() method. + * + * Outside of the SP tree you should not use any references after the root node + * has been deleted. + */ + DrawingItem::DrawingItem(Drawing *drawing) : _drawing(drawing) , _parent(NULL) @@ -84,9 +104,8 @@ DrawingItem::~DrawingItem() DrawingItem * DrawingItem::parent() const { - //if (_clip_child || _mask_child) - // return NULL; - + // initially I wanted to return NULL if we are a clip or mask child, + // but the previous behavior was just to return the parent return _parent; } @@ -106,6 +125,7 @@ DrawingItem::prependChild(DrawingItem *item) _markForUpdate(STATE_ALL, false); } +/// Delete all regular children of this item (not mask or clip). void DrawingItem::clearChildren() { @@ -118,6 +138,7 @@ DrawingItem::clearChildren() _children.clear_and_dispose(DeleteDisposer()); } +/// Set the incremental transform for this item void DrawingItem::setTransform(Geom::Affine const &new_trans) { @@ -153,12 +174,14 @@ DrawingItem::setVisible(bool v) _markForRendering(); } +/// This is currently unused void DrawingItem::setSensitive(bool s) { _sensitive = s; } +/// Enable / disable storing the rendering in memory. void DrawingItem::setCached(bool c) { @@ -197,6 +220,8 @@ DrawingItem::setMask(DrawingItem *item) _markForUpdate(STATE_ALL, true); } +/// Move this item to the given place in the Z order of siblings. +/// Does nothing if the item has no parent. void DrawingItem::setZOrder(unsigned z) { @@ -217,6 +242,27 @@ DrawingItem::setItemBounds(Geom::OptRect const &bounds) _item_bbox = bounds; } +/** @brief Update derived data before operations. + * The purpose of this call is to recompute internal data which depends + * on the attributes of the object, but is not directly settable by the user. + * Precomputing this data speeds up later rendering, because some items + * can be omitted. + * + * Currently this method handles updating the visual and geometric bounding boxes + * in pixels, storing the total transformation from item space to the screen + * and cache invalidation. + * + * @param area Area to which the update should be restricted. Only takes effect + * if the bounding box is known. + * @param ctx A structure to store cascading state. + * @param flags Which internal data should be recomputed. This can be any combination + * of StateFlags. + * @param reset State fields that should be reset before processing them. This is + * a means to force a recomputation of internal data even if the item + * considers it up to date. Mainly for internal use, such as + * propagating bunding box recomputation to children when the item's + * transform changes. + */ void DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset) { @@ -315,6 +361,15 @@ struct MaskLuminanceToAlpha { } }; +/** @brief Rasterize items. + * This method submits the drawing opeartions required to draw this item + * to the supplied DrawingContext, restricting drawing the the specified area. + * + * This method does some common tasks and calls the item-specific rendering + * function, _renderItem(), to render e.g. paths or bitmaps. + * + * @param flags Rendering options. This deals mainly with cache control. + */ void DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flags) { @@ -490,6 +545,13 @@ DrawingItem::_renderOutline(DrawingContext &ct, Geom::IntRect const &area, unsig _drawing->outlinecolor = saved_rgba; // restore outline color } +/** @brief Rasterize the clipping path. + * This method submits drawing operations required to draw a basic filled shape + * of the item to the supplied drawing context. Rendering is limited to the + * given area. The rendering of the clipped object is composited into + * the result of this call using the IN operator. See the implementation + * of render() for details. + */ void DrawingItem::clip(Inkscape::DrawingContext &ct, Geom::IntRect const &area) { @@ -523,6 +585,16 @@ DrawingItem::clip(Inkscape::DrawingContext &ct, Geom::IntRect const &area) } } +/** @brief Get the item under the specified point. + * Searches the tree for the first item in the Z-order which is closer than + * @a delta to the given point. The pick should be visual - for example + * an object with a thick stroke should pick on the entire area of the stroke. + * @param p Search point + * @param delta Maximum allowed distance from the point + * @param sticky Whether the pick should ignore visibility and sensitivity. + * When false, only visible and sensitive objects are considered. + * When true, invisible and insensitive objects can also be picked. + */ DrawingItem * DrawingItem::pick(Geom::Point const &p, double delta, bool sticky) { @@ -544,6 +616,11 @@ DrawingItem::pick(Geom::Point const &p, double delta, bool sticky) return NULL; } +/** Marks the current visual bounding box of the item for redrawing. + * This is called whenever the object changes its visible appearance. + * For some cases (such as setting opacity) this is enough, but for others + * _markForUpdate() also needs to be called. + */ void DrawingItem::_markForRendering() { @@ -561,10 +638,24 @@ DrawingItem::_markForRendering() nr_arena_request_render_rect (_drawing, dirty); } +/** @brief Marks the item as needing a recomputation of internal data. + * + * This mechanism avoids traversing the entire rendering tree (which could be vast) + * on every trivial state changed in any item. Only items marked as needing + * an update (having some bits in their _state unset) will be traversed + * during the update call. + * + * The _propagate variable is another optimization. We use it to specify that + * all children should also have the corresponding flags unset before checking + * whether they need to be traversed. This way there is one less traversal + * of the tree. Without this we would need to unset state bits in all children. + * With _propagate we do this during the update call, when we have to traverse + * the tree anyway. + */ void DrawingItem::_markForUpdate(unsigned flags, bool propagate) { - // here we can't simply assign because a previous markForUpdate call + // we can't simply assign because a previous markForUpdate call // could have had propagate=true even if this one has propagate=false if (propagate) _propagate = true; -- cgit v1.2.3 From 456dddb2670686427c60497702e86648635ce42e Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Sat, 6 Aug 2011 15:45:04 +0200 Subject: Fix compilation (oops). (bzr r10347.1.24) --- src/display/drawing-item.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index caed08e6f..53639f765 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -611,7 +611,7 @@ DrawingItem::pick(Geom::Point const &p, double delta, bool sticky) expanded.expandBy(delta); if (expanded.contains(p)) { - return _pickItem(p, delta); + return _pickItem(p, delta, sticky); } return NULL; } -- cgit v1.2.3 From 75976ea07dba9b97186667524d0a76603de416af Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Sun, 7 Aug 2011 12:53:12 +0200 Subject: Rewrite NRArena -> Inkscape::Drawing. Call render and update methods on the Drawing rather than on the root DrawingItem. (bzr r10347.1.25) --- src/display/drawing-item.cpp | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index 53639f765..47f6c55a1 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -11,11 +11,11 @@ #include "display/cairo-utils.h" #include "display/cairo-templates.h" +#include "display/drawing.h" #include "display/drawing-context.h" #include "display/drawing-item.h" #include "display/drawing-group.h" #include "display/drawing-surface.h" -#include "nr-arena.h" #include "nr-filter.h" #include "preferences.h" #include "style.h" @@ -42,7 +42,7 @@ namespace Inkscape { * has been deleted. */ -DrawingItem::DrawingItem(Drawing *drawing) +DrawingItem::DrawingItem(Drawing &drawing) : _drawing(drawing) , _parent(NULL) , _key(0) @@ -61,21 +61,21 @@ DrawingItem::DrawingItem(Drawing *drawing) // , _renders_opacity(0) , _clip_child(0) , _mask_child(0) + , _drawing_root(0) , _pick_children(0) { - nr_object_ref(_drawing); } DrawingItem::~DrawingItem() { - _drawing->item_deleted.emit(this); + _drawing.signal_item_deleted.emit(this); //if (!_children.empty()) { // g_warning("Removing item with children"); //} // remove from the set of cached items if (_cached) { - _drawing->cached_items.erase(this); + _drawing._cached_items.erase(this); } // remove this item from parent's children list // due to the effect of clearChildren(), this only happens for the top-level deleted item @@ -92,13 +92,14 @@ DrawingItem::~DrawingItem() _parent->_children.erase(ithis); } _parent->_markForUpdate(STATE_ALL, false); + } else if (_drawing_root) { + _drawing._root = NULL; } clearChildren(); delete _transform; delete _clip; delete _mask; delete _filter; - nr_object_unref(_drawing); } DrawingItem * @@ -187,9 +188,9 @@ DrawingItem::setCached(bool c) { _cached = c; if (c) { - _drawing->cached_items.insert(this); + _drawing._cached_items.insert(this); } else { - _drawing->cached_items.erase(this); + _drawing._cached_items.erase(this); } _markForUpdate(STATE_CACHE, false); } @@ -266,8 +267,8 @@ DrawingItem::setItemBounds(Geom::OptRect const &bounds) void DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset) { - bool render_filters = (_drawing->rendermode == Inkscape::RENDERMODE_NORMAL); - bool outline = (_drawing->rendermode == Inkscape::RENDERMODE_OUTLINE); + bool render_filters = _drawing.renderFilters(); + bool outline = _drawing.outline(); // Set reset flags according to propagation status if (_propagate) { @@ -324,7 +325,7 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne // update cache if enabled if (_cached) { - Geom::OptIntRect cl = _drawing->cache_limit; + Geom::OptIntRect cl = _drawing.cacheLimit(); cl.intersectWith(_drawbox); if (cl) { if (_cache) { @@ -373,8 +374,8 @@ struct MaskLuminanceToAlpha { void DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flags) { - bool outline = (_drawing->rendermode == Inkscape::RENDERMODE_OUTLINE); - bool render_filters = (_drawing->rendermode == Inkscape::RENDERMODE_NORMAL); + bool outline = _drawing.outline(); + bool render_filters = _drawing.renderFilters(); /* If we are invisible, just return successfully */ if (!_visible) return; @@ -530,19 +531,19 @@ DrawingItem::_renderOutline(DrawingContext &ct, Geom::IntRect const &area, unsig _renderItem(ct, *carea, flags); // render clip and mask, if any - guint32 saved_rgba = _drawing->outlinecolor; // save current outline color + guint32 saved_rgba = _drawing.outlinecolor; // save current outline color // render clippath as an object, using a different color Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (_clip) { - _drawing->outlinecolor = prefs->getInt("/options/wireframecolors/clips", 0x00ff00ff); // green clips + _drawing.outlinecolor = prefs->getInt("/options/wireframecolors/clips", 0x00ff00ff); // green clips _clip->render(ct, *carea, flags); } // render mask as an object, using a different color if (_mask) { - _drawing->outlinecolor = prefs->getInt("/options/wireframecolors/masks", 0x0000ffff); // blue masks + _drawing.outlinecolor = prefs->getInt("/options/wireframecolors/masks", 0x0000ffff); // blue masks _mask->render(ct, *carea, flags); } - _drawing->outlinecolor = saved_rgba; // restore outline color + _drawing.outlinecolor = saved_rgba; // restore outline color } /** @brief Rasterize the clipping path. @@ -624,7 +625,7 @@ DrawingItem::pick(Geom::Point const &p, double delta, bool sticky) void DrawingItem::_markForRendering() { - bool outline = (_drawing->rendermode == Inkscape::RENDERMODE_OUTLINE); + bool outline = _drawing.outline(); Geom::OptIntRect dirty = outline ? _bbox : _drawbox; if (!dirty) return; @@ -634,8 +635,7 @@ DrawingItem::_markForRendering() i->_cache->markDirty(*dirty); } } - - nr_arena_request_render_rect (_drawing, dirty); + _drawing.signal_request_render.emit(*dirty); } /** @brief Marks the item as needing a recomputation of internal data. @@ -665,7 +665,7 @@ DrawingItem::_markForUpdate(unsigned flags, bool propagate) if (_parent) { _parent->_markForUpdate(flags, false); } else { - nr_arena_request_update (_drawing, this); + _drawing.signal_request_update.emit(this); } } } -- cgit v1.2.3 From f336c94939e9740501835b2584ad9a3160ac6d51 Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Tue, 9 Aug 2011 03:14:07 +0200 Subject: Initial autocache work (bzr r10347.1.26) --- src/display/drawing-item.cpp | 146 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 120 insertions(+), 26 deletions(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index 47f6c55a1..3f409b8ee 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -9,6 +9,7 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ +#include #include "display/cairo-utils.h" #include "display/cairo-templates.h" #include "display/drawing.h" @@ -29,7 +30,7 @@ namespace Inkscape { * portion of the SVG document. Typically this is created by the SP tree, * in particular the show() virtual function. * - * @section ObjectLifetime Object Lifetime + * @section ObjectLifetime Object lifetime * Deleting a DrawingItem will cause all of its children to be deleted as well. * This can lead to nasty surprises if you hold references to things * which are children of what is being deleted. Therefore, in the SP tree, @@ -38,7 +39,7 @@ namespace Inkscape { * - this will cause dangling pointers inside the SPItem and lead to a crash. * Use the corresponing hide() method. * - * Outside of the SP tree you should not use any references after the root node + * Outside of the SP tree, you should not use any references after the root node * has been deleted. */ @@ -57,6 +58,8 @@ DrawingItem::DrawingItem(Drawing &drawing) , _visible(true) , _sensitive(true) , _cached(0) + , _cached_persistent(0) + , _has_cache_iterator(0) , _propagate(0) // , _renders_opacity(0) , _clip_child(0) @@ -77,6 +80,9 @@ DrawingItem::~DrawingItem() if (_cached) { _drawing._cached_items.erase(this); } + if (_has_cache_iterator) { + _drawing._candidate_items.erase(_cache_iterator); + } // remove this item from parent's children list // due to the effect of clearChildren(), this only happens for the top-level deleted item if (_parent) { @@ -182,17 +188,27 @@ DrawingItem::setSensitive(bool s) _sensitive = s; } -/// Enable / disable storing the rendering in memory. +/** @brief Enable / disable storing the rendering in memory. + * Calling setCached(false, true) will also remove the persistent status + */ void -DrawingItem::setCached(bool c) +DrawingItem::setCached(bool cached, bool persistent) { - _cached = c; - if (c) { + static const char *cache_env = getenv("_INKSCAPE_DISABLE_CACHE"); + if (cache_env) return; + + if (_cached_persistent && !persistent) + return; + + _cached = cached; + _cached_persistent = persistent ? cached : false; + if (cached) { _drawing._cached_items.insert(this); } else { _drawing._cached_items.erase(this); + delete _cache; + _cache = NULL; } - _markForUpdate(STATE_CACHE, false); } void @@ -277,7 +293,7 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne } _state &= ~reset; // reset state of this item - if ((~_state & flags) == 0) return; // nothing to do + if ((~_state & flags) == 0) return; // nothing to do // TODO this might be wrong if (_state & STATE_BBOX) { @@ -323,20 +339,40 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne } } - // update cache if enabled - if (_cached) { - Geom::OptIntRect cl = _drawing.cacheLimit(); - cl.intersectWith(_drawbox); - if (cl) { - if (_cache) { - // this takes care of invalidation on transform - _cache->resizeAndTransform(*cl, ctm_change); - } else { - _cache = new Inkscape::DrawingCache(*cl); - // the cache is initially dirty - } + // Update cache score for this item + if (_has_cache_iterator) { + // remove old score information + _drawing._candidate_items.erase(_cache_iterator); + _has_cache_iterator = false; + } + double score = _cacheScore(); + if (score >= _drawing._cache_score_threshold) { + CacheRecord cr; + cr.score = score; + // if _cacheRect() is empty, a negative score will be returnedfrom _cacheScore(), + // so this will not execute (cache score threshold must be positive) + cr.cache_size = _cacheRect()->area() * 4; + cr.item = this; + _drawing._candidate_items.push_back(cr); + _cache_iterator = --_drawing._candidate_items.end(); + _has_cache_iterator = true; + } + + /* Update cache if enabled. + * General note: here we only tell the cache how it has to transform + * during the render phase. The transformation is deferred because + * after the update the item can have its caching turned off, + * e.g. because its filter was removed. This way we avoid tempoerarily + * using more memory than the cache budget */ + if (_cache) { + Geom::OptIntRect cl = _cacheRect(); + if (_visible && cl) { // never create cache for invisible items + // this takes care of invalidation on transform + _cache->scheduleTransform(*cl, ctm_change); } else { - // disable cache for this item - not visible + // Destroy cache for this item - outside of canvas or invisible. + // The opposite transition (invisible -> visible or object + // entering the canvas) is handled during the render phase delete _cache; _cache = NULL; } @@ -377,9 +413,10 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag bool outline = _drawing.outline(); bool render_filters = _drawing.renderFilters(); - /* If we are invisible, just return successfully */ + // If we are invisible, return immediately if (!_visible) return; + // TODO convert outline rendering to a separate virtual function if (outline) { _renderOutline(ct, area, flags); return; @@ -389,10 +426,25 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag Geom::OptIntRect carea = Geom::intersect(area, _drawbox); if (!carea) return; - // render from cache - if (_cached && _cache) { - if (_cache->paintFromCache(ct, *carea)) - return; + // render from cache if possible + if (_cached) { + if (_cache) { + _cache->prepare(); + if (_cache->paintFromCache(ct, *carea)) + return; + } else { + // There is no cache. This could be because caching of this item + // was just turned on after the last update phase, or because + // we are outside of the canvas. + Geom::OptIntRect cl = _drawing.cacheLimit(); + cl.intersectWith(_drawbox); + if (cl) { + _cache = new DrawingCache(*cl); + } + } + } else { + // if our caching was turned off after the last update, it was already + // deleted in setCached() } // expand carea to contain the dependent area of filters. @@ -695,6 +747,48 @@ DrawingItem::_setStyleCommon(SPStyle *&_style, SPStyle *style) && style->enable_background.value == SP_CSS_BACKGROUND_NEW) { _background_new = true; }*/ + + // TODO: STATE_ALL unsets too much + _markForUpdate(STATE_ALL, false); +} + +double +DrawingItem::_cacheScore() +{ + Geom::OptIntRect cache_rect = _cacheRect(); + if (!cache_rect) return -1.0; + + // a crude first approximation: + // the basic score is the number of pixels in the drawbox + double score = cache_rect->area(); + // this is multiplied by the filter complexity and its expansion + if (_filter &&_drawing.renderFilters()) { + score *= _filter->complexity(_ctm); + Geom::IntRect ref_area = Geom::IntRect::from_xywh(0, 0, 16, 16); + Geom::IntRect test_area = ref_area; + Geom::IntRect limit_area(0, INT_MIN, 16, INT_MAX); + _filter->area_enlarge(test_area, this); + // area_enlarge never shrinks the rect, so the result of intersection below + // must be non-empty + score *= double((test_area & limit_area)->area()) / ref_area.area(); + } + // if the object is clipped, add 1/2 of its bbox pixels + if (_clip && _clip->_bbox) { + score += _clip->_bbox->area() * 0.5; + } + // if masked, add mask score + if (_mask) { + score += _mask->_cacheScore(); + } + g_message("caching score: %f", score); + return score; +} + +Geom::OptIntRect +DrawingItem::_cacheRect() +{ + Geom::OptIntRect r = _drawbox & _drawing.cacheLimit(); + return r; } } // end namespace Inkscape -- cgit v1.2.3 From 16c067ffe728e848bd555b1607d766ccd4162ac5 Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Tue, 9 Aug 2011 06:02:34 +0200 Subject: Turn off debug message spam (bzr r10347.1.27) --- src/display/drawing-item.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index 3f409b8ee..e86e222e2 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -780,7 +780,7 @@ DrawingItem::_cacheScore() if (_mask) { score += _mask->_cacheScore(); } - g_message("caching score: %f", score); + //g_message("caching score: %f", score); return score; } -- cgit v1.2.3 From 74b91362758052e0f03bb819663a4606f08e4c69 Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Tue, 9 Aug 2011 07:51:45 +0200 Subject: Use cache even if only part of the redraw region is clean (bzr r10347.1.28) --- src/display/drawing-item.cpp | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index e86e222e2..113bf9c33 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -422,7 +422,7 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag return; } - // carea is the bounding box for intermediate rendering. + // carea is the area to paint Geom::OptIntRect carea = Geom::intersect(area, _drawbox); if (!carea) return; @@ -430,8 +430,8 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag if (_cached) { if (_cache) { _cache->prepare(); - if (_cache->paintFromCache(ct, *carea)) - return; + _cache->paintFromCache(ct, carea); + if (!carea) return; } else { // There is no cache. This could be because caching of this item // was just turned on after the last update phase, or because @@ -447,12 +447,6 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag // deleted in setCached() } - // expand carea to contain the dependent area of filters. - if (_filter && render_filters) { - _filter->area_enlarge(*carea, this); - carea.intersectWith(_drawbox); - } - // determine whether this shape needs intermediate rendering. bool needs_intermediate_rendering = false; bool &nir = needs_intermediate_rendering; @@ -480,7 +474,7 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag if (!needs_intermediate_rendering) { if (_cached && _cache) { Inkscape::DrawingContext cachect(*_cache); - cachect.rectangle(area); + cachect.rectangle(*carea); cachect.clip(); { // 1. clear the corresponding part of cache @@ -498,7 +492,7 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag ct.setSource(_cache); ct.paint(); // 4. mark as clean - _cache->markClean(area); + _cache->markClean(*carea); return; } else { _renderItem(ct, *carea, flags); @@ -506,7 +500,19 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag } } - DrawingSurface intermediate(*carea); + // iarea is the bounding box for intermediate rendering + // Note 1: pixels inside iarea but outside carea might be invalid + // (incomplete filter dependence region). + // Note 2: We only need to render carea of clip and mask, but + // iarea of the object. + Geom::OptIntRect iarea = carea; + // expand carea to contain the dependent area of filters. + if (_filter && render_filters) { + _filter->area_enlarge(*iarea, this); + iarea.intersectWith(_drawbox); + } + + DrawingSurface intermediate(*iarea); DrawingContext ict(intermediate); // 1. Render clipping path with alpha = opacity. @@ -537,9 +543,9 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag ict.setOperator(CAIRO_OPERATOR_OVER); } - // 3. Render object itself. + // 3. Render object itself ict.pushGroup(); - _renderItem(ict, *carea, flags); + _renderItem(ict, *iarea, flags); // 4. Apply filter. if (_filter && render_filters) { @@ -557,12 +563,12 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag // 6. Paint the completed rendering onto the base context (or into cache) if (_cached && _cache) { DrawingContext cachect(*_cache); - cachect.rectangle(area); + cachect.rectangle(*carea); cachect.clip(); cachect.setOperator(CAIRO_OPERATOR_SOURCE); cachect.setSource(&intermediate); cachect.paint(); - _cache->markClean(area); + _cache->markClean(*carea); } ct.setSource(&intermediate); ct.paint(); -- cgit v1.2.3 From 52d06f53a4efef7c6880e34cad9de0c770fc13ad Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Tue, 9 Aug 2011 10:01:41 +0200 Subject: Fix invalidation on scrolling (bzr r10347.1.29) --- src/display/drawing-item.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index 113bf9c33..8da59bbe2 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -296,7 +296,7 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne if ((~_state & flags) == 0) return; // nothing to do // TODO this might be wrong - if (_state & STATE_BBOX) { + if (_state & (outline ? STATE_BBOX : STATE_DRAWBOX)) { // we have up-to-date bbox if (!area.intersects(outline ? _bbox : _drawbox)) return; } @@ -310,6 +310,7 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne _ctm = child_ctx.ctm; // update _bbox + unsigned old_state = _state; _state = _updateItem(area, child_ctx, flags, reset); // compute drawbox @@ -349,7 +350,7 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne if (score >= _drawing._cache_score_threshold) { CacheRecord cr; cr.score = score; - // if _cacheRect() is empty, a negative score will be returnedfrom _cacheScore(), + // if _cacheRect() is empty, a negative score will be returned from _cacheScore(), // so this will not execute (cache score threshold must be positive) cr.cache_size = _cacheRect()->area() * 4; cr.item = this; @@ -381,7 +382,8 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne // now that we know drawbox, dirty the corresponding rect on canvas // unless filtered, groups do not need to render by themselves, only their members if (!is_drawing_group(this) || (_filter && render_filters)) { - if (flags & ~STATE_CACHE) { + // mark for rendering if the item becomes renderable + if ((old_state ^ _state) & STATE_RENDER) { _markForRendering(); } } @@ -501,7 +503,7 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag } // iarea is the bounding box for intermediate rendering - // Note 1: pixels inside iarea but outside carea might be invalid + // Note 1: Pixels inside iarea but outside carea are invalid // (incomplete filter dependence region). // Note 2: We only need to render carea of clip and mask, but // iarea of the object. @@ -665,11 +667,14 @@ DrawingItem::pick(Geom::Point const &p, double delta, bool sticky) if (!sticky && !(_visible && _sensitive)) return NULL; - if (!_bbox) return NULL; - Geom::Rect expanded(*_bbox); - expanded.expandBy(delta); + // some part of the shape might be hidden by clipping + // TODO add Geom::OptRect(Geom::OptIntRect const &) constructor + Geom::OptIntRect expanded_i = _bbox & _drawbox; + Geom::OptRect expanded = expanded_i ? Geom::Rect(*expanded_i) : Geom::OptRect(); + if (!expanded) return NULL; + expanded->expandBy(delta); - if (expanded.contains(p)) { + if (expanded->contains(p)) { return _pickItem(p, delta, sticky); } return NULL; -- cgit v1.2.3 From f0d6cbd77ecb7d022539e5019d2a8532f346084c Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Tue, 9 Aug 2011 22:30:14 +0200 Subject: Do not leak cache objects in DrawingItem destructor (bzr r10347.1.30) --- src/display/drawing-item.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index 8da59bbe2..ae3dd49ab 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -76,10 +76,8 @@ DrawingItem::~DrawingItem() // g_warning("Removing item with children"); //} - // remove from the set of cached items - if (_cached) { - _drawing._cached_items.erase(this); - } + // remove from the set of cached items and delete cache + setCached(false, true); if (_has_cache_iterator) { _drawing._candidate_items.erase(_cache_iterator); } -- cgit v1.2.3 From caa510445fc091c63e1ca0ff8f44f2e81ae0638d Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Sat, 13 Aug 2011 20:30:30 +0200 Subject: More generic handling of child type in DrawingItem. Fix clip object selection bug (LP #365458). Fixed bugs: - https://launchpad.net/bugs/365458 (bzr r10347.1.31) --- src/display/drawing-item.cpp | 83 ++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 27 deletions(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index ae3dd49ab..ac0d1be1e 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -55,6 +55,7 @@ DrawingItem::DrawingItem(Drawing &drawing) , _user_data(NULL) , _cache(NULL) , _state(0) + , _child_type(CHILD_ORPHAN) , _visible(true) , _sensitive(true) , _cached(0) @@ -62,9 +63,6 @@ DrawingItem::DrawingItem(Drawing &drawing) , _has_cache_iterator(0) , _propagate(0) // , _renders_opacity(0) - , _clip_child(0) - , _mask_child(0) - , _drawing_root(0) , _pick_children(0) { } @@ -85,19 +83,29 @@ DrawingItem::~DrawingItem() // due to the effect of clearChildren(), this only happens for the top-level deleted item if (_parent) { _markForRendering(); + } + + switch (_child_type) { + case CHILD_NORMAL: { + ChildrenList::iterator ithis = _parent->_children.iterator_to(*this); + _parent->_children.erase(ithis); + } break; + case CHILD_CLIP: // we cannot call setClip(NULL) or setMask(NULL), // because that would be an endless loop - if (_clip_child) { - _parent->_clip = NULL; - } else if (_mask_child) { - _parent->_mask = NULL; - } else { - ChildrenList::iterator ithis = _parent->_children.iterator_to(*this); - _parent->_children.erase(ithis); - } - _parent->_markForUpdate(STATE_ALL, false); - } else if (_drawing_root) { + _parent->_clip = NULL; + break; + case CHILD_MASK: + _parent->_mask = NULL; + break; + case CHILD_ROOT: _drawing._root = NULL; + break; + default: ; + } + + if (_parent) { + _parent->_markForUpdate(STATE_ALL, false); } clearChildren(); delete _transform; @@ -118,6 +126,8 @@ void DrawingItem::appendChild(DrawingItem *item) { item->_parent = this; + assert(item->_child_type == CHILD_ORPHAN); + item->_child_type = CHILD_NORMAL; _children.push_back(*item); _markForUpdate(STATE_ALL, false); } @@ -126,6 +136,8 @@ void DrawingItem::prependChild(DrawingItem *item) { item->_parent = this; + assert(item->_child_type == CHILD_ORPHAN); + item->_child_type = CHILD_NORMAL; _children.push_front(*item); _markForUpdate(STATE_ALL, false); } @@ -139,6 +151,7 @@ DrawingItem::clearChildren() // from which they have already been removed by clear_and_dispose for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) { i->_parent = NULL; + i->_child_type = CHILD_ORPHAN; } _children.clear_and_dispose(DeleteDisposer()); } @@ -217,7 +230,8 @@ DrawingItem::setClip(DrawingItem *item) _clip = item; if (item) { item->_parent = this; - item->_clip_child = true; + assert(item->_child_type == CHILD_ORPHAN); + item->_child_type = CHILD_CLIP; } _markForUpdate(STATE_ALL, true); } @@ -230,7 +244,8 @@ DrawingItem::setMask(DrawingItem *item) _mask = item; if (item) { item->_parent = this; - item->_mask_child = true; + assert(item->_child_type == CHILD_ORPHAN); + item->_child_type = CHILD_MASK; } _markForUpdate(STATE_ALL, true); } @@ -294,7 +309,7 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne if ((~_state & flags) == 0) return; // nothing to do // TODO this might be wrong - if (_state & (outline ? STATE_BBOX : STATE_DRAWBOX)) { + if (_state & STATE_BBOX) { // we have up-to-date bbox if (!area.intersects(outline ? _bbox : _drawbox)) return; } @@ -655,25 +670,39 @@ DrawingItem::clip(Inkscape::DrawingContext &ct, Geom::IntRect const &area) * When true, invisible and insensitive objects can also be picked. */ DrawingItem * -DrawingItem::pick(Geom::Point const &p, double delta, bool sticky) +DrawingItem::pick(Geom::Point const &p, double delta, unsigned flags) { // Sometimes there's no BBOX in state, reason unknown (bug 992817) // I made this not an assert to remove the warning if (!(_state & STATE_BBOX) || !(_state & STATE_PICK)) return NULL; - - if (!sticky && !(_visible && _sensitive)) + // ignore invisible and insensitive items unless sticky + if (!(flags & PICK_STICKY) && !(_visible && _sensitive)) return NULL; - // some part of the shape might be hidden by clipping - // TODO add Geom::OptRect(Geom::OptIntRect const &) constructor - Geom::OptIntRect expanded_i = _bbox & _drawbox; - Geom::OptRect expanded = expanded_i ? Geom::Rect(*expanded_i) : Geom::OptRect(); - if (!expanded) return NULL; - expanded->expandBy(delta); + bool outline = _drawing.outline(); + + if (!_drawing.outline()) { + // pick inside clipping path; if NULL, it means the object is clipped away there + if (_clip) { + DrawingItem *cpick = _clip->pick(p, delta, flags | PICK_AS_CLIP); + if (!cpick) return NULL; + } + // same for mask + if (_mask) { + DrawingItem *mpick = _mask->pick(p, delta, flags); + if (!mpick) return NULL; + } + } + + Geom::OptIntRect box = (outline || (flags & PICK_AS_CLIP)) ? _bbox : _drawbox; + if (!box) return NULL; + + Geom::Rect expanded = *box; + expanded.expandBy(delta); - if (expanded->contains(p)) { - return _pickItem(p, delta, sticky); + if (expanded.contains(p)) { + return _pickItem(p, delta, flags); } return NULL; } -- cgit v1.2.3 From 01913a7cb9e1f9190fd3f9d2d047cbb88b9aa4ff Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Sun, 14 Aug 2011 09:08:30 +0200 Subject: Correctly invalidate cache of objects with background-accessing filters (bzr r10347.1.32) --- src/display/drawing-item.cpp | 197 ++++++++++++++++++++++++++----------------- 1 file changed, 119 insertions(+), 78 deletions(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index ac0d1be1e..f28894c14 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -56,6 +56,8 @@ DrawingItem::DrawingItem(Drawing &drawing) , _cache(NULL) , _state(0) , _child_type(CHILD_ORPHAN) + , _background_new(0) + , _background_accumulate(0) , _visible(true) , _sensitive(true) , _cached(0) @@ -300,10 +302,9 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne bool outline = _drawing.outline(); // Set reset flags according to propagation status - if (_propagate) { - reset |= ~_state; - _propagate = FALSE; - } + reset |= _propagate_state; + _propagate_state = 0; + _state &= ~reset; // reset state of this item if ((~_state & flags) == 0) return; // nothing to do @@ -323,80 +324,91 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne _ctm = child_ctx.ctm; // update _bbox - unsigned old_state = _state; + unsigned to_update = _state ^ flags; _state = _updateItem(area, child_ctx, flags, reset); - // compute drawbox - if (_filter && render_filters && _item_bbox) { - _drawbox = _filter->compute_drawbox(this, *_item_bbox); - } else { - _drawbox = _bbox; - } - - // Clipping - if (_clip) { - _clip->update(area, child_ctx, flags, reset); - if (outline) { - _bbox.unionWith(_clip->_bbox); + if (to_update & STATE_BBOX) { + // compute drawbox + if (_filter && render_filters && _item_bbox) { + _drawbox = _filter->compute_drawbox(this, *_item_bbox); } else { - _drawbox.intersectWith(_clip->_bbox); + _drawbox = _bbox; } - } - // masking - if (_mask) { - _mask->update(area, child_ctx, flags, reset); - if (outline) { - _bbox.unionWith(_mask->_bbox); - } else { - // for masking, we need full drawbox of mask - _drawbox.intersectWith(_mask->_drawbox); + + // Clipping + if (_clip) { + _clip->update(area, child_ctx, flags, reset); + if (outline) { + _bbox.unionWith(_clip->_bbox); + } else { + _drawbox.intersectWith(_clip->_bbox); + } + } + // Masking + if (_mask) { + _mask->update(area, child_ctx, flags, reset); + if (outline) { + _bbox.unionWith(_mask->_bbox); + } else { + // for masking, we need full drawbox of mask + _drawbox.intersectWith(_mask->_drawbox); + } } } - // Update cache score for this item - if (_has_cache_iterator) { - // remove old score information - _drawing._candidate_items.erase(_cache_iterator); - _has_cache_iterator = false; - } - double score = _cacheScore(); - if (score >= _drawing._cache_score_threshold) { - CacheRecord cr; - cr.score = score; - // if _cacheRect() is empty, a negative score will be returned from _cacheScore(), - // so this will not execute (cache score threshold must be positive) - cr.cache_size = _cacheRect()->area() * 4; - cr.item = this; - _drawing._candidate_items.push_back(cr); - _cache_iterator = --_drawing._candidate_items.end(); - _has_cache_iterator = true; - } - - /* Update cache if enabled. - * General note: here we only tell the cache how it has to transform - * during the render phase. The transformation is deferred because - * after the update the item can have its caching turned off, - * e.g. because its filter was removed. This way we avoid tempoerarily - * using more memory than the cache budget */ - if (_cache) { - Geom::OptIntRect cl = _cacheRect(); - if (_visible && cl) { // never create cache for invisible items - // this takes care of invalidation on transform - _cache->scheduleTransform(*cl, ctm_change); - } else { - // Destroy cache for this item - outside of canvas or invisible. - // The opposite transition (invisible -> visible or object - // entering the canvas) is handled during the render phase - delete _cache; - _cache = NULL; + if (to_update & STATE_CACHE) { + // Update cache score for this item + if (_has_cache_iterator) { + // remove old score information + _drawing._candidate_items.erase(_cache_iterator); + _has_cache_iterator = false; + } + double score = _cacheScore(); + if (score >= _drawing._cache_score_threshold) { + CacheRecord cr; + cr.score = score; + // if _cacheRect() is empty, a negative score will be returned from _cacheScore(), + // so this will not execute (cache score threshold must be positive) + cr.cache_size = _cacheRect()->area() * 4; + cr.item = this; + _drawing._candidate_items.push_back(cr); + _cache_iterator = --_drawing._candidate_items.end(); + _has_cache_iterator = true; + } + + /* Update cache if enabled. + * General note: here we only tell the cache how it has to transform + * during the render phase. The transformation is deferred because + * after the update the item can have its caching turned off, + * e.g. because its filter was removed. This way we avoid tempoerarily + * using more memory than the cache budget */ + if (_cache) { + Geom::OptIntRect cl = _cacheRect(); + if (_visible && cl) { // never create cache for invisible items + // this takes care of invalidation on transform + _cache->scheduleTransform(*cl, ctm_change); + } else { + // Destroy cache for this item - outside of canvas or invisible. + // The opposite transition (invisible -> visible or object + // entering the canvas) is handled during the render phase + delete _cache; + _cache = NULL; + } } } - // now that we know drawbox, dirty the corresponding rect on canvas - // unless filtered, groups do not need to render by themselves, only their members - if (!is_drawing_group(this) || (_filter && render_filters)) { - // mark for rendering if the item becomes renderable - if ((old_state ^ _state) & STATE_RENDER) { + if (to_update & STATE_BACKGROUND) { + // Update _background_accumulate flag + // The code below correctly passes information from _background_new down the tree + _background_accumulate = _background_new; + if (_child_type == CHILD_NORMAL && _parent->_background_accumulate) + _background_accumulate = true; + } + + if (to_update & STATE_RENDER) { + // now that we know drawbox, dirty the corresponding rect on canvas + // unless filtered, groups do not need to render by themselves, only their members + if (!is_drawing_group(this) || (_filter && render_filters)) { _markForRendering(); } } @@ -720,14 +732,37 @@ DrawingItem::_markForRendering() if (!dirty) return; // dirty the caches of all parents + DrawingItem *bkg_root = NULL; + for (DrawingItem *i = this; i; i = i->_parent) { if (i->_cached && i->_cache) { i->_cache->markDirty(*dirty); } + if (i->_background_accumulate) { + bkg_root = i; + } + } + + if (bkg_root) { + bkg_root->_invalidateFilterBackground(*dirty); } _drawing.signal_request_render.emit(*dirty); } +void +DrawingItem::_invalidateFilterBackground(Geom::IntRect const &area) +{ + if (!_drawbox.intersects(area)) return; + + if (_cache && _filter && _filter->uses_background()) { + _cache->markDirty(area); + } + + for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) { + i->_invalidateFilterBackground(area); + } +} + /** @brief Marks the item as needing a recomputation of internal data. * * This mechanism avoids traversing the entire rendering tree (which could be vast) @@ -745,10 +780,9 @@ DrawingItem::_markForRendering() void DrawingItem::_markForUpdate(unsigned flags, bool propagate) { - // we can't simply assign because a previous markForUpdate call - // could have had propagate=true even if this one has propagate=false - if (propagate) - _propagate = true; + if (propagate) { + _propagate_state |= flags; + } if (_state & flags) { _state &= ~flags; @@ -780,16 +814,23 @@ DrawingItem::_setStyleCommon(SPStyle *&_style, SPStyle *style) _filter = NULL; } - /* - if (style && style->enable_background.set - && style->enable_background.value == SP_CSS_BACKGROUND_NEW) { - _background_new = true; - }*/ + if (style && style->enable_background.set) { + if (style->enable_background.value == SP_CSS_BACKGROUND_NEW && !_background_new) { + _background_new = true; + _markForUpdate(STATE_BACKGROUND, true); + } else if (style->enable_background.value == SP_CSS_BACKGROUND_ACCUMULATE && _background_new) { + _background_new = false; + _markForUpdate(STATE_BACKGROUND, true); + } + } - // TODO: STATE_ALL unsets too much _markForUpdate(STATE_ALL, false); } +/** @brief Compute the caching score. + * + * Higher scores mean the item is more aggresively prioritized for automatic + * caching by Inkscape::Drawing. */ double DrawingItem::_cacheScore() { -- cgit v1.2.3 From 66bab1f2b80ff441643fd5fdeb5bade70fb5aa8c Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Tue, 16 Aug 2011 03:40:10 +0200 Subject: Add sanity checks against singular transforms in the drawing tree. Fixes LP #825767. Fixed bugs: - https://launchpad.net/bugs/825767 (bzr r10347.1.33) --- src/display/drawing-item.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index f28894c14..1195bc56c 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -66,8 +66,7 @@ DrawingItem::DrawingItem(Drawing &drawing) , _propagate(0) // , _renders_opacity(0) , _pick_children(0) -{ -} +{} DrawingItem::~DrawingItem() { @@ -120,7 +119,7 @@ DrawingItem * DrawingItem::parent() const { // initially I wanted to return NULL if we are a clip or mask child, - // but the previous behavior was just to return the parent + // but the previous behavior was just to return the parent regardless of child type return _parent; } @@ -442,6 +441,7 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag // If we are invisible, return immediately if (!_visible) return; + if (_ctm.isSingular(NR_EPSILON)) return; // TODO convert outline rendering to a separate virtual function if (outline) { -- cgit v1.2.3 From a49c525365ff86ea1e22281d3a3b66d7ef0087c1 Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Fri, 19 Aug 2011 09:33:48 +0200 Subject: Fix rendering glitches appearing when filtered, cached groups have filtered, cached children (bzr r10347.1.36) --- src/display/drawing-item.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index 1195bc56c..c517b1bb5 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -370,8 +370,8 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne // so this will not execute (cache score threshold must be positive) cr.cache_size = _cacheRect()->area() * 4; cr.item = this; - _drawing._candidate_items.push_back(cr); - _cache_iterator = --_drawing._candidate_items.end(); + _drawing._candidate_items.push_front(cr); + _cache_iterator = _drawing._candidate_items.begin(); _has_cache_iterator = true; } @@ -462,7 +462,7 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag } else { // There is no cache. This could be because caching of this item // was just turned on after the last update phase, or because - // we are outside of the canvas. + // we were previously outside of the canvas. Geom::OptIntRect cl = _drawing.cacheLimit(); cl.intersectWith(_drawbox); if (cl) { @@ -515,9 +515,8 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag // 3. copy from cache to output Inkscape::DrawingContext::Save save(ct); ct.rectangle(*carea); - ct.clip(); ct.setSource(_cache); - ct.paint(); + ct.fill(); // 4. mark as clean _cache->markClean(*carea); return; @@ -591,14 +590,14 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag if (_cached && _cache) { DrawingContext cachect(*_cache); cachect.rectangle(*carea); - cachect.clip(); cachect.setOperator(CAIRO_OPERATOR_SOURCE); cachect.setSource(&intermediate); - cachect.paint(); + cachect.fill(); _cache->markClean(*carea); } + ct.rectangle(*carea); ct.setSource(&intermediate); - ct.paint(); + ct.fill(); ct.setSource(0,0,0,0); // the call above is to clear a ref on the intermediate surface held by ct } @@ -735,7 +734,10 @@ DrawingItem::_markForRendering() DrawingItem *bkg_root = NULL; for (DrawingItem *i = this; i; i = i->_parent) { - if (i->_cached && i->_cache) { + if (i != this && i->_filter) { + i->_filter->area_enlarge(*dirty, i); + } + if (i->_cache) { i->_cache->markDirty(*dirty); } if (i->_background_accumulate) { -- cgit v1.2.3 From 0fc028f7050c91bfdb1a50ba8cb6462b2bf03d57 Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Sun, 21 Aug 2011 17:33:09 +0200 Subject: Filter background rendering now matches the SVG specification. (bzr r10347.1.37) --- src/display/drawing-item.cpp | 112 ++++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 49 deletions(-) (limited to 'src/display/drawing-item.cpp') diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index c517b1bb5..a5496e999 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -123,6 +123,16 @@ DrawingItem::parent() const return _parent; } +/// Returns true if item is among the descendants. Will return false if item == this. +bool +DrawingItem::isAncestorOf(DrawingItem *item) const +{ + for (DrawingItem *i = item->_parent; i; i = i->_parent) { + if (i == this) return true; + } + return false; +} + void DrawingItem::appendChild(DrawingItem *item) { @@ -314,6 +324,16 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne if (!area.intersects(outline ? _bbox : _drawbox)) return; } + // compute which elements need an update + unsigned to_update = _state ^ flags; + + // this needs to be called before we recurse into children + if (to_update & STATE_BACKGROUND) { + _background_accumulate = _background_new; + if (_child_type == CHILD_NORMAL && _parent->_background_accumulate) + _background_accumulate = true; + } + UpdateContext child_ctx(ctx); if (_transform) { child_ctx.ctm = *_transform * ctx.ctm; @@ -322,14 +342,13 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne Geom::Affine ctm_change = _ctm.inverse() * child_ctx.ctm; _ctm = child_ctx.ctm; - // update _bbox - unsigned to_update = _state ^ flags; + // update _bbox and call this function for children _state = _updateItem(area, child_ctx, flags, reset); if (to_update & STATE_BBOX) { // compute drawbox - if (_filter && render_filters && _item_bbox) { - _drawbox = _filter->compute_drawbox(this, *_item_bbox); + if (_filter && render_filters) { + _drawbox = _filter->compute_drawbox(this, _item_bbox); } else { _drawbox = _bbox; } @@ -396,14 +415,6 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne } } - if (to_update & STATE_BACKGROUND) { - // Update _background_accumulate flag - // The code below correctly passes information from _background_new down the tree - _background_accumulate = _background_new; - if (_child_type == CHILD_NORMAL && _parent->_background_accumulate) - _background_accumulate = true; - } - if (to_update & STATE_RENDER) { // now that we know drawbox, dirty the corresponding rect on canvas // unless filtered, groups do not need to render by themselves, only their members @@ -433,32 +444,36 @@ struct MaskLuminanceToAlpha { * * @param flags Rendering options. This deals mainly with cache control. */ -void -DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flags) +unsigned +DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flags, DrawingItem *stop_at) { bool outline = _drawing.outline(); bool render_filters = _drawing.renderFilters(); + // stop_at is handled in DrawingGroup, but this check is required to handle the case + // where a filtered item with background-accessing filter has enable-background: new + if (this == stop_at) return RENDER_STOP; + // If we are invisible, return immediately - if (!_visible) return; - if (_ctm.isSingular(NR_EPSILON)) return; + if (!_visible) return RENDER_OK; + if (_ctm.isSingular(NR_EPSILON)) return RENDER_OK; // TODO convert outline rendering to a separate virtual function if (outline) { _renderOutline(ct, area, flags); - return; + return RENDER_OK; } // carea is the area to paint Geom::OptIntRect carea = Geom::intersect(area, _drawbox); - if (!carea) return; + if (!carea) return RENDER_OK; // render from cache if possible if (_cached) { if (_cache) { _cache->prepare(); _cache->paintFromCache(ct, carea); - if (!carea) return; + if (!carea) return RENDER_OK; } else { // There is no cache. This could be because caching of this item // was just turned on after the last update phase, or because @@ -484,6 +499,7 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag nir |= (_mask != NULL); // 2. it has a mask nir |= (_filter != NULL && render_filters); // 3. it has a filter nir |= needs_opacity; // 4. it is non-opaque + nir |= (_cache != NULL); // 5. it is cached /* How the rendering is done. * @@ -497,33 +513,12 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag * to the opacity value. */ - // short-circuit the simple case. - if (!needs_intermediate_rendering) { - if (_cached && _cache) { - Inkscape::DrawingContext cachect(*_cache); - cachect.rectangle(*carea); - cachect.clip(); - - { // 1. clear the corresponding part of cache - Inkscape::DrawingContext::Save save(cachect); - cachect.setSource(0,0,0,0); - cachect.setOperator(CAIRO_OPERATOR_SOURCE); - cachect.paint(); - } - // 2. render to cache - _renderItem(cachect, *carea, flags); - // 3. copy from cache to output - Inkscape::DrawingContext::Save save(ct); - ct.rectangle(*carea); - ct.setSource(_cache); - ct.fill(); - // 4. mark as clean - _cache->markClean(*carea); - return; - } else { - _renderItem(ct, *carea, flags); - return; - } + // Short-circuit the simple case. + // We also use this path for filter background rendering, because masking, clipping, + // filters and opacity do not apply when rendering the ancestors of the filtered + // element + if ((flags & RENDER_FILTER_BACKGROUND) || !needs_intermediate_rendering) { + return _renderItem(ct, *carea, flags & ~RENDER_FILTER_BACKGROUND, stop_at); } // iarea is the bounding box for intermediate rendering @@ -540,6 +535,7 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag DrawingSurface intermediate(*iarea); DrawingContext ict(intermediate); + unsigned render_result = RENDER_OK; // 1. Render clipping path with alpha = opacity. ict.setSource(0,0,0,_opacity); @@ -571,11 +567,27 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag // 3. Render object itself ict.pushGroup(); - _renderItem(ict, *iarea, flags); + render_result = _renderItem(ict, *iarea, flags, stop_at); // 4. Apply filter. if (_filter && render_filters) { - _filter->render(this, ct, ict); + bool rendered = false; + if (_filter->uses_background() && _background_accumulate) { + DrawingItem *bg_root = this; + for (; bg_root; bg_root = bg_root->_parent) { + if (bg_root->_background_new) break; + } + if (bg_root) { + DrawingSurface bg(*iarea); + DrawingContext bgct(bg); + bg_root->render(bgct, *iarea, flags | RENDER_FILTER_BACKGROUND, this); + _filter->render(this, ict, &bgct); + rendered = true; + } + } + if (!rendered) { + _filter->render(this, ict, NULL); + } // Note that because the object was rendered to a group, // the internals of the filter need to use cairo_get_group_target() // instead of cairo_get_target(). @@ -600,6 +612,8 @@ DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flag ct.fill(); ct.setSource(0,0,0,0); // the call above is to clear a ref on the intermediate surface held by ct + + return render_result; } void @@ -612,7 +626,7 @@ DrawingItem::_renderOutline(DrawingContext &ct, Geom::IntRect const &area, unsig // just render everything: item, clip, mask // First, render the object itself - _renderItem(ct, *carea, flags); + _renderItem(ct, *carea, flags, NULL); // render clip and mask, if any guint32 saved_rgba = _drawing.outlinecolor; // save current outline color -- cgit v1.2.3