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

Drawing in Windows 101

, 26 Jun 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
The basic concepts of drawing in Windows.

Introduction

Drawing in Windows isn't overly complex, but it does require understanding a few concepts.

  1. All drawing is done in a DC (Device Context)!
  2. A DC is basically some area in memory (in RAM or in video RAM) where Windows is prepared to draw. Each DC has attributes, such as what font is selected into the DC, what color pen will be used for drawing, what brush (a small bitmap) will be used to fill in large areas, etc. There are many more attributes than this, such as the clipping area, viewport, mapmode, etc. If you stick with working with just pixels, then a number of attributes need not be concerned with.

  3. Drawing directly into a window DC (video memory through the GDI) can produce some flickering at times, so a common technique is to create a Memory DC, draw into it, and Bitblt the memory DC image into the window DC when needed (during WM_PAINT). This gives your image what is called "persistence". Once the image is drawn, there is no need to redraw it again unless it changes. You simply BitBlt the image during the WM_PAINT message to refresh it when needed.
  4. A memory DC is created using the CreateCompatibleDC function. Then you must create a bitmap to store the image by using the CreateCompatibleBitmap function. You must select the bitmap into the memory DC using the SelectObject function. Make sure you store the return value of SelectObject, since it is the handle to the original bitmap in the DC (a 1 x 1 pixel bitmap). When you destroy the DC (use DeleteDC) (when no longer needed), you must select the old bitmap back into the memory DC and then delete the bitmap you created (use DeleteObject).
  5. To draw into the memory DC, you can use any of the GDI (Graphics Device Interface) API functions. You should define the oen color using the CreatePen function. You should define the brush color using the CreateSolidbrush function. There are a number of drawing functions to draw things like rectangles, circles, lines, etc. (such as the Rectangle, Ellipse, LineTo, and the MoveToEx functions). The pen and brush created must be selected into the DC before drawing with the GDI calls. Remember to store the return value of the SelectObject function, and then select the old pen and brush back into the DC when you are finished.

Key concepts in drawing in Windows

Drawing in Windows requires an understanding of a number of basic concepts. First is what is called a DC.

  1. A DC (Device Context) is basically a defined area for drawing, with its own unique set of attributes. There are different kinds of DCs. One is a window DC, which ultimately means the area drawn on is the video memory of the video card, so the image will be visible to the user. Windows shields you from direct access to the video memory (unless you use DirectX), but the image nonetheless will be drawn on the video memory. The second type of DC is a memory DC. The programmer can create any memory DC they like, when needed. The area drawn on will be a bitmap which has been selected into the DC. A common technique is to create a memory DC, select a bitmap into it, and then draw on it, and once the image is done, then BitBlt (copy) that image from the memory DC to a window DC (so it is visible). Another type of DC is a printer DC, where the area drawn on is the printed page.
  2. To get access to a window DC, one of three ways is used:

    • Process the WM_PAINT message and get the DC using the BeginPaint API function. Now you can draw on it until Endpaint is executed.
    • Use the GetDC API function to get the DC for the client area (inside the border) of any window. You can draw on this DC and the image will appear on the screen immediately. You finish by using the ReleaseDC API function to free the DC.
    • Lastly, you can use the GetWindowDC API function to get a DC for the entire window of any window. This includes the non-client area which is the border of a control. Again, once done drawing, you must use the ReleaseDC function to free the DC.
  3. Every DC has a set of attributes. A newly created DC has those attributes set to the default settings (i.e., a memory DC). A shared DC (window DCs can be shared by many different windows) has the attributes which were defined the last time the DC was accessed. As a rule of thumb, when working with a shared DC (e.g., a window DC), you should always restore the DC's attributes to what they were before you start accessing the DC. This can be done by using the SaveDC and RestoreDC functions.

What are the attributes of a DC?

There are many, but I will list just a few here that are commonly used:

