Creating a Non-Rectangular Window

You've probably come across many applications that use non-rectangular windows. Windows Media Player and Winamp instantly come to mind:

Windows Media Player skin

It's quite easy to create non-rectangular windows from a bitmap file in your Win32 applications, using functions such as CreateRectRgn and CombineRgn.

With a little help

I've created a helper function to simplify the process. Just include the bitmap you want to use in your resource file:

#define IDB_BMRGN 201

IDB_BMPRGN BITMAP DISCARDABLE "region.bmp"

Then, in the initialization part of your dialog, call my CreateBmpRgn function, specifying the handle of the window, the bitmap, and the color your want to be transparent (in the form 00BBGGRRh):

; load image
INVOKE LoadImage, hInstance, IDB_BMPRGN, IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR
mov hBitmap, eax
; create region using magenta as the transparent color
INVOKE CreateBmpRgn, hWnd, hBitmap, 0FF00FFh
; set region
INVOKE SetWindowRgn, hWnd, eax, TRUE

You can check out the results in this example application written in MASM32: non-rectangular.zip (press Esc to close it).

The real thing

The code for the function is a bit more complex:

CreateBmpRgn PROC hWnd :HWND, hBmRgn :HBITMAP, clrTrans :COLORREF

    LOCAL hdcDest :HDC
    LOCAL hdcSrc  :HDC
    LOCAL bmInfo  :BITMAP

    LOCAL hRgn    :HRGN
    LOCAL hTmpRgn :HRGN
    LOCAL x       :DWORD
    LOCAL y       :DWORD
    LOCAL begin   :DWORD

    ; destination device context
    INVOKE GetDC, hWnd
    mov hdcDest, eax
    ; source device context
    INVOKE CreateCompatibleDC, hdcDest
    mov hdcSrc, eax
    ; get bitmap info
    INVOKE GetObject, hBmRgn, SIZEOF BITMAP, ADDR bmInfo
    ; select bitmap into device context
    INVOKE SelectObject, hdcSrc, hBmRgn

    ; create empty region
    INVOKE CreateRectRgn, 0, 0, 0, 0
    mov hRgn, eax

    mov x, 0
    mov y, 0

    ; pixel rows
    .WHILE TRUE
        mov eax, y
        .BREAK .IF eax == bmInfo.bmHeight

        ; pixel columns
        .WHILE TRUE
            mov eax, x
            .BREAK .IF eax == bmInfo.bmWidth

            ; skip first transparent pixels of the row
            .WHILE TRUE
                mov eax, x
                .BREAK .IF eax == bmInfo.bmWidth
                INVOKE GetPixel, hdcSrc, x, y
                .BREAK .IF eax != clrTrans

                inc x
            .ENDW

            ; remember this pixel
            push x
            pop begin

            ; find first non-transparent pixel
            .WHILE TRUE
                mov eax, x
                .BREAK .IF eax == bmInfo.bmWidth
                INVOKE GetPixel, hdcSrc, x, y
                .BREAK .IF eax == clrTrans

                inc x
            .ENDW

            ; create temp region
            mov eax, y
            inc eax
            INVOKE CreateRectRgn, begin, y, x, eax
            mov hTmpRgn, eax
            ; combine it with final region
            INVOKE CombineRgn, hRgn, hRgn, hTmpRgn, RGN_OR
            ; delete temp region
            INVOKE DeleteObject, hTmpRgn
        .ENDW

        mov x, 0
        inc y
    .ENDW

    ; release and delete device contexts
    INVOKE ReleaseDC, hWnd, hdcDest
    INVOKE DeleteDC, hdcSrc

    mov eax, hRgn
    ret

CreateBmpRgn ENDP

What I'm doing is reading the image pixel by pixel, identifying which ones should be transparent, creating temporary rectangular regions, and combining them all to form the final region.

Comments