Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Simple Graphics Class

0.00/5 (No votes)
12 Dec 2016 2  
A simple graphics class for 2D drawing

Introduction

I am a member of this website since 2012. I have learned a lots of things about programming from this website. But now I think it's time to spend some of my time for people. So, I have made a simple graphics class called CGraphics using GDI32. I know using directly GDI32 API is not so hard. But sometimes, it's boring like when you have to write some common code many times. Using this class, you can draw shapes like line, ellipse, rectangle, fill rectangle, etc. I have only added some basic shapes drawing functions in the class. The class also includes other GDI32 functions like BitBlt, StretchBlt, CreateCompatibleDC, CreateCompatibleBitmap, etc. I have added a simple DrawGradientFill function for drawing gradient fill rectangle both vertical and horizontal style.

Background

Using GDI32 API was always painful for me. I know there are so many libraries for 2D drawing, but I always try to use my own and that is why I've made my own graphics class and now I want to share it with people.

Make It Simple

I've written the graphics class in a very simple way. I didn't try to make a big library so that I can show the basic idea of creating a graphics class using GDI32.

Using the Code

In the common way when you want to draw a simple line from 0,0 pixel to 100,100 pixel with red color, you have to write codes in WM_PAINT section like this:

case WM_PAINT:
{
  hdc = BeginPaint(hWnd, &ps);
  
  // TODO: Add any drawing code here...
  
  HPEN hpen = ::CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
  HPEN holdpen = (HPEN)::SelectObject(hdc, hpen);

  MoveToEx(hdc, 0, 0, NULL);
  LineTo(hdc, 100, 100);

  ::SelectObject(hdc, holdpen);
  ::DeleteObject(hpen);
  
  EndPaint(hWnd, &ps);
}
break;

So every time you have to create pen and after drawing, you have to delete it. Not only that, when you want to draw some fill rectangle, you also have to create brush object and you have to delete it.

I know it's ok for simple and smaller codes like that but not so good for a big project.

But using my graphics class, you have to write only simple code like this:

case WM_PAINT:
{
  hdc = BeginPaint(hWnd, &ps);
  // TODO: Add any drawing code here...
  
  CGraphics g(hWnd);

  g.DrawLine(RGB(255, 0, 0), 0, 0, 100, 100);

  g.Render(hdc, rc.right, rc.bottom);
  
  EndPaint(hWnd, &ps);
}
break;

So it looks simple and easy to write. You don't have to create and delete the pen object.

The DrawLine function will create the pen object and will delete after drawing the line.
You don't even have to worry about the memory management. The class destructor will delete all objects from memory. So there is no chances of memory leak. 

Double Buffer Technique

The class also uses double buffer technique. So there is no chances of window flickering. You just have to add 'return TRUE;' statement in WM_ERASEBKGND message section like the following codes:

case WM_ERASEBKGND:
   return TRUE;

The double buffer codes are defined in gpCreateDoubleBuffer function and the gpDeleteDoubleBuffer function deletes the double buffer objects from memory.

The following codes are the definition of the function gpCreateDoubleBuffer. This function creates all the gdi objects require for using the double-buffer technique: 

void gpCreateDoubleBuffer(HDC hdc, int width, int height)
{
    m_hdc = ::CreateCompatibleDC(hdc);
    m_hbmp = ::CreateCompatibleBitmap(hdc, width, height);
    m_holdbmp = (HBITMAP)::SelectObject(m_hdc, m_hbmp);
}

And this is the gpDeleteDoubleBuffer function's codes which delete the double-buffer objects from memory:

void gpDeleteDoubleBuffer()
{
    if ( m_hdc )
    {
        ::SelectObject(m_hdc, m_holdbmp);
        ::DeleteDC(m_hdc);
        m_hdc = NULL;
    }

    if ( m_hbmp )
    {
        ::DeleteObject(m_hbmp);
        m_hbmp = NULL;
    }
}

Here is the Resize function for resizing the drawing canvas area.

void Resize(HWND hWnd, COLORREF clrClear = RGB(255, 255, 255))
{
    gpDeleteDoubleBuffer();

    HDC hdc = ::GetDC(hWnd);
    RECT rc;
    ::GetClientRect(hWnd, &rc);

    gpCreateDoubleBuffer(hdc, rc.right, rc.bottom);

    DrawFillRectangle(clrClear, rc);

    ::ReleaseDC(hWnd, hdc);
}

Actually, the function deletes the double buffer objects and recreates it with the new size. Using the clrClear parameter, you can specify the color which will be used for clearing the canvas after resizing. You can call this function in WM_SIZE message.

