Article

# GDI+ Brushes and Matrices

, 30 May 2001
 Rate this:
Using GDI+ to draw solid/gradient filled and textured shapes

<!-- Main HTML starts here -->

## Introduction

Welcome to my second article on GDI+. This tutorial assumes you know everything covered in the first article, namely that you know where to get GDI+, how to set it up, how to create a project that correctly initialises and shuts it down, and how the `Color` class works.

OK, you're still with me. This article will cover three of the five brush types ( `SolidBrush, LinearGradientBrush` and `TextureBrush`), with `PathGradientBrush` fitting more into the next tutorial, on using Pens and Path's, and `HatchBrush` being left out mainly because I didn't find it interesting, and certainly it is pretty self explanatory once you've gone through this tutorial.

Before I present any brushes, I want to present three methods that we will be using on the canvas in the first example, and on the brush for the other two.

```Status RotateTransform(
REAL angle,
MatrixOrder order
)

Status ScaleTransform(
REAL sx,
REAL sy,
MatrixOrder order
)

Status TranslateTransform(
REAL dx,
REAL dy,
MatrixOrder order
)
```

First, let me explain that a `REAL` is a typedef for a `float`. The `MatrixOrder` is a little more interesting, it is a constant with the possible values `MatrixOrderPrepend` ( the default) or `MatrixOrderAppend`.

Anyone familiar with 3D graphics will recognise these methods - Rotate obviously rotates an object, Scale allows us to resize an object, and Translate moves an object. Actually it would be more correct to say that our point of reference is modified, and this is reflected in the drawing operations which follow. The reason for the `MatrixOrder` would also be obvious to anyone who has done 3D work. Imagine I am going to do a Rotate and a Translate. If I rotate first, what my object considers to be up is changed, and so the direction of my Translate is going to be affected. If I rotate by 45 degrees and apply a Translate of (100, 0), I will move 100 units on the diagonal, but if I reverse the order I will move hard right and then rotate. The example program allows you to experiment with the order of operations and see how the end result differs. In 'real life' you can define two operations of each type, a prepend and an append. It is also possible to create a matrix, perform operations on it, and then pass it in to an object.

On to our first brush then. It's the `SolidBrush`, which has an empty constructor, and one that takes a COLOR as it's parameter. This brush is roughly the equivelant of a CBrush - it's only methods are `GetColor` amd `SetColor`. To draw a solid red rectangle, for example, you can do this

```SolidBrush brush(Color(255,255,0,0));
graphics.FillRectangle(&brush, 300, 300, 100, 50);
// Note the last two parameters are Width and Height, NOT X2/Y2
```
As this class uses solid colours, it does not benefit from any sort of matrix operation, so when you are drawing using the solid brush, these operations are performed on the canvas in the demo program. The methods used are in the code as follows:
```graphics.ScaleTransform(m_ScaleX, m_ScaleY, m_AScale ?
MatrixOrderPrepend : MatrixOrderAppend);

graphics.TranslateTransform(m_TransX, m_TransY, m_ATrans ?
MatrixOrderPrepend : MatrixOrderAppend);

graphics.RotateTransform(m_Rotate, m_ARotate ?
MatrixOrderPrepend : MatrixOrderAppend);
```
Note that these operations can be performed on the canvas equally if you use another brush type in your own code.

The next class is far more interesting: `LinearGradientBrush`. It has a hefty seven constructors available, which allow us to preset a number of options, all of which are available later through `Set` methods. All of these methods conspire to do the same thing: set up a brush which has two colours know to it, and in some cases a gradient mode. The gradient mode can be set to one of the following:

```LinearGradientModeVertical
```

There is also a constructor which takes an angle for the gradient, which is IMHO far more flexible. This is the constructor we use in the demo program, like this:

```LinearGradientBrush brush(rc,            // Rect of gradient
Color(m_Alpha1, m_Red1, m_Green1, m_Blue1), // First colour
Color(m_Alpha2, m_Red2, m_Green2, m_Blue2), // Second colour
TRUE);                           // Is angle Scalable
```

As you can see, there are two variables not yet explained. The first is a rect which defines the size of a gradient. By making this different to the Rect of the object you are drawing, you can make a gradient repeat itself. The same effect can be had by modifying the scale variable with a number between 0 and 1. The BOOL at the end has the name `isAngleScalable`.

Another varibale you can enter into the example program is Blend Focus, a value between 0 and 1. This value specifies at what point in the fill the gradient has reached a point of 50% gradient. This is accomplished with the following code:

```REAL blend1[3] = {0, .5, 1.0};
REAL blend2[3] = {0, m_Focus, 1.0};
brush.SetBlend(blend1, blend2, 3);
```

This is just the tip of the cool stuff you can do with this function. Three is the minimum number of values you can specify, with the first array specifying the percentages at different points, the second the points in terms of percentage along. You can see that this is a powerful and flexible function which opens all sorts of doors.

Additionally you can set the blend type from the menu to be normal, bell or triangle. In all cases the focus variable is used, as follows:

```case bell:
brush.SetBlendBellShape(m_Focus);
break;
case triangle:
brush.SetBlendTriangularShape(m_Focus);
break;
```

If you play with the matrix operations in this part of the example program, you'll find you can do all sorts of interesting things. For example, setting a scale less than one allows you to create a repeating gradient fill. These operations are performed on the brush as follows:

```brush.TranslateTransform(m_TransX, m_TransY, m_ATrans  ?
MatrixOrderPrepend : MatrixOrderAppend);
brush.ScaleTransform(m_ScaleX, m_ScaleY, m_AScale ?
MatrixOrderPrepend : MatrixOrderAppend);
```

Note it is possible to rotate a blend, but this does not create the same effect as specifying a rotation in the constructor, and I have left it out, as applying the same value in two spots does not give a good representation of what it does.

The example program also draws a black line pattern in the background, allowing you to see the effect of a gradient in the alpha channel. `LinearGradientBrush` has many capabilities I have not covered, such as setting gamma correction, interpolation colours, &tc.

The final, and most powerful, brush is `TextureBrush`. In order to cover it, I must first explain another GDI+ class - `Image`. This class can be constructed from an `IStream`, which is a COM interface, or a filepath. There is another class, called `Bitmap`, derived from Image, which can be constructed from a HBITMAP, a surface (DirectDraw), an icon, a pointer to bit data, and more. In the example program I provide a picture of my daughter which is hard coded as the bitmap. This class will load a number of image formats, including jpeg, tiff, bmp, png, &tc. It also supports setting up of other codecs, for example I will end up putting one together for Targa, which we use a lot at work. What is cool about `Image` is that it can also save in these formats. If you want to load an `Image` from a user specified file, you'll need to convert a string to a WCHAR string, which is done as follows:

```// Assuming the string 's' contains our filepath
WCHAR*  filename = new WCHAR[s.GetLength()+1];
mbstowcs(filename, s, s.GetLength()+1);

Image image(filename);
delete filename;
```

Once we have an `Image` object initialised, we can create our texture brush as follows:

```Image image(L"image.jpg");

Rect rct(0, 0, image.GetWidth(), image.GetHeight());

TextureBrush brush(&#8465;,rct);
```

Note this works similar to the gradient - if we specify a `Rect` the same as we are drawing, the image will not scale to fit, but the tiling will not work. In order to get an image to scale to a rectangle, we need to use `Translate` and `Scale`.

As well as scaling, rotating and translating our image, we can set the wrap mode, which is available to you through the menu. The possible values are:

```WrapModeClamp       (Draws the image at 0,0 at correct size, once only)
WrapModeTile        (Draws the image over and over in all directions)
WrapModeTileFlipX   (As above, but every second image is flipped in the X plane)
WrapModeTileFlipY   (As WrapModeTile, but every second image flipped in the Y plane)
WrapModeTileFlipXY  (As WrapModeTile, flipping both in the X and Y plane
every second tile)
```

