comrade's bureau


comments?

Custom-Shaped and Transparent Windows


Hello Win32 coders! This is my first tutorial, so please forgive me if I make any mistakes. I started making this tutorial because of a request from a friend of mine, Chuvak. This tutorial provides some pseudo code (which actually looks more like C) you can use. It is easy to understand it, so I am sure you will have no problem re-writing this pseudo code in whatever language your app is in. Well, I hope you enjoy it and learn something from this!

As you can see by the title, this tutorial is about custom shaped and transparent windows. Yeah, those you see in those cool installers by many groups. Luckily, Win32 provides programmers easy API you can use to make one of those windows in a few minutes. First, you have to make a region. Regions can be rectangular, elliptical, and polygon-based. After you made your region, you must assign it to a window by using the SetWindowRgn() API function.

// we want to redraw the window
BOOL bRedraw = TRUE;
SetWindowRgn(hWnd, hRegion, bRedraw);
...
// remember to free resources
DeleteObject(hRegion);

Now, how do you create the regions? Regions can be created by these Win32 API functions: CreateRectRgn, CreateRectRgnIndirect, CreateRoundRectRgn, CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreatePolyPolygonRgn. CreateRectRgn and CreateEllipticRegion are the simplest and the easiest to use out of all those. You just pass on four parameters to them. The parameters are the X, Y, Width and Height coordinates that are relative to the client area of the window. All these functions return a handle you can use with SetWindowRgn().

HRGN hRegionRect, hRegionCircle;
hRegionRect = CreateRectRgn(0, 0, 400, 300);
hRegionCircle = CreateEllipticRegion(0, 0, 200, 200);
SetWindowRgn(hWnd, hRegionRect, TRUE);
SetWindowRgn(hOtherWnd, hRegionCircle, TRUE);

CreateRectRgnIndirect and CreateEllipticRgnIndirect do the same thing as CreateRectRgn and CreateEllipticRgn, except they take one parameter instead of four. The parameter is the pointer to a RECT structure that contains the coordinates of the region.

RECT rc;
HRGN hRegion;
GetClientRect(hWnd, &rc)
rc.left = rc.left + 10;                // increase x by 10
rc.top = rc.top + 10;                  // increase y by 10
rc.right = rc.right - 20;              // decrease width by 20
rc.bottom = rc.bottom - 20;            // decrease height by 20
hRegion = CreateRectRgnIndirect(&rc);
SetWindowRgn(hWnd, hRegion, TRUE);

CreateRoundRectRgn is very similiar to CreateRoundRect, but it needs two extra parameters - the width and height of the ellipse used to create rounded corners.

HRGN hRegion;
hRegion = CreateRoundedRectRgn(0, 0, 400, 300, 16, 16);
SetWindowRgn(hWnd, hRegion, TRUE);

Now, the only two region creation API functions left to cover in this tutorial are CreatePolygonRgn and CreatePolyPolygonRgn. CreatePolygonRgn takes three parameters. They are the pointer to POINT structures that specify the x and y coordinates of each vertex, the number of vertices and the polygon fill mode that should be used. The third parameter, polygon fill mode, can be of two values - ALTERNATE and WINDING. Both are Windows constants and should be declared in your Windows include file. Generally, you would use the CreatePolygonRgn API function like this:

HRGN hRegion;
POINT pt[4];
pt[0].x = 0;
pt[0].y = 0;
pt[1].x = 10;
pt[1].y = 0;
pt[2].x = 10;
pt[2].y = 20;
pt[3].x = 0;
pt[3].y = 0;
hRegion = CreatePolygonRgn(&pt, 4, ALTERNATE);
SetWindowRgn(hWnd, hRegion, TRUE);

I have not seen any difference between ALTERNATE and WINDING modes. All I can tell you about them is taken from the API reference:

  • ALTERNATE - Fills area between odd-numbered and even-numbered polygon sides on each scan line.
  • WINDING - Fills any region with a nonzero winding value.

This is pretty much everything you need to create a custom-shaped or a transparent window. I will explain one more thing in this tutorial, so if you are interested, read on. Here I will tell you how to shape the window using any image without polygon regions. This method is quite slow, and I suggest you don't use it at all. Stick to polygon regions instead. This method involves reading data from the bitmap, and making the window transparent at places where the pixel read equals to the specified mask color. First, we create a rectangular region with dimensions of our image. Then we "subtract" from that region to create transparencies. Here is the code:

HRGN hRgn, hRgnTemp;
COLORREF mask_color;

// mask color = first pixel
mask_color = bitmap(0, 0);
hRgn = CreateRectRgn(0, 0, width, height);
for x = 0 To width-1
    for y = 0 To height-1
        if (bitmap(x, y) == mask_color)
            // make the pixel transparent
            hRgnTemp = CreateRectRgn(x, y, x+1, y+1);
            CombineRgn(hRgn, hRgn, hRgnTemp, RGN_DIFF);
            DeleteObject(hRgnTemp);
        end if
    next y
next x
SetWindowRgn(hWnd, hRgn, TRUE);

That is it. As you can see from the code above, we just loop through the image, check each pixel against the mask color. If they are equal, we use CombineRgn() with a temporary 1x1 region to mask out the pixel. CombineRgn() has actually many other uses, I am sure you can figure them out on your own. Just substitute the RGN_DIFF flag with RGN_AND, RGN_COPY, RGN_OR, RGN_XOR.

As the last thing in this tutorial, I will explain how you can use the ExtCreateRegion() and GetRegionData() functions. Let's say you have a crazy transparency you want to make, but both the polygon data and the slow method just described above are out of the question, you can actually get the region data and re-use it. First, you must create a region using the slow method and then use GetRegionData() to retrieve the region information. This information can then later be re-used to create that same transparency very quickly (instead of using the slow method again). For that you must use the ExtCreateRegion() to create such region. Here's some example code which retrieves the information about the already created region (first step):

...
// make the region
...
int intSize = 16384;
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE OR GMEM_ZEROINIT, intSize);
char* ptrData = GlobalLock(hMem);
GetRegionData(hRegion, intSize, ptrData);
...
// save it somewhere
...

Note: you must get and save the region data BEFORE you actually assign it to a window with SetWindowRgn(). I don't know why it happens, but the region is unusable after you use SetWindowRgn(). Keep this mind and it might keep you from wasting 20 minutes trying to find the bug like it happened to me.

After retrieving the data, you can make it so it saves it in a file. When it is saved, you can use that data in your program, like this:

...
// read the data
...
char* ptrData = /* somewhere from the saved location */
int intSize = 16384;
HRGN hRegion = ExtCreateRegion(0, intSize, ptrData);
SetWindowRegion(hWnd, hRegion, TRUE);
...

As you can see, I have used 16kb for the data. It should be enough for most transparencies, but if you have a really large one I suggest you increase the buffer size. Another thing you could do is to to call GetRegionData with 0 for all paramaters except the region handle and use the return value as your size for the buffer.

Congratulations! You have finished my tutorial. I have attached C and assembly sources to help you out. Also check out Region Cutter, a tool I have made that does the transparency creation job for you, all you have to do is include the output region data in your program and use the code above to load it!

Attachments

shapewnd.zip (78 KB) - this article in text-format, as well as examples in C and asm.

References

MSDN Library - Platform SDK API reference.

Comments

[an error occurred while processing this directive]