Drawing Functions

The class does not include so many drawing functions but it includes the basic shape drawing functions so that you can easily draw line, rectangle, ellipse. For drawing gradient fill, the graphics class includes a DrawGradientFill function which defines codes like:

void DrawGradientFill
(COLORREF color1, COLORREF color2, int x, int y, int width, int height, bool bVertical)
{
    int Width = width;
    int Height = height;

    int r1 = GetRValue(color1);
    int g1 = GetGValue(color1);
    int b1 = GetBValue(color1);

    int r2 = GetRValue(color2);
    int g2 = GetGValue(color2);
    int b2 = GetBValue(color2);

    COLORREF OldBkColor = GetBkColor(m_hdc);

    if (bVertical)
    {
        for(int i=0; i < Width; ++i)
        {
            int r = r1 + (i * (r2-r1) / Width);
            int g = g1 + (i * (g2-g1) / Width);
            int b = b1 + (i * (b2-b1) / Width);
            SetBkColor(m_hdc, RGB(r, g, b));
            RECT line = {i + x, y, i + 1 + x, y+Height};
            ExtTextOut(m_hdc, 0, 0, ETO_OPAQUE, &line, NULL, 0, 0);
        }
    }
    else
    {
        for(int i=0; i < Height; ++i)
        {
            int r = r1 + (i * (r2-r1) / Height);
            int g = g1 + (i * (g2-g1) / Height);
            int b = b1 + (i * (b2-b1) / Height);
            SetBkColor(m_hdc, RGB(r, g, b));
            RECT line = {x, i + y, x+Width, i + 1 + y};
            ExtTextOut(m_hdc, 0, 0, ETO_OPAQUE, &line, NULL, 0, 0);
        }
    }

    SetBkColor(m_hdc, OldBkColor);
}

Actually, I don't know what is the correct way to draw gradient fill but I think this is the simple way. The graphics class also includes a DrawText function which is simpler than Gdi32 DrawText function.

Here is the public member function declarations of my CGraphics class:

class CGraphics
{
protected:
   // .......
protected:
   // .......
public:
   CGraphics(HDC hdc, int width, int height, COLORREF clrClear = RGB(255, 255, 255));
   CGraphics(HWND hWnd, COLORREF clrClear = RGB(255, 255, 255));
   ~CGraphics();
   void Render(HDC hdc, int width, int height);
   HDC GetHdc();
   HGDIOBJ SelectObject(HGDIOBJ h);
   BOOL DeleteObject(HGDIOBJ h);
   BOOL BitBlt(int x, int y, int width, int height, 
               HDC srcHdc, int srcX, int srcY, int rop = SRCCOPY);
   BOOL StretchBlt(int xDest, int yDest, int wDest, 
                int hDest, HDC srcHdc, int xSrc, int ySrc, int wSrc, int  hSrc, int rop);
   HDC CreateCompatibleDC(HDC hdc);
   HDC CreateCompatibleDC();
   HBITMAP CreateCompatibleBitmap(HDC hdc, int width, int height);
   HBITMAP CreateCompatibleBitmap(int width, int height);
   HFONT CreateFont(char* name, int size, bool bold = false);
   void Resize(HWND hWnd, COLORREF clrClear = RGB(255, 255, 255));
   void DrawLine(COLORREF color, int x1, int y1, int x2, int y2, int width = 1);
   void DrawRectangle(COLORREF clrPen, int x1, int y1, int x2, int y2, int borderWidth = 1);
   void DrawFillRectangle(COLORREF color, int x, int y, int width, int height);
   void DrawFillRectangle(COLORREF color, RECT rc);
   void DrawEllpise(COLORREF clrPen, int x1, int y1, int x2, int y2, int borderWidth = 1);
   void DrawFillEllpise(COLORREF clrPen, COLORREF clrFill, int x1, int y1, int x2, int y2, int borderWidth = 1);
   void DrawText(char* text, COLORREF color, int x, int y, 
                         int format = DT_SINGLELINE | DT_LEFT, HFONT font = NULL);
   /* Simple gradient fill drawing function. I know this not the best algorithm but this is    what
* I every time use.
*/
   void DrawGradientFill(COLORREF color1, COLORREF color2, int x, int y, int width, int height, bool bVertical);
};

Points of Interest

You can extend the class by adding your own drawing functions to it.

Conclusion

Though I have not added so many features in this class, I hope it will be helpful for a beginner who is having trouble with GDI32.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here