You'll also find that rotation, scaling and translation present a number of possibilities in combination with the above.

The example program provided allows you to experiment with different colour/alpha values as well as different values for all three matrix operations. Hopefully playing with this will give you a good idea as to how all these variables fit together to produce a final result, especially what happens when you change a matrix order of operation.

There are two other sets of options provided, one in the menu, the other in the dialog. Firstly, in the menu you can choose the smoothing mode to be one of the following:

```graphics.SetSmoothingMode(SmoothingModeHighSpeed);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
graphics.SetSmoothingMode(SmoothingModeHighQuality);
```

If you are unfamiliar with the concept of antialiasing, imagine opening Paint and draw a diagonal line that is less than 45 degrees in pitch. You'd expect to see a jagged line, with sharp jumps, made more so by lower screen resolution or zooming. This is caused by an obvious fact: our screens have defined resolutions and this combined with our monitor size sets a pixel size, the minimum size of a 'dot' on the screen. Anti-aliasing attempts to do better by dealing in imaginary 'sub pixels', and setting pixel values to be a fraction of the R/G/B value we are using, depending on how much many 'sub pixels' are in the pixel we are about to draw. It makes no difference on a square, so choose 'ellipse' from the shape menu, and then you will see the difference between the two modes. The last mode is high quality, which according to the help files is an advanced form of anti-aliasing especially designed to take advantage of the capabilities of LCD screens. I don't have an LCD screen, so they look the same to me ;0)

Finally, there is a group of options on the bottom, which allow you to experiment with the DrawImage function. There are 16 versions of this function, the one we are using has this prototype:

```Status DrawImage(
Image* image,
const PointF* destPointsF,
INT count,
REAL srcxR,
REAL srcyR,
REAL srcwidthR,
REAL srcheightR,
Unit srcUnit,
ImageAttributes* imageAttributes,
DrawImageAbort callback,
VOID* callbackData)
```

The ability to specify a callback makes this a very powerful function, but we will limit ourselves to just two of the options available. First of all, if we pass in three points, they specify the top left, top right and bottom *left* ( NOT right, as you might think ) corners of a parellelogram. To see this in action, just check 'Parallelogram'.

You'll note that you are now looking at a different image: it has magenta all around it. If you check Use Colour Key, the magenta is not drawn, and we see the dialog underneath. This is done by setting the transparent colour in the ImageAttributes object we then pass in. In fact, we can set a range by specifying two colours, like this:

```Status SetColorKey(
const Color& colorLow,
const Color& colorHigh,
```

The set colour button allows you to change the colour, which we pass as both parameters. You can replace image.bmp in the program directory to try other images if you want to.

Other things you can do with the ImageProperties object is set colour threshold, map colour swapping ( i.e. turn red into blue), set gamma (intensity), colour wrap mode, etc. It is a very powerful tool and certainly deserves a lot more attention than I have given it here. It can be used with DrawImage or with a TextureBrush, allowing it to be used in conjuction with the texture wrap modes/matrix operations provided there.

