QUICK APPROXIMATIONS OF POLYGONAL AREAS USING BITBLT
This article contains the following executables: BITLIT.ASC
Nancy has worked for the U.S. Geological Survey and the National Ocean Service in Alaska. She is the author of a hypertext publishing system for maps and geographically related data.
Several years ago, Garrison Kiellor visited Alaska, putting on a July 4th show that included a side-splitting monologue about a couple visiting from the Midwest who used a roadmap to decide where to go sightseeing. The humor in this is a bit of an inside joke because on one hand, our entire state has fewer miles of road than a small county in the Lower 48; on the other hand, Alaska has one-half of the coastline and one-fifth the landmass of the entire U.S. If we are short on roads, we are even shorter on destinations.
As Gertrude Stein said, "When you get there there is no there there," unless you count our majestic wilderness, which, of course, counts very much to us. In the job of managing natural resources and doing environmental research, the biggest challenges we have involve the logistics of putting scientists in the field, getting back their data, analyzing the information, and communicating it to decision makers and operational users.
Over the years it has become clear that we need to put as much data as possible in the hands of the users in an approachable form. Natural scientists tend to use Geographic Information System (GIS) tools, and invariably, these tools mean a big computer and a data processing bureaucracy. For complex, exacting projects, this is a good arrangement. But for quick, general answers to uncomplicated questions, things become unsatisfactory for both users and DP professionals.
In short, what we need is a "backpack" GIS that can be carried into the field, the meeting room, or the press briefing. We need to do things such as display cartographically faithful maps and employ "hypermaps" to enrich the information content with pictures, charts, graphs, narrative text, and raw data. We need to interact with the data in simple ways. What follows is a look at how we might provide a quick, reasonably accurate answer to a simple question such as, "How much of this habitat zone was covered by the oil spill?"
In this article, I examine the use of graphics and Boolean raster operations to estimate areas. The strategy for doing this relies on determining the unit area represented by a single pixel, distinguishing which pixels belong to the visual representation of the area we want to estimate, counting them, and multiplying by our unit area. For example, Figure 1 and Figure 2 show irregularly shaped areas we can combine to find various areas. Figure 3 shows the pull-down menu listing the various combinations of areas we can do. Figure 4 shows the result of estimating the non-intersecting area of Areas 1 and 2.
Let's start by looking at the C source in Listing Two, page 14. (The entire system, including header files, icons, and so on is available electronically; see "Availability" on page 3.) The program begins with the normal initialization tasks. For the first instance of the program, we need to register the window class and create any generic objects from which all instances could benefit. Notice that when we register this class, the rClass.cbWndExtra field is set to the size of a WORD, which is defined in Windows.h as being an unsigned short. This reserves space for us in the Window structure of each separate instance of the application. This word will hold the value of the constant representing the user's choice from the pull-down menu, and is used to switch the cases in the paint procedure.
Next we need to do the initialization required for every instance of the program. We assign the instance handle to a global variable and create the window based upon the previously registered class. We show the new window, using the cmdShow parameter provided to us by Windows.
At this point, we are waiting in the main program, WinMain, for the messages to come rolling in. WinMain contains the message dispatch loop. When they arrive, those messages will be delivered to the WindowProc. The WM_COMMAND message tells us that the user has spoken; naturally, we will want to do something about that as quickly as possible. The wParam (for Word Parameter) contains a constant that identifies the pull-down menu choice of the user. We will store this value in the space allocated for this purpose in the window structure and call the paint procedure.
When we enter the paint procedure there are some routine things to do. First, we need to get a handle to a display context and find the dimension in pixels of the client area of our window. We want the size of a unit in the logical coordinate system to be the same in both X and Y directions, so we set the mapping mode to MM_ISOTROPIC (or to put it another way: (xUnitLength/yUnitLength = 1)). This is important, because we estimate areas of shapes by counting the number of pixels in the area and multiplying by the value fAreaPerPixel, which we define as the square area in the logical coordinate system represented by one pixel.
When we define the size of the viewport and the window, notice that the signs of the Y extents differ. This aligns the lower-left corner of our window on the display with the origin of our logical coordinate system. As a result of this alignment, the X axis will increase to the right and the Y axis will increase, going upward.
Next we create a memory display context with the same default properties as the DC we got by calling BeginPaint. It's worth stressing that while the default attributes of the memory DC are like those of our visual DC, the size, location, and properties of the drawing surface are not yet defined. The newly created DC has a monochrome "display surface" composed of one pixel. We must explicitly define its coordinate systems and mapping properties. The memory "display surface" itself comes into being when we create a bitmap and select it into the memory DC.
We create memory bitmaps to hold an image of Area 1 and Area 2, and play the previously recorded metafiles onto them to construct those images. The metafiles are used here for the sake of convenience. Notice the calls to SaveDC and RestoreDC that bracket the PlayMetaFile call. Metafiles can and frequently do affect the settings of a DC. It is a good practice to anticipate this and ensure that the state of the DC will be acceptable after the metafile has played.
At this point, there are only two real jobs left. First we need to combine the bitmaps, and then we need to count the bits that represent the area.
A quick look at the source lines for the paint procedure in Listing Two will reveal that we use BitBlt to combine the bitmaps into a single image. BitBlt is one of the most lovable graphics tools around -- it's fast, flexible, and though this example is a little short on spectacular graphics, it can provide them. BitBlt takes several parameters: a handle to a destination DC; X and Y coordinates for the logical origin of the bitmap on the destination; logical width and height of the bitmap on the destination; a handle to a source DC; X and Y coordinates for the logical origin of the bitmap on the source; and, most interestingly, a Raster Operation (ROP) code.
The ROP code determines which of 256 possible Boolean functions will be performed on three sets of bits to produce the final image. The first set of bits is called the "Pattern." The Pattern is a bitmap which is the currently selected "brush" in the destination display context. The other two sets of bits are the bitmaps selected in the source and destination display contexts.
Of the 256 possible ROP code constants, 15 have common names, and it is good form to use the name in place of the constant if one exists. Not surprisingly, the named ROP codes are the ones most frequently used. There are some cases in which it is pretty obvious which one will do the job. For instance, SRCCOPY will simply "wallpaper" the source bitmap on to the destination at the location you specify. For the occasions when it is not so clear which Boolean function will produce the result you want, here is how to determine the appropriate ROP code.
As an example, we'll find a ROP code which will cause all the pixels outside the combination of Area 1 and Area 2 to be black, and all the interior pixels to be white. First construct the following truth table in Table 1(a).
You may wonder about the content and meaning of the bit patterns in this table. (I am always curious when I come across a new "sacred number.") The patterns have no physical meaning, but do result in 256 unique ROP code indexes across a new "sacred number.") The patterns have no physical meaning, but do result in 256 unique ROP code indexes upon Boolean combination. The two hex digits that result from the combination of the Pattern, Source, and Destination bit strings give us an index into the table of ternary raster operation codes in the Windows Software Development Kit Reference literature.
Recall that each of the areas is black and the background is white. In Table 1(b), 0 is black and 1 is white. Where source and destination are both 0 (black), we need a 1 (white) in the Result, because such a bit falls inside both areas; refer to Table 1(c). Where either source or destination are 0 (black), we need a 1 (white) in the Result, because such a bit falls inside one of the areas as in Table 1(d).
Where source and destination are both 1 (white), we need a 0 (black) in the Result, because such a bit falls outside both of the areas. Our Result is 77H.
Our result is called the "Boolean Function Number." We can find this number in the table of ternary raster operation codes, volume 2, chapter 11 of the Windows SDK Reference. The full ROP code, 7700e6H, and a Reverse Polish description of the Boolean function (DSan) are listed there. For this code, there is no common name.
For each of the cases in the paint switch, blackening the bits we want to count is just a matter of determining the ROP code and BitBlting the two images.
Counting the bits is now fairly straight-forward. When we call the function FindArea, we pass the handle to the visual DC because the pixels we need to count have been BitBlted to the screen. We create a single-plane bitmap so that the final array of bits will have a one-to-one correspondence with the pixels they represent. Because we created our other bitmaps to be compatible with the visual DC, they could have had some other color organization (multiple bits per pixel or more than one color plane, for example). Now we want them to be translated to a monochrome bitmap that has the dimensions of the client area of our window. In the translation that will take place, if the source was organized as a color bitmap, all source pixels which are the same color as the source background will be white in the monochrome destination. All other pixels will be black. For our example, this is just right. BitBlt performs this translation when it detects a difference in the color organization of the two bitmaps.
Next, we get information about the bitmap we just created and put it in the BITMAP structure we call bmArea. (In this case, this call was not strictly necessary.) We determine how much space we need to retrieve a copy of the bits and make a call to LocalAlloc. After we lock the block and get a near pointer to it, we retrieve our data with a call to GetBitmapBits.
A little counting and a little multiplying and we're home free. The procedure _COUNT_BITS, shown in Listing One (page 14), is an assembly language routine that does the following things: It takes the bits in word size hunks; it shifts the word left one bit at a time; and for each 0 (or black) bit that "falls off" the top and into the Carry Flag, it increments an accumulator. The value of the accumulator is passed back to the caller as SetBits. I'll be the first to admit that this isn't reminiscent of rocket science or Handel's Water Music. It does accomplish a couple of things, though. Counting bits is the sort of job where performance can be drastically improved through the use of assembly language, and it's a tool we shouldn't avoid when its use is justified. Second, you'll notice that we went to some length to avoid changing any segment registers. It was all right to alter and restore segment registers in previous versions of Windows, but this is no longer true. With respect to mixed-language Windows programming, it's worthwhile to follow most of the rules your mother instructed you to practice: Be careful with things that don't belong to you (segment registers), pick up after yourself (make sure you clear the stack of local data and saved registers), and don't be getting into things you haven't asked to borrow (by writing beyond the end of allocated memory objects). After we count the bits and multiply by fAreaPerPixel, we report our estimate using a MessageBox. Whew!
There we have the concept of finding areas with raster operations. With a little adjustment, the strategy should work in about any environment. Of course, the success of this approach is highly dependent on the original coordinate data. The larger the areas, the less accurate the estimates. Much digital map data is geometrically manipulated to optimize for its use in all sorts of different applications. Inherently, when we try to measure flat squares on our somewhat spherical and bumpy earth, things get complicated. Not all cartographic projections would yield coordinates that would work well in this scheme. Another deficiency of this example is the expectation of monochrome areas. Dealing with colored areas would be more useful, but more involved. Resizing the window and round-off error will result in minor changes in the reported area.
Thanks to Stan Moll of the U.S. Geological Survey/National Mapping Division, for spending a morning rounding up digital data for Area 1 and Area 2. Also many thanks to Bill Clark, colleague and professor of Computer Science at the University of Alaska, for his advice and service as a sounding board. And, as are many Windows programmers, I am greatly in the debt of a wise stranger, Charles Petzold, author of the indispensable Programming Windows 3.
Copyright © 1991, Dr. Dobb's JournalNancy Nicolaisen
Examining the Source Code
Painting the Window
The Versatile BitBlt
Forming the Raster Operation Code
Table 1: Truth tables for interior pixels
(a) Pattern: 1 1 1 1 0 0 0 0
Source: 1 1 0 0 1 1 0 0
Destination: 1 0 1 0 1 0 1 0
(b) Pattern: 1 1 1 1 0 0 0 0
Source: 1 1 0 0 1 1 0 0
Destination: 1 0 1 0 1 0 1 0
---------------------------------------
Result: 1 1
(c) Pattern: 1 1 1 1 0 0 0 0
Source: 1 1 0 0 1 1 0 0
Destination: 1 0 1 0 1 0 1 0
---------------------------------------
Result: 1 1 1 1 1 1
(d) Pattern: 1 1 1 1 0 0 0 0
Source: 1 1 0 0 1 1 0 0
Destination: 1 0 1 0 1 0 1 0
---------------------------------------
Result: 0 1 1 1 0 1 1 1
Counting the Bits
Closings and Caveats
Acknowledgments
_QUICK APPROXIMATIONS OF POLYGONAL AREAS USING BITBLT_
by Nancy Nicolaisen
[LISTING ONE]<a name="02b5_000d">
; Parameters:
; BP+6 = Offset to bits
; BP+8 = Bitmap Height in scan lines
; BP+10 = Bitmap width in pixels
; Local Data
; BP-2 = hiword of the bit count
; BP-4 = loword of the bit count
.8086
.MODEL MEDIUM
memM EQU 1
INCLUDE CMACROS.INC
.CODE
PUBLIC _COUNT_BITS
_COUNT_BITS PROC
PUSH BP ; Preserve BP
MOV BP, SP ; Set stack frame pointer
SUB SP, 4 ; This will hold our byte count
PUSH DI ; Preserve DI
PUSH SI ; Preserve SI
SUB AX, AX ;Zero the accumulator reg
MOV [BP-2], AX ;Zero the local
MOV [BP-4], AX ; storage
MOV DI, [BP+8] ;Height of the bitmap in scan lines
MOV SI, [BP+6] ;DS:offset to bits
scanning_line: MOV DX, [BP+10] ;Number of bits to check in each
; scan line
scanning_bytes: MOV BX, [SI]
XCHG BH, BL ;Get a word
MOV CL, 16 ;prepare to scan 16 bits
shifting_bits: SAL BX,1 ;shift most significant remaining bit
JC shift_again ;if it was set, shift again
INC AX ;increment ax for each 0 bit
shift_again: DEC DX ;Decrement bits/line
JZ new_line ;Process a new scan line?
DEC CL ;do we have bits left in this word?
JNZ shifting_bits ;keep shifting this word
INC SI ;if not, advance to the
INC SI ; next word
JMP scanning_bytes ; and look for black bits
new_line: ADD [BP-4], AX ;Add this line's total to our
ADC WORD PTR [BP-2], 0
; accumulator
DEC DI ;Decrement the line counter
JZ pass_bit_count ;If were done, do exit things
INC SI ;Else bump the pointer to bits
INC SI ; past the word we just scanned
; and any pad bytes
SUB AX, AX ;Zero the accumulator
JMP scanning_line ;Do the next line
pass_bit_count:
POP SI ;Restore SI
POP DI ;Restore DI
POP AX ;Loword of bitcount
POP DX ;Hiword of bitcount
POP BP ;Restore BP
RET ;Return the bit count in DX:AX
; and let the caller clear the stack
_COUNT_BITS ENDP
END
<a name="02b5_000e">
<a name="02b5_000f">
[LISTING TWO]<a name="02b5_000f">
/***************************************************************************/
/* I N C L U D E F I L E S */
/***************************************************************************/
#include "\windev\include\windows.h"
#include "areas.h"
/***************************************************************************/
/* T H E P R O G R A M ' S G L O B A L V A R I A B L E S */
/***************************************************************************/
static HANDLE hInst; /* Data that can be referenced thruout */
static HWND hWnd; /* the program, but is not normally */
long float fAreaPerPixel;
HDC hDCMem;
HBITMAP hOldBitmap;
/***************************************************************************/
/* M A I N P R O G R A M */
/***************************************************************************/
int PASCAL WinMain (hInstance,
hPrevInstance,
lpszCmdLine,
cmdShow)
HANDLE hInstance, hPrevInstance;
LPSTR lpszCmdLine; /* Length of the command line. */
int cmdShow; /* Iconic or Tiled when start. */
{
MSG msg;
hInst = hInstance;
if( hPrevInstance )
{
return (FALSE);
}
Init (hInstance, cmdShow); /* Initialization rtn.*/
while /* The main loop: */
(GetMessage((LPMSG)&msg, NULL, 0, 0)) /* (terminated by a QUIT) */
{
TranslateMessage(&msg); /* Have Windows translate */
DispatchMessage(&msg); /* Have Windows give mess */
/* to the window proc. */
}
exit(msg.wParam); /* End of the program. */
}
/***************************************************************************/
/* I N I T I A L I Z A T I O N */
/***************************************************************************/
int FAR PASCAL Init (hInstance, cmdShow)
HANDLE hInstance;
int cmdShow;
{
WNDCLASS rClass; /* Window class structure. */
int FullScreenX;
int FullScreenY;
rClass.lpszClassName = (LPSTR) "NN:AREA";
rClass.hInstance = hInstance;
rClass.lpfnWndProc = WindowProc;
rClass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
rClass.hIcon = LoadIcon (hInstance, "AREAS");
rClass.lpszMenuName = (LPSTR) "AreaFinder";
rClass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
rClass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
rClass.cbClsExtra = 0;
rClass.cbWndExtra = sizeof( WORD );
RegisterClass ( &rClass); /* Register the class. */
hInst = hInstance;
FullScreenY = GetSystemMetrics( SM_CYFULLSCREEN );
FullScreenX = GetSystemMetrics( SM_CXFULLSCREEN );
hWnd = CreateWindow((LPSTR) "NN:AREA", /* Window class name. */
"Using BitBlt to Estimate Areas - Dr. Dobbs",
/* Window Title */
WS_OVERLAPPEDWINDOW | WS_MAXIMIZE,
/* Type of window. */
0, /* Where the window should */
0, /* go when the app opens... */
(FullScreenX / 16 ) * 16,
/* Make a scan line fill an */
/* even # words */
FullScreenY, /* */
NULL, /* No parent for this wind */
NULL, /* Use the class menu. */
hInstance, /* Who created this window. */
NULL /* No params to pass on. */
) ;
ShowWindow( hWnd, cmdShow );
return TRUE;
}
/***************************************************************/
/* T H E W I N D O W P R O C E D U R E */
/***************************************************************/
long FAR PASCAL WindowProc (hWnd, message, wParam, lParam )
HWND hWnd; /* Handle of the window */
unsigned message; /* Message type */
WORD wParam; /* Message 16 bit param */
LONG lParam; /* Message 32 bit param */
{
switch (message) /* Check the mess type */
{
case WM_COMMAND:
switch(wParam)
{ /* Store wParam in the */
case ID_AREA1: /* WindowWord and tell */
case ID_AREA2: /* the paint proc about */
case ID_UNION: /* it... */
case ID_INTERSECTION:
case ID_EXCLUSIVE:
case ID_OUTSIDE:
case ID_SHOWPOLYS:
SetWindowWord(hWnd, 0, wParam );
InvalidateRect( hWnd, NULL, TRUE );
UpdateWindow( hWnd );
break;
default:
break;
}
break;
case WM_CREATE:
InvalidateRect( hWnd, NULL, TRUE );
UpdateWindow( hWnd );
break;
case WM_PAINT:
PaintAreaWindow( hWnd );
break;
case WM_SIZE: /* Dont let the window change */
break; /* size... */
case WM_DESTROY:
PostQuitMessage(0); /* send yourself a QUIT */
break; /* message. */
default:
return(DefWindowProc(hWnd, message, wParam, lParam));
break;
}
return(0L);
}
RECT rWorkRect;
/***************************************************************************/
/* T H E P A I N T P R O C E D U R E */
/***************************************************************************/
int FAR PASCAL PaintAreaWindow (hWnd)
HWND hWnd; /* Handle of the window. */
{
PAINTSTRUCT ps;
HDC hDC;
HANDLE hArea1Meta;
HANDLE hArea2Meta;
HBITMAP hArea1;
HBITMAP hArea2;
WORD WhatToEstimate;
hDC = BeginPaint( hWnd, &ps);
GetClientRect( hWnd, &rWorkRect );
SetMapMode( hDC, MM_ANISOTROPIC ); /* X and Y are dimensionally equal */
SetViewportOrg( hDC, 0, rWorkRect.bottom );
/* The viewport origin is at the */
/* left corner of the screen... */
SetViewportExt( hDC, rWorkRect.right, -rWorkRect.bottom );
/* X increases to the right and Y */
/* increases going up... */
SetWindowOrg( hDC, X_ORIGIN, Y_ORIGIN );
SetWindowExt( hDC, X_EXTENT, Y_EXTENT );
/* Logical dimensions depend on the */
/* data which defines the areas. */
/* The constants are defined in */
/* Areas.h */
hDCMem = CreateCompatibleDC( hDC );
SetMapMode( hDCMem, MM_ISOTROPIC );
SetViewportOrg( hDCMem, 0, rWorkRect.bottom );
SetViewportExt( hDCMem, rWorkRect.right, -rWorkRect.bottom );
SetWindowOrg( hDCMem, X_ORIGIN, Y_ORIGIN );
SetWindowExt( hDCMem, X_EXTENT, Y_EXTENT );
/* Create a memory display context */
/* that simulates the visible DC...*/
hArea1 =
CreateCompatibleBitmap( hDCMem, rWorkRect.right , rWorkRect.bottom );
hOldBitmap = SelectObject( hDCMem, hArea1 );
/* Create a bitmap with the same */
/* organization as this device and */
/* select it into the memory DC... */
hArea1Meta = GetMetaFile( "Area1.bas" );
SaveDC( hDCMem );
PlayMetaFile( hDCMem, hArea1Meta );
RestoreDC( hDCMem, -1 );
DeleteMetaFile( hArea1Meta );
hArea2 =
CreateCompatibleBitmap( hDCMem, rWorkRect.right, rWorkRect.bottom );
SelectObject( hDCMem, hArea2 );
hArea2Meta = GetMetaFile( "Area2.bas" ); /* For convenience, we'll construct */
SaveDC( hDCMem ); /* our area bitmaps using pre- */
PlayMetaFile( hDCMem, hArea2Meta ); /* recorded metafiles. Since the */
RestoreDC( hDCMem, -1 ); /* metafile can change the attrib- */
DeleteMetaFile( hArea2Meta ); /* utes of the DC, its often a good*/
/* practice to use the context */
/* stack to preserve the DC before */
/* playing the metafile, and */
/* restore it afterward... */
fAreaPerPixel =
( (float)X_EXTENT / (float)rWorkRect.right ) *
( (float)Y_EXTENT / (float)rWorkRect.bottom );
/* Calculate the area of 1 Pixel... */
WhatToEstimate = GetWindowWord( hWnd, 0 );
switch( WhatToEstimate )
{
case ID_AREA1:
SelectObject( hDCMem, hArea1 );
DeleteObject( hArea2 );
BitBlt( hDC, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDCMem, X_ORIGIN, Y_ORIGIN, SRCCOPY);
SelectObject( hDCMem, hOldBitmap );
DeleteObject( hArea1 );
FindArea( hDC, &rWorkRect );
break; /* Estimate the area of Area 1 */
case ID_AREA2:
DeleteObject( hArea1 );
BitBlt( hDC, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDCMem, X_ORIGIN, Y_ORIGIN, SRCCOPY);
SelectObject( hDCMem, hOldBitmap );
DeleteObject( hArea2 );
FindArea( hDC, &rWorkRect ); /* Estimate the area of Area 2... */
break;
case ID_UNION:
SelectObject( hDCMem, hArea1 );
BitBlt( hDC, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDCMem, X_ORIGIN, Y_ORIGIN, SRCCOPY);
SelectObject( hDCMem, hArea2 );
DeleteObject( hArea1 );
BitBlt( hDC, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDCMem, X_ORIGIN, Y_ORIGIN, SRCAND );
SelectObject( hDCMem, hOldBitmap );
DeleteObject( hArea2 );
FindArea( hDC, &rWorkRect ); /* Estimate the area of the union */
break;
case ID_INTERSECTION:
SelectObject( hDCMem, hArea1 );
BitBlt( hDC, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDCMem, X_ORIGIN, Y_ORIGIN, SRCCOPY);
SelectObject( hDCMem, hArea2 );
DeleteObject( hArea1 );
BitBlt( hDC, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDCMem, X_ORIGIN, Y_ORIGIN, SRCPAINT );
SelectObject( hDCMem, hOldBitmap );
DeleteObject( hArea2 );
FindArea( hDC, &rWorkRect ); /* Estimate the area of the intersec-
tion... */
break;
case ID_EXCLUSIVE:
SelectObject( hDCMem, hArea1 );
BitBlt( hDC, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDCMem, X_ORIGIN, Y_ORIGIN, SRCCOPY);
SelectObject( hDCMem, hArea2 );
DeleteObject( hArea1 );
BitBlt( hDC, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDCMem, X_ORIGIN, Y_ORIGIN, 0x990066 );
SelectObject( hDCMem, hOldBitmap );
DeleteObject( hArea2 );
FindArea( hDC, &rWorkRect ); /* Estimate the area of an exclusive
combination... */
break;
case ID_OUTSIDE:
SelectObject( hDCMem, hArea1 );
BitBlt( hDC, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDCMem, X_ORIGIN, Y_ORIGIN, SRCCOPY);
SelectObject( hDCMem, hArea2 );
DeleteObject( hArea1 );
BitBlt( hDC, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDCMem, X_ORIGIN, Y_ORIGIN, 0x7700e6 );
SelectObject( hDCMem, hOldBitmap );
DeleteObject( hArea2 );
FindArea( hDC, &rWorkRect ); /* Estimate the area outside the
combined areas...*/
break;
case ID_SHOWPOLYS:
SelectObject( hDCMem, hArea1 );
BitBlt( hDC, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDCMem, X_ORIGIN, Y_ORIGIN, SRCCOPY);
MessageBox(hWnd, "This is Area 1... ", "Areas", MB_OK );
SelectObject( hDCMem, hArea2 );
DeleteObject( hArea1 );
BitBlt( hDC, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDCMem, X_ORIGIN, Y_ORIGIN, SRCCOPY );
SelectObject( hDCMem, hOldBitmap );
DeleteObject( hArea2 );
MessageBox(hWnd, "This is Area 2... ", "Areas", MB_OK );
/* Lets have a look at the areas... */
break;
default:
break;
}
DeleteDC( hDCMem ); /* Always relinquish GDI leftovers!! */
DeleteObject( hOldBitmap );
ValidateRect( hWnd, NULL );
/* Validate the client area... */
EndPaint (hWnd, &ps); /* Finished painting for now. */
SetWindowWord(hWnd, 0, NULL );
return TRUE;
}
/***************************************************************************/
/* F I N D A R E A */
/***************************************************************************/
void PASCAL FindArea( hDC, lprWork )
HDC hDC;
LPRECT lprWork;
{
HBITMAP hArea;
HANDLE hAreaMemory;
BITMAP bmArea;
PSTR pAreaBits;
unsigned int NumberBytes;
long float fArea;
long SetBits;
char szApproxArea[12];
hArea = CreateBitmap( lprWork->right, lprWork->bottom, 1, 1, NULL );
/* Create a monochrome bitmap with the
same dimensions as the client area...
*/
SelectObject( hDCMem, hArea );
BitBlt( hDCMem, X_ORIGIN, Y_ORIGIN, X_EXTENT, Y_EXTENT,
hDC, X_ORIGIN, Y_ORIGIN, SRCCOPY);
/* Select it into the memory DC and copy
the client area to it...
*/
GetObject( hArea, sizeof( BITMAP ), &bmArea );
/* Get the dimensions and color organization
information about the monochrome bitmap...
*/
NumberBytes = bmArea.bmPlanes * bmArea.bmHeight * bmArea.bmWidthBytes;
hAreaMemory = LocalAlloc( LMEM_FIXED | LMEM_ZEROINIT, NumberBytes );
if( hAreaMemory == NULL )
{
MessageBox(hWnd, "Try closing other windows or resizing Areas...","Unable to allocte memory!", MB_OK | MB_ICONHAND );
return;
}
pAreaBits = LocalLock( hAreaMemory );
GetBitmapBits( hArea, (DWORD)NumberBytes, (LPSTR)pAreaBits );
/* Allocate memory and get bits... */
SetBits = COUNT_BITS( pAreaBits, bmArea.bmHeight, bmArea.bmWidth);
/* Count the Black ( 0H ) bits... */
LocalUnlock( hAreaMemory ); /* Release memory... */
LocalFree( hAreaMemory );
SelectObject( hDCMem, hOldBitmap );
DeleteObject( hArea );
/* Delete the monochrome bitmap... */
fArea = SetBits * fAreaPerPixel;
sprintf( szApproxArea, "%.2f", fArea );
MessageBox(hWnd, szApproxArea, "Approximate area in square meters... ", MB_OK );
/* Calculate and display area... */
return;
}