Coons Patch Mesh Gradients in SVG

A Work in Progress — Last updated 5 January 2012

Other pages:

Motivation

Mesh gradients are important for producing life-like vector art. They are supported by: Adobe Illustrator, Coral Draw, XaraX, PDF, PostScript, Cairo (trunk), Scribus (trunk). Mesh gradients can also be used to simulate other types of gradients such as conical gradients (found in Freehand, Photoshop, Gimp, etc.).

Types of Mesh Gradients

Examples of triangle meshes

Examples of Gourand-shaded triangle meshes. Top: Free-form. Bottom: Lattice-form (black lines mark pseudo-rectangles).

Examples of Coons patch meshes

Examples of Coons patch meshes. From left to right:
Top: Single patch. Four patches forming a sphere. Patches outlined.
Bottom: Single patch simulating a conical gradient. Examples of use.

Example of Coons patch meshes in form of flame.

Another example of Coons patch meshes.

Example of Coons patch meshes in form of spiral.

And one more example of Coons patch meshes.

Notes:

Tensor-product patch meshes are the most generic but they are do not appear to be commonly used.

Coons patch meshes appear to be the most common. They can simulate Gourand-shaded triangle meshes as well as conical gradients.

Typical work flow (e.g. with Illustrator) is to start with a rectangular grid of patches overlaying a photograph of an object to be drawn. The grid is then distorted to best match the object outline and coloring. Finally, colors are sampled at each vertex from the photograph. If necessary, extra rows or columns are inserted by dividing existing rows or columns in two.

One "problem" with Coons patch meshes is that the gradient may be folded on top of itself which can cause problems if the gradient is semi-transparent. The PostScript/PDF spec solves this problem by only painting the part on top. Note that an area outside the region defined by the four Bézier curves can be painted in this case.

Meshes in SVG

Problem can be divided into two parts: Defining a mesh and rendering the mesh.

Defining a Coons Patch Mesh Gradient

Each "patch" requires:

Adjacent patches share two colors and one Bézier path. In PostScript/PDF, each patch can share one edge definition with the immediately preceding defined patch.

Existing SVG gradients:

     <linearGradient x1="100" y1="100" x2="200" y2="200">
      <stop offset="0" stop-color="#000000">
      ...
      <stop offset="1" stop-color="#ff0000">
     </linearGradient>

     <radialGradient cx="100" cy="100" r="100" fx="150" fy="150">
      <stop offset="0" stop-color="#000000">
      ...
      <stop offset="1" stop-color="#ff0000">
     </radialGradient>
   

Mesh gradients can be made to fit into the existing gradient structure with few caveats:

  1. Associate a <stop> to a patch side. A patch then consists of four <stop>s, not all of which need to be defined (see below).
  2. A mesh gradient could be used as a graphical element by itself as in the patch sides define a boundary. However, to fit into the current gradient structure, they must be used to paint another object.

A rectangular grid of patches seems to be the best way to go from looking at use cases. There are two advantages of preserving the rectangular structure in the syntax. First, it would aid content creation as it would be easy to insert new rows and columns (or delete them). Second, it would be possible to compact the data by sharing sides and colors between adjacent patches.

Order of patches in a mesh

An array of patches in a Coons Patches Mesh showing one possible ordering of paths and colors.

Obviously Bézier cubic curves commands are required for paths but all the other path commands would be useful. A lineto or curveto would replace one Bézier curve. One side could be zero length to allow for a triangle mesh or conical gradient.

One possible syntax:

      <meshGradient>
       <!-- First row -->
       <meshRow>
        <meshPatch
            <!-- First patch of first row (four sides, four colors) -->
            d="m 100,100 c 25,-25 75,25 100,0 25,25 25,75 0,100 -25,-25 -75,0 -100,0 25,-25 0,-75 0,-100"
            color="red, blue, green, orange"/>
        <meshPatch
            <!-- Second patch (three sides, two colors) -->
            <!-- Initial point is second point of previous patch, no moveto required -->
            d="c 25,-25 75,25 100,0 25,25 25,75 0,100 -25,-25 -75,0 -100,0"
            color="palegoldenrod, papayawhip"/>
        <meshPatch .../>
            ...
       </meshRow>
       <!-- Second row -->
       <meshRow>
        <meshPatch
            <!-- First patch of second row (three sides, two colors) -->
            <!-- Initial point is third point of first patch in previous row, no moveto required -->
            d="c 25,25 25,75 0,100 -25,-25 -75,0 -100,0 25,-25 0,-75 0,-100"
            color="green, orange"/>
        <meshPatch
            <!-- Second patch of second row (two sides, one color) -->
            <!-- Initial point is second point of second patch in previous row, no moveto required -->
            d="c 25,25 25,75 0,100 -25,-25 -75,0 -100,0"
            color="mediumorchid"/>
            ...
       </meshRow>
      </meshGradient>     
   

