What is clipping?
To understand this, it is necessary to understand how Windows deals with windows which overlap each other. Windows does not draw to the screen a series of windows one on top of the other so that the last one to be painted appears on top. If it did do this there would be a lot of "flashing" as windows appeared then disappeared as they were drawn over. Instead, the system keeps a record of which part of the screen needs updating (the "update region" or "invalid region") and it also knows which part of a window is visible at any one time (the "visible region"). The system only permits drawing in these two regions (together they are known as the "clipping region"). There may be various reasons for the need for updating: for example, another window may have been removed to expose a part of the window to be painted, or the user may have clicked the taskbar to show the window concerned.
Why alter the clipping region ?
The ability to change the clipping region presents many opportunities to customise your application to look quite different on the screen. It gives you the opportunity to create buttons and windows of various shapes and styles which are not available in the Windows API. A simple example was given in temporary window drawn directly, where a copyright notice was drawn to the screen during WM_PAINT and then excluded from the clipping region to make sure it was not written over. There are other examples in clipping control by device context.
Changing the window clipping region
How you change the clipping region depends on whether or not you have called BeginPaint or GetDC or GetDCEx. If you have already called one of these, the clipping region for the window's device context (DC) has already been set to the (now) visible region of the window (ie. the previous visible region, less the region now covered, plus the region now uncovered). Thus you can only change the clipping area by using those functions which directly affect the DC (see clipping control by device context). There are various ways to control the clipping region before getting the DC, for example you can invalidate or invalidate the whole window or parts of the window using InvalidateRect, InvalidateRgn or ValidateRect, ValidateRgn or RedrawWindow. Window styles will also affect how the clipping region is found, for example WS_CLIPCHILDREN and WS_CLIPSIBLINGS.
Here, however we make more complex shapes using the API SetWindowRgn. This tells the system which part of the window may be recognised and drawn to. The system ignores any part of the window outside the region. Windows are always rectangular when made. Regions may be any shape, some very complex. By applying the region to the rectangle using SetWindowRgn in effect you throw away part of the window (that part outside the region). Mouse clicks in that part thrown away will not activate the window, and will be excluded from window drawing altogether, whether on a system paint, or a program paint on the WM_PAINT message.
Try the tests
In each of these tests a region is made of an unusual
shape, and is applied to an ordinary rectangular window using SetWindowRgn.
The region is then drawn on the WM_PAINT message. You never see the
rectangle, you only see the region. Also if you left click where the
rectangle is situated but outside the region, the window does not get
activated. This proves that the system ignores any part of the window
outside the region set by SetWindowRgn. There is a tester window which
you can activate and move around to test this further, and the dialog
allows you to change the shape of the region or text design in the region.
Elliptical window
In the first example an elliptical window is made. Lets have a look at the code, step by step.
First a window class is registered in the usual way (INITIALISE_WNDCLASS
is a function which sets EBX to the WNDCLASS structure and sets all
necessary values in the structure). IRREG_CLASSNAME is just a unique name of your own choosing:-
CALL INITIALISE_WNDCLASS ;get ready for all windows
MOV D[EBX+4],ADDR IrregWndProc
MOV D[EBX+24h],ADDR IRREG_CLASSNAME
PUSH EBX ;address of WNDCLASS
CALL RegisterClassA ;register the window procedure
Then after working out the correct height (in IrregHeight) and width
(in IrregWidth) the new window is made and its handle kept in hIrregWnd:-
PUSH 0,[hInst],0,[hWnd] ;parent=main window
PUSH [IrregHeight],[IrregWidth],120D,120D ;ht, wdth, y-pos, x-pos
PUSH 90000000h ;WS_POPUP+WS_VISIBLE
PUSH 0,ADDR IRREG_CLASSNAME,0
CALL CreateWindowExA ;make window, returning handle in EAX
MOV [hIrregWnd],EAX
Now this is where the fun starts. Before the system displays the
window it sends a WM_CREATE message to the window procedure IrregWndProc.
Upon this message the region is created (handle kept in hIrregRgn), in
this case an ellipse, which fits neatly into the window. Note that
client co-ordinates are used here. Then the region just made is set
for the window using SetWindowRgn. Finally the opportunity is taken
in SC400 to work out the size and position of the "cancel" button in client
co-ordinates (this will be drawn during WM_PAINT). Note that the window
handle is provided by the system on the stack at [EBP+8h] as is the case
for any message sent to a window procedure.
IRREG_CREATE: ;make and set window region
MOV EBX,ADDR CLIENT_RECT
PUSH EBX,[EBP+8h]
CALL GetClientRect
PUSH [EBX+0Ch],[EBX+8h],0,0
CALL CreateEllipticRgn
MOV [hIrregRgn],EAX ;keep handle of region
PUSH 0,[hIrregRgn],[EBP+8h]
CALL SetWindowRgn
CALL SC400 ;get dimensions for cancel button
XOR EAX,EAX ;return zero to continue window creation
RET
;
SC400: ;ebx holds dimensions of window
MOV ESI,ADDR SQUIFFBUTTON_RECT
MOV EAX,[EBX+8h] ;get rhs of window
SUB EAX,80 ;allow for button width
SHR EAX,1 ;centralise
MOV [ESI],EAX ;x-pos of button
ADD EAX,80 ;add width of button
MOV [ESI+8],EAX ;rhs of button
MOV EAX,[EBX+0Ch] ;get bottom of window
SUB EAX,44 ;allow for button height+
MOV [ESI+4],EAX ;use as y-pos of button
ADD EAX,30 ;height of button = 30
MOV [ESI+0Ch],EAX ;bottom of button
RET
Now for the code executed in response to WM_PAINT. This displays our
window as we want it to look. The APIs FillRgn and FrameRgn are used
as a convenient way to draw in the region. In this case they draw the
grey background and the darker frame of the ellipse. Then the "cancel"
button is drawn by DrawFrameControl, and text is added using DrawText.
In the DRAW_REGION procedure you will see the use of SetViewportOrdEx.
The reason for this is as follows. The device context obtained from
BeginPaint is based on client co-ordinates. In other words, when
you use this DC to draw to you provide co-ordinates by reference to
the client area of the window. This makes sense, because you would not
normally wish to draw outside the window when responding to the WM_PAINT
message. However, the APIs FillRgn and FrameRgn work with screen
co-ordinates. When you create a region you provide a size and shape
for the region. When you apply the region to the window using SetWindowRgn
the region adopts the current position of the window on the screen. Thus
the region will follow the window around the screen, which is clearly
useful if you are not drawing to the region, for example, using the
region as a hittest area for the mouse cursor. However it means that
when drawing to the window containing the region some adjustment to the
device context is required. In DRAW_REGION the x and y co-ordinates of
the window are made negative and then passed to SetViewportOrgEx as
negatives values, so that the starting x and y co-ordinates in the DC
(the "viewport origin") become those of the screen rather than those
of the window. SetViewportOrgEx returns the current viewport origin in
a POINT structure which is a structure of two dwords. After the calls
to the region APIs this is restored in the second call to SetViewportOrdEx.
In practice the restored x and y co-ordinates are zero, but there is no
extra overhead in restoring the actual values.
IRREG_PAINT:
PUSH ADDR PAINTSTRUCT
PUSH [EBP+8]
CALL BeginPaint ;ensure DC is validated (controls WM_PAINT)
MOV EDI,EAX ;keep DC in EDI
CALL DRAW_REGION ;draw the region background and border
PUSH 10h,4,ADDR SQUIFFBUTTON_RECT,EDI
CALL DrawFrameControl ;draw a small button for "cancel"
CALL DRAW_IRREGTEXT ;draw the text
PUSH ADDR PAINTSTRUCT
PUSH [EBP+8]
CALL EndPaint
RET
;
DRAW_REGION:
MOV EBX,ADDR RECT
PUSH EBX,[EBP+8h]
CALL GetWindowRect ;get window position
MOV EAX,[EBX] ;left
NEG EAX
MOV EDX,[EBX+4] ;top
NEG EDX
MOV EBX,ADDR POINT
PUSH EBX,EDX,EAX,EDI
CALL SetViewportOrgEx
;*************
PUSH 1 ;light grey brush
CALL GetStockObject
PUSH EAX,[hIrregRgn],EDI
CALL FillRgn ;fill region with light grey brush
PUSH 3 ;dark grey brush
CALL GetStockObject
PUSH 3,3,EAX,[hIrregRgn],EDI
CALL FrameRgn ;and make a darker frame around it
;*************
PUSH EBX,[EBX+4],[EBX],EDI
CALL SetViewportOrgEx ;restore original viewport origin
RET
;
DRAW_IRREGTEXT:
PUSH 1,EDI ;transparent
CALL SetBkMode
MOV EBX,ADDR CLIENT_RECT
PUSH EBX,[EBP+8h]
CALL GetClientRect
ADD D[EBX],10 ;x-pos
ADD D[EBX+4],80 ;y-pos
SUB D[EBX+8],20 ;rhs
PUSH 10h,EBX,-1,ADDR IRREGMESS20,EDI ;multiline
CALL DrawTextA
;********************
PUSH 25h,ADDR SQUIFFBUTTON_RECT,-1 ;25h=centred
PUSH 'Cancel'
PUSH EDI
CALL DrawTextA ;insert button text
RET
Don't forget to release the handle hIrregRgn when the window is destroyed by calling DeleteObject on the message WM_DESTROY. Remember such handles can not be released if they are currently selected in a device context. Here this is not a problem since the handle is only selected in the device context during the WM_PAINT message.
Squiffy window
(simple border)
In this test a parallelogram shape is created using
4 points in CreatePolygonRgn, instead of an ellipse. The degree of slant
of the parallelogram can be altered in the dialog. Note that the frame
is best when the slant is 45 degrees (slant of +260 pixels) because the
screen is able to draw a precise straight line at that angle.
Squiffy window
(GetPixel and SetPixel)
In this test a copy of the window as we want it to
be (but in rectangular form) is
kept in a bitmap of its own which is made on the WM_CREATE message.
Then on WM_PAINT the bitmap is drawn to the screen with the correct amount
of slant using GetPixel and SetPixel. This is a very slow process, but
this example at least demonstrates the use of these APIs. The dialog
also gives the option of writing the text in the original bitmap
("squiff text too") or as an italic font during WM_PAINT. This test
demonstrates how difficult it is to make text look good by slanting
it manually. It is better to use an italic font.
Here is how the bitmap is made during the WM_CREATE message:-
MOV EBX,ADDR CLIENT_RECT
PUSH EBX,[EBP+8h]
CALL GetClientRect ;get window dimensions
PUSH [EBP+8h]
CALL GetDC ;get window device context
MOV [hDC],EAX
PUSH EAX
CALL CreateCompatibleDC ;create a suitable new DC
MOV [hSquiffDC],EAX ;keep handle to the DC
PUSH [EBX+0Ch],[EBX+8h] ;desired height, width
PUSH hDC ;use original DC to ensure colour
CALL CreateCompatibleBitmap ;create a bitmap
MOV [hSquiffBitmap],EAX
PUSH EAX,[hSquiffDC]
CALL SelectObject ;select bitmap into the new DC
MOV EDI,EAX ;keep previous bitmap if any
PUSH [hDC],[EBP+8h]
CALL ReleaseDC ;release original DC
;****
PUSH 10h,4,EBX,[hSquiffDC]
CALL DrawFrameControl ;draw a large button in bitmap
Here is how the bitmap is drawn during the WM_PAINT message:-
DRAW_SQUIFFBYPIXEL:
XOR ESI,ESI ;start at top
MOV EBX,[IrregWidth] ;start on rhs
SUB EBX,[SquiffFactor] ;bitmap is a bit smaller
DEC EBX ;coordinates ebx=x, esi=y
JS >L36 ;safety
L26: ;each row loops to here
CALL DS400 ;get forward slant for this row
;************************
PUSH EBX
L34: ;each pixel loops to here
PUSH ESI,EBX,[hSquiffDC]
CALL GetPixel
MOV EDX,EBX ;get x pos
ADD EDX,EDI ;apply the forward slant
PUSH EAX,ESI,EDX,[hDC] ;destination DC
CALL SetPixel
DEC EBX ;do next pixel to the left
JNS L34
POP EBX
INC ESI ;do next row down
CMP ESI,[IrregHeight]
JNZ L26 ;until none left
L36:
RET
;
DS400: ;get in edi the forward slant for this row
XOR EDX,EDX
XOR EDI,EDI ;to hold forward slant
MOV EAX,[IrregHeight]
SUB EAX,ESI ;get distance from top
MOV ECX,[SquiffFactor]
JECXZ >L30 ;safety
MUL ECX
MOV ECX,[IrregHeight]
JECXZ >L30 ;safety
DIV ECX
SHR ECX,1
CMP EDX,ECX
JB >L28
INC EAX ;round up
L28:
MOV EDI,EAX
L30:
RET
And here is how the italic font is created:-
PUSH 13D
CALL GetStockObject ;get SYSTEM_FONT (used in dialogs)
MOV ESI,ADDR LOGFONT
PUSH ESI,60D,EAX ;get font inf into LOGFONT (height, width)
CALL GetObjectA
MOV B[ESI+14h],1 ;italic font
MOV B[ESI+18h],7 ;only use True Type fonts
PUSH ESI
CALL CreateFontIndirectA
MOV [hSquiffFont],EAX ;keep handle
Don't forget to release the handles hIrregRgn, hSquiffBitmap and hSquiffFont when the window is destroyed by calling DeleteObject on the message WM_DESTROY. Remember such handles can not be released if they are currently selected in a device context. Here this is not a problem since the handles are only selected in the device context during the WM_PAINT message.
Squiffy window
(bitmap manipulation)
This test is much much quicker than the last and works
in a similar way except that the bitmap is manipulated before
painting (when it is created) using assembler techniques. When the time
comes to paint it to the screen the fast API BitBlt (literally "Bit Blast")
draws the new bitmap.
First (as before) a copy of the window as we
want it to be (but in rectangular form) is
kept in a bitmap of its own. This is made larger than necessary to
take the extra size required by the slant. Then we use the API GetDIBits
to get information about the (unslanted) bitmap.
The bitmap is held as a DIB (Device Independent
Bitmap), which means that it has a header (a BITMAPINFO) which contains
information about the bitmap. It is so called because this information
can be read and the bitmap used, rejected or altered to suit the
device to which it is being drawn. We use this information to create
a memory area of the correct size to receive the bits of the bitmap.
Then SQUIFF_BITS
remakes the bitmap within the memory area, but with the correct slant.
This has to allow for the correct bitmap format. There are a number of such formats but in practice it was found that only two types of bitmaps were returned by GetDIBits - either 8 bits-per-pixel or 16 bits-per-pixel. SQUIFF_BITS needs to switch accordingly.
Finally the bitmap as amended is restored to the system using SetDIBits, and
restored to the new DC we made for it earlier.
Now we have a DC containing the slanted bitmap ready to draw to
the screen using BitBlt.
Note that in Windows XP the width and height of the compatible bitmap in pixels should always be divisible by four. If this is not done there will be unpredictable results when using GetDIBits and SetDIBits.
;*********** first, get the size of the unslanted bitmap
MOV ESI,ADDR BUFFER ;(BITMAPINFO is variable and could be very large)
MOV D[ESI],28h ;insert size of header
MOV W[ESI+0Eh],0 ;ensure bit count is zero
PUSH 0,ESI,0,0,0,[hSquiffBitmap],[hSquiffDC]
CALL GetDIBits ;get details into BITMAPINFO
MOV EAX,[ESI+14h] ;get size of bitmap data
PUSH 4h,3000h,EAX,0 ;reserve and commit in memory size required
CALL VirtualAlloc
OR EAX,EAX ;check for error
JZ >L38
MOV [hSquiffMem],EAX
PUSH 0,ESI,EAX,[ESI+8h],0,[hSquiffBitmap],[hSquiffDC]
CALL GetDIBits ;get the bitmap data into hSquiffMem
;******* ;check may manipulate the bits
MOV EAX,[ESI+10h] ;get compression type
OR EAX,EAX ;ok if none
JZ >L36
CMP EAX,3 ;or BI_BITFIELDS (found in W9x bitmaps)
JNZ >L38 ;no - drop out
L36:
CALL SQUIFF_BITS ;squiff the bits directly in memory
;*******
PUSH 0,ESI,[hSquiffMem],[ESI+8h],0,[hSquiffBitmap],[hSquiffDC]
CALL SetDIBits ;put back the amended bitmap
L38:
PUSH [hSquiffBitmap],[hSquiffDC]
CALL SelectObject ;select bitmap into DC again
MOV ECX,[hSquiffMem]
JECXZ >L40
PUSH 8000h,0,ECX
CALL VirtualFree ;free memory used by squiffy bitmap
MOV D[hSquiffMem],0
L40:
;
SQUIFF_8BITS:
XOR ESI,ESI ;start at top (in fact bottom of bitmap)
L32: ;each row loops to here
CALL DS400 ;get in edi the forward slant for this row
SUB EDI,[SquiffFactor]
NEG EDI
;************** esi holds the scanline, ebx the pixel, ebp current mempos
PUSH EBX ;ebx= width-1
MOV ECX,EBP ;get current mempos (source)
ADD EDI,ECX ;get current mempos (destination)
L34: ;each pixel loops to here
MOV AL,[ECX+EBX]
MOV [EDI+EBX],AL ;apply the forward slant
DEC EBX ;do next pixel to the left
JNS L34
POP EBX ;restore ebx=width-1
INC ESI ;do next row down (up)
CMP ESI,[IrregHeight]
JZ >L36 ;none left
ADD EBP,[IrregWidth]
JMP L32
L36:
RET
;
SQUIFF_16BITS:
XOR ESI,ESI ;start at top (in fact bottom of bitmap)
L42: ;each row loops to here
CALL DS400 ;get in edi the forward slant for this row
SUB EDI,[SquiffFactor]
NEG EDI
;************** esi holds the scanline, ebx the pixel, ebp current mempos
PUSH EBX ;ebx= width-1
MOV ECX,EBP ;get current mempos
L44: ;each pixel loops to here
MOV EAX,[ECX+EBX*2]
MOV EDX,EBX ;get x pos
ADD EDX,EDI ;apply the forward slant
MOV [ECX+EDX*2],EAX
DEC EBX ;do next pixel to the left
JNS L44
POP EBX ;restore ebx=width-1
INC ESI ;do next row down (up)
CMP ESI,[IrregHeight]
JZ >L46 ;none left
MOV EAX,[IrregWidth]
SHL EAX,1 ;get width of each scanline
ADD EBP,EAX
JMP L42
L46:
RET
;
SQUIFF_BITS: ;squiff the bits directly (assume 16 bits/pixel)
PUSH EBP,ESI
MOV EBP,[hSquiffMem]
MOV EBX,[IrregWidth] ;get rhs of bitmap
SUB EBX,[SquiffFactor] ;start on rhs of button (bitmap is wider)
DEC EBX ;coordinates ebx=x, esi=y
JS >L56 ;safety
MOV AX,[ESI+0Eh]
CMP AX,10h ;see if there are 16 bits-per-pixel (found in W9x)
JZ >L50 ;yes
CMP AX,8h ;see if there are 8 bits-per-pixel (found in XP)
JNZ >L56 ;no others allowed for the moment
CALL SQUIFF_8BITS
JMP >L56
L50:
CALL SQUIFF_16BITS
L56:
POP ESI,EBP
RET
And here is how the bitmap is drawn to the screen during WM_PAINT:-
DRAW_SQUIFFBITMAP:
PUSH 0CC0020h ;SRCCOPY
PUSH 0,0,[hSquiffDC] ;y, x, source DC
PUSH [IrregHeight],[IrregWidth],0,0 ;height, width, y, x
PUSH [hDC] ;destination DC
CALL BitBlt
RET