Introduction
Clipping is a tool that can be very useful when painting complex user interfaces for
any type of computer graphics display. There are two types of clipping in the subject
of computer graphics, clipping to improve performance, and clipping for effect. This article
will focus on how clipping is implemented in the WIN32 API, specifically within device contexts (DC)s.
While some of the discussion in this tutorial may only be of interest to developers
who seek advanced knowledge on the WIN32 clipping regions, the bulk of this guide
is set at an intermediate level. Also, to know many of the secrets behind the
structure and organization of the clipping regions will allow you to make design
decisions that will allow you to more readily exploit these features.
Much of the knowledge that was gained for this article was learned from the book
Windows Graphics Programming by Feng Yuan. The example that is presented is also inspired
by a demonstration program in that book. The demo program has been developed in the
windows SDK because the code is so simple.
Device Context and its Regions
For all intents and purposes, a DC is an array of pixels aligned in a rectangle.
When no restriction is imposed, any one of these pixels can be written to. However, the
DC has three types of regions, that will restrict access (clip) to these pixels. As each of
these DCs are modified, the intersection of the DCs will create the final clipping region
that allows paint access to a DC.
System Region
The System Region the base region for the device context. This region can also be
referred to as the visible region, since it has the effect of only allowing the visible parts of the
window to be displayed. Initially it will encompass
the entire DC. The system is usually in charge of defining and maintaining this region, however,
it is possible for the application to access this region, and even set the initial System Region
for a device context. This region is closely related to the target window that the
DC was created for. The System Region is also calculated from a number of different
factors. These factors include the following:
- The target window rectangle or region if it was modified by
SetWindowRgn
.
- The window regions for child windows will be removed from the System Region if
the
CS_CLIPCHILDREN
class style is set.
- The window regions for all of the windows above the target window in the Z-Order
will be removed as well.
- If
BeginPaint
was used to create the DC, then the update region for the window
will be intersected with the remaining System Region.
Clip Region
This region is defined by the application, and is the most common region, in which clipping
is accomplished by developers. When a DC is created, the initial Clip region is set to the
entire device surface of the DC. This region can be changed, manipulated and even cleared through
the life of the DC by the application.
Meta Region
This region is very similar to the Clip Region, however, it is not very well documented in
MSDN. The primary purpose of this region is to set a Clip Region for meta files when
they are displayed to a drawing surface while allowing the Clip Region to remain intact.
This region can also be thought of as a second Clip Region.
More details will be given on possible uses for each of these types of clipping regions after
the APIs that manipulate these regions are explained in detail.
System Region Functions
There are only two functions that allow access to the System Region. Both functions
are described in full detail, as well as some of the undocumented features of these functions.
GetRandomRgn
The GetRandomRgn function copies the system clipping region of a specified
device context to a specific region. You may need to get the newer versions of
the windows header files in order to have access to this function, it has just recently
been documented in MSDN, but has been exported from GDI32.dll for quite some time.
The only value that is documented for the iNum parameters is SYSRGN
which is defined as the value 4 in wingdi.h. As you can see from the description below
extracted from MSDN, the iNum must be set to SYSRGN
. This is simply
not true. There are three other undocumented values that are legal to send to
this function.
int GetRandomRgn(
HDC hdc,
HRGN hrgn,
INT iNum
);
Here are the four valid values, and their effect when set into iNum in GetRandomRgn
:
- VALUE = 1: This undocumented value will return the current
Clip Region contained in the DC.
- VALUE = 2: This undocumented value will return the current
Meta Region contained in the DC.
- VALUE = 3: This undocumented value will return the intersection
of the Clip Region and Meta Region. Feng Yuan calls this region the
Rao region, which is supposedly named after the Microsoft engineer
that convinced the development team to cache this intersected region to
increase performance. Take this for what it is worth.
- VALUE = 4, SYSRGN: The only value that is documented and defined for this function.
This value returns the System Region that was described earlier.
One more thing to be aware of when a region is retrieved from GetRandomRgn
is
that on Windows 9x operating systems the region is returned in window coordinates, and on
Windows NT machines the region is in screen coordinates. Therefore to use the region in a
window on Windows NT machines the region will need to be offset. Here is a piece of code that
can be used to translate the region on a Windows NT machine.
POINT pt = {0,0};
::MapWindowPoints(NULL, hWnd, &pt, 1);
::OffsetRgn(hRgn, pt.x, pt.y);
GetDCEx
The GetDCEx
function retrieves a handle to a DC for
a specified window or for the entire screen. This function allows the developer
to specify an initial Clip Region for the newly created DC. However, this statement
is a little misconceiving. The actaul region that gets set when a clipping region is
specified is the System Region. This is the developer's backdoor into setting
the System Region for a DC. However, one the region is set and the DC is created,
there is no way for an application to directly manipulate the System Region.
HDC GetDCEx(
HWND hWnd,
HRGN hrgnClip,
DWORD flags
);
In order to set the region, one of these two flags must be used to indicate how to
use the region:
- DCX_EXCLUDERGN: The clipping region identified by hrgnClip is excluded
from the visible region of the returned DC.
- DCX_INTERSECTRGN: The clipping region identified by hrgnClip is intersected
with the visible region of the returned DC. This would have the effect of reallowing
the regions that have already been clipped away for child window clipping and Z-Order
clipping to be painted on.
One thing to be aware of when passing a valid region to this function, is that this
function assumes ownership of the region. It is very important that this region is not
deleted or even used after this function call.
The regions that are stored in a DC are stored in window coordinates on a Windows 9x machine
and in screen coordinates on a Windows NT machine. This means that if the region is to be set
in GetDCEx
on a Windows NT machine, then the region will need to be translated
to screen coordinates relative to the window before the call to GetDCEx
. Here
is a small code sample that demonstrates how to do this:
POINT pt = {0,0};
::MapWindowPoints(hWnd, NULL, &pt, 1);
::OffsetRgn(hRgn, pt.x, pt.y);
Clipping Region Functions
The Clip Region has the richest set of functions to manipulate their data.
These functions allow the region to be set directly, modified through boolean intersection,
and even translated. A description of each function is given below.
GetClipBox
The GetClipBox
function retrieves the dimensions of the tightest
bounding rectangle that can be drawn around the current visible area on the device.
The visible area is defined by the current Clip Region or clip path, as well
as any overlapping windows.
int GetClipBox(
HDC hdc,
LPRECT lprc
);
The dimensions that this function return are in logical coordinates for the current
DC. The return value will indicate the current region type
that is stored in the Clip Region.
GetClipRgn
The GetClipRgn
function retrieves a handle identifying the current application-defined
Clip Region for the specified device context. The region that is returned will be in device
coordinates, rather than logical coordinates. This differs from the behaviour for GetClipBox
.
It is highly likely that this function could be implemented in terms of GetRandomRgn
by
simply passing in a 1 to the iNum parameter.
int GetClipRgn(
HDC hdc,
HRGN hrgn
);
SelectClipRgn
The SelectClipRgn
function selects a region as the current Clip Region for the specified device
context. This is the simplest way to set the Clip Region of a device context.
int SelectClipRgn(
HDC hdc,
HRGN hrgn
);
The dimensions that this function return are in device coordinates for the current
DC. The return value will indicate the current region type
that is stored in the Clip Region.
ExtSelectClipRgn
The ExtSelectClipRgn
function combines the specified region with the current
Clip Region using the specified mode. If you would like more details on how the
different modes will affect the current Clip Region, refer to this guide:
Guide to WIN32 Regions.
int ExtSelectClipRgn(
HDC hdc,
HRGN hrgn,
int fnMode
);
The dimensions that this function return are in device coordinates for the current
DC. The return value will indicate the current region type
that is stored in the Clip Region.
IntersectClipRect
The IntersectClipRect
function creates a new Clip Region from the
intersection of the current Clip Region and the specified rectangle. Unless one of
the other clipping functions has been called to modify the initial Clip Region, this
function will have not effect as the default Clip Region includes the entire DC.
int IntersectClipRect(
HDC hdc,
int nLeftRect,
int nTopRect,
int nRightRect,
int nBottomRect
);
ExcludeClipRect
The ExcludeClipRect
function creates a new Clip Region that consists of the
existing Clip Region minus the specified rectangle.
int ExcludeClipRect(
HDC hdc,
int nLeftRect,
int nTopRect,
int nRightRect,
int nBottomRect
);
OffsetClipRgn
The OffsetClipRgn
function moves the Clip Region of a device context by the specified offsets.
int OffsetClipRgn(
HDC hdc,
int nXOffset,
int nYOffset
);
SelectClipPath
The SelectClipPath
function selects the current path as a Clip Region for a
device context, combining the new region with any existing Clip Region using the specified mode.
BOOL SelectClipPath(
HDC hdc,
int iMode
);
Meta Region Functions
At first glance the set of Meta Region functions may seem a little meager with only two.
However, when a Meta Region is set, it will combine the intersection of the current
Meta Region, with the current clip region. The Clip Region will then be reset to the entire
DC. This in effect allows the Meta Region to use all of the Clip region functions in order
to create itself. It is highly likely that this function could be implemented in terms of
GetRandomRgn
by simply passing in a 2 to the iNum parameter.
It is however, very important that SaveDC
is used to save the state of the DC before
a meta region is set into the DC. Because when a Meta Region is created, it is intersected with
the current Meta Region. Once a Meta Region region is reduced from the default client
rectangle, then the region cannot be returned back to its original state except by restoring a previously
saved DC context.
GetMetaRgn
The GetMetaRgn
function retrieves the current meta region for the specified device context.
int GetMetaRgn(
HDC hdc,
HRGN hrgn
);
SetMetaRgn
The SetMetaRgn
function intersects the current Clip Region for the specified
device context with the current meta region and saves the combined region as the new
meta region for the specified device context. The Clip Region is reset to a null
region. The only way to reset the Meta Region to its original state is to return to a previously
saved version of the DC with SaveDC
.
int SetMetaRgn(
HDC hdc
);
Application of the DC Regions
The regions that are a part of the DC are used extensively. Quite often an application will
use these regions and not be unaware of this fact. By understanding the regions that are
used, and what these regions accomplish, a developer can possibly design their system around
these features in order to create a more efficient program. This fact is especially true when
an application or control is implemented that does not follow the normal design for WIN32
development.
BeginPaint
This is probably the most common use of the regions that are involved the DC. Internally
BeginPaint
makes a call to GetDCEx
in order to create the DC that
finally gets used. The current update region for the target window is used as the clipping
region in GetDCEx
. This final result is the System Region is equivalent
to the Update Region of the window for the paint DC. When the application attempts to paint
on the paint DC, only the regions that have been invalidated will be repainted.
At this point the application could call GetRandomRgn
in order to get the
region that needs to be repainted, and factor out all of the unnecessary painting increasing the
efficiency of the paint routine. Even if the application sends the entire window to be repainted
upon each paint message, the display ultimately experiences less flicker because of the
automatic clipping that is experienced by the System Region. In order to see a demonstration
of the flicker that would occur with out this feature, make a call to InvalidateRect
before
BeginPaint
in your paint handler.
InvalidateRect(hWnd, NULL, TRUE);
This will have the effect of invalidating the entire target window, and eliminating any benefit that
System Region provides.
Clipping for Effect
Use the Clip Region to create a mask that only allows a special portion of the DC to be
painted on. This is probably what is most commonly thought of when the term clipping is
mentioned in WIN32 development.
Metafiles
Metafiles are a way for an application or an artist to store a set of vector drawing commands
in a file in order to draw them on a surface at a later time. Metafiles are great in the fact that
they are vector based which means that they will scale well to just about any size that they
should be displayed at. The drawing dimensions of a metafile are only restricted to the resolution
of the values used in window GDI.
If an application uses metafiles to paint a portion of a display, it may be necessary
to setup a
clipping region to restrict the areas that rogue metafiles can paint to. That is where the
Meta Region is mainly used for. The application can set up a boundary in the Meta Region
that cannot be painted to, that may be different than the Clip Region. This is in fact the
original intention of the Meta Region.
Libraries
One other possible use for the Meta Region is in imported or exported library code that
paints to a DC. If a library function is imported, and this function restricts its painting by
use of the Clip Region, it would make it difficult for the host application to further
clip the drawing especially of the library function does not incorporate the current Clip Region
into the new Clip Region that it creates. The Meta Region would nicely fix this
problem.
On the other hand, if drawing code is written to be exported in a library, and the Clip Region
is a concern of the developer, then the Meta Region could be used by the library in order to
set the proper clipping area, and allow the user of the library to gain further clipping rights.
Demonstration
The demonstration program that is provided has been inspired by a demo program in
the book Windows Graphics Programming by Feng Yuan. Each of the three regions of
the DC will be demonstrated separately.
This application is written for the Windows SDK. Because of the use of the GetRandomRgn
function, the newest header files should be used. This function was only recently exposed in the header
files. Just in case, I have included a copy of wingdi.h that contains the definition of
GetRandomRgn
for those developers with outdated header files. I would only recommend
using this file to build the application if you do not currently have the most recent header files.
If you want to develop an application using this function, I would download the SDK with the most
recent header files.
This application will allow the user to separately configure each of the three regions that are
located in the DC. Here is a description of how each of the three regions are
presented in the application.
- System Region: This region is represented by a red border around the boundary of the region.
FrameRgn
will be used to paint this boundary. The System Region is the only
region that the application allows the user to manually set.
- Clip Region: This region will be represented by vertical dark purple lines. When this
region is activated, it will automatically be calculated to be the ellipse that fits vertically
between the top and bottom client boundaries, and 2/3 of the width of the window.
- Meta Region: This region will be represented by horizontal blue lines. When this
region is activated, it will automatically be calculated to be the ellipse that fits horizontally
between the left and right client boundaries, and 2/3 of the height of the window.
- Intersection: The intersection of all three regions will always be active and represented
by a pale pink colored fill. This represents the region that would be available to the application
to paint on if these three regions were set as illustrated.
Here is an example of the display with all three regions activated:
There are four menu settings that can be checked in order to change the display.
Listed below are the directions to each of these five settings, and any code that may
need extra explanation in order to make the feature work.
Default System Region: The System Region that is calculated by Windows will be displayed.
When the entire window is refreshed, then the entire client area will be shown as the System Region.
However when only a small portion of the window is updated, the System Region will be set to
the update region of the window.
The System Region will be extracted from the DC with this call:
::GetRandomRgn(hdc, hSystemRgn, SYSRGN );
Manual System Region: The System Region can be set by the user tracing a path of the
desired System Region. The System Region will be set into the DC with this call:
HRGN hRgnExclude = ::CreateRectRgnIndirect(&rClient);
::CombineRgn(hRgnExclude, hRgnExclude, g_hUserRgn, RGN_DIFF);
...
hdc = ::GetDCEx(hWnd, hRgnExclude, DCX_CACHE | DCX_EXCLUDERGN);
::SendMessage(hWnd, WM_ERASEBKGND, (WPARAM)hdc, NULL);
::ValidateRect(hWnd, NULL);
Notice the extra code that is used to create the DC. The message for WM_ERASEBKGND
and
the call to ValidateRect
This is because GetDCEx
is sued to
create the DC rather than BeginPaint
. These actions are normally performed
inside of BeginPaint
.
Clip Region: This will activate or deactivate the Clip Region.
Meta Region: This will activate or deactivate the Meta Region.
The demonstration program is very simple, however it does show that three distinct
regions
do exist inside of the DC, and how they determine the final painting surface after
all of the clipping has been determined.
Conclusion
Clipping is a technique that can often be ignored. However it can be very beneficial
during the right circumstances. WIN32 provides three distinct regions in its DC that
all contribute to the final outcome when clipping is applied. Some of the regions require
extra effort, like the Clip Region and the Meta Region, however, other
regions like the System Region are added as a extra optimization with no
extra work on the developers part.
The information that was provided in this guide is probably more than you will ever
need to know about windows clipping regions. This knowledge will however allow you to
design your program around these features and possibly take advantage of them for
your special programming project in the future.