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-gradient-vector.h"
23 #include "sp-linear-gradient.h"
24 #include "sp-radial-gradient.h"
25 #include "sp-stop.h"
26 #include "widgets/gradient-vector.h"
28 #include "sp-text.h"
29 #include "sp-tspan.h"
30 #include <libnr/nr-matrix-fns.h>
31 #include "xml/repr.h"
32 #include "svg/svg.h"
33 #include "svg/svg-color.h"
36 // Terminology:
37 //
38 // "vector" is a gradient that has stops but not position coords. It can be referenced by one or
39 // more privates. Objects should not refer to it directly. It has no radial/linear distinction.
40 //
41 // "private" is a gradient that has no stops but has position coords (e.g. center, radius etc for a
42 // radial). It references a vector for the actual colors. Each private is only used by one
43 // object. It is either linear or radial.
45 static void sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *gr);
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_style_set_property_url(SP_OBJECT(item), property, SP_OBJECT(gr), true);
407 else
408 sp_style_set_property_url(SP_OBJECT(item), property, SP_OBJECT(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 }
504 SPStop*
505 sp_get_stop_i(SPGradient *gradient, guint stop_i)
506 {
507 SPStop *stop = sp_first_stop (gradient);
509 for (guint i=0; i < stop_i; i++) {
510 if (!stop) return NULL;
511 stop = sp_next_stop (stop);
512 }
514 return stop;
515 }
518 void
519 sp_item_gradient_edit_stop (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke)
520 {
521 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
523 if (!gradient || !SP_IS_GRADIENT(gradient))
524 return;
526 SPGradient *vector = sp_gradient_get_vector (gradient, false);
527 switch (point_type) {
528 case POINT_LG_BEGIN:
529 case POINT_RG_CENTER:
530 case POINT_RG_FOCUS:
531 {
532 GtkWidget *dialog = sp_gradient_vector_editor_new (vector, sp_first_stop (vector));
533 gtk_widget_show (dialog);
534 }
535 break;
537 case POINT_LG_END:
538 case POINT_RG_R1:
539 case POINT_RG_R2:
540 {
541 GtkWidget *dialog = sp_gradient_vector_editor_new (vector, sp_last_stop (vector));
542 gtk_widget_show (dialog);
543 }
544 break;
546 case POINT_LG_MID:
547 {
548 GtkWidget *dialog = sp_gradient_vector_editor_new (vector, sp_get_stop_i (vector, point_i));
549 gtk_widget_show (dialog);
550 }
551 break;
552 default:
553 break;
554 }
555 }
557 guint32
558 sp_item_gradient_stop_query_style (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke)
559 {
560 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
562 if (!gradient || !SP_IS_GRADIENT(gradient))
563 return 0;
565 SPGradient *vector = sp_gradient_get_vector (gradient, false);
567 if (!vector) // orphan!
568 return 0; // what else to do?
570 switch (point_type) {
571 case POINT_LG_BEGIN:
572 case POINT_RG_CENTER:
573 case POINT_RG_FOCUS:
574 {
575 SPStop *first = sp_first_stop (vector);
576 if (first) {
577 return sp_stop_get_rgba32(first);
578 }
579 }
580 break;
582 case POINT_LG_END:
583 case POINT_RG_R1:
584 case POINT_RG_R2:
585 {
586 SPStop *last = sp_last_stop (vector);
587 if (last) {
588 return sp_stop_get_rgba32(last);
589 }
590 }
591 break;
593 case POINT_LG_MID:
594 {
595 SPStop *stopi = sp_get_stop_i (vector, point_i);
596 if (stopi) {
597 return sp_stop_get_rgba32(stopi);
598 }
599 }
600 break;
602 default:
603 break;
604 }
605 return 0;
606 }
608 void
609 sp_item_gradient_stop_set_style (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke, SPCSSAttr *stop)
610 {
611 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
613 if (!gradient || !SP_IS_GRADIENT(gradient))
614 return;
616 SPGradient *vector = sp_gradient_get_vector (gradient, false);
618 if (!vector) // orphan!
619 return;
621 vector = sp_gradient_fork_vector_if_necessary (vector);
622 if ( gradient != vector && gradient->ref->getObject() != vector ) {
623 sp_gradient_repr_set_link(SP_OBJECT_REPR(gradient), vector);
624 }
626 switch (point_type) {
627 case POINT_LG_BEGIN:
628 case POINT_RG_CENTER:
629 case POINT_RG_FOCUS:
630 {
631 SPStop *first = sp_first_stop (vector);
632 if (first) {
633 sp_repr_css_change (SP_OBJECT_REPR (first), stop, "style");
634 }
635 }
636 break;
638 case POINT_LG_END:
639 case POINT_RG_R1:
640 case POINT_RG_R2:
641 {
642 SPStop *last = sp_last_stop (vector);
643 if (last) {
644 sp_repr_css_change (SP_OBJECT_REPR (last), stop, "style");
645 }
646 }
647 break;
649 case POINT_LG_MID:
650 {
651 SPStop *stopi = sp_get_stop_i (vector, point_i);
652 if (stopi) {
653 sp_repr_css_change (SP_OBJECT_REPR (stopi), stop, "style");
654 }
655 }
656 break;
658 default:
659 break;
660 }
661 }
663 void
664 sp_item_gradient_reverse_vector (SPItem *item, bool fill_or_stroke)
665 {
666 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
667 if (!gradient || !SP_IS_GRADIENT(gradient))
668 return;
670 SPGradient *vector = sp_gradient_get_vector (gradient, false);
671 if (!vector) // orphan!
672 return;
674 vector = sp_gradient_fork_vector_if_necessary (vector);
675 if ( gradient != vector && gradient->ref->getObject() != vector ) {
676 sp_gradient_repr_set_link(SP_OBJECT_REPR(gradient), vector);
677 }
679 GSList *child_reprs = NULL;
680 GSList *child_objects = NULL;
681 std::vector<double> offsets;
682 for (SPObject *child = sp_object_first_child(vector);
683 child != NULL; child = SP_OBJECT_NEXT(child)) {
684 child_reprs = g_slist_prepend (child_reprs, SP_OBJECT_REPR(child));
685 child_objects = g_slist_prepend (child_objects, child);
686 offsets.push_back(sp_repr_get_double_attribute(SP_OBJECT_REPR(child), "offset", 0));
687 }
689 GSList *child_copies = NULL;
690 for (GSList *i = child_reprs; i != NULL; i = i->next) {
691 Inkscape::XML::Node *repr = (Inkscape::XML::Node *) i->data;
692 child_copies = g_slist_append (child_copies, repr->duplicate());
693 }
696 for (GSList *i = child_objects; i != NULL; i = i->next) {
697 SPObject *child = SP_OBJECT (i->data);
698 child->deleteObject();
699 }
701 std::vector<double>::iterator iter = offsets.end() - 1;
702 for (GSList *i = child_copies; i != NULL; i = i->next) {
703 Inkscape::XML::Node *copy = (Inkscape::XML::Node *) i->data;
704 vector->appendChildRepr(copy);
705 sp_repr_set_svg_double (copy, "offset", 1 - *iter);
706 iter --;
707 Inkscape::GC::release(copy);
708 }
710 g_slist_free (child_reprs);
711 g_slist_free (child_copies);
712 g_slist_free (child_objects);
713 }
717 /**
718 Set the position of point point_type of the gradient applied to item (either fill_or_stroke) to
719 p_w (in desktop coordinates). Write_repr if you want the change to become permanent.
720 */
721 void
722 sp_item_gradient_set_coords (SPItem *item, guint point_type, guint point_i, NR::Point p_w, bool fill_or_stroke, bool write_repr, bool scale)
723 {
724 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
726 if (!gradient || !SP_IS_GRADIENT(gradient))
727 return;
729 gradient = sp_gradient_convert_to_userspace (gradient, item, fill_or_stroke? "fill" : "stroke");
731 NR::Matrix i2d = sp_item_i2d_affine (item);
732 NR::Point p = p_w * i2d.inverse();
733 p *= (gradient->gradientTransform).inverse();
734 // now p is in gradient's original coordinates
736 Inkscape::XML::Node *repr = SP_OBJECT_REPR(gradient);
738 if (SP_IS_LINEARGRADIENT(gradient)) {
739 SPLinearGradient *lg = SP_LINEARGRADIENT(gradient);
740 switch (point_type) {
741 case POINT_LG_BEGIN:
742 if (scale) {
743 lg->x2.computed += (lg->x1.computed - p[NR::X]);
744 lg->y2.computed += (lg->y1.computed - p[NR::Y]);
745 }
746 lg->x1.computed = p[NR::X];
747 lg->y1.computed = p[NR::Y];
748 if (write_repr) {
749 if (scale) {
750 sp_repr_set_svg_double(repr, "x2", lg->x2.computed);
751 sp_repr_set_svg_double(repr, "y2", lg->y2.computed);
752 }
753 sp_repr_set_svg_double(repr, "x1", lg->x1.computed);
754 sp_repr_set_svg_double(repr, "y1", lg->y1.computed);
755 } else {
756 SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG);
757 }
758 break;
759 case POINT_LG_END:
760 if (scale) {
761 lg->x1.computed += (lg->x2.computed - p[NR::X]);
762 lg->y1.computed += (lg->y2.computed - p[NR::Y]);
763 }
764 lg->x2.computed = p[NR::X];
765 lg->y2.computed = p[NR::Y];
766 if (write_repr) {
767 if (scale) {
768 sp_repr_set_svg_double(repr, "x1", lg->x1.computed);
769 sp_repr_set_svg_double(repr, "y1", lg->y1.computed);
770 }
771 sp_repr_set_svg_double(repr, "x2", lg->x2.computed);
772 sp_repr_set_svg_double(repr, "y2", lg->y2.computed);
773 } else {
774 SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG);
775 }
776 break;
777 case POINT_LG_MID:
778 {
779 //do stuff!
780 }
781 break;
782 default:
783 break;
784 }
785 } else if (SP_IS_RADIALGRADIENT(gradient)) {
787 SPRadialGradient *rg = SP_RADIALGRADIENT(gradient);
788 NR::Point c (rg->cx.computed, rg->cy.computed);
789 NR::Point c_w = c * gradient->gradientTransform * i2d; // now in desktop coords
790 if ((point_type == POINT_RG_R1 || point_type == POINT_RG_R2) && NR::L2 (p_w - c_w) < 1e-3) {
791 // prevent setting a radius too close to the center
792 return;
793 }
794 NR::Matrix new_transform;
795 bool transform_set = false;
797 switch (point_type) {
798 case POINT_RG_CENTER:
799 rg->fx.computed = p[NR::X] + (rg->fx.computed - rg->cx.computed);
800 rg->fy.computed = p[NR::Y] + (rg->fy.computed - rg->cy.computed);
801 rg->cx.computed = p[NR::X];
802 rg->cy.computed = p[NR::Y];
803 if (write_repr) {
804 sp_repr_set_svg_double(repr, "fx", rg->fx.computed);
805 sp_repr_set_svg_double(repr, "fy", rg->fy.computed);
806 sp_repr_set_svg_double(repr, "cx", rg->cx.computed);
807 sp_repr_set_svg_double(repr, "cy", rg->cy.computed);
808 } else {
809 SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG);
810 }
811 break;
812 case POINT_RG_FOCUS:
813 rg->fx.computed = p[NR::X];
814 rg->fy.computed = p[NR::Y];
815 if (write_repr) {
816 sp_repr_set_svg_double(repr, "fx", rg->fx.computed);
817 sp_repr_set_svg_double(repr, "fy", rg->fy.computed);
818 } else {
819 SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG);
820 }
821 break;
822 case POINT_RG_R1:
823 {
824 NR::Point r1_w = (c + NR::Point(rg->r.computed, 0)) * gradient->gradientTransform * i2d;
825 double r1_angle = NR::atan2(r1_w - c_w);
826 double move_angle = NR::atan2(p_w - c_w) - r1_angle;
827 double move_stretch = NR::L2(p_w - c_w) / NR::L2(r1_w - c_w);
829 NR::Matrix move = NR::Matrix (NR::translate (-c_w)) *
830 NR::Matrix (NR::rotate(-r1_angle)) *
831 NR::Matrix (NR::scale(move_stretch, scale? move_stretch : 1)) *
832 NR::Matrix (NR::rotate(r1_angle)) *
833 NR::Matrix (NR::rotate(move_angle)) *
834 NR::Matrix (NR::translate (c_w));
836 new_transform = gradient->gradientTransform * i2d * move * i2d.inverse();
837 transform_set = true;
839 break;
840 }
841 case POINT_RG_R2:
842 {
843 NR::Point r2_w = (c + NR::Point(0, -rg->r.computed)) * gradient->gradientTransform * i2d;
844 double r2_angle = NR::atan2(r2_w - c_w);
845 double move_angle = NR::atan2(p_w - c_w) - r2_angle;
846 double move_stretch = NR::L2(p_w - c_w) / NR::L2(r2_w - c_w);
848 NR::Matrix move = NR::Matrix (NR::translate (-c_w)) *
849 NR::Matrix (NR::rotate(-r2_angle)) *
850 NR::Matrix (NR::scale(move_stretch, scale? move_stretch : 1)) *
851 NR::Matrix (NR::rotate(r2_angle)) *
852 NR::Matrix (NR::rotate(move_angle)) *
853 NR::Matrix (NR::translate (c_w));
855 new_transform = gradient->gradientTransform * i2d * move * i2d.inverse();
856 transform_set = true;
858 break;
859 }
860 }
862 if (transform_set) {
863 gradient->gradientTransform = new_transform;
864 gradient->gradientTransform_set = TRUE;
865 if (write_repr) {
866 gchar s[256];
867 if (sp_svg_transform_write(s, 256, gradient->gradientTransform)) {
868 SP_OBJECT_REPR(gradient)->setAttribute("gradientTransform", s);
869 } else {
870 SP_OBJECT_REPR(gradient)->setAttribute("gradientTransform", NULL);
871 }
872 } else {
873 SP_OBJECT (gradient)->requestModified(SP_OBJECT_MODIFIED_FLAG);
874 }
875 }
876 }
877 }
879 SPGradient *
880 sp_item_gradient_get_vector (SPItem *item, bool fill_or_stroke)
881 {
882 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
884 if (gradient)
885 return sp_gradient_get_vector (gradient, false);
886 return NULL;
887 }
889 SPGradientSpread
890 sp_item_gradient_get_spread (SPItem *item, bool fill_or_stroke)
891 {
892 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
894 if (gradient)
895 return sp_gradient_get_spread (gradient);
896 return SP_GRADIENT_SPREAD_PAD;
897 }
900 /**
901 Returns the position of point point_type of the gradient applied to item (either fill_or_stroke),
902 in desktop coordinates.
903 */
905 NR::Point
906 sp_item_gradient_get_coords (SPItem *item, guint point_type, guint point_i, bool fill_or_stroke)
907 {
908 SPGradient *gradient = sp_item_gradient (item, fill_or_stroke);
910 NR::Point p (0, 0);
912 if (!gradient)
913 return p;
915 if (SP_IS_LINEARGRADIENT(gradient)) {
916 SPLinearGradient *lg = SP_LINEARGRADIENT(gradient);
917 switch (point_type) {
918 case POINT_LG_BEGIN:
919 p = NR::Point (lg->x1.computed, lg->y1.computed);
920 break;
921 case POINT_LG_END:
922 p = NR::Point (lg->x2.computed, lg->y2.computed);
923 break;
924 case POINT_LG_MID:
925 //p = somewhere in between (x1,y1)-(x2,y2) defined by percentage of point[point_i];
926 gdouble offset = lg->vector.stops.at(point_i).offset;
927 p = (1-offset) * NR::Point(lg->x1.computed, lg->y1.computed) + offset * NR::Point(lg->x2.computed, lg->y2.computed);
928 break;
929 }
930 } else if (SP_IS_RADIALGRADIENT(gradient)) {
931 SPRadialGradient *rg = SP_RADIALGRADIENT(gradient);
932 switch (point_type) {
933 case POINT_RG_CENTER:
934 p = NR::Point (rg->cx.computed, rg->cy.computed);
935 break;
936 case POINT_RG_FOCUS:
937 p = NR::Point (rg->fx.computed, rg->fy.computed);
938 break;
939 case POINT_RG_R1:
940 p = NR::Point (rg->cx.computed + rg->r.computed, rg->cy.computed);
941 break;
942 case POINT_RG_R2:
943 p = NR::Point (rg->cx.computed, rg->cy.computed - rg->r.computed);
944 break;
945 }
946 }
948 if (SP_GRADIENT(gradient)->units == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) {
949 sp_document_ensure_up_to_date(SP_OBJECT_DOCUMENT(item));
950 NR::Rect const bbox = item->invokeBbox(NR::identity()); // we need "true" bbox without item_i2d_affine
951 p *= NR::Matrix(bbox.dimensions()[NR::X], 0,
952 0, bbox.dimensions()[NR::Y],
953 bbox.min()[NR::X], bbox.min()[NR::Y]);
954 }
955 p *= NR::Matrix(gradient->gradientTransform) * sp_item_i2d_affine(item);
956 return p;
957 }
960 /**
961 * Sets item fill or stroke to the gradient of the specified type with given vector, creating
962 * new private gradient, if needed.
963 * gr has to be a normalized vector.
964 */
966 SPGradient *
967 sp_item_set_gradient(SPItem *item, SPGradient *gr, SPGradientType type, bool is_fill)
968 {
969 g_return_val_if_fail(item != NULL, NULL);
970 g_return_val_if_fail(SP_IS_ITEM(item), NULL);
971 g_return_val_if_fail(gr != NULL, NULL);
972 g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL);
973 g_return_val_if_fail(gr->state == SP_GRADIENT_STATE_VECTOR, NULL);
975 SPStyle *style = SP_OBJECT_STYLE(item);
976 g_assert(style != NULL);
978 guint style_type = is_fill? style->fill.type : style->stroke.type;
979 SPPaintServer *ps = NULL;
980 if (style_type == SP_PAINT_TYPE_PAINTSERVER)
981 ps = is_fill? SP_STYLE_FILL_SERVER(style) : SP_STYLE_STROKE_SERVER(style);
983 if (ps
984 && ( (type == SP_GRADIENT_TYPE_LINEAR && SP_IS_LINEARGRADIENT(ps)) ||
985 (type == SP_GRADIENT_TYPE_RADIAL && SP_IS_RADIALGRADIENT(ps)) ) )
986 {
988 /* Current fill style is the gradient of the required type */
989 SPGradient *current = SP_GRADIENT(ps);
991 //g_print("hrefcount %d count %d\n", SP_OBJECT_HREFCOUNT(ig), count_gradient_hrefs(SP_OBJECT(item), ig));
993 if (SP_OBJECT_HREFCOUNT(current) == 1 ||
994 SP_OBJECT_HREFCOUNT(current) == count_gradient_hrefs(SP_OBJECT(item), current)) {
996 // current is private and it's either used once, or all its uses are by children of item;
997 // so just change its href to vector
999 if ( current != gr && sp_gradient_get_vector(current, false) != gr ) {
1000 /* href is not the vector */
1001 sp_gradient_repr_set_link(SP_OBJECT_REPR(current), gr);
1002 }
1003 SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1004 return current;
1006 } else {
1008 // the gradient is not private, or it is shared with someone else;
1009 // normalize it (this includes creating new private if necessary)
1010 SPGradient *normalized = sp_gradient_fork_private_if_necessary(current, gr, type, item);
1012 g_return_val_if_fail(normalized != NULL, NULL);
1014 if (normalized != current) {
1016 /* We have to change object style here; recursive because this is used from
1017 * fill&stroke and must work for groups etc. */
1018 sp_style_set_property_url(SP_OBJECT(item), is_fill? "fill" : "stroke", SP_OBJECT(normalized), true);
1019 }
1020 SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1021 return normalized;
1022 }
1024 } else {
1025 /* Current fill style is not a gradient or wrong type, so construct everything */
1026 SPGradient *constructed = sp_gradient_get_private_normalized(SP_OBJECT_DOCUMENT(item), gr, type);
1027 constructed = sp_gradient_reset_to_userspace(constructed, item);
1028 sp_style_set_property_url(SP_OBJECT(item), ( is_fill ? "fill" : "stroke" ), SP_OBJECT(constructed), true);
1029 SP_OBJECT(item)->requestDisplayUpdate(( SP_OBJECT_MODIFIED_FLAG |
1030 SP_OBJECT_STYLE_MODIFIED_FLAG ));
1031 return constructed;
1032 }
1033 }
1035 static void
1036 sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *link)
1037 {
1038 g_return_if_fail(repr != NULL);
1039 g_return_if_fail(link != NULL);
1040 g_return_if_fail(SP_IS_GRADIENT(link));
1042 gchar *ref;
1043 if (link) {
1044 gchar const *id = SP_OBJECT_ID(link);
1045 size_t const len = strlen(id);
1046 ref = (gchar*) alloca(len + 2);
1047 *ref = '#';
1048 memcpy(ref + 1, id, len + 1);
1049 } else {
1050 ref = NULL;
1051 }
1053 repr->setAttribute("xlink:href", ref);
1054 }
1056 /*
1057 * Get default normalized gradient vector of document, create if there is none
1058 */
1060 SPGradient *
1061 sp_document_default_gradient_vector(SPDocument *document, guint32 color)
1062 {
1063 SPDefs *defs = (SPDefs *) SP_DOCUMENT_DEFS(document);
1065 Inkscape::XML::Node *repr = sp_repr_new("svg:linearGradient");
1067 repr->setAttribute("inkscape:collect", "always");
1068 // set here, but removed when it's edited in the gradient editor
1069 // to further reduce clutter, we could
1070 // (1) here, search gradients by color and return what is found without duplication
1071 // (2) in fill & stroke, show only one copy of each gradient in list
1073 Inkscape::XML::Node *stop = sp_repr_new("svg:stop");
1075 gchar b[64];
1076 sp_svg_write_color(b, 64, color);
1078 {
1079 gchar *t = g_strdup_printf("stop-color:%s;stop-opacity:1;", b);
1080 stop->setAttribute("style", t);
1081 g_free(t);
1082 }
1084 stop->setAttribute("offset", "0");
1086 repr->appendChild(stop);
1087 Inkscape::GC::release(stop);
1089 stop = sp_repr_new("svg:stop");
1091 {
1092 gchar *t = g_strdup_printf("stop-color:%s;stop-opacity:0;", b);
1093 stop->setAttribute("style", t);
1094 g_free(t);
1095 }
1097 stop->setAttribute("offset", "1");
1099 repr->appendChild(stop);
1100 Inkscape::GC::release(stop);
1102 SP_OBJECT_REPR(defs)->addChild(repr, NULL);
1103 Inkscape::GC::release(repr);
1105 /* fixme: This does not look like nice */
1106 SPGradient *gr;
1107 gr = (SPGradient *) document->getObjectByRepr(repr);
1108 g_assert(gr != NULL);
1109 g_assert(SP_IS_GRADIENT(gr));
1110 /* fixme: Maybe add extra sanity check here */
1111 gr->state = SP_GRADIENT_STATE_VECTOR;
1113 return gr;
1114 }
1116 /**
1117 Return the preferred vector for \a o, made from (in order of preference) its current vector,
1118 current fill or stroke color, or from desktop style if \a o is NULL or doesn't have style.
1119 */
1120 SPGradient *
1121 sp_gradient_vector_for_object(SPDocument *const doc, SPDesktop *const desktop,
1122 SPObject *const o, bool const is_fill)
1123 {
1124 guint32 rgba = 0;
1125 if (o == NULL || SP_OBJECT_STYLE(o) == NULL) {
1126 rgba = sp_desktop_get_color(desktop, is_fill);
1127 } else {
1128 // take the color of the object
1129 SPStyle const &style = *SP_OBJECT_STYLE(o);
1130 SPIPaint const &paint = ( is_fill
1131 ? style.fill
1132 : style.stroke );
1133 if (paint.type == SP_PAINT_TYPE_COLOR) {
1134 rgba = sp_color_get_rgba32_ualpha(&paint.value.color, 0xff);
1135 } else if (paint.type == SP_PAINT_TYPE_PAINTSERVER) {
1136 SPObject *server = is_fill? SP_OBJECT_STYLE_FILL_SERVER(o) : SP_OBJECT_STYLE_STROKE_SERVER(o);
1137 if (SP_IS_GRADIENT (server)) {
1138 return sp_gradient_get_vector(SP_GRADIENT (server), TRUE);
1139 } else {
1140 rgba = sp_desktop_get_color(desktop, is_fill);
1141 }
1142 } else {
1143 // if o doesn't use flat color, then take current color of the desktop.
1144 rgba = sp_desktop_get_color(desktop, is_fill);
1145 }
1146 }
1148 return sp_document_default_gradient_vector(doc, rgba);
1149 }
1151 /*
1152 Local Variables:
1153 mode:c++
1154 c-file-style:"stroustrup"
1155 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1156 indent-tabs-mode:nil
1157 fill-column:99
1158 End:
1159 */
1160 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :