Graphics Tutorial on BASIC by Lucas K. Tavares - LKT153 Well, first of all I'd like to thank Alipha for giving me the opportunity to write this to his Web Page, what I consider a great honor. This is my first tutorial ever, so I'd like to apologize if something wasn't made very clear, if that happens be sure to e-mail me (lkt153@bol.com.br) or Alipha (aliphax@hotmail.com). I'd also like to apologize for any grammar errors, after all I'm brazilian and I'm doing the best I can! Hope you enjoy... Note: This tutorial was built in windows's notepad with a resolution of 800x600 and in maximized screen, with automatic line breaking, if you do not view it like that it might get screwed up... Table of contents: 01. Getting started 02. Graphics on BASIC - PSET - PRESET - POINT - CIRCLE - DRAW - PAINT - LINE 03. Drawing with DATA statments 04. GETing and PUTing Images 05. Working with multiple pages 06. Masking 07. Palette Changing 08. Speeding up - Binary Saving (BSAVE) - Fast PALETTE - Reducing Flicker 09. Credits and Finishing -------------------------------------------------------------------------------------------------- 01. Getting started: First of all, in order to draw anything in BASIC you would have to change the screen mode you're working on, since the default BASIC screen mode (SCREEN 0) is text based. To change the screen mode we'll use the SCREEN command, here's its syntax: SCREEN [Mode] [, [ColorSwitch]][, [ActivePage]][, [VisualPage]] where: Mode is an integer expression from 0 to 13 which defines the resolution and number of pages you'll be working on. All you have to know about, for now, is mode. I'll explain Pages later, and ColorSwitch is almost never used, so I won't even explain it, look for it on qbasic's the help file. Now the most used screen modes are 0, 7, 12 and 13. 0 is the best one for text based programs, you probably already know how to use it, and every since this is a graphics tutorial I won't be explaining it here. Screen 7 has a 320x200x16 definition and is often used because of its multiple pages (will be explained later). Screen 12 has the highest resolution you'll find in BASIC, 640x480x16 that's basically why it's very often used, although if you build a program that runs too many graphics. Screen 13 is probably the most used, since it has 256 colors, oh by the way it has a 320x200 definition. -------------------------------------------------------------------------------------------------- 02. Graphics on BASIC Now, once you determined the screen you're gonna use, you need to understand that the screen is basically divided in pixels, so if I say I'm writting to pixel (10, 20) it means that I'm walking 11 pixels to the right from the left of the screen (Where X = 0) and 21 pixels to the bottom from the top of the screen (Where Y = 0). If you didn't get that imagine that this is your screen: +---------------+ | 1 | | | | | | 2 | | | +---------------+ Now you can check for dot one coordinates, they're read as (X, Y) where X is distance from the left corner, and Y is distance from the top, X and Y are always positive (You can draw to a negative pixel, but you wouldn't see the graphics) and they are smaller than the screen Width and Height. Now you need to draw something on the screen, you'll draw using the following commands: - PSET: Draws a single dot to the screen, it has this syntax: PSET [STEP](X, Y) [, Color]. (X, Y) is the coords for the drawing, color, if not set is the last forecolor used and Step is used to put graphics relative to the current graphics position. Imagine this you've drawn a pixel at (10,10) if you write PSET (5,5) afterwards it'll draw to pixel (5,5) but if you use PSET STEP(5,5) it'll draw to pixel (15,15). - PRESET: Works just like PSET except if color is not set it uses the last backcolor used. - POINT: Reads the color of a point, and also reads current cursor position. it's used sometimes for colision detection, altough it's not the best way to do it. It has this syntax: POINT (X, Y) or POINT(N) It's a function so you'll have to use something like A = POINT(10,10) get it? If we did that A would receive the value of the color in pixel (10,10). the second syntax won't be explained because it's pratically useless... - CIRCLE: Draws a circle or an ellipse or an arch to the screen. It's hard to be used so I'll just explain the basis. Here's the basic syntax: CIRCLE [STEP] (X, Y), radius [, Color] This works basically like pset, not exactly, but quite like it, see QBasic's reference for a more detailed explanation on arch and ellipse building - DRAW: This is very long to be explained so check QBasic's reference. - PAINT: This paints an entire area, determined by points of the same color specified on the paint command, like if you draw a circle of red and put paint anywhere inside it with the red color, all the circle will be painted red. It's syntax is: PAINT [STEP](X,Y)[,[Color] [,[BorderColor][, BackGround]]] - LINE: This is the most important command of all of the listed above (at least I think). Here's the syntax: LINE [[STEP] (X1, Y1)] - [STEP] (X2, Y2) [[,color][,B[F]][,style]]] Basically it'll draw a line from (X1, Y1) to (X2, Y2), but if you use B and BF you'll have that B is a non-filled box and BF is a filled box. Well those are the basics for drawing. -------------------------------------------------------------------------------------------------- 03. Drawing with DATA statments: Hey you've made it 'till here! Now I suggest to you take a break, practice a little what you've learned so far, and come back in a while... Hello again, here we go... If you don't know them, DATA statments are to store information, like strings and numbers, for graphics we'll only be using numbers... Imagine this we want to build a Character for a game, but we want a high quality character well drawn, etc. What we do? We take a lot of time trying out ellipses with circle, and fight with squares using lines, or we just build a map and load information for it (hey, line and circle are really important, I'm not taking off their merith, use 'em, but this would be a harsh assingnment using them so...). Now imagine we are going to draw a flame, now to use data statments you do this: DATA Constant [, Constant]... where data can be a string or a number, and we also need READ and RESTORE to read 'em READ VariableList RESTORE [{LineNumber|Label}] So data puts the information, READ reads it, and RESTORE sets where the information is to be read from. If you didn't get it take a look at this: ---- Code Start: RESTORE PlaceB READ A PRINT A PlaceA: DATA 1 PlaceB: DATA 3 ---- Code End If we executed this we would get a 3 printed to the screen, why? because the 1st line (RESTORE PlaceB) says that read should start from PlaceB, and not from PlaceA which is the standard place every since data is read from the top to the end... if you use RESTORE with no parameters data is restored to the first DATA statment on the program. Now we'll start building the image: DATA 00,00,04,04,00,00 DATA 00,04,14,14,04,00 DATA 00,04,14,14,04,00 DATA 04,14,14,14,14,04 DATA 04,14,14,14,14,04 DATA 04,14,14,14,14,04 DATA 04,04,04,04,04,04 This is a 6x6 "fire" unfortunately I'm not a pretty good artist, but getting back to the point so what do we do with it?! Well, we read it and PSET every value to the screen, drawing the fire, here's some code that'll do that... ---- Code Start: SCREEN 13 RESTORE Fire FOR PY = 0 TO 5 FOR PX = 0 TO 5 READ A PSET (PX, PY), A NEXT PX NEXT PY END Fire: DATA 00,00,04,04,00,00 DATA 00,04,14,14,04,00 DATA 00,04,14,14,04,00 DATA 04,14,14,14,14,04 DATA 04,14,14,14,14,04 DATA 04,14,14,14,14,04 DATA 04,04,04,04,04,04 ---- Code End Test the code above... what did you think of it?! pretty great huh? anyway, all it does is read six colors and PSET 'em, read another six colors, etc. and does that six times doing the drawing, oh and be carefull not to mess up, the outer loop is PY, if you change it, the drawing will be turned around 90 degrees. But you can use it on purpose to get four images of the same thing in four different positions, like if you read with PX inverted with PY you'll get another image, than later you can invert "FOR PY = 0 TO 5" to "FOR PY = 5 TO 0 STEP -1" that'll invert the image, so imagine you're building a racing game, you can only build one image of a car and get 4 from it! Well that's it for this chapter if you want more info about this e-mail me. -------------------------------------------------------------------------------------------------- 04. GETing and PUTing images: Why is GET and PUT in upper case? Because GET and PUT are BASIC statments that are good for saving images to arrays, and then loading them faster to screen. How QBasic does it? I'm not pretty sure, but I believe it reads data directly from video memory and then writes it back later. Anyway it's syntaxes are: GET [STEP](X1, Y1) - [STEP](X2,Y2), ArrayName[(Indices)] PUT [STEP](X, Y), ArrayName[(Indices)][, ActionVerb] The problem is that you would have to work with arrays, if you don't know 'em here's a brief explanation: Imagine you have this sequence of numbers (0, 5, 9, 1, 3) Now you could save them to six different variables as A0, A1, A2,..., A5 or you could put them in an array of size 6 like A(0) = 0 A(1) = 5 A(2) = 9 ... A(5) = 3 Note: I say size six because it goes from 0 to 5, having six members and not only five. Now let's draw our fire image again, shall we... after you've drawn it, build an array of size 63 divided by the data size we're using, suppose we're using the array as INTEGER which is two bytes long we'll divide 63 by 2 resulting in 31.5. Which we round UP. Here's a table of the size of numeric variable types. INTEGER.....2 BYTES LONG........4 BYTES SINGLE......4 BYTES DOUBLE......8 BYTES Now you might be asking yourself why did we use 63 and not anything else, well here's the formula for the array size needed: 4 + INT(((x2 - x1 + 1) * (bits-per-pixel-per-plane) + 7) / 8) * planes * ((y2 - y1) + 1) And here's a table for planes and bits-per-pixel-per-plane needed: Bits per Pixel Screen Mode per Plane Planes 1 2 1 2 1 1 7 1 4 8 1 4 9 1 2 (if 64K of EGA memory) 4 (if > 64K of EGA memory) 10 1 2 11 1 1 12 1 4 13 8 1 Generalizing, for screen mode 12 you'd use: 4 + (INT(((x2 - x1 + 1) + 7) / 8) * 4 * (y2 - y1) + 1) And for screen mode 13 you'd use: 4 + (INT(((x2 - x1 + 1) * 8 + 7) / 8) * ((y2 - y1) + 1) Now let's give an example of GET and PUT being actually used: ---- Code Start: SCREEN 13 RESTORE Fire DIM FireDrawing (0 TO 32) AS INTEGER FOR PY = 0 TO 5 FOR PX = 0 TO 5 READ A PSET (PX, PY), A NEXT PX NEXT PY GET (0,0)-(5,5), FireDrawing PUT (20, 20), FireDrawing Fire: DATA 00,00,04,04,00,00 DATA 00,04,14,14,04,00 DATA 00,04,14,14,04,00 DATA 04,14,14,14,14,04 DATA 04,14,14,14,14,04 DATA 04,14,14,14,14,04 DATA 04,04,04,04,04,04 ---- Code End Try running the above code... how about it? Here's an explanation, it draws back everything as it was, then it GETs the image to the "FireDrawing" array and then prints it to the screen with the PUT Statment. That was a simple example, now imagine you're building a maze type game, you would have to have an image for each part of the maze, so what do you do? CREATE TONS OF ARRAYS? NO!!! You create one large array with multiple images on it, that's what the Indices part is doing in the syntax, so you have the fire saved to your array, now you want to add a different image to show the same fire in another position, here's what we do: ---- Code Start: SCREEN 13 DIM FireDrawing (0 TO 64) AS INTEGER RESTORE Fire FOR PY = 0 TO 5 FOR PX = 0 TO 5 READ A PSET (PX, PY), A NEXT PX NEXT PY RESTORE Fire2 FOR PY = 0 TO 5 FOR PX = 6 TO 11 READ A PSET (PX, PY), A NEXT PX NEXT PY GET (0,0)-(5,5), FireDrawing(0) GET (6,0)-(11, 5), FireDrawing(33) PUT (20, 20), FireDrawing(0) SLEEP 1 PUT (20,20), FireDrawing(33), PSET END Fire: DATA 00,00,04,04,00,00 DATA 00,04,14,14,04,00 DATA 00,04,14,14,04,00 DATA 04,14,14,14,14,04 DATA 04,14,14,14,14,04 DATA 04,14,14,14,14,04 DATA 04,04,04,04,04,04 Fire2: DATA 00,00,04,04,00,00 DATA 00,04,14,14,04,00 DATA 00,04,14,14,04,00 DATA 00,14,14,14,14,00 DATA 00,14,14,14,14,00 DATA 00,14,14,14,14,00 DATA 00,04,04,04,04,00 ---- Code End Now, pay attention this is hard to understand the first time you see it, all we've done is we've put another image right after the first one (remember that the first one ends at index 32 so we started this other one at index 33), also, what the hell is that PSET doing after the put? Well it is an action verb that means that the image is to be drawn as is. Action verbs will be discussed later on masking. That's basically how PUT and GET works... -------------------------------------------------------------------------------------------------- 05. Working with multiple pages: This is an easy concept... remember while we were at the SCREEN statment that there was the Active and the Visual Page variables? So now we're going to use 'em, the only screen modes that support multiple pages are 3, 7, 8, 9 and 10 (note screen modes 3 and 4 will probably be never used, because they are incompatible with VGA, and 5 and 6 don't even exist so...). Now what are multiple pages? Imagine I have a bunch of papers in my hands, I draw at one, when I finish my drawing I show it to you, and start drawing on another page. That's it, what the computer does is draws on one page while it shows the user another page or even the same one. So ActivePage would be the page we're drawing to and VisualPage would be the one that the user is seeing, but now how do we change which one being showed? Well there are two ways, we could change the screen's active and visual pages all the time, or we could use PCOPY. It's up to you, I believe pcopy is faster, but then you would work with only two pages, and if you're using something like SCREEN 7 which has 8 then that would be a waste... pcopy has the following syntax: PCOPY SourcePage, DestinationPage. Well that all 'bout multiple pages! -------------------------------------------------------------------------------------------------- 06. Masking: Now let's start explaining the ActionVerbs. There are 5 ActionVerbs, they are: PSET, PRESET, AND, OR, XOR. Now how does this work? Well, ActionVerbs are basically used to indicate the way things are supposed to be drawn to the screen, like if you use PSET, the graphics will be put as they originally are. If you use OR the bits of the graphics will be logically modified by OR, that's kinda hard to get... ah let's just say which one of these shows the graphics on different colors on different backgrounds. Here's an explanation taken from QBasic's reference: PSET Draws image as stored, wiping ¦ XOR Reverses points in the image out any existing image ¦ with those in existing image PRESET Draws image in reverse colors ¦ or with background. wiping out any existing image ¦ Used in successive PUT AND Merges stored image with ¦ statements, with time delays, existing image ¦ causes stored image to appear OR Superimposes stored image on ¦ and disappear. With movement existing image ¦ of image, animation is achieved Now, masking is, imagine you have a background and you need to put an image over it without erasing anything that's already there, and letting the image be transparent... how do you do that?! Well you make a mask! I don't know how to explain this very well so I'll just pass the code for it, try understanding it, if you have any questions email me... ---- Code Start: SCREEN 12 CLS DIM Fire(0 TO 32) AS INTEGER DIM Mask(0 TO 32) AS INTEGER RESTORE Fire FOR PY = 0 TO 5 FOR PX = 0 TO 5 READ A PSET (PX, PY), A NEXT PX NEXT PY GET (0, 0)-(5, 5), Fire FOR PX = 0 TO 5 FOR PY = 0 TO 5 IF POINT(PX, PY) = 0 THEN PSET (PX, PY), 255 ELSE PSET (PX, PY), 0 NEXT PY NEXT PX GET (0, 0)-(5, 5), Mask CLS PAINT (0, 0), 3 PUT (0, 0), Mask, AND PUT (0, 0), Fire, OR END Fire: DATA 00,00,04,04,00,00 DATA 00,04,14,14,04,00 DATA 00,04,14,14,04,00 DATA 04,14,14,14,14,04 DATA 04,14,14,14,14,04 DATA 04,14,14,14,14,04 DATA 04,04,04,04,04,04 ---- Code End That's all you have to do!... -------------------------------------------------------------------------------------------------- 07. Palette Changing: Well you've already learned all the basis for good graphics, but one last thing is missing... Changing the colors you can work with, this function is avaliable in most screen modes, but it's more usefull only in screen modes 12 and 13, why? because you can actually change the colors you're working with in RGB pattern, in other screen modes all you can do is change the color that's in one value and change it to another (like 4 = red, you could change 4 to blue), now, how does it work? Well you would have to use the PALETTE statment! Syntax: PALETTE [atribute, color] Now, this gets tricky, Color is not like 1 or 2, it's a giant number determined by: (BlueValue * 256 ^ 2) + (GreenValue * 256) + (RedValue). Why is that? Because every byte can have 256 different values (0 - 256) so the blue value would be the first byte, the second byte would be the Green Value and the last one is the Red Value, RGB Pattern! Note: Blue, Green and Red values are in an interval between 0 and 63... as for other screen modes that support palette changing it works differently, like, if you're in mode 7 and you say PALETTE 0, 4133493 (white, Red = 63, Green = 63, Blue = 63) you'll get an "ilegal function call" error... why? Because in screen 7 you are unable to change RGB values but you can assign color 4 to be like color 1 (PALETTE 4, 1). Other important stuff about palettes, it's always good to keep an array of the colors you are working with, there are many ways to do that, the most used are: - Keep an array of long with all the color vaues you're working with, like: DIM Pal (0 TO 255) AS LONG. - Keep an array of integer with the color values you're working with, divided by colors, like: DIM Pal (0 TO 255, 0 TO 2) AS INTEGER Where: Pal (0, 0) is the red value for color 0, Pal (0, 1) is the green value for color 0, etc. - Create an UDT like: TYPE RGBType Reg AS INTEGER GREEN AS INTEGER BLUE AS INTEGER END TYPE and then DIM an array with it, like DIM Pal (0 TO 255) AS RGBType. Another thing is after dimming an array as the first type said (array of long) you could use the PALETTE USING statment, all you'd have to do is type PALETTE USING Pal!!! -------------------------------------------------------------------------------------------------- 08. Speeding up: Hey, you've made it! This is the final section of this tutorial, and is nothing new, only ways to optimize your code. Here they go: - Binary Saving (BSAVE): This is a way for fast saving and loading graphics, I won't explain how it works for it's not worth the explanation, but I'll explain how to use it. First of all you have to find the memory segment where the array to be saved is, for that we'll use VARSEG, that's a function that returns the memory segment we'll use. Also we'll use DEF SEG, to set the read segment to be the array's one and later to return to QBasic's segment. Now BSAVE saves an array to a file, so you can do it after you GET the array from screen. Now BSAVE's syntax is: BSAVE FileName, OffSet, Length DEF SEG's syntax is: DEF SEG [= adress] as for VARSEG it's syntax is: VARSEG (Variable) now to bsave an array you'll use this: ---- Code Start: DEF SEG = VARSEG(Array[(indices)]) BSAVE FileName, 0, Length of array DEF SEG ---- Code End But how do you use that data ever again? well you BLOAD it! BLOAD's syntax: BLOAD FileName, OffSet Each means the same thing for BLOAD and BSAVE... Actually BSAVE can be used to save anything you want but i more used for graphics... Here's a full example for it: ---- Code Start: SCREEN 13 RESTORE Fire DIM FireDrawing (0 TO 32) AS INTEGER FOR PY = 0 TO 5 FOR PX = 0 TO 5 READ A PSET (PX, PY), A NEXT PX NEXT PY GET (0,0)-(5,5), FireDrawing DEF SEG = VARSEG(FireDrawing(0)) BSAVE "Fire.bsv", 0, 66 '66 is 33 * 2 (2 bytes in an INTEGER) DEF SEG 'Returns to QBASIC's segment CLEAR 'Clears any variables contents, actually anything in stack memory... DEF SEG = VARSEG(FireDrawing(0)) BLOAD "Fire.bsv", 0 DEF SEG PUT (10, 10), FireDrawing, PSET Fire: DATA 00,00,04,04,00,00 DATA 00,04,14,14,04,00 DATA 00,04,14,14,04,00 DATA 04,14,14,14,14,04 DATA 04,14,14,14,14,04 DATA 04,14,14,14,14,04 DATA 04,04,04,04,04,04 ---- Code End In this code BSAVE and BLOAD haven't got much use, but it's most common to create an image editor that works with BSAVE and then load that data from within the program you're creating, a great graphics editor is Pixel Plus 256. - Fast PALETTE: If you're building a game or something like that you might want to change the whole palette which is a slow process (for programming anyway) and in QBasic it's really slow, unless you write the colors directly to the video card. This is how you do it: ---- Code Start: OUT &H3C8, ColorAttribute OUT &H3C9, RedValue OUT &H3C9, GreenValue OUT &H3C9, BlueValue ---- Code End RedValue, GreenValue, and BlueValue have a range of 0 to 63. But then one thing is missing, how would you read the RGB colors? well here's some code that'll do that: ---- Code Start: OUT &H3C7, ColorAttribute Pal(ColorAttribute).Red = INP(&H3C9) Pal(ColorAttribute).Green = INP(&H3C9) Pal(ColorAttribute).Blue = INP(&H3C9) ---- Code End That should do it, I'd even explain it, but it's kinda hard to get, so if you really need to know just e-mail me. - Reducing flicker Ok, perhaps your routines are too slow for your graphics and you're getting a lot of flicker in your programs so here's some stuff you can do: - Fast PSET: This is a simple routine that does a fast PSET on screen mode 13 ONLY! ---- Code Start: SCREEN 13 DEF SEG = &HA000 POKE ((PY * 320&) + PX), Color DEF SEG ---- Code End Note: don't delete the "&" that's after 320 that wasn't a type'o, that was to make that value a long value so that it could cover the whole screen. - Fast POINT: Basically the same as Fast PSET turned inside-out. Also only works on Screen Mode 13. ---- Code Start: SCREEN 13 DEF SEG = &HA000 LET Color = PEEK((PY * 320&) + PX) DEF SEG ---- Code End - WAIT &H3DA: This is a statment that skipps the drawing to the screen, imagine your program is running so approximately at every 1/60 of a second QBasic will dump graphics to the monitor card, but if you have unfinished graphics inside your program it dumps them anyway? Well then you get flicker! So, what you do is to put: WAIT &H3DA, 8 WAIT &H3DA, 8, 8 in your program to avoid that premature dump, reducing flicker! -------------------------------------------------------------------------------------------------- 09. Credits and Finishing: Well I'd like to thank the people at QBasic.com's forum who have helped me to understand a lot of stuff throughout the years I've been visiting there, I'd like to thank Alipha for this opportunity. Well here we come at the end of this tutorial. I hope you enjoyed it, or at least learned something. If you have any doubts about anything on BASIC you can e-mail me, LKT153 (lkt153@bol.com.br). Now here are the credits: - LKT153..........................Author of the tutorial - Alipha..........................Owner of tthe Home Page - QBasic.com' forum people........A great paart of the code in this tutorial. Started at: October 8th, 2001 Finished at: October 10th, 2001 That's it! Happy programming! LKT153