Samstag, 3. November 2012

Working with GDI+ - Part 1

Hello everyone

If you are using nothing but the plain Win32-API you might have a lot of struggles when you want to perform more complicated drawing actions in your window or control. Loading compressed image formats like PNG or JPG requires a lot of work and your code pretty soon starts to get bloated.For that reason Microsoft added a new feature to Windows with XP which is called GDI+. If you need it on an older platform you can install it using Windows Update back till Windows 2000. Now whats GDI+? Pretty simple: Its a library containing a lot (and by that I mean A LOT) of features that help you working with everything related to windows graphics. As the name suggests it supersedes the traditional GDI.

Well, enough talk now, lets get some code! In our first step we just want to draw a simple shape using GDI+. I have created a simple framework that creates a window and runs the message loop, ill omit these because there is nothing special there, its just the regular code for creating a window and running its message loop. This leads me to the following main function:

#include <gdiplus.h>

#pragma comment(lib, "Gdiplus.lib")

BOOL WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, INT) {
	HWND hWindow = createWindow(L"GDI+", WndProc);
	ULONG_PTR gdiToken = 0;

	startupGDIPlus(&gdiToken);
	
	runWindowLoop(hWindow);

	shutdownGDIPlus(gdiToken);
	return TRUE;
}

As you can see you have to start GDI+ before you are using it and you have to shut it down when you are finished using it. The token acts as an indicator which call to startup shutdown should process. These two functions perform the following tasks:

Gdiplus::Pen* redPen = nullptr;

void startupGDIPlus(ULONG_PTR* pToken) {
	Gdiplus::GdiplusStartupInput startInput;
	
	Gdiplus::Status status = Gdiplus::GdiplusStartup(pToken, &startInput, nullptr);
	if(status != Gdiplus::Ok) {
		// do error handling
	}

	redPen = new Gdiplus::Pen(Gdiplus::Color::IndianRed, 3.0f);
}

void shutdownGDIPlus(ULONG_PTR token) {
	delete redPen;
	Gdiplus::GdiplusShutdown(token);
}

The functions are pretty easy to understand, we call GdiplusStartup to start GDI+ and GdiplusShutdown to shut it down. GdiplusStartup has two additional parameters, a GdiplusStartupInput and a GdiplusStartupOutput. With the input you can control a few settings like which version should be loaded. Just passing the default object is fair enough for most cases. The GdiplusStartupOutput parameter gets populated by the function and then contains two function pointers that have to be called if you specified in the input that the background thread should be suspended. You see: Most likely you will never use that!

You can also use the startup-function to load resources that persist over the entire lifetime of GDI+ and then delete them in shutdown. I did this for the pen I will be using later to draw my shape. It creates a pen with a stroke width of 3 and the color IndianRed.

Drawing is now done at the same place it is for regular GDI drawings - in WM_PAINT. You use the HDC from BeginPaint to and then draw the actions to that context. I created a function drawGDI which takes the HDC and then performs the painting.
void drawGDI(HDC hDc) {
	Gdiplus::Graphics* g = Gdiplus::Graphics::FromHDC(hDc);

	g->DrawArc(redPen, Gdiplus::Rect(10, 10, 100, 50), 0, 270);

	g->Flush();
	delete g;
}
All drawing is done with a Graphics object. It encapsulates all the functionality for painting. There are several ways to obtain a Graphics object. You can use FromHDC to draw to a device context, you can use FromImage to draw to an image (we will use that later) or FromHwnd to draw to the window. We use the FromHDC as we want to paint to the device context returned by BeginPaint. The next step is to actually draw something. I used the DrawArc-function to draw... well... an arc! I use the Pen created by the startup function to draw the arc at the coordinates specified by my Rect and using the angles 0 and 270 as boundaries. At the end I Flush all the draw actions so that they get executed and then delete the object. The result you get should be something like that:
We see or first problem: That line is so blocky! Is there something to change that? Of course there is! Its called anti aliasing. The concept of anti aliasing is pretty simple: Instead of saying "Here we have red, here not" the renderer creates a fade out at the edge of the arc addind several pixels around the edge which are partially transparent and fade into completely transparent. For the eye it still looks like a single line but with way less blocky appearance. We are only one function call away from having a perfectly smooth arc. After we created the graphics object we tell GDI+ to smooth the lines using AntiAlias:
void drawGDI(HDC hDc) {
	Gdiplus::Graphics* g = Gdiplus::Graphics::FromHDC(hDc);
	g->SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);

	g->DrawArc(redPen, Gdiplus::Rect(10, 10, 100, 50), 0, 270);

	g->Flush();
	delete g;
}

And thats it, you should see a nice smoothed line like the one in the picture:

Now you can play with the different functions like DrawLine, DrawRectangle and so on and we will meet for the next part when you are ready to draw some images!

Thanks for reading and see you soon
Yanick

Kommentare: