In the previous tutorial we have introduced linear gradients, the simplest gradient paint type provided by the OpenVG API. In this tutorial we will have a look at a much more complex gradient type: radial gradients.

Radial gradients define a scalar-valued gradient function based on a gradient circle defined by a center point `(cx, cy)`, a radius `r`, and a focal point `(fx, fy)` that is forced to lie within the circle. The scalar function has the following properties:

• it is equal to `0` at the focal point `(fx, fy)`

• it is equal to `1` along the circumference of the gradient circle

• elsewhere, it is equal to the distance between `(x, y)` and `(fx, fy)` divided by the length of the line segment starting at `(fx, fy)`, passing through `(x, y)`, and ending on the circumference of the gradient circle

(side note: if the gradient radius is less than or equal to `0`, the function is given the value `1` everywhere)

gradFunc(x, y) = D / (D + d)

Such scalar value is mapped to colors by color ramps, exactly as it happens for linear gradients (have a look at it for more details).

Geometric properties (center, focus and radius) defining a radial gradient are specified in the paint coordinates system; according to the OpenVG pipeline, such system is then transported to the path coordinates system throught a “paint-to-user” affine matrix (`VG_MATRIX_FILL_PAINT_TO_USER` / `VG_MATRIX_STROKE_PAINT_TO_USER`). Finally the filled/stroked path is moved to the drawing surface system by another affine matrix, the so called “path-user-to-surface” (`VG_MATRIX_PATH_USER_TO_SURFACE`).

To enable radial gradient paint, use `vgSetParameteri` to set the paint type to `VG_PAINT_TYPE_RADIAL_GRADIENT`. The radial gradient parameters are set using `vgSetParameterfv` with a `paramType` argument of `VG_PAINT_RADIAL_GRADIENT`. The gradient values are supplied as a vector of 5 floats in the order `cx`, `cy`, `fx`, `fy`, `r`.

``````// create a paint object
// set geometric parameters for the radial gradient (cx, cy), (fx, fy), r
``````

Color ramps behaviour does not change for radial gradients, it’s the same as specified for linear gradients: `VG_PAINT_COLOR_RAMP_STOPS` parameter takes an array of floating-point values giving the offsets and colors of the stops, and all the three spread modes are supported too (`VG_COLOR_RAMP_SPREAD_PAD`, `VG_COLOR_RAMP_SPREAD_REPEAT`, `VG_COLOR_RAMP_SPREAD_REFLECT`).

## The tutorial code

In order to simplify both code and understanding, we will set the `VG_MATRIX_FILL_PAINT_TO_USER` matrix to the identity: so the paint coordinates system will coincide with the path-user one. An additional simplification is given by setting the `VG_MATRIX_PATH_USER_TO_SURFACE` matrix as a unifrom scale plus a translation only (see `tutorialDraw` function):

``````vgSeti(VG_MATRIX_MODE, VG_MATRIX_PATH_USER_TO_SURFACE);
vgTranslate(userToSurfaceTranslation[X], userToSurfaceTranslation[Y]);
vgScale(userToSurfaceScale, userToSurfaceScale);
``````

So we can implement a simple map from the paint coordinates system to the drawing surface system as follow:

``````// Map a point from the paint coordinates system to the drawing surface system.
// NB: in the general case we should have applied a matrix transformation given by the
// concatenation of VG_MATRIX_FILL_PAINT_TO_USER and VG_MATRIX_PATH_USER_TO_SURFACE.
PaintToSurface(paintPoint) = (paintPoint * userToSurfaceScale) + userToSurfaceTranslation
``````

The inverse transformation maps a point from the drawing surface coordinates system to the paint system:

``````// NB: in the general case we should have applied a matrix transformation given by the
// concatenation of inverse(VG_MATRIX_PATH_USER_TO_SURFACE) and inverse(VG_MATRIX_FILL_PAINT_TO_USER) matrices.
SurfaceToPaint(surfacePoint) = (surfacePoint - userToSurfaceTranslation) / userToSurfaceScale
``````

Instead, a different approach must be taken for the gradient radius: the radius represents the value of a distance from the gradient center point, so it is translation agnostic. This implies that given a radius in the paint coordinates system, its value in the drawing surface system is simply calculated by multiplying (or dividing, if going from surface to paint system) it by the scale factor only:

``````PaintToSurface(paintRadius) = paintRadius * userToSurfaceScale
``````

The tutorial draws a circle path at the center of drawing surface, filled with a radial gradient. The path is created in object space with center at `(0, 0)` and a radius of `1` (see `genPaths` function):

``````// create the circle that will be filled by the radial gradient paint
filledCircle = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F,
1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
vguEllipse(filledCircle, 0.0f, 0.0f, 2.0f, 2.0f);
``````

By having defined the path in this tricky way, it’s really easy to scale and center it in order to fit the drawing surface:

``````// calculate "path user to surface" transformation
void userToSurfaceCalc(const int surfaceWidth,
const int surfaceHeight) {
// find the minimum dimension between surface width and height, then halve it
int halfDim = (surfaceWidth < surfaceHeight) ? (surfaceWidth / 2)
: (surfaceHeight / 2);
// calculate scale factor in order to cover 90% of it
userToSurfaceScale = halfDim * 0.9f;
// translate to the surface center
userToSurfaceTranslation[X] = surfaceWidth / 2;
userToSurfaceTranslation[Y] = surfaceHeight / 2;
}
``````

Gradient control points (i.e. center and focus) are highlighted by two white spots that can be moved by using mouse/touch. Even the gradient radius (highlighted by a white circle) can be enlarged or shrinked by dragging it. Such geometric parameters are stored, in paint coordinates system, by three variables:

``````// radial gradient parameters: center, focus and radius
• every time we need to know their coordinates in surface space (e.g. when we want to draw them, within `tutorialDraw` function), we use the `PaintToSurface` mapping (see `gradientParamsGet` function)
• every time we need to set a gradient control point (or the radius) in order to match the mouse/touch position (that is expressed in drawing surface space), we use the `SurfaceToPaint` mapping (see `gradientParamsSet` function)
The `VG_MZT_color_ramp_interpolation` extension, that we have seen in the previous tutorial, is supported by radial grandients too.