Ellipses With Uniform Stroke Width

Cairo has built-in calls for drawing arcs of circles. The following discusses how to use them to draw ellipses.

Common Setup

The following examples will be drawing an ellipse within an image area defined as follows:

const int img_width = 256;
const int img_height = 256;
cairo_surface_t * const pix = cairo_image_surface_create
  (
    /*format =*/ CAIRO_FORMAT_RGB24,
    /*width =*/ img_width,
    /*height =*/ img_height
  );
cairo_t * const cr = cairo_create(pix);

The ellipse will be drawn as a circle centred within the drawing area. Before the circle is drawn, the following scaling is applied:

cairo_translate(cr, img_width / 2.0, img_height / 2.0);
cairo_scale(cr, 0.5, 1);
cairo_translate(cr, - img_width / 2.0, - img_height / 2.0);

This shrinks subsequent drawing operations (including path construction) horizontally by a factor of 0.5 about the image centre, to turn the circle into an ellipse. The ellipse itself is then constructed very simply:

cairo_arc
  (
    /*cr =*/ cr,
    /*xc =*/ img_width / 2.0,
    /*yc =*/ img_height / 2.0,
    /*radius =*/ img_width / 3.0,
    /*angle1 =*/ 0,
    /*angle2 =*/ 2 * M_PI
  );

The following examples use a thick line width (20 units) to make the problem (and its solution) clearer.

The Wrong Way

The obvious way to turn a circle into an ellipse is to apply non-uniform scaling to it. This works, but is prone to an interesting side-effect:

cairo_set_source_rgb(cr, 1, 1, 1);
cairo_paint(cr);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_translate(cr, img_width / 2.0, img_height / 2.0);
cairo_scale(cr, 0.5, 1);
cairo_translate(cr, - img_width / 2.0, - img_height / 2.0);
cairo_new_path(cr);
cairo_arc
  (
    /*cr =*/ cr,
    /*xc =*/ img_width / 2.0,
    /*yc =*/ img_height / 2.0,
    /*radius =*/ img_width / 3.0,
    /*angle1 =*/ 0,
    /*angle2 =*/ 2 * M_PI
  );
cairo_set_line_width(cr, 20.0);
cairo_stroke(cr);

Example output:

The problem is that the CTM also affects the cairo_stroke call. Thus, where the curve is narrow, the stroke gets narrow as well. This is probably not what you want.

The Right Way

The answer is to apply the non-uniform scaling only during the path construction, and remove it before calling cairo_stroke. Here the CTM is saved before path construction, and restored after it:

cairo_set_source_rgb(cr, 1, 1, 1);
cairo_paint(cr);
cairo_set_source_rgb(cr, 0, 0, 0);
  {
    cairo_matrix_t save_matrix;
    cairo_get_matrix(cr, &save_matrix);
    cairo_translate(cr, img_width / 2.0, img_height / 2.0);
    cairo_scale(cr, 0.5, 1);
    cairo_translate(cr, - img_width / 2.0, - img_height / 2.0);
    cairo_new_path(cr);
    cairo_arc
      (
        /*cr =*/ cr,
        /*xc =*/ img_width / 2.0,
        /*yc =*/ img_height / 2.0,
        /*radius =*/ img_width / 3.0,
        /*angle1 =*/ 0,
        /*angle2 =*/ 2 * M_PI
      );
    cairo_set_matrix(cr, &save_matrix);
  }
cairo_set_line_width(cr, 20.0);
cairo_stroke(cr);

Example output:

Much better, don’t you think?

Alternatively you may call cairo_save to save the entire graphics state before the path construction, and cairo_restore afterwards, before the cairo_stroke call. Cairo’s saved graphics state includes the CTM, but not the current path itself.