drawing-item.cpp revision 5989d147b610674d1edd254b4f7d24127a27da40
/**
* @file
* @brief Canvas item belonging to an SVG drawing element
*//*
* Authors:
* Krzysztof KosiĆski <tweenk.pl@gmail.com>
*
* Copyright (C) 2011 Authors
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#include <climits>
#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-filter.h"
#include "preferences.h"
#include "style.h"
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.
*/
, _key(0)
, _opacity(1.0)
, _transform(NULL)
, _user_data(NULL)
, _state(0)
, _visible(true)
, _sensitive(true)
, _cached(0)
, _cached_persistent(0)
, _has_cache_iterator(0)
, _propagate(0)
// , _renders_opacity(0)
, _clip_child(0)
, _mask_child(0)
, _drawing_root(0)
, _pick_children(0)
{
}
{
//if (!_children.empty()) {
// g_warning("Removing item with children");
//}
// remove from the set of cached items and delete cache
setCached(false, true);
if (_has_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) {
// we cannot call setClip(NULL) or setMask(NULL),
// because that would be an endless loop
if (_clip_child) {
} else if (_mask_child) {
} else {
}
} else if (_drawing_root) {
}
delete _transform;
delete _clip;
delete _mask;
delete _filter;
}
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
return _parent;
}
void
{
_markForUpdate(STATE_ALL, false);
}
void
{
_markForUpdate(STATE_ALL, false);
}
/// Delete all regular children of this item (not mask or clip).
void
{
// 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
}
}
/// Set the incremental transform for this item
void
{
if (_transform) {
current = *_transform;
}
// mark the area where the object was for redraw.
if (new_trans.isIdentity()) {
delete _transform; // delete NULL; is safe
_transform = NULL;
} else {
}
_markForUpdate(STATE_ALL, true);
}
}
void
{
}
void
DrawingItem::setVisible(bool v)
{
_visible = v;
}
/// This is currently unused
void
DrawingItem::setSensitive(bool s)
{
_sensitive = s;
}
/** @brief Enable / disable storing the rendering in memory.
* Calling setCached(false, true) will also remove the persistent status
*/
void
{
if (cache_env) return;
if (_cached_persistent && !persistent)
return;
if (cached) {
} else {
delete _cache;
}
}
void
{
delete _clip;
if (item) {
item->_clip_child = true;
}
_markForUpdate(STATE_ALL, true);
}
void
{
delete _mask;
if (item) {
item->_mask_child = true;
}
_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)
{
if (!_parent) return;
}
void
{
_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)
{
// Set reset flags according to propagation status
if (_propagate) {
_propagate = FALSE;
}
// TODO this might be wrong
// we have up-to-date bbox
}
if (_transform) {
}
/* Remember the transformation matrix */
// update _bbox
// compute drawbox
} else {
}
// Clipping
if (_clip) {
if (outline) {
} else {
}
}
// masking
if (_mask) {
if (outline) {
} else {
// for masking, we need full drawbox of mask
}
}
// Update cache score for this item
if (_has_cache_iterator) {
// remove old score information
_has_cache_iterator = false;
}
double score = _cacheScore();
// if _cacheRect() is empty, a negative score will be returned from _cacheScore(),
// so this will not execute (cache score threshold must be positive)
_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) {
// this takes care of invalidation on transform
} 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;
}
}
// now that we know drawbox, dirty the corresponding rect on canvas
// unless filtered, groups do not need to render by themselves, only their members
// mark for rendering if the item becomes renderable
}
}
}
struct MaskLuminanceToAlpha {
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
}
};
/** @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
{
// If we are invisible, return immediately
if (!_visible) return;
// TODO convert outline rendering to a separate virtual function
if (outline) {
return;
}
// carea is the area to paint
if (!carea) return;
// render from cache if possible
if (_cached) {
if (_cache) {
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
// we are outside of the canvas.
if (cl) {
}
}
} else {
// if our caching was turned off after the last update, it was already
// deleted in setCached()
}
// determine whether this shape needs intermediate rendering.
bool needs_intermediate_rendering = false;
bool &nir = needs_intermediate_rendering;
// this item needs an intermediate rendering if:
/* 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) {
{ // 1. clear the corresponding part of cache
}
// 2. render to cache
// 3. copy from cache to output
// 4. mark as clean
return;
} else {
return;
}
}
// iarea is the bounding box for intermediate rendering
// 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.
// expand carea to contain the dependent area of filters.
if (_filter && render_filters) {
}
// 1. Render clipping path with alpha = 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.
if (_clip) {
} else {
// if there is no clipping path, fill the entire surface with alpha = opacity.
}
// 2. Render the mask if present and compose it with the clipping path + opacity.
if (_mask) {
// Convert mask's luminance to alpha
}
// 3. Render object itself
// 4. Apply filter.
if (_filter && render_filters) {
// 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
// 6. Paint the completed rendering onto the base context (or into cache)
}
// the call above is to clear a ref on the intermediate surface held by ct
}
void
{
// intersect with bbox rather than drawbox, as we want to render things outside
// of the clipping path as well
if (!carea) return;
// just render everything: item, clip, mask
// First, render the object itself
// render clip and mask, if any
// render clippath as an object, using a different color
if (_clip) {
}
// render mask as an object, using a different color
if (_mask) {
}
}
/** @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
{
// don't bother if the object does not implement clipping (e.g. DrawingImage)
if (!_canClip()) return;
if (!_visible) 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();
}
ct.pushAlphaGroup();
}
// rasterize the clipping path
if (_clip) {
}
}
/** @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.
*/
{
// Sometimes there's no BBOX in state, reason unknown (bug 992817)
// I made this not an assert to remove the warning
return NULL;
return NULL;
// some part of the shape might be hidden by clipping
// TODO add Geom::OptRect(Geom::OptIntRect const &) constructor
}
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
{
if (!dirty) return;
// dirty the caches of all parents
for (DrawingItem *i = this; i; i = i->_parent) {
}
}
}
/** @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
{
// 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 (_parent) {
} else {
}
}
}
void
{
// if group has a filter
if (!_filter) {
}
} else {
// no filter set for this group
delete _filter;
}
/*
if (style && style->enable_background.set
&& style->enable_background.value == SP_CSS_BACKGROUND_NEW) {
_background_new = true;
}*/
// TODO: STATE_ALL unsets too much
_markForUpdate(STATE_ALL, false);
}
double
{
if (!cache_rect) return -1.0;
// a crude first approximation:
// the basic score is the number of pixels in the drawbox
// this is multiplied by the filter complexity and its expansion
// area_enlarge never shrinks the rect, so the result of intersection below
// must be non-empty
}
// if the object is clipped, add 1/2 of its bbox pixels
}
// if masked, add mask score
if (_mask) {
}
//g_message("caching score: %f", score);
return score;
}
{
return r;
}
} // 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 :