Brushes
Table of contents
In the previous examples, the elements that were drawn on the graphics surface (rectangles, paths and text) were all drawn using a solid Colour
. However, you may have noticed that the all the Fill
/Stroke
methods accept a Brush
parameter, rather than a Colour
. VectSharp currently implements three kinds of Brush
es:
-
A
SolidColourBrush
represents a brush that fills/strokes with a solid colour. TheSolidColourBrush
constructor accepts a single parameter, corresponding to theColour
of the brush. There is also an implicit conversion operator that is defined between aColour
and aSolidColourBrush
(and aBrush
), which is why we were able to directly provide aColour
to methods that actually require aBrush
as an parameter. -
A
LinearGradientBrush
represents a brush that paints with a linear gradient. The constructor for this class takes twoPoint
parameters, which determine the start and end point of the gradient, and a collection ofGradientStop
s, which determine the colours in the gradient (see below). -
A
RadialGradientBrush
represents a brush that paints with a radial gradient. The constructor for this class requires twoPoint
parameters, determining the centre of the circle defining the gradient and the starting point of the gradient, and adouble
which represents the radius of the circle (this is explained in more detail below). It also requires a collection ofGradientStop
s.
The colours used by a gradient brush are defined by a series of GradientStop
s. A GradientStop
associates a Colour
with a double
indicating its position (offset
) in the gradient: an offset
of 0
corresponds to the start of the gradient, an offset of 1
corresponds to the end of the gradient, and intermediate values correspond to intermediate points. Accordingly, the constructor for the GradientStop
class accepts two parameters, corresponding to the Colour
and the offset
.
A collection of GradientStop
objects can be represented as a GradientStops
object (note the plural), which implements IReadOnlyList<GradientStop>
. This provides a method GetColourAt(double position)
, which returns the colour at a specific offset (for offsets ≤ 0 or ≥ 1, the first and last colour are returned, respectively). There is also an implicit conversion operator defined allowing the conversion of a GradientStops
object into a Func<double, Colour>
.
Linear gradients
A linear gradient is defined by two Point
s, determining the start and end of the gradient, and a number of GradientStops
indicating the colours used for the gradient. The following example shows how to use a linear gradient to fill a rectangle:
Expand the section below for more information on how the colour of each point in a linear gradient is determined.
Determining the colour of a point in a linear gradient
In general, a gradient (linear or otherwise) defines a mapping $C$ from $\left [ 0, 1 \right ]$ to the space of RGBA colours. This is defined by the GradientStop
s. Given a real value $0 \leq t \leq 1$, to determine the colour associated to this value:
-
Find the
GradientStop
with the largestOffset
value $s$ such that $s \leq t$. Let the colour of thisGradientStop
be $\mathbf{S}$. -
Find the
GradientStop
with the smallestOffset
value $l$ such that $l \geq t$. Let the colour of thisGradientStop
be $\mathbf{L}$. -
If $s = l$, $\mathbf{C}\left ( t \right ) := \mathbf{L}$. Otherwise, the colour is determined by the following equation:
Now the task is, given an arbitrary point $\mathbf{P} \in \mathbb{R}^2$ on the plane, to find the corresponding value of $t$. To do this, we need to project the point on the line defined by the start and end point of the gradient.
Let $\mathbf{P_0}$ and $\mathbf{P_1}$ be, respectively, the start and end point. Then, $t$ can be found using the following equation:
\[t(\mathbf{P}) = \max \left (0, \min \left ( 1, \frac{\left (\mathbf{P} - \mathbf{P_0} \right ) \cdot \left (\mathbf{P_1} - \mathbf{P_0} \right )}{\left \| \mathbf{P_1} - \mathbf{P_0} \right \|^2} \right ) \right )\]Here, $\cdot$ is the dot product, and $\lVert \mathbf{V} \rVert$ is the length of the vector $\mathbf{V}$.
Using these equations, given a point $\mathbf{P}$ you can compute $t$, and given $t$ you can determine the colour $\mathbf{C}$.
Radial gradients
In VectSharp, radial gradients are defined by a “focal point” and an outer circle. These identify a bundle of circumferences starting from the focal point at radius 0, with increasing radii and centers moving from the focal point to the centre of the outer circle.
The following example shows how to use a radial gradient to fill a rectangle:
Expand the section below for more information on how the colour of each point in a radial gradient is determined.
Determining the colour of a point in a radial gradient
As before, the task is, given an arbitrary point $\mathbf{P} \in \mathbb{R}^2$ on the plane, to find the corresponding value of $t$ to determine the colour of the gradient.
Let $\mathbf{F}$ be the focal point of the gradient, $\mathbf{O}$ be the centre, and $r$ be the radius.
For every $t \in \mathbb{R}$, consider the circumferences centred at $\mathbf{Q}\left ( t \right ) = (1 - t) \cdot \mathbf{F} + t \cdot \mathbf{O}$ with radius $s\left ( t \right) = t \cdot r$.
We seek to find the circumference(s) of this form, to which $\mathbf{P}$ belongs. If $\mathbf{F} \equiv \mathbf{O}$, the circumferences all have the same centre, and the radius of the circumference containing $\mathbf{P}$ is:
\[s = \left \| \mathbf{P} - \mathbf{F} \right \| = t \cdot r \Rightarrow t = \frac{s}{r} = \frac{\left \| \mathbf{P} - \mathbf{F} \right \|}{r}\]Otherwise, we need to solve the system of equations:
\[\left \{ \begin{array}{l} \left \| \mathbf{P} - \mathbf{Q} \right \| = t \cdot r \\ \mathbf{Q} = (1 - t) \cdot \mathbf{F} + t \cdot \mathbf{O} \end{array} \right .\]Substituting $\mathbf{Q}$ in the first equation and rearranging, we get:
\[\left \| \left (\mathbf{P} - \mathbf{F} \right ) - t \left (\mathbf{O} - \mathbf{F} \right ) \right \| = t \cdot r\]Squaring both sides and expanding the norm:
\[\left \| \mathbf{P} - \mathbf{F} \right \|^2 + t^2 \left \| \mathbf{O} - \mathbf{F} \right \|^2 - 2 t \cdot \left (\mathbf{P} - \mathbf{F} \right) \cdot \left ( \mathbf{O} - \mathbf{F} \right) = t^2 \cdot r^2\]Rearranging again:
\[\left ( \left \| \mathbf{O} - \mathbf{F} \right \|^2 - r^2 \right) t^2 - 2 \left ( \mathbf{P} - \mathbf{F} \right ) \cdot \left ( \mathbf{O} - \mathbf{F} \right ) \cdot t + \left \| \mathbf{P} - \mathbf{F} \right \|^2 = 0\]This is a simple second degree equation. If $\lVert \mathbf{O} - \mathbf{F} \rVert^2 - r^2 = 0$ (i.e., the focal point lies on the outer circumference), then if $\left ( \mathbf{P} - \mathbf{F} \right ) \cdot \left ( \mathbf{O} - \mathbf{F} \right ) = 0$, the equation has no solutions and the point is not affected by the gradient. If this term is not $0$, then:
\[t = \frac{ \left \| \mathbf{P} - \mathbf{F} \right \| ^2}{2 \left ( \mathbf{P} - \mathbf{F} \right ) \cdot \left ( \mathbf{O} - \mathbf{F} \right )}\]Otherwise, let:
\[d = \left (\left ( \mathbf{P} - \mathbf{F} \right ) \cdot \left ( \mathbf{O} - \mathbf{F} \right ) \right )^2 - \left \| \mathbf{P} - \mathbf{F} \right \|^2 \left ( \left \| \mathbf{O} - \mathbf{F} \right \|^2 - r^2 \right )\]If $d < 0$, then the equation has no real solutions and the point is not affected by the gradient. Otherwise, the solutions are:
\[t_1 = \frac{ \left ( \mathbf{P} - \mathbf{F} \right ) \cdot \left ( \mathbf{O} - \mathbf{F} \right ) + \sqrt{d} }{\left \| \mathbf{O} - \mathbf{F} \right \|^2 - r^2}\] \[t_2 = \frac{ \left ( \mathbf{P} - \mathbf{F} \right ) \cdot \left ( \mathbf{O} - \mathbf{F} \right ) - \sqrt{d} }{\left \| \mathbf{O} - \mathbf{F} \right \|^2 - r^2}\]Now, if both solutions lie in the interval $\left [ 0, 1 \right ]$, choose the smallest of the two; if instead only one lies in the interval $\left [ 0, 1 \right ]$, then choose that solution. Otherwise, if at least one solution is $> 1$, then $t = 1$. If both are $< 0$, then $t = 0$.
With this value of $t$, you can determine the colour to associate to the point based on the GradientStops
, using the algorithm described above when talking about linear gradients.
Standard gradients
VectSharp includes a number of “standard” gradients, defined in the static Gradients
class. These are implemented as static GradientStop
objects, which you can use when initialising a LinearGradientBrush
or a RadialGradientBrush
. For example:
For some of these gradients (nominally, those included in the “viridis” R package), the Gradients
class also defines a static method (e.g., Colour ViridisColouring(double x)
for the Viridis
gradient, Colour CividisColouring(double x)
for the Cividis
gradient, and so on), which can be used directly to get the colour in the gradient at the specified position. This is both faster and more accurate than using the GetColourAt
on the GradientStops
objects themselves (because the gradients are defined using only 20 colour stops, while these methods use 256 samples).
Here is a list of all the standard gradients included in VectSharp:
TransparentToBlack
- Gradient from transparent black (0) to opaque black (1).WhiteToBlack
- Gradient from white (0) to black (1).RedToGreen
- Gradient from red (0) to green (1).Rainbow
- Rainbow gradient (red, orange, yellow, green, blue, indigo, violet).RedYellowGreen
- Gradient from red (0) to yellow (0.5) to green (1).OkabeItoRainbow
- Rainbow gradient with Okabe-Ito colour-blind safe colours (https://jfly.uni-koeln.de/color/).OkabeItoRainbowDiscrete
- Rainbow gradient with Okabe-Ito colour-blind safe colours (https://jfly.uni-koeln.de/color/) in discrete steps.MutedRainbow
- Rainbow gradient with Paul Tol’s Muted palette (https://personal.sron.nl/~pault/).MutedRainbowDiscrete
- Rainbow gradient with Paul Tol’s Muted palette (https://personal.sron.nl/~pault/) in discrete steps.Magma
- Magma gradient, based on the homonymous colour scale included in the “viridis” R package (https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html).Inferno
- Inferno gradient, based on the homonymous colour scale included in the “viridis” R package (https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html).Plasma
- Plasma gradient, based on the homonymous colour scale included in the “viridis” R package (https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html).Viridis
- Viridis gradient, based on the homonymous colour scale included in the “viridis” R package (https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html).Cividis
- Cividis gradient, based on the homonymous colour scale included in the “viridis” R package (https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html).Rocket
- Rocket gradient, based on the homonymous colour scale included in the “viridis” R package (https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html).Mako
- Mako gradient, based on the homonymous colour scale included in the “viridis” R package (https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html).Turbo
- Turbo gradient, based on the homonymous colour scale included in the “viridis” R package (https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html).