1 #define __SP_GRADIENT_CHEMISTRY_C__
3 /*
4 * Various utility methods for gradients
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak
9 *
10 * Copyright (C) 2001-2005 authors
11 * Copyright (C) 2001 Ximian, Inc.
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
17 #include "style.h"
18 #include "document-private.h"
19 #include "desktop-style.h"
21 #include "sp-gradient-reference.h"
22 #include "sp-linear-gradient.h"
23 #include "sp-radial-gradient.h"
24 #include "sp-stop.h"
25 #include "widgets/gradient-vector.h"
27 #include "sp-text.h"
28 #include "sp-tspan.h"
29 #include <libnr/nr-matrix-fns.h>
30 #include "xml/repr.h"
31 #include "svg/svg.h"
34 // Terminology:
35 //
36 // "vector" is a gradient that has stops but not position coords. It can be referenced by one or
37 // more privates. Objects should not refer to it directly. It has no radial/linear distinction.
38 //
39 // "private" is a gradient that has no stops but has position coords (e.g. center, radius etc for a
40 // radial). It references a vector for the actual colors. Each private is only used by one
41 // object. It is either linear or radial.
43 static void sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *gr);
44 static void sp_item_repr_set_style_gradient(Inkscape::XML::Node *repr, gchar const *property,
45 SPGradient *gr, bool recursive);
47 SPGradient *
48 sp_gradient_ensure_vector_normalized(SPGradient *gr)
49 {
50 g_return_val_if_fail(gr != NULL, NULL);
51 g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
53 /* If we are already normalized vector, just return */
54 if (gr->state == SP_GRADIENT_STATE_VECTOR) return gr;
55 /* Fail, if we have wrong state set */
56 if (gr->state != SP_GRADIENT_STATE_UNKNOWN) {
57 g_warning("file %s: line %d: Cannot normalize private gradient to vector (%s)", __FILE__, __LINE__, SP_OBJECT_ID(gr));
58 return NULL;
59 }
61 /* First make sure we have vector directly defined (i.e. gr has its own stops) */
62 if (!gr->has_stops) {
63 /* We do not have stops ourselves, so flatten stops as well */
64 sp_gradient_ensure_vector(gr);
65 g_assert(gr->vector.built);
66 // this adds stops from gr->vector as children to gr
67 sp_gradient_repr_write_vector (gr);
68 }
70 /* If gr hrefs some other gradient, remove the href */
71 if (gr->ref->getObject()) {
72 /* We are hrefing someone, so require flattening */
73 SP_OBJECT(gr)->updateRepr(((SPObject *) gr)->repr, SP_OBJECT_WRITE_EXT | SP_OBJECT_WRITE_ALL);
74 sp_gradient_repr_set_link(SP_OBJECT_REPR(gr), NULL);
75 }
77 /* Everything is OK, set state flag */
78 gr->state = SP_GRADIENT_STATE_VECTOR;
79 return gr;
80 }
82 /**
83 * Creates new private gradient for the given vector
84 */
86 static SPGradient *
87 sp_gradient_get_private_normalized(SPDocument *document, SPGradient *vector, SPGradientType type)
88 {
89 g_return_val_if_fail(document != NULL, NULL);
90 g_return_val_if_fail(vector != NULL, NULL);
91 g_return_val_if_fail(SP_IS_GRADIENT(vector), NULL);
92 g_return_val_if_fail(SP_GRADIENT_HAS_STOPS(vector), NULL);
94 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
96 // create a new private gradient of the requested type
97 Inkscape::XML::Node *repr;
98 if (type == SP_GRADIENT_TYPE_LINEAR) {
99 repr = sp_repr_new("svg:linearGradient");
100 } else {
101 repr = sp_repr_new("svg:radialGradient");
102 }
104 // privates are garbage-collectable
105 repr->setAttribute("inkscape:collect", "always");
107 // link to vector
108 sp_gradient_repr_set_link(repr, vector);
110 /* Append the new private gradient to defs */
111 SP_OBJECT_REPR(defs)->appendChild(repr);
112 Inkscape::GC::release(repr);
114 // get corresponding object
115 SPGradient *gr = (SPGradient *) document->getObjectByRepr(repr);
116 g_assert(gr != NULL);
117 g_assert(SP_IS_GRADIENT(gr));
119 return gr;
120 }
122 /**
123 Count how many times gr is used by the styles of o and its descendants
124 */
125 guint
126 count_gradient_hrefs(SPObject *o, SPGradient *gr)
127 {
128 if (!o)
129 return 1;
131 guint i = 0;
133 SPStyle *style = SP_OBJECT_STYLE(o);
134 if (style
135 && style->fill.type == SP_PAINT_TYPE_PAINTSERVER
136 && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style))
137 && SP_GRADIENT(SP_STYLE_FILL_SERVER(style)) == gr)
138 {
139 i ++;
140 }
141 if (style
142 && style->stroke.type == SP_PAINT_TYPE_PAINTSERVER
143 && SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style))
144 && SP_GRADIENT(SP_STYLE_STROKE_SERVER(style)) == gr)
145 {
146 i ++;
147 }
149 for (SPObject *child = sp_object_first_child(o);
150 child != NULL; child = SP_OBJECT_NEXT(child)) {
151 i += count_gradient_hrefs(child, gr);
152 }
154 return i;
155 }
158 /**
159 * If gr has other users, create a new private; also check if gr links to vector, relink if not
160 */
161 SPGradient *
162 sp_gradient_fork_private_if_necessary(SPGradient *gr, SPGradient *vector,
163 SPGradientType type, SPObject *o)
164 {
165 g_return_val_if_fail(gr != NULL, NULL);
166 g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
168 // Orphaned gradient, no vector with stops at the end of the line; this used to be an assert
169 // but i think we should not abort on this - maybe just write a validity warning into some sort
170 // of log
171 if (!vector || !SP_GRADIENT_HAS_STOPS(vector))
172 return (gr);
174 // user is the object that uses this gradient; normally it's item but for tspans, we
175 // check its ancestor text so that tspans don't get different gradients from their
176 // texts.
177 SPObject *user = o;
178 while (SP_IS_TSPAN(user)) {
179 user = SP_OBJECT_PARENT(user);
180 }
182 // Check the number of uses of the gradient within this object;
183 // if we are private and there are no other users,
184 if (SP_OBJECT_HREFCOUNT(gr) <= count_gradient_hrefs(user, gr)) {
185 // check vector
186 if ( gr != vector && gr->ref->getObject() != vector ) {
187 /* our href is not the vector, and vector is different from gr; relink */
188 sp_gradient_repr_set_link(SP_OBJECT_REPR(gr), vector);
189 }
190 return gr;
191 }
193 SPDocument *doc = SP_OBJECT_DOCUMENT(gr);
194 SPObject *defs = SP_DOCUMENT_DEFS(doc);
196 if ((gr->has_stops) ||
197 (gr->state != SP_GRADIENT_STATE_UNKNOWN) ||
198 (SP_OBJECT_PARENT(gr) != SP_OBJECT(defs)) ||
199 (SP_OBJECT_HREFCOUNT(gr) > 1)) {
200 // we have to clone a fresh new private gradient for the given vector
202 // create an empty one
203 SPGradient *gr_new = sp_gradient_get_private_normalized(doc, vector, type);
205 // copy all the attributes to it
206 Inkscape::XML::Node *repr_new = SP_OBJECT_REPR(gr_new);
207 Inkscape::XML::Node *repr = SP_OBJECT_REPR(gr);
208 repr_new->setAttribute("gradientUnits", repr->attribute("gradientUnits"));
209 repr_new->setAttribute("gradientTransform", repr->attribute("gradientTransform"));
210 repr_new->setAttribute("spreadMethod", repr->attribute("spreadMethod"));
211 if (SP_IS_RADIALGRADIENT(gr)) {
212 repr_new->setAttribute("cx", repr->attribute("cx"));
213 repr_new->setAttribute("cy", repr->attribute("cy"));
214 repr_new->setAttribute("fx", repr->attribute("fx"));
215 repr_new->setAttribute("fy", repr->attribute("fy"));
216 repr_new->setAttribute("r", repr->attribute("r"));
217 } else {
218 repr_new->setAttribute("x1", repr->attribute("x1"));
219 repr_new->setAttribute("y1", repr->attribute("y1"));
220 repr_new->setAttribute("x2", repr->attribute("x2"));
221 repr_new->setAttribute("y2", repr->attribute("y2"));
222 }
224 return gr_new;
225 } else {
226 return gr;
227 }
228 }
230 SPGradient *
231 sp_gradient_fork_vector_if_necessary (SPGradient *gr)
232 {
233 if (SP_OBJECT_HREFCOUNT(gr) > 1) {
234 SPDocument *document = SP_OBJECT_DOCUMENT(gr);
236 Inkscape::XML::Node *repr = SP_OBJECT_REPR (gr)->duplicate();
237 SP_OBJECT_REPR (SP_DOCUMENT_DEFS (document))->addChild(repr, NULL);
238 SPGradient *gr_new = (SPGradient *) document->getObjectByRepr(repr);
239 gr_new = sp_gradient_ensure_vector_normalized (gr_new);
240 Inkscape::GC::release(repr);
241 return gr_new;
242 }
243 return gr;
244 }
246 /**
247 * Convert an item's gradient to userspace _without_ preserving coords, setting them to defaults
248 * instead. No forking or reapplying is done because this is only called for newly created privates.
249 * @return The new gradient.
250 */
251 SPGradient *
252 sp_gradient_reset_to_userspace (SPGradient *gr, SPItem *item)
253 {
254 Inkscape::XML::Node *repr = SP_OBJECT_REPR(gr);
256 // calculate the bbox of the item
257 sp_document_ensure_up_to_date(SP_OBJECT_DOCUMENT(item));
258 NR::Rect const bbox = item->invokeBbox(NR::identity()); // we need "true" bbox without item_i2d_affine
260 NR::Coord const width = bbox.dimensions()[NR::X];
261 NR::Coord const height = bbox.dimensions()[NR::Y];
262 g_assert(width > 0 && height > 0);
264 NR::Point const center = bbox.midpoint();
266 if (SP_IS_RADIALGRADIENT(gr)) {
267 sp_repr_set_svg_double(repr, "cx", center[NR::X]);
268 sp_repr_set_svg_double(repr, "cy", center[NR::Y]);
269 sp_repr_set_svg_double(repr, "fx", center[NR::X]);
270 sp_repr_set_svg_double(repr, "fy", center[NR::Y]);
271 sp_repr_set_svg_double(repr, "r", width/2);
273 // we want it to be elliptic, not circular
274 NR::Matrix squeeze = NR::Matrix (NR::translate (-center)) *
275 NR::Matrix (NR::scale(1, height/width)) *
276 NR::Matrix (NR::translate (center));
278 gr->gradientTransform = squeeze;
279 {
280 gchar c[256];
281 if (sp_svg_transform_write(c, 256, gr->gradientTransform)) {
282 SP_OBJECT_REPR(gr)->setAttribute("gradientTransform", c);
283 } else {
284 SP_OBJECT_REPR(gr)->setAttribute("gradientTransform", NULL);
285 }
286 }
287 } else {
288 sp_repr_set_svg_double(repr, "x1", (center - NR::Point(width/2, 0))[NR::X]);
289 sp_repr_set_svg_double(repr, "y1", (center - NR::Point(width/2, 0))[NR::Y]);
290 sp_repr_set_svg_double(repr, "x2", (center + NR::Point(width/2, 0))[NR::X]);
291 sp_repr_set_svg_double(repr, "y2", (center + NR::Point(width/2, 0))[NR::Y]);
292 }
294 // set the gradientUnits
295 repr->setAttribute("gradientUnits", "userSpaceOnUse");
297 return gr;
298 }
300 /**
301 * Convert an item's gradient to userspace if necessary, also fork it if necessary.
302 * @return The new gradient.
303 */
304 SPGradient *
305 sp_gradient_convert_to_userspace(SPGradient *gr, SPItem *item, gchar const *property)
306 {
307 g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
309 // First, fork it if it is shared
310 gr = sp_gradient_fork_private_if_necessary(gr, sp_gradient_get_vector(gr, FALSE),
311 SP_IS_RADIALGRADIENT(gr) ? SP_GRADIENT_TYPE_RADIAL : SP_GRADIENT_TYPE_LINEAR, SP_OBJECT(item));
313 if (gr->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) {
315 Inkscape::XML::Node *repr = SP_OBJECT_REPR(gr);
317 // calculate the bbox of the item
318 sp_document_ensure_up_to_date(SP_OBJECT_DOCUMENT(item));
319 NR::Rect const bbox = item->invokeBbox(NR::identity()); // we need "true" bbox without item_i2d_affine
320 NR::Matrix bbox2user(bbox.dimensions()[NR::X], 0,
321 0, bbox.dimensions()[NR::Y],
322 bbox.min()[NR::X], bbox.min()[NR::Y]);
324 /* skew is the additional transform, defined by the proportions of the item, that we need
325 * to apply to the gradient in order to work around this weird bit from SVG 1.1
326 * (http://www.w3.org/TR/SVG11/pservers.html#LinearGradients):
327 *
328 * When gradientUnits="objectBoundingBox" and gradientTransform is the identity
329 * matrix, the stripes of the linear gradient are perpendicular to the gradient
330 * vector in object bounding box space (i.e., the abstract coordinate system where
331 * (0,0) is at the top/left of the object bounding box and (1,1) is at the
332 * bottom/right of the object bounding box). When the object's bounding box is not
333 * square, the stripes that are conceptually perpendicular to the gradient vector
334 * within object bounding box space will render non-perpendicular relative to the
335 * gradient vector in user space due to application of the non-uniform scaling
336 * transformation from bounding box space to user space.
337 */
338 NR::Matrix skew = bbox2user;
339 double exp = skew.expansion();
340 skew[0] /= exp;
341 skew[1] /= exp;
342 skew[2] /= exp;
343 skew[3] /= exp;
344 skew[4] = 0;
345 skew[5] = 0;
347 // apply skew to the gradient
348 gr->gradientTransform = skew;
349 {
350 gchar c[256];
351 if (sp_svg_transform_write(c, 256, gr->gradientTransform)) {
352 SP_OBJECT_REPR(gr)->setAttribute("gradientTransform", c);
353 } else {
354 SP_OBJECT_REPR(gr)->setAttribute("gradientTransform", NULL);
355 }
356 }
358 // Matrix to convert points to userspace coords; postmultiply by inverse of skew so
359 // as to cancel it out when it's applied to the gradient during rendering
360 NR::Matrix point_convert = bbox2user * skew.inverse();
362 if (SP_IS_RADIALGRADIENT(gr)) {
363 SPRadialGradient *rg = SP_RADIALGRADIENT(gr);
365 // original points in the bbox coords
366 NR::Point c_b = NR::Point(rg->cx.computed, rg->cy.computed);
367 NR::Point f_b = NR::Point(rg->fx.computed, rg->fy.computed);
368 double r_b = rg->r.computed;
370 // converted points in userspace coords
371 NR::Point c_u = c_b * point_convert;
372 NR::Point f_u = f_b * point_convert;
373 double r_u = r_b * point_convert.expansion();
375 sp_repr_set_svg_double(repr, "cx", c_u[NR::X]);
376 sp_repr_set_svg_double(repr, "cy", c_u[NR::Y]);
377 sp_repr_set_svg_double(repr, "fx", f_u[NR::X]);
378 sp_repr_set_svg_double(repr, "fy", f_u[NR::Y]);
379 sp_repr_set_svg_double(repr, "r", r_u);
381 } else {
382 SPLinearGradient *lg = SP_LINEARGRADIENT(gr);
384 NR::Point p1_b = NR::Point(lg->x1.computed, lg->y1.computed);
385 NR::Point p2_b = NR::Point(lg->x2.computed, lg->y2.computed);
387 NR::Point p1_u = p1_b * point_convert;
388 NR::Point p2_u = p2_b * point_convert;
390 sp_repr_set_svg_double(repr, "x1", p1_u[NR::X]);
391 sp_repr_set_svg_double(repr, "y1", p1_u[NR::Y]);
392 sp_repr_set_svg_double(repr, "x2", p2_u[NR::X]);
393 sp_repr_set_svg_double(repr, "y2", p2_u[NR::Y]);
394 }
396 // set the gradientUnits
397 repr->setAttribute("gradientUnits", "userSpaceOnUse");
398 }
400 // apply the gradient to the item (may be necessary if we forked it); not recursive
401 // generally because grouped items will be taken care of later (we're being called
402 // from sp_item_adjust_paint_recursive); however text and all its children should all
403 // refer to one gradient, hence the recursive call for text (because we can't/don't
404 // want to access tspans and set gradients on them separately)
405 if (SP_IS_TEXT(item))
406 sp_item_repr_set_style_gradient(SP_OBJECT_REPR(item), property, gr, true);
407 else
408 sp_item_repr_set_style_gradient(SP_OBJECT_REPR(item), property, gr, false);
410 return gr;
411 }
413 void
414 sp_gradient_transform_multiply(SPGradient *gradient, NR::Matrix postmul, bool set)
415 {
416 if (set) {
417 gradient->gradientTransform = postmul;
418 } else {
419 gradient->gradientTransform *= postmul; // fixme: get gradient transform by climbing to hrefs?
420 }
421 gradient->gradientTransform_set = TRUE;
423 gchar c[256];
424 if (sp_svg_transform_write(c, 256, gradient->gradientTransform)) {
425 SP_OBJECT_REPR(gradient)->setAttribute("gradientTransform", c);
426 } else {
427 SP_OBJECT_REPR(gradient)->setAttribute("gradientTransform", NULL);
428 }
429 }
431 SPGradient *
432 sp_item_gradient (SPItem *item, bool fill_or_stroke)
433 {
434 SPStyle *style = SP_OBJECT_STYLE (item);
435 SPGradient *gradient = NULL;
437 if (fill_or_stroke) {
438 if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
439 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
440 if (SP_IS_GRADIENT (server)) {
441 gradient = SP_GRADIENT (server);
442 }
443 }
444 } else {
445 if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
446 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item);
447 if (SP_IS_GRADIENT (server)) {
448 gradient = SP_GRADIENT (server);
449 }
450 }
451 }
453 return gradient;
454 }
457 SPStop*
458 sp_first_stop(SPGradient *gradient)
459 {
460 for (SPObject *ochild = sp_object_first_child(gradient); ochild != NULL; ochild = SP_OBJECT_NEXT(ochild)) {
461 if (SP_IS_STOP (ochild))
462 return SP_STOP(ochild);
463 }
464 return NULL;
465 }
467 SPStop*
468 sp_prev_stop(SPStop *stop, SPGradient *gradient)
469 {
470 if (sp_object_first_child(SP_OBJECT(gradient)) == SP_OBJECT(stop))
471 return NULL;
472 SPObject *found = NULL;
473 for ( SPObject *ochild = sp_object_first_child(SP_OBJECT(gradient)) ; ochild != NULL ; ochild = SP_OBJECT_NEXT(ochild) ) {
474 if (SP_IS_STOP (ochild)) {
475 found = ochild;
476 }
477 if (SP_OBJECT_NEXT(ochild) == SP_OBJECT(stop) || SP_OBJECT(ochild) == SP_OBJECT(stop)) {
478 break;
479 }
480 }
481 return SP_STOP(found);
482 }
484 SPStop*
485 sp_next_stop(SPStop *stop)
486 {
487 for (SPObject *ochild = SP_OBJECT_NEXT(stop); ochild != NULL; ochild = SP_OBJECT_NEXT(ochild)) {
488 if (SP_IS_STOP (ochild))
489 return SP_STOP(ochild);
490 }
491 return NULL;
492 }
494 SPStop*
495 sp_last_stop(SPGradient *gradient)
496 {
497 for (SPStop *stop = sp_first_stop (gradient); stop != NULL; stop = sp_next_stop (stop)) {
498 if (sp_next_stop (stop) == NULL)
499 return stop;
500 }
501 return NULL;
502 }
505 void
506 sp_item_gradient_edit_stop (SPItem *item, guint point_num, bool fill_or_stroke)
507 {
508 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
510 if (!gradient || !SP_IS_GRADIENT(gradient))
511 return;
513 SPGradient *vector = sp_gradient_get_vector (gradient, false);
514 switch (point_num) {
515 case POINT_LG_P1:
516 case POINT_RG_CENTER:
517 case POINT_RG_FOCUS:
518 {
519 GtkWidget *dialog = sp_gradient_vector_editor_new (vector, sp_first_stop (vector));
520 gtk_widget_show (dialog);
521 }
522 break;
524 case POINT_LG_P2:
525 case POINT_RG_R1:
526 case POINT_RG_R2:
527 {
528 GtkWidget *dialog = sp_gradient_vector_editor_new (vector, sp_last_stop (vector));
529 gtk_widget_show (dialog);
530 }
531 break;
532 default:
533 break;
534 }
535 }
537 guint32
538 sp_item_gradient_stop_query_style (SPItem *item, guint point_num, bool fill_or_stroke)
539 {
540 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
542 if (!gradient || !SP_IS_GRADIENT(gradient))
543 return 0;
545 SPGradient *vector = sp_gradient_get_vector (gradient, false);
547 if (!vector) // orphan!
548 return 0; // what else to do?
550 switch (point_num) {
551 case POINT_LG_P1:
552 case POINT_RG_CENTER:
553 case POINT_RG_FOCUS:
554 {
555 SPStop *first = sp_first_stop (vector);
556 if (first) {
557 return sp_stop_get_rgba32(first);
558 }
559 }
560 break;
562 case POINT_LG_P2:
563 case POINT_RG_R1:
564 case POINT_RG_R2:
565 {
566 SPStop *last = sp_last_stop (vector);
567 if (last) {
568 return sp_stop_get_rgba32(last);
569 }
570 }
571 break;
572 default:
573 break;
574 }
575 return 0;
576 }
578 void
579 sp_item_gradient_stop_set_style (SPItem *item, guint point_num, bool fill_or_stroke, SPCSSAttr *stop)
580 {
581 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
583 if (!gradient || !SP_IS_GRADIENT(gradient))
584 return;
586 SPGradient *vector = sp_gradient_get_vector (gradient, false);
588 if (!vector) // orphan!
589 return;
591 vector = sp_gradient_fork_vector_if_necessary (vector);
592 if ( gradient != vector && gradient->ref->getObject() != vector ) {
593 sp_gradient_repr_set_link(SP_OBJECT_REPR(gradient), vector);
594 }
596 switch (point_num) {
597 case POINT_LG_P1:
598 case POINT_RG_CENTER:
599 case POINT_RG_FOCUS:
600 {
601 SPStop *first = sp_first_stop (vector);
602 if (first) {
603 sp_repr_css_change (SP_OBJECT_REPR (first), stop, "style");
604 }
605 }
606 break;
608 case POINT_LG_P2:
609 case POINT_RG_R1:
610 case POINT_RG_R2:
611 {
612 SPStop *last = sp_last_stop (vector);
613 if (last) {
614 sp_repr_css_change (SP_OBJECT_REPR (last), stop, "style");
615 }
616 }
617 break;
618 default:
619 break;
620 }
621 }
623 void
624 sp_item_gradient_reverse_vector (SPItem *item, bool fill_or_stroke)
625 {
626 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
627 if (!gradient || !SP_IS_GRADIENT(gradient))
628 return;
630 SPGradient *vector = sp_gradient_get_vector (gradient, false);
631 if (!vector) // orphan!
632 return;
634 vector = sp_gradient_fork_vector_if_necessary (vector);
635 if ( gradient != vector && gradient->ref->getObject() != vector ) {
636 sp_gradient_repr_set_link(SP_OBJECT_REPR(gradient), vector);
637 }
639 GSList *child_reprs = NULL;
640 GSList *child_objects = NULL;
641 std::vector<double> offsets;
642 for (SPObject *child = sp_object_first_child(vector);
643 child != NULL; child = SP_OBJECT_NEXT(child)) {
644 child_reprs = g_slist_prepend (child_reprs, SP_OBJECT_REPR(child));
645 child_objects = g_slist_prepend (child_objects, child);
646 offsets.push_back(sp_repr_get_double_attribute(SP_OBJECT_REPR(child), "offset", 0));
647 }
649 GSList *child_copies = NULL;
650 for (GSList *i = child_reprs; i != NULL; i = i->next) {
651 Inkscape::XML::Node *repr = (Inkscape::XML::Node *) i->data;
652 child_copies = g_slist_append (child_copies, repr->duplicate());
653 }
656 for (GSList *i = child_objects; i != NULL; i = i->next) {
657 SPObject *child = SP_OBJECT (i->data);
658 child->deleteObject();
659 }
661 std::vector<double>::iterator iter = offsets.end() - 1;
662 for (GSList *i = child_copies; i != NULL; i = i->next) {
663 Inkscape::XML::Node *copy = (Inkscape::XML::Node *) i->data;
664 vector->appendChildRepr(copy);
665 sp_repr_set_svg_double (copy, "offset", 1 - *iter);
666 iter --;
667 Inkscape::GC::release(copy);
668 }
670 g_slist_free (child_reprs);
671 g_slist_free (child_copies);
672 g_slist_free (child_objects);
673 }
677 /**
678 Set the position of point point_num of the gradient applied to item (either fill_or_stroke) to
679 p_w (in desktop coordinates). Write_repr if you want the change to become permanent.
680 */
681 void
682 sp_item_gradient_set_coords (SPItem *item, guint point_num, NR::Point p_w, bool fill_or_stroke, bool write_repr, bool scale)
683 {
684 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
686 if (!gradient || !SP_IS_GRADIENT(gradient))
687 return;
689 gradient = sp_gradient_convert_to_userspace (gradient, item, fill_or_stroke? "fill" : "stroke");
691 NR::Matrix i2d = sp_item_i2d_affine (item);
692 NR::Point p = p_w * i2d.inverse();
693 p *= (gradient->gradientTransform).inverse();
694 // now p is in gradient's original coordinates
696 Inkscape::XML::Node *repr = SP_OBJECT_REPR(gradient);
698 if (SP_IS_LINEARGRADIENT(gradient)) {
699 SPLinearGradient *lg = SP_LINEARGRADIENT(gradient);
700 switch (point_num) {
701 case POINT_LG_P1:
702 if (scale) {
703 lg->x2.computed += (lg->x1.computed - p[NR::X]);
704 lg->y2.computed += (lg->y1.computed - p[NR::Y]);
705 }
706 lg->x1.computed = p[NR::X];
707 lg->y1.computed = p[NR::Y];
708 if (write_repr) {
709 if (scale) {
710 sp_repr_set_svg_double(repr, "x2", lg->x2.computed);
711 sp_repr_set_svg_double(repr, "y2", lg->y2.computed);
712 }
713 sp_repr_set_svg_double(repr, "x1", lg->x1.computed);
714 sp_repr_set_svg_double(repr, "y1", lg->y1.computed);
715 } else {
716 SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG);
717 }
718 break;
719 case POINT_LG_P2:
720 if (scale) {
721 lg->x1.computed += (lg->x2.computed - p[NR::X]);
722 lg->y1.computed += (lg->y2.computed - p[NR::Y]);
723 }
724 lg->x2.computed = p[NR::X];
725 lg->y2.computed = p[NR::Y];
726 if (write_repr) {
727 if (scale) {
728 sp_repr_set_svg_double(repr, "x1", lg->x1.computed);
729 sp_repr_set_svg_double(repr, "y1", lg->y1.computed);
730 }
731 sp_repr_set_svg_double(repr, "x2", lg->x2.computed);
732 sp_repr_set_svg_double(repr, "y2", lg->y2.computed);
733 } else {
734 SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG);
735 }
736 break;
737 default:
738 break;
739 }
740 } else if (SP_IS_RADIALGRADIENT(gradient)) {
742 SPRadialGradient *rg = SP_RADIALGRADIENT(gradient);
743 NR::Point c (rg->cx.computed, rg->cy.computed);
744 NR::Point c_w = c * gradient->gradientTransform * i2d; // now in desktop coords
745 if ((point_num == POINT_RG_R1 || point_num == POINT_RG_R2) && NR::L2 (p_w - c_w) < 1e-3) {
746 // prevent setting a radius too close to the center
747 return;
748 }
749 NR::Matrix new_transform;
750 bool transform_set = false;
752 switch (point_num) {
753 case POINT_RG_CENTER:
754 rg->fx.computed = p[NR::X] + (rg->fx.computed - rg->cx.computed);
755 rg->fy.computed = p[NR::Y] + (rg->fy.computed - rg->cy.computed);
756 rg->cx.computed = p[NR::X];
757 rg->cy.computed = p[NR::Y];
758 if (write_repr) {
759 sp_repr_set_svg_double(repr, "fx", rg->fx.computed);
760 sp_repr_set_svg_double(repr, "fy", rg->fy.computed);
761 sp_repr_set_svg_double(repr, "cx", rg->cx.computed);
762 sp_repr_set_svg_double(repr, "cy", rg->cy.computed);
763 } else {
764 SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG);
765 }
766 break;
767 case POINT_RG_FOCUS:
768 rg->fx.computed = p[NR::X];
769 rg->fy.computed = p[NR::Y];
770 if (write_repr) {
771 sp_repr_set_svg_double(repr, "fx", rg->fx.computed);
772 sp_repr_set_svg_double(repr, "fy", rg->fy.computed);
773 } else {
774 SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG);
775 }
776 break;
777 case POINT_RG_R1:
778 {
779 NR::Point r1_w = (c + NR::Point(rg->r.computed, 0)) * gradient->gradientTransform * i2d;
780 double r1_angle = NR::atan2(r1_w - c_w);
781 double move_angle = NR::atan2(p_w - c_w) - r1_angle;
782 double move_stretch = NR::L2(p_w - c_w) / NR::L2(r1_w - c_w);
784 NR::Matrix move = NR::Matrix (NR::translate (-c_w)) *
785 NR::Matrix (NR::rotate(-r1_angle)) *
786 NR::Matrix (NR::scale(move_stretch, scale? move_stretch : 1)) *
787 NR::Matrix (NR::rotate(r1_angle)) *
788 NR::Matrix (NR::rotate(move_angle)) *
789 NR::Matrix (NR::translate (c_w));
791 new_transform = gradient->gradientTransform * i2d * move * i2d.inverse();
792 transform_set = true;
794 break;
795 }
796 case POINT_RG_R2:
797 {
798 NR::Point r2_w = (c + NR::Point(0, -rg->r.computed)) * gradient->gradientTransform * i2d;
799 double r2_angle = NR::atan2(r2_w - c_w);
800 double move_angle = NR::atan2(p_w - c_w) - r2_angle;
801 double move_stretch = NR::L2(p_w - c_w) / NR::L2(r2_w - c_w);
803 NR::Matrix move = NR::Matrix (NR::translate (-c_w)) *
804 NR::Matrix (NR::rotate(-r2_angle)) *
805 NR::Matrix (NR::scale(move_stretch, scale? move_stretch : 1)) *
806 NR::Matrix (NR::rotate(r2_angle)) *
807 NR::Matrix (NR::rotate(move_angle)) *
808 NR::Matrix (NR::translate (c_w));
810 new_transform = gradient->gradientTransform * i2d * move * i2d.inverse();
811 transform_set = true;
813 break;
814 }
815 }
817 if (transform_set) {
818 gradient->gradientTransform = new_transform;
819 gradient->gradientTransform_set = TRUE;
820 if (write_repr) {
821 gchar s[256];
822 if (sp_svg_transform_write(s, 256, gradient->gradientTransform)) {
823 SP_OBJECT_REPR(gradient)->setAttribute("gradientTransform", s);
824 } else {
825 SP_OBJECT_REPR(gradient)->setAttribute("gradientTransform", NULL);
826 }
827 } else {
828 SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG);
829 }
830 }
831 }
832 }
834 SPGradient *
835 sp_item_gradient_get_vector (SPItem *item, bool fill_or_stroke)
836 {
837 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
839 if (gradient)
840 return sp_gradient_get_vector (gradient, false);
841 return NULL;
842 }
844 SPGradientSpread
845 sp_item_gradient_get_spread (SPItem *item, bool fill_or_stroke)
846 {
847 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
849 if (gradient)
850 return sp_gradient_get_spread (gradient);
851 return SP_GRADIENT_SPREAD_PAD;
852 }
855 /**
856 Returns the position of point point_num of the gradient applied to item (either fill_or_stroke),
857 in desktop coordinates.
858 */
860 NR::Point
861 sp_item_gradient_get_coords (SPItem *item, guint point_num, bool fill_or_stroke)
862 {
863 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
865 NR::Point p (0, 0);
867 if (!gradient)
868 return p;
870 if (SP_IS_LINEARGRADIENT(gradient)) {
871 SPLinearGradient *lg = SP_LINEARGRADIENT(gradient);
872 switch (point_num) {
873 case POINT_LG_P1:
874 p = NR::Point (lg->x1.computed, lg->y1.computed);
875 break;
876 case POINT_LG_P2:
877 p = NR::Point (lg->x2.computed, lg->y2.computed);
878 break;
879 }
880 } else if (SP_IS_RADIALGRADIENT(gradient)) {
881 SPRadialGradient *rg = SP_RADIALGRADIENT(gradient);
882 switch (point_num) {
883 case POINT_RG_CENTER:
884 p = NR::Point (rg->cx.computed, rg->cy.computed);
885 break;
886 case POINT_RG_FOCUS:
887 p = NR::Point (rg->fx.computed, rg->fy.computed);
888 break;
889 case POINT_RG_R1:
890 p = NR::Point (rg->cx.computed + rg->r.computed, rg->cy.computed);
891 break;
892 case POINT_RG_R2:
893 p = NR::Point (rg->cx.computed, rg->cy.computed - rg->r.computed);
894 break;
895 }
896 }
898 if (SP_GRADIENT(gradient)->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) {
899 sp_document_ensure_up_to_date(SP_OBJECT_DOCUMENT(item));
900 NR::Rect const bbox = item->invokeBbox(NR::identity()); // we need "true" bbox without item_i2d_affine
901 p *= NR::Matrix(bbox.dimensions()[NR::X], 0,
902 0, bbox.dimensions()[NR::Y],
903 bbox.min()[NR::X], bbox.min()[NR::Y]);
904 }
905 p *= NR::Matrix(gradient->gradientTransform) * sp_item_i2d_affine(item);
906 return p;
907 }
910 /**
911 * Sets item fill or stroke to the gradient of the specified type with given vector, creating
912 * new private gradient, if needed.
913 * gr has to be a normalized vector.
914 */
916 SPGradient *
917 sp_item_set_gradient(SPItem *item, SPGradient *gr, SPGradientType type, bool is_fill)
918 {
919 g_return_val_if_fail(item != NULL, NULL);
920 g_return_val_if_fail(SP_IS_ITEM(item), NULL);
921 g_return_val_if_fail(gr != NULL, NULL);
922 g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
923 g_return_val_if_fail(gr->state == SP_GRADIENT_STATE_VECTOR, NULL);
925 SPStyle *style = SP_OBJECT_STYLE(item);
926 g_assert(style != NULL);
928 guint style_type = is_fill? style->fill.type : style->stroke.type;
929 SPPaintServer *ps = NULL;
930 if (style_type == SP_PAINT_TYPE_PAINTSERVER)
931 ps = is_fill? SP_STYLE_FILL_SERVER(style) : SP_STYLE_STROKE_SERVER(style);
933 if (ps
934 && ( (type == SP_GRADIENT_TYPE_LINEAR && SP_IS_LINEARGRADIENT(ps)) ||
935 (type == SP_GRADIENT_TYPE_RADIAL && SP_IS_RADIALGRADIENT(ps)) ) )
936 {
938 /* Current fill style is the gradient of the required type */
939 SPGradient *current = SP_GRADIENT(ps);
941 //g_print("hrefcount %d count %d\n", SP_OBJECT_HREFCOUNT(ig), count_gradient_hrefs(SP_OBJECT(item), ig));
943 if (SP_OBJECT_HREFCOUNT(current) == 1 ||
944 SP_OBJECT_HREFCOUNT(current) == count_gradient_hrefs(SP_OBJECT(item), current)) {
946 // current is private and it's either used once, or all its uses are by children of item;
947 // so just change its href to vector
949 if ( current != gr && sp_gradient_get_vector(current, false) != gr ) {
950 /* href is not the vector */
951 sp_gradient_repr_set_link(SP_OBJECT_REPR(current), gr);
952 }
953 SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
954 return current;
956 } else {
958 // the gradient is not private, or it is shared with someone else;
959 // normalize it (this includes creating new private if necessary)
960 SPGradient *normalized = sp_gradient_fork_private_if_necessary(current, gr, type, item);
962 g_return_val_if_fail(normalized != NULL, NULL);
964 if (normalized != current) {
966 /* We have to change object style here; recursive because this is used from
967 * fill&stroke and must work for groups etc. */
968 sp_item_repr_set_style_gradient(SP_OBJECT_REPR(item), is_fill? "fill" : "stroke", normalized, true);
969 }
970 SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
971 return normalized;
972 }
974 } else {
975 /* Current fill style is not a gradient or wrong type, so construct everything */
976 SPGradient *constructed = sp_gradient_get_private_normalized(SP_OBJECT_DOCUMENT(item), gr, type);
977 constructed = sp_gradient_reset_to_userspace(constructed, item);
978 sp_item_repr_set_style_gradient(SP_OBJECT_REPR(item),
979 ( is_fill ? "fill" : "stroke" ),
980 constructed, true);
981 SP_OBJECT(item)->requestDisplayUpdate(( SP_OBJECT_MODIFIED_FLAG |
982 SP_OBJECT_STYLE_MODIFIED_FLAG ));
983 return constructed;
984 }
985 }
987 static void
988 sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *link)
989 {
990 g_return_if_fail(repr != NULL);
991 g_return_if_fail(link != NULL);
992 g_return_if_fail(SP_IS_GRADIENT(link));
994 gchar *ref;
995 if (link) {
996 gchar const *id = SP_OBJECT_ID(link);
997 size_t const len = strlen(id);
998 ref = (gchar*) alloca(len + 2);
999 *ref = '#';
1000 memcpy(ref + 1, id, len + 1);
1001 } else {
1002 ref = NULL;
1003 }
1005 repr->setAttribute("xlink:href", ref);
1006 }
1008 static void
1009 sp_item_repr_set_style_gradient(Inkscape::XML::Node *repr, gchar const *property,
1010 SPGradient *gr, bool recursive)
1011 {
1012 g_return_if_fail(repr != NULL);
1013 g_return_if_fail(gr != NULL);
1014 g_return_if_fail(SP_IS_GRADIENT(gr));
1016 gchar *val = g_strdup_printf("url(#%s)", SP_OBJECT_ID(gr));
1018 SPCSSAttr *css = sp_repr_css_attr_new();
1019 sp_repr_css_set_property(css, property, val);
1020 g_free(val);
1021 if (recursive) {
1022 sp_repr_css_change_recursive(repr, css, "style");
1023 } else {
1024 sp_repr_css_change(repr, css, "style");
1025 }
1026 sp_repr_css_attr_unref(css);
1027 }
1029 /*
1030 * Get default normalized gradient vector of document, create if there is none
1031 */
1033 SPGradient *
1034 sp_document_default_gradient_vector(SPDocument *document, guint32 color)
1035 {
1036 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
1038 Inkscape::XML::Node *repr = sp_repr_new("svg:linearGradient");
1040 repr->setAttribute("inkscape:collect", "always");
1041 // set here, but removed when it's edited in the gradient editor
1042 // to further reduce clutter, we could
1043 // (1) here, search gradients by color and return what is found without duplication
1044 // (2) in fill & stroke, show only one copy of each gradient in list
1046 Inkscape::XML::Node *stop = sp_repr_new("svg:stop");
1048 gchar b[64];
1049 sp_svg_write_color(b, 64, color);
1051 {
1052 gchar *t = g_strdup_printf("stop-color:%s;stop-opacity:1;", b);
1053 stop->setAttribute("style", t);
1054 g_free(t);
1055 }
1057 stop->setAttribute("offset", "0");
1059 repr->appendChild(stop);
1060 Inkscape::GC::release(stop);
1062 stop = sp_repr_new("svg:stop");
1064 {
1065 gchar *t = g_strdup_printf("stop-color:%s;stop-opacity:0;", b);
1066 stop->setAttribute("style", t);
1067 g_free(t);
1068 }
1070 stop->setAttribute("offset", "1");
1072 repr->appendChild(stop);
1073 Inkscape::GC::release(stop);
1075 SP_OBJECT_REPR(defs)->addChild(repr, NULL);
1076 Inkscape::GC::release(repr);
1078 /* fixme: This does not look like nice */
1079 SPGradient *gr;
1080 gr = (SPGradient *) document->getObjectByRepr(repr);
1081 g_assert(gr != NULL);
1082 g_assert(SP_IS_GRADIENT(gr));
1083 /* fixme: Maybe add extra sanity check here */
1084 gr->state = SP_GRADIENT_STATE_VECTOR;
1086 return gr;
1087 }
1089 /**
1090 Return the preferred vector for \a o, made from (in order of preference) its current vector,
1091 current fill or stroke color, or from desktop style if \a o is NULL or doesn't have style.
1092 */
1093 SPGradient *
1094 sp_gradient_vector_for_object(SPDocument *const doc, SPDesktop *const desktop,
1095 SPObject *const o, bool const is_fill)
1096 {
1097 guint32 rgba = 0;
1098 if (o == NULL || SP_OBJECT_STYLE(o) == NULL) {
1099 rgba = sp_desktop_get_color(desktop, is_fill);
1100 } else {
1101 // take the color of the object
1102 SPStyle const &style = *SP_OBJECT_STYLE(o);
1103 SPIPaint const &paint = ( is_fill
1104 ? style.fill
1105 : style.stroke );
1106 if (paint.type == SP_PAINT_TYPE_COLOR) {
1107 rgba = sp_color_get_rgba32_ualpha(&paint.value.color, 0xff);
1108 } else if (paint.type == SP_PAINT_TYPE_PAINTSERVER) {
1109 SPObject *server = is_fill? SP_OBJECT_STYLE_FILL_SERVER(o) : SP_OBJECT_STYLE_STROKE_SERVER(o);
1110 if (SP_IS_GRADIENT (server)) {
1111 return sp_gradient_get_vector(SP_GRADIENT (server), TRUE);
1112 } else {
1113 rgba = sp_desktop_get_color(desktop, is_fill);
1114 }
1115 } else {
1116 // if o doesn't use flat color, then take current color of the desktop.
1117 rgba = sp_desktop_get_color(desktop, is_fill);
1118 }
1119 }
1121 return sp_document_default_gradient_vector(doc, rgba);
1122 }
1124 /*
1125 Local Variables:
1126 mode:c++
1127 c-file-style:"stroustrup"
1128 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1129 indent-tabs-mode:nil
1130 fill-column:99
1131 End:
1132 */
1133 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :