~centralelyon2010/inkscape/imagelinks2

« back to all changes in this revision

Viewing changes to src/display/drawing-item.cpp

  • Committer: JazzyNico
  • Date: 2011-08-29 20:25:30 UTC
  • Revision ID: nicoduf@yahoo.fr-20110829202530-6deuoz11q90usldv
Code refactoring and merging with trunk (revision 10599).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 * @file
 
3
 * @brief Canvas item belonging to an SVG drawing element
 
4
 *//*
 
5
 * Authors:
 
6
 *   Krzysztof Kosiński <tweenk.pl@gmail.com>
 
7
 *
 
8
 * Copyright (C) 2011 Authors
 
9
 * Released under GNU GPL, read the file 'COPYING' for more information
 
10
 */
 
11
 
 
12
#include <climits>
 
13
#include "display/cairo-utils.h"
 
14
#include "display/cairo-templates.h"
 
15
#include "display/drawing.h"
 
16
#include "display/drawing-context.h"
 
17
#include "display/drawing-item.h"
 
18
#include "display/drawing-group.h"
 
19
#include "display/drawing-surface.h"
 
20
#include "nr-filter.h"
 
21
#include "preferences.h"
 
22
#include "style.h"
 
23
 
 
24
namespace Inkscape {
 
25
 
 
26
/** @class DrawingItem
 
27
 * @brief SVG drawing item for display.
 
28
 *
 
29
 * This was previously known as NRArenaItem. It represents the renderable
 
30
 * portion of the SVG document. Typically this is created by the SP tree,
 
31
 * in particular the show() virtual function.
 
32
 *
 
33
 * @section ObjectLifetime Object lifetime
 
34
 * Deleting a DrawingItem will cause all of its children to be deleted as well.
 
35
 * This can lead to nasty surprises if you hold references to things
 
36
 * which are children of what is being deleted. Therefore, in the SP tree,
 
37
 * you always need to delete the item views of children before deleting
 
38
 * the view of the parent. Do not call delete on things returned from show()
 
39
 * - this will cause dangling pointers inside the SPItem and lead to a crash.
 
40
 * Use the corresponing hide() method.
 
41
 *
 
42
 * Outside of the SP tree, you should not use any references after the root node
 
43
 * has been deleted.
 
44
 */
 
45
 
 
46
DrawingItem::DrawingItem(Drawing &drawing)
 
47
    : _drawing(drawing)
 
48
    , _parent(NULL)
 
49
    , _key(0)
 
50
    , _opacity(1.0)
 
51
    , _transform(NULL)
 
52
    , _clip(NULL)
 
53
    , _mask(NULL)
 
54
    , _filter(NULL)
 
55
    , _user_data(NULL)
 
56
    , _cache(NULL)
 
57
    , _state(0)
 
58
    , _child_type(CHILD_ORPHAN)
 
59
    , _background_new(0)
 
60
    , _background_accumulate(0)
 
61
    , _visible(true)
 
62
    , _sensitive(true)
 
63
    , _cached(0)
 
64
    , _cached_persistent(0)
 
65
    , _has_cache_iterator(0)
 
66
    , _propagate(0)
 
67
//    , _renders_opacity(0)
 
68
    , _pick_children(0)
 
69
{}
 
70
 
 
71
DrawingItem::~DrawingItem()
 
72
{
 
73
    _drawing.signal_item_deleted.emit(this);
 
74
    //if (!_children.empty()) {
 
75
    //    g_warning("Removing item with children");
 
76
    //}
 
77
 
 
78
    // remove from the set of cached items and delete cache
 
79
    setCached(false, true);
 
80
    if (_has_cache_iterator) {
 
81
        _drawing._candidate_items.erase(_cache_iterator);
 
82
    }
 
83
    // remove this item from parent's children list
 
84
    // due to the effect of clearChildren(), this only happens for the top-level deleted item
 
85
    if (_parent) {
 
86
        _markForRendering();
 
87
    }
 
88
 
 
89
    switch (_child_type) {
 
90
    case CHILD_NORMAL: {
 
91
        ChildrenList::iterator ithis = _parent->_children.iterator_to(*this);
 
92
        _parent->_children.erase(ithis);
 
93
        } break;
 
94
    case CHILD_CLIP:
 
95
        // we cannot call setClip(NULL) or setMask(NULL),
 
96
        // because that would be an endless loop
 
97
        _parent->_clip = NULL;
 
98
        break;
 
99
    case CHILD_MASK:
 
100
        _parent->_mask = NULL;
 
101
        break;
 
102
    case CHILD_ROOT:
 
103
        _drawing._root = NULL;
 
104
        break;
 
105
    default: ;
 
106
    }
 
107
 
 
108
    if (_parent) {
 
109
        _parent->_markForUpdate(STATE_ALL, false);
 
110
    }
 
111
    clearChildren();
 
112
    delete _transform;
 
113
    delete _clip;
 
114
    delete _mask;
 
115
    delete _filter;
 
116
}
 
117
 
 
118
DrawingItem *
 
119
DrawingItem::parent() const
 
120
{
 
121
    // initially I wanted to return NULL if we are a clip or mask child,
 
122
    // but the previous behavior was just to return the parent regardless of child type
 
123
    return _parent;
 
124
}
 
125
 
 
126
/// Returns true if item is among the descendants. Will return false if item == this.
 
127
bool
 
128
DrawingItem::isAncestorOf(DrawingItem *item) const
 
129
{
 
130
    for (DrawingItem *i = item->_parent; i; i = i->_parent) {
 
131
        if (i == this) return true;
 
132
    }
 
133
    return false;
 
134
}
 
135
 
 
136
void
 
137
DrawingItem::appendChild(DrawingItem *item)
 
138
{
 
139
    item->_parent = this;
 
140
    assert(item->_child_type == CHILD_ORPHAN);
 
141
    item->_child_type = CHILD_NORMAL;
 
142
    _children.push_back(*item);
 
143
    _markForUpdate(STATE_ALL, false);
 
144
}
 
145
 
 
146
void
 
147
DrawingItem::prependChild(DrawingItem *item)
 
148
{
 
149
    item->_parent = this;
 
150
    assert(item->_child_type == CHILD_ORPHAN);
 
151
    item->_child_type = CHILD_NORMAL;
 
152
    _children.push_front(*item);
 
153
    _markForUpdate(STATE_ALL, false);
 
154
}
 
155
 
 
156
/// Delete all regular children of this item (not mask or clip).
 
157
void
 
158
DrawingItem::clearChildren()
 
159
{
 
160
    // prevent children from referencing the parent during deletion
 
161
    // this way, children won't try to remove themselves from a list
 
162
    // from which they have already been removed by clear_and_dispose
 
163
    for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) {
 
164
        i->_parent = NULL;
 
165
        i->_child_type = CHILD_ORPHAN;
 
166
    }
 
167
    _children.clear_and_dispose(DeleteDisposer());
 
168
}
 
