Global Graphics Objects?

Pages: 12
Well, not exactly "global". I am looking for a way to create a graphics object that is accessible throughout the "WndProc()" function. I am a bit rusty at working with classes. So, please forgive any bonehead mistakes. Here is a code snippet of my guess, which is clearly wrong:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
 {
    static int   cxImg, cyImg;
    HDC          hdc;
    PAINTSTRUCT  ps;

    Gdiplus::Graphics graphic1;
    Gdiplus::Image image1;


    switch (iMsg)
     {
        case WM_CREATE:
         {
            Gdiplus::Graphics graphic1(hdc);
            Gdiplus::Image image1(L"Night BW test.jpg");
            cxImg = image1.GetWidth();
            cyImg = image1.GetHeight();
         }


What is the proper way of doing this? Should I instead, create a bitmap or brush object, with a handle that can be passed from function to function? Do I need to delete/destroy those objects when I'm done? Thanx.
OK, I believe that I found a method to suit my purpose:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
case WM_CREATE:
         {
            HDC  hdcImg;

            hdc = GetDC(hwnd);

            Gdiplus::Graphics graphic1(hdc);
            Gdiplus::Image image1(L"Night BW test.jpg");
            cxImg = image1.GetWidth();
            cyImg = image1.GetHeight();
            hdcImg = graphic1.GetHDC();
            hdcMem = CreateCompatibleDC(hdcImg);
            DeleteDC(hdcImg);

            ReleaseDC(hwnd, hdc);
         }


The handle "hdcMem" is declared to work throughout the WndProc() function. However, I get an error when I try to use this statement in another code block:

 
BitBlt(hdc, xImgOrg, yImgOrg, cxSize, cySize, hdcMem, 0, 0, SRCCOPY);


The error is: "uninitialized local variable 'hdcMem' used"

OK, what am I doing wrong this time?
You instantiated hdc outside the switch statement, you instantiated hdcImg within the WM_CREATE block. Where are you instantiating hdcMem? Nowhere that I can see with what you have posted.
With the modified code, I am declaring hdcMem in the same place that I declare hdc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
 {
    static int   cxClient, cyClient, cxImg, cyImg, iVScrlMax;
    HDC          hdc, hdcMem;
    PAINTSTRUCT  ps;

    
    switch (iMsg)
     {
        case WM_CREATE:
         {
            HDC  hdcImg;

            hdc = GetDC(hwnd);

            Gdiplus::Graphics graphic1(hdc);
            Gdiplus::Image image1(L"Night BW test.jpg");
            cxImg = image1.GetWidth();
            cyImg = image1.GetHeight();
            hdcImg = graphic1.GetHDC();
            hdcMem = CreateCompatibleDC(hdcImg);
            DeleteDC(hdcImg);

            ReleaseDC(hwnd, hdc);
         }


"hdcMem" is assigned a value on line 21

However, later, within the WM_PAINT block, I get an error saying that hdcMem has not been assigned a value.
Declare hdcMem as NULL when you create it.

The brackets in the WM_CREATE case have an unfortunate side effect of making the compiler think hdcMem hasn't been properly initialized. Local block scope issues.

You know and I know a WM_CREATE message is sent before any WM_PAINT messages, guaranteed, the compiler doesn't. So it flags hdcMem as having no assigned value.
Well, declaring "hdcMem" as NULL certainly fooled the compiler. However, I am no getting only a blank image. I can see why. The "image1" object is not being passed to the Graphics class. Apparently, the Graphics class does not inherit from the Image class. Nor, can I find a way to pass a pointer to the "image1" object to the necessary Graphics functions.

The only other alternative seems to be, to declare the Graphics and Image objects more globally, so they can function throughout the WndProc() function. However, all attempts at that have faile miserably. I have run out of ideas.

Anyway, here is the entire WndProc code, in its entirety:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
 {
    static int   cxClient, cyClient, cxImg, cyImg;
    HDC         hdc, hdcMem=NULL;
    PAINTSTRUCT  ps;

    
    switch (iMsg)
     {
        case WM_CREATE:
         {
            HDC  hdcImg;

            hdc = GetDC(hwnd);

            Gdiplus::Graphics graphic1(hdc);
            Gdiplus::Image image1(L"Night BW test.jpg");
            cxImg = image1.GetWidth();
            cyImg = image1.GetHeight();
            hdcImg = graphic1.GetHDC();
            hdcMem = CreateCompatibleDC(hdcImg);
            DeleteDC(hdcImg);

            ReleaseDC(hwnd, hdc);
         }


        case WM_SIZE:
         {
            cxClient = LOWORD(lParam);
            cyClient = HIWORD(lParam);
            
            return 0;
         }


        case WM_PAINT:
         {
            int        cxSize, cySize, xImgOrg, yImgOrg, scaleflag=0;  //toggle scaleflag to 0 or 1
            float      fScale;

            hdc = BeginPaint(hwnd, &ps);

            if (scaleflag) {
                if (cxImg/cyImg > cxClient/cyClient)  fScale = float(cxClient)/float(cxImg);
                else                                  fScale = float(cyClient)/float(cyImg);
                cxSize = int(cxImg*fScale+0.5);
                cySize = int(cyImg*fScale+0.5);
             }
            else { cxSize = cxImg;  cySize = cyImg; }

            xImgOrg = (cxClient-cxSize)/2;
            yImgOrg = (cyClient-cySize)/2;

            BitBlt(hdc, xImgOrg, yImgOrg, cxSize, cySize, hdcMem, 0, 0, SRCCOPY);
            
            EndPaint(hwnd, &ps);
            return 0;
         }

        case WM_DESTROY:
            DeleteDC(hdcMem);
            PostQuitMessage(0);
            return 0;
     }

    return DefWindowProc(hwnd, iMsg, wParam, lParam);
 }
Hello, I'm no expert for GdiPlus but obviously you need initialization that looks like this:

1
2
Gdiplus::Image image1(L"Night BW test.jpg");
Gdiplus::Graphics* graphic1 = Gdiplus::Graphics::FromImage(&image1);



So your procedure may look like this finally: (see comments in 4 places)

NOTE: note tested!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <Windows.h>
#include <gdiplus.h>
#include <cassert>

LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
	static int   cxClient, cyClient, cxImg, cyImg;
	HDC         hdc, hdcMem = NULL;
	PAINTSTRUCT  ps;

	// make image graphics "global" to procedure
	static Gdiplus::Graphics* graphic1 = nullptr;

	switch (iMsg)
	{
	case WM_CREATE:
	{
		HDC  hdcImg;

		hdc = GetDC(hwnd);

		// initialize graphics from image
		Gdiplus::Image image1(L"Night BW test.jpg");
		graphic1 = Gdiplus::Graphics::FromImage(&image1);

                // make sure pointer is valid
                assert(graphic1 != nullptr);

		cxImg = image1.GetWidth();
		cyImg = image1.GetHeight();
		hdcImg = graphic1->GetHDC();
		hdcMem = CreateCompatibleDC(hdcImg);
		DeleteDC(hdcImg);

		ReleaseDC(hwnd, hdc);
	}


	case WM_SIZE:
	{
		cxClient = LOWORD(lParam);
		cyClient = HIWORD(lParam);

		return 0;
	}


	case WM_PAINT:
	{
		int        cxSize, cySize, xImgOrg, yImgOrg, scaleflag = 0;  //toggle scaleflag to 0 or 1
		float      fScale;

		hdc = BeginPaint(hwnd, &ps);

		if (scaleflag)
		{
			if (cxImg / cyImg > cxClient / cyClient)  fScale = float(cxClient) / float(cxImg);
			else                                  fScale = float(cyClient) / float(cyImg);
			cxSize = int(cxImg * fScale + 0.5);
			cySize = int(cyImg * fScale + 0.5);
		}
		else
		{
			cxSize = cxImg;  cySize = cyImg;
		}

		xImgOrg = (cxClient - cxSize) / 2;
		yImgOrg = (cyClient - cySize) / 2;

		BitBlt(hdc, xImgOrg, yImgOrg, cxSize, cySize, hdcMem, 0, 0, SRCCOPY);

		EndPaint(hwnd, &ps);
		return 0;
	}

	case WM_DESTROY:
		DeleteDC(hdcMem);

                // destroy graphics image
                graphic1->ReleaseHDC(hdc);

		PostQuitMessage(0);
		return 0;
	}

	return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
Last edited on
Why are you refusing to use the GDI+ Graphics object to draw? No wonder you have a blank image. BitBlt is for GDI drawing, not GDI+.

"How to draw a line using GDI+" shows a skeletal Win32 Desktop code layout, consider using that as boilerplate code for mucking around with GDI+ for learning purposes:
https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-drawing-a-line-use
Last edited on
Using that sample code from the "Getting Started - Drawing a Line" code I changed ONLY the OnPaint function to this (including a picture file called "grapes.png"):
7
8
9
10
11
12
VOID OnPaint(HDC hdc)
{
   Graphics graphics(hdc);
   Image image(L"Grapes.png");
   graphics.DrawImage(&image, 60, 10);
}

And not wow, I have a picture of grapes being displayed in the window's client area.

GDI+ is not too complicated, it is just a very different way to code than GDI.
Furry Guy, yes, originally, I did use that code approach. All of that code was contained within the WM_PAINT block. It worked perfectly. However, there was one serious flaw with that. Every time that the window was resized (or possibly scrolled) both the graphics and image objects would have to be recreated, and the file would again have to be loaded from disk. It was a very slow and ugly process.

I wanted a more global approach. I wanted to declare the objects at the head of the WdnProc() function, create the objects under WM_CREATE, and finally draw the image under WM_PAINT. It took a lot of searching, guessing, posting questions, and hair-pulling, before I finally learned that I needed to use object pointers, rather than the objects themselves. I finally got that method to compile. But, the result still yields a blank image, even though none of the embedded message boxes show an error.

Here is THAT version of the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
 {
    static int   cxClient, cyClient, cxImg, cyImg;
    HDC         hdc;
    PAINTSTRUCT  ps;
    static Gdiplus::Graphics* graphic1=nullptr;
    static Gdiplus::Image* image1=nullptr;
    
    switch (iMsg)
     {
        case WM_CREATE:
         {
            image1 = Gdiplus::Image::FromFile(L"Night BW test.jpg");
            if(image1==nullptr)  MessageBoxW(0, L"File Load Failed", L"File Status", 0);

            graphic1 = Gdiplus::Graphics::FromImage(image1);
            if(image1==nullptr)  MessageBoxW(0, L"Image Load Failed", L"Graphic Object", 0);
            
            cxImg = image1->GetWidth();
            cyImg = image1->GetHeight();
          }

        case WM_SIZE:
         {
            cxClient = LOWORD(lParam);
            cyClient = HIWORD(lParam);
            return 0;
         }

        case WM_PAINT:
         {
            int        cxSize, cySize, xImgOrg, yImgOrg, eImgSts, scaleflag=1;
            float      fScale;

            hdc = BeginPaint(hwnd, &ps);

            if (scaleflag) {
                if (cxImg/cyImg > cxClient/cyClient)  fScale = float(cxClient)/float(cxImg);
                else                                  fScale = float(cyClient)/float(cyImg);
                cxSize = int(cxImg*fScale+0.5);
                cySize = int(cyImg*fScale+0.5);
             }
            else { cxSize = cxImg;  cySize = cyImg; }

            xImgOrg = (cxClient-cxSize)/2;
            yImgOrg = (cyClient-cySize)/2;

            eImgSts = graphic1->DrawImage(image1, xImgOrg, yImgOrg, cxSize, cySize);
            if(eImgSts)  MessageBoxW(0, L"Draw Image Failed", L"DrawImage", 0);

            EndPaint(hwnd, &ps);
            return 0;
         }

        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
     }

    return DefWindowProc(hwnd, iMsg, wParam, lParam);
 }
Did you initialize GdiPlus somewhere ?
Yes, in the WinMain() function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
    const wchar_t szAppName[] = L"LoadImage1";
    const wchar_t szAppTitle[] = L"Load Image Test 1";
    HWND        hwnd;
    MSG         msg;
    WNDCLASSEX  wndclass;
    ULONG_PTR   gdiplusToken;

    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

...(the rest of WinMain() code)...

   while (GetMessage(&msg, 0, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    Gdiplus::GdiplusShutdown(gdiplusToken);
    return msg.wParam;
}
I'll admit it has been a while since I last really mucked around with GDI+, before it was deprecated by MS in favor of Direct2D. Back when I was still using Win98SE.

The GDI+ class based interfaces seem to be more suited for MFC than the Win32 API. IMO.

I had quite a bunch of "how-to" specially written samples I was creating to revamp a Win32 GDI game engine to GDI+ use. A HD crash wiped out all that work, and I've been too lazy to go back to recreate it.

Well, with that said, good luck and keep on coding. :)
Why do you create the graphics object from the image?
I guess you don't want to draw on the image.
Create the graphics from hdc in your inside WM_PAINT.
It also would help if you post the complete WinMain so that people can run your code.
Why do you create the graphics object from the image?

It looks like an attempt to do double buffering.
https://stackoverflow.com/questions/2473799/gdi-double-buffering-in-c
Thanks Thomas. That worked. Creating the Graphics object as a local object (using hdc) solved the problem.

Thanks Furry Guy. That link also helped me to deal with another annoying problem--image flicker when the window is resized or scrolled. I simply added this case statement:

1
2
case WM_ERASEBKGND:
            return TRUE;


Though not the best solution, it will work for the moment. If the image isn't larger than the client area, areas not painted by the image are transparent, and the image leaves ugly artifacts as the window is resized. Still, it will do until I find the need for a better solution.

One other thing, while we're on the subject of double buffering: I may need to double buffer down the line. I wish to draw, erase, and redraw on the image area, without affecting the underlying image. Any suggestions?
Last edited on
1
2
ase WM_ERASEBKGND:
            return TRUE;


This is wrong, if you handle this message then you also need to draw background.

Exception to this rule is if background brush was specified while registering the window in WNDCLASS structure, in which case you'll never receive this message because system will use your registered brush to erase background automatically.

If class brush is set to NULL during window registration, then your program receives this message, you should draw the background because otherwise you get flickering.
The reason for flickering is obvious, your WM_PAINT handler draws a background which was already painted, in 99% cases different colors so it's visible as you resize the window.

In your WM_PAINT the fErase member of PAINTSTRUCT indicates whether the background was erased or not, so if you just return TRUE from WM_ERASEBKGND like in the example above then you should draw the background in WM_PAINT.
Otherwise if erasure may fail during WM_ERASEBKGND , you should check the fErase and conditionally paint the background in WM_PAINT

but for best results, you should draw background in WM_ERASEBKGND and not in WM_PAINT, in which case WM_PAINT is only used to draw on already "painted" background.

system sends WM_ERASEBKGND first and WM_PAINT then immediately follows, WM_ERASEBKGND purpose is to prepare surface for WM_PAINT.

I hope that help ;)
Last edited on
Thanks Malibor. The use of WM_ERASEBKGND was just a temporary fix---a way to deal with the flickering, while I work on other features. The image will always be larger than the window. So, there really is no background

Now, are you saying that I could use the image in WM_ERASEBKGND, as my background? Would it then be possible to have a second overlapping transparent image in WM_PAINT? Think of the concept of "tracing paper".
Now, are you saying that I could use the image in WM_ERASEBKGND, as my background? Would it then be possible to have a second overlapping transparent image in WM_PAINT?

Absolutely, if your image covers entry client area by design then you should fill the background with the image inside the WM_ERASEBKGND

If your goal is to draw over that image you would do the drawing inside WM_PAINT.
If you don't intend to draw over the image, then don't handle WM_PAINT at all, because your painting job is already done in WM_ERASEBKGND

Btw, why don't you use Direct2D instead of deprecated GDI+?
If you're just learning this stuff, you should really consider new API's, unless you have to work with some existing old code.
Last edited on
Can you (or anyone) recommend a good tutorial for "Direct 2D"? The documentation seems rather sparse.
Last edited on
Pages: 12