// SPDX-License-Identifier: GPL-2.0-or-later /* * Various utility methods for gradients * * Authors: * Lauris Kaplinski * bulia byak * Johan Engelen * Jon A. Cruz * Abhishek Sharma * Tavmjong Bah * * Copyright (C) 2012 Tavmjong Bah * Copyright (C) 2010 Authors * Copyright (C) 2007 Johan Engelen * Copyright (C) 2001-2005 authors * Copyright (C) 2001 Ximian, Inc. * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include #include <2geom/transforms.h> #include <2geom/bezier-curve.h> #include <2geom/crossing.h> #include <2geom/line.h> #include "desktop-style.h" #include "desktop.h" #include "document-undo.h" #include "gradient-chemistry.h" #include "gradient-drag.h" #include "selection.h" #include "object/sp-defs.h" #include "object/sp-gradient-reference.h" #include "object/sp-linear-gradient.h" #include "object/sp-mesh-gradient.h" #include "object/sp-radial-gradient.h" #include "object/sp-stop.h" #include "object/sp-text.h" #include "object/sp-tspan.h" #include "object/sp-root.h" #include "style.h" #include "svg/svg.h" #include "svg/svg-color.h" #include "svg/css-ostringstream.h" #include "ui/icon-names.h" #include "ui/tools/tool-base.h" #include "ui/widget/gradient-vector-selector.h" #define noSP_GR_VERBOSE using Inkscape::DocumentUndo; namespace { Inkscape::PaintTarget paintTargetItems[] = {Inkscape::FOR_FILL, Inkscape::FOR_STROKE}; std::vector vectorOfPaintTargets(paintTargetItems, paintTargetItems + (sizeof(paintTargetItems) / sizeof(paintTargetItems[0]))); } // namespace namespace Inkscape { std::vector const &allPaintTargets() { return vectorOfPaintTargets; } } // namespace Inkscape // Terminology: // // "vector" is a gradient that has stops but not position coords. It can be referenced by one or // more privates. Objects should not refer to it directly. It has no radial/linear distinction. // // "array" is a gradient that has mesh rows and patches. It may or may not have "x" and "y" attributes. // An array does have spacial information so it cannot be normalized like a "vector". // // "shared" is either a "vector" or "array" that is shared between multiple objects. // // "private" is a gradient that is not shared. A private linear or radial gradient has no stops but // has position coords (e.g. center, radius etc for a radial); it references a "vector" for the // actual colors. A mesh may or may not reference an array. Each private is only used by one object. static void sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *gr); SPGradient *sp_gradient_ensure_vector_normalized(SPGradient *gr) { #ifdef SP_GR_VERBOSE g_message("sp_gradient_ensure_vector_normalized(%p)", gr); #endif g_return_val_if_fail(gr != nullptr, NULL); g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL); g_return_val_if_fail(!SP_IS_MESHGRADIENT(gr), NULL); /* If we are already normalized vector, just return */ if (gr->state == SP_GRADIENT_STATE_VECTOR) return gr; /* Fail, if we have wrong state set */ if (gr->state != SP_GRADIENT_STATE_UNKNOWN) { g_warning("file %s: line %d: Cannot normalize private gradient to vector (%s)", __FILE__, __LINE__, gr->getId()); return nullptr; } /* First make sure we have vector directly defined (i.e. gr has its own stops) */ if ( !gr->hasStops() ) { /* We do not have stops ourselves, so flatten stops as well */ gr->ensureVector(); g_assert(gr->vector.built); // this adds stops from gr->vector as children to gr gr->repr_write_vector (); } /* If gr hrefs some other gradient, remove the href */ if (gr->ref){ if (gr->ref->getObject()) { // We are hrefing someone, so require flattening gr->updateRepr(SP_OBJECT_WRITE_EXT | SP_OBJECT_WRITE_ALL); sp_gradient_repr_set_link(gr->getRepr(), nullptr); } } /* Everything is OK, set state flag */ gr->state = SP_GRADIENT_STATE_VECTOR; return gr; } /** * Creates new private gradient for the given shared gradient. */ static SPGradient *sp_gradient_get_private_normalized(SPDocument *document, SPGradient *shared, SPGradientType type) { #ifdef SP_GR_VERBOSE g_message("sp_gradient_get_private_normalized(%p, %p, %d)", document, shared, type); #endif g_return_val_if_fail(document != nullptr, NULL); g_return_val_if_fail(shared != nullptr, NULL); g_return_val_if_fail(SP_IS_GRADIENT(shared), NULL); g_return_val_if_fail(shared->hasStops() || shared->hasPatches(), NULL); SPDefs *defs = document->getDefs(); Inkscape::XML::Document *xml_doc = document->getReprDoc(); // create a new private gradient of the requested type Inkscape::XML::Node *repr; if (type == SP_GRADIENT_TYPE_LINEAR) { repr = xml_doc->createElement("svg:linearGradient"); } else if(type == SP_GRADIENT_TYPE_RADIAL) { repr = xml_doc->createElement("svg:radialGradient"); } else { repr = xml_doc->createElement("svg:meshgradient"); } // make auto collection optional Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (prefs->getBool("/option/gradient/auto_collect", true)) { repr->setAttribute("inkscape:collect", "always"); } else { repr->setAttribute("inkscape:collect", "never"); } // link to shared sp_gradient_repr_set_link(repr, shared); /* Append the new private gradient to defs */ defs->getRepr()->appendChild(repr); Inkscape::GC::release(repr); // get corresponding object SPGradient *gr = static_cast(document->getObjectByRepr(repr)); g_assert(gr != nullptr); g_assert(SP_IS_GRADIENT(gr)); return gr; } /** Count how many times gr is used by the styles of o and its descendants */ static guint count_gradient_hrefs(SPObject *o, SPGradient *gr) { if (!o) return 1; guint i = 0; SPStyle *style = o->style; if (style && style->fill.isPaintserver() && SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) && SP_GRADIENT(SP_STYLE_FILL_SERVER(style)) == gr) { i ++; } if (style && style->stroke.isPaintserver() && SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style)) && SP_GRADIENT(SP_STYLE_STROKE_SERVER(style)) == gr) { i ++; } for (auto& child: o->children) { i += count_gradient_hrefs(&child, gr); } return i; } /** * If gr has other users, create a new shared; also check if gr links to shared, relink if not */ static SPGradient *sp_gradient_fork_private_if_necessary(SPGradient *gr, SPGradient *shared, SPGradientType type, SPObject *o) { #ifdef SP_GR_VERBOSE g_message("sp_gradient_fork_private_if_necessary(%p, %p, %d, %p)", gr, shared, type, o); #endif g_return_val_if_fail(gr != nullptr, NULL); g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL); // Orphaned gradient, no shared with stops or patches at the end of the line; this used to be // an assert if ( !shared || !(shared->hasStops() || shared->hasPatches()) ) { std::cerr << "sp_gradient_fork_private_if_necessary: Orphaned gradient" << std::endl; return (gr); } // user is the object that uses this gradient; normally it's item but for tspans, we // check its ancestor text so that tspans don't get different gradients from their // texts. SPObject *user = o; while (SP_IS_TSPAN(user)) { user = user->parent; } // Check the number of uses of the gradient within this object; // if we are private and there are no other users, if (!shared->isSwatch() && (gr->hrefcount <= count_gradient_hrefs(user, gr))) { // check shared if ( gr != shared && gr->ref->getObject() != shared ) { /* our href is not the shared, and shared is different from gr; relink */ sp_gradient_repr_set_link(gr->getRepr(), shared); } return gr; } SPDocument *doc = gr->document; SPObject *defs = doc->getDefs(); if ((gr->hasStops()) || (gr->hasPatches()) || (gr->state != SP_GRADIENT_STATE_UNKNOWN) || (gr->parent != defs) || (gr->hrefcount > 1)) { // we have to clone a fresh new private gradient for the given shared // create an empty one SPGradient *gr_new = sp_gradient_get_private_normalized(doc, shared, type); // copy all the attributes to it Inkscape::XML::Node *repr_new = gr_new->getRepr(); Inkscape::XML::Node *repr = gr->getRepr(); repr_new->setAttribute("gradientUnits", repr->attribute("gradientUnits")); repr_new->setAttribute("gradientTransform", repr->attribute("gradientTransform")); if (SP_IS_RADIALGRADIENT(gr)) { repr_new->setAttribute("cx", repr->attribute("cx")); repr_new->setAttribute("cy", repr->attribute("cy")); repr_new->setAttribute("fx", repr->attribute("fx")); repr_new->setAttribute("fy", repr->attribute("fy")); repr_new->setAttribute("r", repr->attribute("r" )); repr_new->setAttribute("fr", repr->attribute("fr")); repr_new->setAttribute("spreadMethod", repr->attribute("spreadMethod")); } else if (SP_IS_LINEARGRADIENT(gr)) { repr_new->setAttribute("x1", repr->attribute("x1")); repr_new->setAttribute("y1", repr->attribute("y1")); repr_new->setAttribute("x2", repr->attribute("x2")); repr_new->setAttribute("y2", repr->attribute("y2")); repr_new->setAttribute("spreadMethod", repr->attribute("spreadMethod")); } else { // Mesh repr_new->setAttribute("x", repr->attribute("x")); repr_new->setAttribute("y", repr->attribute("y")); repr_new->setAttribute("type", repr->attribute("type")); // We probably want a completely separate mesh gradient so // copy the children and unset the link to the shared. for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) { Inkscape::XML::Node *copy = child->duplicate(doc->getReprDoc()); repr_new->appendChild( copy ); Inkscape::GC::release( copy ); } sp_gradient_repr_set_link(repr_new, nullptr); } return gr_new; } else { return gr; } } SPGradient *sp_gradient_fork_vector_if_necessary(SPGradient *gr) { #ifdef SP_GR_VERBOSE g_message("sp_gradient_fork_vector_if_necessary(%p)", gr); #endif // Some people actually prefer their gradient vectors to be shared... Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (!prefs->getBool("/options/forkgradientvectors/value", true)) return gr; if (gr->hrefcount > 1) { SPDocument *doc = gr->document; Inkscape::XML::Document *xml_doc = doc->getReprDoc(); Inkscape::XML::Node *repr = gr->getRepr()->duplicate(xml_doc); doc->getDefs()->getRepr()->addChild(repr, nullptr); SPGradient *gr_new = static_cast(doc->getObjectByRepr(repr)); gr_new = sp_gradient_ensure_vector_normalized (gr_new); Inkscape::GC::release(repr); return gr_new; } return gr; } /** * Obtain the vector from the gradient. A forked vector will be created and linked to this gradient if another gradient uses it. */ SPGradient *sp_gradient_get_forked_vector_if_necessary(SPGradient *gradient, bool force_vector) { #ifdef SP_GR_VERBOSE g_message("sp_gradient_get_forked_vector_if_necessary(%p, %d)", gradient, force_vector); #endif SPGradient *vector = gradient->getVector(force_vector); vector = sp_gradient_fork_vector_if_necessary (vector); if ( gradient != vector && gradient->ref->getObject() != vector ) { sp_gradient_repr_set_link(gradient->getRepr(), vector); } return vector; } /** * Convert an item's gradient to userspace _without_ preserving coords, setting them to defaults * instead. No forking or reapplying is done because this is only called for newly created privates. * @return The new gradient. */ SPGradient *sp_gradient_reset_to_userspace(SPGradient *gr, SPItem *item) { #ifdef SP_GR_VERBOSE g_message("sp_gradient_reset_to_userspace(%p, %p)", gr, item); #endif Inkscape::XML::Node *repr = gr->getRepr(); // calculate the bbox of the item item->document->ensureUpToDate(); Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine if (!bbox) return gr; Geom::Coord const width = bbox->dimensions()[Geom::X]; Geom::Coord const height = bbox->dimensions()[Geom::Y]; Geom::Point const center = bbox->midpoint(); if (SP_IS_RADIALGRADIENT(gr)) { repr->setAttributeSvgDouble("cx", center[Geom::X]); repr->setAttributeSvgDouble("cy", center[Geom::Y]); repr->setAttributeSvgDouble("fx", center[Geom::X]); repr->setAttributeSvgDouble("fy", center[Geom::Y]); repr->setAttributeSvgDouble("r", width/2); // we want it to be elliptic, not circular Geom::Affine squeeze = Geom::Translate (-center) * Geom::Scale(1, height/width) * Geom::Translate (center); gr->gradientTransform = squeeze; gr->setAttributeOrRemoveIfEmpty("gradientTransform", sp_svg_transform_write(gr->gradientTransform)); } else if (SP_IS_LINEARGRADIENT(gr)) { // Assume horizontal gradient by default (as per SVG 1.1) Geom::Point pStart = center - Geom::Point(width/2, 0); Geom::Point pEnd = center + Geom::Point(width/2, 0); // Get the preferred gradient angle from prefs Inkscape::Preferences *prefs = Inkscape::Preferences::get(); double angle = prefs->getDouble("/dialogs/gradienteditor/angle", 0.0); if (angle != 0.0) { Geom::Line grl(center, Geom::rad_from_deg(angle)); Geom::LineSegment bbl1(bbox->corner(0), bbox->corner(1)); Geom::LineSegment bbl2(bbox->corner(1), bbox->corner(2)); Geom::LineSegment bbl3(bbox->corner(2), bbox->corner(3)); Geom::LineSegment bbl4(bbox->corner(3), bbox->corner(0)); // Find where our gradient line intersects the bounding box. if (!bbl1.isDegenerate() && intersection(bbl1, grl)) { pStart = bbl1.pointAt((*intersection(bbl1, grl)).ta); pEnd = bbl3.pointAt((*intersection(bbl3, grl)).ta); if (intersection(bbl1, grl.ray(grl.angle()))) { std::swap(pStart, pEnd); } } else if (!bbl2.isDegenerate() && intersection(bbl2, grl)) { pStart = bbl2.pointAt((*intersection(bbl2, grl)).ta); pEnd = bbl4.pointAt((*intersection(bbl4, grl)).ta); if (intersection(bbl2, grl.ray(grl.angle()))) { std::swap(pStart, pEnd); } } } repr->setAttributeSvgDouble("x1", pStart[Geom::X]); repr->setAttributeSvgDouble("y1", pStart[Geom::Y]); repr->setAttributeSvgDouble("x2", pEnd[Geom::X]); repr->setAttributeSvgDouble("y2", pEnd[Geom::Y]); } else { // Mesh // THIS IS BEING CALLED TWICE WHENEVER A NEW GRADIENT IS CREATED, WRITING HERE CAUSES PROBLEMS // IN SPMeshNodeArray::create() //repr->setAttributeSvgDouble("x", bbox->min()[Geom::X]); //repr->setAttributeSvgDouble("y", bbox->min()[Geom::Y]); // We don't create a shared array gradient. SPMeshGradient* mg = SP_MESHGRADIENT( gr ); mg->array.create( mg, item, bbox ); } // set the gradientUnits repr->setAttribute("gradientUnits", "userSpaceOnUse"); return gr; } /** * Convert an item's gradient to userspace if necessary, also fork it if necessary. * @return The new gradient. */ SPGradient *sp_gradient_convert_to_userspace(SPGradient *gr, SPItem *item, gchar const *property) { #ifdef SP_GR_VERBOSE g_message("sp_gradient_convert_to_userspace(%p, %p, \"%s\")", gr, item, property); #endif g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL); if ( gr && gr->isSolid() ) { return gr; } // First, fork it if it is shared if (SP_IS_LINEARGRADIENT(gr)) { gr = sp_gradient_fork_private_if_necessary(gr, gr->getVector(), SP_GRADIENT_TYPE_LINEAR, item); } else if (SP_IS_RADIALGRADIENT(gr)) { gr = sp_gradient_fork_private_if_necessary(gr, gr->getVector(), SP_GRADIENT_TYPE_RADIAL, item); } else { gr = sp_gradient_fork_private_if_necessary(gr, gr->getArray(), SP_GRADIENT_TYPE_MESH, item); } if (gr->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { Inkscape::XML::Node *repr = gr->getRepr(); // calculate the bbox of the item item->document->ensureUpToDate(); Geom::Affine bbox2user; Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine if ( bbox ) { bbox2user = Geom::Affine(bbox->dimensions()[Geom::X], 0, 0, bbox->dimensions()[Geom::Y], bbox->min()[Geom::X], bbox->min()[Geom::Y]); } else { // would be degenerate otherwise bbox2user = Geom::identity(); } /* skew is the additional transform, defined by the proportions of the item, that we need * to apply to the gradient in order to work around this weird bit from SVG 1.1 * (http://www.w3.org/TR/SVG11/pservers.html#LinearGradients): * * When gradientUnits="objectBoundingBox" and gradientTransform is the identity * matrix, the stripes of the linear gradient are perpendicular to the gradient * vector in object bounding box space (i.e., the abstract coordinate system where * (0,0) is at the top/left of the object bounding box and (1,1) is at the * bottom/right of the object bounding box). When the object's bounding box is not * square, the stripes that are conceptually perpendicular to the gradient vector * within object bounding box space will render non-perpendicular relative to the * gradient vector in user space due to application of the non-uniform scaling * transformation from bounding box space to user space. */ Geom::Affine skew = bbox2user; double exp = skew.descrim(); skew[0] /= exp; skew[1] /= exp; skew[2] /= exp; skew[3] /= exp; skew[4] = 0; skew[5] = 0; // apply skew to the gradient gr->gradientTransform = skew; gr->setAttributeOrRemoveIfEmpty("gradientTransform", sp_svg_transform_write(gr->gradientTransform)); // Matrix to convert points to userspace coords; postmultiply by inverse of skew so // as to cancel it out when it's applied to the gradient during rendering Geom::Affine point_convert = bbox2user * skew.inverse(); if (SP_IS_LINEARGRADIENT(gr)) { SPLinearGradient *lg = SP_LINEARGRADIENT(gr); Geom::Point p1_b = Geom::Point(lg->x1.computed, lg->y1.computed); Geom::Point p2_b = Geom::Point(lg->x2.computed, lg->y2.computed); Geom::Point p1_u = p1_b * point_convert; Geom::Point p2_u = p2_b * point_convert; repr->setAttributeSvgDouble("x1", p1_u[Geom::X]); repr->setAttributeSvgDouble("y1", p1_u[Geom::Y]); repr->setAttributeSvgDouble("x2", p2_u[Geom::X]); repr->setAttributeSvgDouble("y2", p2_u[Geom::Y]); // set the gradientUnits repr->setAttribute("gradientUnits", "userSpaceOnUse"); } else if (SP_IS_RADIALGRADIENT(gr)) { SPRadialGradient *rg = SP_RADIALGRADIENT(gr); // original points in the bbox coords Geom::Point c_b = Geom::Point(rg->cx.computed, rg->cy.computed); Geom::Point f_b = Geom::Point(rg->fx.computed, rg->fy.computed); double r_b = rg->r.computed; // converted points in userspace coords Geom::Point c_u = c_b * point_convert; Geom::Point f_u = f_b * point_convert; double r_u = r_b * point_convert.descrim(); repr->setAttributeSvgDouble("cx", c_u[Geom::X]); repr->setAttributeSvgDouble("cy", c_u[Geom::Y]); repr->setAttributeSvgDouble("fx", f_u[Geom::X]); repr->setAttributeSvgDouble("fy", f_u[Geom::Y]); repr->setAttributeSvgDouble("r", r_u); // set the gradientUnits repr->setAttribute("gradientUnits", "userSpaceOnUse"); } else { std::cerr << "sp_gradient_convert_to_userspace: Conversion of mesh to userspace not implemented" << std::endl; } } // apply the gradient to the item (may be necessary if we forked it); not recursive // generally because grouped items will be taken care of later (we're being called // from sp_item_adjust_paint_recursive); however text and all its children should all // refer to one gradient, hence the recursive call for text (because we can't/don't // want to access tspans and set gradients on them separately) if (SP_IS_TEXT(item)) { sp_style_set_property_url(item, property, gr, true); } else { sp_style_set_property_url(item, property, gr, false); } return gr; } void sp_gradient_transform_multiply(SPGradient *gradient, Geom::Affine postmul, bool set) { #ifdef SP_GR_VERBOSE g_message("sp_gradient_transform_multiply(%p, , %d)", gradient, set); #endif if (set) { gradient->gradientTransform = postmul; } else { gradient->gradientTransform *= postmul; // fixme: get gradient transform by climbing to hrefs? } gradient->gradientTransform_set = TRUE; auto c = sp_svg_transform_write(gradient->gradientTransform); gradient->setAttributeOrRemoveIfEmpty("gradientTransform", c); } SPGradient *getGradient(SPItem *item, Inkscape::PaintTarget fill_or_stroke) { SPStyle *style = item->style; SPGradient *gradient = nullptr; switch (fill_or_stroke) { case Inkscape::FOR_FILL: if (style && (style->fill.isPaintserver())) { SPPaintServer *server = item->style->getFillPaintServer(); if ( SP_IS_GRADIENT(server) ) { gradient = SP_GRADIENT(server); } } break; case Inkscape::FOR_STROKE: if (style && (style->stroke.isPaintserver())) { SPPaintServer *server = item->style->getStrokePaintServer(); if ( SP_IS_GRADIENT(server) ) { gradient = SP_GRADIENT(server); } } break; } return gradient; } SPStop *sp_last_stop(SPGradient *gradient) { for (SPStop *stop = gradient->getFirstStop(); stop != nullptr; stop = stop->getNextStop()) { if (stop->getNextStop() == nullptr) return stop; } return nullptr; } std::pair sp_get_before_after_stops(SPStop* stop) { SPStop* before = nullptr; SPStop* after = nullptr; if (stop) { before = stop->getPrevStop(); after = stop->getNextStop(); } return std::make_pair(before, after); } static std::pair get_before_after_stops(SPGradient* gradient, double offset) { SPStop* before = nullptr; SPStop* after = nullptr; SPStop* stop = gradient->getFirstStop(); while (stop && stop->offset < offset) { before = stop; stop = stop->getNextStop(); } if (stop && stop->offset > offset) { after = stop; } return std::make_pair(before, after); } guint sp_number_of_stops_before_stop(SPGradient* gradient, SPStop* target) { if (!gradient) return 0; guint n = 0; for (SPStop* stop = gradient->getFirstStop(); stop != nullptr; stop = stop->getNextStop()) { if (stop == target) { return n; } n++; } return n; } SPStop* sp_get_nth_stop(SPGradient* gradient, guint index) { SPStop* stop = gradient->getFirstStop(); if (!stop) return nullptr; for (guint i = 0; i < index; ++i) { if (!stop) return nullptr; stop = stop->getNextStop(); } return stop; } SPStop *sp_get_stop_i(SPGradient *gradient, guint stop_i) { SPStop *stop = gradient->getFirstStop(); if (!stop) { return nullptr; } // if this is valid but weird gradient without an offset-zero stop element, // inkscape has created a handle for the start of gradient anyway, // so when it asks for stop N that corresponds to stop element N-1 if (stop->offset != 0) { stop_i--; } for (guint i = 0; i < stop_i; i++) { if (!stop) { return nullptr; } stop = stop->getNextStop(); } return stop; } guint32 average_color(guint32 c1, guint32 c2, gdouble p) { guint32 r = static_cast(SP_RGBA32_R_U (c1) * (1 - p) + SP_RGBA32_R_U (c2) * p); guint32 g = static_cast(SP_RGBA32_G_U (c1) * (1 - p) + SP_RGBA32_G_U (c2) * p); guint32 b = static_cast(SP_RGBA32_B_U (c1) * (1 - p) + SP_RGBA32_B_U (c2) * p); guint32 a = static_cast(SP_RGBA32_A_U (c1) * (1 - p) + SP_RGBA32_A_U (c2) * p); return SP_RGBA32_U_COMPOSE(r, g, b, a); } void sp_repr_set_css_double(Inkscape::XML::Node* node, const char* key, double value) { if (node) { node->setAttributeCssDouble(key, value); } } SPStop *sp_vector_add_stop(SPGradient *vector, SPStop* prev_stop, SPStop* next_stop, gfloat offset) { #ifdef SP_GR_VERBOSE g_message("sp_vector_add_stop(%p, %p, %p, %f)", vector, prev_stop, next_stop, offset); #endif SPStop* newstop = nullptr; // this function doesn't deal with empty gradients if (!prev_stop && !next_stop) return newstop; guint cnew = 0; // new color Inkscape::XML::Node *new_stop_repr = nullptr; if (!prev_stop || !next_stop) { // inserting stop past next or before previous is supported SPStop* stop = prev_stop ? prev_stop : next_stop; auto repr = stop->getRepr(); new_stop_repr = repr->duplicate(vector->getRepr()->document()); vector->getRepr()->addChild(new_stop_repr, prev_stop ? repr : nullptr); cnew = stop->get_rgba32(); } else { auto repr = prev_stop->getRepr(); new_stop_repr = repr->duplicate(vector->getRepr()->document()); vector->getRepr()->addChild(new_stop_repr, repr); guint32 const c1 = prev_stop->get_rgba32(); guint32 const c2 = next_stop->get_rgba32(); cnew = average_color (c1, c2, (offset - prev_stop->offset) / (next_stop->offset - prev_stop->offset)); } newstop = reinterpret_cast(vector->document->getObjectByRepr(new_stop_repr)); newstop->offset = offset; newstop->getRepr()->setAttributeCssDouble("offset", (double)offset); Inkscape::CSSOStringStream os; gchar c[64]; sp_svg_write_color (c, sizeof(c), cnew); gdouble opacity = (gdouble) SP_RGBA32_A_F (cnew); os << "stop-color:" << c << ";stop-opacity:" << opacity <<";"; newstop->setAttributeOrRemoveIfEmpty("style", os.str()); Inkscape::GC::release(new_stop_repr); return newstop; } // delete gradient's stop void sp_gradient_delete_stop(SPGradient* gradient, SPStop* stop) { if (!stop || !gradient) { return; } if (gradient->getStopCount() > 2) { // 2 is the minimum gradient->getRepr()->removeChild(stop->getRepr()); DocumentUndo::done(gradient->document, _("Delete gradient stop"), INKSCAPE_ICON("color-gradient")); } } // make gradient well-formed if needed; from gradient-vector.cpp static bool verify_grad(SPGradient* gradient) { bool modified = false; int i = 0; SPStop *stop = nullptr; /* count stops */ for (auto& ochild: gradient->children) { if (SP_IS_STOP(&ochild)) { i++; stop = SP_STOP(&ochild); } } Inkscape::XML::Document *xml_doc; xml_doc = gradient->getRepr()->document(); if (i < 1) { Inkscape::CSSOStringStream os; os << "stop-color: #000000;stop-opacity:" << 1.0 << ";"; Inkscape::XML::Node *child; child = xml_doc->createElement("svg:stop"); sp_repr_set_css_double(child, "offset", 0.0); child->setAttribute("style", os.str()); gradient->getRepr()->addChild(child, nullptr); Inkscape::GC::release(child); child = xml_doc->createElement("svg:stop"); sp_repr_set_css_double(child, "offset", 1.0); child->setAttribute("style", os.str()); gradient->getRepr()->addChild(child, nullptr); Inkscape::GC::release(child); modified = true; } else if (i < 2) { sp_repr_set_css_double(stop->getRepr(), "offset", 0.0); Inkscape::XML::Node *child = stop->getRepr()->duplicate(gradient->getRepr()->document()); sp_repr_set_css_double(child, "offset", 1.0); gradient->getRepr()->addChild(child, stop->getRepr()); Inkscape::GC::release(child); modified = true; } return modified; } // add new stop to a gradient; function lifted from gradient-vector.cpp SPStop* sp_gradient_add_stop(SPGradient* gradient, SPStop* current) { if (!gradient || !current) return nullptr; if (verify_grad(gradient)) { // gradient has been fixed by adding stop(s), don't insert another one return nullptr; } SPStop *stop = current; Inkscape::XML::Node *new_stop_repr = nullptr; SPStop *next = stop->getNextStop(); if (next == nullptr) { SPStop *prev = stop->getPrevStop(); if (prev != nullptr) { next = stop; stop = prev; } } if (next != nullptr) { new_stop_repr = stop->getRepr()->duplicate(gradient->getRepr()->document()); gradient->getRepr()->addChild(new_stop_repr, stop->getRepr()); } else { next = stop; new_stop_repr = stop->getPrevStop()->getRepr()->duplicate(gradient->getRepr()->document()); gradient->getRepr()->addChild(new_stop_repr, stop->getPrevStop()->getRepr()); } SPStop *newstop = reinterpret_cast(gradient->document->getObjectByRepr(new_stop_repr)); newstop->offset = (stop->offset + next->offset) * 0.5 ; guint32 const c1 = stop->get_rgba32(); guint32 const c2 = next->get_rgba32(); guint32 cnew = average_color(c1, c2); Inkscape::CSSOStringStream os; gchar c[64]; sp_svg_write_color(c, sizeof(c), cnew); gdouble opacity = static_cast(SP_RGBA32_A_F(cnew)); os << "stop-color:" << c << ";stop-opacity:" << opacity <<";"; newstop->setAttribute("style", os.str()); sp_repr_set_css_double(newstop->getRepr(), "offset", (double)newstop->offset); Inkscape::GC::release(new_stop_repr); DocumentUndo::done(gradient->document, _("Add gradient stop"), INKSCAPE_ICON("color-gradient")); return newstop; } SPStop* sp_gradient_add_stop_at(SPGradient* gradient, double offset) { if (!gradient) return nullptr; verify_grad(gradient); // find stops before and after given offset std::pair stops = get_before_after_stops(gradient, offset); if (stops.first || stops.second) { auto stop = sp_vector_add_stop(gradient, stops.first, stops.second, offset); if (stop) { DocumentUndo::done(gradient->document, _("Add gradient stop"), INKSCAPE_ICON("color-gradient")); } return stop; } else { return nullptr; } } void sp_set_gradient_stop_color(SPDocument* document, SPStop* stop, SPColor color, double opacity) { sp_repr_set_css_double(stop->getRepr(), "offset", stop->offset); Inkscape::CSSOStringStream os; os << "stop-color:" << color.toString() << ";stop-opacity:" << opacity <<";"; stop->setAttribute("style", os.str()); DocumentUndo::done(document, _("Change gradient stop color"), INKSCAPE_ICON("color-gradient")); } SPStop* sp_item_gradient_get_stop(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke) { SPGradient *gradient = getGradient(item, fill_or_stroke); if (!gradient || !SP_IS_GRADIENT(gradient)) { return nullptr; } if (SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT(gradient) ) { SPGradient *vector = gradient->getVector(); if (!vector) // orphan! return nullptr; switch (point_type) { case POINT_LG_BEGIN: case POINT_RG_CENTER: case POINT_RG_FOCUS: return vector->getFirstStop(); case POINT_LG_END: case POINT_RG_R1: case POINT_RG_R2: return sp_last_stop (vector); case POINT_LG_MID: case POINT_RG_MID1: case POINT_RG_MID2: return sp_get_stop_i (vector, point_i); default: g_warning( "Bad linear/radial gradient handle type" ); break; } } return nullptr; } guint32 sp_item_gradient_stop_query_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke) { SPGradient *gradient = getGradient(item, fill_or_stroke); if (!gradient || !SP_IS_GRADIENT(gradient)) { return 0; } if (SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT(gradient) ) { SPGradient *vector = gradient->getVector(); if (!vector) // orphan! return 0; // what else to do? switch (point_type) { case POINT_LG_BEGIN: case POINT_RG_CENTER: case POINT_RG_FOCUS: { SPStop *first = vector->getFirstStop(); if (first) { return first->get_rgba32(); } } break; case POINT_LG_END: case POINT_RG_R1: case POINT_RG_R2: { SPStop *last = sp_last_stop (vector); if (last) { return last->get_rgba32(); } } break; case POINT_LG_MID: case POINT_RG_MID1: case POINT_RG_MID2: { SPStop *stopi = sp_get_stop_i (vector, point_i); if (stopi) { return stopi->get_rgba32(); } } break; default: g_warning( "Bad linear/radial gradient handle type" ); break; } return 0; } else if (SP_IS_MESHGRADIENT(gradient)) { // Mesh gradient SPMeshGradient *mg = SP_MESHGRADIENT(gradient); switch (point_type) { case POINT_MG_CORNER: { if (point_i >= mg->array.corners.size()) { return 0; } SPMeshNode const* cornerpoint = mg->array.corners[ point_i ]; if (cornerpoint) { SPColor color = cornerpoint->color; double opacity = cornerpoint->opacity; return color.toRGBA32( opacity ); } else { return 0; } break; } case POINT_MG_HANDLE: case POINT_MG_TENSOR: { // Do nothing. Handles and tensors don't have color break; } default: g_warning( "Bad mesh handle type" ); } return 0; } return 0; } void sp_item_gradient_stop_set_style(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke, SPCSSAttr *stop) { #ifdef SP_GR_VERBOSE g_message("sp_item_gradient_stop_set_style(%p, %d, %d, %d, %p)", item, point_type, point_i, fill_or_stroke, stop); #endif SPGradient *gradient = getGradient(item, fill_or_stroke); if (!gradient || !SP_IS_GRADIENT(gradient)) return; if (SP_IS_LINEARGRADIENT(gradient) || SP_IS_RADIALGRADIENT(gradient) ) { SPGradient *vector = gradient->getVector(); if (!vector) // orphan! return; vector = sp_gradient_fork_vector_if_necessary (vector); if ( gradient != vector && gradient->ref->getObject() != vector ) { sp_gradient_repr_set_link(gradient->getRepr(), vector); } switch (point_type) { case POINT_LG_BEGIN: case POINT_RG_CENTER: case POINT_RG_FOCUS: { SPStop *first = vector->getFirstStop(); if (first) { sp_repr_css_change(first->getRepr(), stop, "style"); } } break; case POINT_LG_END: case POINT_RG_R1: case POINT_RG_R2: { SPStop *last = sp_last_stop (vector); if (last) { sp_repr_css_change(last->getRepr(), stop, "style"); } } break; case POINT_LG_MID: case POINT_RG_MID1: case POINT_RG_MID2: { SPStop *stopi = sp_get_stop_i (vector, point_i); if (stopi) { sp_repr_css_change(stopi->getRepr(), stop, "style"); } } break; default: g_warning( "Bad linear/radial gradient handle type" ); break; } } else { // Mesh gradient SPMeshGradient *mg = SP_MESHGRADIENT(gradient); bool changed = false; switch (point_type) { case POINT_MG_CORNER: { // Update mesh array (which is not updated automatically when stop is changed?) gchar const* color_str = sp_repr_css_property( stop, "stop-color", nullptr ); if( color_str ) { SPColor color( 0 ); SPIPaint paint; paint.read( color_str ); if( paint.isColor() ) { color = paint.value.color; } mg->array.corners[ point_i ]->color = color; changed = true; } gchar const* opacity_str = sp_repr_css_property( stop, "stop-opacity", nullptr ); if( opacity_str ) { std::stringstream os( opacity_str ); double opacity = 1.0; os >> opacity; mg->array.corners[ point_i ]->opacity = opacity; changed = true; } // Update stop if( changed ) { SPStop *stopi = mg->array.corners[ point_i ]->stop; if (stopi) { sp_repr_css_change(stopi->getRepr(), stop, "style"); } else { std::cerr << "sp_item_gradient_stop_set_style: null stopi" << std::endl; } } break; } case POINT_MG_HANDLE: case POINT_MG_TENSOR: { // Do nothing. Handles and tensors don't have colors. break; } default: g_warning( "Bad mesh handle type" ); } } } void sp_item_gradient_reverse_vector(SPItem *item, Inkscape::PaintTarget fill_or_stroke) { #ifdef SP_GR_VERBOSE g_message("sp_item_gradient_reverse_vector(%p, %d)", item, fill_or_stroke); #endif SPGradient *gradient = getGradient(item, fill_or_stroke); sp_gradient_reverse_vector(gradient); } void sp_gradient_reverse_vector(SPGradient* gradient) { if (!gradient || !SP_IS_GRADIENT(gradient)) return; SPGradient *vector = gradient->getVector(); if (!vector) // orphan! return; vector = sp_gradient_fork_vector_if_necessary (vector); if ( gradient != vector && gradient->ref->getObject() != vector ) { sp_gradient_repr_set_link(gradient->getRepr(), vector); } std::vector child_objects; std::vectorchild_reprs; std::vector offsets; double offset; for (auto& child: vector->children) { child_reprs.push_back(child.getRepr()); child_objects.push_back(&child); offset = child.getRepr()->getAttributeDouble("offset", 0); offsets.push_back(offset); } std::vector child_copies; for (auto repr:child_reprs) { Inkscape::XML::Document *xml_doc = vector->getRepr()->document(); child_copies.push_back(repr->duplicate(xml_doc)); } for (auto i:child_objects) { i->deleteObject(); } std::vector::reverse_iterator o_it = offsets.rbegin(); for (auto c_it = child_copies.rbegin(); c_it != child_copies.rend(); ++c_it, ++o_it) { vector->appendChildRepr(*c_it); (*c_it)->setAttributeSvgDouble("offset", 1 - *o_it); Inkscape::GC::release(*c_it); } } void sp_item_gradient_invert_vector_color(SPItem *item, Inkscape::PaintTarget fill_or_stroke) { #ifdef SP_GR_VERBOSE g_message("sp_item_gradient_invert_vector_color(%p, %d)", item, fill_or_stroke); #endif SPGradient *gradient = getGradient(item, fill_or_stroke); if (!gradient || !SP_IS_GRADIENT(gradient)) return; SPGradient *vector = gradient->getVector(); if (!vector) // orphan! return; vector = sp_gradient_fork_vector_if_necessary (vector); if ( gradient != vector && gradient->ref->getObject() != vector ) { sp_gradient_repr_set_link(gradient->getRepr(), vector); } for (auto& child: vector->children) { if (SP_IS_STOP(&child)) { guint32 color = SP_STOP(&child)->get_rgba32(); //g_message("Stop color %d", color); gchar c[64]; sp_svg_write_color (c, sizeof(c), SP_RGBA32_U_COMPOSE( (255 - SP_RGBA32_R_U(color)), (255 - SP_RGBA32_G_U(color)), (255 - SP_RGBA32_B_U(color)), SP_RGBA32_A_U(color) ) ); SPCSSAttr *css = sp_repr_css_attr_new (); sp_repr_css_set_property (css, "stop-color", c); sp_repr_css_change(child.getRepr(), css, "style"); sp_repr_css_attr_unref (css); } } } // HACK: linear and radial gradients may have first and/or last stops moved from their default positions // of 0 and 1 respectively; this is not what gradient tool was built to handle; instead of making extensive // changes to try to fix it, this hack just makes sure that midpoint draggers don't move to the true 0/1 limits; // with that, code relying on sp_get_stop_i will work correctly double midpoint_offset_hack(double offset) { const double EPS = 0.0001; if (offset <= 0) { offset = EPS; } else if (offset >= 1) { offset = 1 - EPS; } return offset; } /** Set the position of point point_type of the gradient applied to item (either fill_or_stroke) to p_w (in desktop coordinates). Write_repr if you want the change to become permanent. */ 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) { #ifdef SP_GR_VERBOSE 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] ); #endif SPGradient *gradient = getGradient(item, fill_or_stroke); if (!gradient || !SP_IS_GRADIENT(gradient)) return; // Needed only if units are set to SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX gradient = sp_gradient_convert_to_userspace(gradient, item, (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke"); Geom::Affine i2d (item->i2dt_affine ()); Geom::Point p = p_w * i2d.inverse(); p *= (gradient->gradientTransform).inverse(); // now p is in gradient's original coordinates Inkscape::XML::Node *repr = gradient->getRepr(); if (SP_IS_LINEARGRADIENT(gradient)) { SPLinearGradient *lg = SP_LINEARGRADIENT(gradient); switch (point_type) { case POINT_LG_BEGIN: if (scale) { lg->x2.computed += (lg->x1.computed - p[Geom::X]); lg->y2.computed += (lg->y1.computed - p[Geom::Y]); } lg->x1.computed = p[Geom::X]; lg->y1.computed = p[Geom::Y]; if (write_repr) { if (scale) { repr->setAttributeSvgDouble("x2", lg->x2.computed); repr->setAttributeSvgDouble("y2", lg->y2.computed); } repr->setAttributeSvgDouble("x1", lg->x1.computed); repr->setAttributeSvgDouble("y1", lg->y1.computed); } else { gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); } break; case POINT_LG_END: if (scale) { lg->x1.computed += (lg->x2.computed - p[Geom::X]); lg->y1.computed += (lg->y2.computed - p[Geom::Y]); } lg->x2.computed = p[Geom::X]; lg->y2.computed = p[Geom::Y]; if (write_repr) { if (scale) { repr->setAttributeSvgDouble("x1", lg->x1.computed); repr->setAttributeSvgDouble("y1", lg->y1.computed); } repr->setAttributeSvgDouble("x2", lg->x2.computed); repr->setAttributeSvgDouble("y2", lg->y2.computed); } else { gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); } break; case POINT_LG_MID: { // using X-coordinates only to determine the offset, assuming p has been snapped to the vector from begin to end. Geom::Point begin(lg->x1.computed, lg->y1.computed); Geom::Point end(lg->x2.computed, lg->y2.computed); double offset = Geom::LineSegment(begin, end).nearestTime(p); offset = midpoint_offset_hack(offset); SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (lg, false); lg->ensureVector(); lg->vector.stops.at(point_i).offset = offset; if (SPStop* stopi = sp_get_stop_i(vector, point_i)) { stopi->offset = offset; if (write_repr) { stopi->getRepr()->setAttributeCssDouble("offset", stopi->offset); } else { stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); } } } break; default: g_warning( "Bad linear gradient handle type" ); break; } } else if (SP_IS_RADIALGRADIENT(gradient)) { SPRadialGradient *rg = SP_RADIALGRADIENT(gradient); Geom::Point c (rg->cx.computed, rg->cy.computed); Geom::Point c_w = c * gradient->gradientTransform * i2d; // now in desktop coords if ((point_type == POINT_RG_R1 || point_type == POINT_RG_R2) && Geom::L2 (p_w - c_w) < 1e-3) { // prevent setting a radius too close to the center return; } Geom::Affine new_transform; bool transform_set = false; switch (point_type) { case POINT_RG_CENTER: rg->fx.computed = p[Geom::X] + (rg->fx.computed - rg->cx.computed); rg->fy.computed = p[Geom::Y] + (rg->fy.computed - rg->cy.computed); rg->cx.computed = p[Geom::X]; rg->cy.computed = p[Geom::Y]; if (write_repr) { repr->setAttributeSvgDouble("fx", rg->fx.computed); repr->setAttributeSvgDouble("fy", rg->fy.computed); repr->setAttributeSvgDouble("cx", rg->cx.computed); repr->setAttributeSvgDouble("cy", rg->cy.computed); } else { gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); } break; case POINT_RG_FOCUS: rg->fx.computed = p[Geom::X]; rg->fy.computed = p[Geom::Y]; if (write_repr) { repr->setAttributeSvgDouble("fx", rg->fx.computed); repr->setAttributeSvgDouble("fy", rg->fy.computed); } else { gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); } break; case POINT_RG_R1: { Geom::Point r1_w = (c + Geom::Point(rg->r.computed, 0)) * gradient->gradientTransform * i2d; double r1_angle = Geom::atan2(r1_w - c_w); double move_angle = Geom::atan2(p_w - c_w) - r1_angle; double move_stretch = Geom::L2(p_w - c_w) / Geom::L2(r1_w - c_w); Geom::Affine move = Geom::Affine (Geom::Translate (-c_w)) * Geom::Affine (Geom::Rotate(-r1_angle)) * Geom::Affine (Geom::Scale(move_stretch, scale? move_stretch : 1)) * Geom::Affine (Geom::Rotate(r1_angle)) * Geom::Affine (Geom::Rotate(move_angle)) * Geom::Affine (Geom::Translate (c_w)); new_transform = gradient->gradientTransform * i2d * move * i2d.inverse(); transform_set = true; break; } case POINT_RG_R2: { Geom::Point r2_w = (c + Geom::Point(0, -rg->r.computed)) * gradient->gradientTransform * i2d; double r2_angle = Geom::atan2(r2_w - c_w); double move_angle = Geom::atan2(p_w - c_w) - r2_angle; double move_stretch = Geom::L2(p_w - c_w) / Geom::L2(r2_w - c_w); Geom::Affine move = Geom::Affine (Geom::Translate (-c_w)) * Geom::Affine (Geom::Rotate(-r2_angle)) * Geom::Affine (Geom::Scale(move_stretch, scale? move_stretch : 1)) * Geom::Affine (Geom::Rotate(r2_angle)) * Geom::Affine (Geom::Rotate(move_angle)) * Geom::Affine (Geom::Translate (c_w)); new_transform = gradient->gradientTransform * i2d * move * i2d.inverse(); transform_set = true; break; } case POINT_RG_MID1: { Geom::Point start = Geom::Point (rg->cx.computed, rg->cy.computed); Geom::Point end = Geom::Point (rg->cx.computed + rg->r.computed, rg->cy.computed); double offset = Geom::LineSegment(start, end).nearestTime(p); offset = midpoint_offset_hack(offset); SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (rg, false); rg->ensureVector(); rg->vector.stops.at(point_i).offset = offset; if (SPStop* stopi = sp_get_stop_i(vector, point_i)) { stopi->offset = offset; if (write_repr) { stopi->getRepr()->setAttributeCssDouble("offset", stopi->offset); } else { stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); } } break; } case POINT_RG_MID2: { Geom::Point start = Geom::Point (rg->cx.computed, rg->cy.computed); Geom::Point end = Geom::Point (rg->cx.computed, rg->cy.computed - rg->r.computed); double offset = Geom::LineSegment(start, end).nearestTime(p); offset = midpoint_offset_hack(offset); SPGradient *vector = sp_gradient_get_forked_vector_if_necessary(rg, false); rg->ensureVector(); rg->vector.stops.at(point_i).offset = offset; if (SPStop* stopi = sp_get_stop_i(vector, point_i)) { stopi->offset = offset; if (write_repr) { stopi->getRepr()->setAttributeCssDouble("offset", stopi->offset); } else { stopi->requestModified(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); } } break; } default: g_warning( "Bad radial gradient handle type" ); break; } if (transform_set) { gradient->gradientTransform = new_transform; gradient->gradientTransform_set = TRUE; if (write_repr) { auto s = sp_svg_transform_write(gradient->gradientTransform); gradient->setAttributeOrRemoveIfEmpty("gradientTransform", s); } else { gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); } } } else if (SP_IS_MESHGRADIENT(gradient)) { SPMeshGradient *mg = SP_MESHGRADIENT(gradient); //Geom::Affine new_transform; //bool transform_set = false; switch (point_type) { case POINT_MG_CORNER: { mg->array.corners[ point_i ]->p = p; // Handles are moved in gradient-drag.cpp gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); break; } case POINT_MG_HANDLE: { mg->array.handles[ point_i ]->p = p; gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); break; } case POINT_MG_TENSOR: { mg->array.tensors[ point_i ]->p = p; gradient->requestModified(SP_OBJECT_MODIFIED_FLAG); break; } default: g_warning( "Bad mesh handle type" ); } if( write_repr ) { //std::cout << "Write mesh repr" << std::endl; mg->array.write( mg ); } } } SPGradient *sp_item_gradient_get_vector(SPItem *item, Inkscape::PaintTarget fill_or_stroke) { SPGradient *gradient = getGradient(item, fill_or_stroke); if (gradient) { return gradient->getVector(); } return nullptr; } SPGradientSpread sp_item_gradient_get_spread(SPItem *item, Inkscape::PaintTarget fill_or_stroke) { SPGradientSpread spread = SP_GRADIENT_SPREAD_PAD; SPGradient *gradient = getGradient(item, fill_or_stroke); if (gradient) { spread = gradient->fetchSpread(); } return spread; } /** Returns the position of point point_type of the gradient applied to item (either fill_or_stroke), in desktop coordinates. */ Geom::Point getGradientCoords(SPItem *item, GrPointType point_type, guint point_i, Inkscape::PaintTarget fill_or_stroke) { SPGradient *gradient = getGradient(item, fill_or_stroke); #ifdef SP_GR_VERBOSE g_message("getGradientCoords(%p, %d, %d, %d, %p)", item, point_type, point_i, fill_or_stroke, gradient); #endif Geom::Point p (0, 0); if (!gradient) return p; if (SP_IS_LINEARGRADIENT(gradient)) { SPLinearGradient *lg = SP_LINEARGRADIENT(gradient); switch (point_type) { case POINT_LG_BEGIN: p = Geom::Point (lg->x1.computed, lg->y1.computed); break; case POINT_LG_END: p = Geom::Point (lg->x2.computed, lg->y2.computed); break; case POINT_LG_MID: { if (lg->vector.stops.size() < point_i) { g_message("POINT_LG_MID bug trigger, see LP bug #453067"); break; } gdouble offset = lg->vector.stops.at(point_i).offset; p = (1-offset) * Geom::Point(lg->x1.computed, lg->y1.computed) + offset * Geom::Point(lg->x2.computed, lg->y2.computed); } break; default: g_warning( "Bad linear gradient handle type" ); break; } } else if (SP_IS_RADIALGRADIENT(gradient)) { SPRadialGradient *rg = SP_RADIALGRADIENT(gradient); switch (point_type) { case POINT_RG_CENTER: p = Geom::Point (rg->cx.computed, rg->cy.computed); break; case POINT_RG_FOCUS: p = Geom::Point (rg->fx.computed, rg->fy.computed); break; case POINT_RG_R1: p = Geom::Point (rg->cx.computed + rg->r.computed, rg->cy.computed); break; case POINT_RG_R2: p = Geom::Point (rg->cx.computed, rg->cy.computed - rg->r.computed); break; case POINT_RG_MID1: { if (rg->vector.stops.size() < point_i) { g_message("POINT_RG_MID1 bug trigger, see LP bug #453067"); break; } gdouble offset = rg->vector.stops.at(point_i).offset; p = (1-offset) * Geom::Point (rg->cx.computed, rg->cy.computed) + offset * Geom::Point(rg->cx.computed + rg->r.computed, rg->cy.computed); } break; case POINT_RG_MID2: { if (rg->vector.stops.size() < point_i) { g_message("POINT_RG_MID2 bug trigger, see LP bug #453067"); break; } gdouble offset = rg->vector.stops.at(point_i).offset; p = (1-offset) * Geom::Point (rg->cx.computed, rg->cy.computed) + offset * Geom::Point(rg->cx.computed, rg->cy.computed - rg->r.computed); } break; default: g_warning( "Bad radial gradient handle type" ); break; } } else if (SP_IS_MESHGRADIENT(gradient)) { SPMeshGradient *mg = SP_MESHGRADIENT(gradient); switch (point_type) { case POINT_MG_CORNER: p = mg->array.corners[ point_i ]->p; break; case POINT_MG_HANDLE: { p = mg->array.handles[ point_i ]->p; break; } case POINT_MG_TENSOR: { p = mg->array.tensors[ point_i ]->p; break; } default: g_warning( "Bad mesh handle type" ); } } if (gradient->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { item->document->ensureUpToDate(); Geom::OptRect bbox = item->visualBounds(); // we need "true" bbox without item_i2d_affine if (bbox) { p *= Geom::Affine(bbox->dimensions()[Geom::X], 0, 0, bbox->dimensions()[Geom::Y], bbox->min()[Geom::X], bbox->min()[Geom::Y]); } } p *= Geom::Affine(gradient->gradientTransform) * (Geom::Affine)item->i2dt_affine(); return p; } /** * Sets item fill or stroke to the gradient of the specified type with given vector, creating * new private gradient, if needed. * gr has to be a normalized vector. */ SPGradient *sp_item_set_gradient(SPItem *item, SPGradient *gr, SPGradientType type, Inkscape::PaintTarget fill_or_stroke) { #ifdef SP_GR_VERBOSE g_message("sp_item_set_gradient(%p, %p, %d, %d)", item, gr, type, fill_or_stroke); #endif g_return_val_if_fail(item != nullptr, NULL); g_return_val_if_fail(SP_IS_ITEM(item), NULL); g_return_val_if_fail(gr != nullptr, NULL); g_return_val_if_fail(SP_IS_GRADIENT(gr), NULL); g_return_val_if_fail(gr->state == SP_GRADIENT_STATE_VECTOR, NULL); SPStyle *style = item->style; g_assert(style != nullptr); SPPaintServer *ps = nullptr; if ((fill_or_stroke == Inkscape::FOR_FILL) ? style->fill.isPaintserver() : style->stroke.isPaintserver()) { ps = (fill_or_stroke == Inkscape::FOR_FILL) ? SP_STYLE_FILL_SERVER(style) : SP_STYLE_STROKE_SERVER(style); } if (ps && ( (type == SP_GRADIENT_TYPE_LINEAR && SP_IS_LINEARGRADIENT(ps)) || (type == SP_GRADIENT_TYPE_RADIAL && SP_IS_RADIALGRADIENT(ps)) ) ) { /* Current fill style is the gradient of the required type */ SPGradient *current = SP_GRADIENT(ps); //g_message("hrefcount %d count %d\n", current->hrefcount, count_gradient_hrefs(item, current)); if (!current->isSwatch() && (current->hrefcount == 1 || current->hrefcount == count_gradient_hrefs(item, current))) { // current is private and it's either used once, or all its uses are by children of item; // so just change its href to vector if ( current != gr && current->getVector() != gr ) { // href is not the vector sp_gradient_repr_set_link(current->getRepr(), gr); } item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); return current; } else { // the gradient is not private, or it is shared with someone else; // normalize it (this includes creating new private if necessary) SPGradient *normalized = sp_gradient_fork_private_if_necessary(current, gr, type, item); g_return_val_if_fail(normalized != nullptr, NULL); if (normalized != current) { /* We have to change object style here; recursive because this is used from * fill&stroke and must work for groups etc. */ sp_style_set_property_url(item, (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke", normalized, true); } item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); return normalized; } } else { /* Current fill style is not a gradient or wrong type, so construct everything */ /* This is where mesh gradients are constructed. */ g_assert(SP_IS_GRADIENT(gr)); // TEMP SPGradient *constructed = sp_gradient_get_private_normalized(item->document, gr, type); constructed = sp_gradient_reset_to_userspace(constructed, item); sp_style_set_property_url(item, ( (fill_or_stroke == Inkscape::FOR_FILL) ? "fill" : "stroke" ), constructed, true); item->requestDisplayUpdate(( SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG )); return constructed; } } static void sp_gradient_repr_set_link(Inkscape::XML::Node *repr, SPGradient *link) { #ifdef SP_GR_VERBOSE g_message("sp_gradient_repr_set_link(%p, %p)", repr, link); #endif g_return_if_fail(repr != nullptr); if (link) { g_return_if_fail(SP_IS_GRADIENT(link)); } if (link) { Glib::ustring ref("#"); ref += link->getId(); repr->setAttribute("xlink:href", ref); } else { repr->removeAttribute("xlink:href"); } } static void addStop( Inkscape::XML::Node *parent, Glib::ustring const &color, gint opacity, gchar const *offset ) { #ifdef SP_GR_VERBOSE g_message("addStop(%p, %s, %d, %s)", parent, color.c_str(), opacity, offset); #endif Inkscape::XML::Node *stop = parent->document()->createElement("svg:stop"); { gchar *tmp = g_strdup_printf( "stop-color:%s;stop-opacity:%d;", color.c_str(), opacity ); stop->setAttribute( "style", tmp ); g_free(tmp); } stop->setAttribute( "offset", offset ); parent->appendChild(stop); Inkscape::GC::release(stop); } /* * Get default normalized gradient vector of document, create if there is none */ SPGradient *sp_document_default_gradient_vector( SPDocument *document, SPColor const &color, bool singleStop ) { SPDefs *defs = document->getDefs(); Inkscape::XML::Document *xml_doc = document->getReprDoc(); Inkscape::XML::Node *repr = xml_doc->createElement("svg:linearGradient"); if ( !singleStop ) { // make auto collection optional Inkscape::Preferences *prefs = Inkscape::Preferences::get(); if (prefs->getBool("/option/gradient/auto_collect", true)) { repr->setAttribute("inkscape:collect", "always"); } else { repr->setAttribute("inkscape:collect", "never"); } // set here, but removed when it's edited in the gradient editor // to further reduce clutter, we could // (1) here, search gradients by color and return what is found without duplication // (2) in fill & stroke, show only one copy of each gradient in list } Glib::ustring colorStr = color.toString(); addStop( repr, colorStr, 1, "0" ); if ( !singleStop ) { addStop( repr, colorStr, 0, "1" ); } defs->getRepr()->addChild(repr, nullptr); Inkscape::GC::release(repr); /* fixme: This does not look like nice */ SPGradient *gr = static_cast(document->getObjectByRepr(repr)); g_assert(gr != nullptr); g_assert(SP_IS_GRADIENT(gr)); /* fixme: Maybe add extra sanity check here */ gr->state = SP_GRADIENT_STATE_VECTOR; return gr; } SPGradient *sp_gradient_vector_for_object( SPDocument *const doc, SPDesktop *const desktop, SPObject *const o, Inkscape::PaintTarget const fill_or_stroke, bool singleStop ) { SPColor color; if ( (o == nullptr) || (o->style == nullptr) ) { color = sp_desktop_get_color(desktop, (fill_or_stroke == Inkscape::FOR_FILL)); } else { // take the color of the object SPStyle const &style = *(o->style); SPIPaint const &paint = *style.getFillOrStroke(fill_or_stroke == Inkscape::FOR_FILL); if (paint.isPaintserver()) { SPObject *server = (fill_or_stroke == Inkscape::FOR_FILL) ? o->style->getFillPaintServer() : o->style->getStrokePaintServer(); if ( SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server) ) { return SP_GRADIENT(server)->getVector(true); } else { color = sp_desktop_get_color(desktop, (fill_or_stroke == Inkscape::FOR_FILL)); } } else if (paint.isColor()) { color = paint.value.color; } else { // if o doesn't use flat color, then take current color of the desktop. color = sp_desktop_get_color(desktop, (fill_or_stroke == Inkscape::FOR_FILL)); } } return sp_document_default_gradient_vector( doc, color, singleStop ); } void sp_gradient_invert_selected_gradients(SPDesktop *desktop, Inkscape::PaintTarget fill_or_stroke) { Inkscape::Selection *selection = desktop->getSelection(); auto list= selection->items(); for (auto i = list.begin(); i != list.end(); ++i) { sp_item_gradient_invert_vector_color(*i, fill_or_stroke); } // we did an undoable action DocumentUndo::done(desktop->getDocument(), _("Invert gradient colors"), INKSCAPE_ICON("color-gradient")); } void sp_gradient_reverse_selected_gradients(SPDesktop *desktop) { Inkscape::Selection *selection = desktop->getSelection(); Inkscape::UI::Tools::ToolBase *ev = desktop->getEventContext(); if (!ev) { return; } GrDrag *drag = ev->get_drag(); // First try selected dragger if (drag && !drag->selected.empty()) { drag->selected_reverse_vector(); } else { // If no drag or no dragger selected, act on selection (both fill and stroke gradients) auto list= selection->items(); for (auto i = list.begin(); i != list.end(); ++i) { sp_item_gradient_reverse_vector(*i, Inkscape::FOR_FILL); sp_item_gradient_reverse_vector(*i, Inkscape::FOR_STROKE); } } // we did an undoable action DocumentUndo::done(desktop->getDocument(), _("Reverse gradient"), INKSCAPE_ICON("color-gradient")); } void sp_gradient_unset_swatch(SPDesktop *desktop, std::string const &id) { SPDocument *doc = desktop ? desktop->doc() : nullptr; if (doc) { const std::vector gradients = doc->getResourceList("gradient"); for (auto gradient : gradients) { SPGradient* grad = SP_GRADIENT(gradient); if ( id == grad->getId() ) { grad->setSwatch(false); DocumentUndo::done(doc, _("Delete swatch"), INKSCAPE_ICON("color-gradient")); break; } } } } /* * Return a SPItem's gradient */ SPGradient* sp_item_get_gradient(SPItem *item, bool fillorstroke) { SPIPaint *item_paint = item->style->getFillOrStroke(fillorstroke); if (item_paint->isPaintserver()) { SPPaintServer *item_server = fillorstroke ? item->style->getFillPaintServer() : item->style->getStrokePaintServer(); if (SP_IS_LINEARGRADIENT(item_server) || SP_IS_RADIALGRADIENT(item_server) || (SP_IS_GRADIENT(item_server) && SP_GRADIENT(item_server)->getVector()->isSwatch())) { return SP_GRADIENT(item_server)->getVector(); } } return nullptr; } static void get_all_doc_items(std::vector &list, SPObject *from) { for (auto& child: from->children) { if (SP_IS_ITEM(&child)) { list.push_back(SP_ITEM(&child)); } get_all_doc_items(list, &child); } } std::vector sp_get_all_document_items(SPDocument* document) { std::vector items; if (document) { get_all_doc_items(items, document->getRoot()); } return items; } int sp_get_gradient_refcount(SPDocument* document, SPGradient* gradient) { if (!document || !gradient) return 0; int count = 0; for (auto item : sp_get_all_document_items(document)) { if (!item->getId()) { continue; } SPGradient* fill = sp_item_get_gradient(item, true); // fill if (fill == gradient) { ++count; } SPGradient* stroke = sp_item_get_gradient(item, false); // stroke if (stroke == gradient) { ++count; } } return count; } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :