Click here to Skip to main content
Click here to Skip to main content

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

, 24 Jun 2008
Rate this:
Please Sign up or sign in to vote.
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)

About the Author

Darren Sessions
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

 
QuestionThank you very much! Pinmembergabihodoroaga3-Jun-12 2:13 
Question3 extra points Pinmemberdspnerd18-Mar-12 16:47 
GeneralMy vote of 5 Pinmembermanoj kumar choubey18-Feb-12 3:27 
GeneralMy vote of 5 Pinmemberfuturejo17-Nov-11 18:37 
GeneralMy vote of 5 PinmemberAaron Balint26-Oct-11 17:02 
QuestionFlawless PinmemberMember 323738715-Aug-11 11:35 
GeneralThank you so much! Pinmemberawhite9226-Jun-11 23:17 
Generaltank PinmemberDon_Hard17-Jun-10 23:43 
GeneralThank you Pinmembergianco5-Oct-09 2:30 
GeneralExcellent! PinmemberJörgen Sigvardsson27-Feb-09 3:19 
GeneralWhy I needed this class PinmemberDarren Sessions16-Jul-08 7:00 
GeneralA comment about anti-aliasing PinmemberDarren Sessions29-Jun-08 7:21 
GeneralYou looks like G.W Bush PinmemberSatervalley24-Jun-08 17:52 
RantRe: You looks like G.W Bush Pinmembermav.northwind24-Jun-08 19:07 
GeneralRe: You looks like G.W Bush PinmemberDarren Sessions25-Jun-08 4:36 
GeneralRe: You looks like G.W Bush PinmemberSatervalley25-Jun-08 19:39 
GeneralRe: You looks like G.W Bush Pinmemberkyokof19-Oct-08 14:52 
GeneralRe: You looks like G.W Bush Pinmemberkanbang22-Jul-09 15:11 

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.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 24 Jun 2008
Article Copyright 2008 by Darren Sessions
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid