14,977,142 members
Articles / Multimedia / GDI+
Article
Posted 24 Jun 2008

140.9K views
46 bookmarked

A class for creating round rectangles in GDI+ with pixel accurate symmetry

Rate me:
This class overcomes the asymmetry issue associated with round rectangles created in GDI+.

Introduction

If you have ever created a round rectangle in GDI+, you may have noticed that it is often not completely symmetric at the pixel level. The asymmetry occurs when one of the following is true: (radius = 10) or (pen width > 1) or (`FillPath` is used). This can be seen in the above image, which has been magnified with a 4x zoom. The asymmetry doesn’t seem to occur for large radii with values greater than about 15. It may be because there are more pixels to work with, or it is just not noticeable.

Background

Since rounded rectangles are not a native shape to GDI+, they are typically created using a `GraphicsPath` or some other mechanism. It doesn’t matter if you use `AddArc`, `AddBezeir`, Polygon points, Transformations etc., the asymmetry is there if any of the previously stated conditions are true. The reason none of these methods produce accurate results is because the problem is not in the definition of the points. All the methods above will accurately define the points. The problem occurs when the shape is rendered (drawn or filled). I won’t speculate on what the underlying cause of this asymmetry is.

CRoundRect

The `CRoundRect` class is implemented entirely in the header file, and provides these basic functions: `GetRoundRectPath()`, `DrawRoundRect()`, and `FillRoundRect()`. Three workarounds were needed to get the symmetric results.

GetRoundRectPath

This function uses the `AddArc` method for defining the rounded rectangle path. The first workaround handles the special case where the radius is 10. It offsets the arc's rectangle and increases its size at a strategic point. I don’t have a good theory for why this works or why it is only needed for a radius of 10.

```void GetRoundRectPath(GraphicsPath *pPath, Rect r, int dia)
{
// diameter can't exceed width or height
if(dia > r.Width)    dia = r.Width;
if(dia > r.Height)    dia = r.Height;

// define a corner
Rect Corner(r.X, r.Y, dia, dia);

// begin path
pPath->Reset();

// top left

// tweak needed for radius of 10 (dia of 20)
if(dia == 20)
{
Corner.Width += 1;
Corner.Height += 1;
r.Width -=1; r.Height -= 1;
}

// top right
Corner.X += (r.Width - dia - 1);

// bottom right
Corner.Y += (r.Height - dia - 1);

// bottom left
Corner.X -= (r.Width - dia - 1);

// end path
pPath->CloseFigure();
}```

DrawRoundRect

This function draws a rounded rectangle using the passed rectangle, radius, pen color, and pen width. The second workaround involves using a pen width of 1 and drawing “width” number of rectangles, decrementing the size of the rect each time. That alone is insufficient, because it will leave holes at the corners. Instead, this deflates only the x, draws the rect, then deflates the y, and draws again.

```void DrawRoundRect(Graphics* pGraphics, Rect r,  Color color, int radius, int width)
{

// set to pixel mode
int oldPageUnit = pGraphics->SetPageUnit(UnitPixel);

// define the pen
Pen pen(color, 1);
pen.SetAlignment(PenAlignmentCenter);

// get the corner path
GraphicsPath path;

// get path
GetRoundRectPath(&path, r, dia);

// draw the round rect
pGraphics->DrawPath(&pen, &path);

// if width > 1
for(int i=1; i<width; i++)
{
// left stroke
r.Inflate(-1, 0);
// get the path
GetRoundRectPath(&path, r, dia);

// draw the round rect
pGraphics->DrawPath(&pen, &path);

// up stroke
r.Inflate(0, -1);

// get the path
GetRoundRectPath(&path, r, dia);

// draw the round rect
pGraphics->DrawPath(&pen, &path);
}

// restore page unit
pGraphics->SetPageUnit((Unit)oldPageUnit);
}```

FillRoundRect

This function fills a rounded rectangle using the passed rectangle, radius, and brush color. The third workaround involves filling the rect, then drawing the border to fix the edges.

