Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Undocumented Corner


MAR93: UNDOCUMENTED CORNER

UNDOCUMENTED CORNER

Inside Windows Regions

Joseph M. Newcomer and Bruce Horn

Dr. Joseph M. Newcomer received his PhD in the area of compiler optimization from Carnegie Mellon University in 1975. Bruce Horn was one of the original members of the Macintosh design team, having written the Finder (with Steve Capps), and designed and implemented the notion of the Macintosh "resources." He is currently finishing his PhD in Computer Science at Carnegie Mellon University. The work described in this article was done for the Maya Design Group (Pittsburgh, Pennsylvania).


The "Undocumented Corner" is a new DDJ column that will cover undocumented aspects of the programming interfaces for DOS, Windows, OS/2, networks, and any other operating systems for which DDJ readers are programming. The intent is to not just explain obscure or nonintuitive interfaces, but to present solid reference material that really isn't in the manuals -- but should be.

For example, an explanation of how to use the Windows DDE interface wouldn't belong here because, while extremely difficult, that interface is documented. If, however, you've found five new DDE messages that are genuinely useful or important (this is a purely hypothetical example), but aren't mentioned in the manuals that come with the Windows SDK, then this is the place to talk about them. Likewise if you have, say, disassembled Windows and want to talk about how the documented DDE interface is implemented. In other words, in this column DDJ readers will be writing in to talk about undocumented interfaces and operating-system internals.

One purpose of this column is to provide a forum for readers of Undocumented DOS (Addison-Wesley, 1990) and Undocumented Windows (Addison-Wesley, 1992), for which I was a coauthor. While generally recognized as standard works on the subject, both books contain errors and omissions that deserve some public attention before the next editions appear. (The second edition of Undocumented DOS is due to appear in April 1993. It will be about 150 pages longer than the first edition, and will cover topics such as DR-DOS, the relation between Windows and undocumented DOS, and disassembly of the DOS kernel.)

Future "Undocumented Corners" might cover, for example, undocumented LAN Manager named-pipe functions available under DOS, the SmartDrive 4 "BABE" interface, the PIF file format, FDISK internals, walking the Windows VxD chain, undocumented DPMI, undocumented DR-DOS, and undocumented NT. (If you have NT and want a sneak preview, just run coff -dump -imports pview.exe.)

Of course, the usual cautions about relying on undocumented operating-system features apply. These features can change in the next version of the operating system, because the vendor is under no obligation to support them. On the other hand, the most useful undocumented features tend to get documented (look at Microsoft's MS-DOS Programmer's Reference for DOS 5, and the Get/SetSelectorBase/Limit functions in Windows 3.1), so knowing about and using these features may do nothing more dangerous than giving you a year or two jump on your more cautious competitors.

If you have material or a question for an "Undocumented Corner" column, please write to me on CompuServe at 76320,302, or send Internet mail to [email protected]. (I no longer work at Phar Lap Software, but the mail will get forwarded to me.) Articles for the "Undocumented Corner" will appear under your name, and you'll be paid for your contribution.

Our first column, on the RGNOBJ structure in Microsoft Windows, is a beautiful example of what we're looking for. Windows provides functions such as CreateEllipticRgn(), CreatePolygonRgn(), and CreateRectRgn() that return an HRGN, a handle to a region; functions such as CombineRgn() and PtInRegion () operate on these HRGN handles. Given the black-box "you can use a handle without knowing what it points to" philosophy of Windows, it's not surprising that the structure of a RGNOBJ, to which an HRGN points, is not documented.

Regions are maintained by the Windows graphics-device interface (GDI). If you turn to the entry for RGNOBJ in the GDI chapter in Undocumented Windows (pp. 589), this is all the enlightenment you'll receive:

This is the structure behind the documented HRGN handle. It is stored in GDI's default heap segment, and is made up of two parts. The first is the undocumented GDIOBJHDR structure described in the GDI object header entry earlier in this chapter [pp. 566-568], and the second, which contains the actual region data, is not currently understood.

In other words, a total cop out! Frankly, though, I was surprised by the large number of Undocumented Windows readers who wanted this information. We just didn't think it was that important, compared to the structures we did thoroughly cover, like the WND, DC, and TDB. After all, even in Quick-Draw on the Macintosh (which seems a lot better documented than Windows), Inside Macintosh simply says that the "region definition data for nonrectangular regions is stored in a compact way that allows for highly efficient access by QuickDraw routines," and leaves the rest to your imagination.

