Medium resolution line printer plotting. Kimball M. Rudeen.
While I am usually perfectly satisfied with my TRS-80 Model III, I sometimes wish the graphics were a little better. There are times when I could do with a better aspect ratio or smaller pixels or a work space larger than 128 x 48. This article describes a method I have found for reaching these goals using a line printer with graphic character capacity.
As most people who have such a printer will know, it is simple to copy a screen display onto paper. I use a simple PEEK loop that reads the TRS-80 screen memory character by character and prints each character using the CHR$ function. My printer, an Okidata Microline 82A, has maximums of 16.5 characters per inch and 8 lines per inch. Given the 2 x 3 pixels per character of the TRS-80, this means a printed pixel of about .03" x .04". Thi is quite an improvement in aspect ratio and resolution, but there is still the 128 x 48 work space limit.
The PEEK loop I use to copy the screen picture suggested an interesting possibility. This loop treats the TRS-80 screen as a linear array in memory, with each entry containing a byte of a data defining six pixel locations. A similar loop could be used to store a screen picture in a 2D array, or to reproduce a picture so stored.
Consider an N x M array, with each entry able to contain a byte or more of data. Since TRS-80 graphic character codes can be stored in this array, it could also be though of as a 2N x 3M pixel array. if N > 64 and M > 16, the work space provided by this array exceeds the TRS-80 screen space.
The method I have developed makes it possible to address single pixels directly in such an array. It enables a program to translate an x,y location and the character code that will turn the pixel matching that location on.
The best way to describe this method is to state the problems it must solve. Given an x,y location and a N x M array, it is necessary to:
* Determine which entry in the array contains the pixel corresponding to the x,y location.
* Determine the character code that will turn that pixel on in a printout.
* Load that character code into the array entry without disturbing any pixels already set.
The first problem can be solved very simply. Each entry in a row of N array entries contains two columns of pixels for a total of 2N columns. The x location of a pixel specifies the column containing it. If these columns are numbered 0 to 2N-1, then an integer division of x by 2, or INT(X/2), will generate a number from 0 to N-1. This number gives the entry in a row of N entries containing the pixel with the given x coordinate.
Similarly, the function INT(Y/3) will identify the entry in a column of M array entries that contains the pixel with a given y coordinate. So, given the N x M array and an x,y pixel location, the function INT(X/2) and INT(Y/3) will specify the array entry containing the pixel.
The second problem is a bit more complex (pun intended). The basic TRS-80 graphics character code is 128 for a blank space with no pixels set. The six pixels in a character block are set by turning on bits 0 to 5 in addition to bit 7 (128 in binary). Figure 1 illustrates the binary values of the bit positions corresponding to each of the six pixels.
The problem becomes one of converting an x,y location into the bit position for that pixel within its array entry. Consider again the value generated by INT(X/2). This gives the integer result of X divided by 2. The function X-2.sup.*.INT(X/2) generates the missing remainder, which is 0 if X is a multiple of 2, or 1 if X = 2P + 1 for some integer P. But X is a multiple of 2 if X is in the first pixel column of some character position, and X = 2P + 1 for some P is X is in the second column. Similarly, the function Y-3.sup.*.INT(Y/3) gives 0, 1 or 2 for Y in the top, middle, or bottom pixel row of a character position. Figure 1 illustrates this as well.
Let BX = X-2.sup.*.INT(X/2) and BY = Y-3.sup.*.INT(Y/3). Then the function BP = BX + 2.sup.*.BY will return the six values 0 to 5 when given the BX, BY coordinates of the six locations, and therefore specifies the bit position to turn the pixel on. This solves the second problem.
Now the third problem: give the array entry and the position of the bit to be set, set that bit without changing any other bit settings. Given an integer variable, a single bit in that variable can be set by the OR function. If the integer variable is ORed with another variable containing the proper power of 2, the chosen bit will be set. The quickest and most reliable way I have found to do this is to use an array containing the powers of 2 from 0 to 5, indexed by the function BP = BX + 2.sup.*BY. The indexed entry can be ORed into the array entry to set the specified bit.
To sum up, given an x,y pixel location to be set in an M x N array:
* Use the functions PX = INT(X/2), PY = INT(Y/3) to locate the array entry containing the pixel.
* Use the functions BX = X-2.sup.*.PX, BY = Y-3.sup.*.PY, and BP = BX + 2.sup.*.BY to determine the bit position to be set to turn the pixel on.
* OR the PX,PY array entry with BP of an array containing the powers of 2 in the order illustrated in Figure 1.
After all array entries have been set, the array can be printed out. In this method, the array is scanned left to right, top to bottom. If an entry is empty, it is added to 128 to create a graphics character code. The CHR$ function is then used to print out the character. This saves the trouble of initializing all array entries to 128. The Programs
Listing 1 is a program for generating Spirolateral figures, as described by Donald T. Piele in the March and April 1982 issues of Creative Computing. I found that the figures generated by this program were either too large to fit on my screen, or made no sense when displayed in TRS-80 low-resolution graphics.
Listing 2 is the same program with additions and modifications to plot into and display from an internal array. Lines 10-40 define the internal array PT% with a 256 x 192 pixel space, and initialize the pixel setting array BIT%. Lines 210 and 240 have been modified to call the pixel setting and printout subroutines. These subroutines are contained in lines 500-520 and 1000-1060 respectively. The original program was modified in only two places. The rest of the code was added.
Figure 2 is a Spirolateral figure generated by this program from the input string RRRRRLLLRRRRR. This same plot was too large to fit on my TRS-80 screen when generated by the Listing 1 program.
Some readers may have noticed a major drawback to this method as implemented in Listing 2. The 128 x 64 integer array PT% requires more than 16K bytes of memory--too much for a 16K TRS-80. This is due to a very inefficient storage system. Only the first six bits of each 16 bit integer array entry are used to store data.
Fortunately there is a way to extend this method and double data storage. A second 2 x 3 pixel array can be stored in bits 6 to 11 of an integer array entry. An N x M integer array now becomes a 4N x 3M pixel array. Figure 3 illustrates the bit positions for this new coding. Notice that each entry in the left 2 x 3 pixel set is equal to the corresponding entry in the right pixel set sup.*.64, or shifted six bits to the left.
The coordinates of the array entry of a pixel at x,y are now given by PX = INT(X/4) and PY = INT(Y/3). Only the X function is changed. Similarly, the bit positions are now given by BP = BX + 4.sup.*.BY for BX = X-4.sup.*.PX and BY = Y-3.sup.*.PY. The BIT% array now contains twelve entries, initialized according to the bit positions shown in Figure 3.
The most complicated change is unpacking this new coding for printout. Each entry now contains two characters, in bits 6-11 and 0-5. These two-bit sets must be unpacked separately. Listing 3 is Listing 2 converted to double storage. The array initializations and pixel setting subroutine have change as described above. The printout function now prints two space for every empty array entry. If an entry is not empty, it is unpacked in two stages.
First, the entry is ANDed with the integer 4032, which in binary is 111111000000. If anything is stored in bits 6-11, the test condition is true and the entry is divided by 64 to shift bits 6-11 to positions 0-5. The entry is then decoded and printed out.
Next, the entry is ANDed with the integer 63, which in binary is 000000111111. If anything is stored in bits 0-5, the contents of bits 6-11 (if any) are stripped off and the remaining bits in positions 0-5 are decoded and printed out.
Any program using the SET function could be converted to use this method. If memory is tight, the internal graphics array could be cut down quite a bit and still be an improvement on a screen display. In addition, the bit manipulation method I have described could easily be used to create subroutines to replace the RESET and POINT functions. Given all of these subroutines and the proper line printer, TRS-80 graphics are no longer limited to the screen.