Attribute Description Function to create it Function to delete it Function to set it
Pen Line color, width and style for drawing CreatePen DeleteObject SelectObject
Brush 8 x 8 pixel pattern for filling shapes CreateSolidBrush, CreateHatchBrush, CreatePatternBrush DeleteObject SelectObject
Font Font used for drawing text CreateFont DeleteObject SelectObject
Text_FG_Color Color of text SetTextColor
Text_BG_Color BG color behind drawn text SetBKColor
BG fill mode Mode of how to fill background SetBKMode (solid [filled] or transparent)
Draw Mode How a drawn object is mixed with an existing background image SetROP2
. . . . .
Drawing dunctions (some affected by the above attributes):
Function Description
MoveToEx Move pen position to X,Y location
SetPixel Draw pixel by color and return previous color
SetPixelV Draw pixel by color
Ellipse Draw an ellipse
Rectangle Draw a rectangle
LineTo Draw a line from pen position to new position
Arc Draw an arc
PolyLineTo Draw multiple lines by values in array
RoundRect Draw a rounded rectangle
Polygon Draw a polygon
Pie Draw a pie shaped object
BitBlt Copy image from one DC to another (bitmap)
StretchBlt Copy and stretch image from one DC to another (bitmap)

One of the best methods of drawing is to use a memory DC to draw on, and when you need to display it on the screen, simply use the BitBlt function to copy it from the memory DC to the window DC (during WM_PAINT).

Here is how to create a memory DC:

hDC& = CreateCompatibleDC(%NULL)  ' make DC compatible with screen
' newly created DC has a default 1 x 1 pixel monchrome bitmap selected in it
W& = 200 ' pixels
H& = 200 ' pixels
' if you want to make bitmap the size of a windows client area
' use the GetClientRect function to get width and height
hBmp& = CreateCompatibleBitmap(hDC&, W&, H&)
OriginalBitmap& = SelectObject(hDC&, hBmp&)
SaveDC hDC&

'
' now you can draw on this DC using any drawing command
' you can also BitBlt this image into a window DC during WM_PAINT
'
'
' when you are finished with the memory DC and no longer need it
' you must do the following:
SelectObject hDC&, OriginalBitmap&
DeleteObject hBmp&
RestoreDC hDC&, -1
' delete any still existing objects created like pens or brushes
DeleteDC hDC&

How to draw

Let's do some real drawing!

Windows has a lot of overhead when drawing. It's not like the old days (remember the Commodore 64) when you could literally peek and poke video RAM. Especially when you use API functions like SetPixel can you see how slow it can be to draw in Windows. The larger an area an API command draws on, the faster the drawing is per pixel. As a test, use the Rectangle API function to draw a large filled rectangle and then try drawing the same filled rectangle a pixel at a time using the SetPixelV (faster than SetPixel) API function. The speed difference will be dramatic. Both techniques accomplish the same thing, but drawing with SetPixel demonstrates the huge amount of overhead Windows has in drawing.

The next part of the equation is the difference between drawing into RAM and drawing directly into video RAM. When you draw directly into a Windows DC (Device Context), you are drawing into video RAM. Video RAM drawing is terribly slow compared to drawing into regular RAM.

What slows Windows down is the Device Context (DC) arrangement. Using a DC has its purpose though. It allows Windows to draw into a variety of devices using the same GDI functions, such as video RAM, regular RAM, a printer, or any other device that could be drawn into. The use of a DC is very powerful, but the drawback is that there is a lot of extra overhead to keep track of all the stuff associated with a DC. The need to select objects into and out of a DC adds a significant amount of overhead. DCs are needed for Windows to do what it does. It just slows things down.

