Exemple #1
0
/*
 *  Return a "HHSSLL" version of the first stop color so we can sort by it
 */
unsigned long sp_gradient_to_hhssll(SPGradient *gr)
{
    SPStop *stop = gr->getFirstStop();
    unsigned long rgba = stop->get_rgba32();
    float hsl[3];
    sp_color_rgb_to_hsl_floatv (hsl, SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba));

    return ((int)(hsl[0]*100 * 10000)) + ((int)(hsl[1]*100 * 100)) + ((int)(hsl[2]*100 * 1));
}
Exemple #2
0
int SPGradient::getStopCount() const
{
    int count = 0;

    for (SPStop *stop = const_cast<SPGradient*>(this)->getFirstStop(); stop && stop->getNextStop(); stop = stop->getNextStop()) {
        count++;
    }

    return count;
}
Exemple #3
0
// user selected existing stop from list
static void sp_grad_edit_combo_box_changed (GtkComboBox * /*widget*/, GtkWidget *tbl)
{
    SPStop *stop = get_selected_stop(tbl);
    if (!stop) {
        return;
    }

    blocked = TRUE;

    SelectedColor *csel = static_cast<SelectedColor*>(g_object_get_data(G_OBJECT(tbl), "cselector"));
    // set its color, from the stored array
    g_object_set_data(G_OBJECT(tbl), "updating_color", reinterpret_cast<void*>(1));
    csel->setColorAlpha(stop->getEffectiveColor(), stop->opacity);
    g_object_set_data(G_OBJECT(tbl), "updating_color", reinterpret_cast<void*>(0));
    GtkWidget *offspin = GTK_WIDGET(g_object_get_data(G_OBJECT(tbl), "offspn"));
    GtkWidget *offslide =GTK_WIDGET(g_object_get_data(G_OBJECT(tbl), "offslide"));

    GtkAdjustment *adj = static_cast<GtkAdjustment*>(g_object_get_data(G_OBJECT(tbl), "offset"));

    bool isEndStop = false;

    SPStop *prev = NULL;
    prev = stop->getPrevStop();
    if (prev != NULL )  {
        gtk_adjustment_set_lower (adj, prev->offset);
    } else {
        isEndStop = true;
        gtk_adjustment_set_lower (adj, 0);
    }

    SPStop *next = NULL;
    next = stop->getNextStop();
    if (next != NULL ) {
        gtk_adjustment_set_upper (adj, next->offset);
    } else {
        isEndStop = true;
        gtk_adjustment_set_upper (adj, 1.0);
    }

    //fixme: does this work on all possible input gradients?
    if (!isEndStop) {
        gtk_widget_set_sensitive(offslide, TRUE);
        gtk_widget_set_sensitive(GTK_WIDGET(offspin), TRUE);
    } else {
        gtk_widget_set_sensitive(offslide, FALSE);
        gtk_widget_set_sensitive(GTK_WIDGET(offspin), FALSE);
    }

    gtk_adjustment_set_value(adj, stop->offset);

    gtk_adjustment_changed(adj);

    blocked = FALSE;
}
Exemple #4
0
static void sp_grd_ed_del_stop(GtkWidget */*widget*/,  GtkWidget *vb)
{
    SPGradient *gradient = static_cast<SPGradient *>(g_object_get_data(G_OBJECT(vb), "gradient"));

    SPStop *stop = get_selected_stop(vb);
    if (!stop) {
        return;
    }

    if (gradient->vector.stops.size() > 2) { // 2 is the minimum

        // if we delete first or last stop, move the next/previous to the edge
        if (stop->offset == 0) {
            SPStop *next = stop->getNextStop();
            if (next) {
                next->offset = 0;
                sp_repr_set_css_double(next->getRepr(), "offset", 0);
            }
        } else if (stop->offset == 1) {
            SPStop *prev = stop->getPrevStop();
            if (prev) {
                prev->offset = 1;
                sp_repr_set_css_double(prev->getRepr(), "offset", 1);
            }
        }

        gradient->getRepr()->removeChild(stop->getRepr());
        sp_gradient_vector_widget_load_gradient(vb, gradient);
        update_stop_list(GTK_WIDGET(vb), gradient, NULL);
        DocumentUndo::done(gradient->document, SP_VERB_CONTEXT_GRADIENT,
                           _("Delete gradient stop"));
    }

}
Exemple #5
0
static void offadjustmentChanged( GtkAdjustment *adjustment, GtkWidget *vb)
{
    if (!blocked) {
        blocked = TRUE;

        SPStop *stop = get_selected_stop(vb);
        if (stop) {
            stop->offset = gtk_adjustment_get_value (adjustment);
            sp_repr_set_css_double(stop->getRepr(), "offset", stop->offset);

            DocumentUndo::maybeDone(stop->document, "gradient:stop:offset", SP_VERB_CONTEXT_GRADIENT,
                                    _("Change gradient stop offset"));

        }

        blocked = FALSE;
    }
}
Exemple #6
0
static void verify_grad(SPGradient *gradient)
{
    int i = 0;
    SPStop *stop = NULL;
    /* count stops */
    for ( SPObject *ochild = gradient->firstChild() ; ochild ; ochild = ochild->getNext() ) {
        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().c_str());
        gradient->getRepr()->addChild(child, NULL);
        Inkscape::GC::release(child);

        child = xml_doc->createElement("svg:stop");
        sp_repr_set_css_double(child, "offset", 1.0);
        child->setAttribute("style", os.str().c_str());
        gradient->getRepr()->addChild(child, NULL);
        Inkscape::GC::release(child);
        return;
    }
    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);
    }
}
/*
 * User changed the offset
 */
