Click here to Skip to main content
15,867,857 members
Articles / Multimedia / GDI+
Article

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

Rate me:
Please Sign up or sign in to vote.
4.98/5 (24 votes)
24 Jun 2008CPOL3 min read 157.9K   2.7K   48   19
This class overcomes the asymmetry issue associated with round rectangles created in GDI+.

RoundRect.png

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
    pPath->AddArc(Corner, 180, 90);    

    // 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);
    pPath->AddArc(Corner, 270, 90);    
    
    // bottom right
    Corner.Y += (r.Height - dia - 1);
    pPath->AddArc(Corner,   0, 90);    
    
    // bottom left
    Corner.X -= (r.Width - dia - 1);
    pPath->AddArc(Corner,  90, 90);

    // 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)
{
    int dia = 2*radius;

    // 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)
{
    int dia = 2*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);
    FillRoundRect(pGraphics, &sbr, r, color, radius);
}

The Demo Program

Demo.PNG

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.

Additional Comments

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 …

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States 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.

Comments and Discussions

 
GeneralThank You! Pin
Alec Musasa14-Aug-15 17:18
Alec Musasa14-Aug-15 17:18 
QuestionThank you very much! Pin
gabihodoroaga3-Jun-12 2:13
gabihodoroaga3-Jun-12 2:13 
Question3 extra points Pin
dspnerd18-Mar-12 16:47
dspnerd18-Mar-12 16:47 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey18-Feb-12 3:27
professionalManoj Kumar Choubey18-Feb-12 3:27 
GeneralMy vote of 5 Pin
futurejo17-Nov-11 18:37
futurejo17-Nov-11 18:37 
GeneralMy vote of 5 Pin
Aaron Balint26-Oct-11 17:02
Aaron Balint26-Oct-11 17:02 
QuestionFlawless Pin
BHokie15-Aug-11 11:35
BHokie15-Aug-11 11:35 
GeneralThank you so much! Pin
awhite9226-Jun-11 23:17
awhite9226-Jun-11 23:17 
Generaltank Pin
Eduardo_David17-Jun-10 23:43
Eduardo_David17-Jun-10 23:43 
GeneralThank you Pin
gianco5-Oct-09 2:30
gianco5-Oct-09 2:30 
GeneralExcellent! Pin
Jörgen Sigvardsson27-Feb-09 3:19
Jörgen Sigvardsson27-Feb-09 3:19 
GeneralWhy I needed this class Pin
Darren Sessions16-Jul-08 7:00
Darren Sessions16-Jul-08 7:00 
GeneralA comment about anti-aliasing Pin
Darren Sessions29-Jun-08 7:21
Darren Sessions29-Jun-08 7:21 
GeneralYou looks like G.W Bush Pin
Satervalley24-Jun-08 17:52
Satervalley24-Jun-08 17:52 
RantRe: You looks like G.W Bush Pin
mav.northwind24-Jun-08 19:07
mav.northwind24-Jun-08 19:07 
GeneralRe: You looks like G.W Bush Pin
Darren Sessions25-Jun-08 4:36
Darren Sessions25-Jun-08 4:36 
GeneralRe: You looks like G.W Bush Pin
Satervalley25-Jun-08 19:39
Satervalley25-Jun-08 19:39 
GeneralRe: You looks like G.W Bush Pin
kyokof19-Oct-08 14:52
kyokof19-Oct-08 14:52 
GeneralRe: You looks like G.W Bush Pin
kanbang22-Jul-09 15:11
kanbang22-Jul-09 15:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.