Stumpff's Thrown Together QB Graphics Tutorial, 7th edition This is a recasting of what was originally a set of emails to a specific individual to answer specific questions of his about QB and graphics. I have attempted to rewrite those messages (and add some stuff) and make a more general tutorial. However, being busy, I did not necessarily do as complete a job as I might have if I had more time. First, to do graphics in QB (or any other language/compiler/interpreter), you need to put your screen/video system into a graphics mode. If I don't go into as much detail as you might like here, it's because you can get a very good description of the video modes that QB can access by looking up SCREEN, PUT, and GET in QB's (or Qbasic's) on-line help, and other graphics topics are discussed there as well. (Another reason is because I didn't necessarily write this stuff because I know everything about QB and graphics; I wrote it because I was asked to.) I will, however, summarize the video modes and their attributes here. (If the bits per pixel and bit planes per pixel parameters don't mean anything to you, don't worry. They'll be explained eventually--at least to some degree.) QB's GRAPHICS VIDEO MODES HORIZONTAL x VIDEO MODE VERTICAL RESOLUTION BITS/PIXEL BIT PLANES/PIXEL 1 (CGA) 320 x 200 2 1 2 (CGA/mono) 640 x 200 1 1 3 (hercules) 720 x 348 1 1 4 (Olivetti/ATT) 640 x 400 1 1 7 (EGA) 320 x 200 4 4 8 (EGA) 640 x 200 4 4 9 (EGA) 640 x 350 4 4 (> 64K video memory) 9 (EGA) 640 x 350 2 2 (<= 64K video memory) 10 (EGA/monochrome monitor) 640 x 350 2 2 11 (VGA/MCGA) 640 x 480 1 1 12 (VGA/MCGA) 640 x 480 4 4 13 (VGA/MCGA) 320 x 200 8 1 There is one video mode, number 0, that isn't listed in the above table. This is a text mode; you can't do graphics in it. Before performing any graphics function in QB, you need to go into a graphics screen mode. You use the SCREEN statement to do this: SCREEN # where # is one of the mode integers listed in the first column of the above table. You need to use a video mode that your computer/video system supports. However, unless you have a relatively old computer, you probably have support for all of the listed modes except perhaps 3 and 4. (There are other uses of SCREEN. If I get to it, I'll discuss some of them. Otherwise, see your on-line help.) Since the term "pixel" comes up quite often, I'm going to make a digression and explain what that is just in case you really are a true beginner. Any picture (and even text, actually) that you have on the screen is an arrange- ment of small dots that are lit up. Those dots are called pixels. The BITS/PIXEL parameter (call it BITS) relates to the number of different pixel colors that can be on the screen at one time. The actual number of colors (call it MAXCOL) is 2^BITS (2 to the BITS power). For example, SCREEN 12 allows 16 colors to be displayed and SCREEN 13 allows 256. Colors are represented by integers, called attributes. An attribute is in the range 0 to MAXCOL - 1. (A lot of quantities in Basic and other languages are counted from zero, not one. And for now, I'm going to act as if "color" and "attribute" are synonyms. They are NOT, but don't worry about it right now.) The BIT PLANES/PIXEL parameter (call it PLANES) relates to how the graphic data is stored in video memory. There's two reasons why you might want to be concerned with such a detail. One is if you want to bypass QB's intrinsic graphics functions and access video memory yourself. To do that, you're going to need a lot more information than I'm planning on giving you here. (For one thing, I don't *have* all of the information you would need.) The other reason you might need to worry about such nitpicking details is if you want to save a picture on the screen into an array. Later, I'll get to how to use the parameters in the above table to calculate how big that array has to be. Another factor that may affect your choice of video mode is the resolution. In the above table, the horizontal resolution is the number of pixels you can put on a single row and the vertical resolution is the number of rows of pixels that you can have on the screen. And this gets us to coordinate systems. All of QB's graphics functions work in a rectangular (also called "cartesian") (x,y) coordinate system. If the resolution is W x H, x ranges from 0 to W - 1 and y ranges from 0 to H - 1. Further, x and y are integers. (A coordinate of (6.5,8.9) doesn't make any sense, for example.) x increases to the right and y increases downwards. (0,0), the coordinate origin, is at the top lefthand corner of your screen. (The WINDOW function can be used to make y increase upwards or even to allow negative (x,y) coordinates. However, this function is another one of the things that keep me from claiming to be a true graphics expert. It's one of the things I'm not going to discuss further here. The VIEW function can be used to change the location of the coordinate origin--and constrain the maximum number of pixels you can put across or down in your picture. I'll discuss VIEW if I get to it.) Okay, we finally get to how to put pixels on the screen. QB's most primitive or fundamental functions for doing that are PSET and PRESET. PSET is used simply to plot a single pixel on the screen. (But you can of course use PSET over and over again.) The syntax is simply PSET (x,y), attribute where attribute specifies the color of the pixel to be plotted. PRESET serves the same basic function as PSET and has the same syntax. It simply plots the pixel in "inverse video." PSET and PRESET are most useful for generating pictures that can't be described by simple mathematical shapes such as circles, lines, or boxes. Although you can certainly use PSET to draw such shapes, QB has "higher order" functions to do this: CIRCLE and LINE. CIRCLE draws circles (if you couldn't guess). But it also draws ellipses and arcs. The syntax is (and this is all from the on-line help, to which you are referred for anything I left out) CIRCLE (x,y), radius[, [color][, [start][, [end][, aspect]]]] The [] symbols mean that these parameters are optional. You can leave them off if you want to use default values. (The default color is the foreground color--set by the COLOR statements; I guess I'll have to get to that later. (x,y) gives the screen coordinates of the center of the circle. start and end are angles in radians that specify the beginning and ending angles that define the arc. (It only draws a closed circle if the difference between end and start is 2 * PI. If you don't specify start and end, the default values are 0 and 2 * PI--i.e., you want to draw a closed circle/ellipse.) aspect specifies the shape of the figure. The default is is the value required to draw a "perfect" cirular arc (as opposed to an elliptical one). This value is defined as 4 * (H / W) / 3. (Why doesn't aspect = 1 give a circular arc, you ask? Because screen pixels are actually rectangular, not square.) The radius parameter specifies the radius (in pixels) of a circular arc. For nonunity aspects, radius is the x-radius if aspect < 1 and it's the y-radius for aspect > 1. You can also make a pie-shape with CIRCLE. From the on-line help, I quote (I'm making use of the "fair-use clause" in the copyright law): If start or end is negative, then CIRCLE draws a radius to that point on the arc and treats the angle as if it were positive. In using CIRCLE, if you want to leave out an optional parameter in the middle but specify a later one, make sure you put a comma in the location where you're leaving out a parameter (i.e., you may have two or more commas in a row). (If you're leaving off the last parameter, don't include what would otherwise be the trailing comma.) LINE draws lines and boxes (and it will also fill the box with the color it's drawn in if you want). The syntax is (again from the on-line help) LINE (x1,y1) - (x2,y2)[, [color][, [B[F]][, style]]] Again the [] symbols refer to optional parameters. The default color is again the current foreground color. If you leave out the fourth parameter (B or BF), you get a line. Well, actually, it's a line segment. (x1,y1) give the coordinates of the beginning of the segment and (x2,y2) specifies the end. If the fourth parameter is B, you get a box. (x1,y1) then specifies the upper lefthand corner and (x2,y2) specifies the lower right- hand corner coordinates. If you say BF, you still get a box (and the first two parameters have the same meaning), but the box is filled with pixels (of the specified/default color). I'm discussing the style parameter in a separate paragraph because it's a little tricky and might need special/separate attention. This parameter specifies how the line segment is to be drawn--solid, dashed, dotted, dash- dot, whatever. It is a two-byte integer. The 16 bits in that integer specify the pattern. The default style is -1, which gives a solid line segment. Why? Because -1 converted to binary is simply 16 ones, 1111111111111111. A bit in the bit pattern corresponds to a pixel in the line segment. (If the line segment is more than 16 pixels long, just imagine the bit pattern in style repeating itself.) If the bit is unity, the corresponding pixel is lit. If the bit is zero, the pixel is not lit. (By the way, a lit pixel simply means that the attribute is nonzero.) Hence, in the -1 bit pattern, all pixels are to be lit--you get a solid line. If, for example, style = -13108 (or &HCCCC, if you know how to use hex numbers), you get a dashed line segment--because that integer has the binary representation 1100110011001100. Obviously, the tricky part here is knowing how to convert binary numbers to decimal or hexadecimal. QB has the functionality to convert hex to decimal--just put &H in front of the hex number. But curiously, since an advanced use of LINE requres reference to the binary number representation, it does not have a binary number converting function. It is, however, not hard. Here's how you convert a binary number to decimal. Let's say you have a binary number with N bits in it. These bits are numbered/labelled from 0 to N - 1. Bit 0 is at the right and bit N - 1 is at the left. Let i refer to this bit position (i = 0 to N - 1). Let the value of the particular bit (0 or 1) be b. The decimal representation of the binary number is the sum of all quantities calculated as b * 2^i. For the style option of LINE, N = 16. One snag is that the method of converting binary to decimal just outlined gives an unsigned integer between 0 and 65,535 for such a two-byte value. QB uses *signed* two-byte integers. Such integers lie between -32,768 and 32,767, inclusive. So, if the above binary to decimal procedure gives you a value larger than 32,767, subtract 65,536 from it before assigning the result of that calculation to a QB INTEGER variable. One thing you can do that will work no matter what value your SUM has (but don't make SUM an INTEGER variable) is DIM STYLEPARAMETER AS INTEGER STYLEPARAMETER = (SUM + 65536&) MOD 65536& and use STYLEPARAMETER for the style option, where I just used the & suffix above to be safe and force QB to make a LONG (four-byte) integer calculation and keep it from making one of the numerical conversions that it likes to make automatically that sometimes causes overflows. A more general area filling function than LINE's BF is provided by the PAINT function. And I'm going to tell you right now that I have not used very much of its capabilities. I'm only going to talk about what I understand and leave the rest for the on-line help, or others who know more. Let's say there's some area (not necessarily rectangular) on the screen that you want to fill with pixels of some color. You can identify that area because it has attribute "bordercolor". (Perhaps you've just drawn a circle and you want to fill the circle.) The PAINT function is what you need: PAINT (x,y)[, [paint[, [bordercolor] [,background]]]] (If I need to tell you what the [] mean, you haven't been paying attention.) The parameter paint is the color you want to fill the region with. (x,y) specifies the coordinates of a point inside that region (but not on the border). (Actually, (x,y) can be outside the region instead of inside. Then, however, you'll be painting the outside, not the inside.) The default value for paint is the current foreground color, as before. The bordercolor parameter that tells PAINT to quit painting. As PAINT alters the colors on a row of pixels, it stops when it comes to a pixel already on the screen with attribute bordercolor. Okay, that's the part of PAINT that I've used and can say I more or less understand. Here's what you have to look up in the on-line help or ask someone else if you're interested. First, background is a character string that tells PAINT how to paint over pixels that are already on the screen. (The default use for PAINT is to paint over black (0 attribute) areas.) The default value for background is CHR$(0). I don't ever specify a value for background; I just leave the parameter out. (I didn't even know it existed until I went to look up PAINT in the on-line help for the sake of adding some stuff to this in my attempt to recast it as a more general tutorial.) The other thing you might be interested in looking up/researching for yourself is the other use of the paint parameter. It can also be specified as a character string (instead of a numeric variable/constant). This is PAINT's tiling option. It's sort of like LINE's style option; it allows PAINT to fill a region with patterns that you specify, instead of just filling it with a single color. But I can't tell you any more than that. There are other aspects of the above graphics functions that I also am not going to go into here. In particular, the STEP option is one such aspect. This option is used to make the specified coordinates refer to offsets from the last pixel position plotted instead of absolute screen positions. I again refer you to the on-line help. Okay, how do you define a default color? Use the COLOR statement: COLOR # where # is an integer between 0 and MAXCOL - 1. (Generally, 0 isn't a real good default color to use--it's usually the color of your usually black screen. Of course, if you've filled the screen with nonzero pixels, maybe a 0 default attribute is precisely what you want.) Now, for the "more advanced graphics routines." Let's do GET first. This function is used to store the data comprising a picture on the screen in an array. Actually, all it does is copy the pixel data in a specifiable rectangular area into the array. You do not need to transfer the whole screen. (For some screen modes, considering that you're likely going to want to use BSAVE to save the picture to disk-- more on that later, copying the entire screen to an array isn't a very useful thing to do. (It can be *made* useful, but it's just a little tedious.) Let's say you've got some picture occupying a rectangular area on the screen. Let (xl,yl) be the coordinates of the upper lefthand corner of your screen area and (xr,yr) be the coordinates of the lower righthand corner. The first thing you need to do is determine the size of the array that you need to use to store the picture. It's good practice to use LONG or INTEGER arrays to store pictures. (Because of the way your computer stores numbers, bits can be randomly altered slightly in SINGLE or DOUBLE arrays.) In an INTEGER array, each array element takes up two bytes, and in a LONG array, each element requires 4 bytes. Let BPE be the number of Bytes Per Element (2 or 4). In pixels, your picture's width is W = xr - xl + 1 and its height is H = yr - yl + 1 (where I'm temporarily changing the definitions of W and H from thier initial reference to screen dimensions). Further specification of how many bytes it takes to store your picture requires knowing the parameters associated with the graphics mode you're in. These parameters are given in the above table. BYTES = 4 + PLANES * H * INT( (W * BITS / PLANES + 7) / 8 ) The "4" out in front is because, in addition to your graphics data, GET puts 4 other bytes in the array--at its beginning. The first two of these bytes correspond to W * BITS / PLANES and the second two bytes give H. (Also, "BITS / PLANES" is also called, e.g., in your on-line help, "bits per pixel per plane.") The "+ 7" is there because the number of bits in a "scan line" (one bit plane's worth of pixels in a row on your screen) may not be a multiple of 8 (there are 8 bits in a byte) and the truncated byte at the end needs to be padded with zeros to fill out the last byte in that situation. (GET does this automatically.) The "/ 8" simply converts the number of bits in a scan line to the number of bytes. Okay, now you know how many bytes your array needs to allocate. This needs to be converted to the number of elements to specify in a DIM statement. This is given by SIZE = INT( (BYTES + BPE - 1) / BPE ) and then you would do either (where I'm using "PICT" as the array name, but you can use whatever you want) DIM PICT(1 TO SIZE) AS LONG or DIM PICT(1 TO SIZE) AS INTEGER depending on whether BPE is 4 or 2. (Actually, it is of course the other way around; it's LONG or INTEGER that determines BPE. Note that using LONG arrays means less array elements. If you actually do things this way and use a variable to define the size of your array, your array will be dynamic. If you use an explicit number, you may find that you need to use the '$DYNAMIC metacommand in order to be able to handle the necessary array size. Also, you can use zero-based arrays, e.g., "DIM PICT(SIZE - 1) AS LONG", if you want. I'm just more used to one-based arrays.) If the rectangular screen region you want to save is so large that it turns out that SIZE is larger than 32,767, you have a minor problem. QB won't allow more than 32,767 elements in any one dimension of an array. In that situation, you have to use a two dimensional array: DIM PICT(1 TO SIZENEW, 1 TO M) AS LONG where M is 2 or 3, depending on what I'll discuss in a minute, when I'll also get to SIZENEW. First, I used "LONG" here because if you're having array size problems, you probably want to do what minimizes the number of elements that you need. But you can use INTEGER if you want to. If you do use LONG, M should never need to be larger than 2. Otherwise, you might need 3. Here's how to find out. First, divide SIZE by 2. (If SIZE isn't an even integer, add one to it before performing the division.) If that gives you a value less than 32,768, then that's SIZENEW and M = 2. (And these are the results you should get if you're using a LONG array.) If you're still getting a number larger than 32,767 after dividing by 2, then you need to try M = 3. This time, before dividing SIZE by 3, if SIZE isn't an even multiple of 3, add 2 or 3 (whatever's necessary) to make it so. Then divide by 3. That should give a value of SIZENEW less than 32,768. After you have the array defined, your picture is copied to it via GET (xl,yl) - (xr,yr), PICT Okay, your picture data is in the array. How do you save that picture data to a file? You use BSAVE. BSAVE copies data from memory to a file. To do that, you need to tell it where in memory your data is. Well, it's in the memory area where your array is stored. That's a relatively easy piece of information to get (it's real easy to say things like that when you know how to do it ), but I will make a brief digression in the hope that it will help you understand what the functions that I'm eventually going to tell you how to use actually do. In "real mode" (don't worry about what "real" and "protected" mode means--it's real unimportant to what you're doing and I'm no expert on it), computer memory is accessed by referring to segments and offsets. The notation is segment:offset (e.g., 0:449, where those are hexadecimal numbers, is the address of where your current video mode is stored). Quite often, segment and offset are specified in hexadecimal notation. You don't really need to worry about that here. QB works with decimal numbers, and segments and offsets mean the same thing whether you use decimal or hex. Okay, back to BSAVE and where your array is. You need to tell QB the segment and offset of your array. That's a two step process--but they can be combined somewhat. First, you have to get the segment and point QB to it. You get the segment using the VARSEG function and you point QB to that segment with DEF SEG: DEF SEG = VARSEG( PICT(1) ) You can also do something like SM = VARSEG( PICT(1) ) and then do DEF SEG = SM Then, you can get the offset in the process of BSAVEing your picture. The offset is obtained with the VARPTR function. If "PICT.GET" is the name of the file that you want to save your graphics data to, BSAVE can be used as BSAVE "PICT.GET", VARPTR( PICT(1) ), BYTES where BYTES is the number of bytes to save (starting at the beginning of your array because you referred to the first index in PICT when you used VARSEG and VARPTR). (BYTES would normally be the quantity you calculated above, when you went to find out how big your array needed to be.) You can also use VARPTR and BSAVE separately: OFFSET = VARPTR( PICT(1) ) BSAVE "PICT.GET", OFFSET, BYTES If you assign the results of VARSEG and VARPTR separately to variables instead of explicitly using them in the DEF SEG and BSAVE statements, it's a good idea to do this just before you use BSAVE. Otherwise, i.e., you define SEGMENT and OFFSET just after DIMming your array and then sometime later on use BSAVE, at least for dynamic arrays, it is possible that your array has moved (changed memory locations). Further, BYTES must be 65535 (64K - 1) or less. (*This* is why saving really large pictures, such as the entire screen, can be a problem for some video modes. Many of QB's video modes, e.g., the useful ones) require much more than 64K. It is also why you won't often have the above mentioned problem of needing more than 32,767 elements in an array--you couldn't save such an array with a single call to BSAVE. (You could, on the other hand, just want the picture data in an array, so you can put it back on the screen later, and not necessarily need to save it to a file.)) It's also not a good idea to use DEF SEG to redirect QB's memory pointer and then call a subroutine before using BSAVE (or doing whatever else you might have used DEF SEG for the purpose of doing). That subroutine may make its own use of DEF SEG, undoing your use of it. (Mouse routines tend to do that to you. Like using VARSEG and VARPTR, it's a good idea to not use DEF SEG to redirect the memory pointer until just before you actually *need* to do so.) You can reset the memory pointer back to whatever its default location is by just using DEF SEG without any segment specification (or equal sign): DEF SEG Once your picture is in the array, you can put it back on the screen using the PUT function--perhaps after you've cleared the screen. You can even put it at a different location than it originally was (unless it filled the entire screen). Another use of PUT, however, is to put a picture on the screen *after* loading it into an array from a file. So, before discussing how to use PUT, the opposite function of BSAVE is discussed. BLOAD copies graphics data from a file to memory. And like BSAVE, that memory is generally set aside by DIMming an array large enough to hold the picture data. And that's the first hurdle. You don't know a priori how big of an array to make because you don't know a priori what's in the graphics file. The simplest thing to do is assume that the picture is not going to need 64K or more of memory, unless you want to use BLOAD repeatedly (because, again like BSAVE, you can't BLOAD more then 65535 bytes). Then you could just make a 64K array to hold your picture and it should be more than large enough: DIM PICT (1 TO 16384) AS LONG (Later on, I'm going to show you how to actually figure out how many bytes you really need to set aside with your array. For now, let's just proceed with the assumption that, somehow, you have an array big enough to hold the picture you're BLOADING. And again, note that I've used a LONG array to ensure that the graphics data is accurately stored.) Okay, the array exists and is ready to have a picture put in it. First, you use VARSEG and DEF SEG to point Basic to the first of the memory segments that contain your array: DEF SEG = VARSEG( (PICT(1) ) and you can use whatever name instead of PICT that you want to of course. (But use the same one with DEF SEG that you used with DIM. ) Then, you need to get the offset of the array and BLOAD the picture to the array. As with BSAVE, you can do this in one step, where PICT.GET will be taken to be the name of your graphics file: BLOAD "PICT.GET", VARPTR( (PICT(1) ) Note that you do not need to actually be in any graphics mode when you use BLOAD. All you've done so far is put the picture data in an array. (The same goes for BSAVE too, by the way. Once GET is done, you can switch to SCREEN 0, for whatever reason that you might want to, before BSAVEing the data to a file.) You can also define SEGMENT and OFFSET variables using VARSEG and VARPTR and then use those variables with DEF SEG and BLOAD. (But, as before, you may not want to use VARSEG and VARPTR in that manner until just before you need to.) Okay, before moving on to PUT, let's see how we can complicate things (but possibly in a way that proves useful, especially if memory gets to be scarce). When BSAVE was used to save your picture, in addition to the data in your array, it put 7 bytes of "header information" at the front of your file. The last two of those bytes give the number of graphic data bytes + the four for the width and height data, i.e., the minimum number of bytes that you must reserve in your array. How do you get to those two bytes in your file, you ask? It's really easy. First, since you want to get just two bytes, you need to work with an INTEGER variable (because such variables take up two bytes). Define one via DIM ASIZE AS INTEGER and then do the following, substituting whatever the true file name is for the one used here (and you can use a different file number if #1 is already in use). OPEN "PICT.GET" FOR BINARY AS #1 GET#1,6,ASIZE CLOSE #1 Now, except for one minor snag, ASIZE is the size of the array (in bytes) that you need to define with DIM. That minor snag is that, since you defined ASIZE to be an INTEGER, it can't be larger than 32,767 (or smaller than -32,768). Now, the number of bytes is, by definition, a positive number. If the two bytes at positions 6 and 7 in your data file correspond to a number larger than 32,767, when QB went and assigned the value of those two bytes taken together to the INTEGER variable ASIZE, it subtracted 65,536 from what would otherwise have been the number of data bytes in the file. So, to ensure that you work with a positive number, define the number of bytes to reserve in your array as BYTES = (ASIZE + 65536&) MOD 65536& where BYTES is a variable that uses more than 2 bytes, e.g., a LONG variable. (It can also be a SINGLE variable, i.e., the kind that QB uses by default when DIM or a suffix character hasn't been used to change it, or it could even be a DOUBLE variable (8 bytes). However, since it's referring to a quantity that is mathematically an integer, I'd DIM BYTES AS LONG first.) The "MOD" is just the remainder function. "A MOD B" means to divide A by B and just keep the remainder, e.g., 7 MOD 6 = 1, 6 MOD 6 = 0, 5 MOD 6 = 5, and 9 MOD 6 = 3.) Then, the maximum element to allocate in a one-based array is, as before, SIZE = INT( (BYTES + BPE - 1) / BPE ) where BPE = 4 if you're using a LONG array and 2 if you're using an INTEGER array. (SIZE is what you'd use in place of the "16384," above.) Okay, you have the picture in the array. You use PUT to copy it from the array to your screen. Before using PUT, you need to do a few things and make a few decisions or determinations. As discussed at the outset, you need to use an appropriate SCREEN statement to change to a graphics mode. That graphics mode must correspond to the graphics mode involved in the creation of the picture that you just BLOADed (i.e., you have to know something about the graphics files that you're working with). (However, you can sometimes get interesting effects by using the wrong graphics mode, but they're not usually desirable effects.) You need to know where on the screen you want the picture to go. This is specified via the screen coordinates of the upper lefthand corner of your picture. You need to take into account the width and height (in pixels) of your picture also in determining this so you don't overflow the edge of your screen. (I'll discuss how to determine the size of the picture stored in the array you just BLOADed into eventually here. For now, let's get on with PUT.) You need to know what "action verb" you want to use with PUT, i.e., you need to know how you want the pixel attributes in your picture to interact with the attributes of any pixels that are on the screen. (Generally, for animation purposes, this action verb is XOR. XOR and the other action verbs are in fact described in your on-line help. However, if you look under "PUT," you may not find as good a description mathematically as you'll find if you just look up the XOR operator. The results of XORing pixels (or ANDing pixels, ORing them, whatever) are the same as using these operators as mathematical operations with numbers. It is somewhat unfortunate, however, that QB's on-line help chose to use true (T) and false (F) concepts instead of bits (0s and 1s). In your on-line help's table, F means 0 and T means 1.) The default action verb is XOR. Another useful action verb is PSET (not to be confused with the stand-alone PSET function). The latter action verb causes PUT to just overwrite whatever's on the screen where it's putting the picture. Okay, you know all that and you're in the right video mode. Here's how you use PUT. Let (xl,yl) be the coordinates of where you want the upper lefthand corner of your picture to go. The syntax of PUT is PUT (xl,yl), array name, action verb e.g., PUT (10,15), PICT, XOR You may want to keep in mind that, just because you put the picture on the screen, that doesn't mean it's going to stay there. Subsequent QB statements (such as CLS) may/will cause the screen to change. If you want to force the picture to stay there and not have your program do anything to change or erase it (program termination, for example, will also generally clear a graphics screen), you need to pause your program after you PUT the picture on the screen. One way of doing that is WHILE INKEY$ = "" : WEND This will suspend your program until you press a key. Now, then, about finding out how big the picture is in an array after you use BLOAD to put the picture data in it. This data is stored in the first four bytes of the array. If you're using an INTEGER array, it's really simple. First, DIM two variables AS INTEGER: DIM WPAR AS INTEGER, H AS INTEGER Using the same array name as before, do the following: WPAR = PICT(1) H = PICT(2) If you used a LONG array (or any other array type that isn't INTEGER--if you didn't heed my warnings), it's somewhat more complicated. (But it's doable; don't panic.) Just do the following (you don't need to worry about DIMming WPAR and H AS INTEGER in this case--but you can if you want): DEF SEG = VARSEG( PICT(1) ) OFFSET = VARPTR( PICT(1) ) WPAR = PEEK(OS) + 256 * PEEK(OS + 1) H = PEEK(OS + 2) + 256 * PEEK(OS + 3) DEF SEG (PEEK is just a function that reads data bytes from memory; see your on-line help.) However you got WPAR and H, this is what you do with them. H is the height (in pixels) of your picture. WPAR may or may not be the width. It depends on the screen mode that the picture was originally created in. The width is W = WPAR * PLANES / BITS (See the above table.) Since PLANES quite often equals BITS (such as in SCREEN 9 or 12), W is quite often WPAR. However, for example, W certainly does not equal WPAR in SCREEN 13. (The technique of PEEKing these parameters out of the array will of course also work with an INTEGER array; there's just no need to be clumsy in that situation.) As mentioned before, coordinates used with QB's graphics functions are normally referenced to the top lefthand corner of the screen and all coordinates on the screen can have a pixel plotted on them. The VIEW function can be used to alter this situation. (And don't confuse this with the VIEW PRINT statement. That is for a text viewport. Of more interest here is a graphics viewport.) Again from the on-line help, the syntax is VIEW [[SCREEN] (x1,y1)-(x2,y2)[, [color][, border]]] VIEW sets up what is called a graphics viewport. It is a smaller (rectangular) region of the screen with top lefthand corner at (x1,y1) and lower righthand corner at (x2,y2). If color is specified, the rectangular region is filled with that color. (Otherwise the area is left unfilled--the perhaps usual case.) If border is specified, a box is drawn around the edges of the viewport if there is room for it. (The box is drawn one pixel's width *outside* of the rectangular area.) If the SCREEN modifier/ option is omitted, the top lefthand corner becomes the coordinate origin and all coordinates specified after VIEW is used are referenced to the top lefthand corner of the viewport (and the maximum x coordinate that can be used is x2 - x1 and the maximum y coordinate is y2 - y1). In other words, although screen coordinates are used in the VIEW statement itself, all subsequent coordinates are viewport coordinates--(0,0) refers to the top lefthand corner of the viewport. (y still increases downwards.) If you do put the word SCREEN between VIEW and the (x1,y1) parameter, things are different. All subsequent coordinates are still screen coordinates, i.e., they are still referenced to the upper lefthand corner of the screen. The function of VIEW in this case is to keep pixels from being plotted if their coordinates are outside of the graphics viewport. (I've never used VIEW for this function; I just leave SCREEN out of the statement.) There are various ways of erasing the viewport and resetting things so that the entire screen is the graphics viewport (the default situation before VIEW is used). One method is to simply use VIEW all by itself, with no arguments. (That's why there are two "[[" before SCREEN in the syntax.) Also, using the SCREEN statement to change video modes resets the viewport to be the entire screen. So does using RUN. It should be noted that defining a graphics viewport affects QB's clear screen statement, CLS. The syntax for CLS is actually CLS n where n is 0, 1, or 2. If a viewport is not defined, it doesn't matter what you use for n; you might as well not even bother specifying it. If a viewport *is* defined, n = 0 causes the entire screen to be cleared and n = 1 clears only what's inside of the viewport. (Even though graphics pixels are only plotted *inside* of the viewport, you can PRINT text outside of it.) n = 2 is for a text viewport (VIEW PRINT) and there's no point in going into that here. (I've never used VIEW PRINT.) Okay, I guess it's time to make the distinction between colors and attributes more explicit. When you, for example, say things like COLOR 7 and then PRINT "HELLO" or you do something like PSET (10,20), 7 you'll likely notice that things get printed or plotted to the screen with a dull white color. This is, technically, not because the color 7 means "dull white." The "7" here is an attribute, not the color. You only get dull white because that's the color currently assigned to the attribute 7. That can, however, be changed. (This is important, incidentally, because when you BSAVE a picture, it is the *attributes* that get saved to the file, not the colors.) It might also be pointed out that this is not something due to QB. This state of affairs reflects how your video hardware works. QB has no control over it, other than to use the power the video hardware gives it to manipulate the colors assigned to attributes. Essentially, the attribute is just an index into a table. The table itself contains the colors. The attribute can just be made to point to different locations in the table. The data in that table depends on the video mode that you're currently in, as does how you change the data in that table (i.e., how you change what colors are assigned to what attributes). QB's function for assigning colors to attributes is PALETTE. (In computer video terminology, the set of colors assigned to the MAXCOL attributes is called the palette registers. These registers are located in "special" memory chips on your video board--or otherwise somewhere with your video hardware if your video chipset is on your motherboard.) I'm not going to go into how to use PALETTE for all the different video modes. I suspect that SCREENs 9, 12, and 13 are of most interest. (But I'll discuss 11 too.) The general syntax for PALETTE is PALETTE attribute, # where # is a LONG variable or constant and otherwise depends on the video mode. The maximum value of attribute (MAXCOL - 1) also of course depends on the video mode. (The minimum value is always 0.) If it isn't clear, I'll point out that you must be in the video mode you're going to be using *before* using PALETTE. In SCREEN 9, although attribute is limited to a maximum of 15, there are 64 different colors from which you can choose to associate those 16 attributes with, numbered from 0 to 63. (And if I understand what I read in a bios reference, if you're willing to bypass the functionality that QB explicitly provides for manipulating the palette and manipulate the palette registers yourself, using more direct access methods (not discussed here), there are actually 4 different sets (or "banks") of those 64 colors that you can work with. However, QB itself only works with the first bank.) I have looked as hard as I can in QB's on-line help and in my manual, and I cannot find an actual listing of what those fixed 64 colors are. So, even though QB gives you the capability of changing the colors associated with the 16 attributes in SCREEN 9, it doesn't give you much help in deciding what choices you might like to make. (Perhaps one reason for this omission might be that, again, QB doesn't decide what these 64 colors are; your video system does. But since it's a standard list, there's really no good reason for the omission.) One method of making the choice is just to experiment with PALETTE until you get a set of 16 attributes that you like. I've gone to the trouble of providing you with another option. Those 64 colors are tabulated below, from van Gilluwe's "The Undocumented PC" (first edition, after converting his hexadecimal numbers to decimal), as a function of the # parameter you use with PALETTE. (This does not of course necessarily completely eliminate the need for experimentation, perhaps just to see if what's in the table gives you something that you like on the screen. But it may take a lot of the trial and error out of the experimentation.) PALETTE # VALUES FOR SCREEN 9 PALETTE # COLOR PALETTE # COLOR 0 black 32 dark red 1 blue 33 deep blue-purple 2 green 34 green 3 cyan 35 cyan 4 red 36 bright red-orange 5 magenta 37 deep pink 6 dull yellow 38 orange 7 white 39 pink 8 dark blue 40 dark purple 9 medium blue 41 medium blue 10 army green 42 green-gray 11 baby blue 43 medium blue 12 ruby red 44 cherry red 13 lavender 45 deep magenta 14 light gold 46 light orange 15 light lavender 47 light lavender 16 dark green 48 dark army green 17 medium-dark blue 49 blue-purple 18 fluorescent green 50 bright green-yellow 19 green-cyan 51 light green-cyan 20 brown 52 orange-red 21 medium purple 53 hot pink 22 bright yellow-green 54 lemon yellow 23 faded green 55 warm white 24 dark cyan 56 gray 25 deep blue 57 bright blue 26 bright green 58 bright green 27 bright cyan 59 bright cyan 28 faded red 60 bright red 29 purple 61 bright magenta 30 bright yellow-green 62 bright yellow 31 ice blue 63 bright white (Note that the default colors assigned to the attributes for SCREEN 9 are not simply the first 16 values in the above table. I'd tell you what they are, but you *can* get those from the on-line help--under SCREEN. It might also be noted, however, that these standard default color assignments are in fact the standard ones made by your video system.) Although the maximum attribute allowed in SCREENs 11, 12, and 13 depend on the specific video mode, they have at least one thing in common--the colors are assigned to the available attributes in exactly the same way, from exactly the same set of 64^3 = 262,144 colors. However, these colors are NOT numbered consecutively from 0 to 262,143. In using PALETTE to assign a color to an attribute, you MUST use the following formula for #: # = R + 256& * G + 65536& * B, where R, G, and B are the Red, Green, and Blue parameters and must each be in the range 0 to 63. (This constraint is also NOT from QB. It is again an artifact of how the palette registers are used by your video system. Also, you may be able to get away with not using the "&" LONG suffix; I just do it to be safe.) The only difference between the three video modes in this regard is that the maximum value of attribute is 255 for SCREEN 13, 15 for SCREEN 12, and 1 for SCREEN 11. The default values for the RGB values assigned to the attributes for SCREENs 12 and 13 are tabulated below, where only the first 16 apply for SCREEN 12. DEFAULT RGB VALUES FOR SCREENs 12 AND 13 ATTRIBUTE RED GREEN BLUE 0 0 0 0 1 0 0 42 2 0 42 0 3 0 42 42 4 42 0 0 5 42 0 42 6 42 21 0 7 42 42 42 8 21 21 21 9 21 21 63 10 21 63 21 11 21 63 63 12 63 21 21 13 63 21 63 14 63 63 21 15 63 63 63 16 0 0 0 17 5 5 5 18 8 8 8 19 11 11 11 20 14 14 14 21 17 17 17 22 20 20 20 23 24 24 24 24 28 28 28 25 32 32 32 26 36 36 36 27 40 40 40 28 45 45 45 29 50 50 50 30 56 56 56 31 63 63 63 32 0 0 63 33 16 0 63 34 31 0 63 35 47 0 63 36 63 0 63 37 63 0 47 38 63 0 31 39 63 0 16 40 63 0 0 41 63 16 0 42 63 31 0 43 63 47 0 44 63 63 0 45 47 63 0 46 31 63 0 47 16 63 0 48 0 63 0 49 0 63 16 50 0 63 31 51 0 63 47 52 0 63 63 53 0 47 63 54 0 31 63 55 0 16 63 56 31 31 63 57 39 31 63 58 47 31 63 59 55 31 63 60 63 31 63 61 63 31 55 62 63 31 47 63 63 31 39 64 63 31 31 65 63 39 31 66 63 47 31 67 63 55 31 68 63 63 31 69 55 63 31 70 47 63 31 71 39 63 31 72 31 63 31 73 31 63 39 74 31 63 47 75 31 63 55 76 31 63 63 77 31 55 63 78 31 47 63 79 31 39 63 80 45 45 63 81 49 45 63 82 54 45 63 83 58 45 63 84 63 45 63 85 63 45 58 86 63 45 54 87 63 45 49 88 63 45 45 89 63 49 45 90 63 54 45 91 63 58 45 92 63 63 45 93 58 63 45 94 54 63 45 95 49 63 45 96 45 63 45 97 45 63 49 98 45 63 54 99 45 63 58 100 45 63 63 101 45 58 63 102 45 54 63 103 45 49 63 104 0 0 28 105 7 0 28 106 14 0 28 107 21 0 28 108 28 0 28 109 28 0 21 110 28 0 14 111 28 0 7 112 28 0 0 113 28 7 0 114 28 14 0 115 28 21 0 116 28 28 0 117 21 28 0 118 14 28 0 119 7 28 0 120 0 28 0 121 0 28 7 122 0 28 14 123 0 28 21 124 0 28 28 125 0 21 28 126 0 14 28 127 0 7 28 128 14 14 28 129 17 14 28 130 21 14 28 131 24 14 28 132 28 14 28 133 28 14 24 134 28 14 21 135 28 14 17 136 28 14 14 137 28 17 14 138 28 21 14 139 28 24 14 140 28 28 14 141 24 28 14 142 21 28 14 143 17 28 14 144 14 28 14 145 14 28 17 146 14 28 21 147 14 28 24 148 14 28 28 149 14 24 28 150 14 21 28 151 14 17 28 152 20 20 28 153 22 20 28 154 24 20 28 155 26 20 28 156 28 20 28 157 28 20 26 158 28 20 24 159 28 20 22 160 28 20 20 161 28 22 20 162 28 24 20 163 28 26 20 164 28 28 20 165 26 28 20 166 24 28 20 167 22 28 20 168 20 28 20 169 20 28 22 170 20 28 24 171 20 28 26 172 20 28 28 173 20 26 28 174 20 24 28 175 20 22 28 176 0 0 16 177 4 0 16 178 8 0 16 179 12 0 16 180 16 0 16 181 16 0 12 182 16 0 8 183 16 0 4 184 16 0 0 185 16 4 0 186 16 8 0 187 16 12 0 188 16 16 0 189 12 16 0 190 8 16 0 191 4 16 0 192 0 16 0 193 0 16 4 194 0 16 8 195 0 16 12 196 0 16 16 197 0 12 16 198 0 8 16 199 0 4 16 200 8 8 16 201 10 8 16 202 12 8 16 203 14 8 16 204 16 8 16 205 16 8 14 206 16 8 12 207 16 8 10 208 16 8 8 209 16 10 8 210 16 12 8 211 16 14 8 212 16 16 8 213 14 16 8 214 12 16 8 215 10 16 8 216 8 16 8 217 8 16 10 218 8 16 12 219 8 16 14 220 8 16 16 221 8 14 16 222 8 12 16 223 8 10 16 224 11 11 16 225 12 11 16 226 13 11 16 227 15 11 16 228 16 11 16 229 16 11 15 230 16 11 13 231 16 11 12 232 16 11 11 233 16 12 11 234 16 13 11 235 16 15 11 236 16 16 11 237 15 16 11 238 13 16 11 239 12 16 11 240 11 16 11 241 11 16 12 242 11 16 13 243 11 16 15 244 11 16 16 245 11 15 16 246 11 13 16 247 11 12 16 248 0 0 0 249 0 0 0 250 0 0 0 251 0 0 0 252 0 0 0 253 0 0 0 254 0 0 0 255 0 0 0 I would like to say that this table also applies to SCREEN 11 if only the first two attributes are referred to. However, that isn't the case. Attribute 1 does not print/plot as blue; it prints/plots as white. This is in spite of the fact that I got this table by reading the color palette--and I get the same first two lines of data when I'm in SCREEN 11. So, all I can really say here is that I don't understand SCREEN 11; it does not appear to use the palette registers the way my documentation leads me to believe it should. (There's not a lot of call for SCREEN 11. If you're interested in games, you probably aren't interested in a monochrome video mode. I use it for displaying black & white static pictures/images that I need to print out on black & white printers--there's no need to worry about color if you need to print your picture and you don't have a color printer.) A difference between these video modes and SCREEN 9 is that the above table does not represent the same set of colors assigned by the computer itself in video modes 12 and 13. When QB changes to either of these video modes, it also changes the palette. Another syntax of PALETTE is PALETTE USING array(element) where array is an INTEGER or LONG array that stores the palette # data--in order of the attribute numbering (0 - MAXCOL - 1). It must store sufficient data for all of the attributes that are allowed in the current video mode. The element parameter tells PALETTE which element to start getting data from. To use the on-line help's example, if you're in a 16-color mode and element = 5, your array must have at least 20 elements in it. (Your array doesn't need to be named "array".) Any array element = -1 causes that particular attribute to have the color currently assigned to it to be unaffected. Otherwise, negative numbers are not allowed in the array. Although PALETTE may generally allow the array to be INTEGER, you need to use a LONG array if it is to store palette #s larger than 32,767 (and it generally will for SCREENs 12 and 13). To reset the palette registers (i.e., the colors assigned to the attributes) to their default values, just use PALETTE without any arguments/parameters after it. It's interesting to note that the changes to the color assignments made by PALETTE (or by any other method of changing the palette registers) not only affects the colors of pixels yet to be put on the screen, but it also changes the color of what's already on the screen. So, it doesn't matter whether you use PALETTE before or after putting your graphics on the screen. (As your video board continually scans video memory to tell your monitor what to put on the screen, it also dynamically reads the palette registers.) There's another method of changing the color palette. Some people like it because it's slightly faster than using PALETTE. However, my primary motivation for discussing it here is as a precursor to discussing how to read and save the data from the color palette registers. Further, this discussion primary applies to QB video modes 12 and 13. (There may be ways of using the method for lesser video modes, but I don't know any more about that than you. (Perhaps you already know *more* than I do about it.)) The method involves writing the three RGB color values to port 3C9h. (The "h" means that it's a hexadecimal number.) You do that by using QB's OUT statement. Before doing that, however, you need to tell your computer which attribute is associated with the RGB values. You do that by writing (again using OUT) the attribute integer to port 3C8h. Actually, you don't need to write to port 3C8 before everytime you write to 3C9. Whenever you write three bytes to 3C9, the integer in 3C8 automatically gets incremented by one. Let's say you're in SCREEN 13 and you have 256 RGB values stored in three arrays, which you may have dimensionalized via DIM RED(255) AS INTEGER, GREEN(255) AS INTEGER, BLUE(255) AS INTEGER (You don't really need to use INTEGER arrays, but it speeds things up slightly and ensures that the integer data is stored accurately.) You can set the 256-color palette with the following code example: OUT &H3C8, 0 FOR I = 0 TO 255 OUT &H3C9, RED(I) OUT &H3C9, GREEN(I) OUT &H3C9, BLUE(I) NEXT I Things are the same for SCREEN 12; just change "255" above to "15". (You could put an OUT &H3C8, I just after the FOR statement instead of using the OUT &H3C8, 0 just before it, but that would only slow things down.) Now, you may have noticed that when you use PALETTE, or the above method, to change the color palette from QB's default, BSAVE a picture made with that new palette, change the palette to something else (perhaps because you're running a different program than the one that generated and saved the picture), and then BLOAD that picture back in from the file, the colors aren't the same as when you first generated it. This is because BSAVE doesn't save the palette data. It only saves the attribute data in video memory. (The same is true for GET, if you first copied your picture data to an array and then BSAVED the array--as opposed to BSAVEing directly from video memory.) Hence, if you're using PALETTE, in addition to GETting and BSAVEing your picture data, you may also want to save your palette data. Saving your palette data is a two-step process. You first need to read that data from the palette registers and then you need to save it to a file. There are two ways of reading the palette registers (that I know of). One is to call an interrupt. (You can also set the palette registers that way. I just didn't see any point in going into that.) A less clumsy way is again to work with port 3C9h. Instead of using OUT, however, you want to use the opposite QB statement: INP. However, you still need to use OUT to tell the computer which attribute you want the RGB data for. Instead of port 3C8h, you now use 3C7h. There are also different methods used to save your palette data to a file. I'm going to show a simple method that uses an easy to view text file and saves the data in conjunction with reading it from the palette registers. Here goes. OPEN "PALETTE.DAT" FOR OUTPUT AS #1 OUT &H3C7, 0 FOR I = 0 TO 255 PRINT#1, INP(&H3C9), INP(&H3C9), INP(&H3C9) NEXT I CLOSE #1 (Again, the integer in port 3C7h is incremented by one automatically everytime 3 bytes are read from port 3C9h.) PALETTE.DAT now contains 256 rows of numbers, with 3 numbers on each line. Each line corresponds to an attribute; attribute 0 corresponds to the first line and attribute 255 corresponds to the last line. The three numbers on each line are, respectively, the RGB data for the the particular attribute. Okay, you've got the palette data in a file. How do you get it back into QB and reset the palette, perhaps in some other program? Here goes again. DIM R AS INTEGER, G AS INTEGER, B AS INTEGER OPEN "PALETTE.DAT" FOR INPUT AS #1 OUT &H3C8, 0 FOR I = 0 TO 255 INPUT#1, R, G, B OUT &H3C9, R OUT &H3C9, G OUT &H3C9, B NEXT I CLOSE #1 Before I give this up, I'll discuss a few miscellaneous things. Let's say you've got a picture on the screen and you want to know the attribute of some pixel on the screen at position (x,y), i.e., you want to do the opposite of what PSET does. Use the POINT function: attribute = POINT(x,y) The SCREEN statement has a more general syntax than the one shown initially. I didn't discuss that then because you generally don't have to worry about it. But if you want to complicate things, here you go. The general syntax is SCREEN [mode][, [colorswitch]][, [apage]][, [vpage]] First, we're going to forget about colorswitch. Just put two commas in a row between mode and apage (if you're going to worry about this more general syntax at all). (This parameter is only applicable for SCREENs 0 and 1. For those modes, if colorswitch is 0, color printing/plotting is enabled. When it is nonzero, color printing/plotting is disabled.) The most likely reasons that you might want to use the more general syntax is because of the apage and vpage parameters. So far, it's been assumed that when you're drawing a picture or otherwise putting pixels on the screen, you want to see them being put there. What you may not realize is that, in actual fact, at no time do you ever (in QB or any other programming environment) write anything to the screen. Your screen display is not a static thing. In other words, your computer didn't just put stuff on your screen, sort of like you would write stuff on paper, and then just let it there until you tell the computer to change it. What you see on the screen is a replica of what's currently in your video memory. You see what you see because your video card causes your monitor to constantly update the screen with what's in memory. When you think you're writing data to the screen, you're really just writing to video memory. That all may seem like a theoretical excursion into how computers work that really doesn't have any bearing on how you *use* the computer. However, the video memory that's being "mapped" to your screen isn't necessarily the only video memory that you can write to. Depending on the video mode and how much video memory you have, you may have video memory that you can write to that does not currently correspond to what you're seeing on the screen. These different sets of video memory are called video pages. Video pages are numbered from 0 to MAXPAGE - 1, where MAXPAGE is the maximum number of pages supported by the video mode. Modes 11 - 13 only have one video page (page 0). However, if you have 256 KB of video memory, SCREEN 9 has two video pages. QB's manual calls the page you're seeing the visual page. (I prefer "visible page.") This is the vpage parameter in the SCREEN statement. The page you're writing to is the active page--apage in the SCREEN statement. Generally, such as when you don't specify vpage and apage in SCREEN, vpage and apage are the same page (usually 0)--that's why you see what you're writing as you write it. However, if you're in SCREEN 9, for example, and you got there via SCREEN 9,, 1, 0 and then you proceed to use various QB graphics functions to put pixels on the screen, you aren't actually going to see anything happening. That's because you told SCREEN to set the visible page to something different than the page you're writing to. When you're all done writing graphics, you can do something like SCREEN 9,,, 1 and you'll see your graphics data appear on the screen virtually instantaneously. (I left out apage. If you want to simultaneously change apage to something else (0, in this case) or just specify it explicitly, you can put the apage value after the second comma. I think, since you aren't changing video modes, that you could also replace the "9" with a comma if you want to.) Where's whatever data that was on page 0, you ask? It's still there, you just aren't looking at it. SCREEN 9,,, 0 brings it back and of course the previous use of SCREEN will bring page 1 back again.