```void FillRoundRect(Graphics* pGraphics, Brush* pBrush, Rect r, Color border, int radius)
{

// set to pixel mode
int oldPageUnit = pGraphics->SetPageUnit(UnitPixel);

// define the pen
Pen pen(border, 1);
pen.SetAlignment(PenAlignmentCenter);

// get the corner path
GraphicsPath path;

// get path
GetRoundRectPath(&path, r, dia);

// fill
pGraphics->FillPath(pBrush, &path);

// draw the border last so it will be on top
pGraphics->DrawPath(&pen, &path);

// restore page unit
pGraphics->SetPageUnit((Unit)oldPageUnit);
}```

FillRoundRect – Alternate

There is an alternate version of this function that takes a `Brush` as one of its arguments. This is necessary if you want to fill with something other than a `SolidBrush`. The color argument is needed so the function knows what color to make the border. This function can also be used to do a border and fill in a single call, assuming you wanted a border width of one.

```void FillRoundRect(Graphics* pGraphics, Rect r,  Color color, int radius)
{
SolidBrush sbr(color);
}```

The Demo Program

The demo program is compiled with VC7, but this code should work with any compiler and OS that supports GDI+. The demo program also demonstrates a technique for creating concentric borders that don’t have holes in the corners.

This class does not set any of the modes like `SmoothingModeAntiAlias`. It will use the currently defined state. It will set the `PageUnit` to `UnitPixel`, but it will restore it on completion. It took a lot of work to get to this solution, because I view this as a bit of a hack, and I tried everything else first. Unfortunately, I had to make this work for a very cool class that I’m working on now. Stay tuned …

Share

 Software Developer (Senior) United States
I am currently working as a consultant in Southern California.

I have worked as a Hardware Engineer, Firmware Engineer, Software Engineer and Applications Engineer.

I spent 13 years in the Disk Drive industry and the last 7 working in GPS.

 First Prev Next
 Thank You! Alec Musasa14-Aug-15 17:18 Alec Musasa 14-Aug-15 17:18
 Thank you very much! gabihodoroaga3-Jun-12 2:13 gabihodoroaga 3-Jun-12 2:13
 3 extra points dspnerd18-Mar-12 16:47 dspnerd 18-Mar-12 16:47
 My vote of 5 Manoj Kumar Choubey18-Feb-12 3:27 Manoj Kumar Choubey 18-Feb-12 3:27
 My vote of 5 futurejo17-Nov-11 18:37 futurejo 17-Nov-11 18:37
 My vote of 5 Aaron Balint26-Oct-11 17:02 Aaron Balint 26-Oct-11 17:02
 Flawless BHokie15-Aug-11 11:35 BHokie 15-Aug-11 11:35
 Thank you so much! awhite9226-Jun-11 23:17 awhite92 26-Jun-11 23:17
 tank Don_Hard17-Jun-10 23:43 Don_Hard 17-Jun-10 23:43
 Thank you gianco5-Oct-09 2:30 gianco 5-Oct-09 2:30
 Excellent! Jörgen Sigvardsson27-Feb-09 3:19 Jörgen Sigvardsson 27-Feb-09 3:19
 Why I needed this class Darren Sessions16-Jul-08 7:00 Darren Sessions 16-Jul-08 7:00
 A comment about anti-aliasing Darren Sessions29-Jun-08 7:21 Darren Sessions 29-Jun-08 7:21
 You looks like G.W Bush Satervalley24-Jun-08 17:52 Satervalley 24-Jun-08 17:52
 Re: You looks like G.W Bush mav.northwind24-Jun-08 19:07 mav.northwind 24-Jun-08 19:07
 Re: You looks like G.W Bush Darren Sessions25-Jun-08 4:36 Darren Sessions 25-Jun-08 4:36
 Re: You looks like G.W Bush Satervalley25-Jun-08 19:39 Satervalley 25-Jun-08 19:39
 Re: You looks like G.W Bush kyokof19-Oct-08 14:52 kyokof 19-Oct-08 14:52
 Re: You looks like G.W Bush kanbang22-Jul-09 15:11 kanbang 22-Jul-09 15:11
 Last Visit: 31-Dec-99 18:00     Last Update: 30-Jul-21 0:47 Refresh 1