169
 
 
170
/// Set the incremental transform for this item
 
171
void
 
172
DrawingItem::setTransform(Geom::Affine const &new_trans)
 
173
{
 
174
    Geom::Affine current;
 
175
    if (_transform) {
 
176
        current = *_transform;
 
177
    }
 
178
    
 
179
    if (!Geom::are_near(current, new_trans, 1e-18)) {
 
180
        // mark the area where the object was for redraw.
 
181
        _markForRendering();
 
182
        if (new_trans.isIdentity()) {
 
183
            delete _transform; // delete NULL; is safe
 
184
            _transform = NULL;
 
185
        } else {
 
186
            _transform = new Geom::Affine(new_trans);
 
187
        }
 
188
        _markForUpdate(STATE_ALL, true);
 
189
    }
 
190
}
 
191
 
 
192
void
 
193
DrawingItem::setOpacity(float opacity)
 
194
{
 
195
    _opacity = opacity;
 
196
    _markForRendering();
 
197
}
 
198
 
 
199
void
 
200
DrawingItem::setVisible(bool v)
 
201
{
 
202
    _visible = v;
 
203
    _markForRendering();
 
204
}
 
205
 
 
206
/// This is currently unused
 
207
void
 
208
DrawingItem::setSensitive(bool s)
 
209
{
 
210
    _sensitive = s;
 
211
}
 
212
 
 
213
/** @brief Enable / disable storing the rendering in memory.
 
214
 * Calling setCached(false, true) will also remove the persistent status
 
215
 */
 
216
void
 
217
DrawingItem::setCached(bool cached, bool persistent)
 
218
{
 
219
    static const char *cache_env = getenv("_INKSCAPE_DISABLE_CACHE");
 
220
    if (cache_env) return;
 
221
 
 
222
    if (_cached_persistent && !persistent)
 
223
        return;
 
224
 
 
225
    _cached = cached;
 
226
    _cached_persistent = persistent ? cached : false;
 
227
    if (cached) {
 
228
        _drawing._cached_items.insert(this);
 
229
    } else {
 
230
        _drawing._cached_items.erase(this);
 
231
        delete _cache;
 
232
        _cache = NULL;
 
233
    }
 
234
}
 
235
 
 
236
void
 
237
DrawingItem::setClip(DrawingItem *item)
 
238
{
 
239
    _markForRendering();
 
240
    delete _clip;
 
241
    _clip = item;
 
242
    if (item) {
 
243
        item->_parent = this;
 
244
        assert(item->_child_type == CHILD_ORPHAN);
 
245
        item->_child_type = CHILD_CLIP;
 
246
    }
 
247
    _markForUpdate(STATE_ALL, true);
 
248
}
 
