1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Various utility methods for gradients
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak
8 * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
9 * Jon A. Cruz <jon@joncruz.org>
10 * Abhishek Sharma
11 * Tavmjong Bah <tavmjong@free.fr>
12 *
13 * Copyright (C) 2012 Tavmjong Bah
14 * Copyright (C) 2010 Authors
15 * Copyright (C) 2007 Johan Engelen
16 * Copyright (C) 2001-2005 authors
17 * Copyright (C) 2001 Ximian, Inc.
18 *
19 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
20 */
21
22 #include <glibmm/i18n.h>
23
24 #include <2geom/transforms.h>
25 #include <2geom/bezier-curve.h>
26 #include <2geom/crossing.h>
27 #include <2geom/line.h>
28
29 #include "desktop-style.h"
30 #include "desktop.h"
31 #include "document-undo.h"
32 #include "gradient-chemistry.h"
33 #include "gradient-drag.h"
34 #include "selection.h"
35 #include "verbs.h"
36
37 #include "object/sp-defs.h"
38 #include "object/sp-gradient-reference.h"
39 #include "object/sp-linear-gradient.h"
40 #include "object/sp-mesh-gradient.h"
41 #include "object/sp-radial-gradient.h"
42 #include "object/sp-stop.h"
43 #include "object/sp-text.h"
44 #include "object/sp-tspan.h"
45 #include "style.h"
46
47 #include "svg/svg.h"
48 #include "svg/svg-color.h"
49 #include "svg/css-ostringstream.h"
50
51 #include "ui/tools/tool-base.h"
52
53 #include "ui/widget/gradient-vector-selector.h"
54
55 #define noSP_GR_VERBOSE
56
57 using Inkscape::DocumentUndo;
58
59 namespace {
60
61 Inkscape::PaintTarget paintTargetItems[] = {Inkscape::FOR_FILL, Inkscape::FOR_STROKE};
62
63 std::vector<Inkscape::PaintTarget> vectorOfPaintTargets(paintTargetItems, paintTargetItems + (sizeof(paintTargetItems) / sizeof(paintTargetItems[0])));
64
65 } // namespace
66
67 namespace Inkscape {
68
allPaintTargets()69 std::vector<PaintTarget> const &allPaintTargets()
70 {
71 return vectorOfPaintTargets;
72 }
73
74 } // namespace Inkscape
75
76 // Terminology:
77 //
78 // "vector" is a gradient that has stops but not position coords. It can be referenced by one or
79 // more privates. Objects should not refer to it directly. It has no radial/linear distinction.
80 //
81 // "array" is a gradient that has mesh rows and patches. It may or may not have "x" and "y" attributes.
82 // An array does have spacial information so it cannot be normalized like a "vector".
83 //
84 // "shared" is either a "vector" or "array" that is shared between multiple objects.
85 //
86 // "private" is a gradient that is not shared. A private linear or radial gradient has no stops but
87 // has position coords (e.g. center, radius etc for a radial); it references a "vector" for the
88 // actual colors. A mesh may or may not reference an array. Each private is only used by one object.
89
90 static void sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *gr);
91
sp_gradient_ensure_vector_normalized(SPGradient * gr)92 SPGradient *sp_gradient_ensure_vector_normalized(SPGradient *gr)
93 {
94 #ifdef SP_GR_VERBOSE
95 g_message("sp_gradient_ensure_vector_normalized(%p)", gr);
96 #endif
97 g_return_val_if_fail(gr != nullptr, NULL);
98 g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
99 g_return_val_if_fail(!SP_IS_MESHGRADIENT(gr), NULL);
100
101 /* If we are already normalized vector, just return */
102 if (gr->state == SP_GRADIENT_STATE_VECTOR) return gr;
103 /* Fail, if we have wrong state set */
104 if (gr->state != SP_GRADIENT_STATE_UNKNOWN) {
105 g_warning("file %s: line %d: Cannot normalize private gradient to vector (%s)", __FILE__, __LINE__, gr->getId());
106 return nullptr;
107 }
108
109 /* First make sure we have vector directly defined (i.e. gr has its own stops) */
110 if ( !gr->hasStops() ) {
111 /* We do not have stops ourselves, so flatten stops as well */
112 gr->ensureVector();
113 g_assert(gr->vector.built);
114 // this adds stops from gr->vector as children to gr
115 gr->repr_write_vector ();
116 }
117
118 /* If gr hrefs some other gradient, remove the href */
119 if (gr->ref){
120 if (gr->ref->getObject()) {
121 // We are hrefing someone, so require flattening
122 gr->updateRepr(SP_OBJECT_WRITE_EXT | SP_OBJECT_WRITE_ALL);
123 sp_gradient_repr_set_link(gr->getRepr(), nullptr);
124 }
125 }
126
127 /* Everything is OK, set state flag */
128 gr->state = SP_GRADIENT_STATE_VECTOR;
129 return gr;
130 }
131
132 /**
133 * Creates new private gradient for the given shared gradient.
134 */
135
sp_gradient_get_private_normalized(SPDocument * document,SPGradient * shared,SPGradientType type)136 static SPGradient *sp_gradient_get_private_normalized(SPDocument *document, SPGradient *shared, SPGradientType type)
137 {
138 #ifdef SP_GR_VERBOSE
139 g_message("sp_gradient_get_private_normalized(%p, %p, %d)", document, shared, type);
140 #endif
141
142 g_return_val_if_fail(document != nullptr, NULL);
143 g_return_val_if_fail(shared != nullptr, NULL);
144 g_return_val_if_fail(SP_IS_GRADIENT(shared), NULL);
145 g_return_val_if_fail(shared->hasStops() || shared->hasPatches(), NULL);
146
147 SPDefs *defs = document->getDefs();
148
149 Inkscape::XML::Document *xml_doc = document->getReprDoc();
150 // create a new private gradient of the requested type
151 Inkscape::XML::Node *repr;
152 if (type == SP_GRADIENT_TYPE_LINEAR) {
153 repr = xml_doc->createElement("svg:linearGradient");
154 } else if(type == SP_GRADIENT_TYPE_RADIAL) {
155 repr = xml_doc->createElement("svg:radialGradient");
156 } else {
157 repr = xml_doc->createElement("svg:meshgradient");
158 }
159
160 // privates are garbage-collectable
161 repr->setAttribute("inkscape:collect", "always");
162
163 // link to shared
164 sp_gradient_repr_set_link(repr, shared);
165
166 /* Append the new private gradient to defs */
167 defs->getRepr()->appendChild(repr);
168 Inkscape::GC::release(repr);
169
170 // get corresponding object
171 SPGradient *gr = static_cast<SPGradient *>(document->getObjectByRepr(repr));
172 g_assert(gr != nullptr);
173 g_assert(SP_IS_GRADIENT(gr));
174
175 return gr;
176 }
177
178 /**
179 Count how many times gr is used by the styles of o and its descendants
180 */
count_gradient_hrefs(SPObject * o,SPGradient * gr)181 static guint count_gradient_hrefs(SPObject *o, SPGradient *gr)
182 {
183 if (!o)
184 return 1;
185
186 guint i = 0;
187
188 SPStyle *style = o->style;
189 if (style
190 && style->fill.isPaintserver()
191 && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style))
192 && SP_GRADIENT(SP_STYLE_FILL_SERVER(style)) == gr)
193 {
194 i ++;
195 }
196 if (style
197 && style->stroke.isPaintserver()
198 && SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style))
199 && SP_GRADIENT(SP_STYLE_STROKE_SERVER(style)) == gr)
200 {
201 i ++;
202 }
203
204 for (auto& child: o->children) {
205 i += count_gradient_hrefs(&child, gr);
206 }
207
208 return i;
209 }
210
211
212 /**
213 * If gr has other users, create a new shared; also check if gr links to shared, relink if not
214 */
sp_gradient_fork_private_if_necessary(SPGradient * gr,SPGradient * shared,SPGradientType type,SPObject * o)215 static SPGradient *sp_gradient_fork_private_if_necessary(SPGradient *gr, SPGradient *shared,
216 SPGradientType type, SPObject *o)
217 {
218 #ifdef SP_GR_VERBOSE
219 g_message("sp_gradient_fork_private_if_necessary(%p, %p, %d, %p)", gr, shared, type, o);
220 #endif
221 g_return_val_if_fail(gr != nullptr, NULL);
222 g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
223
224 // Orphaned gradient, no shared with stops or patches at the end of the line; this used to be
225 // an assert
226 if ( !shared || !(shared->hasStops() || shared->hasPatches()) ) {
227 std::cerr << "sp_gradient_fork_private_if_necessary: Orphaned gradient" << std::endl;
228 return (gr);
229 }
230
231 // user is the object that uses this gradient; normally it's item but for tspans, we
232 // check its ancestor text so that tspans don't get different gradients from their
233 // texts.
234 SPObject *user = o;
235 while (SP_IS_TSPAN(user)) {
236 user = user->parent;
237 }
238
239 // Check the number of uses of the gradient within this object;
240 // if we are private and there are no other users,
241 if (!shared->isSwatch() && (gr->hrefcount <= count_gradient_hrefs(user, gr))) {
242 // check shared
243 if ( gr != shared && gr->ref->getObject() != shared ) {
244 /* our href is not the shared, and shared is different from gr; relink */
245 sp_gradient_repr_set_link(gr->getRepr(), shared);
246 }
247 return gr;
248 }
249
250 SPDocument *doc = gr->document;
251 SPObject *defs = doc->getDefs();
252
253 if ((gr->hasStops()) ||
254 (gr->hasPatches()) ||
255 (gr->state != SP_GRADIENT_STATE_UNKNOWN) ||
256 (gr->parent != SP_OBJECT(defs)) ||
257 (gr->hrefcount > 1)) {
258
259 // we have to clone a fresh new private gradient for the given shared
260
261 // create an empty one
262 SPGradient *gr_new = sp_gradient_get_private_normalized(doc, shared, type);
263
264 // copy all the attributes to it
265 Inkscape::XML::Node *repr_new = gr_new->getRepr();
266 Inkscape::XML::Node *repr = gr->getRepr();
267 repr_new->setAttribute("gradientUnits", repr->attribute("gradientUnits"));
268 repr_new->setAttribute("gradientTransform", repr->attribute("gradientTransform"));
269 if (SP_IS_RADIALGRADIENT(gr)) {
270 repr_new->setAttribute("cx", repr->attribute("cx"));
271 repr_new->setAttribute("cy", repr->attribute("cy"));
272 repr_new->setAttribute("fx", repr->attribute("fx"));
273 repr_new->setAttribute("fy", repr->attribute("fy"));
274 repr_new->setAttribute("r", repr->attribute("r" ));
275 repr_new->setAttribute("fr", repr->attribute("fr"));
276 repr_new->setAttribute("spreadMethod", repr->attribute("spreadMethod"));
277 } else if (SP_IS_LINEARGRADIENT(gr)) {
278 repr_new->setAttribute("x1", repr->attribute("x1"));
279 repr_new->setAttribute("y1", repr->attribute("y1"));
280 repr_new->setAttribute("x2", repr->attribute("x2"));
281 repr_new->setAttribute("y2", repr->attribute("y2"));
282 repr_new->setAttribute("spreadMethod", repr->attribute("spreadMethod"));
283 } else { // Mesh
284 repr_new->setAttribute("x", repr->attribute("x"));
285 repr_new->setAttribute("y", repr->attribute("y"));
286 repr_new->setAttribute("type", repr->attribute("type"));
287
288 // We probably want a completely separate mesh gradient so
289 // copy the children and unset the link to the shared.
290 for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) {
291 Inkscape::XML::Node *copy = child->duplicate(doc->getReprDoc());
292 repr_new->appendChild( copy );
293 Inkscape::GC::release( copy );
294 }
295 sp_gradient_repr_set_link(repr_new, nullptr);
296 }
297 return gr_new;
298 } else {
299 return gr;
300 }
301 }
302
sp_gradient_fork_vector_if_necessary(SPGradient * gr)303 SPGradient *sp_gradient_fork_vector_if_necessary(SPGradient *gr)
304 {
305 #ifdef SP_GR_VERBOSE
306 g_message("sp_gradient_fork_vector_if_necessary(%p)", gr);
307 #endif
308 // Some people actually prefer their gradient vectors to be shared...
309 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
310 if (!prefs->getBool("/options/forkgradientvectors/value", true))
311 return gr;
312
313 if (gr->hrefcount > 1) {
314 SPDocument *doc = gr->document;
315 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
316
317 Inkscape::XML::Node *repr = gr->getRepr()->duplicate(xml_doc);
318 doc->getDefs()->getRepr()->addChild(repr, nullptr);
319 SPGradient *gr_new = static_cast<SPGradient *>(doc->getObjectByRepr(repr));
320 gr_new = sp_gradient_ensure_vector_normalized (gr_new);
321 Inkscape::GC::release(repr);
322 return gr_new;
323 }
324 return gr;
325 }
326
327 /**
328 * Obtain the vector from the gradient. A forked vector will be created and linked to this gradient if another gradient uses it.
329 */
sp_gradient_get_forked_vector_if_necessary(SPGradient * gradient,bool force_vector)330 SPGradient *sp_gradient_get_forked_vector_if_necessary(SPGradient *gradient, bool force_vector)
331 {
332 #ifdef SP_GR_VERBOSE
333 g_message("sp_gradient_get_forked_vector_if_necessary(%p, %d)", gradient, force_vector);
334 #endif
335 SPGradient *vector = gradient->getVector(force_vector);
336 vector = sp_gradient_fork_vector_if_necessary (vector);
337 if ( gradient != vector && gradient->ref->getObject() != vector ) {
338 sp_gradient_repr_set_link(gradient->getRepr(), vector);
339 }
340 return vector;
341 }
342
343
344 /**
345 * Convert an item's gradient to userspace _without_ preserving coords, setting them to defaults
346 * instead. No forking or reapplying is done because this is only called for newly created privates.
347 * @return The new gradient.
348 */
sp_gradient_reset_to_userspace(SPGradient * gr,SPItem * item)349 SPGradient *sp_gradient_reset_to_userspace(SPGradient *gr, SPItem *item)
350 {
351 #ifdef SP_GR_VERBOSE
352 g_message("sp_gradient_reset_to_userspace(%p, %p)", gr, item);
353 #endif
354 Inkscape::XML::Node *repr = gr->getRepr();
355
356 // calculate the bbox of the item
357 item->document->ensureUpToDate();
358 Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine
359
360 if (!bbox)
361 return gr;
362
363 Geom::Coord const width = bbox->dimensions()[Geom::X];
364 Geom::Coord const height = bbox->dimensions()[Geom::Y];
365
366 Geom::Point const center = bbox->midpoint();
367
368 if (SP_IS_RADIALGRADIENT(gr)) {
369 sp_repr_set_svg_double(repr, "cx", center[Geom::X]);
370 sp_repr_set_svg_double(repr, "cy", center[Geom::Y]);
371 sp_repr_set_svg_double(repr, "fx", center[Geom::X]);
372 sp_repr_set_svg_double(repr, "fy", center[Geom::Y]);
373 sp_repr_set_svg_double(repr, "r", width/2);
374
375 // we want it to be elliptic, not circular
376 Geom::Affine squeeze = Geom::Translate (-center) *
377 Geom::Scale(1, height/width) *
378 Geom::Translate (center);
379
380 gr->gradientTransform = squeeze;
381 gr->setAttributeOrRemoveIfEmpty("gradientTransform", sp_svg_transform_write(gr->gradientTransform));
382 } else if (SP_IS_LINEARGRADIENT(gr)) {
383
384 // Assume horizontal gradient by default (as per SVG 1.1)
385 Geom::Point pStart = center - Geom::Point(width/2, 0);
386 Geom::Point pEnd = center + Geom::Point(width/2, 0);
387
388 // Get the preferred gradient angle from prefs
389 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
390 double angle = prefs->getDouble("/dialogs/gradienteditor/angle", 0.0);
391
392 if (angle != 0.0) {
393
394 Geom::Line grl(center, Geom::rad_from_deg(angle));
395 Geom::LineSegment bbl1(bbox->corner(0), bbox->corner(1));
396 Geom::LineSegment bbl2(bbox->corner(1), bbox->corner(2));
397 Geom::LineSegment bbl3(bbox->corner(2), bbox->corner(3));
398 Geom::LineSegment bbl4(bbox->corner(3), bbox->corner(0));
399
400 // Find where our gradient line intersects the bounding box.
401 if (!bbl1.isDegenerate() && intersection(bbl1, grl)) {
402 pStart = bbl1.pointAt((*intersection(bbl1, grl)).ta);
403 pEnd = bbl3.pointAt((*intersection(bbl3, grl)).ta);
404 if (intersection(bbl1, grl.ray(grl.angle()))) {
405 std::swap(pStart, pEnd);
406 }
407 } else if (!bbl2.isDegenerate() && intersection(bbl2, grl)) {
408 pStart = bbl2.pointAt((*intersection(bbl2, grl)).ta);
409 pEnd = bbl4.pointAt((*intersection(bbl4, grl)).ta);
410 if (intersection(bbl2, grl.ray(grl.angle()))) {
411 std::swap(pStart, pEnd);
412 }
413 }
414
415 }
416
417 sp_repr_set_svg_double(repr, "x1", pStart[Geom::X]);
418 sp_repr_set_svg_double(repr, "y1", pStart[Geom::Y]);
419 sp_repr_set_svg_double(repr, "x2", pEnd[Geom::X]);
420 sp_repr_set_svg_double(repr, "y2", pEnd[Geom::Y]);
421
422 } else {
423 // Mesh
424 // THIS IS BEING CALLED TWICE WHENEVER A NEW GRADIENT IS CREATED, WRITING HERE CAUSES PROBLEMS
425 // IN SPMeshNodeArray::create()
426 //sp_repr_set_svg_double(repr, "x", bbox->min()[Geom::X]);
427 //sp_repr_set_svg_double(repr, "y", bbox->min()[Geom::Y]);
428
429 // We don't create a shared array gradient.
430 SPMeshGradient* mg = SP_MESHGRADIENT( gr );
431 mg->array.create( mg, item, bbox );
432 }
433
434 // set the gradientUnits
435 repr->setAttribute("gradientUnits", "userSpaceOnUse");
436
437 return gr;
438 }
439
440 /**
441 * Convert an item's gradient to userspace if necessary, also fork it if necessary.
442 * @return The new gradient.
443 */
sp_gradient_convert_to_userspace(SPGradient * gr,SPItem * item,gchar const * property)444 SPGradient *sp_gradient_convert_to_userspace(SPGradient *gr, SPItem *item, gchar const *property)
445 {
446 #ifdef SP_GR_VERBOSE
447 g_message("sp_gradient_convert_to_userspace(%p, %p, \"%s\")", gr, item, property);
448 #endif
449 g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
450
451 if ( gr && gr->isSolid() ) {
452 return gr;
453 }
454
455 // First, fork it if it is shared
456 if (SP_IS_LINEARGRADIENT(gr)) {
457 gr = sp_gradient_fork_private_if_necessary(gr, gr->getVector(), SP_GRADIENT_TYPE_LINEAR, item);
458 } else if (SP_IS_RADIALGRADIENT(gr)) {
459 gr = sp_gradient_fork_private_if_necessary(gr, gr->getVector(), SP_GRADIENT_TYPE_RADIAL, item);
460 } else {
461 gr = sp_gradient_fork_private_if_necessary(gr, gr->getArray(), SP_GRADIENT_TYPE_MESH, item);
462 }
463
464 if (gr->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) {
465
466 Inkscape::XML::Node *repr = gr->getRepr();
467
468 // calculate the bbox of the item
469 item->document->ensureUpToDate();
470 Geom::Affine bbox2user;
471 Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine
472 if ( bbox ) {
473 bbox2user = Geom::Affine(bbox->dimensions()[Geom::X], 0,
474 0, bbox->dimensions()[Geom::Y],
475 bbox->min()[Geom::X], bbox->min()[Geom::Y]);
476 } else {
477 // would be degenerate otherwise
478 bbox2user = Geom::identity();
479 }
480
481 /* skew is the additional transform, defined by the proportions of the item, that we need
482 * to apply to the gradient in order to work around this weird bit from SVG 1.1
483 * (http://www.w3.org/TR/SVG11/pservers.html#LinearGradients):
484 *
485 * When gradientUnits="objectBoundingBox" and gradientTransform is the identity
486 * matrix, the stripes of the linear gradient are perpendicular to the gradient
487 * vector in object bounding box space (i.e., the abstract coordinate system where
488 * (0,0) is at the top/left of the object bounding box and (1,1) is at the
489 * bottom/right of the object bounding box). When the object's bounding box is not
490 * square, the stripes that are conceptually perpendicular to the gradient vector
491 * within object bounding box space will render non-perpendicular relative to the
492 * gradient vector in user space due to application of the non-uniform scaling
493 * transformation from bounding box space to user space.
494 */
495 Geom::Affine skew = bbox2user;
496 double exp = skew.descrim();
497 skew[0] /= exp;
498 skew[1] /= exp;
499 skew[2] /= exp;
500 skew[3] /= exp;
501 skew[4] = 0;
502 skew[5] = 0;
503
504 // apply skew to the gradient
505 gr->gradientTransform = skew;
506 gr->setAttributeOrRemoveIfEmpty("gradientTransform", sp_svg_transform_write(gr->gradientTransform));
507
508 // Matrix to convert points to userspace coords; postmultiply by inverse of skew so
509 // as to cancel it out when it's applied to the gradient during rendering
510 Geom::Affine point_convert = bbox2user * skew.inverse();
511
512 if (SP_IS_LINEARGRADIENT(gr)) {
513 SPLinearGradient *lg = SP_LINEARGRADIENT(gr);
514
515 Geom::Point p1_b = Geom::Point(lg->x1.computed, lg->y1.computed);
516 Geom::Point p2_b = Geom::Point(lg->x2.computed, lg->y2.computed);
517
518 Geom::Point p1_u = p1_b * point_convert;
519 Geom::Point p2_u = p2_b * point_convert;
520
521 sp_repr_set_svg_double(repr, "x1", p1_u[Geom::X]);
522 sp_repr_set_svg_double(repr, "y1", p1_u[Geom::Y]);
523 sp_repr_set_svg_double(repr, "x2", p2_u[Geom::X]);
524 sp_repr_set_svg_double(repr, "y2", p2_u[Geom::Y]);
525
526 // set the gradientUnits
527 repr->setAttribute("gradientUnits", "userSpaceOnUse");
528
529 } else if (SP_IS_RADIALGRADIENT(gr)) {
530 SPRadialGradient *rg = SP_RADIALGRADIENT(gr);
531
532 // original points in the bbox coords
533 Geom::Point c_b = Geom::Point(rg->cx.computed, rg->cy.computed);
534 Geom::Point f_b = Geom::Point(rg->fx.computed, rg->fy.computed);
535 double r_b = rg->r.computed;
536
537 // converted points in userspace coords
538 Geom::Point c_u = c_b * point_convert;
539 Geom::Point f_u = f_b * point_convert;
540 double r_u = r_b * point_convert.descrim();
541
542 sp_repr_set_svg_double(repr, "cx", c_u[Geom::X]);
543 sp_repr_set_svg_double(repr, "cy", c_u[Geom::Y]);
544 sp_repr_set_svg_double(repr, "fx", f_u[Geom::X]);
545 sp_repr_set_svg_double(repr, "fy", f_u[Geom::Y]);
546 sp_repr_set_svg_double(repr, "r", r_u);
547
548 // set the gradientUnits
549 repr->setAttribute("gradientUnits", "userSpaceOnUse");
550
551 } else {
552 std::cerr << "sp_gradient_convert_to_userspace: Conversion of mesh to userspace not implemented" << std::endl;
553 }
554 }
555
556 // apply the gradient to the item (may be necessary if we forked it); not recursive
557 // generally because grouped items will be taken care of later (we're being called
558 // from sp_item_adjust_paint_recursive); however text and all its children should all
559 // refer to one gradient, hence the recursive call for text (because we can't/don't
560 // want to access tspans and set gradients on them separately)
561 if (SP_IS_TEXT(item)) {
562 sp_style_set_property_url(item, property, gr, true);
563 } else {
564 sp_style_set_property_url(item, property, gr, false);
565 }
566
567 return gr;
568 }
569
sp_gradient_transform_multiply(SPGradient * gradient,Geom::Affine postmul,bool set)570 void sp_gradient_transform_multiply(SPGradient *gradient, Geom::Affine postmul, bool set)
571 {
572 #ifdef SP_GR_VERBOSE
573 g_message("sp_gradient_transform_multiply(%p, , %d)", gradient, set);
574 #endif
575 if (set) {
576 gradient->gradientTransform = postmul;
577 } else {
578 gradient->gradientTransform *= postmul; // fixme: get gradient transform by climbing to hrefs?
579 }
580 gradient->gradientTransform_set = TRUE;
581
582 auto c = sp_svg_transform_write(gradient->gradientTransform);
583 gradient->setAttributeOrRemoveIfEmpty("gradientTransform", c);
584 }
585
getGradient(SPItem * item,Inkscape::PaintTarget fill_or_stroke)586 SPGradient *getGradient(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
587 {
588 SPStyle *style = item->style;
589 SPGradient *gradient = nullptr;
590
591 switch (fill_or_stroke)
592 {
593 case Inkscape::FOR_FILL:
594 if (style && (style->fill.isPaintserver())) {
595 SPPaintServer *server = item->style->getFillPaintServer();
596 if ( SP_IS_GRADIENT(server) ) {
597 gradient = SP_GRADIENT(server);
598 }
599 }
600 break;
601 case Inkscape::FOR_STROKE:
602 if (style && (style->stroke.isPaintserver())) {
603 SPPaintServer *server = item->style->getStrokePaintServer();
604 if ( SP_IS_GRADIENT(server) ) {
605 gradient = SP_GRADIENT(server);
606 }
607 }
608 break;
609 }
610
611 return gradient;
612 }
613
sp_last_stop(SPGradient * gradient)614 SPStop *sp_last_stop(SPGradient *gradient)
615 {
616 for (SPStop *stop = gradient->getFirstStop(); stop != nullptr; stop = stop->getNextStop()) {
617 if (stop->getNextStop() == nullptr)
618 return stop;
619 }
620 return nullptr;
621 }
622
sp_get_stop_i(SPGradient * gradient,guint stop_i)623 SPStop *sp_get_stop_i(SPGradient *gradient, guint stop_i)
624 {
625 SPStop *stop = gradient->getFirstStop();
626 if (!stop) {
627 return nullptr;
628 }
629
630 // if this is valid but weird gradient without an offset-zero stop element,
631 // inkscape has created a handle for the start of gradient anyway,
632 // so when it asks for stop N that corresponds to stop element N-1
633 if (stop->offset != 0)
634 {
635 stop_i--;
636 }
637
638 for (guint i = 0; i < stop_i; i++) {
639 if (!stop) {
640 return nullptr;
641 }
642 stop = stop->getNextStop();
643 }
644
645 return stop;
646 }
647
average_color(guint32 c1,guint32 c2,gdouble p)648 guint32 average_color(guint32 c1, guint32 c2, gdouble p)
649 {
650 guint32 r = static_cast<guint32>(SP_RGBA32_R_U (c1) * (1 - p) + SP_RGBA32_R_U (c2) * p);
651 guint32 g = static_cast<guint32>(SP_RGBA32_G_U (c1) * (1 - p) + SP_RGBA32_G_U (c2) * p);
652 guint32 b = static_cast<guint32>(SP_RGBA32_B_U (c1) * (1 - p) + SP_RGBA32_B_U (c2) * p);
653 guint32 a = static_cast<guint32>(SP_RGBA32_A_U (c1) * (1 - p) + SP_RGBA32_A_U (c2) * p);
654
655 return SP_RGBA32_U_COMPOSE(r, g, b, a);
656 }
657
sp_vector_add_stop(SPGradient * vector,SPStop * prev_stop,SPStop * next_stop,gfloat offset)658 SPStop *sp_vector_add_stop(SPGradient *vector, SPStop* prev_stop, SPStop* next_stop, gfloat offset)
659 {
660 #ifdef SP_GR_VERBOSE
661 g_message("sp_vector_add_stop(%p, %p, %p, %f)", vector, prev_stop, next_stop, offset);
662 #endif
663
664 Inkscape::XML::Node *new_stop_repr = nullptr;
665 new_stop_repr = prev_stop->getRepr()->duplicate(vector->getRepr()->document());
666 vector->getRepr()->addChild(new_stop_repr, prev_stop->getRepr());
667
668 SPStop *newstop = reinterpret_cast<SPStop *>(vector->document->getObjectByRepr(new_stop_repr));
669 newstop->offset = offset;
670 sp_repr_set_css_double( newstop->getRepr(), "offset", (double)offset);
671 guint32 const c1 = prev_stop->get_rgba32();
672 guint32 const c2 = next_stop->get_rgba32();
673 guint32 cnew = average_color (c1, c2, (offset - prev_stop->offset) / (next_stop->offset - prev_stop->offset));
674 Inkscape::CSSOStringStream os;
675 gchar c[64];
676 sp_svg_write_color (c, sizeof(c), cnew);
677 gdouble opacity = (gdouble) SP_RGBA32_A_F (cnew);
678 os << "stop-color:" << c << ";stop-opacity:" << opacity <<";";
679 newstop->setAttributeOrRemoveIfEmpty("style", os.str());
680 Inkscape::GC::release(new_stop_repr);
681
682 return newstop;
683 }
684
sp_item_gradient_stop_query_style(SPItem * item,GrPointType point_type,guint point_i,Inkscape::PaintTarget fill_or_stroke)685 guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke)
686 {
687 SPGradient *gradient = getGradient(item, fill_or_stroke);
688
689 if (!gradient || !SP_IS_GRADIENT(gradient)) {
690 return 0;
691 }
692
693 if (SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT(gradient) ) {
694
695 SPGradient *vector = gradient->getVector();
696
697 if (!vector) // orphan!
698 return 0; // what else to do?
699
700 switch (point_type) {
701 case POINT_LG_BEGIN:
702 case POINT_RG_CENTER:
703 case POINT_RG_FOCUS:
704 {
705 SPStop *first = vector->getFirstStop();
706 if (first) {
707 return first->get_rgba32();
708 }
709 }
710 break;
711
712 case POINT_LG_END:
713 case POINT_RG_R1:
714 case POINT_RG_R2:
715 {
716 SPStop *last = sp_last_stop (vector);
717 if (last) {
718 return last->get_rgba32();
719 }
720 }
721 break;
722
723 case POINT_LG_MID:
724 case POINT_RG_MID1:
725 case POINT_RG_MID2:
726 {
727 SPStop *stopi = sp_get_stop_i (vector, point_i);
728 if (stopi) {
729 return stopi->get_rgba32();
730 }
731 }
732 break;
733
734 default:
735 g_warning( "Bad linear/radial gradient handle type" );
736 break;
737 }
738 return 0;
739 } else if (SP_IS_MESHGRADIENT(gradient)) {
740
741 // Mesh gradient
742 SPMeshGradient *mg = SP_MESHGRADIENT(gradient);
743
744 switch (point_type) {
745 case POINT_MG_CORNER: {
746 if (point_i >= mg->array.corners.size()) {
747 return 0;
748 }
749 SPMeshNode const* cornerpoint = mg->array.corners[ point_i ];
750
751 if (cornerpoint) {
752 SPColor color = cornerpoint->color;
753 double opacity = cornerpoint->opacity;
754 return color.toRGBA32( opacity );
755 } else {
756 return 0;
757 }
758 break;
759 }
760
761 case POINT_MG_HANDLE:
762 case POINT_MG_TENSOR:
763 {
764 // Do nothing. Handles and tensors don't have color
765 break;
766 }
767
768 default:
769 g_warning( "Bad mesh handle type" );
770 }
771 return 0;
772 }
773
774 return 0;
775 }
776
sp_item_gradient_stop_set_style(SPItem * item,GrPointType point_type,guint point_i,Inkscape::PaintTarget fill_or_stroke,SPCSSAttr * stop)777 void sp_item_gradient_stop_set_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke, SPCSSAttr *stop)
778 {
779 #ifdef SP_GR_VERBOSE
780 g_message("sp_item_gradient_stop_set_style(%p, %d, %d, %d, %p)", item, point_type, point_i, fill_or_stroke, stop);
781 #endif
782 SPGradient *gradient = getGradient(item, fill_or_stroke);
783
784 if (!gradient || !SP_IS_GRADIENT(gradient))
785 return;
786
787 if (SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT(gradient) ) {
788
789 SPGradient *vector = gradient->getVector();
790
791 if (!vector) // orphan!
792 return;
793
794 vector = sp_gradient_fork_vector_if_necessary (vector);
795 if ( gradient != vector && gradient->ref->getObject() != vector ) {
796 sp_gradient_repr_set_link(gradient->getRepr(), vector);
797 }
798
799 switch (point_type) {
800 case POINT_LG_BEGIN:
801 case POINT_RG_CENTER:
802 case POINT_RG_FOCUS:
803 {
804 SPStop *first = vector->getFirstStop();
805 if (first) {
806 sp_repr_css_change(first->getRepr(), stop, "style");
807 }
808 }
809 break;
810
811 case POINT_LG_END:
812 case POINT_RG_R1:
813 case POINT_RG_R2:
814 {
815 SPStop *last = sp_last_stop (vector);
816 if (last) {
817 sp_repr_css_change(last->getRepr(), stop, "style");
818 }
819 }
820 break;
821
822 case POINT_LG_MID:
823 case POINT_RG_MID1:
824 case POINT_RG_MID2:
825 {
826 SPStop *stopi = sp_get_stop_i (vector, point_i);
827 if (stopi) {
828 sp_repr_css_change(stopi->getRepr(), stop, "style");
829 }
830 }
831 break;
832
833 default:
834 g_warning( "Bad linear/radial gradient handle type" );
835 break;
836 }
837 } else {
838
839 // Mesh gradient
840 SPMeshGradient *mg = SP_MESHGRADIENT(gradient);
841
842 bool changed = false;
843 switch (point_type) {
844 case POINT_MG_CORNER: {
845
846 // Update mesh array (which is not updated automatically when stop is changed?)
847 gchar const* color_str = sp_repr_css_property( stop, "stop-color", nullptr );
848 if( color_str ) {
849 SPColor color( 0 );
850 SPIPaint paint;
851 paint.read( color_str );
852 if( paint.isColor() ) {
853 color = paint.value.color;
854 }
855 mg->array.corners[ point_i ]->color = color;
856 changed = true;
857 }
858 gchar const* opacity_str = sp_repr_css_property( stop, "stop-opacity", nullptr );
859 if( opacity_str ) {
860 std::stringstream os( opacity_str );
861 double opacity = 1.0;
862 os >> opacity;
863 mg->array.corners[ point_i ]->opacity = opacity;
864 changed = true;
865 }
866 // Update stop
867 if( changed ) {
868 SPStop *stopi = mg->array.corners[ point_i ]->stop;
869 if (stopi) {
870 sp_repr_css_change(stopi->getRepr(), stop, "style");
871 } else {
872 std::cerr << "sp_item_gradient_stop_set_style: null stopi" << std::endl;
873 }
874 }
875 break;
876 }
877
878 case POINT_MG_HANDLE:
879 case POINT_MG_TENSOR:
880 {
881 // Do nothing. Handles and tensors don't have colors.
882 break;
883 }
884
885 default:
886 g_warning( "Bad mesh handle type" );
887 }
888 }
889 }
890
sp_item_gradient_reverse_vector(SPItem * item,Inkscape::PaintTarget fill_or_stroke)891 void sp_item_gradient_reverse_vector(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
892 {
893 #ifdef SP_GR_VERBOSE
894 g_message("sp_item_gradient_reverse_vector(%p, %d)", item, fill_or_stroke);
895 #endif
896 SPGradient *gradient = getGradient(item, fill_or_stroke);
897 if (!gradient || !SP_IS_GRADIENT(gradient))
898 return;
899
900 SPGradient *vector = gradient->getVector();
901 if (!vector) // orphan!
902 return;
903
904 vector = sp_gradient_fork_vector_if_necessary (vector);
905 if ( gradient != vector && gradient->ref->getObject() != vector ) {
906 sp_gradient_repr_set_link(gradient->getRepr(), vector);
907 }
908
909 std::vector<SPObject *> child_objects;
910 std::vector<Inkscape::XML::Node *>child_reprs;
911 std::vector<double> offsets;
912 double offset;
913 for (auto& child: vector->children) {
914 child_reprs.push_back(child.getRepr());
915 child_objects.push_back(&child);
916 offset=0;
917 sp_repr_get_double(child.getRepr(), "offset", &offset);
918 offsets.push_back(offset);
919 }
920
921 std::vector<Inkscape::XML::Node *> child_copies;
922 for (auto repr:child_reprs) {
923 Inkscape::XML::Document *xml_doc = vector->getRepr()->document();
924 child_copies.push_back(repr->duplicate(xml_doc));
925 }
926
927
928 for (auto i:child_objects) {
929 i->deleteObject();
930 }
931
932 std::vector<double>::reverse_iterator o_it = offsets.rbegin();
933 for (auto c_it = child_copies.rbegin(); c_it != child_copies.rend(); ++c_it, ++o_it) {
934 vector->appendChildRepr(*c_it);
935 sp_repr_set_svg_double (*c_it, "offset", 1 - *o_it);
936 Inkscape::GC::release(*c_it);
937 }
938 }
939
sp_item_gradient_invert_vector_color(SPItem * item,Inkscape::PaintTarget fill_or_stroke)940 void sp_item_gradient_invert_vector_color(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
941 {
942 #ifdef SP_GR_VERBOSE
943 g_message("sp_item_gradient_invert_vector_color(%p, %d)", item, fill_or_stroke);
944 #endif
945 SPGradient *gradient = getGradient(item, fill_or_stroke);
946 if (!gradient || !SP_IS_GRADIENT(gradient))
947 return;
948
949 SPGradient *vector = gradient->getVector();
950 if (!vector) // orphan!
951 return;
952
953 vector = sp_gradient_fork_vector_if_necessary (vector);
954 if ( gradient != vector && gradient->ref->getObject() != vector ) {
955 sp_gradient_repr_set_link(gradient->getRepr(), vector);
956 }
957
958 for (auto& child: vector->children) {
959 if (SP_IS_STOP(&child)) {
960 guint32 color = SP_STOP(&child)->get_rgba32();
961 //g_message("Stop color %d", color);
962 gchar c[64];
963 sp_svg_write_color (c, sizeof(c),
964 SP_RGBA32_U_COMPOSE(
965 (255 - SP_RGBA32_R_U(color)),
966 (255 - SP_RGBA32_G_U(color)),
967 (255 - SP_RGBA32_B_U(color)),
968 SP_RGBA32_A_U(color)
969 )
970 );
971 SPCSSAttr *css = sp_repr_css_attr_new ();
972 sp_repr_css_set_property (css, "stop-color", c);
973 sp_repr_css_change(child.getRepr(), css, "style");
974 sp_repr_css_attr_unref (css);
975 }
976 }
977 }
978
979 /**
980 Set the position of point point_type of the gradient applied to item (either fill_or_stroke) to
981 p_w (in desktop coordinates). Write_repr if you want the change to become permanent.
982 */
sp_item_gradient_set_coords(SPItem * item,GrPointType point_type,guint point_i,Geom::Point p_w,Inkscape::PaintTarget fill_or_stroke,bool write_repr,bool scale)983 void sp_item_gradient_set_coords(SPItem *item, GrPointType point_type, guint point_i, Geom::Point p_w, Inkscape::PaintTarget fill_or_stroke, bool write_repr, bool scale)
984 {
985 #ifdef SP_GR_VERBOSE
986 g_message("sp_item_gradient_set_coords(%p, %d, %d, (%f, %f), ...)", item, point_type, point_i, p_w[Geom::X], p_w[Geom::Y] );
987 #endif
988 SPGradient *gradient = getGradient(item, fill_or_stroke);
989
990 if (!gradient || !SP_IS_GRADIENT(gradient))
991 return;
992
993 // Needed only if units are set to SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX
994 gradient = sp_gradient_convert_to_userspace(gradient, item, (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke");
995
996 Geom::Affine i2d (item->i2dt_affine ());
997 Geom::Point p = p_w * i2d.inverse();
998 p *= (gradient->gradientTransform).inverse();
999 // now p is in gradient's original coordinates
1000
1001 Inkscape::XML::Node *repr = gradient->getRepr();
1002
1003 if (SP_IS_LINEARGRADIENT(gradient)) {
1004 SPLinearGradient *lg = SP_LINEARGRADIENT(gradient);
1005 switch (point_type) {
1006 case POINT_LG_BEGIN:
1007 if (scale) {
1008 lg->x2.computed += (lg->x1.computed - p[Geom::X]);
1009 lg->y2.computed += (lg->y1.computed - p[Geom::Y]);
1010 }
1011 lg->x1.computed = p[Geom::X];
1012 lg->y1.computed = p[Geom::Y];
1013 if (write_repr) {
1014 if (scale) {
1015 sp_repr_set_svg_double(repr, "x2", lg->x2.computed);
1016 sp_repr_set_svg_double(repr, "y2", lg->y2.computed);
1017 }
1018 sp_repr_set_svg_double(repr, "x1", lg->x1.computed);
1019 sp_repr_set_svg_double(repr, "y1", lg->y1.computed);
1020 } else {
1021 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1022 }
1023 break;
1024 case POINT_LG_END:
1025 if (scale) {
1026 lg->x1.computed += (lg->x2.computed - p[Geom::X]);
1027 lg->y1.computed += (lg->y2.computed - p[Geom::Y]);
1028 }
1029 lg->x2.computed = p[Geom::X];
1030 lg->y2.computed = p[Geom::Y];
1031 if (write_repr) {
1032 if (scale) {
1033 sp_repr_set_svg_double(repr, "x1", lg->x1.computed);
1034 sp_repr_set_svg_double(repr, "y1", lg->y1.computed);
1035 }
1036 sp_repr_set_svg_double(repr, "x2", lg->x2.computed);
1037 sp_repr_set_svg_double(repr, "y2", lg->y2.computed);
1038 } else {
1039 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1040 }
1041 break;
1042 case POINT_LG_MID:
1043 {
1044 // using X-coordinates only to determine the offset, assuming p has been snapped to the vector from begin to end.
1045 Geom::Point begin(lg->x1.computed, lg->y1.computed);
1046 Geom::Point end(lg->x2.computed, lg->y2.computed);
1047 double offset = Geom::LineSegment(begin, end).nearestTime(p);
1048 SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (lg, false);
1049 lg->ensureVector();
1050 lg->vector.stops.at(point_i).offset = offset;
1051 SPStop* stopi = sp_get_stop_i(vector, point_i);
1052 stopi->offset = offset;
1053 if (write_repr) {
1054 sp_repr_set_css_double(stopi->getRepr(), "offset", stopi->offset);
1055 } else {
1056 stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1057 }
1058 }
1059 break;
1060 default:
1061 g_warning( "Bad linear gradient handle type" );
1062 break;
1063 }
1064 } else if (SP_IS_RADIALGRADIENT(gradient)) {
1065 SPRadialGradient *rg = SP_RADIALGRADIENT(gradient);
1066 Geom::Point c (rg->cx.computed, rg->cy.computed);
1067 Geom::Point c_w = c * gradient->gradientTransform * i2d; // now in desktop coords
1068 if ((point_type == POINT_RG_R1 || point_type == POINT_RG_R2) && Geom::L2 (p_w - c_w) < 1e-3) {
1069 // prevent setting a radius too close to the center
1070 return;
1071 }
1072 Geom::Affine new_transform;
1073 bool transform_set = false;
1074
1075 switch (point_type) {
1076 case POINT_RG_CENTER:
1077 rg->fx.computed = p[Geom::X] + (rg->fx.computed - rg->cx.computed);
1078 rg->fy.computed = p[Geom::Y] + (rg->fy.computed - rg->cy.computed);
1079 rg->cx.computed = p[Geom::X];
1080 rg->cy.computed = p[Geom::Y];
1081 if (write_repr) {
1082 sp_repr_set_svg_double(repr, "fx", rg->fx.computed);
1083 sp_repr_set_svg_double(repr, "fy", rg->fy.computed);
1084 sp_repr_set_svg_double(repr, "cx", rg->cx.computed);
1085 sp_repr_set_svg_double(repr, "cy", rg->cy.computed);
1086 } else {
1087 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1088 }
1089 break;
1090 case POINT_RG_FOCUS:
1091 rg->fx.computed = p[Geom::X];
1092 rg->fy.computed = p[Geom::Y];
1093 if (write_repr) {
1094 sp_repr_set_svg_double(repr, "fx", rg->fx.computed);
1095 sp_repr_set_svg_double(repr, "fy", rg->fy.computed);
1096 } else {
1097 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1098 }
1099 break;
1100 case POINT_RG_R1:
1101 {
1102 Geom::Point r1_w = (c + Geom::Point(rg->r.computed, 0)) * gradient->gradientTransform * i2d;
1103 double r1_angle = Geom::atan2(r1_w - c_w);
1104 double move_angle = Geom::atan2(p_w - c_w) - r1_angle;
1105 double move_stretch = Geom::L2(p_w - c_w) / Geom::L2(r1_w - c_w);
1106
1107 Geom::Affine move = Geom::Affine (Geom::Translate (-c_w)) *
1108 Geom::Affine (Geom::Rotate(-r1_angle)) *
1109 Geom::Affine (Geom::Scale(move_stretch, scale? move_stretch : 1)) *
1110 Geom::Affine (Geom::Rotate(r1_angle)) *
1111 Geom::Affine (Geom::Rotate(move_angle)) *
1112 Geom::Affine (Geom::Translate (c_w));
1113
1114 new_transform = gradient->gradientTransform * i2d * move * i2d.inverse();
1115 transform_set = true;
1116
1117 break;
1118 }
1119 case POINT_RG_R2:
1120 {
1121 Geom::Point r2_w = (c + Geom::Point(0, -rg->r.computed)) * gradient->gradientTransform * i2d;
1122 double r2_angle = Geom::atan2(r2_w - c_w);
1123 double move_angle = Geom::atan2(p_w - c_w) - r2_angle;
1124 double move_stretch = Geom::L2(p_w - c_w) / Geom::L2(r2_w - c_w);
1125
1126 Geom::Affine move = Geom::Affine (Geom::Translate (-c_w)) *
1127 Geom::Affine (Geom::Rotate(-r2_angle)) *
1128 Geom::Affine (Geom::Scale(move_stretch, scale? move_stretch : 1)) *
1129 Geom::Affine (Geom::Rotate(r2_angle)) *
1130 Geom::Affine (Geom::Rotate(move_angle)) *
1131 Geom::Affine (Geom::Translate (c_w));
1132
1133 new_transform = gradient->gradientTransform * i2d * move * i2d.inverse();
1134 transform_set = true;
1135
1136 break;
1137 }
1138 case POINT_RG_MID1:
1139 {
1140 Geom::Point start = Geom::Point (rg->cx.computed, rg->cy.computed);
1141 Geom::Point end = Geom::Point (rg->cx.computed + rg->r.computed, rg->cy.computed);
1142 double offset = Geom::LineSegment(start, end).nearestTime(p);
1143 SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (rg, false);
1144 rg->ensureVector();
1145 rg->vector.stops.at(point_i).offset = offset;
1146 SPStop* stopi = sp_get_stop_i(vector, point_i);
1147 stopi->offset = offset;
1148 if (write_repr) {
1149 sp_repr_set_css_double(stopi->getRepr(), "offset", stopi->offset);
1150 } else {
1151 stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1152 }
1153 break;
1154 }
1155 case POINT_RG_MID2:
1156 {
1157 Geom::Point start = Geom::Point (rg->cx.computed, rg->cy.computed);
1158 Geom::Point end = Geom::Point (rg->cx.computed, rg->cy.computed - rg->r.computed);
1159 double offset = Geom::LineSegment(start, end).nearestTime(p);
1160 SPGradient *vector = sp_gradient_get_forked_vector_if_necessary(rg, false);
1161 rg->ensureVector();
1162 rg->vector.stops.at(point_i).offset = offset;
1163 SPStop* stopi = sp_get_stop_i(vector, point_i);
1164 stopi->offset = offset;
1165 if (write_repr) {
1166 sp_repr_set_css_double(stopi->getRepr(), "offset", stopi->offset);
1167 } else {
1168 stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1169 }
1170 break;
1171 }
1172 default:
1173 g_warning( "Bad radial gradient handle type" );
1174 break;
1175 }
1176
1177 if (transform_set) {
1178 gradient->gradientTransform = new_transform;
1179 gradient->gradientTransform_set = TRUE;
1180 if (write_repr) {
1181 auto s = sp_svg_transform_write(gradient->gradientTransform);
1182 gradient->setAttributeOrRemoveIfEmpty("gradientTransform", s);
1183 } else {
1184 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1185 }
1186 }
1187 } else if (SP_IS_MESHGRADIENT(gradient)) {
1188 SPMeshGradient *mg = SP_MESHGRADIENT(gradient);
1189 //Geom::Affine new_transform;
1190 //bool transform_set = false;
1191
1192 switch (point_type) {
1193 case POINT_MG_CORNER:
1194 {
1195 mg->array.corners[ point_i ]->p = p;
1196 // Handles are moved in gradient-drag.cpp
1197 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1198 break;
1199 }
1200
1201 case POINT_MG_HANDLE: {
1202 mg->array.handles[ point_i ]->p = p;
1203 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1204 break;
1205 }
1206
1207 case POINT_MG_TENSOR: {
1208 mg->array.tensors[ point_i ]->p = p;
1209 gradient->requestModified(SP_OBJECT_MODIFIED_FLAG);
1210 break;
1211 }
1212
1213 default:
1214 g_warning( "Bad mesh handle type" );
1215 }
1216 if( write_repr ) {
1217 //std::cout << "Write mesh repr" << std::endl;
1218 mg->array.write( mg );
1219 }
1220 }
1221
1222 }
1223
sp_item_gradient_get_vector(SPItem * item,Inkscape::PaintTarget fill_or_stroke)1224 SPGradient *sp_item_gradient_get_vector(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
1225 {
1226 SPGradient *gradient = getGradient(item, fill_or_stroke);
1227
1228 if (gradient) {
1229 return gradient->getVector();
1230 }
1231 return nullptr;
1232 }
1233
sp_item_gradient_get_spread(SPItem * item,Inkscape::PaintTarget fill_or_stroke)1234 SPGradientSpread sp_item_gradient_get_spread(SPItem *item, Inkscape::PaintTarget fill_or_stroke)
1235 {
1236 SPGradientSpread spread = SP_GRADIENT_SPREAD_PAD;
1237 SPGradient *gradient = getGradient(item, fill_or_stroke);
1238
1239 if (gradient) {
1240 spread = gradient->fetchSpread();
1241 }
1242 return spread;
1243 }
1244
1245
1246 /**
1247 Returns the position of point point_type of the gradient applied to item (either fill_or_stroke),
1248 in desktop coordinates.
1249 */
getGradientCoords(SPItem * item,GrPointType point_type,guint point_i,Inkscape::PaintTarget fill_or_stroke)1250 Geom::Point getGradientCoords(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke)
1251 {
1252 SPGradient *gradient = getGradient(item, fill_or_stroke);
1253 #ifdef SP_GR_VERBOSE
1254 g_message("getGradientCoords(%p, %d, %d, %d, %p)", item, point_type, point_i, fill_or_stroke, gradient);
1255 #endif
1256
1257 Geom::Point p (0, 0);
1258
1259 if (!gradient)
1260 return p;
1261
1262 if (SP_IS_LINEARGRADIENT(gradient)) {
1263 SPLinearGradient *lg = SP_LINEARGRADIENT(gradient);
1264 switch (point_type) {
1265 case POINT_LG_BEGIN:
1266 p = Geom::Point (lg->x1.computed, lg->y1.computed);
1267 break;
1268 case POINT_LG_END:
1269 p = Geom::Point (lg->x2.computed, lg->y2.computed);
1270 break;
1271 case POINT_LG_MID:
1272 {
1273 if (lg->vector.stops.size() < point_i) {
1274 g_message("POINT_LG_MID bug trigger, see LP bug #453067");
1275 break;
1276 }
1277 gdouble offset = lg->vector.stops.at(point_i).offset;
1278 p = (1-offset) * Geom::Point(lg->x1.computed, lg->y1.computed) + offset * Geom::Point(lg->x2.computed, lg->y2.computed);
1279 }
1280 break;
1281 default:
1282 g_warning( "Bad linear gradient handle type" );
1283 break;
1284 }
1285 } else if (SP_IS_RADIALGRADIENT(gradient)) {
1286 SPRadialGradient *rg = SP_RADIALGRADIENT(gradient);
1287 switch (point_type) {
1288 case POINT_RG_CENTER:
1289 p = Geom::Point (rg->cx.computed, rg->cy.computed);
1290 break;
1291 case POINT_RG_FOCUS:
1292 p = Geom::Point (rg->fx.computed, rg->fy.computed);
1293 break;
1294 case POINT_RG_R1:
1295 p = Geom::Point (rg->cx.computed + rg->r.computed, rg->cy.computed);
1296 break;
1297 case POINT_RG_R2:
1298 p = Geom::Point (rg->cx.computed, rg->cy.computed - rg->r.computed);
1299 break;
1300 case POINT_RG_MID1:
1301 {
1302 if (rg->vector.stops.size() < point_i) {
1303 g_message("POINT_RG_MID1 bug trigger, see LP bug #453067");
1304 break;
1305 }
1306 gdouble offset = rg->vector.stops.at(point_i).offset;
1307 p = (1-offset) * Geom::Point (rg->cx.computed, rg->cy.computed) + offset * Geom::Point(rg->cx.computed + rg->r.computed, rg->cy.computed);
1308 }
1309 break;
1310 case POINT_RG_MID2:
1311 {
1312 if (rg->vector.stops.size() < point_i) {
1313 g_message("POINT_RG_MID2 bug trigger, see LP bug #453067");
1314 break;
1315 }
1316 gdouble offset = rg->vector.stops.at(point_i).offset;
1317 p = (1-offset) * Geom::Point (rg->cx.computed, rg->cy.computed) + offset * Geom::Point(rg->cx.computed, rg->cy.computed - rg->r.computed);
1318 }
1319 break;
1320 default:
1321 g_warning( "Bad radial gradient handle type" );
1322 break;
1323 }
1324 } else if (SP_IS_MESHGRADIENT(gradient)) {
1325 SPMeshGradient *mg = SP_MESHGRADIENT(gradient);
1326 switch (point_type) {
1327
1328 case POINT_MG_CORNER:
1329 p = mg->array.corners[ point_i ]->p;
1330 break;
1331
1332 case POINT_MG_HANDLE: {
1333 p = mg->array.handles[ point_i ]->p;
1334 break;
1335 }
1336
1337 case POINT_MG_TENSOR: {
1338 p = mg->array.tensors[ point_i ]->p;
1339 break;
1340 }
1341
1342 default:
1343 g_warning( "Bad mesh handle type" );
1344 }
1345 }
1346
1347
1348 if (SP_GRADIENT(gradient)->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) {
1349 item->document->ensureUpToDate();
1350 Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine
1351 if (bbox) {
1352 p *= Geom::Affine(bbox->dimensions()[Geom::X], 0,
1353 0, bbox->dimensions()[Geom::Y],
1354 bbox->min()[Geom::X], bbox->min()[Geom::Y]);
1355 }
1356 }
1357 p *= Geom::Affine(gradient->gradientTransform) * (Geom::Affine)item->i2dt_affine();
1358 return p;
1359 }
1360
1361 /**
1362 * Sets item fill or stroke to the gradient of the specified type with given vector, creating
1363 * new private gradient, if needed.
1364 * gr has to be a normalized vector.
1365 */
1366
sp_item_set_gradient(SPItem * item,SPGradient * gr,SPGradientType type,Inkscape::PaintTarget fill_or_stroke)1367 SPGradient *sp_item_set_gradient(SPItem *item, SPGradient *gr, SPGradientType type, Inkscape::PaintTarget fill_or_stroke)
1368 {
1369 #ifdef SP_GR_VERBOSE
1370 g_message("sp_item_set_gradient(%p, %p, %d, %d)", item, gr, type, fill_or_stroke);
1371 #endif
1372 g_return_val_if_fail(item != nullptr, NULL);
1373 g_return_val_if_fail(SP_IS_ITEM(item), NULL);
1374 g_return_val_if_fail(gr != nullptr, NULL);
1375 g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
1376 g_return_val_if_fail(gr->state == SP_GRADIENT_STATE_VECTOR, NULL);
1377
1378 SPStyle *style = item->style;
1379 g_assert(style != nullptr);
1380
1381 SPPaintServer *ps = nullptr;
1382 if ((fill_or_stroke == Inkscape::FOR_FILL) ? style->fill.isPaintserver() : style->stroke.isPaintserver()) {
1383 ps = (fill_or_stroke == Inkscape::FOR_FILL) ? SP_STYLE_FILL_SERVER(style) : SP_STYLE_STROKE_SERVER(style);
1384 }
1385
1386 if (ps
1387 && ( (type == SP_GRADIENT_TYPE_LINEAR && SP_IS_LINEARGRADIENT(ps)) ||
1388 (type == SP_GRADIENT_TYPE_RADIAL && SP_IS_RADIALGRADIENT(ps)) ) )
1389 {
1390
1391 /* Current fill style is the gradient of the required type */
1392 SPGradient *current = SP_GRADIENT(ps);
1393
1394 //g_message("hrefcount %d count %d\n", current->hrefcount, count_gradient_hrefs(item, current));
1395
1396 if (!current->isSwatch()
1397 && (current->hrefcount == 1 ||
1398 current->hrefcount == count_gradient_hrefs(item, current))) {
1399
1400 // current is private and it's either used once, or all its uses are by children of item;
1401 // so just change its href to vector
1402
1403 if ( current != gr && current->getVector() != gr ) {
1404 // href is not the vector
1405 sp_gradient_repr_set_link(current->getRepr(), gr);
1406 }
1407 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1408 return current;
1409
1410 } else {
1411
1412 // the gradient is not private, or it is shared with someone else;
1413 // normalize it (this includes creating new private if necessary)
1414 SPGradient *normalized = sp_gradient_fork_private_if_necessary(current, gr, type, item);
1415
1416 g_return_val_if_fail(normalized != nullptr, NULL);
1417
1418 if (normalized != current) {
1419
1420 /* We have to change object style here; recursive because this is used from
1421 * fill&stroke and must work for groups etc. */
1422 sp_style_set_property_url(item, (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke", normalized, true);
1423 }
1424 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1425 return normalized;
1426 }
1427
1428 } else {
1429 /* Current fill style is not a gradient or wrong type, so construct everything */
1430 /* This is where mesh gradients are constructed. */
1431 g_assert(SP_IS_GRADIENT(gr)); // TEMP
1432 SPGradient *constructed = sp_gradient_get_private_normalized(item->document, gr, type);
1433 constructed = sp_gradient_reset_to_userspace(constructed, item);
1434 sp_style_set_property_url(item, ( (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke" ), constructed, true);
1435 item->requestDisplayUpdate(( SP_OBJECT_MODIFIED_FLAG |
1436 SP_OBJECT_STYLE_MODIFIED_FLAG ));
1437 return constructed;
1438 }
1439 }
1440
sp_gradient_repr_set_link(Inkscape::XML::Node * repr,SPGradient * link)1441 static void sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *link)
1442 {
1443 #ifdef SP_GR_VERBOSE
1444 g_message("sp_gradient_repr_set_link(%p, %p)", repr, link);
1445 #endif
1446 g_return_if_fail(repr != nullptr);
1447 if (link) {
1448 g_return_if_fail(SP_IS_GRADIENT(link));
1449 }
1450
1451 if (link) {
1452 Glib::ustring ref("#");
1453 ref += link->getId();
1454 repr->setAttribute("xlink:href", ref);
1455 } else {
1456 repr->removeAttribute("xlink:href");
1457 }
1458 }
1459
1460
addStop(Inkscape::XML::Node * parent,Glib::ustring const & color,gint opacity,gchar const * offset)1461 static void addStop( Inkscape::XML::Node *parent, Glib::ustring const &color, gint opacity, gchar const *offset )
1462 {
1463 #ifdef SP_GR_VERBOSE
1464 g_message("addStop(%p, %s, %d, %s)", parent, color.c_str(), opacity, offset);
1465 #endif
1466 Inkscape::XML::Node *stop = parent->document()->createElement("svg:stop");
1467 {
1468 gchar *tmp = g_strdup_printf( "stop-color:%s;stop-opacity:%d;", color.c_str(), opacity );
1469 stop->setAttribute( "style", tmp );
1470 g_free(tmp);
1471 }
1472
1473 stop->setAttribute( "offset", offset );
1474
1475 parent->appendChild(stop);
1476 Inkscape::GC::release(stop);
1477 }
1478
1479 /*
1480 * Get default normalized gradient vector of document, create if there is none
1481 */
sp_document_default_gradient_vector(SPDocument * document,SPColor const & color,bool singleStop)1482 SPGradient *sp_document_default_gradient_vector( SPDocument *document, SPColor const &color, bool singleStop )
1483 {
1484 SPDefs *defs = document->getDefs();
1485 Inkscape::XML::Document *xml_doc = document->getReprDoc();
1486
1487 Inkscape::XML::Node *repr = xml_doc->createElement("svg:linearGradient");
1488
1489 if ( !singleStop ) {
1490 repr->setAttribute("inkscape:collect", "always");
1491 // set here, but removed when it's edited in the gradient editor
1492 // to further reduce clutter, we could
1493 // (1) here, search gradients by color and return what is found without duplication
1494 // (2) in fill & stroke, show only one copy of each gradient in list
1495 }
1496
1497 Glib::ustring colorStr = color.toString();
1498 addStop( repr, colorStr, 1, "0" );
1499 if ( !singleStop ) {
1500 addStop( repr, colorStr, 0, "1" );
1501 }
1502
1503 defs->getRepr()->addChild(repr, nullptr);
1504 Inkscape::GC::release(repr);
1505
1506 /* fixme: This does not look like nice */
1507 SPGradient *gr = static_cast<SPGradient *>(document->getObjectByRepr(repr));
1508 g_assert(gr != nullptr);
1509 g_assert(SP_IS_GRADIENT(gr));
1510 /* fixme: Maybe add extra sanity check here */
1511 gr->state = SP_GRADIENT_STATE_VECTOR;
1512
1513 return gr;
1514 }
1515
sp_gradient_vector_for_object(SPDocument * const doc,SPDesktop * const desktop,SPObject * const o,Inkscape::PaintTarget const fill_or_stroke,bool singleStop)1516 SPGradient *sp_gradient_vector_for_object( SPDocument *const doc, SPDesktop *const desktop,
1517 SPObject *const o, Inkscape::PaintTarget const fill_or_stroke, bool singleStop )
1518 {
1519 SPColor color;
1520 if ( (o == nullptr) || (o->style == nullptr) ) {
1521 color = sp_desktop_get_color(desktop, (fill_or_stroke == Inkscape::FOR_FILL));
1522 } else {
1523 // take the color of the object
1524 SPStyle const &style = *(o->style);
1525 SPIPaint const &paint = *style.getFillOrStroke(fill_or_stroke == Inkscape::FOR_FILL);
1526 if (paint.isPaintserver()) {
1527 SPObject *server = (fill_or_stroke == Inkscape::FOR_FILL) ? o->style->getFillPaintServer() : o->style->getStrokePaintServer();
1528 if ( SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server) ) {
1529 return SP_GRADIENT(server)->getVector(true);
1530 } else {
1531 color = sp_desktop_get_color(desktop, (fill_or_stroke == Inkscape::FOR_FILL));
1532 }
1533 } else if (paint.isColor()) {
1534 color = paint.value.color;
1535 } else {
1536 // if o doesn't use flat color, then take current color of the desktop.
1537 color = sp_desktop_get_color(desktop, (fill_or_stroke == Inkscape::FOR_FILL));
1538 }
1539 }
1540
1541 return sp_document_default_gradient_vector( doc, color, singleStop );
1542 }
1543
sp_gradient_invert_selected_gradients(SPDesktop * desktop,Inkscape::PaintTarget fill_or_stroke)1544 void sp_gradient_invert_selected_gradients(SPDesktop *desktop, Inkscape::PaintTarget fill_or_stroke)
1545 {
1546 Inkscape::Selection *selection = desktop->getSelection();
1547
1548 auto list= selection->items();
1549 for (auto i = list.begin(); i != list.end(); ++i) {
1550 sp_item_gradient_invert_vector_color(*i, fill_or_stroke);
1551 }
1552
1553 // we did an undoable action
1554 DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_GRADIENT,
1555 _("Invert gradient colors"));
1556 }
1557
sp_gradient_reverse_selected_gradients(SPDesktop * desktop)1558 void sp_gradient_reverse_selected_gradients(SPDesktop *desktop)
1559 {
1560 Inkscape::Selection *selection = desktop->getSelection();
1561 Inkscape::UI::Tools::ToolBase *ev = desktop->getEventContext();
1562
1563 if (!ev) {
1564 return;
1565 }
1566
1567 GrDrag *drag = ev->get_drag();
1568
1569 // First try selected dragger
1570 if (drag && !drag->selected.empty()) {
1571 drag->selected_reverse_vector();
1572 } else { // If no drag or no dragger selected, act on selection (both fill and stroke gradients)
1573 auto list= selection->items();
1574 for (auto i = list.begin(); i != list.end(); ++i) {
1575 sp_item_gradient_reverse_vector(*i, Inkscape::FOR_FILL);
1576 sp_item_gradient_reverse_vector(*i, Inkscape::FOR_STROKE);
1577 }
1578 }
1579
1580 // we did an undoable action
1581 DocumentUndo::done(desktop->getDocument(), SP_VERB_CONTEXT_GRADIENT,
1582 _("Reverse gradient"));
1583 }
1584
sp_gradient_unset_swatch(SPDesktop * desktop,std::string const & id)1585 void sp_gradient_unset_swatch(SPDesktop *desktop, std::string const &id)
1586 {
1587 SPDocument *doc = desktop ? desktop->doc() : nullptr;
1588
1589 if (doc) {
1590 const std::vector<SPObject *> gradients = doc->getResourceList("gradient");
1591 for (auto gradient : gradients) {
1592 SPGradient* grad = SP_GRADIENT(gradient);
1593 if ( id == grad->getId() ) {
1594 grad->setSwatch(false);
1595 DocumentUndo::done(doc, SP_VERB_CONTEXT_GRADIENT,
1596 _("Delete swatch"));
1597 break;
1598 }
1599 }
1600 }
1601 }
1602 /*
1603 Local Variables:
1604 mode:c++
1605 c-file-style:"stroustrup"
1606 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1607 indent-tabs-mode:nil
1608 fill-column:99
1609 End:
1610 */
1611 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1612