Another possible syntax suggested by Tab Atkins:

     <meshGradient x="100" y="100" id="example">
       <meshRow>
	 <meshPatch>
	   <stop offset-path="c 25,-25 75,25 100,0 25,25" stop-color="blue" />
	   <stop offset-path="c 25,75 0,100 -25,-25 -75,0" stop-color="green" />
	   <stop offset-path="c -100,0 25,-25 0,-75 0,-100" stop-color="orange" />
	   <stop offset-path="l 100,100" stop-color="red" />
	 </meshPatch>
	 <meshPatch>
	   <stop offset-path="c 25,-25 75,25 100,0 25,25"
		 stop-color="palegoldenrod" />
	   <stop offset-path="c 25,75 0,100 -25,-25 -75,0 -100,0"
		 stop-color="papayawhip" />
	   <stop offset-path="l -75,0" <!-- no stop-color --> />
	 </meshPatch>
	 ...
       </meshRow>
       ...
     </meshGradient>
   

The stop color defines the color at the end of the offset-path (this makes sense if one looks at the second patch in a row, the color at the start of the first path has already been defined). Only the first patch in the first row defines a color for the last <stop>.

Normally a <meshPatch> has four <stop>s if it is the first patch in the first row; three <stop>s if it is a patch in the first row but not the first patch or if it is the first patch in a following row; and two <stop>s otherwise. It is an error if at least one <stop> is not defined with an offset-path.

After experimenting with rendering mesh gradients using a modified version of Inkscape, I propose the following syntax:


     <meshGradient x="100" y="100" id="example"> <!-- x, y used for initial moveto. -->
       <meshRow> <!-- No attributes, used only to define begin/end of row. -->
	 <meshPatch>
	   <stop path="c  25,-25  75, 25  100,0" stop-color="blue" />
	   <stop path="c  25, 25 -25, 75  0,100" stop-color="green" />
	   <stop path="c -25, 25 -75,-25 -100,0" stop-color="orange" />
	   <stop path="c -25,-25, 25,-75"        stop-color="red" /> <!-- Last point not needed (closed path). -->
	 </meshPatch>
	 <meshPatch>
	   <stop path="c  25,-25  75, 25  100,0" /> <!-- stop-color from previous patch. -->
	   <stop path="c  25, 25 -25, 75  0,100" stop-color="green" />
	   <stop path="c -25, 25 -75,-25"        stop-color="orange" /> <!-- Last point not needed (closed path). -->
           <!-- Last path (left side) taken from right side of previous path (with points reversed). -->
	 </meshPatch>
	 ...
       </meshRow>
       <meshRow> <!-- New row. -->
	 <meshPatch>
           <!-- First path (top side) taken from bottom path of patch above. -->
	   <stop path="c  25, 25 -25, 75  0,100" /> <!-- stop-color from patch above. -->
	   <stop path="c -25, 25 -75,-25 -100,0" stop-color="orange" />
	   <stop path="c -25,-25, 25,-75"        stop-color="red" /> <!-- Last point not needed (closed path). -->
	 </meshPatch>
	 <meshPatch>
           <!-- First path (top side) taken from bottom path of patch above (with points reversed). -->
	   <stop path="c  25, 25 -25, 75  0,100" /> <!-- stop-color from patch above. -->
	   <stop path="c -25, 25 -75,-25"        stop-color="orange" /> <!-- Last point not needed (closed path). -->
           <!-- Last path (left side) taken from right side of previous path (with points reversed). -->
	 </meshPatch>
	 ...
       </meshRow>
       ...
     </meshGradient>
   

The changes from what Tab proposed are:

Following the SVG WG resolution, "stop-path" has been changed to simply "path".

The following set of rules facilitates the use of mesh gradients to simulate other types of gradients easily.

If a <meshPatch> is defined with less than the normal number of <stop>s it will be as if the first missing <stop> has an offset-path that consists of a line to the nominal end-point of the last missing <stop> and the other missing <stop>s have offset-paths that consist of zero-length lines.

If a <stop> is missing an offset-path, it is as if the offset-path consists of a zero-length line unless the <stop> is the nominal last <stop> in a <messPatch> in which case it is as if the offset-path is a line to its nominal end-point.

If a <stop> is missing a stop-color, it is as if the stop-color is the same as the previous <stop>. Note that only the first <meshPatch> in the first row defines a color on the first <stop>.

Following the Boston SVG WG F2F resolutions, only 'c', 'C', 'l', and 'L' are allowed in "path". As patches are automatically completed, no 'z' or 'Z' is needed and the meening of 'z' and 'Z' to close a path is a bit different than what is done for patches.

Also decided at the meeting: that we won't include tensor control points and that we won't allow multiple colors at intersections (just use a zero size width/height patch instead).

Example of a conical gradient

     <meshGradient x="100" y="100" id="Conical">
       <meshRow>
	 <meshPatch>
	   <stop offset-path="l 0, -100" stop-color="blue" />
	   <stop offset-path="a 100,100 0 0,1 70.7,29.3" stop-color="green" />
	   <stop stop-color="green" /> <!-- offset-path="l -70.7,70.7" -->
	   <stop stop-color="blue" /> <!-- offset-path="z" -->
	 </meshPatch>
	 <meshPatch>
	   <stop offset-path="a 100,100 0 0,1 29.1,70.7" stop-color="red" />
           <!-- <stop offset-path="l -100, 0" stop-color="red" /> -->
	   <!-- <stop offset-path="z"/>, effective stop-color: green -->
	 </meshPatch>
	 ...
       </meshRow>
     </meshGradient>
   

