TRS-80 graphics made almost painless. (Part 3) John Crew.
TRS-80 Graphics Made Almost Painless
The third in a three-part series, this, article describes Graphics Manager, a program that stores whatever is on the screen when it is called. Stored screen images (which I call frames) can be combined, compressed, saved on tape, loaded from tape, and printed on the screen. Graphics characters and ASCII codes can be listed as well. Frames can have either single or double width characters.
The maximum number of frames that can be simultaneously stored in memory depends on the current amount of free string space (frames are stored as strings). The absolute maximum which can be stored is nine because the subroutine which accepts frame numbers uses a single digit. Graphics Manager in Listing 1 clears 6553 bytes of string space which is just enough to hold six frames with single width characters. If some frames are compressed or have double width characters you may be able to store nine frames.
Graphics Manager requires at least 16K of free memory. If you have more, the program will work without modification. The computer for which the programs in this series were written was described in Part 1. You don't need an MX-80 printer unless you want to print frames on paper without modifying the program.
I wrote Graphics Manager to help me design graphics for programs, to manipulate frames, and to provide a means of printing, recording, and loading frames. Graphics Manager can simplify the design of graphics for TRS-80 programs by allowing you to write a graphics creation program with the slow but versatile SET command.
Add Graphics Manager to that slow graphics creation program. Then list ASCII codes to see the character code and screen position of every character. Next, rewrite the graphics creation program using faster techniques such as POKEing character codes or printing strings of graphics characters.
If you have a favorite computer generated picture, Graphics Manager can print it on paper. If you add my Sketch/Print program (January 1983) you can doodle, make cartoons, create art work or save screens filled with text and graphics.
The ability of Sketch/Print and Graphics Manager combined to record screens of text and graphics can be used to create a simple educational program. The teacher would type information on the screen as it would appear to the student. He would then record a series of screen contents on tape. Students would use a stripped-down version of Graphics Manager to load and view the frames.
Because Graphics Manager allows printing of some or all frames in forward or reverse order at a rate controlled by the user, students could review a screenful of information as many times as they liked until they understood and remembered it. A student could also print some frames on paper if he wanted to study them later.
This method of teaching merely uses the computer to replace a book and doesn't take advantage of the ability of the computer to ask and answer questions. This approach might be used to teach young children simple concepts by using graphics and words to present the material.
Mistakes In The Manual
Writing Graphics Manager was complicated by the poorly organized, sometimes unclear, something incorrect, and often too brief Level II manual. The quality of the manual varies from section to section. The more I learned about Level II, the more I appreciated Microsoft Basic and the less I appreciated the manual. I'll mention just a few things the manual doesn't cover.
INPUT won't accept more than 240 characters at once, which should rarely be a problem.
The only place you can use TAB in a PRINT USING statement is between PRINT and USING (the only legal form is PRINT TAB (N) USING . . .). When you add or delete lines from a program, Level II moves the rest of the program around as needed so that the pointers to the next line are always in ascending order. PRINT TAB (N) works much like PRINT STRING$ (N, "') for N<64.
The Edit mode of Level II can be used to find lower case letters.
FRE("') can be used instead of something like FRE ("A') which saves one byte. FRE(0) works the same way as MEM.
You can use an arithmetic expression such as ERROR N/10+2 after CLEAR or ERROR. If you use an arithmetic expression after ERROR and you are told there is a syntax error in the line where ERROR appears, don't pay attention to that message.
If you put spaces between GO and TO, they are removed so GOTO is always one word.
When you print a number, Level II won't print it on the current line if there isn't room for the entire number. When you record a string with leading blanks or one which contains a comma or colon, you should record that string with a quotation mark at the front. If you record a string on tape with a quotation mark at the beginning and a quotation mark before the end, you'll get an FD (bad datum in file) error when you try to load it, and only the part of the string between the first and second quotation marks will be loaded.
Using Graphics Manager
An external program that calls Graphics Manager must be added to Graphics Manager for it to work. Graphics Manager appears in Listing 1.
The program doesn't require you to press the ENTER key when you are typing in information. It quickly reacts to the pressing of a key and either accepts or rejects it.
The menu lists will primary options and tells you how much free frame storage is left, permanently reserving 408 bytes for workspace. When you see that 408 or 409 bytes of frame storage remain, only the workspace is left and no more frames can be stored unless you make more room by compressing or erasing some frames. To select one of the options listed in the menu, press the key which represents your choice. Next to each letter which represents an option is a short description. If you press a key other than the ones used to represent options, it will be ignored.
Storing, compressing, and reversing graphics characters, and uniting two frames are somewhat slow (longer than 10 seconds). Graphics Manager performs these operations visibly so you can tell how near completion they are. As each character in a frame is stored, it is erased on the screen. The reversal of graphics characters and the union of two frames are also performed on the screen so you can see how much has been done.
For every option except the frequently used print option, you are asked to confirm your choice. This enables you to return to the menu if you pressed the wrong key or changed your mind.
Stored frames are referred to by using a number. The number of a frame is between 1 and the count of currently stored frames. When Graphics Manager asks you to enter a frame number, the legal range is printed in parentheses following the request for a number. Some functions ask for one frame number, some ask for two, others ask for the first and last frame numbers for the range of frames.
When you print, erase, compress, save (record on tape), list ASCII codes, or reverse graphics characters, you are asked to enter the starting and final frame numbers of the range of frames you want the function to work on. If the final frame number is greater than the first, you are asked if you want the function done to that range of frames in reverse order. You can have reverse order for any of the options listed above. If, for example, you had six frames stored, you could print 1-6, 6-1, 2-4, 3-3, or some other legal range of frames.
Frame compression is one of the most important features of Graphics Manager and one of the most difficult to implement. It uses the seldom used space compression characters. Substrings of consecutive blanks (ordinary blank, graphics blank, or CHR$ (193)) are replaced by a compression character. Up to 63 blanks can be replaced by a single compression character. When a compression character is printed, it is expanded to a series of blanks. Frames without two or more consecutive blanks within a subframe are unchanged when you try to compress them. If you want to know how many bytes were gained by compression, note the free frame space before and after compression.
Compressing may give you enough room to store more frames. Printing or combining frames will be much faster if the frames are compressed. Most other parts of Graphics Manager will be slightly faster if some or all frames are compressed. If you want to decompress a frame for some reason, reverse its graphics characters twice. A compressed frame will expand to its original size when its graphics characters are reversed.
Combination Of Frames
Combination of two frames is done by printing the first frame and then merging graphics characters and putting nongraphics characters from the second frame into blank spaces in the first. You could think of combination as putting the second frame behind the first so the characters in the second frame show through holes in the first. The first frame number you type is the frame which will be printed first. The second is the number of the frame to be combined with the first. A comma is automatically put between the two numbers. The combined frame is stored as a new frame so you can't unite two frames if there isn't enough room to store the result.
Combining two frames may give different results depending on the order of combination. This is because a graphics character in the first frame takes precedence over a nongraphics character in the second, and an alphanumeric character in the first takes precedence over any type of character in the frame.
Combination is faster if the second frame is compressed more than the first. If you want to combine two compressed frames, estimate which is more compressed (the one with the most blank spaces) and type its frame number second. The more compressed frame will be quickly put behind the first.
You can, of course, quit when not saving or loading frames by pressing the BREAK key, but I suggest you use the quit option instead. When you use the quit option, all variables are erased and the large amount of string space used by Graphics Manager is released. If the printer is turned on, it is set back to 80 characters per line.
Returning To The Fraphics Program
I call the graphics creation program the main program even though Graphics Manager may well be longer and more complex. If you want, Graphics Manager can put a frame on the screen before returning. If the graphics program takes advantage of the ability of Graphics Manager to return frame, you can modify that frame and then store it if there is room. Sketch/Print or Vector Plotter can modify a frame sent by Graphics Manager.
Don't try to save on tape a frame which contains a quotation mark because you will get an FD error when you try to load it. You can store such a frame and do anything except load it properly. This problem is a result of the way Level II handles string input.
Graphics Manager does much error checking to avoid having the program fail because you pressed the wrong key or asked for a function under the wrong circumstances. I tried to do a thorough job of making the program reject erroneous information and print a message when it detects an error. Depending on the error and where it occurs, Graphics Manager will either go to the menu or repeat the question you answered incorrectly.
If you ask for any option other than load, return, or quit when no frames are stored, the program will tell you that frame storage space is empty. If you try to store, load, duplicate, or unite two frames when nine frames are stored, the program says frame storage space is full. If there are fewer than nine frames stored and too little memory to hold another frame when you try to do one of the four things just listed, Graphics Manger will tell you that frame space is insufficient to store a frame.
If you ask Graphics Manager to print frames on paper, it checks the printer. If the printer isn't on-line, the program says the printer isn't ready and then asks you again if you want frames printed on the MX-80.
You also get an error message if you try to combine two frames which have different width chacters. Whenever you enter a frame number, Graphics Manager checks to see if it is in the proper range.
Adding Graphics Manager To Another Program
Figure 1 lists the steps you should follow to append Graphics Manager to another program you have recorded separately. These instructions are for a Level II cassette system but should work for most related systems. The program to which Graphics Manager is to be added should have line numbers greater than 0 and less then 32049. You can have line numbers greater than 32690 but those lines will have to be either typed after Graphics Manager has been added or appended to the combination of Graphics Manager and the low-numbered lines of the graphics creation program.
The common method of merging programs in Figure 1 works by setting the address of the beginning of Basic program storage to the address of the end of the program in memory. That moves the beginning of Basic program storage to just after your program so CLOAD, NEW, and LIST won't affect your hidden program. Graphics Manager is loaded next.
Line 0 of Graphics Manager is listed just so you can see it if you don't have a printed copy handy. Next, line 0 is deleted. Then the beginning of Basic program storage is set to its previous value (which you should have written down after step 3). Finally, line 0 is retyped so it is put before the graphics creation program.
The graphics program you combine with Graphics Manager must leave 408 bytes of string storage. If you use an ON ERROR GOTO statement in your graphics program, you must execute that statement every time you return from Graphics Manager because the program uses an error handling subroutine. Don't use any of the following variables: FC%, MF%, QB$, SC$ (0-4, 0-MF%), or CM% (0-MF%).
Figures 2 and 3 give complete instructions for adding Sketch/Print or Vector Plotter. Figures 2 and 3 refer to the merging process described in Figure 1. If you add Graphics Manager to Sketch/ Print as shown, you can go to Graphics Manager and store a frame at any time after the instructions by pressing the ENTER key, or if you want to go straight to the menu, press the SHIFT and (escape code) keys. The SHIFT, , and ENTER keys are used the same way if you combine Vector Plotter and Graphics Manager as shown, but you must wait for all vectors to be drawn before those keys are recognized.
Calling The Program
To add Graphics Manager to one of your own programs, you need to know how to get to Graphics Manager and
Figure 1. Merging Graphics Manager with Another Program.
Do the following in the command mode:
1. CLOAD first program
3. PRINT PEEK (ST); PEEK (ST+1). Write down the two numbers printed
4. BRW=PEEK(FIN)<2:POKE ST, PEEK(FIN)-2-256*56BRW: POKE ST+1, PEEK(FIN+1)+BRW
5. CLOAD Graphics Manager
6. LIST 0
7. DELETE 0
8. POKE at 16548 and 16549 the two numbers printed in step 3
9. Retype line 0
Figure 2. Adding Graphics Manager to Sketch/Print
1. Merge programs
2. Remove CLEAR2:DEFINTA-Z:in line 100
3. Insert IFN=13ANDS=0,32050ELSEIFN=27,32090 ELSE at the beginning of line 185
4. Change line 32399 to 32399 GOTO 160
Figure 3. Adding Graphics Manager to Vector Plotter.
1. Merge programs
2. Change line 360 to 360 QA INKEY$:IFQA CHR$ (13)THEN32050ELSEIFQA CHR$ (27)THEN 32090ELSE360
3. Change line 32399 to 32399 GOTO110
4. Remove CLEAR12:DEFINTA-Z:in line 100
5. Add: B "' after NEXT in line 350
6. Insert IFQE>=0GOSUB32590ELSE before CLS in line 230
7. Add: QE=-1 to end of line 100
Graphics Manager is written entirely in Basic. It is very compact, and, I hope, efficient. It doesn't use any READ or DATA statements, so you can use them in your own graphics creation program without problems. It uses as few variables as practical. It also uses integer variables for storing numbers, because they use less memory and arithmetic is faster using them. I tried to use variable names which you probably wouldn't use in your graphics program.
Figure 4 lists all the variable used in Graphics Manager along with a short description of the use(s) of each. The variables QA, QB, QC, . . . QI are used for a variety of short-term purposes. You may use QA-QI in the graphics creation program, and if speed is your goal, you should use those variable to minimize the time spent by Level II looking through the variable storage area.
Graphics Manager uses zero positions in arrays so memory isn't wasted. The user isn't aware of this since frame numbers go from 1 to the count of stored frames.
How The Program Works
Frames are stored in an array called SC$ (screen characters). Level II allows character strings to be a maximum of 255 characters long. A frame with single width characters uses 1024 bytes of string memory so it must be stored as five strings (which I call subframes). A frame with double width characters takes 512 bytes, so it could be sotred in three strings, but I had Graphics Manager store it in five strings so the program would be simpler. Figure 5 shows how characters are distributed among the five strings in each frame.
A subframe is stored by first reserving the needed space by assigning a string of blanks to that subframe using the STRING$ instruction. Then characters are PEEKed from video memory and POKEd into string memory. This method is fast because it minimizes string space reallocation (also known as garbage collection).
When a frame is compressed, each subframe is compressed by itself and no characters are moved from one subframe to another. The first blank is searched for, and, if there is a blank, the next nonblank character or the end of the string is searched for. If there is a substring of two or more consecutive blanks in a subframe, it is replaced by a space compression character.
Frame erasure releases the memory used by the erased frames. If you want a range of frames which includes the final frame erased, then erasure is done by assigning a null string to each subframe of each frame to be erased. If you want some beginning frames erased but not the final frames, then the final frames are moved down over the frames to be erased.
Frames are moved by copying string addresses and lengths so no physical movement of characters is done. That method of moving strings is fast and avoids an OS (out of string space) error when space is tight and a long string is assigned to a variable which held a short string. After moving down any strings which need to be moved, the indicated number of final frames are erased.
If you are still confused by the method used to erase frames, consider this example. Suppose you have seven frames stored and you want 4 and 5 erased. The range of frames you want erased doesn't include the final frame, so frames 6 and 7 must be moved down over 4 and 5 respectively. At this point 4 is the same as 6 and 5 is the same as 7. You wanted two frames erased, so two frames at the end are erased. The count of stored frames is reduced by two. The result is as if frames 4 and 5 were taken out.
Trading (swapping) two frames is done by exchanging string addresses and lengths.
Duplication of a frame is done by assigning the subframe of the frame being copied to an end location in the frame storage array. You can't copy a frame if there isn't room to hold the copy.
I didn't copy a frame by copying string addresses and lengths because I was afraid Level II would later make an actual copy of the string. I experimented a little with copying a string by setting the pointer and length of the second string to the pointer and length of the first and found Level II won't make an actual copy when it does garbage collection.
I suspect that if two strings have the same pointer and length and you use the name of one of those strings anywhere in an assignment statement, an actual copy of the original string will be made. I didn't use that method because I didn't know if it worked under all circumstances. I leave it to you to experiment with that method. If it works, you could easily change Graphics Manager to copy frames that way.
Unlike some graphics reversal subroutines I have seen, mine is fast, efficient, and doesn't disturb nongraphics characters. A blank space or graphics blank is replaced by a completely white graphics character.
I had an odd problem with line 32388 in the program. Sometimes extra characters would appear at the end. This problem seems to occur when a line of about 250 characters is listed after a line of 255 characters. I think Level II doesn't clear the output buffer after listing a very long line so the next long line gets some characters from the previous one.
When this problem occurs, remove the unwanted characters from the line in which they appear using the edit mode. Then list a short line. Next list the line which had extra characters and you should see only the desired characters in that line. To avoid the problem, either use short lines or don't list the program unless you are willing to go through the corrective steps mentioned before.
In a few places Graphics Manager ends a loop early because some special condition is detected. This is done by setting the loop index to its final value and then executing a NEXT for that loop. This is done in line 32130, the search of the command string, if a match is found. It is also done when reversing graphics characters if a compressed frame would expand more than there is room for.
Modifying And Extending The Program
I grew tired of waiting for a frame to be stored and found that frames with quotation marks in them wouldn't be loaded properly, so I wrote the two assembly language subroutines shown in Listing 3. The first subroutine stores a subframe after space has been reserved for it. The second scans a string for a quotation mark. If a quotation mark is found, the subroutine returns a 0; if none is found, a nonzero number is returned. The machine language string scan subroutine is much faster than scanning a string in Basic, using a loop and the MID$ function to check every character.
To put the two machine language subroutines in memory you can either use an assembler to make a system format tape and then load it, or, if you prefer Basic, you can use the Basic program in Listing 4 to put the two machine language subroutines in high memory. The program in Listing 4 sets the memory size for you and lets you put the machine language program in memory starting at any high address. It checks each byte of the machine language program to make sure it was POKED properly. If you have a bad memory location or you ask for the machine language program to be put in nonexistent memory, you are told that a datum (part of the machine language program) wasn't properly stored.
To make Graphics Manager work with the two machine language programs, lines 32240, 32260, and 32630 should be changed to match Listing 5. Delete 32640 and 32664. If you use the Basic program to put the machine language into memory, you can remove CLEAR6553: from line 0. The program in Listing 4 ends with CLOAD so Graphics Manager will be automatically loaded, so I suggest you record the modified version of Grapics Manager right after it.
The program or program segments which appear in Listings 3 through 5 are written for a system with 16K of free memory. If you know Basic well and know a little assembly language, you could easily modify them for a different amount of free memory.
Extensions and Modifications
Some features you might want to incorporate into Graphics Manager are disk storage and retrieval of frames, and storage of more than nine frames at once (you would need to change the subframe number entry subroutines). You might also CLEAR more string space (If you have more than 16K of free RAM, I recommend CLEAR 1024*N+409 with N equal to the number of frames you want to be able to store at once); give each frame a name and search for a frame by name on tape, disk, and in memory; rewrite some of Graphics Manager in assembly language; allow storage and manipulation of partial frames; allow switching of frames from single to double width characters and vice versa; or write an assembly language program to load a frame containing a quotation mark.
Some more exotic features you might want to add are: top-bottom reflection of a frame; right-left reflection of a frame; shifting a frame right, left, up, or down; and rotating graphics about a user specified center. You might want to modify Sketch/Print and/or Vector Plotter so they could use double width characters.
I hope you have found a useful program or learned something from this series. I worked extremely hard preparing it--experimenting, writing, and rewriting and I ask you to have the decency not to distribute my programs for your own profit.
Table: Listing 1. GM (Graphics Manager).
Table: Figure 4. Variables used by Graphics Manager.
Table: Figure 5. Bytes of String Spaces Required for an Uncompressed Frame. back. This can be done in either of two ways: You can use GOSUB to jump to Graphics Manager and RETURN to get back, or you can use GOTO to get back. If you enter Graphics Manager at line 32050, the current screen contents will be stored if there is room. If you want to go directly to the menu, enter Graphics Manager at line 32090. Line 32399 contains the statement which goes back to the graphics creation program.
Table: Listing 2. Lines which can be added to GM to test GM.
Table: Listing 3. Assembly Language version of subframe store and scan subroutines.
Table: Listing 4. Relocating Basic loader for subframe store and scan Machine Language subroutines.
Table: Listing 5. Lines in GM which are modified to use the two Machine Language subroutines.
Table: Partial sample run of Graphics Manager.