Now using the above information, we can develop better approaches to drawing in Windows for faster display rates. Here are a few techniques of how to speed things up.

  1. Drawing into RAM is significantly faster than drawing into video RAM (a Windows DC)!
  2. This is where buffering comes in. By creating a memory DC and selecting a bitmap into it, you can now draw directly into RAM. When you must use the slower, lower level GDI functions (like SetPixel) to draw with, then you should always draw into a memory DC rather than a window DC (video RAM). This will significantly speed up drawing. Now remember, when you draw into a memory DC, you can't see the results. You will have to somehow move the image from RAM (memory DC) to video RAM (window DC). This brings us to point #2.

  3. Use only high level, large area, GDI (API) functions when drawing into a Windows DC!
  4. When drawing into the actual video RAM (window DC during WM_PAINT), the more complex GDI functions which cover a larger area should be used. For example, PatBlt is commonly used for filling the background of a Windows DC. These types of GDI functions (which cover a large area) are more highly optimized than the lower level ones. One of the more commonly used GDI functions used when using buffers (memory DC) is BitBlt. BitBlt is highly optimized, and it can move large amounts of data (pixels) back and forth between DCs. BitBlt is commonly used to move data (pixels) from a memory DC where an image has been drawn to a window DC (which is the video RAM). Another GDI function that can be very useful is StretchBlt, which can scale an image into any size window DC.

By using these very fast and highly optimized GDI (API) functions, you can significantly speed up drawing into a window DC.

Note: When drawing into a printer DC, things are a little different since many printer drivers don't support BitBlt. In this case, DIBs (Device Independent Bitmaps) need to be used and GDI functions like StretchDiBits should be used.

Putting the above two principles to use is the basis to using a memory DC as a buffer. Using a memory buffer is also useful in adding persistence to a window DC. Rather than have to redraw an image from scratch into a window DC, every time the WM_PAINT message is processed, a single image can be copied (using BitBlt) from memory into the window DC. Consider this technique as draw once, BitBlt many.

There are other techniques other than this for drawing that can add speed, but they are a bit more advanced. One such is using DIBs. Simply put, a DIB is a memory image similar to a bitmap, but the difference is that it is device independent and you can actually choose the format of how the data is stored. For example, if the video display is 16 bit color, you could copy the data into a DIB which is stored as 32 bit color. The other difference, which is significant, is that with a DIB, you have direct access to each pixel in byte form. The GDI is not necessary for accessing the pixels. It's kind of like the old days where you can peek and poke directly into the video RAM. In this case, there are a few extra steps. You create a memory DC and memory bitmap which is compatible with the video mode (i.e., 256 color, 16 bit, etc.). You then move the data from the memory bitmap (and DC) into a DIB section (simply a block of RAM allocated to hold a bunch of bytes). Now you can peek and poke all you want directly into the DIB section data. You can write your own custom drawing functions which work directly on RAM data. Once you finish drawing, you now move the data in the DIB section back into the memory DC bitmap. Now you can BitBlt the memory DC into video ram. I think it is also possible to skip the memory DC step when using DIBs and to move data back and forth between a window DC and a DIB section, but I haven't tried it yet and I can't verify how it works.

Just to add to my graphics 101

I wasn't trying to get into the technical aspects of how Windows draws things. Of course, more is involved than what I mentioned. The video driver and the GDI are between your code and the direct video RAM, and you can't access it directly.

