Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Drawing on GDI+ From Native C++


Drawing on GDI+ From Native C++

One of the big selling points of .NET is the new graphics library called GDI+. Reading through all of the articles and books written about GDI+, you get the impression that it is a feature only available to .NET developers, but this, in fact, is far from the truth. The GDI+ library is implemented as a native DLL and the classes in .NET are merely a wrapper around this native code. The GDI+ DLL is provided as part of Windows XP, but it is also installed with the .NET redistributable. This native code is provided through exported DLL functions, and the Platform SDK provides a thin wrapper class library that gives a friendlier face to these functions. In this article, I will describe these wrapper classes and explain how you can write rich graphics with native C++.

Getting Started

The GDI+ library is contained in a DLL called "GdiPlus.dll," and it's provided with XP as part of the operating system. Normally, a system library is located in the %systemroot%\System32 folder because LoadLibrary automatically searches this folder for system libraries, and this is the case when you install the .NET framework on Windows 2000. However, this universal dumping ground approach has caused all kinds of problems in the past, particularly when more than one version of a library is available. XP has native support for multiple versions of libraries through side-by-side installation and manifest files. XP calls such shared libraries "assemblies," but make no mistake — these are not .NET assemblies, they do not contain .NET code; instead, they are native code DLLs that are located and loaded using an extended version of the Windows DLL loader. A manifest file is an XML file that contains versioning information about the library, and an application can also have a manifest file to indicate the version of the libraries that it expects.

When a side-by-side shared library is installed on XP, Windows Installer will store the library in a folder in the side-by-side assembly cache: %systemroot%\WinSxS. The subfolder is named according to the version of the library, so different versions of the same library are available on the machine. For example, my XP machine has service pack 1 installed, which included a new version of GdiPlus.dll, so there are two versions of this DLL with the following file versions as seen through the properties dialog in Windows Explorer:

5.1.3097.0 (xpclient.010817-1148)
5.1.3101.0 (xpsp1.020828-1920)

The first file is GDI+ Version 1.0.0.0, the second file is GDI+ Version 1.0.10.0.

When XP loads an application, it examines the manifest file provided with the application to determine the version of GDI+ that the application requires. If the manifest or the version information is not available, XP will load the latest version of the library from the side-by-side assembly cache.

Headers and Libraries

As with all Windows libraries, you can access the GDI+ facilities through exported functions. If you run DUMPBIN /EXPORTS on this library, you will see over 600 functions with names that start with Gdip. The GDI+ library is object oriented and it contains various "classes" for things like fonts, bitmaps, and brushes. However, DLLs are not class based; instead, functions are exported as C functions, so each method of each GDI+ "class" is exported as a standalone C function. Of course, a class method is applied to a particular instance of the class and has access to the instance through a this pointer. This is simulated with the exported functions through the first parameter, which is an opaque pointer to a data structure of the particular object type. The definitions of these functions can be found in the GdiPlusFlat.h header file in the Platform SDK and the data structures are defined in GdiPlusGpStubs.h. The import library is called GdiPlus.lib.

For example, the functions associated with solid brushes are:

GpStatus WINGDIPAPI GdipCreateSolidFill
   (ARGB color, GpSolidFill **brush);
GpStatus WINGDIPAPI GdipSetSolidFillColor
   (GpSolidFill *brush, ARGB color);
GpStatus WINGDIPAPI GdipGetSolidFillColor
   (GpSolidFill *brush, ARGB *color);
GpStatus WINGDIPAPI GdipCloneBrush
   (GpBrush *brush, GpBrush **cloneBrush);
GpStatus WINGDIPAPI GdipDeleteBrush
   (GpBrush *brush);
GpStatus WINGDIPAPI GdipGetBrushType
   (GpBrush *brush, GpBrushType *type);

The first function can be considered the constructor, which will create a brush with a particular alphaRGB color. This function returns an instance of GpSolidFill, which is passed as the first parameter to the other solid fill brush functions. The last three functions in this list can be used with any of the GDI+ brush types and GdipDeleteBrush can be considered the destructor of brushes, and is used to clean up the resources used by the brush. If you look up GpBrush and GpSolidFill in GdiPlusGpStubs.h you'll see these definitions:

class GpBrush {};
class GpSolidFill : public GpBrush {};

These classes have no members, which reinforces the fact that when they are used as parameters to the Gdip functions, they are opaque parameters and do not refer to any data that you can use.

Although you can call these GDI+ exported functions in your application code, the flat API is a pain to use, and you do not have the advantages of object-oriented encapsulation. To address this issue, Microsoft has provided C++ thin wrapper classes that give access to these GDI+ objects through C++ objects. In total, there are 30 header files describing these classes with one master header file, GdiPlus.h, that includes them all. The GdiPlusFlat.h header file is included in GdiPlus.h in a namespace called "DllExports" to isolate the flat API.

A Basic GDI+ Application

GDI+ does not change the basic windowing facilities of Windows, it just gives you a richer set of APIs to draw in an existing window. So, an application that uses GDI+ must create a window (and register a class for the window) and implement a message pump to dispatch messages meant for the window to a custom windows procedure. This is how a Windows application has always been implemented, and there is no change with GDI+. But, the GDI+ library must be initialized before you can use it, and it must be shut down before your process finishes — this is performed by the functions GdiplusStartup and GdiplusShutdown, respectively.

Listing 1 shows the basic code for initializing the GDI+ library; the initialization occurs before the message pump starts. GdiplusStartup is passed a GdiplusStartupObject to indicate the version of the library and to allow you to provide a callback function for debug builds. You can also indicate in this parameter if you would prefer to replace the GDI+ background thread with your own thread, in which case the final parameter is used to return hook and unhook functions to setup and shutdown this thread. Once GDI+ has initialized, you will be returned a token in the first parameter that is passed to GdiplusShutdown after the message pump has completed and all GDI+ objects have been released.

Once you have initialized the library, you can use the GDI+ objects in your paint handlers. The process is straightforward and is considerably helped by the wrapper classes that perform clean up of the wrapped GDI+ object in the destructor so that you can allow variables to go out of scope to clean up resources. The workhorse of GDI+ is the Graphics class, which should be familiar to .NET developers. An instance of this class is created by passing a device context handle to the constructor, or a pointer to an Image object if you want to draw to an offscreen buffer. The Graphics class has all of the methods that you need to draw lines, curves, strings, bitmaps, and shapes.

Solid shapes are drawn using a Brush object, and lines are drawn using Pen objects; pens and solid brushes are constructed from a Color object that gives both the RGB value of the color and an Alpha value that determines the transparency of the color. Listing 2 shows example code to draw a gray string in the center of a window's client area. The DrawString method takes a Font object and a Brush object to indicate how the text should be drawn. I have used the version of DrawString that takes a bounding rectangle as a parameter, so I also have to pass a StringFormat object to indicate how the string will be drawn inside this rectangle: In this case, the string should be centered vertically and horizontally.

GDI+ offers many features that are not part of GDI. I have already mentioned that colors have an Alpha value, which means that you can blend colors by drawing two colors at the same position. Another way to do this is with a gradient brush, where you supply two colors and two points and GDI+ will blend from one color to the other along the line defined by the points. GDI+ also has native support for cardinal splines, and support for multiple image formats such as JPEG, GIF, and TIFF.

Coordinate Systems

GDI+ uses three coordinate systems to rationalize the GDI coordinate system, which are called world, page, and device. The units that you pass to the Graphics methods are world units, which can have any scale and any origin. These units are converted to page units, which always have their origin at the top left-hand corner of the client area, but can be scaled to real-world units like inches or millimeters. Finally, page units are converted to device units, which are pixels. You have control over these units through transforms. Page units are set through Graphics::SetPageUnit(). For example, to use inches you call:

Graphics graphics(hdc);
graphics.SetPageUnit(UnitInch);

If you draw a line straight after these statements, each unit will be an inch and the origin will be the top left-hand corner. Further, the x-coordinate increases from left to right and the y-coordinate increases from top to bottom. Since your windows will only be a few inches wide or high, it means that most of the coordinates that you will use will be fractions of an inch. This is why rectangles, points, and sizes are represented with classes that have floating-point numbers. However, the width of the pen that is used will also be in inches, and the default width will be one unit, which will give you a wide pen! It makes more sense to use smaller units. To do this you can pass a scaling factor to Graphics::SetPageScale(), and to indicate that each unit is 100th of an inch you can call:

graphics.SetPageScale(0.01f);

If you want to shift the origin or change the direction of the axes, you have to apply a world transform, which will convert the world units (in the format you prefer) to page units. World transforms can be applied in one of two ways: through calling the various Transform methods on the Graphics class or by creating a Matrix object. To mirror along the x-axis and to get the y-coordinates to increase from bottom to top, the y-coordinate should be scaled by -1:

graphics.ScaleTransform(1.0f, -1.0f, 			MatrixOrderAppend);

Moving the origin is just as simple: Call the TranslateTransform method with suitable values to move the origin from the top left-hand corner to the new location. However, you have to take into account the other transforms that might be applied. So if we assume that the page transforms outlined earlier will be performed on the coordinates, we need to scale the location of the new origin. Listing 3 shows how to place the origin in the center of the window assuming the transforms shown already. First, this code gets the width of the client area in device units and divides by two to get the center of the window in device units. This is then scaled to world units by dividing by the page scale and by the screen resolution in pixels per inch. The y transform is calculated in a similar way.

Wrap Up

GDI+ brings you great new graphic features: graphics transforms, cardinal splines, alpha blending and gradient brushes, and support for creating and rendering multiple image formats. All of these facilities are available to non.NET code because GDI+ itself is a native code library. The GDI+ C++ wrapper classes offer a simplified way to access the GDI+ functions making unmanaged graphics code simple and straightforward to write. w::d


Richard Grimes is an author and speaker on .NET. His latest book, Programming with Managed Extensions for Microsoft Visual C++ .NET, updated for Visual C++.NET 2003, is available now from Microsoft Press. He can be contacted at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.