249
 
 
250
void
 
251
DrawingItem::setMask(DrawingItem *item)
 
252
{
 
253
    _markForRendering();
 
254
    delete _mask;
 
255
    _mask = item;
 
256
        if (item) {
 
257
        item->_parent = this;
 
258
        assert(item->_child_type == CHILD_ORPHAN);
 
259
        item->_child_type = CHILD_MASK;
 
260
    }
 
261
    _markForUpdate(STATE_ALL, true);
 
262
}
 
263
 
 
264
/// Move this item to the given place in the Z order of siblings.
 
265
/// Does nothing if the item has no parent.
 
266
void
 
267
DrawingItem::setZOrder(unsigned z)
 
268
{
 
269
    if (!_parent) return;
 
270
 
 
271
    ChildrenList::iterator it = _parent->_children.iterator_to(*this);
 
272
    _parent->_children.erase(it);
 
273
 
 
274
    ChildrenList::iterator i = _parent->_children.begin();
 
275
    std::advance(i, std::min(z, unsigned(_parent->_children.size())));
 
276
    _parent->_children.insert(i, *this);
 
277
    _markForRendering();
 
278
}
 
279
 
 
280
void
 
281
DrawingItem::setItemBounds(Geom::OptRect const &bounds)
 
282
{
 
283
    _item_bbox = bounds;
 
284
}
 
285
 
 
286
/** @brief Update derived data before operations.
 
287
 * The purpose of this call is to recompute internal data which depends
 
288
 * on the attributes of the object, but is not directly settable by the user.
 
289
 * Precomputing this data speeds up later rendering, because some items
 
290
 * can be omitted.
 
291
 *
 
292
 * Currently this method handles updating the visual and geometric bounding boxes
 
293
 * in pixels, storing the total transformation from item space to the screen
 
294
 * and cache invalidation.
 
295
 *
 
296
 * @param area Area to which the update should be restricted. Only takes effect
 
297
 *             if the bounding box is known.
 
298
 * @param ctx A structure to store cascading state.
 
299
 * @param flags Which internal data should be recomputed. This can be any combination
 
300
 *              of StateFlags.
 
301
 * @param reset State fields that should be reset before processing them. This is
 
302
 *              a means to force a recomputation of internal data even if the item
 
303
 *              considers it up to date. Mainly for internal use, such as
 
304
 *              propagating bunding box recomputation to children when the item's
 
305
 *              transform changes.
 
306
 */
 
307
void
 
308
DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
 
