Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
sp-gradient.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/*
7 * Authors:
8 * Lauris Kaplinski <lauris@kaplinski.com>
9 * bulia byak <buliabyak@users.sf.net>
10 * Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
11 * Jon A. Cruz <jon@joncruz.org>
12 * Abhishek Sharma
13 * Tavmjong Bah <tavmjong@free.fr>
14 *
15 * Copyright (C) 1999-2002 Lauris Kaplinski
16 * Copyright (C) 2000-2001 Ximian, Inc.
17 * Copyright (C) 2004 David Turner
18 * Copyright (C) 2009 Jasper van de Gronde
19 * Copyright (C) 2011 Tavmjong Bah
20 *
21 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
22 *
23 */
24
25#define noSP_GRADIENT_VERBOSE
26//#define OBJECT_TRACE
27
28#include "sp-gradient.h"
29
30#include <cstring>
31#include <string>
32
33#include <2geom/transforms.h>
34
35#include <cairo.h>
36
37#include <sigc++/functors/ptr_fun.h>
38#include <sigc++/adaptors/bind.h>
39
40#include "attributes.h"
41#include "bad-uri-exception.h"
42#include "document.h"
43#include "gradient-chemistry.h"
44
46#include "sp-linear-gradient.h"
47#include "sp-radial-gradient.h"
48#include "sp-mesh-gradient.h"
49#include "sp-mesh-row.h"
50#include "sp-mesh-patch.h"
51#include "sp-stop.h"
52
53#include "display/cairo-utils.h"
54
55#include "svg/svg.h"
58
60{
61 return has_stops;
62}
63
65{
66 return has_patches;
67}
68
70{
71 return units_set;
72}
73
75{
76 return units;
77}
78
80{
81 return spread_set;
82}
83
88
89void SPGradient::setSwatch( bool swatch )
90{
91 if ( swatch != isSwatch() ) {
92 this->swatch = swatch; // to make isSolid() work, this happens first
93 gchar const* paintVal = swatch ? (isSolid() ? "solid" : "gradient") : nullptr;
94 setAttribute( "inkscape:swatch", paintVal);
95
96 requestModified( SP_OBJECT_MODIFIED_FLAG );
97 }
98}
99
100void SPGradient::setPinned(bool pinned)
101{
102 if (pinned != isPinned()) {
103 setAttribute("inkscape:pinned", pinned ? "true" : "false");
104 requestModified(SP_OBJECT_MODIFIED_FLAG);
105 }
106}
107
108
115{
116 //TODO Make this work for mesh gradients
117
118 bool status = false;
119
120 while(true){ // not really a loop, used to avoid deep nesting or multiple exit points from function
121 if (this->getStopCount() != that->getStopCount()) { break; }
122 if (this->hasStops() != that->hasStops()) { break; }
123 if (!this->getVector() || !that->getVector()) { break; }
124 if (this->isSwatch() != that->isSwatch()) { break; }
125 if ( this->isSwatch() ){
126 // drop down to check stops.
127 }
128 else if (
129 (is<SPLinearGradient>(this) && is<SPLinearGradient>(that)) ||
130 (is<SPRadialGradient>(this) && is<SPRadialGradient>(that)) ||
131 (is<SPMeshGradient>(this) && is<SPMeshGradient>(that))) {
132 if(!this->isAligned(that))break;
133 }
134 else { break; } // this should never happen, some unhandled type of gradient
135
136 SPStop *as = this->getVector()->getFirstStop();
137 SPStop *bs = that->getVector()->getFirstStop();
138
139 bool effective = true;
140 while (effective && (as && bs)) {
141 if (!as->getColor().isClose(bs->getColor(), 0.001) || as->offset != bs->offset) {
142 effective = false;
143 break;
144 }
145 else {
146 as = as->getNextStop();
147 bs = bs->getNextStop();
148 }
149 }
150 if (!effective) break;
151
152 status = true;
153 break;
154 }
155 return status;
156}
157
164{
165 bool status = false;
166
167 /* Some gradients have coordinates/other values specified, some don't.
168 yes/yes check the coordinates/other values
169 no/no aligned (because both have all default values)
170 yes/no not aligned
171 no/yes not aligned
172 It is NOT safe to just compare the computed values because if that field has
173 not been set the computed value could be full of garbage.
174
175 In theory the yes/no and no/yes cases could be aligned if the specified value
176 matches the default value.
177 */
178
179 while(true){ // not really a loop, used to avoid deep nesting or multiple exit points from function
180 if(this->gradientTransform_set != that->gradientTransform_set) { break; }
181 if(this->gradientTransform_set &&
182 (this->gradientTransform != that->gradientTransform)) { break; }
183 if (is<SPLinearGradient>(this) && is<SPLinearGradient>(that)) {
184 auto sg = cast<SPLinearGradient>(this);
185 auto tg = cast<SPLinearGradient>(that);
186
187 if( sg->x1._set != tg->x1._set) { break; }
188 if( sg->y1._set != tg->y1._set) { break; }
189 if( sg->x2._set != tg->x2._set) { break; }
190 if( sg->y2._set != tg->y2._set) { break; }
191 if( sg->x1._set && sg->y1._set && sg->x2._set && sg->y2._set) {
192 if( (sg->x1.computed != tg->x1.computed) ||
193 (sg->y1.computed != tg->y1.computed) ||
194 (sg->x2.computed != tg->x2.computed) ||
195 (sg->y2.computed != tg->y2.computed) ) { break; }
196 } else if( sg->x1._set || sg->y1._set || sg->x2._set || sg->y2._set) { break; } // some mix of set and not set
197 // none set? assume aligned and fall through
198 } else if (is<SPRadialGradient>(this) && is<SPLinearGradient>(that)) {
199 auto sg = cast<SPRadialGradient>(this);
200 auto tg = cast<SPRadialGradient>(that);
201
202 if( sg->cx._set != tg->cx._set) { break; }
203 if( sg->cy._set != tg->cy._set) { break; }
204 if( sg->r._set != tg->r._set) { break; }
205 if( sg->fx._set != tg->fx._set) { break; }
206 if( sg->fy._set != tg->fy._set) { break; }
207 if( sg->cx._set && sg->cy._set && sg->fx._set && sg->fy._set && sg->r._set) {
208 if( (sg->cx.computed != tg->cx.computed) ||
209 (sg->cy.computed != tg->cy.computed) ||
210 (sg->r.computed != tg->r.computed ) ||
211 (sg->fx.computed != tg->fx.computed) ||
212 (sg->fy.computed != tg->fy.computed) ) { break; }
213 } else if( sg->cx._set || sg->cy._set || sg->fx._set || sg->fy._set || sg->r._set ) { break; } // some mix of set and not set
214 // none set? assume aligned and fall through
215 } else if (is<SPMeshGradient>(this) && is<SPMeshGradient>(that)) {
216 auto sg = cast<SPMeshGradient>(this);
217 auto tg = cast<SPMeshGradient>(that);
218
219 if( sg->x._set != !tg->x._set) { break; }
220 if( sg->y._set != !tg->y._set) { break; }
221 if( sg->x._set && sg->y._set) {
222 if( (sg->x.computed != tg->x.computed) ||
223 (sg->y.computed != tg->y.computed) ) { break; }
224 } else if( sg->x._set || sg->y._set) { break; } // some mix of set and not set
225 // none set? assume aligned and fall through
226 } else {
227 break;
228 }
229 status = true;
230 break;
231 }
232 return status;
233}
234
235/*
236 * Gradient
237 */
239 spread(),
240 ref(nullptr),
241 state(2),
242 vector() {
243
244 this->ref = new SPGradientReference(this);
245 this->ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(SPGradient::gradientRefChanged), this));
246
252
254 this->units_set = FALSE;
255
257 this->gradientTransform_set = FALSE;
258
260 this->spread_set = FALSE;
261
262 this->has_stops = FALSE;
263 this->has_patches = FALSE;
264
265 this->vector.built = false;
266 this->vector.stops.clear();
267}
268
269SPGradient::~SPGradient() = default;
270
275{
276 // Work-around in case a swatch had been marked for immediate collection:
277 if ( repr->attribute("inkscape:swatch") && repr->attribute("inkscape:collect") ) {
278 repr->removeAttribute("inkscape:collect");
279 }
280
281 this->readAttr(SPAttr::STYLE);
282
283 SPPaintServer::build(document, repr);
284
285 for (auto& ochild: children) {
286 if (is<SPStop>(&ochild)) {
287 this->has_stops = TRUE;
288 break;
289 }
290 if (is<SPMeshrow>(&ochild)) {
291 for (auto& ochild2: ochild.children) {
292 if (is<SPMeshpatch>(&ochild2)) {
293 this->has_patches = TRUE;
294 break;
295 }
296 }
297 if (this->has_patches == TRUE) {
298 break;
299 }
300 }
301 }
302
309
310 // Register ourselves
311 document->addResource("gradient", this);
312}
313
318{
319
320#ifdef SP_GRADIENT_VERBOSE
321 g_print("Releasing this %s\n", this->getId());
322#endif
323
324 if (this->document) {
325 // Unregister ourselves
326 this->document->removeResource("gradient", this);
327 }
328
329 if (this->ref) {
330 this->modified_connection.disconnect();
331 this->ref->detach();
332 delete this->ref;
333 this->ref = nullptr;
334 }
335
337}
338
342void SPGradient::set(SPAttr key, gchar const *value)
343{
344#ifdef OBJECT_TRACE
345 std::stringstream temp;
346 temp << "SPGradient::set: " << sp_attribute_name(key) << " " << (value?value:"null");
347 objectTrace( temp.str() );
348#endif
349
350 switch (key) {
352 if (value) {
353 if (!strcmp(value, "userSpaceOnUse")) {
355 } else {
357 }
358
359 this->units_set = TRUE;
360 } else {
362 this->units_set = FALSE;
363 }
364
365 this->requestModified(SP_OBJECT_MODIFIED_FLAG);
366 break;
367
369 Geom::Affine t;
370 if (value && sp_svg_transform_read(value, &t)) {
371 this->gradientTransform = t;
372 this->gradientTransform_set = TRUE;
373 } else {
375 this->gradientTransform_set = FALSE;
376 }
377
378 this->requestModified(SP_OBJECT_MODIFIED_FLAG);
379 break;
380 }
382 if (value) {
383 if (!strcmp(value, "reflect")) {
385 } else if (!strcmp(value, "repeat")) {
387 } else {
389 }
390
391 this->spread_set = TRUE;
392 } else {
393 this->spread_set = FALSE;
394 }
395
396 this->requestModified(SP_OBJECT_MODIFIED_FLAG);
397 break;
398
400 if (value) {
401 try {
402 this->ref->attach(Inkscape::URI(value));
403 } catch (Inkscape::BadURIException &e) {
404 g_warning("%s", e.what());
405 this->ref->detach();
406 }
407 } else {
408 this->ref->detach();
409 }
410 break;
411
413 {
414 if (value) {
415 this->_pinned = !strcmp(value, "true");
416 }
417 break;
418 }
420 {
421 bool newVal = (value != nullptr);
422 bool modified = false;
423
424 if (newVal != this->swatch) {
425 this->swatch = newVal;
426 modified = true;
427 }
428
429 if (newVal) {
430 // Might need to flip solid/gradient
431 Glib::ustring paintVal = ( this->hasStops() && (this->getStopCount() <= 1) ) ? "solid" : "gradient";
432
433 if ( paintVal != value ) {
434 this->setAttribute( "inkscape:swatch", paintVal);
435 modified = true;
436 }
437 }
438
439 if (modified) {
440 this->requestModified(SP_OBJECT_MODIFIED_FLAG);
441 }
442 }
443 break;
444 default:
445 SPPaintServer::set(key, value);
446 break;
447 }
448
449#ifdef OBJECT_TRACE
450 objectTrace( "SPGradient::set", false );
451#endif
452}
453
458{
459 if (old_ref) {
460 gr->modified_connection.disconnect();
461 }
462 if ( is<SPGradient>(ref)
463 && ref != gr )
464 {
465 gr->modified_connection = ref->connectModified(sigc::bind<2>(sigc::ptr_fun(&SPGradient::gradientRefModified), gr));
466 }
467
468 // Per SVG, all unset attributes must be inherited from linked gradient.
469 // So, as we're now (re)linked, we assign linkee's values to this gradient if they are not yet set -
470 // but without setting the _set flags.
471 // FIXME: do the same for gradientTransform too
472 if (!gr->units_set) {
473 gr->units = gr->fetchUnits();
474 }
475 if (!gr->spread_set) {
476 gr->spread = gr->fetchSpread();
477 }
478
480 gradientRefModified(ref, 0, gr);
481}
482
487{
488 this->invalidateVector();
489
491
492 SPObject *ochild = this->get_child_by_repr(child);
493 if ( ochild && is<SPStop>(ochild) ) {
494 this->has_stops = TRUE;
495 if ( this->getStopCount() > 1 ) {
496 gchar const * attr = this->getAttribute("inkscape:swatch");
497 if ( attr && strcmp(attr, "gradient") ) {
498 this->setAttribute( "inkscape:swatch", "gradient" );
499 }
500 }
501 }
502 if ( ochild && is<SPMeshrow>(ochild) ) {
503 this->has_patches = TRUE;
504 }
505
507 this->requestModified(SP_OBJECT_MODIFIED_FLAG);
508}
509
514{
515 this->invalidateVector();
516
518
519 this->has_stops = FALSE;
520 this->has_patches = FALSE;
521 for (auto& ochild: children) {
522 if (is<SPStop>(&ochild)) {
523 this->has_stops = TRUE;
524 break;
525 }
526 if (is<SPMeshrow>(&ochild)) {
527 for (auto& ochild2: ochild.children) {
528 if (is<SPMeshpatch>(&ochild2)) {
529 this->has_patches = TRUE;
530 break;
531 }
532 }
533 if (this->has_patches == TRUE) {
534 break;
535 }
536 }
537 }
538
539 if ( this->getStopCount() <= 1 ) {
540 gchar const * attr = this->getAttribute("inkscape:swatch");
541
542 if ( attr && strcmp(attr, "solid") ) {
543 this->setAttribute( "inkscape:swatch", "solid" );
544 }
545 }
546
547 /* Fixme: should we schedule "modified" here? */
548 this->requestModified(SP_OBJECT_MODIFIED_FLAG);
549}
550
554void SPGradient::modified(guint flags)
555{
556#ifdef OBJECT_TRACE
557 objectTrace( "SPGradient::modified" );
558#endif
559 if (flags & SP_OBJECT_CHILD_MODIFIED_FLAG) {
560 if (is<SPMeshGradient>(this)) {
561 this->invalidateArray();
562 } else {
563 this->invalidateVector();
564 }
565 }
566
567 if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
568 if (is<SPMeshGradient>(this)) {
569 this->ensureArray();
570 } else {
571 this->ensureVector();
572 }
573 }
574
575 if (flags & SP_OBJECT_MODIFIED_FLAG) flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
576 flags &= SP_OBJECT_MODIFIED_CASCADE;
577
578 // FIXME: climb up the ladder of hrefs
579 std::vector<SPObject *> l;
580 for (auto& child: children) {
582 l.push_back(&child);
583 }
584
585 for (auto child:l) {
586 if (flags || (child->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) {
587 child->emitModified(flags);
588 }
590 }
591
592#ifdef OBJECT_TRACE
593 objectTrace( "SPGradient::modified", false );
594#endif
595}
596
598{
599 SPStop* first = nullptr;
600 for (auto& ochild: children) {
601 if (is<SPStop>(&ochild)) {
602 first = cast<SPStop>(&ochild);
603 break;
604 }
605 }
606 return first;
607}
608
610{
611 int count = 0;
612 // fixed off-by one count
613 SPStop *stop = const_cast<SPGradient*>(this)->getFirstStop();
614 while (stop) {
615 count++;
616 stop = stop->getNextStop();
617 }
618
619 return count;
620}
621
626{
627#ifdef OBJECT_TRACE
628 objectTrace( "SPGradient::write" );
629#endif
630
631 SPPaintServer::write(xml_doc, repr, flags);
632
633 if (flags & SP_OBJECT_WRITE_BUILD) {
634 std::vector<Inkscape::XML::Node *> l;
635
636 for (auto& child: children) {
637 Inkscape::XML::Node *crepr = child.updateRepr(xml_doc, nullptr, flags);
638
639 if (crepr) {
640 l.push_back(crepr);
641 }
642 }
643
644 for (auto i=l.rbegin();i!=l.rend();++i) {
645 repr->addChild(*i, nullptr);
647 }
648 }
649
650 if (this->ref->getURI()) {
651 auto uri_string = this->ref->getURI()->str();
652 auto href_key = Inkscape::getHrefAttribute(*repr).first;
653 repr->setAttributeOrRemoveIfEmpty(href_key, uri_string);
654 }
655
656 if ((flags & SP_OBJECT_WRITE_ALL) || this->units_set) {
657 switch (this->units) {
659 repr->setAttribute("gradientUnits", "userSpaceOnUse");
660 break;
661 default:
662 repr->setAttribute("gradientUnits", "objectBoundingBox");
663 break;
664 }
665 }
666
667 if ((flags & SP_OBJECT_WRITE_ALL) || this->gradientTransform_set) {
669 repr->setAttributeOrRemoveIfEmpty("gradientTransform", c);
670 }
671
672 if ((flags & SP_OBJECT_WRITE_ALL) || this->spread_set) {
673 /* FIXME: Ensure that this->spread is the inherited value
674 * if !this->spread_set. Not currently happening: see SPGradient::modified.
675 */
676 switch (this->spread) {
678 repr->setAttribute("spreadMethod", "reflect");
679 break;
681 repr->setAttribute("spreadMethod", "repeat");
682 break;
683 default:
684 repr->setAttribute("spreadMethod", "pad");
685 break;
686 }
687 }
688
689 if ( (flags & SP_OBJECT_WRITE_EXT) && this->isSwatch() ) {
690 if ( this->isSolid() ) {
691 repr->setAttribute( "inkscape:swatch", "solid" );
692 } else {
693 repr->setAttribute( "inkscape:swatch", "gradient" );
694 }
695 } else {
696 repr->removeAttribute("inkscape:swatch");
697 }
698
699#ifdef OBJECT_TRACE
700 objectTrace( "SPGradient::write", false );
701#endif
702 return repr;
703}
704
711{
712 if ( !vector.built ) {
714 }
715}
716
718{
719 if (!vector.built) {
721 }
722 return vector;
723}
724
731{
732 //std::cout << "SPGradient::ensureArray()" << std::endl;
733 if ( !array.built ) {
734 rebuildArray();
735 }
736}
737
742{
743 if (units != this->units) {
744 this->units = units;
745 units_set = TRUE;
746 requestModified(SP_OBJECT_MODIFIED_FLAG);
747 }
748}
749
754{
755 if (spread != this->spread) {
756 this->spread = spread;
757 spread_set = TRUE;
758 requestModified(SP_OBJECT_MODIFIED_FLAG);
759 }
760}
761
772static SPGradient *
773chase_hrefs(SPGradient *const src, bool (*match)(SPGradient const *))
774{
775 g_return_val_if_fail(src, NULL);
776
777 /* Use a pair of pointers for detecting loops: p1 advances half as fast as p2. If there is a
778 loop, then once p1 has entered the loop, we'll detect it the next time the distance between
779 p1 and p2 is a multiple of the loop size. */
780 SPGradient *p1 = src, *p2 = src;
781 bool do1 = false;
782 for (;;) {
783 if (match(p2)) {
784 return p2;
785 }
786
787 p2 = p2->ref->getObject();
788 if (!p2) {
789 return p2;
790 }
791 if (do1) {
792 p1 = p1->ref->getObject();
793 }
794 do1 = !do1;
795
796 if ( p2 == p1 ) {
797 /* We've been here before, so return NULL to indicate that no matching gradient found
798 * in the chain. */
799 return nullptr;
800 }
801 }
802}
803
807static bool has_stopsFN(SPGradient const *gr)
808{
809 return gr->hasStops();
810}
811
815static bool has_patchesFN(SPGradient const *gr)
816{
817 return gr->hasPatches();
818}
819
823static bool has_spread_set(SPGradient const *gr)
824{
825 return gr->isSpreadSet();
826}
827
831static bool
833{
834 return gr->isUnitsSet();
835}
836
837
839{
840 SPGradient * src = chase_hrefs(this, has_stopsFN);
841 if (src == nullptr) {
842 src = this;
843 }
844
845 if (force_vector) {
847 }
848 return src;
849}
850
852{
853 SPGradient * src = chase_hrefs(this, has_patchesFN);
854 if (src == nullptr) {
855 src = this;
856 }
857 return src;
858}
859
866{
867 // TODO: chase_hrefs should allow for const
868 SPGradient const *src = chase_hrefs(const_cast<SPGradient *>(this), has_spread_set);
869 return ( src
870 ? src->spread
871 : SP_GRADIENT_SPREAD_PAD ); // pad is the default
872}
873
880{
881 SPGradient const *src = chase_hrefs(this, has_units_set);
882 return ( src
883 ? src->units
884 : SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX ); // bbox is the default
885}
886
887
891void
893{
895
896 /* Collect stops from original repr */
897 std::vector<Inkscape::XML::Node *> l;
898 for (Inkscape::XML::Node *child = repr->firstChild() ; child != nullptr; child = child->next() ) {
899 if (!strcmp(child->name(), "svg:stop")) {
900 l.push_back(child);
901 }
902 }
903 /* Remove all stops */
904 for (auto i=l.rbegin();i!=l.rend();++i) {
910 }
911}
912
917void
919{
922
923 /* We have to be careful, as vector may be our own, so construct repr list at first */
924 std::vector<Inkscape::XML::Node *> l;
925
926 for (auto & stop : vector.stops) {
928 Inkscape::XML::Node *child = xml_doc->createElement("svg:stop");
929 child->setAttributeCssDouble("offset", stop.offset);
930 /* strictly speaking, offset an SVG <number> rather than a CSS one, but exponents make no
931 * sense for offset proportions. */
932 auto obj = cast<SPStop>(document->getObjectByRepr(child));
933 if (auto color = stop.color)
934 obj->setColor(*color);
935 /* Order will be reversed here */
936 l.push_back(child);
937 }
938
940
941 /* And insert new children from list */
942 for (auto i=l.rbegin();i!=l.rend();++i) {
944 repr->addChild(child, nullptr);
946 }
947}
948
949
950void SPGradient::gradientRefModified(SPObject */*href*/, guint /*flags*/, SPGradient *gradient)
951{
952 if ( gradient->invalidateVector() ) {
953 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
954 // Conditional to avoid causing infinite loop if there's a cycle in the href chain.
955 }
956}
957
960{
961 bool ret = false;
962
963 if (vector.built) {
964 vector.built = false;
965 vector.stops.clear();
966 ret = true;
967 }
968
969 return ret;
970}
971
974{
975 bool ret = false;
976
977 if (array.built) {
978 array.built = false;
979 // array.clear();
980 ret = true;
981 }
982
983 return ret;
984}
985
988{
989 gint len = 0;
990 for (auto& child: children) {
991 if (is<SPStop>(&child)) {
992 len ++;
993 }
994 }
995
996 vector.stops.clear();
997
998 SPGradient *reffed = ref ? ref->getObject() : nullptr;
999 if ( !hasStops() && reffed ) {
1000 /* Copy vector from referenced gradient */
1001 vector.built = true; // Prevent infinite recursion.
1002 reffed->ensureVector();
1003 if (!reffed->vector.stops.empty()) {
1004 vector.built = reffed->vector.built;
1005 vector.stops.assign(reffed->vector.stops.begin(), reffed->vector.stops.end());
1006 return;
1007 }
1008 }
1009
1010 for (auto& child: children) {
1011 if (is<SPStop>(&child)) {
1012 auto stop = cast<SPStop>(&child);
1013
1014 SPGradientStop gstop;
1015 if (!vector.stops.empty()) {
1016 // "Each gradient offset value is required to be equal to or greater than the
1017 // previous gradient stop's offset value. If a given gradient stop's offset
1018 // value is not equal to or greater than all previous offset values, then the
1019 // offset value is adjusted to be equal to the largest of all previous offset
1020 // values."
1021 gstop.offset = MAX(stop->offset, vector.stops.back().offset);
1022 } else {
1023 gstop.offset = stop->offset;
1024 }
1025
1026 // "Gradient offset values less than 0 (or less than 0%) are rounded up to
1027 // 0%. Gradient offset values greater than 1 (or greater than 100%) are rounded
1028 // down to 100%."
1029 gstop.offset = CLAMP(gstop.offset, 0, 1);
1030 gstop.color = stop->getColor();
1031 vector.stops.push_back(gstop);
1032 }
1033 }
1034
1035 // Normalize per section 13.2.4 of SVG 1.1.
1036 if (vector.stops.empty()) {
1037 /* "If no stops are defined, then painting shall occur as if 'none' were specified as the
1038 * paint style."
1039 */
1040 {
1041 SPGradientStop gstop;
1042 gstop.offset = 0.0;
1043 vector.stops.push_back(gstop);
1044 }
1045 {
1046 SPGradientStop gstop;
1047 gstop.offset = 1.0;
1048 vector.stops.push_back(gstop);
1049 }
1050 } else {
1051 /* "If one stop is defined, then paint with the solid color fill using the color defined
1052 * for that gradient stop."
1053 */
1054 if (vector.stops.front().offset > 0.0) {
1055 // If the first one is not at 0, then insert a copy of the first at 0.
1056 SPGradientStop gstop;
1057 gstop.offset = 0.0;
1058 gstop.color = vector.stops.front().color;
1059 vector.stops.insert(vector.stops.begin(), gstop);
1060 }
1061 if (vector.stops.back().offset < 1.0) {
1062 // If the last one is not at 1, then insert a copy of the last at 1.
1063 SPGradientStop gstop;
1064 gstop.offset = 1.0;
1065 gstop.color = vector.stops.back().color;
1066 vector.stops.push_back(gstop);
1067 }
1068 }
1069
1070 vector.built = true;
1071}
1072
1075{
1076 // std::cout << "SPGradient::rebuildArray()" << std::endl;
1077
1078 if( !is<SPMeshGradient>(this) ) {
1079 g_warning( "SPGradient::rebuildArray() called for non-mesh gradient" );
1080 return;
1081 }
1082
1083 array.read( cast<SPMeshGradient>( this ) );
1085}
1086
1089{
1091 return ( Geom::Scale(bbox.dimensions())
1092 * Geom::Translate(bbox.min())
1093 * Geom::Affine(ctm) );
1094 } else {
1095 return ctm;
1096 }
1097}
1098
1101{
1103 return ( gradientTransform
1104 * Geom::Scale(bbox.dimensions())
1105 * Geom::Translate(bbox.min())
1106 * Geom::Affine(ctm) );
1107 } else {
1108 return gradientTransform * ctm;
1109 }
1110}
1111
1112void
1114 Geom::Rect const &bbox, Geom::Affine const &gs2d)
1115{
1116 gradientTransform = gs2d * ctm.inverse();
1119 * Geom::Translate(-bbox.min())
1120 * Geom::Scale(bbox.dimensions()).inverse() );
1121 }
1122 gradientTransform_set = TRUE;
1123
1124 requestModified(SP_OBJECT_MODIFIED_FLAG);
1125}
1126
1132{
1133 std::vector<SPObject *> links;
1135
1136 Geom::OptRect bbox;
1137 for (auto obj : links) {
1138 if (auto item = cast<SPItem>(obj)) {
1139 bbox.unionWith(item->visualBounds(Geom::identity(), true, false, true));
1140 }
1141 }
1142 return bbox;
1143}
1144
1145/* CAIRO RENDERING STUFF */
1146
1147void
1149 SPGradient *gr,
1150 Geom::OptRect const &bbox,
1151 double opacity)
1152{
1153 // set spread type
1154 switch (gr->getSpread()) {
1156 cairo_pattern_set_extend(cp, CAIRO_EXTEND_REFLECT);
1157 break;
1159 cairo_pattern_set_extend(cp, CAIRO_EXTEND_REPEAT);
1160 break;
1162 default:
1163 cairo_pattern_set_extend(cp, CAIRO_EXTEND_PAD);
1164 break;
1165 }
1166
1167 // add stops
1168 if (!is<SPMeshGradient>(gr)) {
1169 for (auto & stop : gr->vector.stops) {
1170 // multiply stop opacity by paint opacity
1171 ink_cairo_pattern_add_color_stop(cp, stop.offset, *stop.color, opacity);
1172 }
1173 }
1174
1175 // set pattern transform matrix
1176 Geom::Affine gs2user = gr->gradientTransform;
1177 if (gr->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX && bbox) {
1178 Geom::Affine bbox2user(bbox->width(), 0, 0, bbox->height(), bbox->left(), bbox->top());
1179 gs2user *= bbox2user;
1180 }
1182}
1183
1186{
1187 cairo_pattern_t *pat = nullptr;
1188
1189 if (!is<SPMeshGradient>(this)) {
1190 ensureVector();
1191
1192 pat = cairo_pattern_create_linear(0, 0, width, 0);
1193
1194 for (auto & stop : vector.stops) {
1195 if (stop.color.has_value()) {
1196 ink_cairo_pattern_add_color_stop(pat, stop.offset, *stop.color);
1197 }
1198 }
1199 } else if (unsigned const num_columns = array.patch_columns()) {
1200 // For the moment, use the top row of nodes for preview.
1201 double offset = 1.0/double(num_columns);
1202
1203 pat = cairo_pattern_create_linear(0, 0, width, 0);
1204
1205 for (unsigned i = 0; i < num_columns + 1; ++i) {
1206 SPMeshNode* node = array.node( 0, i*3 );
1207 if (node->color.has_value()) {
1208 ink_cairo_pattern_add_color_stop(pat, i * offset, *node->color);
1209 }
1210 }
1211 }
1212
1213 return pat;
1214}
1215
1217{
1218 if (swatch && hasStops() && getStopCount() == 1) {
1219 return true;
1220 }
1221 return false;
1222}
1223
1224/*
1225 Local Variables:
1226 mode:c++
1227 c-file-style:"stroustrup"
1228 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1229 indent-tabs-mode:nil
1230 fill-column:99
1231 End:
1232*/
1233// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1234
gchar const * sp_attribute_name(SPAttr id)
Get attribute name by id.
Lookup dictionary for attributes/properties.
SPAttr
Definition attributes.h:27
@ INKSCAPE_SWATCH
@ GRADIENTUNITS
@ XLINK_HREF
@ INKSCAPE_PINNED
@ SPREADMETHOD
@ GRADIENTTRANSFORM
TODO: insert short description here.
void ink_cairo_pattern_add_color_stop(cairo_pattern_t *ptn, double offset, Inkscape::Colors::Color const &color, double opacity)
void ink_cairo_pattern_set_matrix(cairo_pattern_t *cp, Geom::Affine const &m)
Cairo integration helpers.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
void unionWith(CRect const &b)
Enlarge the rectangle to contain the argument.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
CPoint dimensions() const
Get rectangle's width and height as a point.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Axis aligned, non-empty rectangle.
Definition rect.h:92
Scaling from the origin.
Definition transforms.h:150
Scale inverse() const
Definition transforms.h:172
Translation by a vector.
Definition transforms.h:115
A thin wrapper around std::ostringstream, but writing floating point numbers in the format required b...
bool isClose(Color const &other, double epsilon=EPSILON) const
Find out if a color is a close match to another color of the same type.
Definition color.cpp:583
URI const * getURI() const
Returns a pointer to a URI containing the currently attached URI, or NULL if no URI is currently atta...
void detach()
Detaches from the currently attached URI target, if any; the current referrent is signaled as NULL.
sigc::signal< void(SPObject *, SPObject *)> changedSignal()
Accessor for the referrent change notification signal; this signal is emitted whenever the URIReferen...
void attach(URI const &uri)
Attaches to a URI, relative to the specified document.
Represents an URI as per RFC 2396.
Definition uri.h:36
std::string str(char const *baseuri=nullptr) const
Return the string representation of this URI.
Definition uri.cpp:281
Interface for refcounted XML nodes.
Definition node.h:80
virtual void addChild(Node *child, Node *after)=0
Insert another node as a child of this node.
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:167
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual Node * firstChild()=0
Get the first child of this node.
bool setAttributeCssDouble(Util::const_char_ptr key, double val)
Set a property attribute to val [slightly rounded], in the format required for CSS properties: in par...
Definition node.cpp:102
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
void removeAttribute(Inkscape::Util::const_char_ptr key)
Remove an attribute of this node.
Definition node.h:280
Typed SVG document implementation.
Definition document.h:103
bool removeResource(char const *key, SPObject *object)
bool addResource(char const *key, SPObject *object)
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
SPGradient * getObject() const
Gradient.
Definition sp-gradient.h:86
SPGradient * getArray(bool force_private=false)
Returns private mesh of given gradient (the gradient at the end of the href chain which has patches),...
unsigned int spread_set
void setPinned(bool pinned=true)
unsigned int state
State in Inkscape gradient system.
void ensureArray()
Forces array (mesh) to be built, if not present (i.e.
void build(SPDocument *document, Inkscape::XML::Node *repr) override
Virtual build: set gradient attributes from its associated repr.
void set_gs2d_matrix(Geom::Affine const &ctm, Geom::Rect const &bbox, Geom::Affine const &gs2d)
~SPGradient() override
SPStop * getFirstStop()
SPGradientVector vector
Linear and Radial Gradients.
Inkscape::XML::Node * write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags) override
Write gradient attributes to repr.
void remove_child(Inkscape::XML::Node *child) override
Callback for remove_child event.
Geom::Affine gradientTransform
gradientTransform attribute
Definition sp-gradient.h:99
SPGradientUnits fetchUnits()
Returns the effective units of given gradient (climbing up the refs chain if needed).
sigc::connection modified_connection
SPGradientSpread getSpread() const
bool invalidateVector()
Return true if change made.
unsigned int has_stops
Gradient stops.
void release() override
Virtual release of SPGradient members before destruction.
unsigned int units_set
Definition sp-gradient.h:95
void child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) override
Callback for child_added event.
static void gradientRefChanged(SPObject *old_ref, SPObject *ref, SPGradient *gr)
Gets called when the gradient is (re)attached to another gradient.
SPGradientSpread fetchSpread() const
Returns the effective spread of given gradient (climbing up the refs chain if needed).
bool hasStops() const
void modified(unsigned int flags) override
Callback for modified event.
bool isSpreadSet() const
SPGradientReference * ref
Reference (href)
unsigned int gradientTransform_set
SPGradient * getVector(bool force_private=false)
Returns private vector of given gradient (the gradient at the end of the href chain which has stops),...
bool invalidateArray()
Return true if change made.
void rebuildVector() const
Creates normalized color vector.
void setSpread(SPGradientSpread spread)
Set spread property of gradient and emit modified.
cairo_pattern_t * create_preview_pattern(double width)
SPGradientUnits getUnits() const
SPGradientUnits units
gradientUnits attribute
Definition sp-gradient.h:94
void repr_clear_vector()
Clears the gradient's svg:stop children from its repr.
unsigned int has_patches
Gradient patches.
bool isEquivalent(SPGradient *b)
return true if this gradient is "equivalent" to that gradient.
bool isPinned() const
void rebuildArray()
Creates normalized color mesh patch array.
SPMeshNodeArray array
Mesh Gradients.
bool _pinned
Pinned in swatches dialog.
Geom::OptRect getAllItemsBox() const
Return a visual bounding box that covers every item this gradient would paint added together.
bool isUnitsSet() const
All Gradients.
bool isSolid() const
bool isAligned(SPGradient *b)
return true if this gradient is "aligned" to that gradient.
int getStopCount() const
void setUnits(SPGradientUnits units)
Set units property of gradient and emit modified.
Geom::Affine get_g2d_matrix(Geom::Affine const &ctm, Geom::Rect const &bbox) const
Transforms to/from gradient position space in given environment.
SPGradientSpread spread
spreadMethod attribute
void set(SPAttr key, char const *value) override
Set gradient attribute to value.
static void gradientRefModified(SPObject *href, unsigned int flags, SPGradient *gradient)
bool hasPatches() const
SPGradientVector const & getGradientVector() const
void repr_write_vector()
Writes the gradient's internal vector (whether from its own stops, or inherited from refs) into the g...
Geom::Affine get_gs2d_matrix(Geom::Affine const &ctm, Geom::Rect const &bbox) const
void ensureVector()
Forces vector to be built, if not present (i.e.
void setSwatch(bool swatch=true)
Geom::OptRect visualBounds(Geom::Affine const &transform=Geom::identity(), bool wfilter=true, bool wclip=true, bool wmask=true) const
Get item's visual bounding box in this item's coordinate system.
Definition sp-item.cpp:925
unsigned patch_columns()
Number of patch columns.
bool read(SPMeshGradient *mg)
SPMeshNode * node(unsigned i, unsigned j)
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
Inkscape::XML::Node * repr
Definition sp-object.h:193
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
void requestModified(unsigned int flags)
Requests that a modification notification signal be emitted later (e.g.
SPObject * get_child_by_repr(Inkscape::XML::Node *repr)
Return object's child whose node pointer equals repr.
SPDocument * document
Definition sp-object.h:188
virtual void set(SPAttr key, const char *value)
virtual void remove_child(Inkscape::XML::Node *child)
void getLinkedRecursive(std::vector< SPObject * > &objects, LinkedObjectNature direction=LinkedObjectNature::ANY) const
Grows the input list with all linked items recursively in both child nodes and links of links.
char const * getId() const
Returns the objects current ID string.
virtual Inkscape::XML::Node * write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags)
virtual void release()
void readAttr(char const *key)
Read value of key attribute from XML node into object.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
virtual void child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref)
char const * getAttribute(char const *name) const
void objectTrace(std::string const &, bool in=true, unsigned flags=0)
virtual void build(SPDocument *doc, Inkscape::XML::Node *repr)
ChildrenList children
Definition sp-object.h:907
bool isSwatch() const
Gradient stop.
Definition sp-stop.h:31
float offset
Definition sp-stop.h:38
SPStop * getNextStop()
Virtual write: write object attributes to repr.
Definition sp-stop.cpp:98
Inkscape::Colors::Color getColor() const
Definition sp-stop.cpp:130
TODO: insert short description here.
double c[8][4]
_cairo_pattern cairo_pattern_t
SPGradient * sp_gradient_ensure_vector_normalized(SPGradient *gr)
Either normalizes given gradient to vector, or returns fresh normalized vector - in latter case,...
SPItem * item
Inkscape::XML::Node * node
double offset
Affine identity()
Create an identity matrix.
Definition affine.h:210
static R & release(R &r)
Decrements the reference count of a anchored object.
std::pair< char const *, char const * > getHrefAttribute(XML::Node const &node)
Get the 'href' or 'xlink:href' (fallback) attribute from an XML node.
static cairo_user_data_key_t key
Ocnode * child[8]
Definition quantize.cpp:33
Ocnode ** ref
Definition quantize.cpp:32
unsigned long bs
Definition quantize.cpp:38
void sp_repr_unparent(Inkscape::XML::Node *repr)
Remove repr from children of its parent node.
Definition repr.h:107
auto len
Definition safe-printf.h:21
TODO: insert short description here.
SPGradientSpread
@ SP_GRADIENT_SPREAD_PAD
@ SP_GRADIENT_SPREAD_REPEAT
@ SP_GRADIENT_SPREAD_REFLECT
SPGradientUnits
@ SP_GRADIENT_UNITS_USERSPACEONUSE
@ SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX
static bool has_patchesFN(SPGradient const *gr)
True if gradient has patches (i.e.
void sp_gradient_pattern_common_setup(cairo_pattern_t *cp, SPGradient *gr, Geom::OptRect const &bbox, double opacity)
static SPGradient * chase_hrefs(SPGradient *const src, bool(*match)(SPGradient const *))
Returns the first of {src, src->ref->getObject(), src->ref->getObject()->ref->getObject(),...
static bool has_stopsFN(SPGradient const *gr)
True if gradient has stops.
static bool has_spread_set(SPGradient const *gr)
True if gradient has spread set.
static bool has_units_set(SPGradient const *gr)
True if gradient has units set.
@ SP_GRADIENT_STATE_UNKNOWN
Definition sp-gradient.h:41
TODO: insert short description here.
TODO: insert short description here.
SPMeshpatch: SVG <meshpatch> implementation.
SPMeshrow: SVG <meshrow> implementation.
SPObject * sp_object_unref(SPObject *object, SPObject *owner)
Decrease reference count of object, with possible debugging and finalization.
SPObject * sp_object_ref(SPObject *object, SPObject *owner)
Increase reference count of object, with possible debugging.
TODO: insert short description here.
TODO: insert short description here.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
Differs from SPStop in that SPStop mirrors the <stop> element in the document, whereas SPGradientStop...
std::optional< Inkscape::Colors::Color > color
The effective gradient vector, after copying stops from the referenced gradient if necessary.
std::vector< SPGradientStop > stops
bool sp_svg_transform_read(gchar const *str, Geom::Affine *transform)
std::string sp_svg_transform_write(Geom::Affine const &transform)
double width
Affine transformation classes.