static void gr_stop_offset_adjustment_changed(GtkAdjustment *adj, GObject *tbl)
{
    if (blocked) {
        return;
    }

    blocked = TRUE;

    SPStop *stop = get_selected_stop(GTK_WIDGET(tbl));
    if (stop) {
        stop->offset = gtk_adjustment_get_value(adj);
        sp_repr_set_css_double(stop->getRepr(), "offset", stop->offset);

        DocumentUndo::maybeDone(stop->document, "gradient:stop:offset", SP_VERB_CONTEXT_GRADIENT,
                                _("Change gradient stop offset"));

    }

    blocked = FALSE;
}
static void gr_stop_set_offset(GtkComboBox * /*widget*/, GtkWidget *data)
{
    SPStop *stop = get_selected_stop(data);
    if (!stop) {
        return;
    }

    EgeAdjustmentAction* act = (EgeAdjustmentAction *)g_object_get_data( G_OBJECT(data), "offset_action");
    if (!act) {
        return;
    }

    GtkAdjustment *adj = ege_adjustment_action_get_adjustment(act);

    bool isEndStop = false;

    SPStop *prev = NULL;
    prev = stop->getPrevStop();
    if (prev != NULL )  {
        gtk_adjustment_set_lower(adj, prev->offset);
    } else {
        isEndStop = true;
        gtk_adjustment_set_lower(adj, 0);
    }

    SPStop *next = NULL;
    next = stop->getNextStop();
    if (next != NULL ) {
        gtk_adjustment_set_upper(adj, next->offset);
    } else {
        isEndStop = true;
        gtk_adjustment_set_upper(adj, 1.0);
    }

    blocked = TRUE;
    gtk_adjustment_set_value(adj, stop->offset);
    gtk_action_set_sensitive( GTK_ACTION(act), !isEndStop );
    gtk_adjustment_changed(adj);
    blocked = FALSE;

}
Exemple #9
0
/**
 * return true if this gradient is "equivalent" to that gradient.
 * Equivalent meaning they have the same stop count, same stop colors and same stop opacity
 * @param that - A gradient to compare this to
 */
bool SPGradient::isEquivalent(SPGradient *that)
{
    //TODO Make this work for mesh gradients

    bool status = false;
    
    while(1){ // not really a loop, used to avoid deep nesting or multiple exit points from function
        if (this->getStopCount() != that->getStopCount()) { break; }
        if (this->hasStops() != that->hasStops()) { break; }
        if (!this->getVector() || !that->getVector()) { break; }
        if (this->isSwatch() != that->isSwatch()) {  break; }
        if ( this->isSwatch() ){
           // drop down to check stops.
        }
        else if (
            (SP_IS_LINEARGRADIENT(this) && SP_IS_LINEARGRADIENT(that)) ||
            (SP_IS_RADIALGRADIENT(this) && SP_IS_RADIALGRADIENT(that)) ||
            (SP_IS_MESH(this)           && SP_IS_MESH(that))) {
            if(!this->isAligned(that))break;
        }
        else { break; }  // this should never happen, some unhandled type of gradient

        SPStop *as = this->getVector()->getFirstStop();
        SPStop *bs = that->getVector()->getFirstStop();

        bool effective = true;
        while (effective && (as && bs)) {
            if (!as->getEffectiveColor().isClose(bs->getEffectiveColor(), 0.001) ||
                    as->offset != bs->offset) {
                effective = false;
                break;
            } 
            else {
                as = as->getNextStop();
                bs = bs->getNextStop();
            }
        }
        if (!effective) break;

        status = true;
        break;
    }
    return status;
}
static gboolean update_stop_list( GtkWidget *stop_combo, SPGradient *gradient, SPStop *new_stop, GtkWidget *widget, bool gr_multi)
{
    gboolean sensitive = FALSE;

    if (!stop_combo) {
        return sensitive;
    }

    GtkListStore *store = (GtkListStore *)gtk_combo_box_get_model(GTK_COMBO_BOX(stop_combo));
    if (!store) {
        return sensitive;
    }

    blocked = TRUE;

    /* Clear old list, if there is any */
    gtk_list_store_clear(store);
    GtkTreeIter iter;

    if (!SP_IS_GRADIENT(gradient)) {
        gtk_list_store_append(store, &iter);
        gtk_list_store_set(store, &iter, 0, _("No gradient"), 1, NULL, 2, NULL, -1);
        gtk_combo_box_set_active(GTK_COMBO_BOX(stop_combo) , 0);
        sensitive = FALSE;
        blocked = FALSE;
        return sensitive;
    }

    /* Populate the combobox store */
    GSList *sl = NULL;
    if ( gradient->hasStops() ) {
        for ( SPObject *ochild = gradient->firstChild() ; ochild ; ochild = ochild->getNext() ) {
            if (SP_IS_STOP(ochild)) {
                sl = g_slist_append(sl, ochild);
            }
        }
    }
    if (!sl) {
        gtk_list_store_append(store, &iter);
        gtk_list_store_set(store, &iter, 0, _("No stops in gradient"), 1, NULL, 2, NULL, -1);
        sensitive = FALSE;

    } else {

        for (; sl != NULL; sl = sl->next){
            if (SP_IS_STOP(sl->data)){
                SPStop *stop = SP_STOP(sl->data);
                Inkscape::XML::Node *repr = reinterpret_cast<SPItem *>(sl->data)->getRepr();
                Inkscape::UI::Widget::ColorPreview *cpv = Gtk::manage(new Inkscape::UI::Widget::ColorPreview(stop->get_rgba32()));
                GdkPixbuf *pb = cpv->toPixbuf(32, 16);
                Glib::ustring label = gr_ellipsize_text(repr->attribute("id"), 25);

                gtk_list_store_append(store, &iter);
                gtk_list_store_set(store, &iter, 0, label.c_str(), 1, pb, 2, stop, -1);
                sensitive = FALSE;
            }
        }

        sensitive = TRUE;
    }

    if (gr_multi) {
        sensitive = FALSE;
    }

    if (new_stop == NULL) {
        gtk_combo_box_set_active(GTK_COMBO_BOX(stop_combo) , 0);
    } else {
        select_stop_in_list(stop_combo, gradient, new_stop, widget, TRUE);
    }

    blocked = FALSE;

    return sensitive;
}
Exemple #11
0
static void sp_grd_ed_add_stop(GtkWidget */*widget*/,  GtkWidget *vb)
{
    SPGradient *gradient = static_cast<SPGradient *>(g_object_get_data(G_OBJECT(vb), "gradient"));
    verify_grad(gradient);

    SPStop *stop = get_selected_stop(vb);
    if (!stop) {
        return;
    }

    Inkscape::XML::Node *new_stop_repr = NULL;

    SPStop *next = stop->getNextStop();

    if (next == NULL) {
        SPStop *prev = stop->getPrevStop();
        if (prev != NULL) {
            next = stop;
            stop = prev;
        }
    }

    if (next != NULL) {
        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<SPStop *>(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 = sp_average_color(c1, c2);

    Inkscape::CSSOStringStream os;
    gchar c[64];
    sp_svg_write_color(c, sizeof(c), cnew);
    gdouble opacity = static_cast<gdouble>(SP_RGBA32_A_F(cnew));
    os << "stop-color:" << c << ";stop-opacity:" << opacity <<";";
    newstop->getRepr()->setAttribute("style", os.str().c_str());
    sp_repr_set_css_double( newstop->getRepr(), "offset", (double)newstop->offset);

    sp_gradient_vector_widget_load_gradient(vb, gradient);
    Inkscape::GC::release(new_stop_repr);
    update_stop_list(GTK_WIDGET(vb), gradient, newstop);
    GtkWidget *offspin = GTK_WIDGET(g_object_get_data(G_OBJECT(vb), "offspn"));
    GtkWidget *offslide =GTK_WIDGET(g_object_get_data(G_OBJECT(vb), "offslide"));
    gtk_widget_set_sensitive(offslide, TRUE);
    gtk_widget_set_sensitive(GTK_WIDGET(offspin), TRUE);
    DocumentUndo::done(gradient->document, SP_VERB_CONTEXT_GRADIENT,
                       _("Add gradient stop"));
}
Exemple #12
0
static void update_stop_list( GtkWidget *vb, SPGradient *gradient, SPStop *new_stop)
{

    if (!SP_IS_GRADIENT(gradient)) {
        return;
    }

    blocked = TRUE;

    /* Clear old list, if there is any */
    GtkWidget *combo_box = static_cast<GtkWidget *>(g_object_get_data(G_OBJECT(vb), "combo_box"));
    if (!combo_box) {
        return;
    }
    GtkListStore *store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combo_box)));
    if (!store) {
        return;
    }
    gtk_list_store_clear(store);
    GtkTreeIter iter;

    /* Populate the combobox store */
    GSList *sl = NULL;
    if ( gradient->hasStops() ) {
        for ( SPObject *ochild = gradient->firstChild() ; ochild ; ochild = ochild->getNext() ) {
            if (SP_IS_STOP(ochild)) {
                sl = g_slist_append(sl, ochild);
            }
        }
    }
    if (!sl) {
        gtk_list_store_append (store, &iter);
        gtk_list_store_set (store, &iter, 0, NULL, 1, _("No stops in gradient"), 2, NULL, -1);
        gtk_widget_set_sensitive (combo_box, FALSE);

    } else {

        for (; sl != NULL; sl = sl->next){
            if (SP_IS_STOP(sl->data)){
                SPStop *stop = SP_STOP(sl->data);
                Inkscape::XML::Node *repr = reinterpret_cast<SPItem *>(sl->data)->getRepr();
                Inkscape::UI::Widget::ColorPreview *cpv = Gtk::manage(new Inkscape::UI::Widget::ColorPreview(stop->get_rgba32()));
                GdkPixbuf *pb = cpv->toPixbuf(64, 16);

                gtk_list_store_append (store, &iter);
                gtk_list_store_set (store, &iter, 0, pb, 1, repr->attribute("id"), 2, stop, -1);
                gtk_widget_set_sensitive (combo_box, FALSE);
            }
        }

        gtk_widget_set_sensitive(combo_box, TRUE);
    }

    /* Set history */
    if (new_stop == NULL) {
        gtk_combo_box_set_active (GTK_COMBO_BOX(combo_box) , 0);
    } else {
        select_stop_in_list(vb, gradient, new_stop);
    }

    blocked = FALSE;
}
Exemple #13
0
static void sp_gradient_vector_color_changed(Inkscape::UI::SelectedColor *selected_color, GObject *object)
{
    (void)selected_color;

    void* updating_color = g_object_get_data(G_OBJECT(object), "updating_color");
    if (updating_color) {
        return;
    }

    if (blocked) {
        return;
    }

    SPGradient *gradient = static_cast<SPGradient*>(g_object_get_data(G_OBJECT(object), "gradient"));
    if (!gradient) {
        return;
    }

    blocked = TRUE;

    SPGradient *ngr = sp_gradient_ensure_vector_normalized(gradient);
    if (ngr != gradient) {
        /* Our master gradient has changed */
        sp_gradient_vector_widget_load_gradient(GTK_WIDGET(object), ngr);
    }

    ngr->ensureVector();

    /* Set start parameters */
    /* We rely on normalized vector, i.e. stops HAVE to exist */
    g_return_if_fail(ngr->getFirstStop() != NULL);

    SPStop *stop = get_selected_stop(GTK_WIDGET(object));
    if (!stop) {
        return;
    }

    SelectedColor *csel = static_cast<SelectedColor *>(g_object_get_data(G_OBJECT(object), "cselector"));
    SPColor color;
    float alpha = 0;
    csel->colorAlpha(color, alpha);

    sp_repr_set_css_double(stop->getRepr(), "offset", stop->offset);
    Inkscape::CSSOStringStream os;
    os << "stop-color:" << color.toString() << ";stop-opacity:" << static_cast<gdouble>(alpha) <<";";
    stop->getRepr()->setAttribute("style", os.str().c_str());
    // g_snprintf(c, 256, "stop-color:#%06x;stop-opacity:%g;", rgb >> 8, static_cast<gdouble>(alpha));
    //stop->getRepr()->setAttribute("style", c);

    DocumentUndo::done(ngr->document, SP_VERB_CONTEXT_GRADIENT,
                       _("Change gradient stop color"));

    blocked = FALSE;

    // Set the color in the selected stop after change
    GtkWidget *combo_box = static_cast<GtkWidget *>(g_object_get_data(G_OBJECT(object), "combo_box"));
    if (combo_box) {
        GtkTreeIter  iter;
        if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX(combo_box), &iter)) {
            GtkListStore *store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combo_box)));

            Inkscape::UI::Widget::ColorPreview *cp = Gtk::manage(new Inkscape::UI::Widget::ColorPreview(stop->get_rgba32()));
            GdkPixbuf *pb = cp->toPixbuf(64, 16);

            gtk_list_store_set (store, &iter, 0, pb, /*1, repr->attribute("id"),*/ 2, stop, -1);
        }
    }

}
Exemple #14
0
static void sp_gradient_vector_widget_load_gradient(GtkWidget *widget, SPGradient *gradient)
{
    blocked = TRUE;

    SPGradient *old;

    old = static_cast<SPGradient*>(g_object_get_data(G_OBJECT(widget), "gradient"));

    if (old != gradient) {
        sigc::connection *release_connection;
        sigc::connection *modified_connection;

        release_connection = static_cast<sigc::connection *>(g_object_get_data(G_OBJECT(widget), "gradient_release_connection"));
        modified_connection = static_cast<sigc::connection *>(g_object_get_data(G_OBJECT(widget), "gradient_modified_connection"));

        if (old) {
            g_assert( release_connection != NULL );
            g_assert( modified_connection != NULL );
            release_connection->disconnect();
            modified_connection->disconnect();
            sp_signal_disconnect_by_data(old, widget);
        }

        if (gradient) {
            if (!release_connection) {
                release_connection = new sigc::connection();
            }
            if (!modified_connection) {
                modified_connection = new sigc::connection();
            }
            *release_connection = gradient->connectRelease(sigc::bind<1>(sigc::ptr_fun(&sp_gradient_vector_gradient_release), widget));
            *modified_connection = gradient->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_gradient_vector_gradient_modified), widget));
        } else {
            if (release_connection) {
                delete release_connection;
                release_connection = NULL;
            }
            if (modified_connection) {
                delete modified_connection;
                modified_connection = NULL;
            }
        }

        g_object_set_data(G_OBJECT(widget), "gradient_release_connection", release_connection);
        g_object_set_data(G_OBJECT(widget), "gradient_modified_connection", modified_connection);
    }

    g_object_set_data(G_OBJECT(widget), "gradient", gradient);

    if (gradient) {
        gtk_widget_set_sensitive(widget, TRUE);

        gradient->ensureVector();

        SPStop *stop = get_selected_stop(widget);
        if (!stop) {
            return;
        }

        // get the color selector
        SelectedColor *csel =  static_cast<SelectedColor*>(g_object_get_data(G_OBJECT(widget), "cselector"));

        g_object_set_data(G_OBJECT(widget), "updating_color", reinterpret_cast<void*>(1));
        csel->setColorAlpha(stop->getEffectiveColor(), stop->opacity);
        g_object_set_data(G_OBJECT(widget), "updating_color", reinterpret_cast<void*>(0));

        /* Fill preview */
        GtkWidget *w = static_cast<GtkWidget *>(g_object_get_data(G_OBJECT(widget), "preview"));
        sp_gradient_image_set_gradient(SP_GRADIENT_IMAGE(w), gradient);

        update_stop_list(GTK_WIDGET(widget), gradient, NULL);

        // Once the user edits a gradient, it stops being auto-collectable
        if (gradient->getRepr()->attribute("inkscape:collect")) {
            SPDocument *document = gradient->document;
            bool saved = DocumentUndo::getUndoSensitive(document);
            DocumentUndo::setUndoSensitive(document, false);
            gradient->getRepr()->setAttribute("inkscape:collect", NULL);
            DocumentUndo::setUndoSensitive(document, saved);
        }
    } else { // no gradient, disable everything
        gtk_widget_set_sensitive(widget, FALSE);
    }

    blocked = FALSE;
}
Exemple #15
0
/** Creates normalized color vector */
void SPGradient::rebuildVector()
{
    gint len = 0;
    for ( SPObject *child = firstChild() ; child ; child = child->getNext() ) {
        if (SP_IS_STOP(child)) {
            len ++;
        }
    }

    has_stops = (len != 0);

    vector.stops.clear();

    SPGradient *reffed = ref ? ref->getObject() : NULL;
    if ( !hasStops() && reffed ) {
        /* Copy vector from referenced gradient */
        vector.built = true;   // Prevent infinite recursion.
        reffed->ensureVector();
        if (!reffed->vector.stops.empty()) {
            vector.built = reffed->vector.built;
            vector.stops.assign(reffed->vector.stops.begin(), reffed->vector.stops.end());
            return;
        }
    }

    for ( SPObject *child = firstChild(); child; child = child->getNext() ) {
        if (SP_IS_STOP(child)) {
            SPStop *stop = SP_STOP(child);

            SPGradientStop gstop;
            if (!vector.stops.empty()) {
                // "Each gradient offset value is required to be equal to or greater than the
                // previous gradient stop's offset value. If a given gradient stop's offset
                // value is not equal to or greater than all previous offset values, then the
                // offset value is adjusted to be equal to the largest of all previous offset
                // values."
                gstop.offset = MAX(stop->offset, vector.stops.back().offset);
            } else {
                gstop.offset = stop->offset;
            }

            // "Gradient offset values less than 0 (or less than 0%) are rounded up to
            // 0%. Gradient offset values greater than 1 (or greater than 100%) are rounded
            // down to 100%."
            gstop.offset = CLAMP(gstop.offset, 0, 1);

            gstop.color = stop->getEffectiveColor();
            gstop.opacity = stop->opacity;

            vector.stops.push_back(gstop);
        }
    }

    // Normalize per section 13.2.4 of SVG 1.1.
    if (vector.stops.empty()) {
        /* "If no stops are defined, then painting shall occur as if 'none' were specified as the
         * paint style."
         */
        {
            SPGradientStop gstop;
            gstop.offset = 0.0;
            gstop.color.set( 0x00000000 );
            gstop.opacity = 0.0;
            vector.stops.push_back(gstop);
        }
        {
            SPGradientStop gstop;
            gstop.offset = 1.0;
            gstop.color.set( 0x00000000 );
            gstop.opacity = 0.0;
            vector.stops.push_back(gstop);
        }
    } else {
        /* "If one stop is defined, then paint with the solid color fill using the color defined
         * for that gradient stop."
         */
        if (vector.stops.front().offset > 0.0) {
            // If the first one is not at 0, then insert a copy of the first at 0.
            SPGradientStop gstop;
            gstop.offset = 0.0;
            gstop.color = vector.stops.front().color;
            gstop.opacity = vector.stops.front().opacity;
            vector.stops.insert(vector.stops.begin(), gstop);
        }
        if (vector.stops.back().offset < 1.0) {
            // If the last one is not at 1, then insert a copy of the last at 1.
            SPGradientStop gstop;
            gstop.offset = 1.0;
            gstop.color = vector.stops.back().color;
            gstop.opacity = vector.stops.back().opacity;
            vector.stops.push_back(gstop);
        }
    }

    vector.built = true;
}