309
{
 
310
    bool render_filters = _drawing.renderFilters();
 
311
    bool outline = _drawing.outline();
 
312
 
 
313
    // Set reset flags according to propagation status
 
314
    reset |= _propagate_state;
 
315
    _propagate_state = 0;
 
316
 
 
317
    _state &= ~reset; // reset state of this item
 
318
 
 
319
    if ((~_state & flags) == 0) return;  // nothing to do
 
320
 
 
321
    // TODO this might be wrong
 
322
    if (_state & STATE_BBOX) {
 
323
        // we have up-to-date bbox
 
324
        if (!area.intersects(outline ? _bbox : _drawbox)) return;
 
325
    }
 
326
 
 
327
    // compute which elements need an update
 
328
    unsigned to_update = _state ^ flags;
 
329
 
 
330
    // this needs to be called before we recurse into children
 
331
    if (to_update & STATE_BACKGROUND) {
 
332
        _background_accumulate = _background_new;
 
333
        if (_child_type == CHILD_NORMAL && _parent->_background_accumulate)
 
334
            _background_accumulate = true;
 
335
    }
 
336
 
 
337
    UpdateContext child_ctx(ctx);
 
338
    if (_transform) {
 
339
        child_ctx.ctm = *_transform * ctx.ctm;
 
340
    }
 
341
    /* Remember the transformation matrix */
 
342
    Geom::Affine ctm_change = _ctm.inverse() * child_ctx.ctm;
 
343
    _ctm = child_ctx.ctm;
 
344
 
 
345
    // update _bbox and call this function for children
 
346
    _state = _updateItem(area, child_ctx, flags, reset);
 
347
 
 
348
    if (to_update & STATE_BBOX) {
 
349
        // compute drawbox
 
350
        if (_filter && render_filters) {
 
351
            _drawbox = _filter->compute_drawbox(this, _item_bbox);
 
352
        } else {
 
353
            _drawbox = _bbox;
 
354
        }
 
355
 
 
356
        // Clipping
 
357
        if (_clip) {
 
358
            _clip->update(area, child_ctx, flags, reset);
 
359
            if (outline) {
 
360
                _bbox.unionWith(_clip->_bbox);
 
361
            } else {
 
362
                _drawbox.intersectWith(_clip->_bbox);
 
363
            }
 
364
        }
 
365
        // Masking
 
366
        if (_mask) {
 
367
            _mask->update(area, child_ctx, flags, reset);
 
368
            if (outline) {
 
369
                _bbox.unionWith(_mask->_bbox);
 
370
            } else {
 
371
                // for masking, we need full drawbox of mask
 
372
                _drawbox.intersectWith(_mask->_drawbox);
 
373
            }
 
374
        }
 
375
    }
 
376
 
 
377
    if (to_update & STATE_CACHE) {
 
378
        // Update cache score for this item
 
379
        if (_has_cache_iterator) {
 
380
            // remove old score information
 
381
            _drawing._candidate_items.erase(_cache_iterator);
 
382
            _has_cache_iterator = false;
 
383
        }
 
384
        double score = _cacheScore();
 
385
        if (score >= _drawing._cache_score_threshold) {
 
386
            CacheRecord cr;
 
387
            cr.score = score;
 
388
            // if _cacheRect() is empty, a negative score will be returned from _cacheScore(),
 
389
            // so this will not execute (cache score threshold must be positive)
 
390
            cr.cache_size = _cacheRect()->area() * 4;
 
391
            cr.item = this;
 
392
            _drawing._candidate_items.push_front(cr);
 
393
            _cache_iterator = _drawing._candidate_items.begin();
 
394
            _has_cache_iterator = true;
 
395
        }
 
396
 
 
397
        /* Update cache if enabled.
 
398
         * General note: here we only tell the cache how it has to transform
 
399
         * during the render phase. The transformation is deferred because
 
400
         * after the update the item can have its caching turned off,
 
401
         * e.g. because its filter was removed. This way we avoid tempoerarily
 
402
         * using more memory than the cache budget */
 
403
        if (_cache) {
 
404
            Geom::OptIntRect cl = _cacheRect();
 
405
            if (_visible && cl) { // never create cache for invisible items
 
406
                // this takes care of invalidation on transform
 
407
                _cache->scheduleTransform(*cl, ctm_change);
 
408
            } else {
 
409
                // Destroy cache for this item - outside of canvas or invisible.
 
410
                // The opposite transition (invisible -> visible or object
 
411
                // entering the canvas) is handled during the render phase
 
412
                delete _cache;
 
413
                _cache = NULL;
 
414
            }
 
415
        }
 
416
    }
 
417
 
 
418
    if (to_update & STATE_RENDER) {
 
419
        // now that we know drawbox, dirty the corresponding rect on canvas
 
420
        // unless filtered, groups do not need to render by themselves, only their members
 
421
        if (!is_drawing_group(this) || (_filter && render_filters)) {
 
422
            _markForRendering();
 
423
        }
 
424
    }
 
425
}
 
426
 
 
427
struct MaskLuminanceToAlpha {
 
428
    guint32 operator()(guint32 in) {
 
429
        EXTRACT_ARGB32(in, a, r, g, b)
 
430
        // the operation of unpremul -> luminance-to-alpha -> multiply by alpha
 
431
        // is equivalent to luminance-to-alpha on premultiplied color values
 
432
        // original computation in double: r*0.2125 + g*0.7154 + b*0.0721
 
433
        guint32 ao = r*109 + g*366 + b*37; // coeffs add up to 512
 
434
        return ((ao + 256) << 15) & 0xff000000; // equivalent to ((ao + 256) / 512) << 24
 
435
    }
 
436
};
 
437
 
 
438
/** @brief Rasterize items.
 
439
 * This method submits the drawing opeartions required to draw this item
 
440
 * to the supplied DrawingContext, restricting drawing the the specified area.
 
441
 *
 
442
 * This method does some common tasks and calls the item-specific rendering
 
443
 * function, _renderItem(), to render e.g. paths or bitmaps.
 
444
 *
 
445
 * @param flags Rendering options. This deals mainly with cache control.
 
446
 */
 
447
unsigned
 
448
DrawingItem::render(DrawingContext &ct, Geom::IntRect const &area, unsigned flags, DrawingItem *stop_at)
 
449
{
 
450
    bool outline = _drawing.outline();
 
451
    bool render_filters = _drawing.renderFilters();
 
452
 
 
453
    // stop_at is handled in DrawingGroup, but this check is required to handle the case
 
454
    // where a filtered item with background-accessing filter has enable-background: new
 
455
    if (this == stop_at) return RENDER_STOP;
 
456
 
 
457
    // If we are invisible, return immediately
 
458
    if (!_visible) return RENDER_OK;
 
459
    if (_ctm.isSingular(1e-18)) return RENDER_OK;
 
460
 
 
461
    // TODO convert outline rendering to a separate virtual function
 
462
    if (outline) {
 
463
        _renderOutline(ct, area, flags);
 
464
        return RENDER_OK;
 
465
    }
 
466
 
 
467
    // carea is the area to paint
 
468
    Geom::OptIntRect carea = Geom::intersect(area, _drawbox);
 
469
    if (!carea) return RENDER_OK;
 
470
 
 
471
    // render from cache if possible
 
472
    if (_cached) {
 
473
        if (_cache) {
 
474
            _cache->prepare();
 
475
            _cache->paintFromCache(ct, carea);
 
476
            if (!carea) return RENDER_OK;
 
477
        } else {
 
478
            // There is no cache. This could be because caching of this item
 
479
            // was just turned on after the last update phase, or because
 
480
            // we were previously outside of the canvas.
 
481
            Geom::OptIntRect cl = _drawing.cacheLimit();
 
482
            cl.intersectWith(_drawbox);
 
483
            if (cl) {
 
484
                _cache = new DrawingCache(*cl);
 
485
            }
 
486
        }
 
487
    } else {
 
488
        // if our caching was turned off after the last update, it was already
 
489
        // deleted in setCached()
 
490
    }
 
491
 
 
492
    // determine whether this shape needs intermediate rendering.
 
493
    bool needs_intermediate_rendering = false;
 
494
    bool &nir = needs_intermediate_rendering;
 
495
    bool needs_opacity = (_opacity < 0.995);
 
496
 
 
497
    // this item needs an intermediate rendering if:
 
498
    nir |= (_clip != NULL); // 1. it has a clipping path
 
499
    nir |= (_mask != NULL); // 2. it has a mask
 
500
    nir |= (_filter != NULL && render_filters); // 3. it has a filter
 
501
    nir |= needs_opacity; // 4. it is non-opaque
 
502
    nir |= (_cache != NULL); // 5. it is cached
 
503
 
 
504
    /* How the rendering is done.
 
505
     *
 
506
     * Clipping, masking and opacity are done by rendering them to a surface
 
507
     * and then compositing the object's rendering onto it with the IN operator.
 
508
     * The object itself is rendered to a group.
 
509
     *
 
510
     * Opacity is done by rendering the clipping path with an alpha
 
511
     * value corresponding to the opacity. If there is no clipping path,
 
512
     * the entire intermediate surface is painted with alpha corresponding
 
513
     * to the opacity value.
 
514
     */
 
515
 
 
516
    // Short-circuit the simple case.
 
517
    // We also use this path for filter background rendering, because masking, clipping,
 
518
    // filters and opacity do not apply when rendering the ancestors of the filtered
 
519
    // element
 
520
    if ((flags & RENDER_FILTER_BACKGROUND) || !needs_intermediate_rendering) {
 
521
        return _renderItem(ct, *carea, flags & ~RENDER_FILTER_BACKGROUND, stop_at);
 
522
    }
 
523
 
 
524
    // iarea is the bounding box for intermediate rendering
 
525
    // Note 1: Pixels inside iarea but outside carea are invalid
 
526
    //         (incomplete filter dependence region).
 
527
    // Note 2: We only need to render carea of clip and mask, but
 
528
    //         iarea of the object.
 
529
    Geom::OptIntRect iarea = carea;
 
530
    // expand carea to contain the dependent area of filters.
 
531
    if (_filter && render_filters) {
 
532
        _filter->area_enlarge(*iarea, this);
 
533
        iarea.intersectWith(_drawbox);
 
534
    }
 
535
 
 
536
    DrawingSurface intermediate(*iarea);
 
537
    DrawingContext ict(intermediate);
 
538
    unsigned render_result = RENDER_OK;
 
539
 
 
540
    // 1. Render clipping path with alpha = opacity.
 
541
    ict.setSource(0,0,0,_opacity);
 
542
    // Since clip can be combined with opacity, the result could be incorrect
 
543
    // for overlapping clip children. To fix this we use the SOURCE operator
 
544
    // instead of the default OVER.
 
545
    ict.setOperator(CAIRO_OPERATOR_SOURCE);
 
546
    if (_clip) {
 
547
        _clip->clip(ict, *carea); // fixme: carea or area?
 
548
    } else {
 
549
        // if there is no clipping path, fill the entire surface with alpha = opacity.
 
550
        ict.paint();
 
551
    }
 
552
    ict.setOperator(CAIRO_OPERATOR_OVER); // reset back to default
 
553
 
 
554
    // 2. Render the mask if present and compose it with the clipping path + opacity.
 
555
    if (_mask) {
 
556
        ict.pushGroup();
 
557
        _mask->render(ict, *carea, flags);
 
558
 
 
559
        cairo_surface_t *mask_s = ict.rawTarget();
 
560
        // Convert mask's luminance to alpha
 
561
        ink_cairo_surface_filter(mask_s, mask_s, MaskLuminanceToAlpha());
 
562
        ict.popGroupToSource();
 
563
        ict.setOperator(CAIRO_OPERATOR_IN);
 
564
        ict.paint();
 
565
        ict.setOperator(CAIRO_OPERATOR_OVER);
 
566
    }
 
567
 
 
568
    // 3. Render object itself
 
569
    ict.pushGroup();
 
570
    render_result = _renderItem(ict, *iarea, flags, stop_at);
 
571
 
 
572
    // 4. Apply filter.
 
573
    if (_filter && render_filters) {
 
574
        bool rendered = false;
 
575
        if (_filter->uses_background() && _background_accumulate) {
 
576
            DrawingItem *bg_root = this;
 
577
            for (; bg_root; bg_root = bg_root->_parent) {
 
578
                if (bg_root->_background_new) break;
 
579
            }
 
580
            if (bg_root) {
 
581
                DrawingSurface bg(*iarea);
 
582
                DrawingContext bgct(bg);
 
583
                bg_root->render(bgct, *iarea, flags | RENDER_FILTER_BACKGROUND, this);
 
584
                _filter->render(this, ict, &bgct);
 
585
                rendered = true;
 
586
            }
 
587
        }
 
588
        if (!rendered) {
 
589
            _filter->render(this, ict, NULL);
 
590
        }
 
591
        // Note that because the object was rendered to a group,
 
592
        // the internals of the filter need to use cairo_get_group_target()
 
593
        // instead of cairo_get_target().
 
594
    }
 
595
 
 
596
    // 5. Render object inside the composited mask + clip
 
597
    ict.popGroupToSource();
 
598
    ict.setOperator(CAIRO_OPERATOR_IN);
 
599
    ict.paint();
 
600
 
 
601
    // 6. Paint the completed rendering onto the base context (or into cache)
 
602
    if (_cached && _cache) {
 
603
        DrawingContext cachect(*_cache);
 
604
        cachect.rectangle(*carea);
 
605
        cachect.setOperator(CAIRO_OPERATOR_SOURCE);
 
606
        cachect.setSource(&intermediate);
 
607
        cachect.fill();
 
608
        _cache->markClean(*carea);
 
609
    }
 
610
    ct.rectangle(*carea);
 
611
    ct.setSource(&intermediate);
 
612
    ct.fill();
 
613
    ct.setSource(0,0,0,0);
 
614
    // the call above is to clear a ref on the intermediate surface held by ct
 
615
 
 
616
    return render_result;
 
617
}
 
