Smooth transitions in mesh gradients

Mesh gradients are supposed to allow for smooth shading/colouring of vector graphics. The usual Coons (and tensor) patches are smooth on their interior, but across boundaries we may still have sudden changes (in derivative), leading to visual artefacts. Here I show how using tensor patches, while using the same interpolation for both positions and colours, allows us to overcome these problems. The key here is to use cubic rather than linear interpolation for colours (which Illustrator at least indeed seems to use), and to make effective use of the interior control points of tensor patches (Coons patches themselves cannot be easily modified to achieve the same effect, but we can develop a Coons-patch-like system that does support smooth boundaries). Note that just using tensor control points is not enough.


The above is a demonstration of how tensor patches using cubic interpolation for both positions and colours can help in creating smooth transitions between patches (where smooth is taken to mean that the derivative is continuous). Both gradients consist of four patches (one for each quadrant), but the left gradient uses linear interpolation for the colours, while the right one uses cubic (Bézier) interpolation such that the derivatives are matched on both sides of the boundary between the two patches (essentially by mirroring the values across the boundary, just like with the `S‘ path command). In both cases the position handles are mirrored across the boundary (again like with the `S‘ path command). Note that in this example this mostly means the derivatives are zero across the boundaries, but this is not true everywhere: the green channel essentially increases linearly from left to right at the top of both images (which is of course also smooth across the boundary). The handles for the above gradients are displayed below (just for the top halves):


Note that this effect cannot (in general) be created using Coons patches. In part, this is because if we represent a Coons patch using a tensor patch, the interior handles depend on (almost) all of the handles specified by the Coons patch, this makes it impossible to independently make the derivatives match the derivatives of neighbouring patches on all sides of a Coons patch.

It is possible to develop an alternative to Coons patches, that does allow matching derivatives across boundaries. In particular, if the interior control point p(2,2) is set equal to p(2,3)+p(3,2)-p(3,3) (and other interior control points follow analogously), then whenever a gradient is smooth at its crossings, it is smooth everywhere (drawing the situation immediately makes clear that the relevant symmetry properties of the handles on the boundary are inherited by the interior handles). This type of patch would have the same kind of number of control points as a Coons patch (except that we’re now treating colour on equal footing with position). However, in contrast to Coons patches it would allow matching derivatives across boundaries.

It is interesting to examine what is needed for everything to be smooth when four patches meet at a corner. For the gradient to be smooth across the “vertical” boundaries (I mean the boundaries between two patches that are adjacent in a single row in the definition of the mesh) all handles (including the “colour handles”) must be mirrored across those boundaries. For the gradient to be smooth across the horizontal boundaries, the handles must be mirrored across those boundaries as well. This means that the interior control points closest to the crossing are all interdependent (one is sufficient to determine the other three). In general, we always have one of the following four cases at any crossing of four patches (the thick blue lines indicate boundaries across which we mirror, circles with dotted strokes are mirrored handles):


Note that there is some leeway with “mirroring” the control points. The derivative of the gradient across the edge of a patch is essentially the derivative of the colours divided by the derivative of the positions, so we can scale both by the same factor and still end up with the same derivative at the boundary. This is demonstrated in the figure below (both are “smooth”, but the bottom uses “stretched” handles):


Mirroring colours across an edge might require “control colours” that are out of the normal range (supplied explicitly or implicitly as a result of some shorthand for mirroring the control colour). It would be possible to simply allow this, and specify that the colours are clipped in the output. After all, it is possible that although you need an out-of-range colour to match the derivative, you might not actually generate out-of-range colours (just like a Bézier curve typically does not go through its control points). And if you do generate out-of-range colours, the artist should see this, and he then has the possibility to work around the issue. Alternatively, the specification could demand that implementations clip the out-of-range control colours (scaling towards the control colour on the boundary) and scale the corresponding control points accordingly (relative to the control point on the boundary). In principle the transition would then remain smooth, as explained in the previous paragraph. This doesn’t really work well when the control point ends up (almost) equal to the control point on the boundary, but if that is the case then either the transition was not very large to begin with (suggesting we have a fairly abrupt boundary anyway), or the control colour at the boundary is practically on the boundary of the allowed range, making a smooth continuation fairly impossible. Of course we can always adjust the control colours on both sides of the boundary in such a way that they both fit within the allowed range (creating an effect reminiscent of monotonic interpolation), but this would also alter the appearance on both sides.

Mathematical reasoning

For the mathematically inclined, what is actually going on is that we try to make sure that the gradients of two patches match on the shared boundary. For Bézier patches, this gradient is completely determined by the 12 control points that determine the boundary itself (shared by both patches) and their direct neighbours. These control points can be considered to form three pairs of curves: the boundary positions bp(v) and colours bc(v), the “left” positions and colours (lp(v),lc(v)) and the “right” positions and colours (rp(v),rc(v)). Now suppose that (x,y) is a point on the boundary, so that there is some v such that bp(v)=(x,y). Now, what is the gradient at (x,y) for both of these patches?

It is easiest to examine this question in the parameter space of the patch, and then transforming this result to the image plane. As a function of the parameters u and v, the colours of a patch c(u,v) are defined as a cubic function on a square domain ([0,1]×[0,1]). If we look at the gradient on the right boundary (of the left patch), the colour values are given by bc(v), so the partial derivative with respect to v is given by bc'(v), while the partial derivative with respect to u is given by 3(bc(v)-lc(v)). The gradient (transposed, or “Jacobian row vector”) of the colours c(u,v) at (1,v)is thus ∇c(1,v)T=Jc(1,v)=(3(bc(v)-lc(v)),bc'(v)). For the positions we can similarly find the Jacobian matrix Jp(1,v)=(3(bp(v)-lp(v)),bp'(v)). The gradient of the resulting “mesh gradient” c(p-1(x,y)) at the corresponding point (x,y)=bp(v)=p(1,v) in the image plane can be found to be Jc(1,v) Jp(1,v)-1. A similar result holds for the left border (of the right patch), with Jc(1,v)=(3(rc(v)-bc(v)),bc'(v)) and Jp(1,v)=(3(rp(v)-bp(v)),bp'(v)).

Given the gradients of both patches at their shared boundary, it is clear that the most straightforward way to make them match is to make sure that rc(v)=2bc(v)-lc(v) and rp(v)=2bp(v)-lp(v). The method discussed above does this, but allowing this mirroring to be specified per corner, rather than per side (slightly more flexible).

This entry was posted in Vector graphics. Bookmark the permalink.

2 Responses to Smooth transitions in mesh gradients

  1. In the top two images (the grayscale gradients) I can clearly see a rectangle pattern in both gradients. Is this due to the implementation or image compression/conversion?

    • jaspervdg says:

      This is due to the implementation (regardless of what happens on the boundaries, the patches are smooth in their interior). Basically the implementation just computes the positions and colours on a regular grid (in the parameter space), rounds the resulting positions and “splats” them onto the output. (There are obviously better ways to do this.)

      Edit: I just uploaded new versions of the examples in which the artifacts are pretty much gone (effectively uses a “tent” kernel rather than a box kernel).

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s