Click here to Skip to main content
15,392,636 members
Articles / Multimedia / GDI
Tip/Trick
Posted 22 Oct 2014

Stats

27.2K views
1.5K downloads
23 bookmarked

Simple Graphics Class

Rate me:
Please Sign up or sign in to vote.
4.43/5 (17 votes)
12 Dec 2016CPOL3 min read
A simple graphics class for 2D drawing

Image 1

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:

C++
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:

C++
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:

C++
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: 

C++
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:

C++
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.

C++
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:

C++
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Farhad Reza
Software Developer
Bangladesh Bangladesh
I'm Shah Farhad Reza son of Md Ali Ershad (Rokan). I'm a software developer mainly develop software for PC usable under Microsoft Windows.

The areas of my expertise are the followings:

- OS Kernel developing.
- Programming language's compiler design and implement.
- Expert in C, C++ and Visual Basic and have basic knowledge on C#, D, Java.
- A few times used the Microsoft's new language F#.
- SQL Database programming.
- I've basic knowledge on lowest level programming language like assembly.
- Learning Mozilla’s Rust & Google’s GO programming language for software development.
- Code optimization for performance.
- Multi-threaded programming in C/C++ and Java.
- Know various advanced computer algorithm and have used them to develop graphics and simulation programs. Also working with Linear Algebra and keen to learn Quadratic Algebra in future.
- Graphics and Game programming (Both 2D and 3D).

Currently, I'm doing researches on programming language and its compiler development. I've made various kind of software and now I want to share my experiences with people.

I'm proud of being a Bengali and a Bangladeshi technical article writer. I'm trying to take steps towards working on Computer Programming and also on Scientific subjects for my country. But it is too far from being a success and will require a lot of time due to my family's economic position.

Comments and Discussions

 
QuestionWhy not ... Pin
Richard MacCutchan23-Oct-14 5:31
mveRichard MacCutchan23-Oct-14 5:31 
AnswerRe: Why not ... Pin
Farhad Reza23-Oct-14 5:44
MemberFarhad Reza23-Oct-14 5:44 
GeneralRe: Why not ... Pin
Richard MacCutchan23-Oct-14 6:13
mveRichard MacCutchan23-Oct-14 6:13 
GeneralRe: Why not ... Pin
Farhad Reza23-Oct-14 6:59
MemberFarhad Reza23-Oct-14 6:59 
GeneralRe: Why not ... Pin
degski14-Dec-16 19:35
Memberdegski14-Dec-16 19:35 
AnswerI tell you why! Pin
Thornik15-Dec-16 0:48
MemberThornik15-Dec-16 0:48 
GeneralRe: I tell you why! Pin
Richard MacCutchan15-Dec-16 1:40
mveRichard MacCutchan15-Dec-16 1:40 
GeneralRe: I tell you why! Pin
Thornik15-Dec-16 6:53
MemberThornik15-Dec-16 6:53 
GeneralRe: I tell you why! Pin
Richard MacCutchan15-Dec-16 7:41
mveRichard MacCutchan15-Dec-16 7:41 
GeneralRe: I tell you why! Pin
Thornik16-Dec-16 0:20
MemberThornik16-Dec-16 0:20 
GeneralRe: I tell you why! Pin
Richard MacCutchan16-Dec-16 0:28
mveRichard MacCutchan16-Dec-16 0:28 

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.