618
 
 
619
void
 
620
DrawingItem::_renderOutline(DrawingContext &ct, Geom::IntRect const &area, unsigned flags)
 
621
{
 
622
    // intersect with bbox rather than drawbox, as we want to render things outside
 
623
    // of the clipping path as well
 
624
    Geom::OptIntRect carea = Geom::intersect(area, _bbox);
 
625
    if (!carea) return;
 
626
 
 
627
    // just render everything: item, clip, mask
 
628
    // First, render the object itself
 
629
    _renderItem(ct, *carea, flags, NULL);
 
630
 
 
631
    // render clip and mask, if any
 
632
    guint32 saved_rgba = _drawing.outlinecolor; // save current outline color
 
633
    // render clippath as an object, using a different color
 
634
    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
 
635
    if (_clip) {
 
636
        _drawing.outlinecolor = prefs->getInt("/options/wireframecolors/clips", 0x00ff00ff); // green clips
 
637
        _clip->render(ct, *carea, flags);
 
638
    }
 
639
    // render mask as an object, using a different color
 
640
    if (_mask) {
 
641
        _drawing.outlinecolor = prefs->getInt("/options/wireframecolors/masks", 0x0000ffff); // blue masks
 
642
        _mask->render(ct, *carea, flags);
 
643
    }
 
644
    _drawing.outlinecolor = saved_rgba; // restore outline color
 
645
}
 
646
 
 
647
/** @brief Rasterize the clipping path.
 
648
 * This method submits drawing operations required to draw a basic filled shape
 
649
 * of the item to the supplied drawing context. Rendering is limited to the
 
650
 * given area. The rendering of the clipped object is composited into
 
651
 * the result of this call using the IN operator. See the implementation
 
652
 * of render() for details.
 
653
 */
 
654
void
 
655
DrawingItem::clip(Inkscape::DrawingContext &ct, Geom::IntRect const &area)
 
656
{
 
657
    // don't bother if the object does not implement clipping (e.g. DrawingImage)
 
658
    if (!_canClip()) return;
 
659
    if (!_visible) return;
 
660
    if (!area.intersects(_bbox)) return;
 
661
 
 
662
    // The item used as the clipping path itself has a clipping path.
 
663
    // Render this item's clipping path onto a temporary surface, then composite it
 
664
    // with the item using the IN operator
 
665
    if (_clip) {
 
666
        ct.pushAlphaGroup();
 
667
        {   Inkscape::DrawingContext::Save save(ct);
 
668
            ct.setSource(0,0,0,1);
 
669
            _clip->clip(ct, area);
 
670
        }
 
671
        ct.pushAlphaGroup();
 
672
    }
 
673
 
 
674
    // rasterize the clipping path
 
675
    _clipItem(ct, area);
 
676
    
 
677
    if (_clip) {
 
678
        ct.popGroupToSource();
 
679
        ct.setOperator(CAIRO_OPERATOR_IN);
 
680
        ct.paint();
 
681
        ct.popGroupToSource();
 
682
        ct.setOperator(CAIRO_OPERATOR_SOURCE);
 
683
        ct.paint();
 
684
    }
 
685
}
 
686
 
 
687
/** @brief Get the item under the specified point.
 
688
 * Searches the tree for the first item in the Z-order which is closer than
 
689
 * @a delta to the given point. The pick should be visual - for example
 
690
 * an object with a thick stroke should pick on the entire area of the stroke.
 
691
 * @param p Search point
 
692
 * @param delta Maximum allowed distance from the point
 
693
 * @param sticky Whether the pick should ignore visibility and sensitivity.
 
694
 *               When false, only visible and sensitive objects are considered.
 
695
 *               When true, invisible and insensitive objects can also be picked.
 
696
 */
 
697
DrawingItem *
 
698
DrawingItem::pick(Geom::Point const &p, double delta, unsigned flags)
 
699
{
 
700
    // Sometimes there's no BBOX in state, reason unknown (bug 992817)
 
701
    // I made this not an assert to remove the warning
 
702
    if (!(_state & STATE_BBOX) || !(_state & STATE_PICK))
 
703
        return NULL;
 
704
    // ignore invisible and insensitive items unless sticky
 
705
    if (!(flags & PICK_STICKY) && !(_visible && _sensitive))
 
706
        return NULL;
 
707
 
 
708
    bool outline = _drawing.outline();
 
709
 
 
710
    if (!_drawing.outline()) {
 
711
        // pick inside clipping path; if NULL, it means the object is clipped away there
 
712
        if (_clip) {
 
713
            DrawingItem *cpick = _clip->pick(p, delta, flags | PICK_AS_CLIP);
 
714
            if (!cpick) return NULL;
 
715
        }
 
716
        // same for mask
 
717
        if (_mask) {
 
718
            DrawingItem *mpick = _mask->pick(p, delta, flags);
 
719
            if (!mpick) return NULL;
 
720
        }
 
721
    }
 
722
 
 
723
    Geom::OptIntRect box = (outline || (flags & PICK_AS_CLIP)) ? _bbox : _drawbox;
 
724
    if (!box) return NULL;
 
725
 
 
726
    Geom::Rect expanded = *box;
 
727
    expanded.expandBy(delta);
 
728
 
 
729
    if (expanded.contains(p)) {
 
730
        return _pickItem(p, delta, flags);
 
731
    }
 
732
    return NULL;
 
733
}
 
734
 
 
735
/** Marks the current visual bounding box of the item for redrawing.
 
736
 * This is called whenever the object changes its visible appearance.
 
737
 * For some cases (such as setting opacity) this is enough, but for others
 
738
 * _markForUpdate() also needs to be called.
 
739
 */
 
740
void
 
741
DrawingItem::_markForRendering()
 
742
{
 
743
    bool outline = _drawing.outline();
 
744
    Geom::OptIntRect dirty = outline ? _bbox : _drawbox;
 
745
    if (!dirty) return;
 
746
 
 
747
    // dirty the caches of all parents
 
748
    DrawingItem *bkg_root = NULL;
 
749
 
 
750
    for (DrawingItem *i = this; i; i = i->_parent) {
 
751
        if (i != this && i->_filter) {
 
752
            i->_filter->area_enlarge(*dirty, i);
 
753
        }
 
754
        if (i->_cache) {
 
755
            i->_cache->markDirty(*dirty);
 
756
        }
 
757
        if (i->_background_accumulate) {
 
758
            bkg_root = i;
 
759
        }
 
760
    }
 
761
    
 
762
    if (bkg_root) {
 
763
        bkg_root->_invalidateFilterBackground(*dirty);
 
764
    }
 
765
    _drawing.signal_request_render.emit(*dirty);
 
766
}
 
767
 
 
768
void
 
769
DrawingItem::_invalidateFilterBackground(Geom::IntRect const &area)
 
770
{
 
771
    if (!_drawbox.intersects(area)) return;
 
772
 
 
773
    if (_cache && _filter && _filter->uses_background()) {
 
774
        _cache->markDirty(area);
 
775
    }
 
776
 
 
777
    for (ChildrenList::iterator i = _children.begin(); i != _children.end(); ++i) {
 
778
        i->_invalidateFilterBackground(area);
 
779
    }
 
780
}
 
781
 
 
782
/** @brief Marks the item as needing a recomputation of internal data.
 
783
 *
 
784
 * This mechanism avoids traversing the entire rendering tree (which could be vast)
 
785
 * on every trivial state changed in any item. Only items marked as needing
 
786
 * an update (having some bits in their _state unset) will be traversed
 
787
 * during the update call.
 
788
 *
 
789
 * The _propagate variable is another optimization. We use it to specify that
 
790
 * all children should also have the corresponding flags unset before checking
 
791
 * whether they need to be traversed. This way there is one less traversal
 
792
 * of the tree. Without this we would need to unset state bits in all children.
 
793
 * With _propagate we do this during the update call, when we have to traverse
 
794
 * the tree anyway.
 
795
 */
 
796
void
 
797
DrawingItem::_markForUpdate(unsigned flags, bool propagate)
 
798
{
 
799
    if (propagate) {
 
800
        _propagate_state |= flags;
 
801
    }
 
802
 
 
803
    if (_state & flags) {
 
804
        _state &= ~flags;
 
805
        if (_parent) {
 
806
            _parent->_markForUpdate(flags, false);
 
807
        } else {
 
808
            _drawing.signal_request_update.emit(this);
 
809
        }
 
810
    }
 
811
}
 
812
 
 
813
void
 
814
DrawingItem::_setStyleCommon(SPStyle *&_style, SPStyle *style)
 
815
{
 
816
    if (style) sp_style_ref(style);
 
817
    if (_style) sp_style_unref(_style);
 
818
    _style = style;
 
819
 
 
820
    // if group has a filter
 
821
    if (style->filter.set && style->getFilter()) {
 
822
        if (!_filter) {
 
823
            int primitives = sp_filter_primitive_count(SP_FILTER(style->getFilter()));
 
824
            _filter = new Inkscape::Filters::Filter(primitives);
 
825
        }
 
826
        sp_filter_build_renderer(SP_FILTER(style->getFilter()), _filter);
 
827
    } else {
 
828
        // no filter set for this group
 
829
        delete _filter;
 
830
        _filter = NULL;
 
831
    }
 
832
 
 
833
    if (style && style->enable_background.set) {
 
834
        if (style->enable_background.value == SP_CSS_BACKGROUND_NEW && !_background_new) {
 
835
            _background_new = true;
 
836
            _markForUpdate(STATE_BACKGROUND, true);
 
837
        } else if (style->enable_background.value == SP_CSS_BACKGROUND_ACCUMULATE && _background_new) {
 
838
            _background_new = false;
 
839
            _markForUpdate(STATE_BACKGROUND, true);
 
840
        }
 
841
    }
 
842
 
 
843
    _markForUpdate(STATE_ALL, false);
 
844
}
 
845
 
 
846
/** @brief Compute the caching score.
 
847
 *
 
848
 * Higher scores mean the item is more aggresively prioritized for automatic
 
849
 * caching by Inkscape::Drawing. */
 
850
double
 
851
DrawingItem::_cacheScore()
 
852
{
 
853
    Geom::OptIntRect cache_rect = _cacheRect();
 
854
    if (!cache_rect) return -1.0;
 
855
 
 
856
    // a crude first approximation:
 
857
    // the basic score is the number of pixels in the drawbox
 
858
    double score = cache_rect->area();
 
859
    // this is multiplied by the filter complexity and its expansion
 
860
    if (_filter &&_drawing.renderFilters()) {
 
861
        score *= _filter->complexity(_ctm);
 
862
        Geom::IntRect ref_area = Geom::IntRect::from_xywh(0, 0, 16, 16);
 
863
        Geom::IntRect test_area = ref_area;
 
864
        Geom::IntRect limit_area(0, INT_MIN, 16, INT_MAX);
 
865
        _filter->area_enlarge(test_area, this);
 
866
        // area_enlarge never shrinks the rect, so the result of intersection below
 
867
        // must be non-empty
 
868
        score *= double((test_area & limit_area)->area()) / ref_area.area();
 
869
    }
 
870
    // if the object is clipped, add 1/2 of its bbox pixels
 
871
    if (_clip && _clip->_bbox) {
 
872
        score += _clip->_bbox->area() * 0.5;
 
873
    }
 
874
    // if masked, add mask score
 
875
    if (_mask) {
 
876
        score += _mask->_cacheScore();
 
877
    }
 
878
    //g_message("caching score: %f", score);
 
879
    return score;
 
880
}
 
881
 
 
882
Geom::OptIntRect
 
883
DrawingItem::_cacheRect()
 
884
{
 
885
    Geom::OptIntRect r = _drawbox & _drawing.cacheLimit();
 
886
    return r;
 
887
}
 
888
 
 
889
} // end namespace Inkscape
 
890
 
 
891
/*
 
892
  Local Variables:
 
893
  mode:c++
 
894
  c-file-style:"stroustrup"
 
895
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
 
896
  indent-tabs-mode:nil
 
897
  fill-column:99
 
898
  End:
 
899
*/
 
900
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :