sp-gradient.cpp revision 08c7fad9770a23c9f39bee80a844eee3b53f299c
/** \file
* SPGradient, SPStop, SPLinearGradient, SPRadialGradient.
*/
/*
* Authors:
* Lauris Kaplinski <lauris@kaplinski.com>
* bulia byak <buliabyak@users.sf.net>
* Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
*
* Copyright (C) 1999-2002 Lauris Kaplinski
* Copyright (C) 2000-2001 Ximian, Inc.
* Copyright (C) 2004 David Turner
* Copyright (C) 2009 Jasper van de Gronde
*
* Released under GNU GPL, read the file 'COPYING' for more information
*
*/
#define noSP_GRADIENT_VERBOSE
#include <cstring>
#include <string>
#include <libnr/nr-matrix-fns.h>
#include <libnr/nr-matrix-ops.h>
#include <libnr/nr-matrix-scale-ops.h>
#include "libnr/nr-gradient.h"
#include "libnr/nr-pixops.h"
#include "svg/svg-color.h"
#include "svg/css-ostringstream.h"
#include "attributes.h"
#include "document-private.h"
#include "sp-gradient.h"
#include "gradient-chemistry.h"
#include "sp-gradient-reference.h"
#include "sp-linear-gradient.h"
#include "sp-radial-gradient.h"
#include "sp-stop.h"
#include "streq.h"
#include "uri.h"
#define SP_MACROS_SILENT
#include "macros.h"
/// Has to be power of 2
#define NCOLORS NR_GRADIENT_VECTOR_LENGTH
static Inkscape::XML::Node *sp_stop_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
static SPObjectClass *stop_parent_class;
class SPGradientImpl
{
friend class SPGradient;
static Inkscape::XML::Node *write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags);
};
/**
* Registers SPStop class and returns its type.
*/
{
if (!type) {
sizeof(SPStopClass),
sizeof(SPStop),
16,
NULL, /* value_table */
};
}
return type;
}
/**
* Callback to initialize SPStop vtable.
*/
{
}
/**
* Callback to initialize SPStop object.
*/
static void
{
stop->currentColor = false;
}
/**
* Virtual build: set stop attributes from its associated XML node.
*/
{
}
/**
* Virtual set: set attribute to value.
*/
static void
{
switch (key) {
case SP_ATTR_STYLE: {
/** \todo
* fixme: We are reading simple values 3 times during build (Lauris).
* \par
* We need presentation attributes etc.
* \par
* remove the hackish "style reading" from here: see comments in
* sp_object_get_style_property about the bugs in our current
* approach. However, note that SPStyle doesn't currently have
* stop-color and stop-opacity properties.
*/
{
if (streq(p, "currentColor")) {
stop->currentColor = true;
} else {
}
}
{
}
break;
}
case SP_PROP_STOP_COLOR: {
{
if (streq(p, "currentColor")) {
stop->currentColor = true;
} else {
stop->currentColor = false;
}
}
break;
}
case SP_PROP_STOP_OPACITY: {
{
}
break;
}
case SP_ATTR_OFFSET: {
break;
}
default: {
break;
}
}
}
/**
* Virtual write: write object attributes to repr.
*/
sp_stop_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
{
}
// Since we do a hackish style setting here (because SPStyle does not support stop-color and
// stop-opacity), we must do it AFTER calling the parent write method; otherwise
// sp_object_write would clear our style= attribute (bug 1695287)
os << "stop-color:";
if (stop->currentColor) {
os << "currentColor";
} else {
gchar c[64];
sp_svg_write_color(c, sizeof(c), specifiedcolor);
os << c;
}
/* strictly speaking, offset an SVG <number> rather than a CSS one, but exponents make no sense
* for offset proportions. */
return repr;
}
bool SPGradient::hasStops() const
{
return has_stops;
}
bool SPGradient::isUnitsSet() const
{
return units_set;
}
{
return units;
}
bool SPGradient::isSpreadSet() const
{
return spread_set;
}
{
return spread;
}
{
}
}
/**
* Return stop's color as 32bit value.
*/
{
/* Default value: arbitrarily black. (SVG1.1 and CSS2 both say that the initial
* value depends on user agent, and don't give any further restrictions that I can
* see.) */
if (stop->currentColor) {
if (str) {
}
rgb0 | 0xff);
} else {
}
}
/**
* Return stop's color as SPColor.
*/
static SPColor
{
if (stop->currentColor) {
/* Default value: arbitrarily black. (SVG1.1 and CSS2 both say that the initial
* value depends on user agent, and don't give any further restrictions that I can
* see.) */
if (str) {
}
return ret;
} else {
return stop->specified_color;
}
}
/*
* Gradient
*/
static SPPaintServerClass *gradient_parent_class;
/**
* Registers SPGradient class and returns its type.
*/
{
static GType gradient_type = 0;
if (!gradient_type) {
sizeof(SPGradientClass),
sizeof(SPGradient),
16,
NULL, /* value_table */
};
&gradient_info, (GTypeFlags)0);
}
return gradient_type;
}
/**
* SPGradient vtable initialization.
*/
{
}
/**
* Callback for SPGradient object initialization.
*/
{
gr->ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(SPGradientImpl::gradientRefChanged), gr));
/** \todo
* Fixme: reprs being rearranged (e.g. via the XML editor)
* may require us to clear the state.
*/
}
/**
* Virtual build: set gradient attributes from its associated repr.
*/
{
// Work-around in case a swatch had been marked for immediate collection:
}
}
if (SP_IS_STOP(ochild)) {
break;
}
}
/* Register ourselves */
}
/**
* Virtual release of SPGradient members before destruction.
*/
{
#ifdef SP_GRADIENT_VERBOSE
#endif
if (SP_OBJECT_DOCUMENT(object)) {
/* Unregister ourselves */
}
}
}
}
/**
* Set gradient attribute to value.
*/
{
switch (key) {
case SP_ATTR_GRADIENTUNITS:
if (value) {
} else {
}
} else {
}
break;
case SP_ATTR_GRADIENTTRANSFORM: {
gr->gradientTransform = t;
} else {
}
break;
}
case SP_ATTR_SPREADMETHOD:
if (value) {
} else {
}
} else {
}
break;
case SP_ATTR_XLINK_HREF:
if (value) {
try {
} catch (Inkscape::BadURIException &e) {
}
} else {
}
break;
case SP_ATTR_OSB_SWATCH:
{
bool modified = false;
modified = true;
}
if (newVal) {
modified = true;
}
}
if (modified) {
}
}
break;
default:
}
break;
}
}
/**
* Gets called when the gradient is (re)attached to another gradient.
*/
{
if (old_ref) {
}
if ( SP_IS_GRADIENT(ref)
{
gr->modified_connection = ref->connectModified(sigc::bind<2>(sigc::ptr_fun(&SPGradientImpl::gradientRefModified), gr));
}
// Per SVG, all unset attributes must be inherited from linked gradient.
// So, as we're now (re)linked, we assign linkee's values to this gradient if they are not yet set -
// but without setting the _set flags.
// FIXME: do the same for gradientTransform too
}
if (!gr->spread_set) {
}
/// \todo Fixme: what should the flags (second) argument be? */
}
/**
* Callback for child_added event.
*/
void SPGradientImpl::childAdded(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref)
{
gr->invalidateVector();
}
if ( gr->getStopCount() > 0 ) {
}
}
}
/// \todo Fixme: should we schedule "modified" here?
}
/**
* Callback for remove_child event.
*/
{
gr->invalidateVector();
}
if (SP_IS_STOP(ochild)) {
break;
}
}
if ( gr->getStopCount() == 0 ) {
g_message("Check on remove");
}
}
/* Fixme: should we schedule "modified" here? */
}
/**
* Callback for modified event.
*/
{
if (flags & SP_OBJECT_CHILD_MODIFIED_FLAG) {
gr->invalidateVector();
}
if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
gr->ensureColors();
}
// FIXME: climb up the ladder of hrefs
for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
l = g_slist_prepend(l, child);
}
l = g_slist_reverse(l);
while (l) {
l = g_slist_remove(l, child);
}
}
}
{
for (SPObject *ochild = sp_object_first_child(this); ochild && !first; ochild = SP_OBJECT_NEXT(ochild)) {
if (SP_IS_STOP(ochild)) {
}
}
return first;
}
int SPGradient::getStopCount() const
{
int count = 0;
for (SPStop *stop = const_cast<SPGradient*>(this)->getFirstStop(); stop && stop->getNextStop(); stop = stop->getNextStop()) {
count++;
}
return count;
}
/**
* Write gradient attributes to repr.
*/
Inkscape::XML::Node *SPGradientImpl::write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
{
}
if (flags & SP_OBJECT_WRITE_BUILD) {
if (crepr) {
l = g_slist_prepend(l, crepr);
}
}
while (l) {
l = g_slist_remove(l, l->data);
}
}
}
break;
default:
break;
}
}
g_free(c);
}
/* FIXME: Ensure that gr->spread is the inherited value
* if !gr->spread_set. Not currently happening: see SPGradient::modified.
*/
break;
break;
default:
break;
}
}
} else {
}
} else {
}
return repr;
}
/**
* Forces the vector to be built, if not present (i.e., changed).
*
* \pre SP_IS_GRADIENT(gradient).
*/
void SPGradient::ensureVector()
{
}
}
/**
* Set units property of gradient and emit modified.
*/
{
}
}
/**
* Set spread property of gradient and emit modified.
*/
{
spread_set = TRUE;
}
}
/**
* Returns the first of {src, src-\>ref-\>getObject(),
* src-\>ref-\>getObject()-\>ref-\>getObject(),...}
* for which \a match is true, or NULL if none found.
*
* The raison d'être of this routine is that it correctly handles cycles in the href chain (e.g., if
* a gradient gives itself as its href, or if each of two gradients gives the other as its href).
*
* \pre SP_IS_GRADIENT(src).
*/
static SPGradient *
{
/* Use a pair of pointers for detecting loops: p1 advances half as fast as p2. If there is a
loop, then once p1 has entered the loop, we'll detect it the next time the distance between
p1 and p2 is a multiple of the loop size. */
bool do1 = false;
for (;;) {
return p2;
}
if (!p2) {
return p2;
}
if (do1) {
}
/* We've been here before, so return NULL to indicate that no matching gradient found
* in the chain. */
return NULL;
}
}
}
/**
* True if gradient has stops.
*/
{
}
/**
* True if gradient has spread set.
*/
{
return gr->isSpreadSet();
}
/**
* True if gradient has units set.
*/
static bool
{
return gr->isUnitsSet();
}
{
if (force_vector) {
}
return src;
}
/**
* Returns the effective spread of given gradient (climbing up the refs chain if needed).
*
* \pre SP_IS_GRADIENT(gradient).
*/
{
return ( src
: SP_GRADIENT_SPREAD_PAD ); // pad is the default
}
/**
* Returns the effective units of given gradient (climbing up the refs chain if needed).
*
* \pre SP_IS_GRADIENT(gradient).
*/
{
return ( src
: SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX ); // bbox is the default
}
/**
* Clears the gradient's svg:stop children from its repr.
*/
void
{
/* Collect stops from original repr */
}
}
/* Remove all stops */
while (sl) {
/** \todo
* fixme: This should work, unless we make gradient
* into generic group.
*/
}
}
/**
* Writes the gradient's internal vector (whether from its own stops, or
* inherited from refs) into the gradient repr as svg:stop elements.
*/
void
{
/* We have to be careful, as vector may be our own, so construct repr list at first */
/* strictly speaking, offset an SVG <number> rather than a CSS one, but exponents make no
* sense for offset proportions. */
gchar c[64];
/* Order will be reversed here */
}
/* And insert new children from list */
while (cl) {
}
}
{
if ( gradient->invalidateVector() ) {
// Conditional to avoid causing infinite loop if there's a cycle in the href chain.
}
}
/** Return true if change made. */
bool SPGradient::invalidateVector()
{
bool ret = false;
ret = true;
}
ret = true;
}
return ret;
}
/** Creates normalized color vector */
void SPGradient::rebuildVector()
{
if (SP_IS_STOP(child)) {
len ++;
}
}
/* Copy vector from referenced gradient */
reffed->ensureVector();
return;
}
}
if (SP_IS_STOP(child)) {
// "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."
} else {
}
// "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%."
}
}
// Normalize per section 13.2.4 of SVG 1.1.
/* "If no stops are defined, then painting shall occur as if 'none' were specified as the
* paint style."
*/
{
}
{
}
} else {
/* "If one stop is defined, then paint with the solid color fill using the color defined
* for that gradient stop."
*/
// If the first one is not at 0, then insert a copy of the first at 0.
}
// If the last one is not at 1, then insert a copy of the last at 1.
}
}
}
/**
* The gradient's color array is newly created and set up from vector.
*/
void SPGradient::ensureColors()
{
}
/// \todo Where is the memory freed?
if (!color) {
}
// This assumes that vector is a zero-order B-spline (box function) approximation of the "true" gradient.
// This means that the "true" gradient must be prefiltered using a zero order B-spline and then sampled.
// Furthermore, the first element corresponds to offset="0" and the last element to offset="1".
double remainder[4] = {0,0,0,0};
switch(spread) {
case SP_GRADIENT_SPREAD_PAD:
remainder[0] = 0.5*vector.stops[0].color.v.c[0]; // Half of the first cell uses the color of the first stop
remainder_for_end[0] = 0.5*vector.stops[vector.stops.size() - 1].color.v.c[0]; // Half of the first cell uses the color of the last stop
break;
// These two are handled differently, see below.
break;
default:
g_error("Spread type not supported!");
};
unsigned int ob = (unsigned int) floor(o0+.5); // These are the first and last element that might be affected by this interval.
unsigned int oe = (unsigned int) floor(o1+.5); // These need to be computed the same to ensure that ob will be covered by the next interval if oe==ob
// Simple case, this interval starts and stops within one cell
// The contribution of this interval is:
// (o1-o0)*(c(o0)+c(o1))/2
// = (o1-o0)*(c0+c1)/2
} else {
// First compute colors for the cells which are fully covered by the current interval.
// The prefiltered values are equal to the midpoint of each cell here.
// f = (j-o0)/(o1-o0)
// = j*(1/(o1-o0)) - o0/(o1-o0)
f += df;
}
// Now handle the beginning
// The contribution of the last point is already in remainder.
// The contribution of this point is:
// (ob+.5-o0)*(c(o0)+c(ob+.5))/2
// = (ob+.5-o0)*c((o0+ob+.5)/2)
// = (ob+.5-o0)*(c0+((o0+ob+.5)/2-o0)*df*(c1-c0))
// = (ob+.5-o0)*(c0+(ob+.5-o0)*df*(c1-c0)/2)
// The first half of the first cell is just a mirror image of the second half, so simply multiply it by 2.
// The first cell is the same as the last cell, so save whatever is in the second half here and deal with the rest later.
} else {
// The first half of the cell was already in remainder.
}
// Now handle the end, which should end up in remainder
// The contribution of this point is:
// (o1-oe+.5)*(c(o1)+c(oe-.5))/2
// = (o1-oe+.5)*c((o1+oe-.5)/2)
// = (o1-oe+.5)*(c0+((o1+oe-.5)/2-o0)*df*(c1-c0))
}
}
switch(spread) {
case SP_GRADIENT_SPREAD_PAD:
break;
// The second half is the same as the first half, so multiply by 2.
break;
// The second half is the same as the second half of the first cell (which was saved in remainder_for_end).
color[0] = color[4 * (NCOLORS-1) + 0] = (unsigned char) floor(255*(remainder[0]+remainder_for_end[0]) + .5);
color[1] = color[4 * (NCOLORS-1) + 1] = (unsigned char) floor(255*(remainder[1]+remainder_for_end[1]) + .5);
color[2] = color[4 * (NCOLORS-1) + 2] = (unsigned char) floor(255*(remainder[2]+remainder_for_end[2]) + .5);
color[3] = color[4 * (NCOLORS-1) + 3] = (unsigned char) floor(255*(remainder[3]+remainder_for_end[3]) + .5);
break;
}
}
/**
* Renders gradient vector to buffer as line.
*
* RGB buffer background should be set up beforehand.
*
* @param len,width,height,rowstride Buffer parameters (1 or 2 dimensional).
* @param span Full integer width of requested gradient.
* @param pos Buffer starting position in span.
*/
static void
{
g_return_if_fail(len > 0);
g_return_if_fail(pos >= 0);
g_return_if_fail(span > 0);
gradient->ensureColors();
}
/// \todo Can this be done with 4 byte copies?
}
}
/**
* Render rectangular RGBA area from gradient vector.
*/
void
{
g_return_if_fail(width > 0);
g_return_if_fail(height > 0);
g_return_if_fail(pos >= 0);
g_return_if_fail(span > 0);
if (horizontal) {
}
} else {
*b++ = tmp[0];
*b++ = tmp[1];
*b++ = tmp[2];
*b++ = tmp[3];
}
tmp += 4;
}
}
}
/**
* Render rectangular RGB area from gradient vector.
*/
void
{
g_return_if_fail(width > 0);
g_return_if_fail(height > 0);
g_return_if_fail(pos >= 0);
g_return_if_fail(span > 0);
if (horizontal) {
gint a = t[3];
buf += 3;
t += 4;
}
}
} else {
gint a = t[3];
}
}
}
}
{
} else {
return ctm;
}
}
{
return ( gr->gradientTransform
} else {
}
}
void
{
}
}
/*
* Linear Gradient
*/
class SPLGPainter;
/// A context with linear gradient, painter, and gradient renderer.
struct SPLGPainter {
};
static Inkscape::XML::Node *sp_lineargradient_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr,
static SPGradientClass *lg_parent_class;
/**
* Register SPLinearGradient class and return its type.
*/
{
if (!type) {
sizeof(SPLinearGradientClass),
sizeof(SPLinearGradient),
16,
NULL, /* value_table */
};
}
return type;
}
/**
* SPLinearGradient vtable initialization.
*/
{
}
/**
* Callback for SPLinearGradient object initialization.
*/
{
}
/**
* Callback: set attributes from associated repr.
*/
{
}
/**
* Callback: set attribute.
*/
static void
{
switch (key) {
case SP_ATTR_X1:
break;
case SP_ATTR_Y1:
break;
case SP_ATTR_X2:
break;
case SP_ATTR_Y2:
break;
default:
break;
}
}
/**
* Callback: write attributes to associated repr.
*/
sp_lineargradient_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
{
}
return repr;
}
/**
* Create linear gradient context.
*
* Basically we have to deal with transformations
*
* 1) color2norm - maps point in (0,NCOLORS) vector to (0,1) vector
* 2) norm2pos - maps (0,1) vector to x1,y1 - x2,y2
* 2) gradientTransform
* 3) bbox2user
* 4) ctm == userspace to pixel grid
*
* See also (*) in sp-pattern about why we may need parent_transform.
*
* \todo (point 1 above) fixme: I do not know how to deal with start > 0
* and end < 1.
*/
{
gr->ensureColors();
}
/** \todo
* Technically speaking, we map NCOLORS on line [start,end] onto line
* [0,1]. I almost think we should fill color array start and end in
* that case. The alternative would be to leave these just empty garbage
* or something similar. Originally I had 1023.9999 here - not sure
* whether we have really to cut out ceil int (Lauris).
*/
/* BBox to user coordinate system */
} else {
/* Problem: What to do, if we have mixed lengths and percentages? */
/* Currently we do ignore percentages at all, but that is not good (lauris) */
}
// TODO: remove color2px_nr after converting to 2geom
}
static void
{
}
/**
* Directly set properties of linear gradient and request modified.
*/
void
{
/* fixme: units? (Lauris) */
}
/**
* Callback when linear gradient object is rendered.
*/
static void
{
}
}
/*
* Radial Gradient
*/
class SPRGPainter;
/// A context with radial gradient, painter, and gradient renderer.
struct SPRGPainter {
};
static Inkscape::XML::Node *sp_radialgradient_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr,
static SPGradientClass *rg_parent_class;
/**
* Register SPRadialGradient class and return its type.
*/
{
if (!type) {
sizeof(SPRadialGradientClass),
sizeof(SPRadialGradient),
16,
NULL, /* value_table */
};
}
return type;
}
/**
* SPRadialGradient vtable initialization.
*/
{
}
/**
* Callback for SPRadialGradient object initialization.
*/
static void
{
}
/**
* Set radial gradient attributes from associated repr.
*/
static void
{
}
/**
* Set radial gradient attribute.
*/
static void
{
switch (key) {
case SP_ATTR_CX:
}
}
break;
case SP_ATTR_CY:
}
}
break;
case SP_ATTR_R:
}
break;
case SP_ATTR_FX:
}
break;
case SP_ATTR_FY:
}
break;
default:
break;
}
}
/**
* Write radial gradient attributes to associated repr.
*/
sp_radialgradient_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
{
}
if ((flags & SP_OBJECT_WRITE_ALL) || rg->cx._set) sp_repr_set_svg_double(repr, "cx", rg->cx.computed);
if ((flags & SP_OBJECT_WRITE_ALL) || rg->cy._set) sp_repr_set_svg_double(repr, "cy", rg->cy.computed);
if ((flags & SP_OBJECT_WRITE_ALL) || rg->fx._set) sp_repr_set_svg_double(repr, "fx", rg->fx.computed);
if ((flags & SP_OBJECT_WRITE_ALL) || rg->fy._set) sp_repr_set_svg_double(repr, "fy", rg->fy.computed);
return repr;
}
/**
* Create radial gradient context.
*/
{
gr->ensureColors();
}
/** \todo
* fixme: We may try to normalize here too, look at
* linearGradient (Lauris)
*/
/* BBox to user coordinate system */
} else {
/** \todo
* Problem: What to do, if we have mixed lengths and percentages?
* Currently we do ignore percentages at all, but that is not
* good (lauris)
*/
}
// TODO: remove gs2px_nr after converting to 2geom
&gs2px_nr,
}
static void
{
}
/**
* Directly set properties of radial gradient and request modified.
*/
void
{
/* fixme: units? (Lauris) */
}
/**
* Callback when radial gradient object is rendered.
*/
static void
{
}
}
/*
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:encoding=utf-8:textwidth=99 :