But treating regions as a black box works on the Macintosh because Quick-Draw provides a relatively complete set of operations that work on regions, including UnionRgn( ), DiffRgn( ), XorRgn( ), and the powerful MapRgn( ). (For a good description of Macintosh regions, see Howard Katz's "Region Maker," Byte, January 1987.)

Windows does not provide a complete set of region operations, and this is where the otherwise-excellent black-box idea (that you can use an HRGN without knowing what a RGNOBJ is) breaks down. Windows brings UnionRgn( ), DiffRgn ( ), and like operations under the umbrella of CombineRgn( ) with options like RGN_OR (union) and RGN_DIFF. But porting code from the Macintosh to Windows is quite difficult because Windows doesn't have MapRgn( ) -- and without knowing what a RGNOBJ looks like, your hopes of implementing MapRgn( ) for Windows are pretty low.

It turns out that the structure of the region-data bytes in a RGNOBJ are far from straightforward. Since CreateEllipticRgn( ) and CreateRectRgn( ) are called with nothing more than the two (x,y) bounding coordinates for the region, you might think there's nothing more here than a rectangle. However, creating some regions and then exploring the GDI local heap with Microsoft's HeapWalk utility, you can see that, whereas CreateRectRgn(10,20,100,200) generates a 42-byte object, CreateEllipticRgn (10,200,100,200) generates one that's 846 bytes! What the heck is going on? Several readers of Undocumented Windows got out their debuggers, disassemblers, and hip-waders, and figured out what a RGNOBJ actually looks like. Ed Andrews ([email protected]) did so by looking at how CombineRgn( ) works. Joseph Newcomer and Bruce Horn, whose article appears here, spent a long time figuring out that an HRGN is an LMEM_MOVABLE pointer into GDI's local heap. They then stared long and hard at the data. Note their comment below, complete with a great Knuth citation, about the need to test with unique values. Joe and Bruce's article is a fine combination of low-level, nitty-gritty detail with a high-level appreciation for important details.

--Andrew Schulman

Regions are one of the more mysterious graphics-device interface (GDI) objects in Microsoft Windows. Fundamentally, a region is an arbitrarily bounded area that can be used for a variety of purposes, such as filling, outlining, and clipping. However, one operation that is missing is the ability to scale a region, even though this ability is available on the Macintosh and in the X Window system. This means that multiplatform software that depends on this ability cannot be readily ported to Windows. We were confronted with exactly this problem, and therefore had to look "under the floor" to find out how Windows regions were implemented. This article tells what we discovered and what we did with it.

A note of caution: This code is highly sensitive to the current Windows 3.0/3.1 implementation. There's no guarantee that it will work in any future version of Windows, and in particular it will not work in Windows NT. The usual cautions of using undocumented features apply: Use this at your own risk! Perhaps in a future version of Windows, Microsoft will include a region-scaling operation and this hack will no longer be necessary.

Locating the GDI Data

The first problem we confronted was discovering exactly what a region looked like. Precisely where were the bits stored? Undocumented Windows (by Schulman, Maxey, and Pietrek, Addison-Wesley, 1992), hereafter referred to as UndocWin, describes in a less-than-useful fashion the key component of the RGNOBJ structure as "not currently understood" (pp. 589). We were therefore on our own in reverse-engineering the actual GDI data.

Several hours of tracing assembly code with Codeview began to reveal some of the truth. The critical question was how, given the HRGN region handle, to get an address for an actual region.

Regions are objects that change size. They are managed in a local storage heap of the Windows GDI module. However, because they can change size, GDI wants to be able to shift their position around, to allocate new storage and compact existing storage. The particular function that does this is GdiSeeGdiDo( ), described on page 570 of UndocWin. So an indirection table, similar to the Macintosh's master pointer block, is used. The region handle is the offset in a GDI data segment of the actual pointer to the region data. This pointer remains fixed during the entire lifetime of the region. As the contents of the region change (based on operations such as CombineRgn( )), the actual region data may shift position. In other words, the region handle is an LMEM_MOVEABLE pointer into the GDI heap. In Windows, LMEM_MOVEABLE is a pointer to a pointer, just like handles on the Macintosh (see UndocWin, pp. 305).

We first had to convert the handle to a far pointer, then use this far pointer to access another pointer, which was the pointer to the actual region data. This second pointer can be thought of as a handle to a movable chunk of storage that contains the actual region data. We thus have to convert this second handle to a global segment:offset pointer, simulating the effect of LocalLock.

As this work was begun in Windows 3.0, Microsoft's TOOLHELP.DLL was not yet available. Without ToolHelp, obtaining the segment portion of the address is one of those truly ugly and disgusting hacks that's always required when you're overreaching the interface specifications. The code we used depended critically upon the procedure entry sequence, documented in the Windows 3.1 SDK (Programmer's Reference: Volume 1, pp. 410). The prolog for an exported Windows library module procedure starts with the instruction movax,DGROUP. The code in Listing One (page 150) returns the segment of the GDI heap by extracting the DGROUP value from the actual instruction. The set of casts is singularly ugly C code.

As shown in Listing Two (page 150), it's much easier to get the segment of the GDI heap using ToolHelp. This code will work under either Windows 3.1 or 3.0, but in the case of 3.0 you would need to ship TOOLHELP.DLL along with your software.

This is all illustrated in Figure 1, where the user has an HRGN variable which has the value 0x003e. The GDI segment, hGDISegment (the name of the SYSHEAPINFO field; see Listing Two), is 0x0617. At location 0617:003e is the value 0x04ac. Thus, 0617:04ac is the far address of the RGNOBJ for this region. If the region were recomputed, such as by specifying it as the destination region of CombineRgn( ), the value at 0617:003e would most likely change to point to a newly allocated chunk of GDI heap memory for the newly computed region. The memory at location 0x4ac would be freed. This is all handled by the previously mentioned internal function GdiSeeGdiDo( ).

Having obtained the GDI segment, we can convert the region handle to a reference to the region data by extracting the near pointer at GDIseg:handle, as shown in Listing Three (page 150). DOS.H file provides the macros FP_SEG ( ) and FP_OFF( ).

Decoding the Region Data

Having carefully studied the way in which regions were created and combined, we knew that one of the WORDs was a length of the region data, which simplified some of the printout. We knew we only had to print out a fixed number of words to get the complete region displayed. Our examples were a set of examples of two rectangles, a hollow box formed from them, an L-shaped region formed from two rectangles, and an ellipse.

Our next job was to decode the region. The adventures here, including the false starts, guesses, and general staring at the code and data would make a great series of boring war stories told over suitable beverages, but we'll proceed directly to the final result, with only a brief diversion about reverse engineering.

Some years ago, Donald Knuth wrote a paper on the early Babylonian mathematicians' representation of algorithms ("Ancient Babylonian Algorithms," Communications of the ACM, July 1972, pp. 671-677; thanks to Jeremy Gibbons in New Zealand for this citation). They had no notion of algebra, nor of variables, but expressed the algorithms numerically. The art was to choose the initial values so that all intermediate values were unique; thus an integer represented the equivalent of a variable of that "name." Once the sequence of operations in terms of the numbers was memorized, the person executing the algorithm had only to associate the numbers representing the "variables" or "parameters" at each step with the actual numbers of the particular computation.

Whenever you are reverse-engineering something, it's important to similarly choose values that guarantee that, insofar as possible, you have unique values internally. For example, a rectangular region with coordinates {100, 100, 200, 200} would be an exceedingly poor choice, because not only would it not be possible to tell the X[0] coordinate from the Y[0] coordinate, or X[1] from Y[1], but if the internal representation happened to store width and height instead of absolute coordinates, the internal representation might be {100, 100, 100, 100}. The values we used were chosen so that not only were the values unique, but all differences in X and Y were also unique values.

The Representation of a Region

A region, we found, is stored as a sequence of horizontal stripes, kept in vertical order, which we call "Y-spans." Each Y-span contains a sequence of one or more rectangles, which we call "X-spans." [Editor's note: Ed Andrews uses the terminology "stripes" and "bands."] In a simple example, the difference between the two rectangular regions shown in Figure 2, an outer box and an inner box, creates the hollow box shown in Figure 3. Code to create the two boxes and the difference is shown in Listing Eight (page 150). Initially, each of the rectangular regions consists of a single Y-span defining one single rectangle. However, when they are combined, a new region is created.

On examining the region defining the hollow box, we find that the region consists of three Y-spans. The first is the top segment of the box and has a single X-span, labeled A. The second Y-span consists of two X-spans, one for the left edge of the box (B) and one for the right edge of the box (C). The third Y-span consists of the bottom segment of the box (D). This is shown in Figure 4.

A simple ellipse took 78 horizontal stripes. An example of an elliptical region is shown in Figure 6.

A region has a region header followed by the region data. The region header consists of:

  • A GDI object header [words 0..4]. This follows the format given as GDIOBJ-HDR in UndocWin, pp. 567. [Editor's note: This must be adjusted for the Debug version of Windows!]
  • The length of the region object, in words (includes the length of the header) [word 5].
  • The number of Y-spans [word 6].
  • What appears to be the length of the longest X-span data, in words [word 7].
  • The bounding box for the entire region, in the order left, top, right, bottom [words 8..11].
Following the region header, the region data appears. This consists of a sequence of Y-spans, as given by word 6 of the region header. Each Y-span has the following format, where the offsets are the offsets in the Y-span:

  • The number of coordinates nx in the X-spans (which is always an even number since each X-span has two coordinate values) [Y-span word 0].
  • The starting Y-coordinate of the Y-span [Y-span word 1].
  • The ending Y-coordinate of the Y-span [Y-span word 2].
  • The starting X-coordinate of X-span i (for i=O..nx/2-1) [Y-span word 3+2*i].
  • The ending X-coordinate of X-span i [Y-span word word 4+2*i].
  • A repeat of the number of X-spans in the region [Y-span word 3+nx].

Implementing the Region-mapping Function

We had a choice of how to implement region mapping. The X Window system implements two primitives, Xoffset-Region, which performs a linear transformation of the region, and XShrink-Region, which shrinks a region by specified integral amounts in the X and Y direction. The Macintosh Toolbox implements a much more general mechanism, MapRgn. We chose to implement the latter.

MapRgn( ) is defined as mapping a region based on the relationship of two rectangles, the source and destination rectangles. The vertical dimensions of the region are scaled according to the ratio of the destination-rectangle height to the source-rectangle height. The horizontal dimensions of the region are scaled according to the ratio of their widths. The final (absolute) position of the region is effectively the result placing the top-left corner of its bounding box of the region relative to the destination rectangle in the scaled relationship as the top-left corner of the unmapped region relative to the source rectangle.

This can be done by mapping each of the X,Y coordinates of the region according to the formula dst=(src-pos_src) * scale+pos_dst, where: dst is the value for x or y in the mapped region; src is the value for x or y in the source region; pos_src is the value for x or y of the top-left corner of the source rectangle; pos_dst is the value for x or y of the top-left corner of the destination rectangle; and scale represents the ratio of the destination-rectangle width or height to the corresponding source-rectangle dimension.

The dimensions of the rectangles are expressed as integer values. We have to deal with nonintegral ratios, such as a mapping of 0.7 or 1.2. The solution is obvious: We need to use floating-point computations. This means that to map the points, we need to convert each point in the region to floating point, perform a floating-point computation, and convert the result back to an integer. This is not particularly attractive from the view-point of performance, particularly if a math coprocessor is not available.

There is an alternative: fixed-point arithmetic. We convert each 16-bit integer to a 32-bit fixed-point number, where the high-order 16 bits are the integer part of the number and the low-order 16 bits are the fractional part of the number. We need to implement the following operations: convert integer to fixed point, convert fixed point to integer (rounded), multiply two fixed-point numbers generating a fixed-point result, and divide two integers, producing a fixed-point result.

A fixed-point number is described by the declaration typedef long tFixed;. An integer is converted to a fixed-point number with the ToFixed procedure. This simply shifts the integer left 16 bits. This is shown in Listing Four (page 150). Multiplication is performed by the computation: sign *(int(abs(num [1]) *frac (abs(num[2]))) + abs(num[1]) * int(abs (num[2]))) where: num[1] and num[2] are the two operands; abs(x) gives the integer absolute value of x; frac(x) gives the low-order 16 bits (the fraction part) of x; int(x) gives a 16-bit value which is the high-order 16 bits (the integer part) of x; and sign is -1 if the signs of num[1] and num[2] differ and 1 if they are the same.

The code to implement this is shown in Listing Five (page 150). The comments show that this will fail if the largest negative integer value, -32,768, appears as the integer part of either input value, because the absolute value is taken.

The ratio of two integers is computed by simply converting the numerator to a fixed-point number and dividing that number by the integral denominator, as shown in Listing Six (page 150).

The final conversion back to an integer (rounded) is shown in Listing Seven (page 150). Positive numbers are rounded up and negative numbers are rounded down; that is, rounding is done to the nearest integer value. One implication of this is that some rectangles may shrink to a 0 dimension in width or height. Such rectangles could be eliminated entirely from the region description, but such a change involves modifications to the region size. We're not certain of the implications of this with respect to the way regions are allocated; for example, Windows might not reclaim storage beyond what the region claims to own. We have therefore chosen to leave these collapsed rectangles in place. This incurs a minor performance penalty but does not seem to affect the rendering of regions. We could handle this with the GdiSeeGdiDo internal call if we really cared.

The MapRgn code is shown in Listing Nine (page 150). This code walks across the region structure and maps each point. In addition to mapping the points for each of the internal rectangles, it maps the bounding box. The general MapScalar procedure is used to map each of the points. The result of performing the mapping is shown in Figure 5. In this example, the larger rectangle on the left represents the source rectangle, and the smaller rectangle on the right represents the destination rectangle. The rectangles surrounding the ellipses represent the bounding box of the region. The grayscale shown in the illustration is displayed as color banding on the display. Note the proportional relationship of the source and destination rectangles to the shape of the ellipses.

Limitations of the Method

The region coordinates are stored as integers. Consequently, applying scaling several times to a region will tend to accumulate round-off errors. Scaling a smoothly curved region, such as an ellipse, or sloped regions, such as polygon regions, to a larger value will exhibit the usual effects of such scaling; the quantization that was appropriate for the smaller image will be magnified in the larger image, as shown in Figure 6. This technique will work best when applied to regions composed of rectangles.

This quantization will also become apparent when a rectangle shrinks to a 0 horizontal or vertical dimension. Scaling will leave these dimensions as 0, so the original shape may not be realizable; for example, scaling by 0.01 and then by 100 may not produce the original shape because some subregions shrank to 0 dimension.

Why This Representation?

You may be wondering: Why this particular implementation of regions? As we analyzed this data, we came up with some conjectures of the rationale for the particular representation chosen.

One representation that might be chosen is to keep the properties as abstract as possible; for example, when an elliptical region is created, to store the parameters of the elliptical region and mark the region type as elliptical; similarly for a rectangular region. A significant problem with this representation is the operations of intersection or difference on the regions; for example, imagine what it would take to represent an elliptical region with a rectangular hole in the middle of it. Four elliptical chords could do it, but these are expensive to compute in the first place and even more expensive to do subsequent operations with. The organization of rectangular banding seems a natural way to reduce the computational complexity.

The problem with this is, as we observed earlier, rectangular approximations to nonrectangular polygons (and the "infinite-sided polygon" elliptical regions) are only as accurate as the quantization. Making smaller (down to one pixel) bands gives the best approximation, but at the highest cost of storage; making larger bands reduces the number of rectangles, at the cost of accuracy. The Windows NT GDI represents regions as trapezoids, which allows for better approximations with fewer region segments, at a slightly higher computational cost.

Another representation would be a linked list of segments. One such representation is described in the paper "Scan-Line Coherent Shape Algebra," by Jonathan E. Steinhart (Graphics Gems II, James Arvo, ed., Academic Press, 1991). A region is represented not by rectangular segments, as described here, but as a linked list of horizontal spans. Each span contains its top Y-coordinate, a pointer to the next span, and a pointer to the segment list. The bottom Y coordinate is obtained from the top Y coordinate of the next span (and a discontinuity in the spans is thus represented as a span with no segments). The segment list is a linked list of segments ordered by X coordinates. In the paper Steinhart describes how to implement the operations of intersection, union, and translation.

As in the Steinhart paper, the Microsoft GDI maintains ordering in both Y and X. This serves two purposes. For one thing, it makes it easy to clip regions; you ignore all regions above your clipping region, and when you finally encounter a region whose top is below your clipping region, you can stop. Since the segments are maintained in X-coordinate order within each span, you can apply the same technique for clipping left and right; only the subregions partially intersecting the clipping region require computation. More importantly (especially when polygonal regions are used), it allows for a more efficient computation of the intersection, union, difference, XOR, and other operations because you can ignore certain computations outside the bounding box of the other region, and need only compute new rectangles in the overlapping areas. For example, in computing an intersection of two regions A and B, any rectangles in A that are outside the bounding box of B can be ignored, as can any rectangles in B that are outside the bounding box of A.

Why a vector instead of a linked list? We conjecture that this is because the GDI space is limited to a single 64K local heap; the vector representation is much more compact than a linked-list representation. We also conjecture that the repeated number of X-spans in the region (Y-span word 3+nx) is present to allow the structure to be traversed in a backward direction, for example, to locate the start of the previous Y-span.

One aspect of the representation we find dismaying is that it appears that coordinates are kept in the screen space rather than in a user-space coordinate system. This means that scaling a rectangle of a region so that it has a 0 dimension and then scaling it by a multiple greater than 1 means that it will not give the same effect as scaling the original to the same size; for example, scaling a region by 0.1 and then scaling the resulting region by 20 is not necessarily the same as scaling the original region by 2.

We suppose this was done because it has an impact only when scaling, and the absence of that operation made the more-general representation superfluous. Because Windows was originally committed to run efficiently on an 8088, and adding the necessary transformations to the internals of the region operations would have made these representations unacceptably inefficient. However, since this representation is an undocumented interface, there's no reason for Microsoft to maintain it across releases. Perhaps a future release of Windows could use a representation more amenable to scaling, and we could have a supported scale-region operation.

In conclusion, with some considerable effort we were able to enhance the Windows region operations with a new and very useful operation. It is the fond hope of the authors that such an operation will become part of the official Windows API in the future; but we were faced with the problem that we needed this operation and had no other choice but to add it ourselves.

Acknowledgments

We would like to acknowledge Maya Design Group and Jim Morris for permission to publish this work.


_UNDOCUMENTED CORNER_
edited by Andrew Schulman


[LISTING ONE]
<a name="00dd_000b">

WORD GetGDIsegment(void)
    {
     HRGN (FAR PASCAL * A)(int, int, int, int);
     unsigned short far * B;
     WORD GDIseg;  /* GDI segment */
     A = CreateRectRgn;
     B = (unsigned short far *)A; /* function prolog: MOV AX,DGROUP */
     ((char far *) B)++;
     GDIseg = *B;
     return GDIseg;
    }





<a name="00dd_000c">
<a name="00dd_000d">
[LISTING TWO]
<a name="00dd_000d">


#include <toolhelp.h>
WORD GetGDISegment(void)
    {
     SYSHEAPINFO info;
     info.dwSize = sizeof(info);
     SystemHeapInfo(&info);
     return info.hGDISegment;
    }





<a name="00dd_000e">
<a name="00dd_000f">
[LISTING THREE]
<a name="00dd_000f">

#include <dos.h>
static unsigned short far * ConvertRegionToAddress(HRGN region)
    {
     WORD GDIseg;
     unsigned short far * * p; /* pointer to GDI segment */
     unsigned short far *  pv; /* pointer to region data */
     GDIseg = GetGDIsegment();
     FP_SEG(p) = GDIseg;
     FP_OFF(p) = handle;
     pv = (unsigned short far *) *p;
     FP_SEG(pv) = GDIseg;
     return pv;
    }






<a name="00dd_0010">
<a name="00dd_0011">
[LISTING FOUR]
<a name="00dd_0011">

static tFixed ToFixed(int value)
    {
     return ((tFixed) value) << 16;
    }





<a name="00dd_0012">
<a name="00dd_0013">
[LISTING FIVE]
<a name="00dd_0013">

static tFixed tFixMul(tFixed num1, tFixed num2)
    {
     tFixed result;
     int    negated = false;
     unsigned short int2;
     unsigned short frac2;

    /* 'negated' is the sign of the result. We in effect store
    the sign bits. This will fail if we give it -32768.0000 */
     num1 = ((long) num1 < 0) ? negated = !negated, -num1 : num1;
     num2 = ((long) num2 < 0) ? negated = !negated, -num2 : num2;

     /* Extract the highword (integer part) and lowword (fraction
     part) of the second operand */
     int2 = (unsigned long) num2 >> 16;
     frac2 = (unsigned long) num2 & 0xFFFF;

     result=(((unsigned long) num1*frac2) >> 16)+((unsigned long) num1*int2);
     return (negated ? -result : result);
    }





<a name="00dd_0014">
<a name="00dd_0015">
[LISTING SIX]
<a name="00dd_0015">

static tFixed tFixRatio(int num, int denom)
    {
     tFixed     result;
     result = ((long) num << 16) / (long) denom;
     return result;
    }





<a name="00dd_0016">
<a name="00dd_0017">
[LISTING SEVEN]
<a name="00dd_0017">

static short tFixRound(tFixed num)
    {
     int negated = ((long) num < 0);
     short result  = negated ? (-num) >> 16 : num >> 16;
     unsigned short low = negated ? (-num) & 0xFFFF : num & 0xFFFF;
     if (negated)
        return -result - (low > 0x7FFF ? 1 : 0);
     else
        return  result + (low > 0x7FFF ? 1 : 0);
}






<a name="00dd_0018">
<a name="00dd_0019">
[LISTING EIGHT]
<a name="00dd_0019">

HRGN outer = CreateRectRgn(100,200,300,400);
HRGN inner = CreateRectRgn(110,210,290,390);
HRGN hollow = CreateRectRgn(0,0,0,0);
CombineRgn(hollow, outer, NULL, RGN_COPY);
CombineRgn(hollow, hollow, inner, RGN_DIFF);




<a name="00dd_001a">
<a name="00dd_001b">
[LISTING NINE]
<a name="00dd_001b">

static short MapScalar(short value, short srcOffset, short dstOffset,
    tFixed scale)
    {
     return tFixRound(tFixMul(scale, ToFixed(value-srcOffset))) + dstOffset;
    }
void MapRgn(HRGN rgn, RECT * srcRect, RECT * dstRect)
    {
     register short srcX = srcRect->left;
     register short srcY = srcRect->top;
     register short dstX = dstRect->left;
     register short dstY = dstRect->top;
     /* Compute the X-scale and Y-scale as the ratio of the
        length of the source and destination width and height */
     tFixed scaleX = tFixRatio( (dstRect->right - dstX),
                                (srcRect->right - srcX));
     tFixed scaleY = tFixRatio( (dstRect->bottom - dstY),
                                (srcRect->bottom - srcY));
     short ySpans;         // number of Y-spans in the region
     short yspan;          // current Y-span being processed
     short xSpans;         // number of X-spans in this Y-span
     short xspan;          // current X-span being processed
     short RgnLength;      // length of region, in words
     short * regionbase;   // base of region data
     short * scanC;        // current region word

     regionbase = ConvertRegionToAddress(rgn); // Point to beginning of region
     scanC = regionbase;            // start at base of region
     scanC += 5;                    // skip GDI header, point to region length
     if (GetSystemMetrics(SM_DEBUG) && (GetVersion() != 3))
         scanC += 2;                // adjust for 3.1+ Debug version!!
     RgnLength     = *scanC++;      // word 5: region length
     ySpans        = *scanC++;      // word 6: number of Y-spans
     scanC += 1;                    // Skip to bounding box.

     // Map bounding box for the region
     *scanC++ = MapScalar(*scanC, srcX, dstX, scaleX);     // left
     *scanC++ = MapScalar(*scanC, srcY, dstY, scaleY);     // top
     *scanC++ = MapScalar(*scanC, srcX, dstX, scaleX);     // right
     *scanC++ = MapScalar(*scanC, srcY, dstY, scaleY);     // bottom
     for (yspan = 0; yspan < ySpans; yspan++)
        { /* foreach y-span */
         xSpans = *scanC++ / 2;

         // Now we are pointing to the y start and y end points. Scale them.
         *scanC = MapScalar(*scanC, srcY, dstY, scaleY);   // startY
         scanC++;
         *scanC = MapScalar(*scanC, srcY, dstY, scaleY);   // endY
         scanC++;
         // We are pointing to the x limit pairs for this y-span.
         // Iterate down these, scaling each of them
         for (xspan = 0; xspan < xSpans; xspan++)
            { /* foreach x-span */
             // now we are pointing to the x start and end points. Scale them
             *scanC = MapScalar(*scanC, srcX, dstX, scaleX);  // startX
             scanC++;
             *scanC = MapScalar(*scanC, srcX, dstX, scaleX);  // endX
             scanC++;
            } /* foreach x-span */
         // The next word is the second copy of the number of X spans * 2.
         // Ignore it.
         scanC++;
        } /* foreach y-span */

}












Copyright © 1993, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.