I hope I've whetted your appetite with this tutorial, which gets more into some of the exciting possibilities opened by GDI+. Unless I get flamed to death for these two articles, I'll be presenting some more shortly, firstly on using Pens ( which can also be texture wrapped ) and path objects to draw things. I will probably also cover how to create a soft brush ( even though it's frustrating that I had to roll my own and now Microsoft are giving them away ;0) Thanks for reading - if you have any questions, I virtually live here, so I'm sure to answer anything sent to me via email, or in the comments section here.

A list of licenses authors might use can be found here

## You may also be interested in...

Software Developer (Senior)
Australia
Programming computers ( self taught ) since about 1984 when I bought my first Apple ][. Was working on a GUI library to interface Win32 to Python, and writing graphics filters in my spare time, and then building n-tiered apps using asp, atl and asp.net in my job at Dytech. After 4 years there, I've started working from home, at first for Code Project and now for a vet telemedicine company. I owned part of a company that sells client education software in the vet market, but we sold that and I worked for the owners for five years before leaving to get away from the travel, and spend more time with my family. I now work for a company here in Hobart, doing all sorts of Microsoft based stuff in C++ and C#, with a lot of T-SQL in the mix.

 First PrevNext
 My vote of 5 manoj kumar choubey 20-Feb-12 21:01
 My vote of 1 Greg_moon 7-Oct-10 0:00
 My vote of 1 jessca 23-Feb-10 0:14
 HI, PLEASE ANSWER FOR MY SIMPLE QUESTIONS TO HELP ME! powerddd 30-Oct-09 2:51
 Hi (I HAVE SOME DOUBTS) powerddd 30-Oct-09 2:38
 About scale and transparent hai-long, wang 31-Jan-05 16:48
 Re: About scale and transparent Christian Graus 1-Feb-05 9:03
 Re: About scale and transparent hai-long, wang 1-Feb-05 14:33
 HatchBrush Dieter Hammer 25-Oct-04 2:18
 Re: HatchBrush Christian Graus 25-Oct-04 10:19
 how to get my new raster after rotation? au_hannibal 9-Jun-04 22:32
 Swapping R and B Values Hammerfall 8-Dec-03 6:50
 Re: Swapping R and B Values Christian Graus 8-Dec-03 8:29
 Gdi++ Image Codecs Graeme Scott 18-Nov-03 23:57
 Re: Gdi++ Image Codecs Christian Graus 19-Nov-03 10:20
 IStream from CComVariant yitzhak 18-Jul-03 23:46
 DrawImageAbort Callback Debusne 30-Jun-03 6:44
 Re: DrawImageAbort Callback Debusne 1-Jul-03 4:24
 Re: DrawImageAbort Callback dswigger 16-Dec-07 6:38
 More smooth Antialiasing chamana 8-Jun-03 16:08
 multiple rotation khaldoun 2-Apr-03 21:27
 How to save image after rotating or scaling? AngelWang 23-Mar-03 21:04
 Re: How to save image after rotating or scaling? Christian Graus 24-Mar-03 18:48
 Re: How to save image after rotating or scaling? Anonymous 24-Mar-03 20:38
 Re: How to save image after rotating or scaling? joyce0822 26-Mar-03 0:09
 Rotating by center Locked Ghost 22-Mar-03 23:38
 Re: Rotating by center Christian Graus 23-Mar-03 10:58
 Re: Rotating by center Anonymous 10-Mar-05 9:25
 great!! Seungwoo Oh 23-Jul-07 22:44
 Multiple rotations around different centers Iulian Ionescu 5-Jan-03 12:32
 rotation around center point... Anonymous 29-Oct-02 18:48
 Re: rotation around center point... Anonymous 29-Dec-02 17:47
 PageUnit confusion... dswigger 19-May-02 15:15
 IStream ? swinefeaster 11-May-02 13:02
 Re: IStream ? Christian Graus 11-May-02 14:49
 Re: IStream ? swinefeaster 11-May-02 14:48
 Re: IStream ? Christian Graus 11-May-02 15:35
 ActiveX Mazdak 24-Feb-02 0:40
 Re: ActiveX Christian Graus 24-Feb-02 22:37
 Re: ActiveX Paul Selormey 10-Apr-02 1:13
 Re: ActiveX Mazdak 10-Apr-02 1:37
 moving object in GDI+ Mazdak 22-Feb-02 10:56
 Re: moving object in GDI+ Christian Graus 22-Feb-02 12:52
 Re: moving object in GDI+ Mazdak 22-Feb-02 19:09
 Last Visit: 31-Dec-99 18:00     Last Update: 30-Aug-14 13:33 Refresh 12 Next »