Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
drawing-item.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
6 * Authors:
7 * Krzysztof KosiƄski <tweenk.pl@gmail.com>
8 *
9 * Copyright (C) 2011 Authors
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include <climits>
14
21#include "display/drawing.h"
22
23#include "display/cairo-utils.h"
25
27#include "ui/widget/canvas.h" // Mark area for redrawing.
28
29#include "nr-filter.h"
30#include "style.h"
31
32#include "object/sp-item.h"
33
34static constexpr auto CACHE_SCORE_THRESHOLD = 50000.0;
35
36namespace Inkscape {
37
38struct CacheData
39{
40 mutable std::mutex mutables;
41 mutable std::optional<DrawingCache> surface;
42};
43
65 : _drawing(drawing)
66 , _parent(nullptr)
67 , _key(0)
68 , _style(nullptr)
69 , _context_style(nullptr)
70 , _contains_unisolated_blend(false)
71 , style_vector_effect_size(false)
72 , style_vector_effect_rotate(false)
73 , style_vector_effect_fixed(false)
74 , _opacity(1.0)
75 , _clip(nullptr)
76 , _mask(nullptr)
77 , _fill_pattern(nullptr)
78 , _stroke_pattern(nullptr)
79 , _item(nullptr)
80 , _state(0)
81 , _child_type(ChildType::ORPHAN)
82 , _background_new(0)
83 , _background_accumulate(0)
84 , _visible(true)
85 , _sensitive(true)
86 , _cached_persistent(0)
87 , _has_cache_iterator(0)
88 , _propagate_state(0)
89 , _pick_children(0)
90 , _antialias(Antialiasing::Good)
91 , _isolation(SP_CSS_ISOLATION_AUTO)
92 , _blend_mode(SP_CSS_BLEND_NORMAL)
93{
94}
95
97{
98 // Unactivate if active.
99 if (auto itemdrawing = _drawing.getCanvasItemDrawing()) {
100 if (itemdrawing->get_active() == this) {
101 itemdrawing->set_active(nullptr);
102 }
103 } else {
104 // Typically happens, e.g. for any non-Canvas Drawing.
105 }
106
107 // Remove caching candidate entry.
110 }
111
112 // Remove from the set of cached items and delete cache.
113 _setCached(false, true);
114
115 _children.clear_and_dispose([] (auto c) { delete c; });
116 delete _clip;
117 delete _mask;
118 delete static_cast<DrawingItem*>(_fill_pattern);
119 delete static_cast<DrawingItem*>(_stroke_pattern);
120}
121
124{
125 for (auto c = item->_parent; c; c = c->_parent) {
126 if (c == this) return true;
127 }
128 return false;
129}
130
132{
134 return true;
135 } else if (_mask || _filter || _opacity < 0.995 || _isolation == SP_CSS_ISOLATION_ISOLATE) {
136 return false;
137 } else {
139 }
140}
141
143{
144 // Ok to perform non-deferred modification of child, because not part of rendering tree yet.
145 assert(item->_child_type == ChildType::ORPHAN);
146 item->_parent = this;
147 item->_child_type = ChildType::NORMAL;
148
149 defer([=, this] {
150 _children.push_back(*item);
151
152 // This ensures that _markForUpdate() called on the child will recurse to this item
153 item->_state = STATE_ALL;
154 // Because _markForUpdate recurses through ancestors, we can simply call it
155 // on the just-added child. This has the additional benefit that we do not
156 // rely on the appended child being in the default non-updated state.
157 // We set propagate to true, because the child might have descendants of its own.
158 item->_markForUpdate(STATE_ALL, true);
159 });
160}
161
163{
164 // See appendChild for explanations.
165 assert(item->_child_type == ChildType::ORPHAN);
166 item->_parent = this;
167 item->_child_type = ChildType::NORMAL;
168
169 defer([=, this] {
170 _children.push_front(*item);
171 item->_state = STATE_ALL;
172 item->_markForUpdate(STATE_ALL, true);
173 });
174}
175
176// Clear this node's ordinary children, deleting them and their descendants without otherwise changing them in any way.
178{
179 defer([=, this] {
180 if (_children.empty()) return;
182 _children.clear_and_dispose([] (auto c) { delete c; });
184 });
185}
186
188{
189 defer([=, this] {
190 auto constexpr EPS = 1e-18;
192 if (Geom::are_near(transform, current, EPS)) return;
193
195 _transform = transform.isIdentity(EPS) ? nullptr : std::make_unique<Geom::Affine>(transform);
197 });
198}
199
200void DrawingItem::setOpacity(float opacity)
201{
202 defer([=, this] {
203 if (opacity == _opacity) return;
204 _opacity = opacity;
206 });
207}
208
210{
211 defer([=, this] {
212 if (_antialias == antialias) return;
213 _antialias = antialias;
215 });
216}
217
218void DrawingItem::setIsolation(bool isolation)
219{
220 defer([=, this] {
221 if (isolation == _isolation) return;
222 _isolation = isolation;
224 });
225}
226
228{
229 defer([=, this] {
230 if (blend_mode == _blend_mode) return;
231 _blend_mode = blend_mode;
233 });
234}
235
237{
238 defer([=, this] {
239 if (visible == _visible) return;
242 });
243}
244
245void DrawingItem::setSensitive(bool sensitive)
246{
247 defer([=, this] { // Must be deferred, since in bitfield.
249 });
250}
251
256void DrawingItem::_setCached(bool cached, bool persistent)
257{
258 static bool const cache_env = getenv("_INKSCAPE_DISABLE_CACHE");
259 if (cache_env) {
260 return;
261 }
262
263 if (persistent) {
264 _cached_persistent = cached && persistent;
265 } else if (_cached_persistent) {
266 return;
267 }
268
269 if (cached == (bool)_cache) {
270 return;
271 }
272
273 if (cached) {
274 _cache = std::make_unique<CacheData>();
275 _drawing._cached_items.insert(this);
276 } else {
277 _cache.reset();
278 _drawing._cached_items.erase(this);
279 }
280}
281
287void DrawingItem::setStyle(SPStyle const *style, SPStyle const *context_style)
288{
289 // Ok to not defer setting the style pointer, because the pointer itself is only read by SPObject-side code.
290 _style = style;
291 if (context_style) {
292 _context_style = context_style;
293 } else if (_parent) {
295 }
296
297 // Copy required information out of style.
298 bool background_new = false;
299 bool vector_effect_size = false;
300 bool vector_effect_rotate = false;
301 bool vector_effect_fixed = false;
302 if (style) {
303 background_new = style->enable_background.set && style->enable_background.value == SP_CSS_BACKGROUND_NEW;
304 vector_effect_size = _style->vector_effect.size;
305 vector_effect_rotate = _style->vector_effect.rotate;
306 vector_effect_fixed = _style->vector_effect.fixed;
307 }
308
309 // Defer setting the style information on the DrawingItem.
310 defer([=, this] {
312
313 if (background_new != _background_new) {
314 _background_new = background_new;
316 }
317
318 style_vector_effect_size = vector_effect_size;
319 style_vector_effect_rotate = vector_effect_rotate;
320 style_vector_effect_fixed = vector_effect_fixed;
321
323 });
324}
325
333void DrawingItem::setChildrenStyle(SPStyle const *context_style)
334{
335 _context_style = context_style;
336 for (auto &i : _children) {
337 i.setChildrenStyle(context_style);
338 }
339}
340
342{
343 if (item) {
344 assert(item->_child_type == ChildType::ORPHAN);
345 item->_parent = this;
346 item->_child_type = ChildType::CLIP;
347 }
348
349 defer([=, this] {
351 delete _clip;
352 _clip = item;
354 });
355}
356
358{
359 if (item) {
360 assert(item->_child_type == ChildType::ORPHAN);
361 item->_parent = this;
362 item->_child_type = ChildType::MASK;
363 }
364
365 defer([=, this] {
367 delete _mask;
368 _mask = item;
370 });
371}
372
374{
375 if (pattern) {
376 assert(pattern->_child_type == ChildType::ORPHAN);
377 pattern->_parent = this;
378 pattern->_child_type = ChildType::FILL;
379 }
380
381 defer([=, this] {
383 delete static_cast<DrawingItem*>(_fill_pattern);
384 _fill_pattern = pattern;
386 });
387}
388
390{
391 if (pattern) {
392 assert(pattern->_child_type == ChildType::ORPHAN);
393 pattern->_parent = this;
395 }
396
397 defer([=, this] {
399 delete static_cast<DrawingItem*>(_stroke_pattern);
400 _stroke_pattern = pattern;
402 });
403}
404
406void DrawingItem::setZOrder(unsigned zorder)
407{
408 if (_child_type != ChildType::NORMAL) return;
409
410 defer([=, this] {
411 auto it = _parent->_children.iterator_to(*this);
412 _parent->_children.erase(it);
413
414 auto it2 = _parent->_children.begin();
415 std::advance(it2, std::min<unsigned>(zorder, _parent->_children.size()));
416 _parent->_children.insert(it2, *this);
418 });
419}
420
422{
423 defer([=, this] {
425 });
426}
427
428void DrawingItem::setFilterRenderer(std::unique_ptr<Filters::Filter> filter)
429{
430 defer([=, this, filter = std::move(filter)] () mutable {
431 _filter = std::move(filter);
433 });
434}
435
458void DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
459{
460 // We don't need to update what is not visible
461 if (!_visible) {
462 _state = STATE_ALL; // Touch the state for future change to this item
463 return;
464 }
465
467 bool const filters = _drawing.renderMode() != RenderMode::NO_FILTERS;
468 bool const forcecache = _filter && filters;
469
470 // Set reset flags according to propagation status
471 reset |= _propagate_state;
473
474 _state &= ~reset; // reset state of this item
475
476 if ((~_state & flags) == 0) return; // nothing to do
477
478 // TODO this might be wrong
479 if (_state & STATE_BBOX) {
480 // we have up-to-date bbox
481 if (!area.intersects(outline ? _bbox : _drawbox)) return;
482 }
483
484 // compute which elements need an update
485 unsigned to_update = _state ^ flags;
486
487 // this needs to be called before we recurse into children
488 if (to_update & STATE_BACKGROUND) {
492 }
493
494 UpdateContext child_ctx(ctx);
495 if (_transform) {
496 child_ctx.ctm = *_transform * ctx.ctm;
497 }
498
499 // Vector effects
501 child_ctx.ctm.setTranslation(Geom::Point(0, 0));
502 }
503
505 double value = child_ctx.ctm.descrim();
506 if (value > 0.0) {
507 child_ctx.ctm[0] /= value;
508 child_ctx.ctm[1] /= value;
509 child_ctx.ctm[2] /= value;
510 child_ctx.ctm[3] /= value;
511 }
512 }
513
515 double value = child_ctx.ctm.descrim();
516 child_ctx.ctm[0] = value;
517 child_ctx.ctm[1] = 0.0;
518 child_ctx.ctm[2] = 0.0;
519 child_ctx.ctm[3] = value;
520 }
521
522 // Remember the transformation matrix.
523 Geom::Affine ctm_change;
524 bool affine_changed = false;
525 if (!Geom::are_near(_ctm, child_ctx.ctm)) {
526 ctm_change = _ctm.inverse() * child_ctx.ctm;
527 affine_changed = true;
528 }
529 _ctm = child_ctx.ctm;
530
531 bool const totally_invalidated = reset & STATE_TOTAL_INV;
532 if (totally_invalidated) {
533 // Perform work that would have been done by our call to _markForRendering(),
534 // had it not been overshadowed by a totally-invalidating node.
535 if (_cache && _cache->surface) {
536 _cache->surface->markDirty();
537 }
539 }
540
541 // Decide whether this node should be a totally-invalidating node.
542 bool const totally_invalidate = _update_complexity >= 20 && affine_changed;
543 if (totally_invalidate) {
544 reset |= STATE_TOTAL_INV;
545 }
546
547 // Recalculate update complexity; to be recalculated immediately below and by _updateItem().
549 auto add_complexity_if = [&] (DrawingItem *c) {
550 if (c) {
551 _update_complexity += c->_update_complexity;
552 }
553 };
554 add_complexity_if(_clip);
555 add_complexity_if(_mask);
556 add_complexity_if(_fill_pattern);
557 add_complexity_if(_stroke_pattern);
558
559 // Reset contains_unisolated_blend; to be recalculated by _updateItem().
561
562 // Moved from code that was previously in render().
563 if (forcecache) {
564 _setCached((bool)_cacheRect(), true);
565 }
566
567 // update _bbox and call this function for children
568 _state = _updateItem(area, child_ctx, flags, reset);
569
570 // update drawingitems contained in filter
571 if (_filter) {
572 _filter->update();
573 }
574
575 if (to_update & STATE_BBOX) {
576 // compute drawbox
577 if (_filter && filters) {
578 Geom::OptRect enlarged = _filter->filter_effect_area(_item_bbox);
579 if (enlarged) {
580 *enlarged *= ctm();
581 _drawbox = enlarged->roundOutwards();
582 } else {
584 }
585 } else {
586 _drawbox = _bbox;
587 }
588
589 // Clipping
590 if (_clip) {
591 _clip->update(area, child_ctx, flags, reset);
592 if (outline) {
594 } else {
596 }
597 }
598 // Masking
599 if (_mask) {
600 _mask->update(area, child_ctx, flags, reset);
601 if (outline) {
603 } else {
604 // for masking, we need full drawbox of mask
606 }
607 }
608 // Crude fix for outline overlay bbox issues with filtered objects.
609 // (Real solution is to carefully review all bbox/drawbox uses.)
610 if (_drawing.outlineOverlay()) {
611 _bbox |= _drawbox;
612 }
613 }
614 if (to_update & STATE_CACHE) {
615 // Remove old cache iterator.
618 _has_cache_iterator = false;
619 }
620
621 // Determine whether this item is cachable.
622 bool isolated = _mask || _filter || _opacity < 0.995
626 bool cacheable = !_contains_unisolated_blend || isolated;
627
628 // Determine whether to make this item eligible for caching, by creating a cache iterator.
629 double score = _cacheScore();
630 if (score >= CACHE_SCORE_THRESHOLD && cacheable) {
631 CacheRecord cr;
632 cr.score = score;
633 // if _cacheRect() is empty, a negative score will be returned from _cacheScore(),
634 // so this will not execute (cache score threshold must be positive)
635 cr.cache_size = _cacheRect()->area() * 4;
636 cr.item = this;
637 auto it = std::lower_bound(_drawing._candidate_items.begin(), _drawing._candidate_items.end(), cr, std::greater<CacheRecord>());
639 _has_cache_iterator = true;
640 }
641
642 /* Update cache if enabled.
643 * General note: here we only tell the cache how it has to transform
644 * during the render phase. The transformation is deferred because
645 * after the update the item can have its caching turned off,
646 * e.g. because its filter was removed. This way we avoid temporarily
647 * using more memory than the cache budget */
648 if (_cache && _cache->surface) {
650 if (_visible && cl && _has_cache_iterator) { // never create cache for invisible items
651 // this takes care of invalidation on transform
652 _cache->surface->scheduleTransform(*cl, ctm_change);
653 } else {
654 // Destroy cache for this item - outside of canvas or invisible.
655 // The opposite transition (invisible -> visible or object
656 // entering the canvas) is handled during the render phase
657 _setCached(false, true);
658 }
659 }
660 }
661
662 if (to_update & STATE_RENDER) {
663 // now that we know drawbox, dirty the corresponding rect on canvas
664 // unless filtered, groups do not need to render by themselves, only their members
665 if (_fill_pattern) {
666 _fill_pattern->update(area, child_ctx, flags, reset);
667 }
668 if (_stroke_pattern) {
669 _stroke_pattern->update(area, child_ctx, flags, reset);
670 }
671 if (!totally_invalidated) {
672 if (!is<DrawingGroup>(this) || (_filter && filters) || totally_invalidate) {
674 }
675 }
676 }
677}
678
679struct MaskLuminanceToAlpha
680{
681 guint32 operator()(guint32 in)
682 {
683 guint r = 0, g = 0, b = 0;
684 Display::ExtractRGB32(in, r, g, b);
685 // the operation of unpremul -> luminance-to-alpha -> multiply by alpha
686 // is equivalent to luminance-to-alpha on premultiplied color values
687 // original computation in double: r*0.2125 + g*0.7154 + b*0.0721
688 guint32 ao = r*109 + g*366 + b*37; // coeffs add up to 512
689 return ((ao + 256) << 15) & 0xff000000; // equivalent to ((ao + 256) / 512) << 24
690 }
691};
692
703unsigned DrawingItem::render(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, unsigned flags, DrawingItem const *stop_at) const
704{
705 bool const outline = flags & RENDER_OUTLINE;
706 bool const render_filters = !(flags & RENDER_NO_FILTERS);
707 bool const forcecache = _filter && render_filters;
708
709 // stop_at is handled in DrawingGroup, but this check is required to handle the case
710 // where a filtered item with background-accessing filter has enable-background: new
711 if (this == stop_at) {
712 return RENDER_STOP;
713 }
714
715 // If we are invisible, return immediately
716 if (!_visible) {
717 return RENDER_OK;
718 }
719
720 if (_ctm.isSingular(1e-18)) {
721 return RENDER_OK;
722 }
723
724 // TODO convert outline rendering to a separate virtual function
725 if (outline) {
726 _renderOutline(dc, rc, area, flags);
727 return RENDER_OK;
728 }
729
730 Geom::OptIntRect carea = area & _drawbox;
731 if (!carea) {
732 return RENDER_OK;
733 }
734
735 Geom::OptIntRect iarea = carea;
736 // expand carea to contain the dependent area of filters.
737 if (forcecache) {
738 iarea = _cacheRect();
739 if (!iarea) {
740 iarea = carea;
741 _filter->area_enlarge(*iarea, this);
742 iarea.intersectWith(_drawbox);
743 }
744 }
745 // carea is the area to paint
746 carea = iarea & _drawbox;
747 if (!carea) {
748 return RENDER_OK;
749 }
750
751 // Device scale for HiDPI screens (typically 1 or 2)
752 int const device_scale = dc.surface()->device_scale();
753
754 std::unique_lock<std::mutex> lock;
755
756 // Render from cache if possible, unless requested not to (hatches).
757 if (_cache && !(flags & RENDER_BYPASS_CACHE)) {
758 lock = std::unique_lock(_cache->mutables);
759
760 if (_cache->surface) {
761 if (_cache->surface->device_scale() != device_scale) {
762 _cache->surface->markDirty();
763 }
764 _cache->surface->prepare();
766 _cache->surface->paintFromCache(dc, carea, forcecache);
767 if (!carea) {
768 dc.setSource(0, 0, 0, 0);
769 return RENDER_OK;
770 }
771 } else {
772 // There is no cache. This could be because caching of this item
773 // was just turned on after the last update phase, or because
774 // we were previously outside of the canvas.
776 if (!cl)
777 cl = carea;
778 _cache->surface.emplace(*cl, device_scale);
779 }
780
781 if (!forcecache) {
782 lock.unlock(); // Only hold the lock for the full duration of rendering for filters.
783 }
784 } else {
785 // if our caching was turned off after the last update, it was already deleted in setCached()
786 }
787
788 // determine whether this shape needs intermediate rendering.
789 bool const greyscale = _drawing.colorMode() == ColorMode::GRAYSCALE && !(flags & RENDER_OUTLINE);
790 bool const isolate_root = _contains_unisolated_blend || greyscale;
791 bool const needs_intermediate_rendering =
792 _clip // 1. it has a clipping path
793 || _mask // 2. it has a mask
794 || (_filter && render_filters) // 3. it has a filter
795 || _opacity < 0.995 // 4. it is non-opaque
796 || _blend_mode != SP_CSS_BLEND_NORMAL // 5. it has blend mode
797 || _isolation == SP_CSS_ISOLATION_ISOLATE // 6. it is isolated
798 || (_child_type == ChildType::ROOT && isolate_root) // 7. it is the root and needs isolation
799 || (bool)_cache; // 8. it is to be cached
800
801 auto antialias = rc.antialiasing_override.value_or(_antialias);
802
803 /* How the rendering is done.
804 *
805 * Clipping, masking and opacity are done by rendering them to a surface
806 * and then compositing the object's rendering onto it with the IN operator.
807 * The object itself is rendered to a group.
808 *
809 * Opacity is done by rendering the clipping path with an alpha
810 * value corresponding to the opacity. If there is no clipping path,
811 * the entire intermediate surface is painted with alpha corresponding
812 * to the opacity value.
813 *
814 */
815 // Short-circuit the simple case.
816 // We also use this path for filter background rendering, because masking, clipping,
817 // filters and opacity do not apply when rendering the ancestors of the filtered
818 // element
819
820 if ((flags & RENDER_FILTER_BACKGROUND) || !needs_intermediate_rendering) {
822 apply_antialias(dc, antialias);
823 return _renderItem(dc, rc, *carea, flags & ~RENDER_FILTER_BACKGROUND, stop_at);
824 }
825
826 DrawingSurface intermediate(*carea, device_scale);
827 DrawingContext ict(intermediate);
828 cairo_set_antialias(ict.raw(), cairo_get_antialias(dc.raw())); // propagate antialias setting
829
830 // This path fails for patterns/hatches when stepping the pattern to handle overflows.
831 // The offsets are applied to drawing context (dc) but they are not copied to the
832 // intermediate context. Something like this is needed:
833 // Copy cairo matrix from dc to intermediate, needed for patterns/hatches
834 // cairo_matrix_t cairo_matrix;
835 // cairo_get_matrix(dc.raw(), &cairo_matrix);
836 // cairo_set_matrix(ict.raw(), &cairo_matrix);
837 // For the moment we disable caching for patterns,
838 // see https://gitlab.com/inkscape/inkscape/-/issues/309
839
840 unsigned render_result = RENDER_OK;
841
842 // 1. Render clipping path with alpha = opacity.
843 ict.setSource(0,0,0,_opacity);
844 // Since clip can be combined with opacity, the result could be incorrect
845 // for overlapping clip children. To fix this we use the SOURCE operator
846 // instead of the default OVER.
847 ict.setOperator(CAIRO_OPERATOR_SOURCE);
848 ict.paint();
849 if (_clip) {
850 ict.pushGroup();
851 _clip->clip(ict, rc, *carea);
852 ict.popGroupToSource();
853 ict.setOperator(CAIRO_OPERATOR_IN);
854 ict.paint();
855 }
856 ict.setOperator(CAIRO_OPERATOR_OVER); // reset back to default
857
858 // 2. Render the mask if present and compose it with the clipping path + opacity.
859 if (_mask) {
860 ict.pushGroup();
861 _mask->render(ict, rc, *carea, flags);
862
863 cairo_surface_t *mask_s = ict.rawTarget();
864 // Convert mask's luminance to alpha
865 ink_cairo_surface_filter(mask_s, mask_s, MaskLuminanceToAlpha());
866 ict.popGroupToSource();
867 ict.setOperator(CAIRO_OPERATOR_IN);
868 ict.paint();
869 ict.setOperator(CAIRO_OPERATOR_OVER);
870 }
871
872 // 3. Render object itself
873 ict.pushGroup();
874 apply_antialias(ict, antialias);
875 render_result = _renderItem(ict, rc, *carea, flags, stop_at);
876
877 // 4. Apply filter.
878 if (_filter && render_filters) {
879 bool rendered = false;
880 if (_filter->uses_background() && _background_accumulate) {
881 auto bg_root = this;
882 for (; bg_root; bg_root = bg_root->_parent) {
883 if (bg_root->_background_new || bg_root->_filter) break;
884 }
885 if (bg_root) {
886 DrawingSurface bg(*carea, device_scale);
887 DrawingContext bgdc(bg);
888 bg_root->render(bgdc, rc, *carea, flags | RENDER_FILTER_BACKGROUND, this);
889 _filter->render(this, ict, &bgdc, rc);
890 rendered = true;
891 }
892 }
893 if (!rendered) {
894 _filter->render(this, ict, nullptr, rc);
895 }
896 // Note that because the object was rendered to a group,
897 // the internals of the filter need to use cairo_get_group_target()
898 // instead of cairo_get_target().
899 }
900
901 // 4b. Apply greyscale rendering mode, if root node.
902 if (greyscale && _child_type == ChildType::ROOT) {
904 }
905
906 // 5. Render object inside the composited mask + clip
907 ict.popGroupToSource();
908 ict.setOperator(CAIRO_OPERATOR_IN);
909 ict.paint();
910
911 // 6. Paint the completed rendering onto the base context (or into cache)
912 if (_cache && !(flags & RENDER_BYPASS_CACHE)) {
913 if (!forcecache) {
914 lock.lock(); // Only hold the lock for the full duration of rendering for filters.
915 }
916 assert(lock);
917 assert(_cache->surface);
918
919 auto cachect = DrawingContext(*_cache->surface);
920 cachect.rectangle(*carea);
921 cachect.setOperator(CAIRO_OPERATOR_SOURCE);
922 cachect.setSource(&intermediate);
923 cachect.fill();
924 _cache->surface->markClean(*carea);
925 }
926
927 dc.rectangle(*carea);
928 dc.setSource(&intermediate);
929
930 // 7. Render blend mode
932 dc.fill();
933 dc.setSource(0,0,0,0);
934 // Web isolation only works if parent doesn't have transform
935
936 // the call above is to clear a ref on the intermediate surface held by dc
937
938 return render_result;
939}
940
944unsigned DrawingItem::render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags) const
945{
946 auto rc = RenderContext{
947 .outline_color = 0xff,
948 .antialiasing_override = _drawing._antialiasing_override,
949 .dithering = _drawing._use_dithering
950 };
951 return render(dc, rc, area, flags);
952}
953
954void DrawingItem::_renderOutline(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, unsigned flags) const
955{
956 // intersect with bbox rather than drawbox, as we want to render things outside
957 // of the clipping path as well
958 auto carea = Geom::intersect(area, _bbox);
959 if (!carea) return;
960
961 // just render everything: item, clip, mask
962 // First, render the object itself
963 _renderItem(dc, rc, *carea, flags, nullptr);
964
965 // render clip and mask, if any
966 auto saved_rgba = rc.outline_color; // save current outline color
967 // render clippath as an object, using a different color
968 if (_clip) {
969 rc.outline_color = _drawing.clipOutlineColor();
970 _clip->render(dc, rc, *carea, flags);
971 }
972 // render mask as an object, using a different color
973 if (_mask) {
974 rc.outline_color = _drawing.maskOutlineColor();
975 _mask->render(dc, rc, *carea, flags);
976 }
977 rc.outline_color = saved_rgba; // restore outline color
978}
979
989{
990 // don't bother if the object does not implement clipping (e.g. DrawingImage)
991 if (!_canClip()) return;
992 if (!_visible) return;
993 if (!area.intersects(_bbox)) return;
994
995 dc.setSource(0,0,0,1);
996 dc.pushGroup();
997 // rasterize the clipping path
998 _clipItem(dc, rc, area);
999 if (_clip) {
1000 // The item used as the clipping path itself has a clipping path.
1001 // Render this item's clipping path onto a temporary surface, then composite it
1002 // with the item using the IN operator
1003 dc.pushGroup();
1004 _clip->clip(dc, rc, area);
1005 dc.popGroupToSource();
1006 dc.setOperator(CAIRO_OPERATOR_IN);
1007 dc.paint();
1008 }
1009 dc.popGroupToSource();
1010 dc.setOperator(CAIRO_OPERATOR_OVER);
1011 dc.paint();
1012 dc.setSource(0,0,0,0);
1013}
1014
1026DrawingItem *DrawingItem::pick(Geom::Point const &p, double delta, unsigned flags)
1027{
1028 // Sometimes there's no BBOX in state, reason unknown (bug 992817)
1029 // I made this not an assert to remove the warning
1030 if (!(_state & STATE_BBOX) || !(_state & STATE_PICK)) {
1031 g_warning("Invalid state when picking: STATE_BBOX = %d, STATE_PICK = %d", _state & STATE_BBOX, _state & STATE_PICK);
1032 return nullptr;
1033 }
1034 // ignore invisible and insensitive items unless sticky
1035 if (!(flags & PICK_STICKY) && !(_visible && _sensitive)) {
1036 return nullptr;
1037 }
1038
1039 bool outline = flags & PICK_OUTLINE;
1040
1041 if (!outline) {
1042 // pick inside clipping path; if NULL, it means the object is clipped away there
1043 if (_clip) {
1044 DrawingItem *cpick = _clip->pick(p, delta, flags | PICK_AS_CLIP);
1045 if (!cpick) {
1046 return nullptr;
1047 }
1048 }
1049 // same for mask
1050 if (_mask) {
1051 DrawingItem *mpick = _mask->pick(p, delta, flags);
1052 if (!mpick) {
1053 return nullptr;
1054 }
1055 }
1056 }
1057
1058 Geom::OptIntRect box = outline || (flags & PICK_AS_CLIP) ? _bbox : _drawbox;
1059 if (!box) {
1060 return nullptr;
1061 }
1062
1063 Geom::Rect expanded = *box;
1064 expanded.expandBy(delta);
1065 auto dglyps = cast<DrawingGlyphs>(this);
1066 if (dglyps && !(flags & PICK_AS_CLIP)) {
1067 expanded = dglyps->getPickBox();
1068 }
1069
1070 if (expanded.contains(p)) {
1071 return _pickItem(p, delta, flags);
1072 }
1073 return nullptr;
1074}
1075
1076// For debugging
1077Glib::ustring DrawingItem::name() const
1078{
1079 if (_item) {
1080 if (_item->getId())
1081 return _item->getId();
1082 else
1083 return "No object id";
1084 } else {
1085 return "No associated object";
1086 }
1087}
1088
1089// For debugging: Print drawing tree structure.
1090void DrawingItem::recursivePrintTree(unsigned level) const
1091{
1092 if (level == 0) {
1093 std::cout << "Display Item Tree" << std::endl;
1094 }
1095 std::cout << "DI: ";
1096 for (int i = 0; i < level; i++) {
1097 std::cout << " ";
1098 }
1099 std::cout << name() << std::endl;
1100 for (auto &i : _children) {
1101 i.recursivePrintTree(level + 1);
1102 }
1103}
1104
1112{
1115 if (!dirty) return;
1116
1117 // dirty the caches of all parents
1118 DrawingItem *bkg_root = nullptr;
1119
1120 for (auto i = this; i; i = i->_parent) {
1121 if (i != this && i->_filter) {
1122 i->_filter->area_enlarge(*dirty, i);
1123 }
1124 if (i->_cache && i->_cache->surface) {
1125 i->_cache->surface->markDirty(*dirty);
1126 }
1127 i->_dropPatternCache();
1128 if (i->_background_accumulate) {
1129 bkg_root = i;
1130 }
1131 }
1132
1133 if (bkg_root && bkg_root->_parent && bkg_root->_parent->_parent) {
1134 bkg_root->_invalidateFilterBackground(*dirty);
1135 }
1136
1137 if (auto canvasitem = drawing().getCanvasItemDrawing()) {
1138 canvasitem->get_canvas()->redraw_area(*dirty);
1139 }
1140}
1141
1143{
1144 if (!_drawbox.intersects(area)) return;
1145
1146 if (_cache && _cache->surface && _filter && _filter->uses_background()) {
1147 _cache->surface->markDirty(area);
1148 }
1149
1150 for (auto & i : _children) {
1151 i._invalidateFilterBackground(area);
1152 }
1153}
1154
1170void DrawingItem::_markForUpdate(unsigned flags, bool propagate)
1171{
1172 if (propagate) {
1173 _propagate_state |= flags;
1174 }
1175
1176 if (_state & flags) {
1177 unsigned oldstate = _state;
1178 _state &= ~flags;
1179 if (oldstate != _state && _parent) {
1180 // If we actually reset anything in state, recurse on the parent.
1181 _parent->_markForUpdate(flags, false);
1182 } else {
1183 // If nothing changed, it means our ancestors are already invalidated
1184 // up to the root. Do not bother recursing, because it won't change anything.
1185 // Also do this if we are the root item, because we have no more ancestors
1186 // to invalidate.
1187 if (drawing().getCanvasItemDrawing()) {
1189 } else {
1190 // Typically happens, e.g. for any non-Canvas Drawing.
1191 }
1192 }
1193 }
1194}
1195
1203{
1204 Geom::OptIntRect cache_rect = _cacheRect();
1205 if (!cache_rect) return -1.0;
1206 // a crude first approximation:
1207 // the basic score is the number of pixels in the drawbox
1208 double score = cache_rect->area();
1209 // this is multiplied by the filter complexity and its expansion
1211 score *= _filter->complexity(_ctm);
1212 Geom::IntRect ref_area = Geom::IntRect::from_xywh(0, 0, 16, 16);
1213 Geom::IntRect test_area = ref_area;
1214 Geom::IntRect limit_area(0, INT_MIN, 16, INT_MAX);
1215 _filter->area_enlarge(test_area, this);
1216 // area_enlarge never shrinks the rect, so the result of intersection below must be non-empty
1217 score *= (double)(test_area & limit_area)->area() / ref_area.area();
1218 }
1219 // if the object is clipped, add 1/2 of its bbox pixels
1220 if (_clip && _clip->_bbox) {
1221 score += _clip->_bbox->area() * 0.5;
1222 }
1223 // if masked, add mask score
1224 if (_mask) {
1225 score += _mask->_cacheScore();
1226 }
1227 //g_message("caching score: %f", score);
1228 return score;
1229}
1230
1235
1237{
1238 switch (antialias) {
1239 case Antialiasing::None:
1240 cairo_set_antialias(dc.raw(), CAIRO_ANTIALIAS_NONE);
1241 break;
1242 case Antialiasing::Fast:
1243 cairo_set_antialias(dc.raw(), CAIRO_ANTIALIAS_FAST);
1244 break;
1245 case Antialiasing::Good:
1246 cairo_set_antialias(dc.raw(), CAIRO_ANTIALIAS_GOOD);
1247 break;
1248 case Antialiasing::Best:
1249 cairo_set_antialias(dc.raw(), CAIRO_ANTIALIAS_BEST);
1250 break;
1251 default:
1252 g_assert_not_reached();
1253 }
1254}
1255
1257{
1258 switch (shape_rendering) {
1260 item.setAntialiasing(Antialiasing::Good);
1261 break;
1263 item.setAntialiasing(Antialiasing::Fast);
1264 break;
1266 item.setAntialiasing(Antialiasing::None);
1267 break;
1269 item.setAntialiasing(Antialiasing::Best);
1270 break;
1271 default:
1272 g_assert_not_reached();
1273 }
1274}
1275
1276// Remove this node from its parent, then delete it.
1278{
1279 defer([=, this] {
1280 // This only happens for the top-level deleted item.
1281 if (_parent) {
1283 }
1284
1285 switch (_child_type) {
1286 case ChildType::NORMAL: {
1287 auto it = _parent->_children.iterator_to(*this);
1288 _parent->_children.erase(it);
1289 break;
1290 }
1291 case ChildType::CLIP:
1292 _parent->_clip = nullptr;
1293 break;
1294 case ChildType::MASK:
1295 _parent->_mask = nullptr;
1296 break;
1297 case ChildType::FILL:
1298 _parent->_fill_pattern = nullptr;
1299 break;
1300 case ChildType::STROKE:
1301 _parent->_stroke_pattern = nullptr;
1302 break;
1303 case ChildType::ROOT:
1304 _drawing._root = nullptr;
1305 break;
1306 default:
1307 break;
1308 }
1309
1310 if (_parent) {
1311 bool propagate = _child_type == ChildType::CLIP || _child_type == ChildType::MASK;
1312 _parent->_markForUpdate(STATE_ALL, propagate);
1313 }
1314
1315 delete this;
1316 });
1317}
1318
1319} // namespace Inkscape
1320
1321/*
1322 Local Variables:
1323 mode:c++
1324 c-file-style:"stroustrup"
1325 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1326 indent-tabs-mode:nil
1327 fill-column:99
1328 End:
1329*/
1330// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
std::mutex mutables
Definition async.cpp:41
Cairo software blending templates.
void ink_cairo_surface_filter(cairo_surface_t *in, cairo_surface_t *out, Filter &&filter)
cairo_operator_t ink_css_blend_to_cairo_operator(SPBlendMode css_blend)
Cairo integration helpers.
Cairo::RefPtr< Cairo::ImageSurface > surface
Definition canvas.cpp:137
Geom::IntRect visible
Definition canvas.cpp:154
Geom::IntRect bounds
Definition canvas.cpp:182
Inkscape canvas widget.
3x3 matrix representing an affine transformation.
Definition affine.h:70
void setTranslation(Point const &loc)
Sets the translation imparted by the Affine.
Definition affine.cpp:56
Coord descrim() const
Calculate the descriminant.
Definition affine.cpp:434
bool isSingular(Coord eps=EPSILON) const
Check whether this matrix is singular.
Definition affine.cpp:377
bool isIdentity(Coord eps=EPSILON) const
Check whether this matrix is an identity matrix.
Definition affine.cpp:109
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Axis-aligned generic rectangle that can be empty.
C area() const
Compute the rectangle's area.
void unionWith(CRect const &b)
Enlarge the rectangle to contain the argument.
bool intersects(CRect const &r) const
Check whether the rectangles have any common points.
void intersectWith(CRect const &b)
Leave only the area overlapping with the argument.
Axis aligned, non-empty, generic rectangle.
static CRect from_xywh(C x, C y, C w, C h)
Create rectangle from origin and dimensions.
C area() const
Compute the rectangle's area.
bool contains(GenericRect< C > const &r) const
Check whether the rectangle includes all points in the given rectangle.
bool intersects(GenericRect< C > const &r) const
Check whether the rectangles have any common points.
void expandBy(C amount)
Expand the rectangle in both directions by the specified amount.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
Minimal wrapper over Cairo.
void setSource(cairo_pattern_t *source)
cairo_surface_t * rawTarget()
DrawingSurface * surface()
void paint(double alpha=1.0)
void rectangle(Geom::Rect const &r)
void setOperator(cairo_operator_t op)
SVG drawing item for display.
virtual void _clipItem(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area) const
unsigned _cached_persistent
If set, will always be cached regardless of score.
unsigned _background_accumulate
Whether this element accumulates background (has any ancestor with enable-background: new)
void clip(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area) const
Rasterize the clipping path.
DrawingPattern * _fill_pattern
SPItem * _item
Used to associate DrawingItems with SPItems that created them.
void appendChild(DrawingItem *item)
void _setCached(bool cached, bool persistent=false)
Enable / disable storing the rendering in memory.
Geom::OptIntRect _cacheRect() const
Antialiasing _antialias
antialiasing level (default is Good)
CacheList::iterator _cache_iterator
unsigned render(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, unsigned flags=0, DrawingItem const *stop_at=nullptr) const
Rasterize items.
std::unique_ptr< CacheData > _cache
Geom::Affine const & ctm() const
DrawingItem * pick(Geom::Point const &p, double delta, unsigned flags=0)
Get the item under the specified point.
Geom::OptIntRect _drawbox
Full visual bounding box - enlarged by filters, shrunk by clips and masks.
void setBlendMode(SPBlendMode blend_mode)
void setSensitive(bool sensitive)
Geom::OptIntRect _bbox
Bounding box in display (pixel) coords including stroke.
Drawing & drawing() const
virtual void _dropPatternCache()
void setOpacity(float opacity)
virtual void setStyle(SPStyle const *style, SPStyle const *context_style=nullptr)
Process information related to the new style.
unsigned _sensitive
Whether this item responds to events.
unsigned _has_cache_iterator
If set, _cache_iterator is valid.
std::unique_ptr< Inkscape::Filters::Filter > _filter
double _cacheScore()
Compute the caching score.
bool unisolatedBlend() const
virtual unsigned _renderItem(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, unsigned flags, DrawingItem const *stop_at) const
Geom::OptRect _item_bbox
Geometric bounding box in item's user space.
void setVisible(bool visible)
DrawingItem * _parent
void setZOrder(unsigned zorder)
Move this item to the given place in the Z order of siblings. Does nothing if the item is not a norma...
virtual DrawingItem * _pickItem(Geom::Point const &p, double delta, unsigned flags)
void setAntialiasing(Antialiasing antialias)
virtual void setChildrenStyle(SPStyle const *context_style)
Recursively update children style.
SPStyle const * _style
std::unique_ptr< Geom::Affine > _transform
Incremental transform from parent to this item's coords.
void _markForUpdate(unsigned state, bool propagate)
Marks the item as needing a recomputation of internal data.
DrawingPattern * _stroke_pattern
void setFilterRenderer(std::unique_ptr< Filters::Filter > renderer)
Glib::ustring name() const
virtual bool _canClip() const
void setTransform(Geom::Affine const &trans)
void _renderOutline(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, unsigned flags) const
DrawingItem(Drawing &drawing)
unsigned _background_new
Whether enable-background: new is set for this element.
void setStrokePattern(DrawingPattern *pattern)
void update(Geom::IntRect const &area=Geom::IntRect::infinite(), UpdateContext const &ctx=UpdateContext(), unsigned flags=STATE_ALL, unsigned reset=0)
Update derived data before operations.
bool isAncestorOf(DrawingItem const *item) const
Returns true if item is among the descendants. Will return false if item == this.
void prependChild(DrawingItem *item)
SPStyle const * _context_style
Geom::Affine _ctm
Total transform from item coords to display coords.
void setIsolation(bool isolation)
virtual unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
void setFillPattern(DrawingPattern *pattern)
void _invalidateFilterBackground(Geom::IntRect const &area)
void _markForRendering()
Marks the current visual bounding box of the item for redrawing.
Geom::Affine transform() const
void setMask(DrawingItem *item)
void setItemBounds(Geom::OptRect const &bounds)
void setClip(DrawingItem *item)
void recursivePrintTree(unsigned level=0) const
Drawing tree node used for rendering paints.
Drawing surface that remembers its origin.
uint32_t maskOutlineColor() const
Definition drawing.h:72
DrawingItem * _root
Definition drawing.h:102
CacheList _candidate_items
Definition drawing.h:125
Geom::OptIntRect const & cacheLimit() const
Definition drawing.h:80
ColorMode colorMode() const
Definition drawing.h:68
CanvasItemDrawing * getCanvasItemDrawing()
Definition drawing.h:47
uint32_t clipOutlineColor() const
Definition drawing.h:71
RenderMode renderMode() const
Definition drawing.h:67
bool outlineOverlay() const
Definition drawing.h:69
auto & grayscaleMatrix() const
Definition drawing.h:70
std::optional< Antialiasing > _antialiasing_override
Definition drawing.h:122
std::set< DrawingItem * > _cached_items
Definition drawing.h:124
char const * getId() const
Returns the objects current ID string.
An SVG style object.
Definition style.h:45
T< SPAttr::ENABLE_BACKGROUND, SPIEnum< SPEnableBackground > > enable_background
enable-background, used for defining where filter effects get their background image
Definition style.h:281
T< SPAttr::VECTOR_EFFECT, SPIVectorEffect > vector_effect
vector effect
Definition style.h:237
RectangularCluster rc
double c[8][4]
static char const *const current
Definition dir-util.cpp:71
Cairo drawing context with Inkscape extensions.
unsigned int guint32
Group belonging to an SVG drawing element.
static constexpr auto CACHE_SCORE_THRESHOLD
Do not consider objects for caching below this score.
Canvas item belonging to an SVG drawing element.
Canvas belonging to SVG pattern.
Cairo surface that remembers its origin.
struct _cairo_surface cairo_surface_t
Group belonging to an SVG drawing element.
SVG drawing for display.
SPItem * item
GenericOptRect< IntCoord > OptIntRect
Definition forward.h:58
Affine identity()
Create an identity matrix.
Definition affine.h:210
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
std::vector< Point > intersect(const xAx &C1, const xAx &C2)
Definition conicsec.cpp:361
void ExtractRGB32(guint32 px, guint32 &r, guint32 &g, guint &b)
Helper class to stream background task notifications as a series of messages.
Geom::PathVector outline(Geom::Path const &input, double width, double miter, LineJoinType join, LineCapType butt, double tolerance)
Strokes the path given by input.
void propagate_antialias(SPShapeRendering shape_rendering, DrawingItem &item)
Propagate element's shape rendering attribute into internal anti-aliasing setting of DrawingItem.
void apply_antialias(DrawingContext &dc, Antialiasing antialias)
Apply antialias setting to Cairo.
Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx.
std::uint32_t outline_color
SPShapeRendering
@ SP_CSS_SHAPE_RENDERING_AUTO
@ SP_CSS_SHAPE_RENDERING_GEOMETRICPRECISION
@ SP_CSS_SHAPE_RENDERING_CRISPEDGES
@ SP_CSS_SHAPE_RENDERING_OPTIMIZESPEED
@ SP_CSS_BACKGROUND_NEW
SPBlendMode
@ SP_CSS_BLEND_NORMAL
@ SP_CSS_ISOLATION_ISOLATE
@ SP_CSS_ISOLATION_AUTO
SPStyle - a style object for SPItem objects.
int delta