Click here to Skip to main content
Licence CPOL
First Posted 24 Jun 2008
Views 30,646
Downloads 441
Bookmarked 32 times

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

By Darren Sessions | 24 Jun 2008
This class overcomes the asymmetry issue associated with round rectangles created in GDI+.
2 votes, 12.5%
1

2

3
1 vote, 6.3%
4
13 votes, 81.3%
5
4.96/5 - 16 votes
2 removed
μ 4.52, σa 2.46 [?]

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)

About the Author

Darren Sessions

Software Developer (Senior)

United States United States

Member
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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 5 Pinmemberfuturejo19:37 17 Nov '11  
GeneralMy vote of 5 PinmemberAaron Balint18:02 26 Oct '11  
QuestionFlawless PinmemberMember 323738712:35 15 Aug '11  
GeneralThank you so much! Pinmemberawhite920:17 27 Jun '11  
Generaltank PinmemberDon_Hard0:43 18 Jun '10  
GeneralThank you Pinmembergianco3:30 5 Oct '09  
GeneralExcellent! PinmemberJörgen Sigvardsson4:19 27 Feb '09  
GeneralWhy I needed this class PinmemberDarren Sessions8:00 16 Jul '08  
GeneralA comment about anti-aliasing PinmemberDarren Sessions8:21 29 Jun '08  
GeneralYou looks like G.W Bush PinmemberSatervalley18:52 24 Jun '08  
RantRe: You looks like G.W Bush Pinmembermav.northwind20:07 24 Jun '08  
GeneralRe: You looks like G.W Bush PinmemberDarren Sessions5:36 25 Jun '08  
GeneralRe: You looks like G.W Bush PinmemberSatervalley20:39 25 Jun '08  
it's surprise that you can speak chinese.
If without beard, I may think that you use bush's photo as your portrait. Smile | :)
GeneralRe: You looks like G.W Bush Pinmemberkyokof15:52 19 Oct '08  
GeneralRe: You looks like G.W Bush Pinmemberkanbang16:11 22 Jul '09  

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

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.5.120210.1 | Last Updated 24 Jun 2008
Article Copyright 2008 by Darren Sessions
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid