Chapter 8 Video Page Management 110 Fastgraph User's Guide Overview The amount of memory required to store one full screen of information is called a video page. This chapter will discuss video pages in detail, along with the Fastgraph routines you can use to manage video pages. Physical Pages and Virtual Pages Pages that use the memory that resides on the video adapter are called physical pages or true pages. The number of physical pages available depends on the video mode and the amount of memory resident on the user's video adapter. All video modes have at least one physical page. In certain video modes, Fastgraph can allocate available random-access memory (RAM) and treat this memory as a video page. Pages that use standard RAM in this sense are called virtual pages. From a programmer's perspective, virtual pages are essentially identical to physical pages. The following table shows the number of physical pages in each video mode. It also indicates whether or not specific video modes support virtual pages. A discussion on how to calculate the listed video page sizes (that is, their memory requirements) was presented in chapter 2. Mode Page Size Physical Virtual Number Description in Bytes Pages Pages 0 40 column color text 2,000 8 no 1 40 column color text 2,000 8 no 2 80 column color text 4,000 4 no 3 80 column color text 4,000 4 no 4 320 x 200 CGA graphics 16,000 1 yes 5 320 x 200 CGA graphics 16,000 1 yes 6 640 x 200 CGA graphics 16,000 1 yes 7 80 column monochrome text 4,000 1 yes 9 320 x 200 Tandy graphics 32,000 1 yes 11 720 x 348 Hercules graphics 31,320 2 yes 12 320 x 200 Hercules graphics 31,320 2 yes 13 320 x 200 EGA graphics 32,000 8 no 14 640 x 200 EGA graphics 64,000 4 no 15 640 x 350 EGA mono graphics 56,000 2 no 16 640 x 350 EGA graphics 112,000 2 no 17 640 x 480 MCGA/VGA graphics 38,400 1 no 18 640 x 480 VGA graphics 153,600 1 no 19 320 x 200 MCGA graphics 64,000 1 yes 20 320 x 200 VGA graphics 64,000 4 no 21 320 x 400 VGA graphics 128,000 2 no The preceding table assumes the video adapter for EGA and VGA modes contains 256K bytes of video memory. For EGA adapters with less video memory, the number of physical pages is reduced proportionately. In other words, a 64K EGA has two video pages available instead of eight pages in mode 13. Chapter 8: Video Page Management 111 Physical pages are numbered starting at zero. For example, there are four physical video pages available in mode 3, and they are numbered 0 to 3. Virtual pages are numbered n to 15, where n is the number of physical pages in that mode. For example, there are two physical pages (numbered 0 and 1) and fourteen virtual pages (numbered 2 to 15) in mode 11. Note only modes 4 through 12 and mode 19 offer virtual pages, and the amount of available RAM in the user's system may limit the number of virtual pages Fastgraph may use (this is especially true in mode 19 because of the large page size). Pages With Special Meanings There are three video pages that have special meanings to Fastgraph. The visual page, as one might guess, is the video page currently visible on the user's display. The active page is the video page to which Fastgraph writes text or graphics information. The hidden page is meaningful only to a few Fastgraph routines and will be discussed specifically within the context of those routines. The fg_setmode routine sets all three of these pages to page 0, and it does not matter if these pages are physical or virtual. One of the most useful features of multiple video pages (either physical or virtual) is the ability to build a text or graphics image off screen (that is, on some video page besides the visual page). Then, once the image is ready, we can either transfer it to the visual page, or make the page on which the image resides the visual page. This feature is especially useful in animation, for it displays an image instantaneously instead of visibly updating the screen while producing the image. Some Simple Examples In this section, we will present six variations of a simple program that uses four video pages. The program fills each video page with a rectangle and then displays text containing the video page number in the center of each page. The first two examples run in a specific text or graphics video mode and only use physical pages. The next two examples also run in a specific text or graphics video mode, but they also use virtual pages. The final two examples are more general and run in several video modes. You could of course write a program that essentially does the same thing as the examples in this section without using multiple video pages. However, to use Fastgraph's image display and animation routines effectively, you must first understand the concept of video pages. Before proceeding, we must introduce the Fastgraph routines fg_setpage and fg_setvpage. The fg_setpage routine defines the active video page, which causes Fastgraph to put subsequent text and graphics output on that page. The fg_setvpage routine defines the visual video page displayed on the screen. Both routines take a single integer argument between 0 and 15 that specifies the video page number. It does not matter if the referenced video page is a physical page or a virtual page. As mentioned earlier, fg_setmode makes page 0 the active and visual video page. Example 8-1 uses four video pages (numbered 0 to 3) in the 40-column color text mode (mode 1). The program first calls fg_testmode to check the availability of the requested video mode when used with four video pages. If it is available, the program calls fg_setmode to establish that video mode. The first for loop fills each of the four pages with different color 112 Fastgraph User's Guide rectangles and then displays black text containing the video page number in the center of each page. It does this by calling fg_setpage to define the active video page, fg_setcolor and fg_rect to draw the colored rectangles, and finally fg_setattr, fg_locate, and fg_text to display the text. The program must call fg_locate inside the loop because each video page has its own text cursor position. The second for loop successively makes each video page the visual page; the page remains displayed until you press a key. After displaying all four video pages, the program restores the original video mode and screen attributes before returning to DOS. Example 8-1. #define PAGES 4 main() { int color; int old_mode; int page; char string[8]; if (fg_testmode(1,PAGES) == 0) { printf("This program requires color.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(1); for (page = 0; page < PAGES; page++) { fg_setpage(page); color = page + 1; fg_setcolor(color); fg_rect(0,fg_getmaxx(),0,fg_getmaxy()); fg_setattr(0,color,0); fg_locate(12,17); sprintf(string,"page %d",page); fg_text(string,6); } for (page = 0; page < PAGES; page++) { fg_setvpage(page); fg_waitkey(); } fg_setmode(old_mode); fg_reset(); } Example 8-2 is similar to example 8-1, but it uses the 320 by 200 EGA graphics mode (mode 13) instead of a text mode. Note the only real difference between this program and the text mode version is the use of fg_setcolor instead of fg_setattr to make the text appear in black. Chapter 8: Video Page Management 113 Example 8-2. #define PAGES 4 main() { int color; int old_mode; int page; char string[8]; if (fg_testmode(13,PAGES) == 0) { printf("This program requires a "); printf("320 x 200 EGA graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(13); for (page = 0; page < PAGES; page++) { fg_setpage(page); color = page + 1; fg_setcolor(color); fg_rect(0,fg_getmaxx(),0,fg_getmaxy()); fg_setcolor(0); fg_locate(12,17); sprintf(string,"page %d",page); fg_text(string,6); } for (page = 0; page < PAGES; page++) { fg_setvpage(page); fg_waitkey(); } fg_setmode(old_mode); fg_reset(); } Virtual video pages are created with Fastgraph's fg_allocate routine. The fg_allocate routine reserves random-access memory (RAM) which Fastgraph then treats as a video page. The amount of memory required depends on the current video mode. The fg_allocate routine takes a single integer argument that specifies the page number by which the virtual page will be referenced. This value must be between 0 and 15. If you attempt to create a virtual page with a page number already assigned to a physical page, fg_allocate does nothing. For example, in the Hercules graphics modes (modes 11 and 12) there are two physical pages numbered 0 and 1. Virtual pages in the Hercules graphics modes must thus have page numbers between 2 and 15. If you tell fg_allocate to create a Hercules virtual page numbered 0 or 1, it does nothing because the requested video page already exists as a physical page. Similarly, if you use the fg_allocate routine in a video mode that does not support virtual video pages, it simply returns without doing anything. 114 Fastgraph User's Guide A possible problem with fg_allocate can occur when there is not enough memory available for creating a virtual page in the current video mode. The fg_allocate routine returns as its function value a status code indicating whether or not it was successful. The possible values of the status code are: value meaning 0 virtual page created 7 virtual page created, but memory control blocks were destroyed 8 insufficient memory to create the virtual page If you use the fg_testmode or fg_bestmode routines to check if the required number of video pages are available when using the requested video mode, you should not need to monitor the status code returned by the fg_allocate routine. The fg_freepage routine releases the memory for a virtual page created with the fg_allocate routine. It requires a single integer argument that specifies the virtual page number to release. This value must be between 0 and 15. If you try to release a physical video page, or release a virtual page that was never created, fg_freepage does nothing. It is a good idea to use fg_freepage to release all virtual video pages before a program returns control to DOS, or just before a program selects a new video mode. Example 8-3 is also similar to example 8-1, but it uses the monochrome text mode (mode 7). Because the monochrome text mode only has one physical video page, we must use virtual video pages for page numbers 1, 2, and 3. Note how the fg_allocate and fg_freepage routines are used to create and release the virtual video pages in this example. Example 8-3. #define PAGES 4 main() { int old_mode; int page; char string[8]; if (fg_testmode(7,PAGES) == 0) { printf("This program requires monochrome.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(7); fg_cursor(0); for (page = 0; page < PAGES; page++) { fg_allocate(page); fg_setpage(page); fg_setcolor(7); Chapter 8: Video Page Management 115 fg_rect(0,fg_getmaxx(),0,fg_getmaxy()); fg_setattr(0,7,0); fg_locate(12,37); sprintf(string,"page %d",page); fg_text(string,6); } for (page = 0; page < PAGES; page++) { fg_setvpage(page); fg_waitkey(); fg_freepage(page); } fg_setmode(old_mode); fg_reset(); } Example 8-4 is similar to example 8-3, but it uses the standard Hercules graphics mode (mode 11) instead of the monochrome text mode. Because the Hercules graphics modes have two physical video pages, we must use virtual video pages for page numbers 2 and 3. Note the only real difference between this program and the text mode version is the use of fg_setcolor instead of fg_setattr to make the text appear in black. Example 8-4. #define PAGES 4 main() { int old_mode; int page; char string[8]; if (fg_testmode(11,PAGES) == 0) { printf("This program requires Hercules "); printf("monochrome graphics.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(11); for (page = 0; page < PAGES; page++) { fg_allocate(page); fg_setpage(page); fg_setcolor(7); fg_rect(0,fg_getmaxx(),0,fg_getmaxy()); fg_setcolor(0); fg_locate(12,37); sprintf(string,"page %d",page); fg_text(string,6); } for (page = 0; page < PAGES; page++) { fg_setvpage(page); 116 Fastgraph User's Guide fg_waitkey(); fg_freepage(page); } fg_setmode(old_mode); fg_reset(); } Example 8-5 is a generalized version of examples 8-1 and 8-3 that runs in any 80-column text video mode. To simplify the program, each video page is filled with rectangles of the same color. Note that fg_allocate and fg_freepage are used to manage the virtual video pages in case fg_bestmode selects the monochrome text mode (mode 7). If fg_bestmode selects one of the 80-column color text modes (which have four physical video pages), fg_allocate and fg_freepage will simply return without doing anything. Example 8-5. #define PAGES 4 main() { int old_mode, new_mode; int page; char string[8]; new_mode = fg_bestmode(80,25,PAGES); if (new_mode < 0) { printf("This program requires "); printf("an 80-column display.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_cursor(0); for (page = 0; page < PAGES; page++) { fg_allocate(page); fg_setpage(page); fg_setcolor(7); fg_rect(0,fg_getmaxx(),0,fg_getmaxy()); fg_setattr(0,7,0); fg_locate(12,37); sprintf(string,"page %d",page); fg_text(string,6); } for (page = 0; page < PAGES; page++) { fg_setvpage(page); fg_waitkey(); fg_freepage(page); } fg_setmode(old_mode); fg_reset(); } Chapter 8: Video Page Management 117 Example 8-6 is a generalized version of examples 8-2 and 8-4 that runs in any 320 by 200 graphics video mode. To simplify the program, each video page is filled with rectangles of the same color. As in example 8-5, fg_allocate and fg_freepage are used to manage the virtual video pages in case fg_bestmode selects a video mode with fewer than four physical video pages. Note the only real difference between this program and the text mode version is the use of fg_setcolor instead of fg_setattr to make the text appear in black. Example 8-6. #define PAGES 4 main() { int old_mode, new_mode; int page; char string[8]; new_mode = fg_bestmode(320,200,PAGES); if (new_mode < 0) { printf("This program requires a "); printf("320 x 200 graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); for (page = 0; page < PAGES; page++) { fg_allocate(page); fg_setpage(page); fg_setcolor(15); fg_rect(0,fg_getmaxx(),0,fg_getmaxy()); fg_setcolor(0); fg_locate(12,17); sprintf(string,"page %d",page); fg_text(string,6); } for (page = 0; page < PAGES; page++) { fg_setvpage(page); fg_waitkey(); fg_freepage(page); } fg_setmode(old_mode); fg_reset(); } 118 Fastgraph User's Guide Text Cursors As mentioned in the previous chapter, Fastgraph draws hardware characters at the position defined by the text cursor. Like the graphics cursor, the text cursor is not a cursor in the true sense, but is simply a pair of character space (row,column) coordinates with a special meaning. The first 8 video pages (that is, pages 0 through 7) each have their own text cursor. The last 8 video pages (pages 8 through 15) respectively share the same text cursor positions as the first 8 pages. This means the fg_locate routine will update one of 8 different text cursors depending on the active video page. Similarly, the fg_where routine returns the text cursor position for the active page. The fg_setmode routine sets all 8 text cursor positions to the character space coordinates (0,0). Example 8-7 demonstrates the use of different text cursors in an 80- column color text mode (mode 3). The program first displays the text "Page " on video page 0 (the visible page) and waits for a keystroke. It then makes page 1 the active video page, changes the text cursor location for that page, and displays the text "Page 1" on video page 1. Next, it appends the character "0" to the text originally displayed on page 0. Note it is not necessary to restore the text cursor position for page 0 because it is unaffected by changing the text cursor for page 1. After waiting for another keystroke, the program makes video page 1 the visual page and then waits for yet another keystroke before returning to DOS. Example 8-7. main() { int old_mode; old_mode = fg_getmode(); fg_setmode(3); fg_cursor(0); fg_setattr(10,0,0); fg_locate(1,0); fg_text("Page ",5); fg_waitkey(); fg_setpage(1); fg_locate(23,0); fg_text("Page 1",6); fg_setpage(0); fg_text("0",1); fg_waitkey(); fg_setvpage(1); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Chapter 8: Video Page Management 119 Obtaining Video Page Information Fastgraph includes two routines, fg_getpage and fg_getvpage, that respectively return the current active or visual video page number. Each routine returns the video page number as its function value, and neither routine requires any arguments. The fg_getaddr routine is sometimes useful when using virtual pages. It returns as its function value the segment address for the start of the active video page. It does not require any arguments. Although fg_getaddr is more useful when using virtual video pages, it works equally well when using physical video pages. Example 8-8 illustrates the use of the fg_getpage, fg_getvpage, and fg_getaddr routines in the standard CGA color graphics mode (mode 4). This video mode offers only one physical page, so the program uses fg_allocate to create a virtual video page (page 1). After creating the virtual page, the program makes it the active video page; page 0 remains the visual video page. The fg_getpage routine then returns the active page number (1), followed by a call to fg_getvpage to return the visual page number (0). Next, the program uses fg_getaddr to return the segment address for video pages 0 and 1. Finally, it restores the original video mode and screen attributes, displays the returned values, and returns to DOS. Example 8-8. main() { int old_mode; int active, visual; int page0, page1; old_mode = fg_getmode(); fg_setmode(4); fg_allocate(1); fg_setpage(1); active = fg_getpage(); visual = fg_getvpage(); fg_setpage(0); page0 = fg_getaddr(); fg_setpage(1); page1 = fg_getaddr(); fg_freepage(1); fg_setmode(old_mode); fg_reset(); printf("Active page is %d.\n",active); printf("Visual page is %d.\n",visual); printf("Page 0 address is %4X\n",page0); printf("Page 1 address is %4X\n",page1); } 120 Fastgraph User's Guide Considerations for Virtual Pages When you are using virtual pages, you should avoid using the fg_setvpage routine in sections of the program that require fast screen updates or animation sequences. This is because the PC and PS/2 video BIOS are only capable of displaying physical pages. To compensate for this restriction, Fastgraph exchanges the contents of a physical page with the requested virtual page. In other words, if page 1 is a virtual page and you make it the visual page, Fastgraph will exchange the contents of page 1 with whatever page was previously the visual page. This does not mean Fastgraph's page numbers change because Fastgraph also maintains an internal table containing video page addresses and also exchanges the two corresponding table entries. As before, you would make page 1 the active video page if you wanted to write something to the visual page. About the only other potential problem when using virtual pages is what happens when you attempt to write to a non-existent video page (for example, if you write to virtual video page 1 before creating it with fg_allocate). In this case, Fastgraph simply redirects the video output to the visual page. Summary of Video Page Management Routines This section summarizes the functional descriptions of the Fastgraph routines presented in this chapter. More detailed information about these routines, including their arguments and return values, may be found in the Fastgraph Reference Manual. FG_ALLOCATE creates a virtual video page. The amount of memory required depends on the current video mode. This routine has no effect if it references a physical video page. FG_FREEPAGE releases a virtual video page created with the fg_allocate routine. This routine has no effect if it references a physical video page, or a virtual page that was never created. FG_GETADDR returns the segment address of the active video page. FG_GETPAGE returns the active video page number. FG_GETVPAGE returns the visual video page number. FG_SETPAGE establishes the active video page. It may be a physical or virtual page. FG_SETVPAGE establishes the visual video page. It may be a physical or virtual page. Chapter 9 Images and Image Management 122 Fastgraph User's Guide Overview Within the context of Fastgraph, an image is a rectangular area of video memory containing some type of picture. An image might be something as simple as a pointing hand icon, or as detailed as the dashboard of a sports car. Fastgraph includes a number of routines to display, retrieve, and manipulate images, as well as transfer them between different areas of video memory. This chapter will discuss these routines in detail. The information presented here, combined with the video page management techniques described in the previous chapter, will provide the tools we need for sophisticated animation techniques. Mode-Independent Bit-Mapped Images This section will discuss the image display routines that use the same bit-mapped image format for all graphics video modes. Another class of routines, described in the next section, use different formats for different video modes. While these mode-independent image display routines are more general, they achieve this generality at the sake of some execution speed. This may especially be a concern if the image is large, or if speed is critical in an application (as in arcade-style graphics). For many programs, however, the mode-independent routines provide all the image display capability that is needed. Let's begin with an example of a very simple image. Suppose we need to display a small triangle whose perimeter is a different color than its interior. To use this image with Fastgraph, we must inscribe it in a rectangular area. Hence, the pixel representation of our triangle might appear as shown below. . . . . * . . . . . . . * x * . . . . . * x x x * . . . * x x x x x * . * * * * * * * * * As shown in this diagram, our triangle is 9 pixels wide at its base and 5 pixels high. The pixels indicated by an asterisk (*) are the triangle's perimeter, while those indicated by an x represent its interior points. We need to distinguish between these pixels because they will be different colors. The pixels shown as periods (.) are not part of the triangle itself. They are required to make the image rectangular, so from Fastgraph's perspective they are indeed part of the image. The Fastgraph routine fg_drawmap is a suitable routine for drawing our triangle. To use fg_drawmap, we must create a separate bit map for each color in the image (excluding the points used to fill the rectangular region, which is considered transparent). In this example, we will thus need two bit maps -- one for the perimeter points, and one for the interior points. Let's break the image into these two bit maps. Chapter 9: Images and Image Management 123 . . . . * . . . . . . . . . . . . . . . . * . * . . . . . . . x . . . . . . * . . . * . . . . . x x x . . . . * . . . . . * . . . x x x x x . . * * * * * * * * * . . . . . . . . . perimeter points interior points The next step is to convert these two bit maps into their binary representations. Just as there are eight bits in a byte, we will create a data structure (an array in this case) with each byte holding eight pixels. Bits that are set (1) indicate the corresponding pixel will appear displayed in the color associated with that bit map. Bits that are reset (0) leave the corresponding pixel unchanged. The size of each bit map array must be at least 10 bytes because each bit map contains five rows with nine pixels in each row (that is, two bytes are required for each row of the image). Hence, when we convert these bit maps to their binary representations, and subsequently to their hexadecimal equivalent, the results will appear as shown below. The bits displayed in boldface represent the actual image; the other bits are filler bits needed to complete each row of the bit maps after the ninth pixel. All filler bits must be zero. 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 08 00 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 14 00 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 22 00 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 41 00 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 FF 80 perimeter bit map 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 00 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 08 00 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 1C 00 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 3E 00 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 00 00 interior bit map The next question is the order in which the bit maps are stored in the corresponding data structures. Since our data structure is an array, it is only necessary to show the relationship of the subscripts to the bit map structures above. The next diagram shows the subscript order for the case of a two-column by five-row bit map. 124 Fastgraph User's Guide [8] [9] [6] [7] [4] [5] [2] [3] [0] [1] From this diagram, we see the first element of the array (that is, the element with subscript [0]) represents the lower left corner of the image. The subscript progression then continues right until reaching the end of the first row. It then resumes at the leftmost element of the second row and continues to the right until the end of that row. It continues in this manner for all remaining rows. We are now ready to present an example program to display our triangle. The program will use the Fastgraph routine fg_drawmap, which expects three arguments. The first argument is the bit map array (passed by reference), the second is the width of the bit map in bytes, and the last is the height of the bit map in pixel rows. The fg_drawmap routine displays the image such that its lower left corner is at the graphics cursor position on the active video page. The routine has no effect in text video modes. Additionally, fg_drawmap displays the image using the current color index, which means we will need to call fg_drawmap once for each color in the image. Example 9-1 runs in any 320 by 200 color graphics mode (it could be made to run in mode 12 too, but that would detract from the purpose of the example). After establishing the video mode, the program uses fg_rect to fill the entire screen with a gray rectangle (white in CGA). Next, the program establishes (156,101) as the graphics cursor position; this causes the triangle to be centered on the screen. The two calls to fg_drawmap, one for each of the colors in the image, actually display the triangle. Note especially how fg_setcolor is used before each call to fg_drawmap to define the current color index. The end result is a triangle with a blue perimeter (cyan in CGA) and green interior (magenta in CGA). Example 9-1. char perimeter[] = { 0xFF,0x80,0x41,0x00,0x22,0x00,0x14,0x00,0x08,0x00 }; char interior[] = { 0x00,0x00,0x3E,0x00,0x1C,0x00,0x08,0x00,0x00,0x00 }; main() { int old_mode, new_mode; new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); Chapter 9: Images and Image Management 125 exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_setcolor(7); fg_rect(0,319,0,199); fg_move(156,101); fg_setcolor(1); fg_drawmap(perimeter,2,5); fg_setcolor(2); fg_drawmap(interior,2,5); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } The different color bit maps used by fg_drawmap do not all have to be the same size. In our triangle example, the perimeter is 9 pixels wide by 5 pixels high, but the interior is only 5 pixels wide by 3 pixels high. Hence, the bit map for the interior pixels only requires one byte for each of its the three rows, so we can store it in a three-byte array. Its structure would be: [2] 08 [1] 1C [0] 3E Example 9-2 is similar to example 9-1, but it uses a three-byte array for the interior bit map. Note the second call to fg_move in this example. It is needed because the bottom row of the smaller interior bit map corresponds to the second row of the larger perimeter bit map. In other words, the interior bit map must be displayed one row above the perimeter bit map. Example 9-2. char perimeter[] = { 0xFF,0x80,0x41,0x00,0x22,0x00,0x14,0x00,0x08,0x00 }; char interior[] = { 0x3E,0x1C,0x08 }; main() { int old_mode, new_mode; new_mode = fg_bestmode(320,200,1); 126 Fastgraph User's Guide if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_setcolor(7); fg_rect(0,319,0,199); fg_move(156,101); fg_setcolor(1); fg_drawmap(perimeter,2,5); fg_move(156,100); fg_setcolor(2); fg_drawmap(interior,1,3); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } In example 9-2, the time required to execute the second call to fg_move may not be worth the saving of 7 bytes. When array space is critical, or when the images are larger, the use of smaller bit maps for certain colors may be more valuable. Yet another possibility for example 9-2 would be to shift the elements of the interior bit map two pixels to the left. In this way, the bit map would be aligned against the left side of the array, just as the perimeter bit map is. The three values comprising the interior bit map would then become F8, 70, and 20. We would also need to change the x coordinate in the second call to fg_move from 156 to 158. Mode-Specific Bit-Mapped Images This section will discuss the image display routines that use bit-mapped image formats that are specific to each text and graphics video mode. The different image formats closely resemble the structure of video memory in each mode, so these routines are much faster than displaying mode-independent bit maps with fg_drawmap. If you use the mode-specific bit maps in a program that supports several video modes, there will be some additional programming that is not needed when using mode-independent bit maps. In most cases, however, your efforts will be rewarded with considerably faster graphics. We'll demonstrate the use of mode-specific bit maps in graphics modes with the familiar two-color triangle whose pixel representation appears below. Chapter 9: Images and Image Management 127 . . . . * . . . . . . . * x * . . . . . * x x x * . . . * x x x x x * . * * * * * * * * * As before, our triangle is 9 pixels wide at its base and 5 pixels high. The pixels indicated by an asterisk (*) are the triangle's perimeter, while those indicated by an x represent its interior points. We need to distinguish between these pixels because they will be different colors. The pixels shown as periods (.) are not part of the triangle itself. They are required to make the image rectangular, so from Fastgraph's perspective they are indeed part of the image. Regular Images The Fastgraph routine fg_drwimage displays regular mode-specific bit- mapped images (by regular, we mean an image that is neither clipped nor rotated). Its arguments are identical to the fg_drawmap routine, and the bit map array's subscript order is also the same as for fg_drawmap. The major difference is the bit map structure -- only in the EGA and VGA video modes (modes 13 through 18) do we create separate bit maps for each color. In all other video modes, we combine the information for all colors into a single bit map. This is consistent with the structure and accessibility of video memory for the various modes. As in all of the other image display routines, fg_drwimage displays the image on the active video page with its lower left corner at the graphics cursor position (or the text cursor position for text modes). We'll now examine the use of fg_drwimage in several video modes. CGA four-color graphics modes In the four-color CGA graphics modes (modes 4 and 5), each pixel can assume a value between 0 and 3. This means it takes two bits to represent a pixel, or put another way, each byte of video memory holds four pixels. Our triangle image is nine pixels wide, so three bytes are needed for each row of the image. Because the image is five pixels high, we need a bit map array of at least 15 bytes (five rows times three bytes per row) to hold the image. The image's binary representation and its hexadecimal equivalent for the four-color CGA graphics modes are shown below. The binary values displayed in boldface represent the actual image; the other bits are the filler bits needed to complete each row of the bit map after the ninth pixel. We have coded the perimeter pixels to be color 1 (01 binary) and the interior pixels to be color 2 (10 binary). Any pixel whose value is zero (00 binary) is transparent and will thus leave the contents of video memory at that position unchanged. 128 Fastgraph User's Guide 00 00 00 00 01 00 00 00 00 00 00 00 00 40 00 00 00 00 01 10 01 00 00 00 00 00 00 01 90 00 00 00 01 10 10 10 01 00 00 00 00 00 06 A4 00 00 01 10 10 10 10 10 01 00 00 00 00 1A A9 00 01 01 01 01 01 01 01 01 01 00 00 00 55 55 40 Example 9-3 uses this mode-specific bit map to display the triangle in the standard CGA four-color graphics mode (mode 4). After establishing the video mode, the program uses fg_rect to fill the entire screen with a white rectangle. Next, the program establishes (156,101) as the graphics cursor position; this causes the triangle to be centered on the screen. The call to fg_drwimage produces a triangle with a cyan perimeter (color 1) and a magenta interior (color 2). Example 9-3. char triangle[] = { 0x55,0x55,0x40, 0x1A,0xA9,0x00, 0x06,0xA4,0x00, 0x01,0x90,0x00, 0x00,0x40,0x00 }; main() { int old_mode; if (fg_testmode(4,1) == 0) { printf("This program requires a 320 "); printf("x 200 CGA graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(4); fg_setcolor(7); fg_rect(0,319,0,199); fg_move(156,101); fg_drwimage(triangle,3,5); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } CGA two-color graphics mode In the two-color CGA graphics mode (mode 6), each pixel can assume the values 0 or 1. This means it takes just one bit to represent a pixel, so each byte of video memory holds eight pixels. Our triangle image is nine Chapter 9: Images and Image Management 129 pixels wide, so two bytes are needed for each row of the image. Because the image is five pixels high, we need a bit map array of at least 10 bytes (five rows times two bytes per row) to hold the image. The image's binary representation and its hexadecimal equivalent for the two-color CGA graphics mode is shown below. The binary values displayed in boldface represent the actual image; the other bits are the filler bits needed to complete each row of the bit map after the ninth pixel. We have coded both the perimeter pixels and the interior pixels to be color 1. Any pixel whose value is zero is transparent and will thus leave the contents of video memory at that position unchanged. 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 08 00 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 1C 00 0 0 1 1 1 1 1 0 0 0 0 0 0 0 0 0 3E 00 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 7F 00 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 FF 80 Example 9-4 uses this mode-specific bit map to display the triangle in the CGA two-color graphics mode (mode 6). After establishing the video mode, the program establishes (316,101) as the graphics cursor position; this causes the triangle to be centered on the screen. The call to fg_drwimage produces a solid triangle. Example 9-4. char triangle[] = { 0xFF,0x80, 0x7F,0x00, 0x3E,0x00, 0x1C,0x00, 0x08,0x00 }; main() { int old_mode; if (fg_testmode(6,1) == 0) { printf("This program requires a "); printf("CGA graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(6); fg_move(316,101); fg_drwimage(triangle,2,5); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } 130 Fastgraph User's Guide Tandy/PCjr 16-color graphics mode In the Tandy/PCjr 16-color graphics mode (mode 9), each pixel can assume a value between 0 and 15. This means it takes four bits to represent a pixel, so each byte of video memory holds two pixels. Our triangle image is nine pixels wide, so five bytes are needed for each row of the image. Because the image is five pixels high, we need a bit map array of at least 25 bytes (five rows times five bytes per row) to hold the image. In video mode 9, it is fairly easy to develop the hexadecimal representation of a bit map without first producing its binary equivalent. This is because a pixel value and a hexadecimal digit each occupy four bits. The triangle's hexadecimal representation for the 16-color Tandy/PCjr graphics mode is shown below. The pixels appearing in boldface represent the actual image; the others are the filler values needed to complete each row of the bit map after the ninth pixel. We have chosen to display the perimeter pixels in color 1 and the interior pixels in color 2. Any pixel whose value is zero is transparent and will thus leave the contents of video memory at that position unchanged. 00 00 10 00 00 00 01 21 00 00 00 12 22 10 00 01 22 22 21 00 11 11 11 11 10 Example 9-5 is similar to example 9-3, but it uses the Tandy/PCjr 16- color graphics mode (mode 9) and its mode-specific bit map just constructed to display the triangle. The call to fg_drwimage produces a triangle with a blue perimeter (color 1) and a green interior (color 2). Example 9-5. char triangle[] = { 0x11,0x11,0x11,0x11,0x10, 0x01,0x22,0x22,0x21,0x00, 0x00,0x12,0x22,0x10,0x00, 0x00,0x01,0x21,0x00,0x00, 0x00,0x00,0x10,0x00,0x00 }; main() { int old_mode; if (fg_testmode(9,1) == 0) { printf("This program requires a 320 "); printf("x 200 Tandy graphics mode.\n"); exit(); Chapter 9: Images and Image Management 131 } old_mode = fg_getmode(); fg_setmode(9); fg_setcolor(7); fg_rect(0,319,0,199); fg_move(156,101); fg_drwimage(triangle,5,5); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Hercules graphics modes The structure of the mode-specific bit maps for the Hercules graphics modes (modes 11 and 12) is identical to two of the CGA graphics modes. For the standard Hercules graphics mode (mode 11), please refer to the discussion of CGA two-color (mode 6) bit maps on page 128. For the low-resolution Hercules graphics mode (mode 12), please refer to the discussion of the CGA four-color (mode 4) bit maps on page 127. EGA and VGA graphics modes The structure of the mode-specific bit maps for the native EGA and VGA graphics modes (modes 13 through 18) is identical to the mode-independent bit map structure described in the previous section. It thus makes no difference if you use the fg_drwimage or fg_drawmap routines in these video modes. Please refer to page 122 for a discussion of mode-independent bit maps. MCGA and VGA 256-color graphics modes In the MCGA and VGA 256-color graphics modes (modes 19, 20, and 21), each pixel can assume a value between 0 and 255 (FF hex). This means it takes eight bits to represent a pixel, or each byte of video memory holds one pixel. Our triangle image is nine pixels wide, so nine bytes are needed for each row of the image. Because the image is five pixels high, we need a bit map array of at least 45 bytes (five rows times nine bytes per row) to hold the image. Note we will never need any filler bits in the 256-color video modes. In the 256-color graphics video modes, it is quite simple to develop the bit map for an image because each byte holds exactly one pixel. The triangle's hexadecimal representation for the 256-color graphics modes is shown below. As before, we have coded the perimeter pixels to be color 1 (01 hex) and the interior pixels to be color 2 (02 hex). Any pixel whose value is zero is transparent and will thus leave the contents of video memory at that position unchanged. 132 Fastgraph User's Guide 00 00 00 00 01 00 00 00 00 00 00 00 01 02 01 00 00 00 00 00 01 02 02 02 01 00 00 00 01 02 02 02 02 02 01 00 01 01 01 01 01 01 01 01 01 Example 9-6 is also similar to example 9-3, but it uses the MCGA 256- color graphics mode (mode 19) and its mode-specific bit map just constructed to display the triangle. The call to fg_drwimage produces a triangle with a blue perimeter (color 1) and a green interior (color 2). Example 9-6. char triangle[] = { 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, 0x00,0x01,0x02,0x02,0x02,0x02,0x02,0x01,0x00, 0x00,0x00,0x01,0x02,0x02,0x02,0x01,0x00,0x00, 0x00,0x00,0x00,0x01,0x02,0x01,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00 }; main() { int old_mode; if (fg_testmode(19,1) == 0) { printf("This program requires a 320 "); printf("x 200 MCGA graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(19); fg_setcolor(7); fg_rect(0,319,0,199); fg_move(156,101); fg_drwimage(triangle,9,5); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Text Modes You can also use the fg_drwimage routine to display images in text video modes (modes 0, 1, 2, 3, and 7). As one might expect, the image structure in Chapter 9: Images and Image Management 133 the text modes is rather different from the graphics modes. In chapter 5 we saw that each character cell on the screen actually consists of a character and an attribute. The character value determines what character is displayed, while the attribute value controls the character's appearance. The structure of the attribute is: bits attribute 0-3 foreground color 4-6 background color 7 blinking The text mode image structure used with fg_drwimage also consists of a series of characters and attributes. For example, the following diagram illustrates the structure of an image that is three characters wide and two characters high. char attr char attr char attr char attr char attr char attr To illustrate the use of fg_drwimage in a text video mode, we'll display the phrase "hello there" on two different lines in the center of the screen. Furthermore, let's assume we would like the first character of each word to appear in foreground color 1, the second in color 2, and so forth. Our image will consist of two lines each containing five characters, and each character requires two bytes of storage (one for the character and another for its attribute), so we'll need a 20-byte array for holding the image. The array really doesn't hold a bit map as in the graphics modes, so in the text modes the first argument passed to fg_drwimage is instead called the image array. In our example, the structure of the image array is: 'h' 1 'e' 2 'l' 3 'l' 4 'o' 5 't' 1 'h' 2 'e' 3 'r' 4 'e' 5 The subscript order that fg_drwimage uses for text modes is the same as for the graphics modes. For our five-row by two-column image, this means the array subscripts would be numbered as follows: [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] Depending on the character and attribute values in the image array, fg_drwimage can display new characters and attributes, new characters leaving the existing attribute unchanged, new attributes leaving the existing character unchanged, or leave both the existing character and attribute 134 Fastgraph User's Guide unchanged in video memory. To keep an existing character or attribute, simply specify a value of 0 in the corresponding element of the image array. This capability is analogous to the fact that zero-valued pixels in graphics mode bit maps leave video memory unchanged. Example 9-7 demonstrates the use of the fg_drwimage routine in the 80- column color text mode (mode 3). After establishing the video mode and making the cursor invisible, the program calls fg_drwimage to display the "hello there" image just discussed (note we pass the dimensions of the image array as the number of bytes, not the number of characters). The program waits for a keystroke and then calls fg_drwimage again, passing a different image array (called "image") of the same size. This array changes the first letter of both words from lower case to upper case (leaving the attribute unchanged), and it makes the remaining characters have the same attribute as the first character. This is done in part by using zero-valued characters and attributes to leave video memory unchanged. After waiting for another keystroke, the program exits. Example 9-7. char hello[] = { 't',1, 'h',2, 'e',3, 'r',4, 'e',5, 'h',1, 'e',2, 'l',3, 'l',4, 'o',5 }; char image[] = { 'T',0, 0,1, 0,1, 0,1, 0,1, 'H',0, 0,1, 0,1, 0,1, 0,1 }; main() { int old_mode; old_mode = fg_getmode(); fg_setmode(3); fg_cursor(0); fg_locate(12,37); fg_drwimage(hello,10,2); fg_waitkey(); fg_drwimage(image,10,2); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Clipped Images The fg_drwimage routine displays an image without regard to the current clipping limits. If you want the image to be displayed with respect to the clipping limits (as established by the most recent call to fg_setclip), you should use the fg_clpimage routine instead of fg_drwimage. Fg_clpimage takes the same three arguments as fg_drwimage, and likewise displays the image such Chapter 9: Images and Image Management 135 that its lower left corner is at the graphics cursor position. Unlike fg_drwimage, the fg_clpimage routine has no effect when used in a text video mode. Refer to pages 135 and 137 for example programs that use fg_clpimage. Because of the additional overhead involved in checking the clipping limits, fg_clpimage is not as fast as fg_drwimage. However, fg_clpimage does attempt to reduce this overhead by temporarily extending the horizontal clipping coordinates to a byte boundary in video memory. For example, the CGA two-color graphics mode (mode 6), the standard Hercules mode (mode 11), and the native EGA and VGA graphics modes (modes 13 through 18) store eight pixels in each byte of video memory. This means the byte boundaries occur at the horizontal coordinates 0, 8, 16, and so on. The fg_clpimage routine will extend the minimum horizontal (left) clipping limit to a byte boundary, and it will extend the maximum horizontal (right) clipping limit to the last pixel in a byte. That is, if the left clipping limit is at x=10 and the right clipping limit is at x=26, fg_clpimage will temporarily extend these values to x=8 and x=31 respectively. Please refer to page 158 for a further discussion of byte boundaries. Reversed Images The fg_revimage routine displays an image reversed (that is, mirrored about the y-axis). Fg_revimage takes the same three arguments as fg_drwimage, and likewise displays the image such that its lower left corner is at the graphics cursor position. The fg_revimage routine has no effect when used in a text video mode. Refer to pages 135 and 137 for example programs that use fg_revimage. Reversed Clipped Images The fg_flpimage routine combines the effects of the fg_revimage and fg_clpimage routines -- it displays a reversed image with respect to the current clipping limits. Fg_flpimage takes the same three arguments as fg_drwimage, and likewise displays the image such that its lower left corner is at the graphics cursor position. Like the fg_clpimage routine, fg_flpimage extends the clipping limits to a byte boundary to reduce the overhead involved in clipping, and it has no effect when used in a text video mode. Refer to pages 135 and 137 for example programs that use fg_flpimage. Some Examples Example 9-8 illustrates the use of the fg_drwimage, fg_clpimage, fg_revimage, and fg_flpimage routines in the standard CGA four-color graphics mode (mode 4). The program uses each of these routines to display a small white arrow, as shown in the pixel map below. . . . . . . * . . . . . . . . . * * . . * * * * * * * * * . * * * * * * * * * * * * * * * * * * * . . . . . . . * * . . . . . . . . * . . . 136 Fastgraph User's Guide As before, we must first convert this image to a bit map. The image is ten pixels wide and seven high. In mode 4, each pixel occupies two bits, so we need a 21-byte array (7 rows by 3 columns) to store the image. Since we want to make the arrow white, each pixel will be displayed in color 3 (11 binary). Here is the bit map and its hexadecimal equivalent for the arrow image in mode 4 (the actual image is in boldface). 00 00 00 00 00 00 11 00 00 00 00 00 00 0C 00 00 00 00 00 00 00 11 11 00 00 00 00 00 0F 00 11 11 11 11 11 11 11 11 11 00 00 00 FF FF C0 11 11 11 11 11 11 11 11 11 11 00 00 FF FF F0 11 11 11 11 11 11 11 11 11 00 00 00 FF FF C0 00 00 00 00 00 00 11 11 00 00 00 00 00 0F 00 00 00 00 00 00 00 11 00 00 00 00 00 00 0C 00 After establishing the video mode, the program defines the clipping region. It then uses fg_drwimage to display the arrow pointing to the right and fg_clpimage to do the same thing, but with respect to the clipping limits. Because the left edge of the arrow is displayed at x=10 and the right clipping limit is at x=15, the call to fg_clpimage only draws the first six columns of the arrow (that is, it does not draw the arrow head). Next, example 9-8 uses fg_revimage to display the arrow pointing to the left. To allow for the filler pixels, we must establish the graphics cursor position two pixels to the left of the position used by fg_drwimage if we want the tip of the left-pointing arrow to align with the tail of the right- pointing arrow. Finally, the program uses fg_flpimage to display an arrow pointing to the left with regard to the clipping limits. The call to fg_flpimage displays the arrow head and the first two columns of the arrow shaft. Example 9-8. char arrow[] = { 0x00,0x0C,0x00, 0x00,0x0F,0x00, 0xFF,0xFF,0xC0, 0xFF,0xFF,0xF0, 0xFF,0xFF,0xC0, 0x00,0x0F,0x00, 0x00,0x0C,0x00 }; main() { int old_mode; if (fg_testmode(4,1) == 0) { printf("This program requires a 320 "); printf("x 200 CGA graphics mode.\n"); exit(); } Chapter 9: Images and Image Management 137 old_mode = fg_getmode(); fg_setmode(4); fg_setclip(0,15,0,199); fg_move(10,10); fg_drwimage(arrow,3,7); fg_move(10,20); fg_clpimage(arrow,3,7); fg_move(8,30); fg_revimage(arrow,3,7); fg_move(8,40); fg_flpimage(arrow,3,7); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Example 9-9 is identical to example 9-8, but it uses the low resolution EGA graphics mode (mode 13). In the native EGA and VGA video modes, recall that fg_drwimage uses the same mode-independent bit map format as the fg_drawmap routine. Thus we need a 14-byte array (7 rows by 2 columns) to store the image. Here is the bit map and its hexadecimal equivalent for the arrow image in mode 13 (again, the actual image is in boldface). 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 02 00 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 03 00 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 FF 80 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 FF C0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 FF 80 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 03 00 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 02 00 Here is the program to display the arrows in mode 13. Note that we must define the color associated with the bit map in the native EGA and VGA graphics modes with a call to the fg_setcolor routine (the other video modes implicitly define the pixel colors in the bit map itself). Example 9-9. char arrow[] = { 0x02,0x00, 0x03,0x00, 0xFF,0x80, 0xFF,0xC0, 0xFF,0x80, 0x03,0x00, 0x02,0x00 }; main() { int old_mode; 138 Fastgraph User's Guide if (fg_testmode(13,1) == 0) { printf("This program requires a 320 "); printf("x 200 EGA graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(13); fg_setclip(0,15,0,199); fg_setcolor(15); fg_move(10,10); fg_drwimage(arrow,2,7); fg_move(10,20); fg_clpimage(arrow,2,7); fg_move(4,30); fg_revimage(arrow,2,7); fg_move(4,40); fg_flpimage(arrow,2,7); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Pixel Run Maps The bit maps used with the fg_drawmap, fg_drwimage, and related routines can consume array space quite rapidly. This is especially true if the image is large or contains many colors. For example, a mode-independent bit-mapped image that occupies the entire screen in a 320 by 200 graphics mode requires 8,000 bytes of space per color. Fastgraph provides another mode-independent image format called pixel run maps, which are more efficient in terms of space. In pixel run maps, you store the entire image in a single array. Pixel run maps are particularly useful for displaying static images such as backgrounds. Let's return to our familiar triangle example and show how we could use a pixel run map to display it. . . . . * . . . . . . . * x * . . . . . * x x x * . . . * x x x x x * . * * * * * * * * * As before, the pixels indicated by an asterisk (*) are the triangle's perimeter, while those indicated by an x represent its interior points. The pixels shown as periods (.) are not part of the triangle itself, but they are part of the pixel run map. If we start at the lower left corner of the image and proceed to the right, we could represent the first row of the image as nine pixels of color "asterisk". Such a group of consecutive identically colored pixels is called Chapter 9: Images and Image Management 139 a pixel run, so a single pixel run describes the first row of the image. The row above this one is a bit more complex. It consists of five pixel runs: one pixel of color "period", followed by one of color "asterisk", then five of color "x", one of color "asterisk", and finally one of color "period". While we could construct separate pixel runs for each row of the image, notice that three of the five rows in our triangle begin with the same color pixel as the rightmost pixel in the previous row. Fastgraph's pixel run map format lets you take advantage of this property by allowing pixel runs to wrap from one row to the next. This means we can represent the pixel run of color "period" extending from the right side of the second row to the left side of the third row as a single run of three pixels. The Fastgraph routine fg_display displays an image stored as a pixel run map. The fg_display routine expects three arguments. The first is an array containing the pixel runs (passed by reference), the second is the number of pixel runs in the array, and the third is the width in pixels of the image. As with the other image display routines, the fg_display routine displays the image such that its lower left corner is at the graphics cursor position on the active video page. The pixel run array is of the following format: [0] color for run 1 [1] count for run 1 [2] color for run 2 [3] count for run 2 . . . [2n-2] color for run n [2n-1] count for run n Each color is a value between 0 and 255 specifying the color index for that pixel run. Each count is a value between 0 and 255 specifying the length in pixels of that pixel run. If a run is longer than 255 pixels, it must be broken into two or more runs. For example, we could represent a pixel run of length 265 as a run of length 255 followed by a run of length 10 of the same color. Note also the array space in bytes needed to store a pixel run map is twice the number of runs. It requires 16 pixel runs to store our triangle image as a pixel run map. If we want to display the perimeter pixels in color 1, the interior pixels in color 2, and the filler area in color 7, the pixel run map would contain 16 sets of (color,count) pairs: (1,9), (7,1), (1,1), (2,5), (1,1), (7,3), (1,1), (2,3), (1,1), (7,5), (1,1), (2,1), (1,1), (7,7), (1,1), and (7,4). Example 9-10 uses the fg_display routine to display the triangle as a pixel run map in a 320 by 200 graphics mode. The program displays the triangle against a background of color 7, so the selection of color 7 for the 140 Fastgraph User's Guide filler area was important. If some other color were chosen, the filler area would not blend in with the background. Example 9-10. char triangle[] = { 1,9, 7,1, 1,1, 2,5, 1,1, 7,3, 1,1, 2,3, 1,1, 7,5, 1,1, 2,1, 1,1, 7,7, 1,1, 7,4 }; main() { int old_mode, new_mode; new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_setcolor(7); fg_rect(0,319,0,199); fg_move(156,101); fg_display(triangle,16,9); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } If you have a pixel run map that only uses the first 16 color indices (0 to 15), you can use Fastgraph's packed pixel run map image format. This format packs two color values into each color byte and thus needs 25% less array space to store an image. The Fastgraph routine fg_displayp displays an image stored as a packed pixel run map. It is identical in all respects to the fg_display routine except for the structure of the pixel run array. Like fg_display, the pixel run array used with fg_displayp is a list of pixel runs, but two runs are packed into three bytes. In each such set of three bytes, the high four bits of the first byte contain the color of the first run, and the low four bits contain the color of the second run. The second byte contains the length of the first run, and the third byte contains the length of the second run. The following diagram illustrates the format of the pixel run array used with the fg_displayp routine. The image is assumed to contain n pixel runs, where n is an even number. If n is odd, the index of the last element is 3n/2 (truncated) instead of 3n/2-1, and the low four bits of the last color byte (that is, the color for pixel run n+1) are ignored. Chapter 9: Images and Image Management 141 7 4 3 0 [0] color for run 1 color for run 2 [1] count for run 1 [2] count for run 2 [3] color for run 3 color for run 4 [4] count for run 3 [5] count for run 4 . . . [3n/2-3] color for run n-1 color for run n [3n/2-2] count for run n-1 [3n/2-1] count for run n The structure of the packed pixel run array allows for color values to be between 0 and 15, and pixel run lengths to be between 0 and 255. The array space in bytes needed to store a packed pixel run map is 1.5 times the number of runs, as compared to twice the number of runs for the standard pixel run format. Example 9-11 is identical to example 9-10, but it uses fg_displayp rather than fg_display to display the image. Note the use of hexadecimal numbers for defining the packed color values, which of course is not necessary but certainly easier to read than expressing the quantities as decimal numbers. Example 9-11. char triangle[] = { 0x17,9,1, 0x12,1,5, 0x17,1,3, 0x12,1,3, 0x17,1,5, 0x12,1,1, 0x17,1,7, 0x17,1,4 }; main() { int old_mode, new_mode; new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); 142 Fastgraph User's Guide fg_setcolor(7); fg_rect(0,319,0,199); fg_move(156,101); fg_displayp(triangle,16,9); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Both the fg_display and fg_displayp routines require the pixel run image to be stored in an array. In examples 9-10 and 9-11, the image is defined within the program itself. However, if the image is stored in a file, it must first be read into the pixel run array. Example 9-12 demonstrates this procedure. The program displays two images stored in files, one in standard pixel run format and the other in packed pixel run format. Each image is a picture of the sea floor and some coral, as might be used for the background in an aquarium. The program runs in a 320 by 200 graphics mode, and the image fills the entire screen. It is assumed the image files contain the list of pixel runs as a single byte stream that does not include embedded characters such as carriage returns or line feeds. The first image, in standard pixel run format, is in the file coral.spr. Note the program must open the file for reading in binary mode ("rb" in the call to fopen). The program reads the file's entire contents into the pixel_runs array, whose size must be at least as large as the file size. Because the image is stored in standard pixel run format, the number of pixel runs is one-half the file size. The program then uses the fg_move routine to establish the lower left corner of the screen as the graphics cursor position and then calls fg_display to display the image. As mentioned earlier, the image fills the entire screen, so its width is 320 pixels. After waiting for a keystroke, the program similarly displays the second image. This image is in the file coral.ppr and is stored in packed pixel run format. Because the image is packed, the number of pixel runs is two-thirds the file size. The program then clears the previous image from the screen and calls fg_displayp to display the image. After another keystroke, the program restores the original video mode and screen attributes and returns to DOS. Example 9-12. #include char pixel_runs[20000]; main() { long filelength(); FILE *stream; int file_size, run_count; int old_mode, new_mode; new_mode = fg_bestmode(320,200,1); Chapter 9: Images and Image Management 143 if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); stream = fopen("coral.spr","rb"); file_size = (int)(filelength(fileno(stream))); fread(pixel_runs,sizeof(char),file_size,stream); fclose(stream); run_count = file_size / 2; fg_move(0,199); fg_display(pixel_runs,run_count,320); fg_waitkey(); stream = fopen("coral.ppr","rb"); file_size = (int)(filelength(fileno(stream))); fread(pixel_runs,sizeof(char),file_size,stream); fclose(stream); run_count = file_size / 3 * 2; fg_erase(); fg_displayp(pixel_runs,run_count,320); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Another Fastgraph routine, fg_dispfile, displays an image directly from a file. This eliminates the need to read the file contents into an array before displaying the image, and it also eliminates the need to compute the number of pixel runs in the image. The fg_dispfile routine can display images stored in either standard or packed pixel run images. The first of its three arguments is the name of the file containing the image. The file name must be terminated with a null character, so QuickBASIC and FORTRAN programmers will need to store a zero byte as the last character of the file name string. The second argument is the width in pixels of the image, and the third argument defines the image format (that is, standard or packed). As with fg_display and fg_displayp, the fg_dispfile routine displays the image such that its lower left corner is at the graphics cursor position. Example 9-13 illustrates how to use the fg_dispfile routine to display an image stored in a file. It is functionally identical to example 9-12, but it is much simpler because it uses fg_dispfile instead of fg_display and fg_displayp to display the images. The value of fg_dispfile's third argument tells Fastgraph the image format. A value of 0 indicates the file contains an image in standard pixel run format, while a value of 1 indicates an image in packed pixel run format. As in example 9-12, the image files are assumed to contain the list of pixel runs as a single byte stream that does not include embedded characters such as carriage returns or line feeds. 144 Fastgraph User's Guide Example 9-13. main() { int old_mode, new_mode; new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_move(0,199); fg_dispfile("coral.spr",320,0); fg_waitkey(); fg_erase(); fg_dispfile("coral.ppr",320,1); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } To display the image, the fg_dispfile routine attempts to allocate enough dynamic memory to read the entire file. If it is unable to do so, it allocates the available memory and displays the image in more than one pass. In either case, Fastgraph deallocates the memory after fg_dispfile displays the image. The SNAPSHOT utility distributed with Fastgraph is a terminate and stay resident program (TSR) that can capture graphics mode screen images and save them in standard pixel run format files. Thus, you can easily create files with SNAPSHOT and display them with the fg_dispfile routine. Appendix A contains a complete description of the SNAPSHOT utility. Display Patterns Examples 9-11, 9-12, and 9-13 work well in the graphics video modes with 16 or 256 available colors. However, in the four-color CGA graphics modes the resulting image is not too good because of our limited color choices, and it would look even worse in the Hercules graphics mode. The Fastgraph routine fg_pattern allows you to associate a dither pattern (actually, any pixel sequence) with one of Fastgraph's 256 color indices appearing in a pixel run map. When displaying a pixel run map (with the fg_display, fg_displayp, or fg_dispfile routine), Fastgraph will use the pattern associated with that color index instead of displaying the color itself. The fg_pattern routine requires two integer arguments -- a color index (between 0 and 255) and the display pattern defined for that color index. A display pattern's structure resembles the structure of video memory and is thus dependent on the current video mode. The following sections list the Chapter 9: Images and Image Management 145 initial display patterns and explain how to construct new display patterns for different graphics video modes. CGA four-color graphics modes In the four-color CGA graphics modes (modes 4 and 5), the display pattern is a 16-bit quantity consisting of an 8-bit shift count followed by an 8-bit pixel pattern. Each pixel assumes a value between 0 and 3, so the pattern represents four pixels. In even-numbered pixel rows, Fastgraph uses the pixel pattern itself. In odd-numbered pixel rows, Fastgraph rotates the original pattern to the left by the number of bits specified by the shift count. For example, if we are using the default CGA color palette, we could create a lighter shade of cyan by alternating cyan pixels (color 1, 01 binary) with white pixels (color 3, 11 binary), as shown below. 01 11 01 11 If we convert this pixel pattern to its hexadecimal equivalent, we obtain the value 77. To complete the display pattern, we need to determine the shift count. If we use a shift count of zero, the resulting display will simply be a series of cyan and white vertical lines. What we really need is a checkerboard effect where a white pixel is above and below each cyan pixel, and vice versa. If we rotate the pattern one pixel (two bits) to the left, we will obtain the desired effect. That is, a shift count of two produces the following pixel patterns: even-numbered rows 01 11 01 11 odd-numbered rows 11 01 11 01 Combining the shift count with the pixel pattern yields the display pattern 0277 hex. The shift count is normally a multiple of two; note that a zero shift count results in the same pattern being applied to all pixel rows. For the CGA four-color graphics modes, the fg_setmode routine establishes the following initial display patterns: color shift count hexadecimal index and pattern equivalent 0 0 00000000 0000 1 0 01010101 0055 2 0 10101010 00AA 3 0 11111111 00FF These values are repeated as necessary to define color indices 4 to 255. That is, colors 4, 8, 12, ... , 252 use the same defaults as color 0. Colors 5, 9, 13, ... , 253 use the same defaults as color 1, and so forth. Also note that pattern 0000 represents four pixels of color 0, 0055 represents 146 Fastgraph User's Guide four pixels of color 1, 00AA represents four pixels of color 2, and 00FF represents four pixels of color 3. CGA two-color graphics mode In the two-color CGA graphics mode (mode 6), the display pattern is also a 16-bit quantity consisting of an 8-bit shift count followed by an 8-bit pixel pattern. Each pixel assumes the value 0 or 1, so the pattern represents eight pixels. In even-numbered pixel rows, Fastgraph uses the pixel pattern itself. In odd-numbered pixel rows, Fastgraph rotates the original pattern to the left by the number of bits specified by the shift count. For example, we could create a lighter shade of white by alternating black pixels (color 0) with white pixels (color 1), as shown below. 0 1 0 1 0 1 0 1 If we convert this pixel pattern to its hexadecimal equivalent, we obtain the value 55. To complete the display pattern, we need to determine the shift count. We must rotate the pattern one pixel (one bit) to the left to obtain the checkerboard effect as in the CGA four color graphics modes. That is, a shift count of one produces the following pixel patterns: even-numbered rows 0 1 0 1 0 1 0 1 odd-numbered rows 1 0 1 0 1 0 1 0 Combining the shift count with the pixel pattern yields the display pattern 0155 hex. For the CGA two-color graphics mode, the fg_setmode routine establishes the initial display patterns such that all even-numbered color indices are assigned the value 0000, while all odd-numbered color indices are assigned the value 00FF. Note that pattern 0000 represents eight pixels of color 0, and 00FF represents eight pixels of color 1. Tandy/PCjr 16-color graphics mode In the Tandy/PCjr 16-color graphics mode (mode 9), the display pattern is also 16-bit quantity consisting of an 8-bit shift count followed by an 8- bit pixel pattern. Each pixel assumes a value between 0 and 15, so the pattern represents two pixels. In even-numbered pixel rows, Fastgraph uses the pixel pattern itself. In odd-numbered pixel rows, Fastgraph rotates the original pattern to the left by the number of bits specified by the shift count. For example, we could create a lighter shade of blue by alternating blue pixels (color 1, 0001 binary) with white pixels (color 15, 1111 binary), as shown below. 0001 1111 Chapter 9: Images and Image Management 147 If we convert this pixel pattern to its hexadecimal equivalent, we obtain the value 1F. To complete the display pattern, we need to determine the shift count. Using the same process as in the CGA graphics modes, we must rotate the pattern one pixel (four bits) to the left to obtain the checkerboard effect. That is, a shift count of four produces the following pixel patterns: even-numbered rows 0001 1111 odd-numbered rows 1111 0001 Combining the shift count with the pixel pattern yields the display pattern 041F hex. The shift count is normally zero or four; note that a zero shift count results in the same pattern being applied to all pixel rows. For the Tandy/PCjr 16-color graphics modes, the fg_setmode routine establishes the initial display patterns such that color 0 is assigned the value 0000 (two pixels of color 0), color 1 is assigned the value 0011 (two pixels of color 1), color 2 is assigned the value 0022 (two pixels of color 2), and so forth. These values are repeated as necessary to define color indices 16 to 255. That is, colors 0, 16, 32, ... , 240 use the same defaults as color 0. Colors 1, 17, 33, ... , 241 use the same defaults as color 1, and so forth. Hercules graphics modes The structure of the display patterns for the Hercules graphics modes (modes 11 and 12) is identical to two of the CGA graphics modes. For the standard Hercules graphics mode (mode 11), please refer to the discussion of CGA two-color (mode 6) display patterns on page 146. For the low-resolution Hercules graphics mode (mode 12), please refer to the discussion of the CGA four-color (mode 4) display patterns on page 145. EGA graphics modes In the EGA graphics modes (modes 13 to 16), the display pattern is an 8- bit quantity consisting of two 4-bit color values (for consistency with the other video modes, we still pass the display pattern as a 16-bit quantity). Each pixel assumes a value between 0 and 15 (0 and 5 in the EGA monochrome graphics mode), so the pattern represents two pixels. In even-numbered pixel rows, Fastgraph uses the pixel pattern itself. In odd-numbered pixel rows, Fastgraph rotates the original pattern one pixel (four bits) to the left. For example, we could create a lighter shade of blue by alternating blue pixels (color 1, 0001 binary) with white pixels (color 15, 1111 binary), as shown below. 0001 1111 If we convert this pixel pattern to its hexadecimal equivalent, we obtain the value 1F. The implied four-bit shift count produces the following pixel patterns: 148 Fastgraph User's Guide even-numbered rows 0001 1111 odd-numbered rows 1111 0001 Extending the pixel pattern to a 16-bit quantify yields the display pattern 001F hex. For the EGA and VGA 16-color graphics modes, the fg_setmode routine establishes the initial display patterns such that color 0 is assigned the value 0000 (two pixels of color 0), color 1 is assigned the value 0011 (two pixels of color 1), color 2 is assigned the value 0022 (two pixels of color 2), and so forth. These values are repeated as necessary to define color indices 16 to 255. That is, colors 0, 16, 32, ... , 240 use the same defaults as color 0. Colors 1, 17, 33, ... , 241 use the same defaults as color 1, and so forth. MCGA/VGA 2-color graphics mode In the two-color MCGA/VGA graphics mode (mode 17), the display pattern is a 2-bit quantity consisting of two 1-bit color values (for consistency with the other video modes, we still pass the display pattern as a 16-bit quantity). Each pixel assumes the value 0 or 1, so the pattern represents two pixels. In even-numbered pixel rows, Fastgraph uses the pixel pattern itself. In odd-numbered pixel rows, Fastgraph rotates the original pattern one pixel (one bit) to the left. For example, we could create a lighter shade of white by alternating black pixels (color 0) with white pixels (color 1), as shown below. 0 1 If we convert this pixel pattern to its hexadecimal equivalent, we obtain the value 01. The implied one-bit shift count produces the following pixel patterns: even-numbered rows 0 1 odd-numbered rows 1 0 Extending the pixel pattern to a 16-bit quantity yields the display pattern 0001 hex. For the MCGA/VGA two-color graphics mode, the fg_setmode routine establishes the initial display patterns such that all even-numbered color indices are assigned the value 0000 (two pixels of color 0), while all odd- numbered color indices are assigned the value 0003 (11 binary, or two pixels of color 1). VGA 16-color graphics mode The structure of the display patterns for the VGA 16-color graphics mode (mode 18) is identical to that of the EGA graphics modes. A discussion of EGA display patterns appears on page 147. Chapter 9: Images and Image Management 149 MCGA and VGA 256-color graphics modes The MCGA and VGA 256-color graphics modes (modes 19, 20, and 21) offer 262,144 different colors, so dithering is seldom (if ever) required. For this reason, the fg_pattern routine has no effect in these video modes. An example Example 9-14 illustrates the use of display patterns in several graphics modes. This program runs in any 320 by 200 color graphics mode and displays the coral image in packed pixel run format, as in example 9-13, but it redefines one or more of the color indices. If the program runs in the standard CGA four-color mode (mode 4), it redefines the first 16 display patterns using the fg_pattern routine and the values in the CGApatterns array. In the Tandy/PCjr 16-color graphics mode (mode 9) and the EGA low- resolution graphics mode (mode 13), the program redefines color index 15 to produce an alternating gray and bright white dither pattern. In the MCGA 256-color mode (mode 19), display patterns are not available, so the program uses fg_setrgb to define color index 15 as slightly darker shade of gray than the default for color 7. Example 9-14. int CGApatterns[] = { 0x0000,0x00FF,0x00FF,0x00FF, 0x02BB,0x0000,0x0222,0x0255, 0x00FF,0x00FF,0x00FF,0x0055, 0x00AA,0x00AA,0x00FF,0x0277 }; main() { int color; int old_mode, new_mode; new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); if (new_mode == 4) { fg_palette(0,0); for (color = 0; color < 16; color++) fg_pattern(color,CGApatterns[color]); } else if (new_mode == 9 || new_mode == 13) fg_pattern(15,0x04F7); else fg_setrgb(15,38,38,38); fg_move(0,199); 150 Fastgraph User's Guide fg_dispfile("coral.ppr",320,1); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Masking Maps When you are using the CGA, Tandy/PCjr, Hercules, or MCGA graphics video modes, it is not possible to include color 0 pixels in an image displayed with the fg_drwimage, fg_clpimage, fg_revimage, or fg_flpimage routines. This is because these routines consider color 0 pixels to be transparent, which means such pixels do not affect the corresponding pixels in video memory. There are times, however, when you will want color 0 pixels to be destructive, or replace the video memory contents. Consider again the arrow image of example 9-8 (see page 135). In this example, we displayed a bright white (color 3) arrow against a black (color 0) background in the standard CGA four-color graphics mode. Suppose, though, that we want to do just the opposite -- display a black (color 0) arrow against a bright white (color 3) background. Example 9-9 (see page 137) does this in an EGA graphics mode, but how would we display the black arrow in a CGA graphics mode? We could of course use the fg_drawmap routine or one of the routines for displaying pixel run maps, but fg_drawmap does not support clipping or reversing an image. There are, however, four Fastgraph routines designed just for this purpose. These routines are fg_drawmask, fg_clipmask, fg_revmask, and fg_flipmask. Each of these routines uses a data structure called a masking map. A masking map is similar in structure to a pixel run map, but it does not include any information about colors. Instead, it consists of a series of pixel runs that alternate between protected and unprotected pixels. An example might best clarify this. Once again, here is the arrow image of examples 9-8 and 9-9. . . . . . . * . . . . . . . . . * * . . * * * * * * * * * . * * * * * * * * * * * * * * * * * * * . . . . . . . * * . . . . . . . . * . . . This time, though, we want the arrow to appear in color 0. Put another way, we need the "period" pixels (.) to protect video memory, while we want the "asterisk" pixels (*) to zero video memory. Looking at this problem from the perspective of a pixel run map, we have an alternating series of "protect" and "zero" runs. We don't need any information about pixel colors, just whether to protect or to zero video memory. This is precisely the structure of a masking map. Starting from the lower left corner of the image and proceeding to the right, wrapping up to the next row when needed, we could represent this image as a masking map with Chapter 9: Images and Image Management 151 6 protected pixels, 1 zeroed pixel, 9 protected pixels, 2 zeroed pixels, and so on. In general, the structure of a masking map is as follows. [1] length of 1st protect run [2] length of 1st zero run [3] length of 2nd protect run [4] length of 2nd zero run . . . [n-2] length of final protect run [n-1] length of final zero run Looking at this diagram, we see that the even-numbered array elements hold the length of the "protect" runs, and the odd-numbered elements hold the length of the "zero" runs. If you need the first run to be a "zero" run, just include a "protect" run of length zero as the first element of the array. If the final run is a "protect" run, you do not need to include a zero-length "zero" run at the end of the array. Finally, if either type of run exceeds 255 pixels, you'll need to split this into two or more pixel runs. In this case, be sure to include a zero-length run of the other type between the two array elements. Example 9-15 illustrates the use of a masking map through the fg_drawmask, fg_clipmask, fg_revmask, and fg_flipmask routines in the standard CGA four-color graphics mode (mode 4) to draw a black (color 0) arrow against a bright white background. These four routines are respectively analogous to the fg_drwimage, fg_clpimage, fg_revimage, and fg_flpimage routines, but they use masking maps rather than bit maps. The first argument of each routine is the masking map array (passed by reference), the second argument is the number of runs (that is, the number of elements) in the masking map array, and the third argument is the width in pixels of the image. Example 9-15. char arrow[] = {6,1,9,2,2,9,1,19,7,2,8,1}; main() { int old_mode; if (fg_testmode(4,1) == 0) { printf("This program requires a 320 "); printf("x 200 CGA graphics mode.\n"); exit(); } old_mode = fg_getmode(); 152 Fastgraph User's Guide fg_setmode(4); fg_setclip(0,15,0,199); fg_setcolor(3); fg_rect(0,319,0,199); fg_move(10,10); fg_drawmask(arrow,12,10); fg_move(10,20); fg_clipmask(arrow,12,10); fg_move(10,30); fg_revmask(arrow,12,10); fg_move(10,40); fg_flipmask(arrow,12,10); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } One of the more useful features of masking maps is the ability to clear a portion of video memory before placing an image there. This technique provides an efficient, simple way to include color 0 pixels in an image. It is especially effective when displaying large or dithered images because the masking map is typically much smaller than the bit map required by fg_drawmap or its related routines. Example 9-16 illustrates this process in the standard CGA four-color graphics mode (mode 4) by displaying our arrow image against a colored background. In this example, the arrow has a bright white (color 3) perimeter and a black (color 0) interior. The program displays the arrow in two steps. It first uses fg_drawmask to clear the video memory where the arrow will be displayed. It then draws the arrow's perimeter using the fg_drwimage routine. The interior pixels in the perimeter bit map are transparent, but since we just zeroed that video memory, they appear in color 0. Note we could improve this example by creating a smaller masking map that only applies to the rectangle inscribing the arrow's interior. That is, we don't need to zero the video memory under the arrow's perimeter because we will immediately display other pixels there. Example 9-16. char arrow_white[] = { 0x00,0x0C,0x00, 0x00,0x0F,0x00, 0xFF,0xFC,0xC0, 0xC0,0x00,0x30, 0xFF,0xFC,0xC0, 0x00,0x0F,0x00, 0x00,0x0C,0x00 }; char arrow_black[] = {6,1,9,2,2,9,1,19,7,2,8,1}; main() { int old_mode; if (fg_testmode(4,1) == 0) { printf("This program requires a 320 "); printf("x 200 CGA graphics mode.\n"); Chapter 9: Images and Image Management 153 exit(); } old_mode = fg_getmode(); fg_setmode(4); fg_setcolor(2); fg_rect(0,319,0,199); fg_move(10,10); fg_drawmask(arrow_black,12,10); fg_drwimage(arrow_white,3,7); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Retrieving Images Sometimes it is necessary to retrieve an image from video memory and store it in one or more bit map arrays. Fastgraph includes two routines, fg_getmap and fg_getimage, for this purpose. The fg_getmap routine retrieves pixels of the current color index and stores them in the mode-independent bit map format used by fg_drawmap. The fg_getimage routine retrieves an image and stores it in the mode-specific bit map format used by fg_drwimage, fg_clpimage, fg_revimage, and fg_flpimage. The arguments to fg_getmap and fg_getimage are respectively analogous to those of fg_drawmap and fg_drwimage: the first is an array (passed by reference) to receive the bit map, the second is the width of the bit map in bytes, and the last is the height of the bit map in pixel rows. With either routine, the graphics cursor position on the active video page defines the lower left corner of the image to retrieve. If we are using the fg_getmap routine (or the fg_getimage routine in any of the native EGA and VGA graphics video modes) to retrieve an image containing more than one color, we must call the routine once per color. In this case we'll usually want to pass different bit map arrays to fg_getmap or fg_getimage (or perhaps different offsets into the same array). This might seem unusual at first, but it parallels the behavior of the fg_drawmap and fg_drwimage routines. That is, to display a multicolor image using fg_drawmap, we must call fg_drawmap once for each color in the image. The same holds true if we are using fg_drwimage in the native EGA and VGA graphics video modes, because in these modes the mode-specific bit map structure is identical to the mode-independent structure. Example 9-17 demonstrates a typical use of the fg_getmap routine. The program displays the word "text" in the upper left corner of the screen using a 320 by 200 graphics mode. It uses fg_getmap to retrieve the word as an image and then displays it in a new position with the fg_drawmap routine. Let's look at the program now, and afterward we'll more closely examine the screen coordinates and the structure of the bit map array. 154 Fastgraph User's Guide Example 9-17. main() { char bitmap[32]; int old_mode, new_mode; new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_setcolor(9); fg_text("text",4); fg_waitkey(); fg_move(0,7); fg_getmap(bitmap,4,8); fg_move(4,15); fg_drawmap(bitmap,4,8); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } In all 320 by 200 graphics video modes, individual characters are 8 pixels wide and 8 pixels high. This means the lower left corner of the (0,0) character cell is referenced by the screen coordinates (0,7). Hence, these are the coordinates of the first call to fg_move. The image retrieved in example 9-17 is four characters long (32 pixels wide), so we need a bit map array capable of holding 8 rows of 32 pixels (4 bytes) each. Our bit map array is therefore a 32-byte array, logically structured to have 4 columns and 8 rows. These values are the width and height arguments passed to fg_getmap and fg_drawmap. After it retrieves the image, example 9-17 displays it one line below and one-half character cell (four pixels) to the right of its original position. In other words, the program displays the image four pixels to the right of the (1,0) character cell. The lower left corner of that cell is referenced by the screen coordinates (0,15), so the image should appear at the position (4,15). These are the coordinates of the second call to fg_move. Example 9-18 illustrates the use of the fg_getmap and fg_drawmap routines to retrieve and display two-color image. This example is similar to example 9-17, but this program first draws a rectangle in the upper left corner of the screen and then displays the word "text" on top of the rectangle in a different color. Each character in a 320 by 200 graphics video mode is 8 pixels wide and 8 pixels high, so the rectangle must be 32 pixels wide (4 characters times 8 pixels per character) and 8 pixels high. The image to retrieve will be the same size as the rectangle. Chapter 9: Images and Image Management 155 The image retrieved in example 9-17 required a 32-byte array, logically structured to have 4 columns and 8 rows. Example 9-18 will retrieve an image of the same structure, but the image contains two colors instead of just one. This means we need two 32-byte arrays, one for each color, to hold the image. We could instead use a single 64-byte array and pass an offset into that array (specifically, &bitmap[32]) for processing the second color. Example 9-18. main() { char bitmap1[32], bitmap2[32]; int old_mode, new_mode; new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_setcolor(7); fg_rect(0,31,0,7); fg_setcolor(9); fg_text("text",4); fg_waitkey(); fg_move(0,7); fg_setcolor(7); fg_getmap(bitmap1,4,8); fg_setcolor(9); fg_getmap(bitmap2,4,8); fg_move(4,15); fg_setcolor(7); fg_drawmap(bitmap1,4,8); fg_setcolor(9); fg_drawmap(bitmap2,4,8); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Example 9-19 is similar to example 9-18, but it uses fg_getimage and fg_drwimage instead of fg_getmap and fg_drawmap to retrieve and display the image. That is, it uses the mode-specific rather than the mode-independent image retrieval and display routines. When using the mode-specific routines, the size of the bit map needed to hold the image depends on the video mode. Furthermore, we must account for the fact that the native EGA and VGA graphics video modes (mode 13 in this example) only retrieve and display one color at a time. 156 Fastgraph User's Guide For the CGA, Tandy/PCjr, and MCGA graphics video modes (modes 4, 9, and 19), example 9-19 first computes the image width in bytes. The image is 32 pixels wide. In the CGA graphics mode, there are four pixels per byte of video memory, so the image width is 8 bytes. Similarly, there are two pixels per byte in the Tandy/PCjr graphics mode, so in this mode the image width is 16 bytes. In the MCGA graphics mode, each byte holds one pixel, so the image width is 32 bytes. The image height is 8 pixels in all cases. The size of the bit map array is 256 bytes, which is the size required in the MCGA graphics mode (32 bytes times 8 bytes). The other video modes require less storage, so in these modes only a portion of the bit map array will actually be used. The specified width is used in the calls to both fg_getimage and fg_drwimage in these video modes. For the EGA graphics mode (mode 13), we must call fg_getimage and fg_drwimage once for each of the two colors in the image. As in example 9-18, each color requires 32 bytes. The first call to fg_getimage stores the color 7 pixels in the first 32 bytes of the bit map array, and the second call stores the color 9 pixels in the next 32 bytes. Similarly, the first call to fg_drwimage displays the color 7 pixels from the first 32 bytes of the bit map array, while the second call displays the color 9 pixels from the next 32 bytes. Note how the most recent call to the fg_setcolor routine determines which pixels are retrieved or the color of the displayed pixels. Example 9-19. main() { char bitmap[256]; int old_mode, new_mode; int width; new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } if (new_mode == 4) width = 8; else if (new_mode == 9) width = 16; else if (new_mode == 19) width = 32; old_mode = fg_getmode(); fg_setmode(new_mode); fg_setcolor(7); fg_rect(0,31,0,7); fg_setcolor(9); fg_text("text",4); fg_waitkey(); fg_move(0,7); if (new_mode == 13) { Chapter 9: Images and Image Management 157 fg_setcolor(7); fg_getimage(bitmap,4,8); fg_setcolor(9); fg_getimage(&bitmap[32],4,8); } else fg_getimage(bitmap,width,8); fg_move(4,15); if (new_mode == 13) { fg_setcolor(7); fg_drwimage(bitmap,4,8); fg_setcolor(9); fg_drwimage(&bitmap[32],4,8); } else fg_drwimage(bitmap,width,8); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } We can also use the fg_getimage routine to retrieve images in text video modes. In text modes, however, there are a few differences we must consider when using fg_getimage. First, the text cursor position, not the graphics cursor position, specifies the lower left corner of the image. Hence we must use the fg_locate routine instead of fg_move to define the image location. Second, the image width is always twice the number of characters per image row (that is, for each character we have an character byte and an attribute byte). The fg_getmap routine has no effect when used in a text video mode. Example 9-20 shows a simple use of fg_getimage in text modes. This program is similar to example 9-19, but it runs in an 80-column text mode rather than a 320 by 200 graphics mode. As before, the program will retrieve the four characters "text" as an image from the upper left corner of the screen and then display it in a different location. Because the image consists of four characters in one row, the image width is 8 bytes and the image height is 1. Example 9-20. main() { int old_mode; char image[8]; old_mode = fg_getmode(); if (fg_testmode(3,1)) fg_setmode(3); else if (fg_testmode(7,1)) fg_setmode(7); else { printf("This program requires\n"); 158 Fastgraph User's Guide printf("an 80-column display.\n"); exit(); } fg_cursor(0); fg_setattr(9,7,0); fg_text("text",4); fg_waitkey(); fg_locate(0,0); fg_getimage(image,8,1); fg_locate(1,1); fg_drwimage(image,8,1); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Byte Boundaries Video memory, like standard random-access memory, is divided into units called bytes. In text modes, each byte holds either a character or an attribute. In graphics modes, each byte of video memory holds one or more horizontally contiguous pixels. If two adjacent horizontal pixels are stored in different bytes, then we say that a byte boundary exists between the two pixels. The number of pixels per byte depends on the video mode being used, so the byte boundaries also depend on the video mode. That is, a byte boundary in a CGA graphics mode is not necessarily a byte boundary in an EGA graphics mode. The following table summarizes the number of pixels per byte of video memory and the byte boundary sequences for each supported graphics video mode. Note that any horizontal coordinate whose value is a multiple of 8 is always a byte boundary, regardless of the video mode. Chapter 9: Images and Image Management 159 mode pixels horizontal coordinates number per byte of byte boundaries 4 4 0, 4, 8, 12, ... , 316 5 4 0, 4, 8, 12, ... , 316 6 8 0, 8, 16, 24, ... , 632 9 2 0, 2, 4, 6, ... , 318 11 8 0, 8, 16, 24, ... , 712 12 4 0, 4, 8, 12, ... , 316 13 8 0, 8, 16, 24, ... , 312 14 8 0, 8, 16, 24, ... , 632 15 8 0, 8, 16, 24, ... , 632 16 8 0, 8, 16, 24, ... , 632 17 8 0, 8, 16, 24, ... , 632 18 8 0, 8, 16, 24, ... , 632 19 1 0, 1, 2, 3, ... , 319 20 1 0, 1, 2, 3, ... , 319 21 1 0, 1, 2, 3, ... , 319 Image Transfer Routines The Fastgraph routines described in this section transfer images between areas of video memory. These routines are often used in animation sequences requiring high-performance graphics, so they must be as efficient as possible. To this end, Fastgraph will force their horizontal pixel coordinates to byte boundaries, which eliminates the need to process any pixels individually. Fastgraph accomplishes this by reducing minimum horizontal coordinates to a byte boundary and extending maximum horizontal coordinates to the last pixel in a video memory byte. Note that since we are talking about pixel coordinates and not character cells, the coordinate modification only occurs in graphics video modes. An example might best help explain this important feature. The CGA four-color graphics modes (modes 4 and 5) store four pixels in each byte of video memory. This means the byte boundaries occur at multiples of four pixels. Thus, when you use the image transfer routines in modes 4 and 5, Fastgraph reduces minimum x coordinates to the next lower multiple of four. Similarly, it extends their maximum x coordinates to the next higher multipleof four, less one pixel. That is, if a minimum x coordinate is 7 and a 160 Fastgraph User's Guide maximum x coordinate is 30, Fastgraph will modify these values to 4 and 31 respectively. If the x coordinates were originally 4 and 31, Fastgraph would leave them unchanged. Note, too, that because each pixel in the MCGA and VGA 256-color graphics modes (modes 19, 20, and 21) occupies a separate byte of video memory, Fastgraph does not need to modify horizontal coordinates in these video modes. Several of Fastgraph's image transfer routines reference a video page called the hidden page. The Fastgraph routine fg_sethpage defines which video page will be used as the hidden page. This routine takes as its only argument an integer value specifying the hidden page number. If you are using a virtual video page for the hidden page, you must call the fg_sethpage routine after allocating that page. There is also a routine named fg_gethpage that returns the hidden page number, as specified in the most recent call to fg_sethpage, as its function value. The fg_gethpage routine takes no arguments. Fastgraph's simplest image transfer routines are fg_save and fg_restore. The fg_save routine transfers a rectangular region from the active video page (as defined in the most recent call to fg_setpage) to the same position on the hidden video page (as defined in the most recent call to fg_sethpage). The fg_restore routine performs the complementary task -- it transfers a rectangular region from the hidden page to the active page. Each of these routines requires four arguments that define the coordinates of the region to transfer, in the order minimum x, maximum x, minimum y, and maximum y. In text modes, the coordinates are expressed as character space quantities (rows and columns). In graphics modes, they are expressed as screen space values (pixels); the x coordinates are extended to byte boundaries if required. There are also world space versions of these routines named fg_savew and fg_restorew available in graphics modes. Example 9-21 demonstrates the use of Fastgraph's fg_save, fg_restore, and fg_sethpage routines in an 80-column text video mode. After establishing the video mode (note the calls to fg_testmode specify that two video pages are needed), the program fills the screen with text and then waits for a keystroke. Following this, the program displays a small pop-up window prompting for another keystroke. After waiting for the second keystroke, the program erases the pop-up window by restoring the original screen contents, and then waits for yet another keystroke before returning to DOS. We'll present the program now, and afterward analyze it in detail. Example 9-21. main() { int row; int old_mode; char string[17]; old_mode = fg_getmode(); if (fg_testmode(3,2)) fg_setmode(3); else if (fg_testmode(7,2)) fg_setmode(7); else { Chapter 9: Images and Image Management 161 printf("This program requires\n"); printf("an 80-column display.\n"); exit(); } fg_cursor(0); fg_setattr(9,7,0); for (row = 0; row < 25; row++) { sprintf(string," This is row %2d ",row); fg_locate(row,0); fg_text(string,16); fg_text(string,16); fg_text(string,16); fg_text(string,16); fg_text(string,16); } fg_waitkey(); fg_allocate(1); fg_sethpage(1); fg_save(32,47,11,13); fg_setcolor(1); fg_rect(32,47,11,13); fg_setattr(15,1,0); fg_locate(12,33); fg_text("Press any key.",14); fg_waitkey(); fg_restore(32,47,11,13); fg_waitkey(); fg_freepage(1); fg_setmode(old_mode); fg_reset(); } Example 9-21 first establishes the video mode and uses the fg_cursor routine to make the BIOS cursor invisible. It then executes a for loop that fills each row of the screen with the phrase "This is row n", where n is the row number (between 0 and 24). Next, the program uses the fg_allocate routine to create video page 1 as a virtual video page. This is needed in case the program is running in mode 7, which has only one true page (if the program is running in mode 3, the call to fg_allocate has no effect). The program then makes page 1 the hidden page by calling the fg_sethpage routine. After setting up the hidden video page, but before displaying the pop-up window, example 9-21 uses the fg_save routine to save the current contents of the area that the pop-up window will replace. The fg_save routine copies this region to the hidden page. The program then displays the pop-up window in the middle of the screen and leaves it there until a key is pressed. Following this, the program uses the fg_restore routine to replace the pop-up window with the original contents of that region. This effectively erases the pop-up window and restores the original screen. The program then waits for another keystroke, after which it releases the virtual page and returns to DOS. 162 Fastgraph User's Guide The next example, 9-22, is similar to example 9-21, but it runs in a 320 by 200 color graphics mode instead of a text mode. The main differences between this program and example 9-21 are the use of 40-column text and the use of screen space coordinates instead of character space coordinates in the calls to fg_save, fg_restore, and fg_rect. Note that the call to fg_allocate creates a virtual page if the program is running in modes 4, 9, or 19. In mode 13, which has 8 true pages, the fg_allocate routine does nothing. Example 9-22. main() { int row; int new_mode, old_mode; char string[21]; new_mode = fg_bestmode(320,200,2); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_setcolor(7); fg_rect(0,319,0,199); fg_setcolor(9); for (row = 0; row < 25; row++) { sprintf(string," This is row %2d ",row); fg_locate(row,0); fg_text(string,20); fg_text(string,20); } fg_waitkey(); fg_allocate(1); fg_sethpage(1); fg_save(96,223,88,111); fg_setcolor(1); fg_rect(96,223,88,111); fg_setcolor(15); fg_locate(12,13); fg_text("Press any key.",14); fg_waitkey(); fg_restore(96,223,88,111); fg_waitkey(); fg_freepage(1); fg_setmode(old_mode); fg_reset(); } Chapter 9: Images and Image Management 163 The fg_save and fg_restore routines each copy a rectangular region from one video page to the same position on another video page. What if you need to copy the region to a different position on another video page, or perhaps copy it elsewhere on the same video page? Fastgraph provides a more general image transfer routine named fg_transfer. The fg_transfer routine copies a rectangular region on any video page to any position on any video page. Like fg_save and fg_restore, the fg_transfer routine works in text and graphics video modes. In graphics modes, fg_transfer extends its x coordinates to byte boundaries if necessary. The fg_transfer routine requires eight integer arguments. The first four arguments define the region to copy, in the same order as expected by the fg_save and fg_restore routines. The next two arguments define the lower left corner of the image destination, while the final two arguments respectively specify the source and destination video page numbers. In short, fg_transfer copies the specified region from the source page to the specified position on the destination page. Example 9-23 is identical to example 9-21, but it uses fg_transfer rather than fg_save and fg_restore. We have arbitrarily chosen to copy the region overwritten by the pop-up window to the lower left corner of the hidden page (page 1). When we copy this region back to the visual page, we copy from the lower left corner of the hidden page back to the original position on the visual page (page 0). This sequence is shown in the following diagram. (11,32) (11,47) (22,0) (22,15) +--------------+ first call +--------------+ |This is row 11| ------------> |This is row 11| |This is row 12| |This is row 12| |This is row 13| <------------ |This is row 13| +--------------+ second call +--------------+ (13,32) (13,47) (24,0) (24,15) visual page (0) hidden page (1) To copy one region to a new position and then back to its original position, note how we make the fifth and sixth arguments in the first call to fg_transfer the same values as the first and fourth arguments in the second call. Similarly, the fifth and sixth arguments in the second call must be the same as the first and fourth arguments in the first call. Now, here is example 9-23. Example 9-23. main() { int row; int old_mode; char string[17]; old_mode = fg_getmode(); if (fg_testmode(3,2)) fg_setmode(3); else if (fg_testmode(7,2)) 164 Fastgraph User's Guide fg_setmode(7); else { printf("This program requires\n"); printf("an 80-column display.\n"); exit(); } fg_cursor(0); fg_setattr(9,7,0); for (row = 0; row < 25; row++) { sprintf(string," This is row %2d ",row); fg_locate(row,0); fg_text(string,16); fg_text(string,16); fg_text(string,16); fg_text(string,16); fg_text(string,16); } fg_waitkey(); fg_allocate(1); fg_transfer(32,47,11,13,0,24,0,1); fg_setcolor(1); fg_rect(32,47,11,13); fg_setattr(15,1,0); fg_locate(12,33); fg_text("Press any key.",14); fg_waitkey(); fg_transfer(0,15,22,24,32,13,1,0); fg_fg_waitkey(); fg_freepage(1); fg_setmode(old_mode); fg_reset(); } Example 9-24 illustrates another use of the fg_transfer routine. This example is functionally identical to example 9-17 (see page 153), but it uses fg_transfer instead of fg_getmap and fg_drawmap. By using the fg_transfer routine, we eliminate the calls to fg_getmap and fg_drawmap, the two calls to fg_move, and the 32-byte array needed to retrieve the image. As an added bonus, using fg_transfer is much faster than the technique of example 9-17, although we probably won't notice this gain with such a small image. The image copied in example 9-24 is one row of four characters, so its width in screen space is 32 pixels and its height is 8 pixels. Because the image is in the upper left corner of the screen, the image boundaries are xmin=0, xmax=31, ymin=0, and ymax=7. We want to move the image one-half character cell (4 pixels) to the right and one row (8 pixels) down, so our destination coordinates are x=4 (xmin+4) and y=15 (ymax+8). Also, we are copying the image from one position to another on the visual page, so both the source and destination pages are 0. Chapter 9: Images and Image Management 165 Example 9-24. main() { int old_mode, new_mode; new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_setcolor(9); fg_text("text",4); fg_waitkey(); fg_transfer(0,31,0,7,4,15,0,0); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Example 9-25 shows yet another application of the fg_transfer routine in a graphics video mode. The program displays a rectangle in the upper left quadrant of the screen and then centers the word "quadrant" inside the rectangle. After waiting for a keystroke, the program uses fg_transfer to first copy the upper left quadrant to the upper right quadrant. It then uses fg_transfer again to copy the upper half of the screen to the lower half. The end result of this is the screen being filled with what was originally in the upper left quadrant. Example 9-25. main() { int new_mode, old_mode; new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_setcolor(7); fg_rect(0,159,0,99); fg_setcolor(9); fg_locate(6,6); fg_text("quadrant",8); fg_waitkey(); 166 Fastgraph User's Guide fg_transfer(0,159,0,99,160, 99,0,0); fg_transfer(0,319,0,99, 0,199,0,0); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } The final routines pertaining to image transfer are fg_tcxfer and fg_tcmask. The fg_tcxfer routine is similar to fg_transfer in that it copies a rectangular region from one position to another, but fg_tcxfer allows you to treat one or more colors as transparent (the name fg_tcxfer stands for "transparent color transfer"). In other words, any pixel whose color value is defined to be transparent is not copied to the destination area. The fg_tcxfer routine's arguments are the same as for the fg_transfer routine, but fg_tcxfer has no effect in text video modes. Because fg_tcxfer must examine the color of individual pixels, it is not as fast as the fg_transfer routine, especially in the native EGA and VGA graphics video modes (modes 13 through 18). The fg_tcmask routine defines which colors are considered transparent in subsequent calls to fg_tcxfer. Its argument is an integer bit mask (specifically, a 16-bit mask) where each bit indicates whether or not the color is transparent. For example, if bit 0 (the rightmost bit) is set in the mask, then color 0 will be transparent; if bit 0 is reset, color 0 will not be transparent. Because the bit mask size is 16 bits, only the first 16 color values may be defined as transparent. Example 9-26 illustrates the use of the fg_tcxfer and fg_tcmask routines. This program is identical to example 9-25, except the color of the word "quadrant" (color 9) is defined to be transparent, and fg_tcxfer is used in place of fg_transfer. Because color 9 maps to color 1 in the CGA four- color graphics mode (mode 4), we must define both colors 1 and 9 to be transparent (remember, fg_tcmask considers actual color values transparent, not color indices). The bit mask passed to fg_tcmask thus will be 0000 0010 0000 0010 binary, or 0202 hex. The end result of this program is the same as example 9-25, but the word "quadrant" appears in the background color (color 0) instead of color 9 in the upper right, lower left, and lower right quadrants. Example 9-26. main() { int new_mode, old_mode; new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_setcolor(7); Chapter 9: Images and Image Management 167 fg_rect(0,159,0,99); fg_setcolor(9); fg_locate(6,6); fg_text("quadrant",8); fg_waitkey(); fg_tcmask(0x0202); fg_tcxfer(0,159,0,99,160, 99,0,0); fg_tcxfer(0,319,0,99, 0,199,0,0); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Summary of Image Display Routines This section summarizes the functional descriptions of the Fastgraph routines presented in this chapter. More detailed information about these routines, including their arguments and return values, may be found in the Fastgraph Reference Manual. For all of the image display routines, images are positioned so that their lower left corner is at the graphics cursor position (or text cursor position for those routines that also work in text video modes). For all of the image transfer routines, Fastgraph extends the horizontal pixel coordinates to a byte boundary when the routines are used in a graphics video mode. FG_CLIPMASK displays a clipped image stored as a masking map. This routine has no effect when used in a text video mode. FG_CLPIMAGE displays a clipped image stored as a mode-specific bit map. This routine has no effect when used in a text video mode. FG_DISPFILE displays an image stored in Fastgraph's standard or packed pixel run format, where the image resides in an external file. This routine has no effect when used in a text video mode. FG_DISPLAY displays an image stored in Fastgraph's standard pixel run format, where the image resides in an array. This routine has no effect when used in a text video mode. FG_DISPLAYP displays an image stored in Fastgraph's packed pixel run format, where the image resides in an array. This routine has no effect when used in a text video mode. FG_DRAWMAP displays an image stored as a mode-independent bit map. This routine has no effect when used in a text video mode. FG_DRAWMASK displays an image stored as a masking map. This routine has no effect when used in a text video mode. FG_DRWIMAGE displays an image stored as a mode-specific bit map. 168 Fastgraph User's Guide FG_FLIPMASK displays a reversed clipped image stored as a masking map. This routine has no effect when used in a text video mode. FG_FLPIMAGE displays a reversed clipped image stored as a mode-specific bit map. This routine has no effect when used in a text video mode. FG_GETHPAGE returns the hidden page number, as defined in the most recent call to fg_sethpage. FG_GETIMAGE retrieves an image as a mode-specific bit map. FG_GETMAP retrieves an image as a mode-independent bit map. This routine has no effect when used in a text video mode. FG_PATTERN defines a display pattern for use with the fg_dispfile, fg_display, or fg_displayp routines. This routine has no effect when used in a text video mode. FG_RESTORE copies an image from the hidden video page to the same position on the active video page. FG_RESTOREW is identical to fg_restore, but the image extremes are specified as world space coordinates. FG_REVIMAGE displays a reversed image stored as a mode-specific bit map. This routine has no effect when used in a text video mode. FG_REVMASK displays a reversed image stored as a masking map. This routine has no effect when used in a text video mode. FG_SAVE copies an image from the active video page to the same position on the hidden video page. FG_SAVEW is identical to fg_save, but the image extremes are specified as world space coordinates. FG_SETHPAGE defines the hidden video page (used by fg_restore, fg_restorew, fg_save, and fg_savew). FG_TCMASK defines which colors the fg_tcxfer routine will consider transparent. This routine has no effect when used in a text video mode. FG_TCXFER copies an image from any position on any video page to any position on any video page, excluding any pixels whose color value is transparent. This routine has no effect when used in a text video mode. FG_TRANSFER copies an image from any position on any video page to any position on any video page. It is Fastgraph's most general image transfer routine. Chapter 10 Animation Techniques 170 Fastgraph User's Guide Overview Unlike other microcomputers, the IBM PC and PS/2 family of systems do not have any special graphics hardware or firmware to assist in performing animation. This means that any animation done on these systems must be implemented entirely through software. This chapter will describe how to do this using Fastgraph's video page management and image management routines. The methods described in this chapter are not intended to be all inclusive, for that would itself fill a separate volume at least as large as this manual. However, the animation techniques presented here should provide a basis that you can readily extend to develop more sophisticated uses of animation. The examples in this chapter are restricted to graphics video modes. Simple Animation The first type of animation we'll examine is called simple animation. In simple animation, we display an object, erase it, and then display it in a new position. When we perform this "erase and redisplay" sequence repetitively, the object moves. This method, however, has two drawbacks. First, unless the object is rather small, it will flicker because the erasing and display of the object does not coincide with the refresh rate of the video display. Second, and perhaps more importantly, anything underneath the object is not saved as the object moves across it. Despite these limitations, simple animation is sometimes useful, and it is a good place to begin our discussion of animation techniques. Example 10-1 moves a small bright green rectangle (magenta in CGA) from left to right across the screen in any 320 by 200 color graphics mode. The program moves the rectangle, which is 20 pixels wide and 10 pixels high, using a for loop. This loop first uses the fg_clprect routine to display the rectangle, then uses the fg_waitfor routine to leave the object on the screen momentarily, and finally uses fg_clprect again to erase the rectangle by redisplaying it in the original background color (the fg_waitfor routine is described in chapter 14). We use fg_clprect rather than fg_rect because the first few and last few loop iterations result in at least part of the rectangle being off the screen. Each successive loop iteration displays the rectangle five pixels to the right of its previous position. Example 10-1. main() { int new_mode, old_mode; int x; /* initialize the video environment */ new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } Chapter 10: Animation Techniques 171 old_mode = fg_getmode(); fg_setmode(new_mode); /* move the object across the screen */ for (x = -20; x < 320; x+=5) { fg_setcolor(10); fg_clprect(x,x+19,95,104); fg_waitfor(1); fg_setcolor(0); fg_clprect(x,x+19,95,104); } /* restore the original video mode and return to DOS */ fg_setmode(old_mode); fg_reset(); } Example 10-2 is identical to example 10-1, but it shows what happens when we move the rectangle across an existing background (in this case, the background is solid white). If you run this program, you'll see that the rectangle leaves a trail of color 0 behind it. While this might be occasionally useful, it demonstrates that simple animation is destructive because it does not preserve the background. In this particular example, if we changed the second call to fg_setcolor within the for loop so revert to color 15 instead of color 0, the background would be restored. In general, though, it may not be this easy to replace the background, so we must rely on some other method for preserving it. Example 10-2. main() { int new_mode, old_mode; int x; /* initialize the video environment */ new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); /* draw some type of background */ fg_setcolor(15); fg_rect(0,319,0,199); /* move the object across the screen */ for (x = -20; x < 320; x+=5) { 172 Fastgraph User's Guide fg_setcolor(10); fg_clprect(x,x+19,95,104); fg_waitfor(1); fg_setcolor(0); fg_clprect(x,x+19,95,104); } /* restore the original video mode and return to DOS */ fg_setmode(old_mode); fg_reset(); } To summarize, we see that simple animation is easy to implement, but it is destructive and typically causes the animated object to flicker. For these reasons, it is not used too frequently. XOR Animation "Exclusive or" animation, or XOR animation for short, is an interesting extension of simple animation and is most useful when animating a single- color object against a single-color background. Like simple animation, it uses the "erase and redisplay" technique to move an object, but it does this differently. Instead of erasing the object by displaying it in the background color, XOR animation does so by displaying it in the same color using an exclusive or, or XOR, operation. This method relies on a specific property of the exclusive or operator: (object XOR background) XOR object = background In other words, if you XOR something twice in the same position, the end result is the same as the original image in that position. Example 10-3 demonstrates XOR animation. This program is similar to example 10-2, but it only runs in the 320 by 200 EGA graphics mode (mode 13). After establishing the video mode, it uses the Fastgraph routine fg_setfunc to select XOR mode. This causes any subsequent graphics output to be XORed with the contents of video memory instead of just replacing it. The fg_setfunc routine is described further in chapter 15. The other differences between examples 10-3 and 10-2 are that the call to fg_setcolor has been moved outside the for loop, and that fg_setcolor takes a different value. Since the existing background is bright white (color 15), we can't just use color 10 if we want to display a bright green object. The desired value is that which when XORed with color 15 produces color 10; the easiest way to obtain this value is to XOR these two numbers. The call to fg_setcolor can be moved outside the loop because we display the object using the same color index throughout. Example 10-3. main() { int old_mode; int x; Chapter 10: Animation Techniques 173 /* initialize the video environment */ if (fg_testmode(13,1) == 0) { printf("This program requires EGA.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(13); fg_setfunc(3); /* draw some type of background */ fg_setcolor(15); fg_rect(0,319,0,199); /* move the object across the screen */ fg_setcolor(10^15); for (x = -20; x < 320; x+=5) { fg_clprect(x,x+19,95,104); fg_waitfor(1); fg_clprect(x,x+19,95,104); } /* restore the original video mode and return to DOS */ fg_setmode(old_mode); fg_reset(); } Fastgraph only supports the XOR pixel operation in the native EGA and VGA graphics video modes (modes 13 through 18). Thus, you cannot use XOR animation in CGA, Tandy/PCjr, Hercules, or MCGA graphics modes. While XOR animation is non-destructive (that is, it restores the original background), it still suffers from the flickering encountered in simple animation. In spite of this, it may be useful when animating a single-color object against a single-color background. Static Frame Animation Static frame animation uses a different strategy than simple animation or XOR animation. The general scheme of this method is to create the entire animation sequence off-screen and then successively display each item, or frame, in this sequence on one position of the visual video page. This results in a visually appealing animation which is non-destructive and does not include the flickering associated with simple animation and XOR animation. Static frame animation requires the visual video page and one or more additional pages to implement. The number of pages needed depends on the number of frames and the size of each frame. Example 10-4 runs in any 320 by 200 color graphics video mode and illustrates a simple use of static frame animation. The program displays an animation sequence containing 12 frames; it displays this sequence three times. The animation sequence consists of a bright green rectangle (magenta 174 Fastgraph User's Guide in CGA) moving from left to right across the center of the frame. Each frame is 96 pixels wide and 50 pixels high. The 12 frames are set up on an off- screen video page as shown below. 0 95 96 191 192 287 0 frame 1 frame 2 frame 3 49 50 frame 4 frame 5 frame 6 99 100 frame 7 frame 8 frame 9 149 150 frame 10 frame 11 frame 12 199 Example 10-4 first establishes the video mode and allocates the additional video page (needed if using a video mode in which page 1 is a virtual video page). The program then generates the background for frame 1; the background is a blue rectangle (cyan in CGA) with a white ellipse centered on it. After the call to fg_ellipse, the first frame is ready. The next step is to create the remaining 11 frames. In frame 2, the right half of the 20-pixel wide rectangle will enter the left edge of the frame. In frame 3, the rectangle will be ten pixels farther right, or aligned against the left edge of the frame. In frames 4 through 12, the rectangle will be ten pixels farther right in each frame, so that by frame 12 only the left half of the rectangle appears on the right edge of the frame. The first for loop in the program builds frames 2 through 12 by copying the background from frame 1 and then displaying the rectangle (that is, the animated object) in the proper position for that frame. The second for loop performs the animation sequence. To display the 12- frame sequence three times, it must perform 36 iterations. The loop simply copies each frame from the appropriate position on video page 1 to the middle of the visual video page. Note how the fg_waitfor routine is used to pause momentarily between each frame. Example 10-4. #define VISUAL 0 #define HIDDEN 1 int xmin[] = { 0, 96,192, 0, 96,192, 0, 96,192, 0, 96,192}; int ymax[] = { 49, 49, 49, 99, 99, 99,149,149,149,199,199,199}; main() { int new_mode, old_mode; int frame, offset; Chapter 10: Animation Techniques 175 int i, x, y; /* initialize the video environment */ new_mode = fg_bestmode(320,200,2); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_allocate(HIDDEN); /* draw the background in the upper left corner */ fg_setpage(HIDDEN); fg_setcolor(1); fg_rect(0,95,0,49); fg_setcolor(15); fg_move(48,25); fg_ellipse(20,20); /* display the animated object against each background */ fg_setcolor(10); offset = -10; for (i = 1; i < 12; i++) { x = xmin[i]; y = ymax[i]; fg_transfer(0,95,0,49,x,y,HIDDEN,HIDDEN); fg_setclip(x,x+95,0,199); fg_clprect(x+offset,x+offset+19,y-29,y-20); offset += 10; } /* slide the object across the background three times */ for (i = 0; i < 36; i++) { frame = i % 12; x = xmin[frame]; y = ymax[frame]; fg_transfer(x,x+95,y-49,y,112,124,HIDDEN,VISUAL); fg_waitfor(2); } /* restore the original video mode and return to DOS */ fg_freepage(HIDDEN); fg_setmode(old_mode); fg_reset(); } 176 Fastgraph User's Guide Dynamic Frame Animation Dynamic frame animation is similar to static frame animation, but the all of the animation frames are built as needed during the animation sequence instead of in advance. When using this method, you must first store a copy of the background on an off-screen video page. Then, to build a frame, create another copy (called the workspace) of the background elsewhere on the off-screen page (or perhaps even to a different off-screen page) and display the object on that copy. Finally, transfer the workspace to the visual page. Like static frame animation, this method produces a non-destructive, flicker- free animation sequence. Example 10-5 is functionally identical to example 10-4, but it uses dynamic rather than static frame animation. As before, the program builds the background in the upper left corner of video page 1, but it then uses fg_transfer to copy it to the center of the visual video page. The for loop builds each frame as it is needed and likewise copies it to the center of the visual page. Again, fg_waitfor creates the necessary pause between frames. Example 10-5. #define VISUAL 0 #define HIDDEN 1 main() { int new_mode, old_mode; int frame, offset; int i; /* initialize the video environment */ new_mode = fg_bestmode(320,200,2); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_allocate(HIDDEN); /* draw the background in the upper left corner */ fg_setpage(HIDDEN); fg_setcolor(1); fg_rect(0,95,0,49); fg_setcolor(15); fg_move(48,25); fg_ellipse(20,20); /* copy it to the center of the visual page */ fg_transfer(0,95,0,49,112,124,HIDDEN,VISUAL); Chapter 10: Animation Techniques 177 /* slide the object across the background three times */ fg_setcolor(10); for (i = 0; i < 36; i++) { frame = i % 12; offset = 10 * frame - 10; fg_transfer(0,95,20,29,112,105,HIDDEN,HIDDEN); fg_rect(112+offset,131+offset,96,105); fg_transfer(112,207,96,105,112,105,HIDDEN,VISUAL); fg_waitfor(2); } /* restore the original video mode and return to DOS */ fg_freepage(HIDDEN); fg_setmode(old_mode); fg_reset(); } Two items in example 10-5 merit further discussion. First, we have chosen our workspace on page 1 so that it uses the same screen space coordinates as the image area on the visual page. This is not necessary unless you are using the fg_restore routine instead of fg_transfer. Second, the program is able to use the faster fg_rect routine in place of fg_clprect. It can do this because even though the object will extend beyond the workspace limits, we only transfer the workspace itself. However, for this to function properly, the workspace's horizontal limits must fall on byte boundaries. Note too that we do not need to transfer the entire frame during the animation sequence. In example 10-5, we know the vertical extremes of the moving image are y=96 and y=105, so we only transfer 10 rows instead of the entire frame. We could similarly compute the x extremes for each frame and only transfer the necessary portion. Recall, however, that fg_transfer extends the horizontal coordinates to byte boundaries, so we may copy a few extra pixels as well. This may or may not affect the animation sequence. Again, the problem is eliminated if you align your workspace on byte boundaries. When we use dynamic frame animation, it is fairly easy to change the number of frames in the animation sequence. Suppose we wish to produce a smoother animation by increasing the number of frames from 12 to 24. This means the object will move in increments of five pixels instead of ten. The only changes needed are to double the number of loop iterations, modify the calculations for the frame number and offset values as shown below, and reduce the fg_waitfor pause from 2 to 1. frame = i % 24; offset = 5 * frame - 10; Compare this to all of the changes that would be necessary if we were using static frame animation. 178 Fastgraph User's Guide Page Flipping Page flipping is a variation of frame animation in which you construct images on off-screen video pages and then repetitively make those pages the visual page. We can further divide the page flipping technique into static and dynamic variants, just as we did with frame animation. In static page flipping, we construct the entire animation sequence in advance, with one frame per video page. Once this is done, we can display each frame by using the fg_setvpage routine to instantly switch from one video page to another. Although this produces a smooth, flicker-free animation, we cannot carry the sequence very far before running out of video pages (and hence animation frames). In dynamic page flipping, we construct each animation frame when it is needed. As in static page flipping, we construct each frame on a separate video page. However, as example 10-6 demonstrates, we only need three video pages to produce the animation sequence, regardless of the number of frames in the sequence. Two of the three video pages will alternate as the visual page, while the remaining video page keeps a copy of the background. Example 10-6, which performs an animation sequence similar to examples 10-4 and 10-5, illustrates dynamic frame animation in the 320 by 200 EGA graphics video mode (mode 13). The program begins by displaying the background on video page 2. Video pages 0 and 1 will alternate as the visual page; the page that is not the visual page is called the hidden page. We start with page 0 as the visual page, and hence page 1 as the hidden page. To build each frame, the program uses fg_transfer to copy the background from page 2 to the hidden page and then uses fg_clprect to display the animated object at the correct position on the hidden page. After this, it displays the next frame by using fg_setvpage to make the hidden page the visual page. Before beginning the next iteration, the program toggles the hidden page number in preparation for the next frame. Example 10-6. main() { int old_mode; int hidden; int x; /* initialize the video environment */ if (testmode(fg_13,3) == 0) { printf("This program requires EGA.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(13); /* draw the background on page two */ fg_setpage(2); fg_setcolor(1); Chapter 10: Animation Techniques 179 fg_rect(0,319,0,199); fg_setcolor(15); fg_move(160,100); fg_ellipse(20,20); /* slide the object across the screen */ hidden = 1; setcolor(10); for (x = -10; x < 320; x+=4) { fg_setpage(hidden); fg_transfer(0,319,0,199,0,199,2,hidden); fg_clprect(x,x+19,96,105); fg_setvpage(hidden); hidden = 1 - hidden; fg_waitfor(1); } /* restore the original video mode and return to DOS */ fg_setmode(old_mode); fg_reset(); } A problem with either page flipping technique arises if we use virtual video pages. Page flipping relies on the fact that changing the visual page number occurs instantly, which is exactly what happens when we use physical video pages. However, such is not the case with virtual pages because Fastgraph must copy the entire virtual page contents into video memory. While this occurs quite rapidly, it is not instantaneous, and its effects are immediately apparent on the animation. Summary of Animation Techniques This chapter has presented five animation techniques: simple animation, XOR animation, static frame animation, dynamic frame animation, and page flipping. The following table summarizes their behavior. technique destructive? flicker-free? simple yes no XOR no no static frame no yes dynamic frame no yes page flipping no yes Simple animation and XOR animation are elementary techniques that are seldom used once you master the concepts of frame animation and page flipping. As stated at the beginning of this chapter, the simple examples presented here serve as the basis for understanding the mechanics of the animation techniques we have discussed. In "real world" programs, you'll typically want to display an image using the fg_drwimage or fg_drawmap routines instead using rudimentary images such as the rectangles in our 180 Fastgraph User's Guide examples. A helpful rule is to use pixel run maps (displayed by fg_dispfile, fg_display, or fg_displayp) for both backgrounds and moving objects, and then use fg_getimage or fg_getmap to retrieve the moving objects as bit-mapped images for later display. Of course, it is desirable to do all of this "behind the scenes" work on video pages other than the visual page. Chapter 11 Special Effects 182 Fastgraph User's Guide Overview This chapter will discuss the Fastgraph routines that help produce special visual effects. These include the ability to dissolve the screen contents in small increments, scroll areas of the screen, and change the physical origin of the screen. The accompanying example programs illustrate how to use these routines to produce some interesting effects. Screen Dissolving Screen dissolving is the process of replacing the entire screen contents in random small increments instead of all at once. Fastgraph includes two routines, fg_fadeout and fg_fadein, for this purpose. The fg_fadeout routine incrementally replaces the visual page contents with pixels of the current color, while fg_fadein incrementally replaces the visual page contents with the hidden page contents (that is, the page defined in the most recent call to fg_sethpage). Both routines accept an integer argument that defines the delay between each incremental replacement. A value of zero means to perform the replacement as quickly as possible, while 1 is slightly slower, 2 is slower yet, and so forth. The fg_fadeout and fg_fadein routines have no effect in text video modes. Example 11-1 shows how to use the fg_fadeout routine. The program, which runs in any graphics video mode, first fills the screen with a rectangle of color 2. After waiting for a keystroke, the program incrementally replaces the screen contents with pixels of color 15 (the current color index when fg_fadeout is called). After another keystroke, the program exits gracefully. Example 11-1. main() { int old_mode; old_mode = fg_getmode(); fg_setmode(fg_automode()); fg_setcolor(2); fg_rect(0,fg_getmaxx(),0,fg_getmaxy()); fg_waitkey(); fg_setcolor(15); fg_fadeout(0); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Example 11-2 shows how to use the fg_fadein routine in any 320 by 200 color graphics video mode. The program first fills the screen with a rectangle of color 2 and then fills video page 1 with a rectangle of color 1. After waiting for a keystroke, the program incrementally transfers the Chapter 11: Special Effects 183 contents of page 1 to the visual page. After the call to fg_fadein, both page 0 (the visual page) and page 1 (the hidden page) will contain rectangles of color 1 that fill the entire video page. Finally, the program waits for another keystroke before returning to DOS. Example 11-2. main() { int new_mode, old_mode; new_mode = fg_bestmode(320,200,2); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_allocate(1); fg_sethpage(1); fg_setcolor(2); fg_rect(0,319,0,199); fg_setpage(1); fg_setcolor(1); fg_rect(0,319,0,199); fg_waitkey(); fg_fadein(0); fg_waitkey(); fg_freepage(1); fg_setmode(old_mode); fg_reset(); } You can also produce some appealing visual effects by replacing the screen contents in a non-random fashion using the fg_restore or fg_transfer routines. For example, you could copy the hidden page contents to the visual page with a through a series of concentric rectangular areas, each slightly larger than the previous, until the entire screen is copied. Another interesting effect is to start around the screen perimeter and proceed toward the screen center, thus producing a "snake-like" effect. Experimenting with such techniques may reveal other effects that suit your application. Scrolling Another useful effect is scrolling, and Fastgraph provides a routine that performs vertical scrolling within a given region of the active video page. The fg_scroll routine scrolls a region defined in screen space or character space. It can scroll up or down and offers two types of scrolling: circular and end-off. In circular scrolling, rows that scroll off one edge of the defined region appear at its opposite edge. In end-off scrolling, 184 Fastgraph User's Guide such rows are simply wind up above or below the scrolling area. The following diagrams illustrate the two types of scrolling. end-off scrolling circular scrolling before after before after C B A A A A B B In these diagrams, the area bounded by the double lines is the scrolling region, as specified in the call to fg_scroll. Also, the scrolling direction is assumed to be down (that is, toward the bottom of the screen), and the number of rows to scroll is the height of the area designated B. The number of rows to scroll is often called the scrolling increment. For the end-off scrolling example, the scrolling operation transfers region A downward so that part of it is copied into area B. The area C (which is the same size as area B) at the top of the scrolling region is filled with pixels of the current color index (as defined in the most recent call to fg_setcolor), and the original contents of area B are lost. The circular scrolling example also copies region A downward into the original area B. Unlike end-off scrolling, however, circular scrolling preserves the area B by copying it to the opposite edge of the scrolling region. The fg_scroll routine takes six arguments. The first four define the scrolling region in the order minimum x coordinate, maximum x coordinate, minimum y coordinate, and maximum y coordinate. In graphics video modes, the x coordinates are extended by byte boundaries (see page 158) if needed. The fifth argument is the scrolling increment. It specifies the number of rows to scroll. If it is positive, the scrolling direction is toward the bottom of the screen; if it is negative, the scrolling direction is toward the top of the screen. The sixth and final argument specifies the scroll type. If this value is zero, the scroll will be circular; if it is any other value, the scroll will be end-off. If the scroll type is circular, Fastgraph will use the hidden page (as defined in the most recent call to fg_sethpage) as a workspace (more specifically, the area bounded by the scrolling region extremes on the hidden page will be used). We'll now present three example programs that use the fg_scroll routine. Example 11-3 runs in any 320 by 200 graphics video mode. The program displays two lines of text ("line one" and "line two") in the upper left corner of the screen against a white background. It then uses the fg_scroll routine to move the second line down four pixel rows using an end-off scroll. After waiting for a keystroke, the program again uses fg_scroll to move the text back to its original position. Note especially how the fg_setcolor routine appears before the first call to fg_scroll to replace the "scrolled off" rows with pixels of color 15, thus preserving the white background. Example 11-3. main() { Chapter 11: Special Effects 185 int new_mode, old_mode; new_mode = fg_bestmode(320,200,1); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_setcolor(15); fg_rect(0,319,0,199); fg_setcolor(10); fg_text("line one",8); fg_locate(1,0); fg_text("line two",8); fg_waitkey(); fg_setcolor(15); fg_scroll(0,63,8,15,4,1); fg_waitkey(); fg_scroll(0,63,12,19,-4,1); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Example 11-4 is similar to example 11-3, but it runs in the 80-column color text mode (mode 3). In text modes, we cannot scroll half a character row (four pixels) as in example 11-3, so the program scrolls the minimum one row instead. Example 11-4. main() { int old_mode; old_mode = fg_getmode(); fg_setmode(3); fg_cursor(0); fg_setcolor(7); fg_rect(0,79,0,24); fg_setattr(10,7,0); fg_text("line one",8); fg_locate(1,0); fg_text("line two",8); fg_waitkey(); fg_setcolor(7); fg_scroll(0,7,1,1,1,1); fg_waitkey(); fg_scroll(0,7,2,2,-1,1); 186 Fastgraph User's Guide fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Example 11-5, the final scrolling example, demonstrates a circular scroll. The program runs in any 320 by 200 color graphics video mode; note the use of video page 1 for the workspace required when the fg_scroll routine performs a circular scroll. The program first fills the screen with a light blue rectangle (cyan in CGA), displays a smaller white rectangle in the center of the screen, and then uses fg_move, fg_draw, and fg_paint to display a light green star (magenta in CGA) within the white rectangle. The program executes a while loop to scroll the star upward in four pixel increments. Because the scroll is circular, rows of the star that "scroll off" the top edge of the white rectangle (whose height is the same as the scrolling region) reappear at its bottom edge. The use of fg_waitfor within the loop simply slows down the scroll. The scrolling continues until any key is pressed. Example 11-5. main() { int new_mode, old_mode; new_mode = fg_bestmode(320,200,2); if (new_mode < 0 || new_mode == 12) { printf("This program requires a 320 "); printf("x 200 color graphics mode.\n"); exit(); } old_mode = fg_getmode(); fg_setmode(new_mode); fg_allocate(1); fg_sethpage(1); fg_setcolor(9); fg_rect(0,319,0,199); fg_setcolor(15); fg_rect(132,188,50,150); fg_setcolor(10); fg_move(160,67); fg_draw(175,107); fg_draw(140,82); fg_draw(180,82); fg_draw(145,107); fg_draw(160,67); fg_paint(160,77); fg_paint(150,87); fg_paint(160,87); fg_paint(170,87); fg_paint(155,97); fg_paint(165,97); Chapter 11: Special Effects 187 while (kbhit() == 0) { fg_waitfor(1); fg_scroll(136,184,50,150,-4,0); } fg_waitkey(); fg_freepage(1); fg_setmode(old_mode); fg_reset(); } Changing the Screen Origin Fastgraph includes two routines for changing the screen origin. By changing the screen origin, we simply mean defining the (x,y) coordinate of the upper left corner of the display area. The fg_pan routine performs this function in screen space, while the fg_panw routine does in world space. Neither routine changes the graphics cursor position. Each of these routines has two arguments that specify the x and y coordinates of the screen origin. For the fg_pan routine, the arguments are integer quantities. For the fg_panw routine, they are floating point quantities. In the native EGA and VGA graphics modes (modes 13 through 18), you can set the screen origin to any (x,y) coordinate position (that is, to any pixel). In the other graphics modes, certain restrictions exist, as imposed by specific video hardware. These constraints limit the coordinate positions that can be used as the screen origin. Fastgraph compensates for these restrictions by reducing the specified x and y coordinates to values that are acceptable to the current video mode, as shown in the following table. x will be reduced y will be reduced video mode to a multiple of: to a multiple of: 4, 5 8 2 6 16 2 9 4 4 11 8 4 12 4 2 or 3 19, 20, 21 4 1 For example, in modes 4 and 5, the x coordinate will be reduced to a multiple of 8 pixels, and the y coordinate will be reduced to a multiple of 2 pixels. In the Hercules low resolution mode (mode 12), the y coordinate reduction depends on whether or not the specified pixel row is scan doubled. Example 11-6 shows a useful effect that can be made with the fg_pan or fg_panw routines. This program uses the fg_automode routine to select a video mode and then draws an unfilled rectangle in color 15 (bright white). The top and bottom sides of the rectangle are intentionally drawn just smaller than the physical screen size. After waiting for a keystroke, the 188 Fastgraph User's Guide program uses a for loop to make the rectangle jiggle up and down. The rectangle moves because the fg_pan routine is called inside the loop to switch the screen origin between the rectangle's upper left corner and the original origin. Note also the use of the fg_waitfor routine to cause slight delays after each call to fg_pan. If we didn't use fg_waitfor, the changing of the origin would occur so rapidly we wouldn't notice the effect. Finally, the program restores the original video mode and screen attributes before returning to DOS. Example 11-6. #define DELAY 2 #define JUMP 4 main() { int i; int old_mode; old_mode = fg_getmode(); fg_setmode(fg_automode()); fg_setcolor(15); fg_move(0,JUMP); fg_draw(fg_getmaxx(),JUMP); fg_draw(fg_getmaxx(),fg_getmaxy()-JUMP); fg_draw(0,fg_getmaxy()-JUMP); fg_draw(0,JUMP); fg_waitkey(); for (i = 0; i < 6; i++) { fg_pan(0,JUMP); fg_waitfor(DELAY); fg_pan(0,0); fg_waitfor(DELAY); } fg_setmode(old_mode); fg_reset(); } Summary of Special Effects Routines This section summarizes the functional descriptions of the Fastgraph routines presented in this chapter. More detailed information about these routines, including their arguments and return values, may be found in the Fastgraph Reference Manual. FG_FADEIN incrementally replaces the visual page contents with the hidden page contents. This routine has no effect in text video modes. FG_FADEOUT incrementally replaces the visual page contents with pixels of the current color. This routine has no effect in text video modes. Chapter 11: Special Effects 189 FG_PAN changes the screen origin (the upper left corner of the screen) to the specified screen space coordinates. This routine has no effect in text video modes. FG_PANW is the world space version of the fg_pan routine. FG_SCROLL vertically scrolls a region of the active video page. The scrolling may be done either up or down, using either an end-off or circular method. Circular scrolling uses part of the hidden page as a temporary workspace. 190 Fastgraph User's Guide Chapter 12 Input Device Support 192 Fastgraph User's Guide Overview The selection of application input devices is an important part of designing a program for the IBM PC and PS/2 family of systems. The keyboard and mouse are very popular, and in fact more and more applications, especially those that utilize a graphical interface, actually require a mouse to use the product. Another input device, primarily used in entertainment software, is the joystick. Although not as popular as the mouse, the joystick nevertheless can simplify the use of certain applications. Fastgraph provides support for these three types of input devices, and this chapter will discuss this in detail. Keyboard Support Fastgraph's keyboard support includes routines to read keystrokes, check the state of certain keys, and set the state of these keys. These routines are independent of the other parts of Fastgraph and thus do not require that you call fg_setmode. All of the keyboard-related routines work in text and graphics video modes. The IBM PC and PS/2 keyboards produce two types of character codes -- standard codes and extended codes (extended codes are sometimes called auxiliary codes). The standard codes correspond to the 128 characters in the ASCII character set. In general, pressing keys on the main part of the keyboard, or on the numeric keypad with NumLock turned on, will generate a standard code. The 128 extended codes are specific to the IBM PC and PS/2 keyboards. Some common keystrokes that produce extended codes are keys on the numeric keypad with NumLock turned off, the function keys, or pressing Alt with another key. The following tables show the all of the standard and extended keyboard codes. Chapter 12: Input Device Support 193 Table of standard keyboard codes key code key code key code key code (none) 0 space 32 @ 64 ` 96 Ctrl+A 1 ! 33 A 65 a 97 Ctrl+B 2 " 34 B 66 b 98 Ctrl+C 3 # 35 C 67 c 99 Ctrl+D 4 $ 36 D 68 d 100 Ctrl+E 5 % 37 E 69 e 101 Ctrl+F 6 & 38 F 70 f 102 Ctrl+G 7 ' 39 G 71 g 103 Ctrl+H 8 ( 40 H 72 h 104 Ctrl+I 9 ) 41 I 73 i 105 Ctrl+J 10 * 42 J 74 j 106 Ctrl+K 11 + 43 K 75 k 107 Ctrl+L 12 , 44 L 76 l 108 Ctrl+M 13 - 45 M 77 m 109 Ctrl+N 14 . 46 N 78 n 110 Ctrl+O 15 / 47 O 79 o 111 Ctrl+P 16 0 48 P 80 p 112 Ctrl+Q 17 1 49 Q 81 q 113 Ctrl+R 18 2 50 R 82 r 114 Ctrl+S 19 3 51 S 83 s 115 Ctrl+T 20 4 52 T 84 t 116 Ctrl+U 21 5 53 U 85 u 117 Ctrl+V 22 6 54 V 86 v 118 Ctrl+W 23 7 55 W 87 w 119 Ctrl+X 24 8 56 X 88 x 120 Ctrl+Y 25 9 57 Y 89 y 121 Ctrl+Z 26 : 58 Z 90 z 122 Ctrl+[ 27 ; 59 [ 91 { 123 Ctrl+\ 28 < 60 \ 92 | 124 Ctrl+] 29 = 61 ] 93 } 125 Ctrl+^ 30 > 62 ^ 94 ~ 126 Ctrl+- 31 ? 63 _ 95 Ctrl+BS 127 194 Fastgraph User's Guide Table of extended keyboard codes code key 3 Ctrl+@ 15 Shift+Tab (back tab) 16-25 Alt+Q to Alt+P (top row of letters) 30-38 Alt+A to Alt+L (middle row of letters) 44-50 Alt+Z to Alt+M (bottom row of letters) 59-68 F1 to F10 71 Home 72 up arrow 73 PgUp 75 left arrow 77 right arrow 79 End 80 down arrow 81 PgDn 82 Ins 83 Del 84-93 Shift+F1 to Shift+F10 94-103 Ctrl+F1 to Ctrl+F10 104-113 Alt+F1 to Alt+F10 114 Ctrl+PrtSc 115 Ctrl+left arrow 116 Ctrl+right arrow 117 Ctrl+End 118 Ctrl+PgDn 119 Ctrl+Home 120-131 Alt+1 to Alt+= (top row of keys) 132 Ctrl+PgUp In addition, four keys generate the same standard codes as other control key combinations. These keys are: key same as code Backspace Ctrl+H 8 Tab Ctrl+I 9 Enter Ctrl+M 13 Escape Ctrl+[ 27 The CapsLock, NumLock, and ScrollLock keys do not generate a standard or extended code when pressed. Instead, they toggle between off and on states. Reading Keystrokes When you press a key or key combination, the standard or extended code representing that keystroke is stored in the ROM BIOS keyboard buffer. This buffer can hold up to 16 keystrokes and thus provides a type-ahead capability. Fastgraph includes three routines for reading keystroke information from the keyboard buffer. The fg_getkey routine reads the next item in the keyboard buffer if one is available (that is, if a key has been Chapter 12: Input Device Support 195 pressed). If the keyboard buffer is empty (meaning no key has been pressed), fg_getkey waits for a keystroke and then reports information about it. Another routine, fg_intkey, reads the next keystroke from the keyboard buffer if one is available. If the keyboard buffer is empty, fg_intkey immediately returns and reports this condition. The fg_intkey routine is useful when a program must continue performing a task until a key is pressed. We've already seen the third routine, fg_waitkey, which flushes the keyboard buffer and then waits for another keystroke. Unlike fg_getkey and fg_intkey, fg_waitkey does not return any keystroke information. It is most useful in "press any key to continue" situations. Both the fg_getkey and fg_intkey routines require two one-byte arguments passed by reference. If the keystroke is represented by a standard keyboard code, fg_getkey and fg_intkey return its code in the first argument and set the second argument to zero. Similarly, if the keystroke generates an extended code, the routines return its code in the second argument and set the first argument to zero. If the fg_intkey routine detects an empty keyboard buffer, it sets both arguments to zero. Example 12-1 is a simple program that uses the fg_getkey routine. It solicits keystrokes and then displays the two values returned by fg_getkey, one of which will always be zero. The variable key receives the key's standard code, while aux receives its extended code. Note that fg_getkey is the only Fastgraph routine in the program; this can be done because the keyboard support routines are logically independent from the rest of Fastgraph. The program returns to DOS when you press the Escape key. Example 12-1. #define ESC 27 main() { unsigned char key, aux; fg_getkey(&key,&aux); while (key != ESC) { printf("key = %3d aux = %3d\n",key,aux); fg_getkey(&key,&aux); } } Example 12-2 reads keystrokes using the fg_intkey routine at half-second intervals (18 fg_waitfor units equals one second). As in the previous example, the program displays the standard and extended codes for each keystroke. However, example 12-2 will continuously execute the while loop even if no keystrokes are available, in which case the key and aux values will both be zero. The program returns to DOS when you press the Escape key. Example 12-2. #define ESC 27 main() { unsigned char key, aux; 196 Fastgraph User's Guide fg_intkey(&key,&aux); while (key != ESC) { printf("key = %3d aux = %3d\n",key,aux); fg_waitfor(9); fg_intkey(&key,&aux); } } When you use fg_intkey in a "tight" loop that does little else, you should force a small delay within the loop by calling fg_waitfor as in example 12-2. Typically a delay of one or two clock ticks is sufficient. Without this delay, the BIOS may not be able to process all keyboard activity, and thus some keystrokes may not be available to your program. Testing and Setting Key States As mentioned earlier, the CapsLock, NumLock, and ScrollLock keys do not generate a standard or extended code when pressed but instead toggle between off and on states. Fastgraph includes routines for checking the state of these keys, as well as setting the state of the CapsLock and NumLock keys. The Fastgraph routines fg_capslock, fg_numlock, and fg_scrlock respectively read the state of the CapsLock, NumLock, and ScrollLock keys. Each routine has no arguments and returns the key state as its function value. A return value of 0 means the associated key is in the off state, while 1 indicates the key is in the on state. If the keyboard does not have a ScrollLock key, fg_scrlock considers the key off and returns a value of zero. Example 12-3 is a simple program that uses the fg_capslock, fg_numlock, and fg_scrlock routines to print messages describing the current state of these three keys. Example 12-3. main() { if (fg_capslock()) printf("CapsLock is on.\n"); else printf("CapsLock is off.\n"); if (fg_numlock()) printf("NumLock is on.\n"); else printf("NumLock is off.\n"); if (fg_scrlock()) printf("ScrollLock is on.\n"); else printf("ScrollLock is off.\n"); } Chapter 12: Input Device Support 197 You can also set the state of the CapsLock and NumLock keys within a program. Fastgraph includes two routines, fg_setcaps and fg_setnum, for this purpose. Each routine requires an integer argument that specifies the new key state. If the argument value is 0, the key will be turned off; if the value is 1, the key will be turned on. Example 12-4 uses fg_setcaps and fg_setnum to turn off CapsLock and NumLock. Example 12-4. main() { fg_setcaps(0); fg_setnum(0); } On most keyboards, changing key states with fg_setcaps or fg_setnum will also change the keyboard state light to reflect the new key state. However, some older keyboards, especially when used on PC, PC/XT, or Tandy 1000 systems, do not update the state light. This makes the state light inconsistent with the true key state. Mouse Support The mouse is a very popular input and pointing device, especially in graphically-oriented programs. Fastgraph contains a number of routines to support mice. These routines perform such tasks as mouse initialization, controlling and defining the mouse cursor, and reporting information about the mouse position and button status. The underlying software that controls the mouse is called the mouse driver. Fastgraph's mouse support routines provide a high-level interface to this driver. The Microsoft Mouse and its accompanying mouse driver have essentially become an industry standard, and other manufacturers of mice have also made their mouse drivers Microsoft compatible. For this reason, the Fastgraph mouse support routines assume you are using a Microsoft or compatible mouse driver. Unfortunately, not all mouse drivers are created equal. That is, some drivers are not truly Microsoft compatible, even though they may be advertised as such. In some cases, these incompatibilities are rather trivial, but others are significant. For example, early versions of some third party mouse drivers had real problems in the EGA graphics modes. The Microsoft mouse driver, the Logitech mouse driver (version 3.2 or above), and the DFI mouse driver (version 3.00 or above) are known to work well with Fastgraph's mouse support routines. Any other Microsoft compatible mouse driver should also work properly. Initializing the Mouse There are two steps required to use Fastgraph's mouse support routines within an application program. First, you must install the mouse driver. This is done prior to running the application, typically by entering the command MOUSE at the DOS command prompt. Second, you must use the Fastgraph routine fg_mouseini to initialize the mouse within the program. 198 Fastgraph User's Guide The fg_mouseini routine has no arguments and returns a "success or failure" indicator as its function value. If the return value is -1, it means fg_mouseini could not initialize the mouse (either because the mouse driver is not installed, or the driver is installed but the mouse is physically disconnected). The fg_mouseini routine will also return -1 when used in video modes 20 and 21 because there is no mouse support available in these video modes. If fg_mouseini returns a positive integer value, then the mouse initialization was successful. The value itself indicates the number of buttons (either 2 or 3) on the mouse. If you don't call fg_mouseini, or if fg_mouseini can't initialize the mouse, none of Fastgraph's other mouse support routines will have any effect. Example 12-5 illustrates how to initialize the mouse. Unlike the keyboard support routines, Fastgraph's mouse support routines require that fg_setmode first be called. In this example, we simply pass fg_setmode the value -1 to initialize Fastgraph for whatever video mode is in effect when we run the program. The program then calls fg_mouseini and prints a message indicating whether or not the initialization was successful. If it was, the message includes the number of buttons on the mouse. Example 12-5. main() { int status; fg_setmode(-1); status = fg_mouseini(); if (status < 0) printf("Mouse not available.\n"); else printf("%d button mouse found.\n",status); } You should be aware that certain Microsoft-compatible mouse drivers do not fully initialize the mouse when a program changes video modes. This problem most frequently occurs when you restore the original video mode at the end of a program that has called fg_mouseini. When changing video modes, it is recommended that you first make the mouse cursor invisible (this is described in the next section), change the video mode, and then call fg_mouseini again to initialize the mouse for the new video mode. Controlling the Mouse Cursor The mouse cursor indicates the current position of the mouse. By default, the cursor is a small white arrow in graphics modes and a one- character rectangle in text modes. After you use fg_mouseini to initialize the mouse, the mouse cursor is invisible. To make it visible, you must use the fg_mousevis routine. This routine has a single integer argument that defines the mouse cursor visibility. If it is 0, the mouse cursor will be invisible; if it is 1, the mouse cursor becomes visible. Chapter 12: Input Device Support 199 If the mouse cursor is in an area of the screen that is being updated, or if it moves into this area during the update process, you must make the mouse cursor invisible. Instead of checking if the mouse cursor is within such an area, it is generally more convenient just to make the mouse cursor invisible during screen updates and then make it visible again when the updating is finished. After you initialize the mouse, the cursor is positioned in the center of the screen. Moving the mouse of course changes the cursor position, but you can also position the mouse cursor with the Fastgraph routine fg_mousemov. This routine has two arguments that specify the new horizontal and vertical cursor position. The position is expressed in screen space units for graphics modes, while it is expressed in character cells for text modes. The fg_mousemov routine moves the cursor whether or not it is currently visible. Sometimes it is useful to restrict the mouse cursor to a specific area of the screen. The Fastgraph routine fg_mouselim prevents the mouse cursor from moving outside the specified rectangular area. It requires four arguments that specify the minimum horizontal coordinate, maximum horizontal coordinate, minimum vertical coordinate, and maximum vertical coordinate of this area. Again, the coordinates are expressed in screen space units for graphics modes and character cells for text modes. One of the most important functions of the mouse driver is to translate the horizontal and vertical mouse movements into a position on the screen. The mouse reports these movements to the mouse driver in units called mickeys (one mickey is about 1/200 of an inch). By default, moving the mouse 8 mickeys in the horizontal direction moves the mouse cursor one horizontal pixel. Similarly, moving the mouse 16 mickeys vertically moves the cursor one vertical pixel. Fastgraph provides a routine named fg_mousespd that can change these values, which effectively allows you to control the speed at which the mouse cursor moves relative to the movement of the mouse itself. The fg_mousespd routine requires two arguments that define the number of mickeys required for eight pixels of mouse cursor movement. The first argument specifies this for the horizontal direction, and the second for the vertical direction. Example 12-6, which runs in any graphics mode, demonstrates the fg_mousevis, fg_mousemov, fg_mouselim, and fg_mousespd routines. The program first establishes the video mode, initializes the mouse, and fills the screen with a white rectangle. Next, the program calls fg_mousevis to make the mouse cursor visible and then calls fg_mouselim to restrict the mouse cursor to an area one-fourth the size of the screen, centered in the middle of the screen. At this point you should move the mouse cursor around the screen to see the effect of fg_mouselim and note the speed at which the cursor moves relative to the mouse itself. The program continues when you press any key. The program then uses fg_mousemov to move the mouse cursor to each corner of the region established by fg_mouselim. The call to fg_waitfor keeps the cursor in each corner for two seconds, unless you move the mouse. Note how the program tries to move the mouse cursor to each corner of the screen, but since doing so would move the cursor outside the defined region of movement, fg_mousemov just positions the cursor at the nearest point possible within this region. The last call to fg_mousemov moves the cursor back to the middle of the screen. After doing this, the program calls fg_mousespd to change the mouse cursor speed. The values passed to 200 Fastgraph User's Guide fg_mousespd (16 and 32) are twice the defaults and therefore make you move the mouse twice as far as before to move the mouse cursor the same distance. When you run the program, compare the mouse sensitivity to the original speed. After a keystroke, the program returns to DOS. Example 12-6. main() { int maxx, maxy; int old_mode; old_mode = fg_getmode(); fg_setmode(fg_automode()); if (fg_mouseini() < 0) { fg_setmode(old_mode); fg_reset(); exit(); } maxx = fg_getmaxx(); maxy = fg_getmaxy(); fg_setcolor(15); fg_rect(0,maxx,0,maxy); fg_mousevis(1); fg_mouselim(maxx/4,3*maxx/4,maxy/4,3*maxy/4); fg_waitkey(); fg_mousemov(0,0); fg_waitfor(36); fg_mousemov(maxx,0); fg_waitfor(36); fg_mousemov(maxx,maxy); fg_waitfor(36); fg_mousemov(0,maxy); fg_waitfor(36); fg_mousemov(maxx/2,maxy/2); fg_mousespd(16,32); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Reporting the Mouse Status It is obviously important to be able to track the mouse position and button status. The Fastgraph routines fg_mousepos and fg_mousebut enable you to do this. The fg_mousepos routine returns information about the current mouse cursor position and button status. It requires three integer arguments, all passed by reference. The first two arguments respectively receive the Chapter 12: Input Device Support 201 horizontal and vertical coordinates of the mouse cursor. These values are expressed in screen space units for graphics modes and character cells for text modes. The third argument receives a three-bit mask containing the button status as indicated below. bit number meaning 0 1 if left button pressed, 0 if not 1 1 if right button pressed, 0 if not 2 1 if middle button pressed, 0 if not For example, if both the left and right buttons are pressed, the button status will be set to 3. If the mouse only has two buttons, bit 2 will always be zero. Another routine, fg_mousebut, is available for returning the number of button press or release counts that have occurred since the last check, or since calling fg_mouseini. Each mouse button maintains its own separate counters, so fg_mousebut returns this information for a specific button. Additionally, fg_mousebut returns the horizontal and vertical position of the mouse cursor at the time the specified button was last pressed or released. The fg_mousebut routine takes four integer arguments, of which the last three are passed by reference. The first argument specifies the button of interest (1 means the left button, 2 is the right button, and 3 is the middle button). If this value is positive, button press counts will be reported. If it is negative, release counts will be reported. The second, third, and fourth arguments respectively receive the press or release count, the horizontal mouse cursor position at the time of the last press or release, and the vertical position at that same time. If the press or release count is zero, the mouse cursor position is returned as (0,0). The coordinate positions are expressed in screen space units for graphics modes and character cells for text modes. Example 12-7 runs in any graphics video mode and illustrates the use of the fg_mousepos and fg_mousebut routines. The program first establishes the video mode and then initializes the mouse (the program exits if the initialization fails). It next fills the entire screen with a white rectangle and then calls fg_mousevis to make the mouse cursor visible. The main part of example 12-7 is a while loop that polls the mouse at three-second intervals (the call fg_waitfor(54) delays the program for three seconds). Within the loop, the program first uses fg_mousebut to obtain the number of times the left mouse button was pressed in the last three seconds. Following this, the fg_mousepos routine obtains the current mouse position. The program then displays this information in the upper left corner of the screen; note how fg_mousevis is used to make the cursor invisible during graphics operations. The program continues until you press the right mouse button, checked by the call to fg_mousebut at the end of the loop. 202 Fastgraph User's Guide Example 12-7. main() { int old_mode; int buttons, count; int x, y; char string[25]; old_mode = fg_getmode(); fg_setmode(fg_automode()); if (fg_mouseini() < 0) { fg_setmode(old_mode); fg_reset(); exit(); } fg_setcolor(15); fg_rect(0,fg_getmaxx(),0,fg_getmaxy()); fg_mousevis(1); count = 0; while (count == 0) { fg_waitfor(54); fg_mousebut(1,&count,&x,&y); fg_mousepos(&x,&y,&buttons); sprintf(string,"X=%3d Y=%3d count=%4d",x,y,count); fg_mousevis(0); fg_setcolor(15); fg_rect(0,fg_xconvert(25),0,fg_yconvert(1)); fg_setcolor(0); fg_locate(0,0); fg_text(string,24); fg_mousevis(1); fg_mousebut(2,&count,&x,&y); } fg_setmode(old_mode); fg_reset(); } Defining the Mouse Cursor By default, the mouse cursor is a small white arrow in graphics modes and a one-character rectangle in text modes. In graphics modes, you can change the mouse cursor to any 16 by 16 pixel image with the Fastgraph routine fg_mouseptr (in the CGA four-color graphics modes, the cursor size is 8 by 16 pixels). You cannot change the mouse cursor shape in text modes, but you can use the Fastgraph routine fg_mousecur to define how it interacts with existing characters on the screen. Chapter 12: Input Device Support 203 Text Modes To change the mouse cursor in text modes, you must first define two 16- bit quantities called the screen mask and cursor mask. The following figure defines the format of each mask. bits meaning 0 to 7 ASCII character value 8 to 11 foreground color 12 to 14 background color 15 blink Notice how this structure parallels the character and attribute bytes associated with each character cell. The default screen mask is 77FF hex, and the default cursor mask is 7700 hex. When you position the mouse over a specific character cell, the mouse driver uses the current screen and cursor masks to determine the mouse cursor's appearance. First, the mouse driver logically ANDs the screen mask with the existing contents of that character cell. It then XORs that result with the cursor mask to display the mouse cursor. For example, consider how the mouse cursor is produced in the 80-column color text mode (mode 3). Suppose a specific character cell contains the ASCII character 0 (48 decimal, 30 hex) and an attribute byte that specifies a white (color 15) foreground on a blue background (color 1) and does not blink (blink bit 0). The binary structure of the character and its attribute are: attribute character 0 001 1111 00110000 Now let's see what happens when we apply the screen and cursor masks to the character and its attribute. attribute/character 0001 1111 0011 0000 (1F30 hex) default screen mask 0111 0111 1111 1111 (77FF hex) ------------------- result of AND 0001 0111 0011 0000 (1730 hex) default cursor mask 0111 0111 0000 0000 (7700 hex) ------------------- result of XOR 0110 0000 0011 0000 (6030 hex) The resulting character (30 hex) is the original character, but the new attribute (60 hex) represents a black foreground with a brown background and does not blink. As long as the mouse cursor remains positioned on this character cell, it would appear black on brown. 204 Fastgraph User's Guide When we use the default screen and cursor masks, the mouse cursor will always display the original character and it will not blink. The cursor foreground color will be 15-F, where F is the displayed character's foreground color. Similarly, the cursor background color will be 7-B, where B is the displayed character's background color. The default masks will virtually always produce a satisfactory mouse cursor. It is possible, however, to change the appearance of the mouse cursor in text modes by using your own screen and cursor masks. The Fastgraph routine fg_mousecur does just that. It expects two arguments, the first being the cursor mask and the second the screen mask. Example 12-8 demonstrates the use of fg_mousecur. The program displays some text and uses the default mouse cursor. After waiting for a keystroke, the program calls fg_mousecur to define a new mouse cursor. The new cursor is similar to the default cursor, but it displays the foreground colors in the opposite intensity as the default cursor. The program then waits for another keystroke before returning to DOS. Example 12-8. main() { int old_mode; int row; old_mode = fg_getmode(); fg_setmode(3); if (fg_mouseini() < 0) { fg_setmode(old_mode); fg_reset(); exit(); } fg_setattr(7,0,0); fg_rect(0,fg_getmaxx(),0,fg_getmaxy()); fg_setattr(12,7,0); for (row = 0; row < 25; row++) { fg_locate(row,34); fg_text("example 12-8",12); } fg_mousevis(1); fg_waitkey(); fg_mousecur(0x7FFF,0x7F00); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Chapter 12: Input Device Support 205 Graphics Modes Defining the mouse cursor in graphics video modes also requires creating a screen mask and cursor mask, but as one might expect, the structure of these masks is vastly different than for text modes. In fact, it closely resembles the mode-independent bit map format used by the fg_drawmap routine. Although their structure differs, the way the mouse driver uses the masks is the same as in the text modes. That is, the driver displays the mouse cursor by first logically ANDing video memory with the screen mask, and then XORing that result with the cursor mask. Let's begin by looking at the masks for the default mouse cursor in graphics modes. The size of each mask (and hence the mouse cursor) is 16 pixels wide and 16 pixels high. As mentioned earlier, the default cursor is a small white arrow with a black outline around it. Here are its screen and cursor masks expressed as binary values. screen cursor cursor mask mask appearance 1001111111111111 0000000000000000 ** 1000111111111111 0010000000000000 *x* 1000011111111111 0011000000000000 *xx* 1000001111111111 0011100000000000 *xxx* 1000000111111111 0011110000000000 *xxxx* 1000000011111111 0011111000000000 *xxxxx* 1000000001111111 0011111100000000 *xxxxxx* 1000000000111111 0011111110000000 *xxxxxxx* 1000000000011111 0011111111000000 *xxxxxxxx* 1000000000001111 0011111000000000 *xxxxx***** 1000000011111111 0011011000000000 *xx*xx* 1000100001111111 0010001100000000 *x* *xx* 1001100001111111 0000001100000000 ** *xx* 1111110000111111 0000000110000000 *xx* 1111110000111111 0000000110000000 *xx* 1111111000111111 0000000000000000 *** The mouse driver first ANDs the screen mask with video memory at the mouse cursor position. This means the screen mask 1 bits leave video memory intact, while the 0 bits change the corresponding pixels to black. Next, the mouse driver XORs the result with the cursor mask. This time the cursor mask 0 bits leave video memory unchanged, while the 1 bits change the corresponding pixels to white. This produces a mouse cursor as shown above on the right, where a dot ( ) represents an unchanged pixel, an asterisk (*) a black pixel, and an x a white pixel. The following table summarizes the cursor appearance for all possible combinations of mask bits. screen mask bit cursor mask bit resulting cursor pixel 0 0 black 0 1 white 1 0 unchanged 1 1 inverted 206 Fastgraph User's Guide The color of an "inverted" pixel is n-k, where n is the maximum color number in the current video mode, and k is the color of the pixel being replaced. Also, "black" and "white" pixels are not necessarily these colors in 16-color and 256-color modes. More correctly, "black" pixels are displayed in the color assigned to palette 0, and "white" pixels are the displayed in the color assigned to palette 15. If you're using the CGA color modes, "black" pixels are displayed in the background color, and "white" pixels appear in color 3 (whose actual color is determined by the selected CGA palette). With an understanding of the way the default mouse cursor works in graphics modes, we're now ready to define our own mouse cursor. Shown below are the screen mask, cursor mask, and resulting appearance for a solid plus- shaped cursor. The hexadecimal equivalents of the binary mask values are also given. ----- screen mask ---- ----- cursor mask ---- cursor binary hex binary hex appearance 1110000000111111 E03F 0000000000000000 0000 ...*******...... 1110000000111111 E03F 0000111110000000 0F80 ...*xxxxx*...... 1110000000111111 E03F 0000111110000000 0F80 ...*xxxxx*...... 0000000000000111 0007 0000111110000000 0F80 ****xxxxx****... 0000000000000111 0007 0111111111110000 7FF0 *xxxxxxxxxxx*... 0000000000000111 0007 0111111111110000 7FF0 *xxxxxxxxxxx*... 0000000000000111 0007 0111111111110000 7FF0 *xxxxxxxxxxx*... 0000000000000111 0007 0111111111110000 7FF0 *xxxxxxxxxxx*... 0000000000000111 0007 0111111111110000 7FF0 *xxxxxxxxxxx*... 0000000000000111 0007 0000111110000000 0F80 ****xxxxx****... 1110000000111111 E03F 0000111110000000 0F80 ...*xxxxx*...... 1110000000111111 E03F 0000111110000000 0F80 ...*xxxxx*...... 1110000000111111 E03F 0000000000000000 0000 ...*******...... 1111111111111111 FFFF 0000000000000000 0000 ................ 1111111111111111 FFFF 0000000000000000 0000 ................ 1111111111111111 FFFF 0000000000000000 0000 ................ If we wanted to make the mouse cursor hollow rather than solid, the masks and resulting cursor appearance would look like this. Chapter 12: Input Device Support 207 ----- screen mask ---- ----- cursor mask ---- cursor binary hex binary hex appearance 1110000000111111 E03F 0000000000000000 0000 ...*******...... 1110111110111111 EFBF 0000000000000000 0000 ...*.....*...... 1110111110111111 EFBF 0000000000000000 0000 ...*.....*...... 0000111110000111 0F87 0000000000000000 0000 ****.....****... 0111111111110111 7FF7 0000000000000000 0000 *...........*... 0111111111110111 7FF7 0000000000000000 0000 *...........*... 0111111111110111 7FF7 0000001000000000 0200 *.....x.....*... 0111111111110111 7FF7 0000000000000000 0000 *...........*... 0111111111110111 7FF7 0000000000000000 0000 *...........*... 0000111110000111 0F87 0000000000000000 0000 ****.....****... 1110111110111111 EFBF 0000000000000000 0000 ...*.....*...... 1110111110111111 EFBF 0000000000000000 0000 ...*.....*...... 1110000000111111 E03F 0000000000000000 0000 ...*******...... 1111111111111111 FFFF 0000000000000000 0000 ................ 1111111111111111 FFFF 0000000000000000 0000 ................ 1111111111111111 FFFF 0000000000000000 0000 ................ Note that the center bit defined in the cursor mask causes the corresponding pixel in video memory to be inverted. There is one more item needed to define a graphics mode mouse cursor completely. That item is the hot spot, or the actual screen position used or reported by the mouse driver. For the plus-shaped cursors just constructed, it would be sensible to define the hot spot in the center of the plus. The hot spot is specified relative to the upper left corner of the cursor, so its position within the cursor would be (6,6) -- that is, six pixels to the right and six pixels below the upper left corner. You can specify the hot spot offsets using negative values or values above 15 to position it outside the mouse cursor matrix if desired. The Fastgraph routine fg_mouseptr defines a mouse cursor in graphics modes. The first of its three arguments is a 32-element integer array, passed by reference. The array's first 16 elements contain the screen mask, and its second 16 elements contain the cursor mask. The remaining two arguments respectively specify the horizontal and vertical offsets for the hot spot. The fg_mouseptr routine has no effect in a text video mode. Example 12-9 is similar to example 12-8. It shows how to define a graphics mode mouse cursor using fg_mouseptr. The values stored in the solid and hollow arrays define the screen and cursor masks for the solid and hollow plus-shaped mouse cursors discussed earlier. After making the mouse cursor visible, the program uses the default mouse cursor until a key is pressed. Following this, it changes to the solid cursor. After another keystroke, the program changes to the hollow cursor. The program waits for yet another keystroke before returning to DOS. When you run example 12-9, you should compare the physical differences between the three mouse cursors it uses. 208 Fastgraph User's Guide Example 12-9. int solid[] = {0xE03F,0xE03F,0xE03F,0x0007,0x0007,0x0007,0x0007,0x0007, 0x0007,0x0007,0xE03F,0xE03F,0xE03F,0xFFFF,0xFFFF,0xFFFF, 0x0000,0x0F80,0x0F80,0x0F80,0x7FF0,0x7FF0,0x7FF0,0x7FF0, 0x7FF0,0x0F80,0x0F80,0x0F80,0x0000,0x0000,0x0000,0x0000}; int hollow[] = {0xE03F,0xEFBF,0xEFBF,0x0F87,0x7FF7,0x7FF7,0x7FF7,0x7FF7, 0x7FF7,0x0F87,0xEFBF,0xEFBF,0xE03F,0xFFFF,0xFFFF,0xFFFF, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0200,0x0000, 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000}; main() { int old_mode; int column, row, last_row; old_mode = fg_getmode(); fg_setmode(fg_automode()); if (fg_mouseini() < 0) { fg_setmode(old_mode); fg_reset(); exit(); } fg_setcolor(15); fg_rect(0,fg_getmaxx(),0,fg_getmaxy()); fg_setcolor(12); column = fg_xalpha(fg_getmaxx()/2) - 6; last_row = fg_yalpha(fg_getmaxy()) + 1; for (row = 0; row < last_row; row++) { fg_locate(row,column); fg_text("example 12-9",12); } fg_mousevis(1); fg_waitkey(); fg_mouseptr(solid,6,6); fg_waitkey(); fg_mouseptr(hollow,6,6); fg_waitkey(); fg_setmode(old_mode); fg_reset(); } Chapter 12: Input Device Support 209 CGA Considerations The mouse driver treats the screen and cursor masks differently in the CGA four-color graphics modes (modes 4 and 5) than in the other graphics modes. In the CGA modes, each pair of mask bits corresponds to one pixel. This means the masks more closely resemble the mode-specific format used by fg_drwimage instead of the mode-independent format of fg_drawmap. Fastgraph uses a different default mouse cursor for modes 4 and 5. Its screen and cursor masks, as well as the resulting cursor appearance, are shown in the following diagram. 210 Fastgraph User's Guide screen cursor cursor mask mask appearance 0000111111111111 0000000000000000 ** 0000001111111111 0011000000000000 *** 0000000011111111 0011110000000000 **** 0000000000111111 0011111100000000 ***** 0000000000001111 0011111111000000 ****** 0000000000000011 0011111111110000 ******* 0000000000000011 0011111100000000 ******* 0000000000111111 0011111110000000 ***** 0000000000001111 0011000011000000 ****** 0000110000001111 0000000011000000 ** *** 1111111100000011 0000000000110000 *** 1111111100000011 0010000000110000 *** 1111111111000011 0000000000000000 ** 1111111111111111 0000000000000000 1111111111111111 0000000000000000 1111111111111111 0000000000000000 As you can see, the resulting mouse cursor is eight pixels wide instead of 16. Another important point concerning mouse cursors in modes 4 and 5 is the possibility of pixel bleeding, or the changing of colors within the mouse cursor as it moves horizontally. Bleeding will occur if you use the bit pairs 01 or 10 in either mask to represent a pixel. In the default masks for modes 4 and 5, note that only the binary values 00 and 11 appear as bit pairs. Keep this in mind if you create your own masks in these video modes. Joystick Support The third type of input device supported by Fastgraph is the joystick. Although joysticks are not as popular as mice, they generally are preferable in cases where a user's reactions are critical, such as in an arcade-style game. Fastgraph includes routines for initializing a joystick, reading a joystick's position or button status, and making a joystick behave analogously to the keyboard. These routines are independent of the rest of Fastgraph and thus do not require that you first call the fg_setmode routine. Joysticks are connected to a system by means of a game port. The PCjr and Tandy 1000 systems come equipped with two game ports, and hence support two joysticks. On other systems in the IBM family, you can install a game port card that contains either one or two game ports. If the card only has one game port, you can use a splitter cable to fork two joysticks into the port. Initializing Joysticks Before you can use any of Fastgraph's joystick support routines with a specific joystick, you must initialize that joystick. The fg_initjoy routine performs this task. This routine requires a single integer argument that specifies which joystick to initialize, either 1 or 2. If successful, fg_initjoy returns 0 as the function value. If the machine has no game port, or if the requested joystick is not connected to the game port, fg_initjoy Chapter 12: Input Device Support 211 returns -1. When you use fg_initjoy, the joystick being initialized must be centered (that is, the stick itself must not be tilted in either direction). Example 12-10 uses the fg_initjoy routine to attempt to initialize both joysticks. For each joystick, the program prints a message stating whether or not the initialization was successful. Example 12-10. main() { if (fg_initjoy(1) < 0) printf("Joystick 1 not available.\n"); else printf("Joystick 1 found.\n"); if (fg_initjoy(2) < 0) printf("Joystick 2 not available.\n"); else printf("Joystick 2 found.\n"); } Reporting Joystick Status Each joystick is capable of reporting three items: its horizontal position, its vertical position, and the button status. Fastgraph includes routines for obtaining each of these quantities. The fg_getxjoy and fg_getyjoy routines respectively return the horizontal and vertical position of the indicated joystick. Both routines require a single integer argument, whose value is either 1 or 2, to identify the joystick. The requested position is returned as the function value. Horizontal coordinates increase as the joystick moves to the right, while vertical coordinates increase as the joystick moves downward. If fg_initjoy failed to initialize the specified joystick, or if your program hasn't yet called fg_initjoy, both fg_getxjoy and fg_getyjoy will return the value -1. Joystick characteristics vary more than those of any other input device. The values returned by fg_getxjoy and fg_getyjoy depend on the system's processor speed and the brand of joystick used. It often suffices just to know the joystick position relative to its previous position, in which case the actual coordinate values do not matter. However, if you must rely on specific coordinate values, your program must perform some type of manual joystick calibration and then scale the coordinates reported by fg_getxjoy and fg_getyjoy as needed. The other piece of information joysticks provide is the button status. Most joysticks have two buttons, called the top and bottom buttons. Others have three buttons, but one of them duplicates the functionality of another (for example, a joystick might have one bottom button on its left side and another on its right side). The Fastgraph routine fg_button returns the joystick button status as its function value. Like fg_getxjoy and fg_getyjoy, the fg_button routine requires a single argument that specifies the joystick number. The meaning of the returned value is shown below. 212 Fastgraph User's Guide value meaning 0 neither button pressed 1 top button pressed 2 bottom button pressed 3 top and bottom buttons pressed It is not necessary to call fg_initjoy before using fg_button. If the specified joystick is not present, the fg_button routine will return a value of 0. Example 12-11 uses fg_getxjoy, fg_getyjoy, and fg_button to poll both joysticks at half-second intervals. It then displays the joystick number (1 or 2), horizontal position, vertical position, and button status for each joystick. As the program runs, you can move the joysticks and watch how the movements affect the displayed coordinate values. The program continues doing this until you press Ctrl/C or Ctrl/Break to stop it. Example 12-11. main() { int b, x, y; fg_initjoy(1); fg_initjoy(2); while (1) { x = fg_getxjoy(1); y = fg_getyjoy(1); b = fg_button(1); printf("1: %3d %3d %1d\n",x,y,b); x = fg_getxjoy(2); y = fg_getyjoy(2); b = fg_button(2); printf("2: %3d %3d %1d\n\n",x,y,b); fg_waitfor(9); } } There are two ways of effectively monitoring joystick button status. One is to call fg_button at numerous places in your program and then take appropriate action depending on the button status. However, the preferable method is to extend the BIOS time-of-day interrupt to check the button status at each clock tick (there are 18.2 clock ticks per second), set a flag if a button is pressed, and then check the flag as needed in your program. Information on modifying the BIOS time-of-day interrupt appears in Appendix C of this document. Chapter 12: Input Device Support 213 Keyboard Emulation Although we can use the fg_getxjoy and fg_getyjoy routines to monitor relative joystick movements, it is usually easier to do this with another Fastgraph routine, fg_intjoy. This routine is similar to the fg_intkey routine in that it returns two values that are equivalent to the standard or extended keyboard codes for analogous keystrokes. The fg_intjoy routine needs three arguments. The first argument specifies the joystick number, either 1 or 2. The second and third arguments, both one-byte quantities passed by reference, receive the standard and extended keyboard codes analogous to the joystick movement and button status. The second argument receives a value of 13 (the standard keyboard code for the Enter key) if any joystick button is pressed; it receives a value of 0 if not. The third argument receives a value corresponding to the extended keyboard code for one of the directional keys on the numeric keypad, as summarized in the following table. joystick position corresponding key extended key code up and left Home 71 up up arrow 72 up and right PgUp 73 left left arrow 75 centered (no action) 0 right right arrow 77 down and left End 79 down down arrow 80 down and right PgDn 81 The fg_intjoy routine will set both key code arguments to zero if the specified joystick has not yet been initialized. Example 12-12 is similar to example 12-10, but it uses fg_intjoy in place of fg_getxjoy and fg_getyjoy to report relative joystick position. This program does not report the joystick button status as example 12-10 does, but you could readily add this feature to it. Example 12-12. main() { char key, aux; fg_initjoy(1); fg_initjoy(2); while (1) { fg_intjoy(1,&key,&aux); printf("1: %2d %2d\n",key,aux); fg_intjoy(2,&key,&aux); printf("2: %2d %2d\n\n",key,aux); fg_waitfor(9); } } 214 Fastgraph User's Guide Special Joystick Considerations If you develop a program that supports only one joystick, you should use joystick 1. The reasons for this are twofold. First, it will make your program consistent with most other products that support joysticks. Second, and perhaps more importantly, many Tandy 1000 series machines cannot determine if joystick 2 is present when neither joystick is connected. This means if you use joystick 2 instead of joystick 1 in a single joystick program, you won't be able to tell if a joystick is available when running on a Tandy 1000. Summary of Input Routines This section summarizes the functional descriptions of the Fastgraph routines presented in this chapter. More detailed information about these routines, including their arguments and return values, may be found in the Fastgraph Reference Manual. FG_BUTTON returns information about the state of either joystick's buttons. FG_CAPSLOCK determines the state of the CapsLock key. FG_GETKEY waits for a keystroke (or reads the next entry from the BIOS keyboard buffer). It returns the keystroke's standard or extended keyboard code. FG_GETXJOY and FG_GETYJOY return the horizontal and vertical coordinate position of the specified joystick. The actual coordinates depend on the processor speed and brand of joystick used. FG_INITJOY initializes joystick 1 or 2 and must be called before using fg_getxjoy, fg_getyjoy, or fg_intjoy. It returns a status code indicating whether or not the initialization was successful. FG_INTJOY returns the standard and extended keyboard codes analogous to the current position and button status of the specified joystick. FG_INTKEY reads the next entry from the BIOS keyboard buffer and returns the keystroke's standard or extended keyboard code. It is similar to fg_getkey, but it does not wait for a keystroke if the keyboard buffer is empty. FG_MOUSEBUT returns information about mouse button press or release counts, as well as the mouse cursor position at the time of the last button press or release. FG_MOUSECUR defines the appearance of the mouse cursor in text video modes. FG_MOUSEINI initializes the mouse and must be called before any of Fastgraph's other mouse support routines. It returns an error status if the mouse driver has not been loaded, or if the mouse is not connected. Chapter 12: Input Device Support 215 FG_MOUSELIM defines the rectangular area in which the mouse cursor may move. FG_MOUSEMOV moves the mouse cursor to the specified character cell (in text modes) or screen space position (in graphics modes). FG_MOUSEPOS returns the current mouse position and button status. FG_MOUSEPTR defines the shape and appearance of the mouse cursor in graphics video modes. FG_MOUSESPD defines the number of mickey units per eight pixels of cursor movement. This effectively controls the speed at which the mouse cursor moves relative to the movement of the mouse itself. FG_MOUSEVIS makes the mouse cursor visible or invisible. FG_NUMLOCK determines the state of the NumLock key. FG_SCRLOCK determines the state of the ScrollLock key (which is not present on some keyboards). FG_SETCAPS controls the state of the CapsLock key. FG_SETNUM controls the state of the NumLock key. FG_WAITKEY flushes the BIOS keyboard buffer (that is, removes any type- ahead characters) and then waits for another keystroke. 216 Fastgraph User's Guide Chapter 13 Sound Effects 218 Fastgraph User's Guide Overview In the realm of the IBM PC and PS/2 family of systems, a sound is defined by its frequency, duration, and volume. The frequency of a sound is measured in units called Hertz. While the PC and PS/2 can produce sounds ranging from 18 to over 1 million Hertz, the average human can hear sounds between 20 and about 20,000 Hertz. The length of a sound, called its duration, is expressed in clock ticks; there are either 18.2 of 72.8 clock ticks per second, depending on the method used to produce the sound. Finally, the volume determines the loudness of the sound. As we'll see in this chapter, we can control a sound's volume only on the PCjr and Tandy 1000 systems. Fastgraph contains several different methods for producing sound effects. These include single tones, a series of tones expressed numerically, or a series of tones expressed as musical notes. The sound effects may be discrete, continuous, or performed at the same time as other activity. The sound-related routines are independent of the other parts of Fastgraph and do not require any initialization routines be called. Sound Sources All members of the PC and PS/2 families can produce sounds using the 8253-5 programmable timer chip and the internal speaker. This method is essentially limited to producing single sounds of given frequencies and durations, although we can combine these sounds to create interesting audio effects or play music. When we use this technique, we have no control over the sound volume. In fact, sound volumes often vary slightly on different systems because the physical properties of the speaker and its housing are not always identical. The PCjr and Tandy 1000 systems have an additional, more powerful chip for producing sounds. This is the Texas Instruments SN76496A sound chip, called the TI sound chip for short. The TI sound chip has three independent voice channels for producing pure tones, and a fourth channel for generating periodic or white noise. Each voice channel has a separate volume control that allows us to control the loudness of the sound it emits. Synchronous Sound A sound effect is said to be synchronous if it is produced while no other activity is being performed. In other words, a program makes a synchronous sound by starting the sound, waiting for a specified duration, and then stopping the sound. The program must wait for the sound to complete before doing anything else. As long as the duration is relatively short, the fact that the sound is synchronous has little or no effect on the program's execution speed. Fastgraph includes routines for producing synchronous sound using either the 8253-5 programmable timer or the TI sound chip. The fg_sound routine uses the programmable timer to produce a sound of a given frequency and duration. The frequency, defined by the first argument, is expressed in Hertz and must be an integer value between 18 and 32,767. The second argument defines the duration and is expressed in clock ticks; Chapter 13: Sound Effects 219 there are 18.2 clock ticks per second. If the duration is zero or negative, the sound will continue until it is stopped with the fg_quiet routine. Example 13-1 uses the fg_sound routine to create different sound effects, pausing for one second between each. It first produces three distinct sounds of 20, 100, and 1,000 Hertz. Each of these sounds lasts for approximately 1/6 of a second (three clock ticks). The program then makes a warbling noise by quickly alternating sounds of similar frequencies. Finally, the program creates a sliding tone of increasing frequencies between 100 and 500 Hertz. Each tone in this sequence lasts for two clock ticks, so it takes about 4.5 seconds to play the entire sequence. In all cases, example 13-1 displays an identifying message just before each sound. Example 13-1. main() { int freq; printf("20 Hz tone...\n"); fg_sound(20,3); fg_waitfor(18); printf("100 Hz tone...\n"); fg_sound(100,3); fg_waitfor(18); printf("1000 Hz tone...\n"); fg_sound(1000,3); fg_waitfor(18); printf("warble...\n"); fg_sound(400,1); fg_sound(410,1); fg_sound(400,1); fg_sound(410,1); fg_waitfor(18); printf("sliding tone from 100 to 500 Hz...\n"); for (freq = 100; freq <= 500; freq+=10) fg_sound(freq,2); } The fg_voice routine is analogous to the fg_sound routine, but it uses the TI sound chip rather than the programmable timer to create sound. For this reason, the fg_voice routine can only be used on the PCjr or Tandy 1000 systems. The TI sound chip allows us to control the volume of a sound, and it also offers four distinct voice channels. Thus, fg_voice requires two additional arguments besides frequency and duration to define the voice channel and sound volume. The first argument to fg_voice defines the voice channel, as shown below. 220 Fastgraph User's Guide value meaning 1 voice channel #1 2 voice channel #2 3 voice channel #3 4 voice channel #4, periodic noise 5 voice channel #4, white noise If we use voice channels 1, 2, or 3, the second argument defines the sound frequency in Hertz, between 18 and 32,767. If we use voice channel 4, however, the second argument instead is a value that represents a specific frequency, as shown in this table. value frequency 0 512 Hertz 1 1024 Hertz 2 2048 Hertz The third argument defines the sound volume. It must be between 0 and 15, where 0 is silent and 15 is loudest. The fourth argument defines the sound duration in clock ticks. As with the fg_sound routine, there are 18.2 clock ticks per second, and if the duration is zero or negative, the sound will continue until stopped with the fg_quiet routine. Example 13-2 uses the fg_voice routine to create different sound effects using the TI sound chip. As in example 13-1, there is a pause of one second between each. The program first calls the fg_testmode routine to verify it is running on a PCjr or Tandy 1000 system (video mode 9 is only available on these systems). If so, the program uses voice channel #4 to produce a 2,048 Hertz periodic noise, followed by white noise of the same frequency. Both sounds are emitted at the maximum volume level (15) and last for about 1/6 of a second each (three clock ticks). After these noises, example 13-2 produces a 500 Hertz tone of increasing volume. In all cases, the program displays an identifying message just before each sound. Example 13-2. main() { int volume; if (fg_testmode(9,0) == 0) { printf("This program requires a PCjr or "); printf("a Tandy 1000 system.\n"); exit(); } Chapter 13: Sound Effects 221 printf("2048 Hz periodic noise...\n"); fg_voice(4,2,15,3); fg_waitfor(18); printf("2048 Hz white noise...\n"); fg_voice(5,2,15,3); fg_waitfor(18); printf("500 Hz tone of increasing volume...\n"); for (volume = 1; volume <= 15; volume++) { fg_voice(1,500,volume,0); fg_waitfor(4); } fg_quiet(); } Note how example 13-2 uses a duration of zero (continuous sound) and the fg_waitfor routine to specify the duration for each volume level the 500 Hertz tone sequence. This causes the transition between changes in volume to blend better with each other. The fg_quiet routine, which stops continuous sound started with the fg_sound or fg_voice routines, ends the sound after the final volume level. The fg_sound and fg_voice routines each produce a single sound. We've seen how we can combine sounds to produce sound effects, but still the individual sounds are defined numerically -- that is, by a certain frequency and duration. It is often easier to create sounds in terms of musical notes, and for this reason Fastgraph includes a routine fg_music that produces such sounds. The fg_music routine uses the programmable timer to produce synchronous sound; it does not support the TI sound chip. The fg_music routine has a single argument called the music string, which is passed by reference as a byte array or character string. The music string is simply a variable-length sequence of music commands, followed by a dollar-sign ($) terminator. Music commands are summarized in the following table. command meaning A thru G Play the specified note in the current octave. # May be appended to a note character (A through G) to make that note sharp. . May be appended to a note character (A through G) or a sharp (#) to extend that note by half its normal length. Multiple dots may be used, and each one will again extend the note by half as much as the previous extension. Ln Set the length of subsequent notes and pauses. The value of n is an integer between 222 Fastgraph User's Guide 1 and 64, where 1 indicates a whole note, 2 a half note, 4 a quarter note, and so forth. If no L command is present, L4 is assumed. On Set the octave for subsequent notes. The value of n may be an integer between 0 and 6 to set a specific octave. It can also be a plus (+) or minus (-) character to increment or decrement the current octave number. Octave 4 contains middle C, and if no O command is present, O4 is assumed. P Pause (rest) for the duration specified by the most recent L command. Sn Set the amount of silence between notes. The value of n is an integer between 0 and 2. If n is 0, each note plays for the full period set by the L command (music legato). If n is 1, each note plays for 7/8 the period set by the L command (music normal). If n is 2, each note plays for 3/4 the period set by the L command (music staccato). If no S command is present, S1 is assumed. Tn Set the tempo of the music (the number of quarter notes per minute). The value of n is an integer between 32 and 255. If no T command is present, T120 is assumed. The fg_music routine ignores any other characters in the music string. It also ignores command values outside the allowable range, such as T20 or O8. Example 13-3 illustrates some uses of the fg_music routine. The program plays the first few bars of "Mary Had a Little Lamb", followed by the musical scale (including sharps) in two octaves, and finally the introduction to Beethoven's Fifth Symphony. There is a pause of one second between each piece of music, and the program displays the titles before playing the music. Blank characters appear in the music strings to help make them more readable. Example 13-3. main() { printf("Mary Had a Little Lamb...\n"); fg_music("T150 L8 EDCDEEE P DDD P EGG P EDCDEEE L16 P L8 EDDEDC$"); fg_waitfor(18); printf("up the scale in two octaves...\n"); fg_music("L16 CC#DD#EFF#GG#AA#B O+ CC#DD#EFF#GG#AA#B$"); fg_waitfor(18); printf("Beethoven's Fifth Symphony...\n"); fg_music("T180 O2 L2 P L8 P GGG L2 D# L24 P L8 P FFF L2 D$"); } Chapter 13: Sound Effects 223 Asynchronous Sound Sounds made concurrently with other activity in a program are said to be asynchronous. Fastgraph's routines that produce asynchronous sound just start the sound and then immediately return control to the calling program. The sounds will automatically stop when the end of the sequence is reached, and you can also stop it on demand before that time. None of Fastgraph's asynchronous sound routines have any effect if there is already asynchronous sound in progress. In addition, the asynchronous sound routines temporarily disable the synchronous sound routines (fg_sound, fg_voice, and fg_music) while asynchronous sound is in progress. To expand the range of sound effects and to play fast-tempo music, Fastgraph temporarily quadruples the clock tick interrupt rate from 18.2 to 72.8 ticks per second while producing asynchronous sound. Because many disk controllers rely on the 18.2 tick per second clock rate to synchronize disk accesses, your programs should not perform any disk operations when asynchronous sound is in progress. The fg_sounds routine is the asynchronous version of the fg_sound routine. It uses the programmable timer to play a sequence of tones simultaneous to other operations. This routine expects as its first argument a variable-length integer array, passed by reference, containing pairs of frequency and duration values. As with the fg_sound routine, each frequency is expressed in Hertz and must be between 18 and 32,767. The durations are likewise measured in clock ticks, but because the interrupt rate is quadrupled, there are 72.8 instead of 18.2 ticks per second. The format of the frequency and duration array passed to fg_sounds is shown below. [0] frequency of sound 1 [1] duration of sound 1 [2] frequency of sound 2 [3] duration of sound 2 . . . [2n-2] frequency of sound n [2n-1] duration of sound n [2n] terminator (0) Note that a null character (that is, a zero byte) terminates the array. The second argument passed to fg_sounds is an integer value indicating the number of times to cycle through the frequency and duration array. If this value is 224 Fastgraph User's Guide negative, the sounds will continue until stopped with the fg_hush or fg_hushnext routines. Example 13-4 uses the fg_sounds routine to play the 100 to 500 Hertz sliding tone sequence of example 13-1. To demonstrate the sounds are being made concurrently with other operations, messages are displayed while the sequence is playing. This is controlled by the Fastgraph routine fg_playing, which returns a value of 1 if asynchronous sounds are in progress, and 0 if not. Note how the duration must be specified as 8 clock ticks (instead of 2 as in example 13-1) to compensate for the quadrupled clock tick interrupt rate. Example 13-4. main() { int i; int freq; int sound_array[83]; i = 0; for (freq = 100; freq <= 500; freq+=10) { sound_array[i++] = freq; sound_array[i++] = 8; } sound_array[i] = 0; fg_sounds(sound_array,1); while(fg_playing()) printf("Still playing...\n"); } Just as the fg_sounds routine is analogous to the fg_sound routine, there is a Fastgraph routine fg_voices that is similar to the fg_voice routine. That is, fg_voices uses the TI sound chip to play an asynchronous sequence of tones. Its arguments are identical to those of the fg_sounds routine, but the structure of the sound array is different. Its structure is: Chapter 13: Sound Effects 225 [0] channel # of sound 1 [1] frequency of sound 1 [2] volume of sound 1 [3] duration of sound 1 . . . [4n-4] channel # of sound n [4n-3] frequency of sound n [4n-2] volume of sound n [4n-1] duration of sound n [4n] terminator (0) The channel numbers, frequencies, volumes, and durations must be in the same ranges as discussed in the description of the fg_voice routine, except the durations are quadrupled because of the accelerated clock tick interrupt rate. Again, note that a null character (that is, a zero byte) terminates the array. Example 13-5 uses the fg_voices routine to play the 500 Hertz tone sequence of increasing volume introduced in example 13-2. As in example 13-4, the program displays messages while the tone sequence is playing to demonstrate the sounds are being made concurrently with other operations. Note how the duration is now 16 clock ticks (instead of 4 as in example 13-2) because of the quadrupled clock tick interrupt rate. Example 13-5. main() { int voice_array[61]; int i; int volume; if (fg_testmode(9,0) == 0) { printf("This program requires a PCjr or "); printf("a Tandy 1000 system.\n"); exit(); } i = 0; for (volume = 1; volume <= 15; volume++) { voice_array[i++] = 1; /* use channel 1 */ voice_array[i++] = 500; /* 500 Hz frequency */ voice_array[i++] = volume; /* variable volume */ 226 Fastgraph User's Guide voice_array[i++] = 16; /* duration */ } voice_array[i] = 0; fg_voices(voice_array,1); while(fg_playing()) printf("Still playing...\n"); } There is also an asynchronous version of the fg_music routine. It is called fg_musicb, and it uses the same format music string as the fg_music routine does. However, the fg_musicb routine has a second argument that specifies the number of times to cycle through the music string. If this value is negative, the music will play repetitively until you stop it with the fg_hush or fg_hushnext routine. Example 13-6 plays the same three pieces of music as example 13-3, but it does so concurrently with other operations. As the music plays, the program continuously displays the title of each piece. Note how we can take advantage of the repetition in the music string for the "up the scale" sequence by playing the sequence twice. Example 13-6. main() { fg_musicb("T150 L8 EDCDEEE P DDD P EGG P EDCDEEE L16 P L8 EDDEDC$",1); while (fg_playing()) printf("Mary Had a Little Lamb...\n"); fg_waitfor(18); fg_musicb("L16 CC#DD#EFF#GG#AA#B O+$",2); while (fg_playing()) printf("up the scale in two octaves...\n"); fg_waitfor(18); fg_musicb("T180 O2 L2 P L8 P GGG L2 D# L24 P L8 P FFF L2 D$",1); while (fg_playing()) printf("Beethoven's Fifth Symphony...\n"); } Chapter 13: Sound Effects 227 Our final example pertaining to asynchronous sound demonstrates the effects of the Fastgraph routines fg_hush and fg_hushnext, which stop sounds started with the fg_sounds, fg_voices, or fg_musicb routines. The fg_hush routine immediately stops asynchronous sound, whereas the fg_hushnext routine does so when the current cycle finishes. Neither routine has any arguments, and neither routine has any effect if no asynchronous sound is in progress. Furthermore, note that fg_hushnext has no effect unless the asynchronous sound is continuous. Example 13-7 runs in any text or graphics video mode. It displays rectangles in up to 16 colors while playing continuous asynchronous music. The program periodically checks for keystrokes with the fg_intkey routine, and it continues to play the music as long as there is no keyboard activity. If you press the Escape key, the program uses fg_hush to stop the music immediately; this causes an exit from the while loop. If you press any other key, the program uses fg_hushnext to stop the music as soon as the current repetition finishes. Once it does, the program exits the while loop because fg_playing will return a value of zero. Example 13-7. #define ESC 27 main() { int color; int old_mode; unsigned char key, aux; old_mode = fg_getmode(); fg_setmode(fg_automode()); color = 0; fg_musicb("O4 L16 CC#DD#EFF#GG#AA#B O+ CC#DD#EFF#GG#AA#B$",-1); while (fg_playing()) { color = (color + 1) & 15; fg_setcolor(color); fg_rect(0,fg_getmaxx(),0,fg_getmaxy()); fg_waitfor(4); fg_intkey(&key,&aux); if (key == ESC) fg_hush(); else if (key+aux != 0) fg_hushnext(); } fg_setmode(old_mode); fg_reset(); } 228 Fastgraph User's Guide Example 13-7 also demonstrates an important side-effect of the fg_musicb routine when playing continuous music. Any length, octave, silence, or tempo values changed within the string are not reset to their original values at the beginning of each repetition. If we did not include the O4 command at the beginning of the string, the later O+ command would cause the music to play in octaves 4 and 5 during the first repetition, 5 and 6 during the second repetition, and octave 6 for all subsequent repetitions (because you cannot increase the octave number above 6). Summary of Sound Routines This section summarizes the functional descriptions of the Fastgraph routines presented in this chapter. More detailed information about these routines, including their arguments and return values, may be found in the Fastgraph Reference Manual. FG_HUSH immediately stops asynchronous sound started with the fg_sounds, fg_voices, or fg_musicb routines. FG_HUSHNEXT is similar to fg_hush, but it does not stop the asynchronous sound until the current repetition finishes. FG_MUSIC uses the programmable timer to play a sequence of musical tones. FG_MUSICB is the asynchronous version of the fg_music routine. It uses the programmable timer to play a sequence of musical tones, concurrent with other activity. FG_PLAYING determines whether or not there is any asynchronous sound in progress. FG_QUIET stops continuous synchronous sound started with the fg_sound or fg_voice routines. FG_SOUND produces a tone of a specified frequency and duration using the programmable timer. FG_SOUNDS is the asynchronous version of the fg_sound routine. It can play a series of tones of specified frequencies and durations, concurrent with other activity. FG_VOICE produces a tone of a specified frequency, duration, and volume using one of the TI sound chip's four voice channels. FG_VOICES is the asynchronous version of the fg_voice routine. It can play a series of tones of specified frequencies, durations, and volumes, concurrent with other activity. Chapter 14 Program Timing 230 Fastgraph User's Guide Overview It is occasionally necessary to delay a program's execution for a brief period, or to determine how long it takes to execute specific sections of a program. Fastgraph includes routines to accomplish these tasks. Some of these routines are said to be real-time, which means they are independent of a system's processor speed, while the speed of others is processor-specific. This chapter describes both classes of timing routines, all of which are independent of the other parts of Fastgraph. Real-Time Routines Real-time operations center around the BIOS time-of-day clock, which is nothing more than a counter that the system automatically increments 18.2 times per second. This number is often called the clock tick interrupt rate because an interrupt routine performs the incrementing. In addition, each increment is usually called a clock tick. The Fastgraph routine fg_waitfor delays a program's execution by the number of clock ticks specified as its argument. Because fg_waitfor uses clock ticks, the actual length of the delay is the same, regardless of the system's processor speed. Even when Fastgraph's asynchronous sound routines quadruple the clock tick interrupt rate, Fastgraph compensates for this internally so that fg_waitfor always works as though the actual rate were still 18.2 times per second. Example 14-1 displays a message every five seconds that states how long the program has been running. The fg_waitfor routine produces the five- second delay by pausing 91 (18.2 times 5) clock ticks before the program displays each message. The program returns to DOS when you press any key. Example 14-1. main() { unsigned int seconds; unsigned char key, aux; seconds = 0; fg_intkey(&key,&aux); while (key+aux == 0) { fg_waitfor(91); seconds += 5; printf("%u seconds have elapsed.\n",seconds); fg_intkey(&key,&aux); } } Another common application of the fg_waitfor routine is to slow down a loop that uses the fg_intkey routine to check for keystrokes. In loops that do little else, we may call fg_intkey too rapidly without this delay, and it is then possible that the BIOS may not be able to store characters in its Chapter 14: Program Timing 231 keystroke buffer fast enough. A small delay, even one clock tick, often helps such "tight" loops. The fg_getclock routine provides an efficient way to measure time, especially differences in time. This routine has no arguments and returns a 32-bit unsigned integer (as its function value) representing the number of clock ticks since midnight. Example 14-2 demonstrates the fg_getclock routine. In response to any keystroke (except Escape, which returns control to DOS), the program displays the number of clock ticks since midnight, and also the number since the program started. The FASTGRAF.H include file is needed in this example to define fg_getclock as a function that returns a long integer. Example 14-2. #include #define ESC 27 main() { unsigned long start, ticks; unsigned char key, aux; start = fg_getclock(); fg_getkey(&key,&aux); while (key != ESC) { ticks = fg_getclock(); printf("%lu ticks since midnight.\n",ticks); printf("%lu ticks since start of program.\n\n",ticks-start); fg_getkey(&key,&aux); } } Routines Dependent on the System Speed The fg_waitfor routine described in the previous section is independent of the system's processor speed. This means the actual length of its delay is the same on any system. Another routine, fg_stall, is similar to fg_waitfor, but its delay is proportional to the processor speed. Like fg_waitfor, fg_stall has a single integer argument that specifies the length of the delay. However, instead of being expressed in clock ticks, fg_stall measures the delay in delay units. The fg_stall routine treats the length as an unsigned quantity, so the maximum number of delay units we can specify is 65,535. The following table lists the approximate number of delay units per clock tick on three typical systems. 232 Fastgraph User's Guide system delay units type per clock tick Tandy 1000 HX 675 10 MHz 80286 3,000 25 MHz 80386 11,000 Fastgraph includes a routine that determines the number of delay units per clock tick for the current processor. This is the fg_measure routine, which has no arguments and returns the number of delay units per clock tick as its function value. Once we determine this value, we can use fg_stall to delay a program's execution in real time. This provides a much more refined delay than the clock tick unit used by fg_waitfor. Example 14-3 is functionally identical to example 14-1, but it uses the fg_stall routine instead of fg_waitfor to delay the program execution. The program first calls the fg_measure routine to determine number of delay units equivalent to one clock tick. It then passes this value to fg_stall, which is called 91 times inside the for loop to create the five-second delay (because 91 clock ticks equals five seconds). The program returns to DOS when you press any key. Example 14-3. main() { int i; int units_per_tick; unsigned int seconds; unsigned char key, aux; seconds = 0; printf("Benchmarking system speed...\n"); units_per_tick = fg_measure(); printf("Benchmark completed.\n\n"); fg_intkey(&key,&aux); while (key+aux == 0) { for (i = 0; i < 91; i++) fg_stall(units_per_tick); seconds += 5; printf("%u seconds have elapsed.\n",seconds); fg_intkey(&key,&aux); } } One final point: the fg_measure routine takes a few seconds to benchmark the system speed accurately. For this reason, you should only call fg_measure once (typically at the beginning of the program) and use its return value instead of calling fg_measure throughout the program. Chapter 14: Program Timing 233 Summary of Timing Routines This section summarizes the functional descriptions of the Fastgraph routines presented in this chapter. More detailed information about these routines, including their arguments and return values, may be found in the Fastgraph Reference Manual. FG_GETCLOCK returns the number of clock ticks since midnight as its function value. This quantity is a 32-bit unsigned integer. FG_MEASURE returns the approximate number of delay units per clock tick as its function value. This quantity is proportional to the system's processor speed. FG_STALL delays a program's execution for a given number of processor- specific delay units. FG_WAITFOR delays a program's execution for a given number of clock ticks. There are 18.2 clock ticks per second, regardless of the system's processor speed. 234 Fastgraph User's Guide Chapter 15 Miscellaneous Routines 236 Fastgraph User's Guide Overview There are a few remaining Fastgraph routines that really don't fit into any of the categories discussed so far. For this reason, they are described separately in this chapter. Determining Available Memory The fg_memavail routine returns the amount of free memory (in bytes) available to DOS. It returns the amount of memory as its function value, which is a 32-bit unsigned integer. Fg_memavail has no arguments. Example 15-1 uses fg_memavail to show the effects of allocating and releasing virtual pages. When run in a video mode in which video pages 1 and 2 are physical pages, the amount of free memory remains the same because these pages use memory that is resident on the video adapter. However, in modes where pages 1 and 2 are virtual pages, the amount of free memory decreases after each call to fg_allocate and returns to its original value after the calls to fg_freepage. Note how the program requests the video mode, and that we must include the FASTGRAF.H file to define fg_memavail as a function that returns a long integer. Example 15-1. #include main() { long original, mem0, mem1, mem2; int mode, old_mode; printf("Which video mode? "); scanf("%d",&mode); if (fg_testmode(mode,0) == 0) { printf("Your system does not support that video mode.\n"); exit(); } if (fg_testmode(mode,3) == 0) { printf("Your system does not have enough memory.\n"); exit(); } original = fg_memavail(); old_mode = fg_getmode(); fg_setmode(mode); mem0 = fg_memavail(); fg_allocate(1); mem1 = fg_memavail(); fg_allocate(2); mem2 = fg_memavail(); fg_freepage(1); fg_freepage(2); Chapter 15: Miscellaneous Routines 237 fg_setmode(old_mode); fg_reset(); printf("originally = %ld\n",original); printf("after setmode = %ld\n",mem0); printf("after 1st page = %ld\n",mem1); printf("after 2nd page = %ld\n",mem2); printf("at end = %ld\n",memavail()); } Choosing the Video Memory Update Function In chapter 10, we saw how to use the fg_setfunc routine to perform XOR animation in native EGA and VGA graphics modes (modes 13 to 18). In these video modes, fg_setfunc controls the logical operation applied when the contents of video memory change. The specific operation is defined by its argument, as shown below. value of logical argument operation 0 replacement 1 and 2 or 3 exclusive or If your program does not use the fg_setfunc routine, replacement mode is always used. That is, information written to video memory replaces whatever was there before. The fg_setfunc routine does nothing in CGA, Tandy/PCjr, Hercules, or MCGA graphics modes, or in any text modes. Example 15-2 demonstrates the fg_setfunc routine. The program is similar to example 6-10 which displays 200 random rectangles on the screen. However, example 15-2 displays the rectangles in XOR mode, which means the rectangle intersections will appear in different colors. Example 15-2. #define RECTANGLES 200 #define SWAP(a,b,temp) { temp = a; a = b; b = temp; } main() { int i; int minx, maxx, miny, maxy; int old_mode; int temp; int xres, yres; if (fg_egacheck() == 0) { printf("This program requires EGA or VGA.\n"); exit(); } 238 Fastgraph User's Guide old_mode = fg_getmode(); fg_setmode(fg_automode()); fg_setfunc(3); xres = fg_getmaxx() + 1; yres = fg_getmaxy() + 1; for (i = 0; i < RECTANGLES; i++) { minx = rand() % xres; maxx = rand() % xres; miny = rand() % yres; maxy = rand() % yres; if (minx > maxx) SWAP(minx,maxx,temp); if (miny > maxy) SWAP(miny,maxy,temp); fg_setcolor(rand()%16); fg_rect(minx,maxx,miny,maxy); } fg_setmode(old_mode); fg_reset(); } Summary of Miscellaneous Routines This section summarizes the functional descriptions of the Fastgraph routines presented in this chapter. More detailed information about these routines, including their arguments and return values, may be found in the Fastgraph Reference Manual. FG_MEMAVAIL returns the amount of memory available to DOS. FG_SETFUNC specifies the logical operation (replacement, or, and, exclusive or) applied when video memory changes in the native EGA and VGA graphics modes. This routine has no effect in other video modes. Appendix A Fastgraph Utilities 240 Fastgraph User's Guide Overview This appendix describes three utilities that allow you to create and manage image files used with the fg_dispfile routine. The Fastgraph installation procedure places these utilities in the \FG directory. To use these utilities, you must either (1) copy the appropriate .EXE file from \FG to your current directory, (2) make \FG your current directory, or (3) include the \FG directory in your DOS path specification. SNAPSHOT Utility The SNAPSHOT utility is a terminate and stay resident program (TSR) to capture graphic images. It stores the image in Fastgraph's standard pixel run format. To load SNAPSHOT, just enter the command SNAPSHOT at the DOS prompt, and you'll see messages similar to the following if SNAPSHOT loads successfully. C> SNAPSHOT SNAPSHOT Version 1.0 Copyright (c) 1991 Ted Gruber Software. All Rights Reserved. Press - to activate. After SNAPSHOT loads, control returns to the DOS prompt. At this point, you can use any method whatsoever to display a graphic image and then press the Alt and left shift keys at the same time to capture the image. You don't need to load SNAPSHOT for each image capture, just once per system boot. SNAPSHOT uses about 12,000 bytes of conventional memory once loaded. To illustrate the use of SNAPSHOT, suppose you have drawn and saved an image with a commercial paint program, and you want to incorporate this image into a Fastgraph application. Once you load SNAPSHOT, activate the paint program and retrieve your image. Then press the Alt and left shift keys simultaneously and wait for the success tone (three quick medium-pitched sounds). Finally, exit the paint program to return to the DOS prompt. The sequence described in the preceding paragraph will store the captured image in Fastgraph's standard pixel run format, in a file named SNAPSHOT.nnn in the current directory. The file type nnn will be the first sequence of digits that does not result in a duplicate file name. That is, if there are no captured image files in the current directory, SNAPSHOT will use the file name SNAPSHOT.000. The next time you capture an image, SNAPSHOT will store it in SNAPSHOT.001, then SNAPSHOT.002, and so forth. If you rename or delete one of these files, SNAPSHOT will again use that file name. For example, if you delete SNAPSHOT.000 but keep SNAPSHOT.001, SNAPSHOT will store the next image it captures in SNAPSHOT.000. If for some reason SNAPSHOT is unable to capture the image, it will produce its error tone (a single low-pitched sound). The most common cause of this is trying to capture an image from a text video mode, but it will Appendix A: Fastgraph Utilities 241 also occur if there is insufficient disk space or if all 1,000 image file names are already being used. CLIP Utility The SNAPSHOT utility described in the previous section captures the entire screen. While this might be desirable in many cases, other times you'll just need a portion of the screen. CLIP is an interactive utility that you can use to reduce the size of any image stored in Fastgraph's standard or packed pixel run format. The syntax of the command for invoking the CLIP utility from the DOS prompt is CLIP input_file output_file options where input_file is the name of the original image file, and output_file is the name of the new image file. CLIP does not modify the input_file in any way, but it will overwrite the output_file if an identically named file exists in the current directory. The options list specifies one or more optional switches as shown below. option meaning /M:mode Specifies the video mode number in which to display the image. The mode value must be an integer between 0 and 21. If that video mode is a text mode, an unsupported graphics mode, or an unavailable graphics mode, CLIP displays an error message stating this. If the /M switch is not present, CLIP uses the first available video mode from the list 16, 15, 19, 13, 9, 4, 12. /P Indicates the input_file is in Fastgraph's packed pixel run format. If the /P switch is not present, CLIP assumes it is in standard pixel run format. The output_file will be in the same format as the input_file. /W:width Specifies the image width in pixels. The width value must be an integer between 1 and the horizontal resolution of the selected video mode. If the /W switch is not present, CLIP uses the horizontal resolution of the selected video mode. For example, if you wanted to create the image file PARTIAL.PPR from the packed pixel run file SCREEN.PPR, and use the native 320 by 200 EGA graphics video mode (mode 13), you would activate CLIP with the following command. CLIP PARTIAL.PPR SCREEN.PPR /P /M:13 242 Fastgraph User's Guide Because no /W switch appears in the above command and the horizontal resolution of mode 13 is 320 pixels, CLIP assumes the image width is 320 pixels. When CLIP displays the image and the plus-shaped cursor, you are ready to define one corner of the clipping region (that part of the image used to create the output_file). To do this, use the directional keys on the numeric keypad to move the cursor to the desired position, then press the Enter key. You are then ready to define the clipping region's opposite corner. Again, use the directional keys to move the cursor to the desired position. When defining the second corner, however, CLIP uses a rectangular box instead of the plus-shaped cursor to simplify marking the clipping region's boundaries. After you press Enter to define the second corner, CLIP creates the output_file and displays the resulting image width and the number of pixel runs the image contains. CLIP includes some features that may help you define the clipping region. You can change the distance the cursor moves in response to the directional keys, display the current (x,y) pixel coordinates of the cursor, and change the cursor color. The following table explains the keystrokes that CLIP recognizes when you are defining the clipping region. key meaning F1 Displays the (x,y) coordinate bar at the top of the screen. If the coordinate bar is already on, F1 removes it. F2 Displays the (x,y) coordinate bar at the bottom of the screen. If the coordinate bar is already on, F2 removes it. F3 Changes the cursor or box color from white to black, or from black to white. F4 Displays a summary of the keys CLIP recognizes when defining the clipping region. KP1 Moves the cursor one unit down and to the left. KP2 Moves the cursor one unit down. KP3 Moves the cursor one unit down and to the right. KP4 Moves the cursor one unit to the left. KP6 Moves the cursor one unit to the right. KP7 Moves the cursor one unit up and to the left. KP8 Moves the cursor one unit up. KP9 Moves the cursor one unit up and to the right. + Increases the unit of cursor movement by one pixel. The default cursor movement is one pixel. - Decreases the unit of cursor movement by one pixel. Enter Defines a corner of the clipping region at the cursor position. Esc Exits to DOS without creating the output_file. CLIP will first issue an "Exit to DOS?" prompt in case you pressed the Esc key accidentally. Appendix A: Fastgraph Utilities 243 The CLIP utility requires two video pages to run. Thus, you cannot use it in video modes 17 and 18, which have only one physical video page and no virtual video pages. CONVERT Utility The CONVERT utility lets you translate files between Fastgraph's supported image file formats. The syntax of the command for invoking CONVERT from the DOS prompt is CONVERT input_file output_file where input_file is the name of the original image file, and output_file is the name of the new translated image file. CONVERT does not modify the input_file in any way, but it will overwrite the output_file if an identically named file exists in the current directory. By default, the file type of the input_file and output_file determine the image format of that file. If the file type is .PPR, CONVERT assumes the image is in Fastgraph's packed pixel run format. If the file type is .SPR, CONVERT assumes it is in the Fastgraph's standard pixel run format. If your image files use other file types, you can explicitly specify the file's image format by appending one of the switches /PPR or /SPR to the file name. The input_file and output_file must not both specify the same image format (CONVERT will display an error message if this is so). The following command will translate the standard pixel run file PICTURE.SPR to packed format. The packed image will be stored in the file PICTURE.IMG, so we must append the switch /PPR to tell CONVERT that it will be a packed file. CONVERT PICTURE.SPR PICTURE.IMG/PPR 244 Fastgraph User's Guide Appendix B Using Fastgraph from Assembly Language 246 Fastgraph User's Guide Fastgraph uses the same naming and calling conventions as Microsoft C and Turbo C. The details of these conventions that are important to assembly language programming are summarized below. If you are calling Fastgraph routines from an assembly language program, the program must follow these conventions. All arrays and pointers are passed by reference All other items are passed by value Arguments are pushed onto the stack in reverse order 16-bit function values are returned in the AX register 32-bit function values are returned in the DX:AX register pair Fastgraph routine names are prefixed with an underscore character The small and medium model Fastgraph libraries pass arrays and pointers by near reference, while the large model library does so by far reference. This is consistent with the run-time libraries for the supported compilers. All Fastgraph routines preserve the BP, DS, DI, and SI registers. The contents of any other registers are unknown upon return from a Fastgraph routine (except for the AX register, which will either contain zero or the routine's return value). The following DOS commands show how to assemble a program (using the Microsoft Macro Assembler) and then link it with Fastgraph. In all cases, we'll assume the file EXAMPLE.ASM contains the source code for the program. The resulting executable file will be called EXAMPLE.EXE. small memory model MASM EXAMPLE.ASM; LINK /CP:4096 /E EXAMPLE,,NUL.MAP,FGS medium memory model MASM EXAMPLE.ASM; LINK /CP:4096 /E EXAMPLE,,NUL.MAP,FGM large memory model MASM EXAMPLE.ASM; LINK /CP:4096 /E EXAMPLE,,NUL.MAP,FGL Example B-1 calls the fg_getmode, fg_setmode, fg_reset, and fg_version routines from an assembly language program. The fg_getmode routine returns its function value in the AX register. The fg_setmode routine has a single argument, while fg_reset has no arguments. The fg_version routine has two arguments, both passed by reference. Notice how they are pushed on the stack in reverse order. This particular example would work with either the medium or large memory model Fastgraph libraries. To make it work with the small model library, all you would need to do is change the word "far" to "near" in the EXTRN declarations, and also change the name of the code segment from "main_TEXT" to "_TEXT". Appendix B: Using Fastgraph from Assembly Language 247 Example B-1. EXTRN _fg_getmode:far ; Fastgraph's GETMODE routine EXTRN _fg_reset:far ; Fastgraph's RESET routine EXTRN _fg_setmode:far ; Fastgraph's SETMODE routine EXTRN _fg_version:far ; Fastgraph's VERSION routine stackseg SEGMENT stack ; suppress the linker's stackseg ENDS ; "no stack segment" error message _DATA SEGMENT word public 'DATA' major dw ? ; major version number minor dw ? ; minor version number old_mode dw ? ; original video mode _DATA ENDS dgroup GROUP _DATA ASSUME cs:main_TEXT,ds:dgroup main_TEXT SEGMENT byte public 'CODE' start: mov ax,_DATA ; load segment location mov ds,ax ; into DS register call _fg_getmode ; AX = current video mode mov old_mode,ax ; save it mov ax,4 ; use video mode 4 push ax ; pass argument to SETMODE call _fg_setmode ; establish CGA four-color mode add sp,2 ; remove SETMODE argument push old_mode ; pass argument to SETMODE call _fg_setmode ; restore original video mode add sp,2 ; remove SETMODE argument call _fg_reset ; restore screen attributes lea ax,minor ; get address of minor variable push ax ; pass argument #2 to VERSION lea ax,major ; get address of major variable push ax ; pass argument #1 to VERSION call _fg_version ; get the Fastgraph version number add sp,4 ; remove VERSION arguments mov ah,76 ; function 76: terminate process xor al,al ; errorlevel 0 int 21h ; exit to DOS main_TEXT ENDS END start 248 Fastgraph User's Guide Appendix C Interrupts and Fastgraph 250 Fastgraph User's Guide Interrupts Used by Fastgraph DOS maintains an interrupt vector table that contains the addresses of 256 interrupt handlers, or routines, that perform various functions. The handlers are usually referenced by their hexadecimal interrupt number, between 00 and FF. Of these, only interrupts 60 through 67 and F1 through FF are not used by DOS or the ROM BIOS and are thus available for user applications. Certain Fastgraph routines use some of the available interrupts. Namely, the fg_music routine uses interrupt 60, the asynchronous sound routines (fg_musicb, fg_sounds, and fg_voices) use interrupts 60 and 61, all Fastgraph/Light routines use interrupt 62, and any program that runs in the native EGA or VGA graphics video modes (modes 13 through 18) uses interrupt 64. If your program defines its own interrupt handlers, it must not use any of the interrupts reserved for Fastgraph (unless, of course, it doesn't use any of the Fastgraph routines or video modes that would create a conflict). Extending the Time-of-Day Interrupt As mentioned in chapter 14, the BIOS time-of-day clock is incremented by an interrupt handler. The routine that does this is interrupt 08, a hardware interrupt automatically activated 18.2 times per second. After incrementing the clock, interrupt 08 invokes interrupt 1C, which by default references a "do-nothing" interrupt handler. While modifying interrupt 08 can be tricky, it is fairly straightforward to define our own handler for interrupt 1C. This handler will also be executed automatically 18.2 times per second. Example C-1 illustrates how to do this. When we discussed joysticks in chapter 12, we said there were two ways to monitor joystick button status. One is to intersperse calls to the fg_button routine at strategic places in your program and then take appropriate action depending on the button status. However, the problem with this scheme is the possibility of missing a button press -- if you press the joystick button and then release it between calls to fg_button, the program will not detect the joystick activity. A preferable method is to call fg_button from a handler for interrupt 1C, which essentially provides continuous monitoring of the joystick buttons. When we need the button status within our program, all we need to do is examine a global variable. Example C-1 consists of a main program (written in C) and an assembly language subroutine named int1C (suitable for the medium memory model). The main program calls int1C to define a handler for interrupt 1C. In response to any keystroke (except Escape), the program displays the button press information for each joystick since the previous keystroke (refer to the discussion of the fg_button routine for the meanings of the status values). When you press the Escape key, the program exits to DOS, but not before calling int1C to restore the original interrupt 1C handler. Appendix C: Interrupts and Fastgraph 251 Example C-1 (main program). #define ESC 27 int status1, status2; main() { unsigned char key, aux; int1C(1); status1 = 0; status2 = 0; fg_getkey(&key,&aux); while (key != ESC) { printf("\n"); printf("Joystick 1 status: %d\n",status1); printf("Joystick 2 status: %d\n",status2); status1 = 0; status2 = 0; fg_getkey(&key,&aux); } int1C(0); } We'll now examine the int1C assembly language subroutine. It actually consists of three parts: a portion to enable our interrupt handler, our handler itself, and a portion to disable the handler. When we call int1C with a nonzero argument, it saves the original data segment (so we can access the global variables within the handler), saves the original handler's address (called the vector) for interrupt 1C, and then enables our handler, which takes the form of a far procedure. The handler routine then begins to be activated at 18.2 times per second. After saving all of the important registers, the handler calls the Fastgraph routine fg_button twice, once for each joystick. The return values are logically ORed with the status1 and status2 C global variables to update the button status information. Finally, the handler restores the original registers and returns control to the point of the interrupt. Before the main program exits, it calls int1C with a zero argument to restore the original handler for interrupt 1C. No provision is made in the program to check if we had previously defined our own handler (and hence saved the original interrupt 1C vector), but this could be added with little difficulty. Example C-1 (assembly language subroutine). EXTRN _status1:word ; C global variable for button 1 status EXTRN _status2:word ; C global variable for button 2 status EXTRN _fg_button:far ; Fastgraph routine int1C_TEXT SEGMENT byte public 'CODE' 252 Fastgraph User's Guide ASSUME cs:int1C_TEXT int1C_CS dw ? ; holds original INT 1C segment address int1C_IP dw ? ; holds original INT 1C offset orig_DS dw ? ; holds original data segment _int1C PROC far PUBLIC _int1C push bp ; save caller's BP register mov bp,sp ; make BP point to argument list push si ; save caller's SI register push di ; save caller's DI register mov dx,[bp+6] ; get the flag parameter or dx,dx ; replace the old interrupt handler? jz replace ; yes, branch to that processing ; define a new handler for INT 1C define: mov ax,ds ; put current data segment in AX mov cs:orig_DS,ax ; save it in the control information area mov al,1Ch ; interrupt vector to save mov ah,53 ; function 53: get interrupt vector int 21h ; get the interrupt vector mov cs:int1C_CS,es; save the segment mov cs:int1C_IP,bx; save the offset push ds ; save our DS register mov dx,offset handler ; get offset of interrupt handler mov ax,seg handler; get segment of interrupt handler mov ds,ax ; put it in DS mov al,1Ch ; interrupt vector to change mov ah,37 ; function 37: set interrupt vector int 21h ; change the INT 1C vector to our handler pop ds ; restore our DS register jmp short return ; return to the caller ; replace the original handler for INT 1C replace: push ds ; save our DS register mov dx,cs:int1C_IP; put original INT 1C offset in DX mov ds,cs:int1C_CS; put original INT 1C segment in DS mov ah,37 ; function 37: set interrupt vector mov al,1Ch ; interrupt vector 1C int 21h ; restore original INT 1C vector pop ds ; restore our DS register return: xor ax,ax ; in case int1C was called as a function pop di ; restore our DI register pop si ; restore our SI register pop bp ; restore our BP register ret Appendix C: Interrupts and Fastgraph 253 _int1C ENDP handler PROC far ; interrupt handler that replaces INT 1C cli ; disable interrupts while handler active push ax ; save registers that may be altered push bx push cx push dx push di push si push ds push es mov ds,cs:orig_DS ; retrieve the original data segment mov ax,1 ; use joystick 1 push ax ; pass joystick number to button routine call _fg_button ; AX = button status for joystick 1 add sp,2 ; remove the argument or _status1,ax ; update status variable for joystick 1 mov ax,2 ; use joystick 2 push ax ; pass joystick number to button routine call _fg_button ; AX = button status for joystick 2 add sp,2 ; remove the argument or _status2,ax ; update status variable for joystick 2 pop es ; restore altered registers pop ds pop si pop di pop dx pop cx pop bx pop ax iret ; return from the interrupt routine handler ENDP int1C_TEXT ENDS END The example just presented is not meant to be a tutorial on interrupts; there are many good references on DOS that explain them in detail. However, an example specific to Fastgraph should be helpful. Appendix D Contents of the Compiler-Specific Libraries 254 Fastgraph User's Guide For each of the supported Fastgraph compilers except QuickBASIC, there is a compiler-specific Fastgraph library (also called the extended Fastgraph library) that contains the following routines: fg_circlew fg_getworld fg_rectw fg_setworld fg_clprectw fg_initw fg_restorew fg_swchar fg_dashrw fg_moverw fg_savew fg_swlength fg_dashw fg_movew fg_setangle fg_swtext fg_drawrw fg_paintw fg_setclipw fg_xscreen fg_draww fg_panw fg_setratio fg_xworld fg_drectw fg_pointw fg_setsize fg_yscreen fg_ellipsew fg_polygonw fg_setsizew fg_yworld All of these routines use the world space coordinate system, either directly or internally. Note that none of these routines are included in Fastgraph/Light. As mentioned in chapter 1, if your program uses any of these routines, you must link it with the standard Fastgraph library (FGx.LIB) and the compiler-specific Fastgraph library (FGcccx.LIB).