My point though is when you draw into a window DC, for all practical purposes, you are indirectly drawing into the video RAM (that's where what you draw is stored). The problem with what you draw in a window DC is that it doesn't have persistence. The memory where the window DC's pixels are stored (video RAM or whatever) is shared by all windows. The DC itself may be shared (it stores the current objects like pen, brush) or not, but the area where the pixels are stored is shared by all windows. Any other window can write over your window's pixel data in video RAM. Because of its lack of persistence because of sharing pixel space with any window that may draw in it, a window DC's pixel data should always be considered temporary.

A memory DC (with an associated bitmap, which is where the pixels are actually stored) can be isolated from access by other windows. You can create your own memory DC and memory bitmap to draw in and no one else can bother it. This produces persistence.

Memory DCs (with a memory bitmap selected into it) have two advantages. One is speed of drawing. Drawing into a memory DC is always faster than drawing into a window DC (for whatever reasons). Second, a memory DC (with its bitmap) has persistence, whereas a window DC does not.

To prove this concept of persistence, write a program that only processes the WM_PAINT message once (the first time called) and see what happens when you move over it with another window.

A lesson in persistance!

Note: The code below uses the PowerBASIC DDT syntax which simplifies creating dialogs.

Given below is a program that demonstrates what happens with a window DC not being painted all the time. It demonstrates the lack of persistence when you draw into a window DC. If the window is prevented from processing the WM_PAINT (or the WM_ERASEBKGND) message, the pixels drawn in the window DC are not remembered.

#COMPILE EXE
#REGISTER NONE
#DIM ALL          '  This is helpful to prevent errors in coding

#INCLUDE "win32api.inc"   ' Must come first before other include files !

DECLARE SUB LIB_InitColors()
DECLARE SUB LIB_DeleteBrushes()
DECLARE SUB ShowDialog_Form1(BYVAL hParent&)
DECLARE CALLBACK FUNCTION Form1_DLGPROC
DECLARE SUB ShowDialog_Form2(BYVAL hParent&)
DECLARE CALLBACK FUNCTION Form2_DLGPROC
DECLARE CALLBACK FUNCTION CBF_FORM2_BUTTON1()

%FORM2_BUTTON1            = 100

GLOBAL App_Brush&()
GLOBAL App_Color&()
GLOBAL App_Font&()
GLOBAL hForm1&    ' Dialog handle
GLOBAL hForm2&    ' Dialog handle
GLOBAL PaintFlag&

' *************************************************************
'                    Application Entrance
' *************************************************************

FUNCTION PBMAIN
   LOCAL Count&
   LIB_InitColors
   PaintFlag&=1
   ShowDialog_Form1 0
   ShowDialog_Form2 hForm1&
   DO
       DIALOG DOEVENTS TO Count&
   LOOP UNTIL Count&=0
   LIB_DeleteBrushes
END FUNCTION

SUB ShowDialog_Form1(BYVAL hParent&)
   LOCAL Style&, ExStyle&
   Style& = %WS_POPUP OR %DS_MODALFRAME OR %WS_CAPTION OR %WS_MINIMIZEBOX
OR %WS_SYSMENU OR %DS_CENTER
   ExStyle& = 0
   DIALOG NEW hParent&, "WM_PAINT limited window", 0, 0,  267,  177,
Style&, ExStyle& TO hForm1&
   DIALOG SHOW MODELESS hForm1& , CALL Form1_DLGPROC
END SUB

CALLBACK FUNCTION Form1_DLGPROC
   SELECT CASE CBMSG
       CASE %WM_PAINT
           ' Windows calls WM_ERASEBKGND to fill background
           ' so I process that message
       CASE %WM_ERASEBKGND
           IF PaintFlag&=0 THEN
               FUNCTION=1
               EXIT FUNCTION
           END IF
       ' -----------------------------------------------
       CASE %WM_CTLCOLORDLG
           IF CBLPARAM=CBHNDL THEN
               ' Dialogs colors
               SetTextColor CBWPARAM, App_Color&(0)
               SetBkColor   CBWPARAM, App_Color&( 17)
               FUNCTION=App_Brush&( 17)
           END IF
       CASE ELSE
   END SELECT
END FUNCTION

SUB ShowDialog_Form2(BYVAL hParent&)
   LOCAL Style&, ExStyle&
   Style& = %WS_POPUP OR %DS_MODALFRAME OR %WS_CAPTION OR %WS_MINIMIZEBOX
OR %WS_SYSMENU OR %DS_CENTER
   ExStyle& = 0
   DIALOG NEW hParent&, "Click Button to Toggle painting of other window",
0, 0,  245,  59, Style&, ExStyle& TO hForm2&
   CONTROL ADD "Button", hForm2&,  %FORM2_BUTTON1,  "Toggle WM_PAINT for
other Window", 37, 17, 176, 15, _
       %WS_CHILD OR %WS_VISIBLE OR %BS_PUSHBUTTON OR %WS_TABSTOP CALL
CBF_FORM2_BUTTON1
   DIALOG SHOW MODELESS hForm2& , CALL Form2_DLGPROC
END SUB

CALLBACK FUNCTION Form2_DLGPROC
   SELECT CASE CBMSG
       ' -----------------------------------------------
       CASE %WM_CTLCOLORDLG
           IF CBLPARAM=CBHNDL THEN
               ' Dialogs colors
               SetTextColor CBWPARAM, App_Color&(0)
               SetBkColor   CBWPARAM, App_Color&( 10)
               FUNCTION=App_Brush&( 10)
           END IF
       CASE ELSE
   END SELECT
END FUNCTION

' *******************************************************************
' *                         Library Code   *
' ********************************************************************

SUB LIB_InitColors()
   DATA         0,  8388608,    32768,  8421376,      196,  8388736,
16512, 12895428
   DATA   8421504, 16711680,    65280, 16776960,      255, 16711935,
65535, 16777215
   DATA  10790052, 16752768, 10551200, 16777120, 10526975, 16752895,
10551295, 13948116
   DATA  11842740, 16768188, 14483420, 16777180, 14474495, 16768255,
14483455, 15000804
   LOCAL T&, RGBVal&
   REDIM App_Brush&(0 TO 31)
   REDIM App_Color&(0 TO 31)
   FOR T&=0 TO 31
       RGBVal&=VAL(READ$(T&+1))
       App_Brush&(T&)=CreateSolidBrush(RGBVal&)
       App_Color&(T&)=RGBVal&
   NEXT T&
END SUB

' -------------------------------------------------------------

SUB LIB_DeleteBrushes()
   LOCAL T&
   FOR T&=0 TO 31
       DeleteObject App_Brush&(T&)
   NEXT T&
END SUB

' -------------------------------------------------------------

CALLBACK FUNCTION CBF_FORM2_BUTTON1
   IF CBCTLMSG=%BN_CLICKED THEN
       IF PaintFlag&=0 THEN
           PaintFlag&=1
       ELSE
           PaintFlag&=0
       END IF
   END IF
END FUNCTION

License

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

Share

About the Author

Chris Boss
Software Developer Computer Workshop
United States United States
Chris Boss is the owner (and programmer) of a small software development business in rural Virginia, called the Computer Workshop. For the last ten years or so he has been developing tools for use by Powerbasic programmers (see: http://powerbasic.com ). His main product called EZGUI (Easy GUI) is a high level GUI engine with Visual Designer and code generator. It is in its fifth generation now. He is an experienced Windows API programmer (more low level) and has experience in writing GUI engines (forms/controls), drag and drop Visual Designers, Graphics engines (printing and to the screen) and one of his favorites is a Sprite engine (2D animated movable images). His current project is version 5.0 of his main product EZGUI, adding such features as multi-monitor support, component engine, custom control engine, superclass engine and the latest project a 3D OpenGL based custom control. One of the goals he has is to push the limits of Windows software development, while making it easy, fast execution speed, small footprint (size of executables) and code reusability while providing a more graphic experience in user interfaces, while still being able to write software which can fit on a floppy disk (small footprint), use minimal amount of memory and able to run on multiple versions of Windows from 95 to Win8.

Comments and Discussions

 
QuestionMy vote of 5 Pinmembernorrislees21-Sep-14 21:23 
GeneralMy vote of 5 Pinmemberzsigmond9-Jan-13 16:32 
Generalwhat Windows trying to hide Pinmembercolexiahan22-May-12 7:17 
GeneralMy vote of 5 Pinmembernv32-Dec-11 2:06 
GeneralMy vote of 5 Pinmemberdb_cooper195015-Jul-10 10:05 

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
Web02 | 2.8.141015.1 | Last Updated 26 Jun 2010
Article Copyright 2010 by Chris Boss
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid