Advanced Gradients

From Inkscape Wiki
Revision as of 13:42, 31 March 2009 by Jaspervdg (talk | contribs)
Jump to navigation Jump to search

There are (apparently) several types of gradients that can be interesting to support, but are not part of the SVG standard. This page is meant to document which gradient types Inkscape may want to support and how these could be simulated.

Separating shape/pattern from color

Any gradient can be defined by a mapping [math]\displaystyle{ f:\mathbb{R}^2\rightarrow[0,1] }[/math] from any point in the plane to a value in the range [math]\displaystyle{ [0,1] }[/math], combined with a mapping from the range [math]\displaystyle{ [0,1] }[/math] to colors. The former is defined by the type of gradient (and its parameters) and the latter by the gradient stops.

For each new gradient type it suffices to find a good method of generating [math]\displaystyle{ f }[/math] (over all four color channels). The desired colors can then be determined by using an feComponentTransfer filter with the table transfer function type. This allows the gradient to be approximated to any required accuracy. An example for a gradient from red to green to blue to transparent black (assumes we are in a filter definition and an image with [math]\displaystyle{ f }[/math] on all four channels is in "f"):

<feComponentTransfer in="f">
  <feFuncR type="table" tableValues="1 0 0 0" />
  <feFuncG type="table" tableValues="0 1 0 0" />
  <feFuncB type="table" tableValues="0 0 1 0" />
  <feFuncA type="table" tableValues="1 1 1 0" />

Alternatively the mapping to colors of the gradient could be done using feDisplacementMap, if [math]\displaystyle{ f }[/math] is pre-processed properly. Specifically, the values of [math]\displaystyle{ f }[/math] are meant to be interpreted in an absolute sense, while feDisplacementMap uses them as an offset relative to the current position. So if [math]\displaystyle{ (x1,y1),(x2,y2) }[/math] defines the bounding box of the object to which the gradient is applied, and a (horizontal) linear gradient is applied to a rectangle using these same coordinates, then the scale parameter should be set in such a way that [math]\displaystyle{ x1+scale*0.5=x2 }[/math] and [math]\displaystyle{ x2-scale*0.5=x1 }[/math], so [math]\displaystyle{ scale=2(x2-x1) }[/math]. The x displacement channel should then be set to a channel corresponding to [math]\displaystyle{ f }[/math] and the y displacement channel should then be set to some channel that is zero. An example for the same gradient as above (we are in a defs element, the alpha channel is assumed to correspond to [math]\displaystyle{ f }[/math] and the red channel to zero, the bounding box is assumed to be [math]\displaystyle{ (0,0),(1,1) }[/math]):

<linearGradient id="gradient">
  <stop offset="0" stop-color="red" />
  <stop offset="0.33" stop-color="green" />
  <stop offset="0.67" stop-color="blue" />
  <stop offset="1" stop-color="black" stop-opacity="0" />
<rect id="rect" x="0" y="0" width="1" height="1" fill="url(#gradient)" />
<linearGradient id="xgradient">
  <stop offset="0" stop-opacity="1" />
  <stop offset="1" stop-opacity="0" />
<rect id="xrect" x="0" y="0" width="1" height="1" fill="url(#xgradient)" />
<filter ...>
  <feImage xlink:href="#xrect" result="xoffset" />
  <feComposite in="f" in2="xoffset" operator="arithmetic" k2="0.5" k3="0.5" />
  <feImage xlink:href="#rect" result="gr" />
  <feDisplacementMap in="gr" in2="fp" scale="2" xChannelSelector="A" yChannelSelector="R" />

This last method has the advantage that there is a more direct correspondence with the gradient stops defined by the user (and does not use an approximation to the gradient as the feComponentTransfer method), but it is obviously also more complex.

Conical gradient

"A gradient which goes along the circular arc around a center." In other words (ignoring rotations and so on), given a center [math]\displaystyle{ (cx,cy) }[/math], the mapping [math]\displaystyle{ f }[/math] is defined as [math]\displaystyle{ f(x,y)=\frac{1}{2\pi}\arctan(y-cy,x-cx)+\frac{1}{2} }[/math].