Note, arcs gets converted to Beziers for painting gradient.

Order of patches in a conical mesh

An array of patches in a Coons Patches Mesh showing one possible way of creating a conical gradient.

Example of a triangle patch mesh

To come.

Rendering

Divide and conquer seems to be the method of choice. Poppler divides each patch into smaller patches until the color in a patch is sufficiently uniform. It then paints each patch with a uniform color (see below). The Cairo (trunk) implementation uses "Forward-rasterize a Bézier cubic surface using (adaptive) forward differences." References to ACM papers included in source. Cairo rendering seems plenty fast so speed should not be a problem.

Comments

Jasper would like to see a separation of shading and transformations. Defining a Coons Patch mesh gradient is essentially a two step problem. The first is defining a "base image" (gradient) on a unit square and the second is mapping that square to a shape. This is similar to what Inkscape already does for linear and radial gradients: one gradient defines the stops and a second how the first is transformed. This allows reuse of the gradient stops. Both the steps could be generalized.

To generalize the definition of the shading, he proposes a syntax like:

   <gradient n="4" transform="...">
     <stop offset="0" stop-color="#f00" />
     <stop offset="1" stop-color="#0f0" />
     <stop offset="2" stop-color="#00f" />
     <stop offset="3" stop-color="#fff" />
   </gradient>
     

where the "transform" maps the stops to the corners of a patch (n=2 for linear gradient, n=3 for triangle, etc.).

Content Creation

To better understand the content creation process, I've used Inkscape and Cairo to "hand create" a mesh gradient from a photo.

Steps:

  1. Create an outline for the gradient: Keep it simple as a patch can have only one Bezier per side so the more Bezier curves are used, the more patches you'll need to draw. It the object you are drawing is simple, it can be drawn using just the mesh. If it is complicated so that many Bezier curves are needed to follow the edge, it may be easier to use a mesh larger than the object and then clip the mesh.
  2. Split the outline into patches. It is easiest programmatically to use a grid of patches with the same number of patches in each row and in each column.

    A lamp outlined by a path and then
		     divided into four patches.

    Here, I needed four rows as the left and right sides have four path segments each. The bottom and top are single segments (zero-length for the top).

  3. Add more patches as needed by splitting rows and/or columns. You'll need a patch edge anywhere the color changes rapidly.

    The outline of the lamp has been split
		     into a 4 x 16 grid of patches.

    Each row split into sixteen patches. I used Inkscape's interpolate extension to generate the in-between paths.

  4. Adjust the patches to better represent the drawing.
    The patches have been distorted to better
		     match the coloring of the lamp.

    Excess paths were deleted, others adjusted. In practice, I used Inkscape's "Divide" command to divide the outline path into separate paths for each patch (a rather tedious procedure and one that a proper content creation program would do automatically).

  5. Determine the colors of each corner. A drawing program could automatically sample the color under each node. I sampled the color under each vertical group of paths.

I used a PERL script to extract the path data from the Inkscape SVG file and write out C code with Cairo functions. The final result:

A drawing of a lamp using a mesh gradient.

The final drawing as rendered by Cairo trunk. PDF version.

Obviously one could do better but drawing meshes "by hand" with Inkscape is a rather tedious process.

Resources

A Quadrilateral Rendering Primitive

http://vcg.isti.cnr.it/publications/papers/quadrendering.pdf

Experimental Cairo Branch

This branch has been merged (after modification) into Cairo trunk.

git://cgit.freedesktop.org/~ajohnson/cairo

Examples

Mesh is rasterized in cairo-mesh-gradient-rasterizer.c.

Inkscape Mailing List Discussion

http://old.nabble.com/gradient-mesh-td26882354.html

Experimental Inkscape Branch

Requires Cairo trunk

Allows creation and editing Mesh gradients using the suggested SVG syntax. This branch is highly unstable!

bzr source

poppler: Gfx.cc:

Rendering strategy:

Recursively divides each patch into four smaller patches until color variation across patch less than some threshold or maximum number of recursions reached. Then fills patch with solid color.

Handles color spaces.

Gfx::doPatchMeshShFill(GfxPatchMeshShading *shading)

calls for each patch in shading:

Gfx::fillPatch(GfxPatch *patch, int colorComps, int patchColorComps, double refineColorThreshold, int depth, GfxPatchMeshShading *shading)

Recursively calls itself until color difference less than threshold or maximum depth reached. Starting "depth" depends on number of patches in shading.

PostScript Language Reference (3rd Edition)

http://www.adobe.com/products/postscript/pdfs/PLRM.pdf

4.9.3. Shading Patterns: pgs 260-287

PDF documentation similar.