Rectangles, Rounded Rectangles and Ellipses in Windows can only be drawn by GDI as axis-aligned figures.
If one wishes to draw rotated or skewed figures under Windows NT, one can use World Transforms
but they are unfortunately not available under Windows 95/98. For a cross-platform solution, one needs to do
more of the work oneself. Rectangles can be simulated trivially using polygons with four vertices, which can then
be rotated or skewed. However, what is one do for ellipses? There are basically three choices:
- Use a custom function to draw the ellipse.
The mathematics for ellipses are relatively simple and
there are modified Bresenham equations for rotated ellipses in standard texts. However, this means that one
must perform the rasterization oneself, which can get complicated for thick lines. This amount of effort
is probably only worthwhile if one is already drawing to an off-screen surface (e.g. DirectDraw) or a bitmap.
- Drawing ellipses as connected line segments.
The actual lines can be drawn via LineTo(…)
or Polyline(…) GDI calls. One can perform the approximation of the ellipses oneself or take advantage of the
GDI FlattenPath(…) function.
- Use cubic bezier curves to approximate ellipses.
This is the method illustrated here.
Drawing ellipses as connected bezier curves
Using just four bezier curves, each representing 90 degrees of the original axis-aligned ellipse, one can arrive at a fair approximation with
a maximum error of just 0.027%. This is equivalent to a maximum error of less than one pixel for ellipses with radii up to
3700, more than enough accuracy for our purposes.
- Device independence.
One can take advantage of hardware support of curve drawing on newer video cards. On my system,
this is as fast as, if not faster than, GDI calls to Ellipse(…)
Because bezier curves are invariant under rotation, scaling and translation, one only needs to
transform the control points to apply the same transformations to one’s ellipse. More precisely, since each point
on a cubic bezier curve is a barycentric combination of the control points, the relationship of the curve to the control points is
not changed under affine maps.
If one is converting ellipses to line segments or rasterizing oneself, each time
the resolution of one’s surfaces or device context changes (for example when drawing to a printer DC), one must re-rasterize.
This is not necessary with bezier curves. An additional advantage is that the ellipses can be exported via metafiles to
drawing programmes such as CORELDRAW, which can then scale the figures without gross distortions or artefacts.
First start off with a rectangle bounding an axis aligned ellipse (as in a normal GDI call). The thirteen bezier control
points (labelled 0-12 below) defining the four bezier curves making up the ellipse can be calculated easily using an empirically
derived “magical constant”. The following code produces the control points for mapping modes where +ve Y is downwards (e.g. MM_TEXT).
Where the opposite is true, just have a negative y value for the offset variable
as indicated in the comments.
// Create points to simulate ellipse using beziers
void EllipseToBezier(CRect& r, CPoint* cCtlPt)
// MAGICAL CONSTANT to map ellipse to beziers
const double EToBConst = 0.2761423749154;
CSize offset((int)(r.Width() * EToBConst), (int)(r.Height() * EToBConst));
// Use the following line instead for mapping systems where +ve Y is upwards
// CSize offset((int)(r.Width() * EToBConst), -(int)(r.Height() * EToBConst));
CPoint centre((r.left + r.right) / 2, (r.top + r.bottom) / 2);
cCtlPt.x = //------------------------/
cCtlPt.x = // /
cCtlPt.x = // 2___3___4 /
cCtlPt.x = r.left; // 1 5 /
cCtlPt.x = // | | /
cCtlPt.x = // | | /
cCtlPt.x = r.right; // 0,12 6 /
cCtlPt.x = // | | /
cCtlPt.x = centre.x - offset.cx; // | | /
cCtlPt.x = // 11 7 /
cCtlPt.x = centre.x + offset.cx; // 10___9___8 /
cCtlPt.x = // /
cCtlPt.x = centre.x; //------------------------*
cCtlPt.y = r.top;
cCtlPt.y = r.bottom;
cCtlPt.y = centre.y + offset.cy;
cCtlPt.y = centre.y - offset.cy;
cCtlPt.y = centre.y;
Rotation of the Ellipse can be accomplished using code similar to:
// LDPoint is an equivalent type to CPoint but with floating point precision
void Rotate(double radians, const CPoint& c, CPoint* vCtlPt, unsigned Cnt)
double sinAng = sin(radians);
double cosAng = cos(radians);
LDPoint constTerm( c.x - c.x * cosAng - c.y * sinAng,
c.y + c.x * sinAng - c.y * cosAng);
for (int i = Cnt-1; i>=0; --i)
vCtlPt[i] = (LDPoint( vCtlPt[i].x * cosAng + vCtlPt[i].y * sinAng,
-vCtlPt[i].x * sinAng + vCtlPt[i].y * cosAng) + constTerm).GetCPoint();
// Create Ellipse
CRect rect; GetClientRect(&rect);
Rotate(m_Radians, midPoint, ellipsePts, 13);
Of course, four bezier curves together only make up the outline of an ellipse, whether rotated or
not. Thankfully, Win32 Path functions are there for filled ellipses. One only needs to enclose the
PolyBezier(…) call in a Path Bracket. The resulting path can be stroked and filled to one’s
satisfaction. If one is feeling adventurous, further special fills like gradients, custom bitmaps or
fractals etc. can be achieved by first setting the clipping region to the path via SelectClipPath(…).
// or FillPath();
// or StrokeAndFillPath();
// or PathToRegion(dc.m_hDC);
Thick Dashed or Dotted Ellipses Outlines under Win95/8
Win95/8 only supports solid thick lines. However, dashed or dotted ellipse outlines can easily
be simulated with a series of bezier segments.