ELEMENTARY GRAPHICS
part 1 of 3
ZX Computing, July 1986

Toni Baker begins a three part series on
mastering machine code graphics.


When we use the word "Graphics" we usually think of producing complex,
and possibly moving or 3D effect pictures on the screen - but graphics
doesn't necessarily mean that. Something as simple as PRINT "*", is
utilising graphics - and in machine code if you don't understand how
to do the simple things then the more intricate designs will remain
beyond you.

By "Graphics" I mean the process of altering the image on the TV
screen in any way at all. This three part series is intended to cover
the bare necessities of machine code graphics. In future series I may
go on to the cleverer stuff, but for now this is a beginner's course -
an apprentice course.

The first things we need to know about are streams and channels. A
"Stream" is computer jargon for "A small river", whereas a "Channel"
is what we computer experts call "A narrow sea". These nautical
references are, I confess, difficult concepts to master when viewed in
the context of the ZX Spectrum, but the words are taken from the
conventional English words of the same spelling: a stream (a number
between zero and fifteen to index input and output) and channel (a
device such as a printer or a TV screen). I hope I've made that clear.

We need to know about them because although you can forget them
altogether in BASIC if you want to, in machine code they are of
paramount importance. Because:
INPUT "HELLO";A$ is the same thing as PRINT #0;"HELLO";: INPUT A$
PRINT "HELLO" is the same thing as PRINT #2;"HELLO"
LPRINT "HELLO" is the same thing as PRINT #3;"HELLO"

The point is that all printing - whether to the main screen, the lower
screen (ie. the bottom two or more lines normally used for INPUT
items) or to a printer are - as far as the machine code programmer is
concerned - identical. In the BASIC statements above, the number after
the "#" symbol is the stream number. Since we usually only ever need
to print to the screen or the printer, or to the lower screen (when
creating an INPUT prompt) the stream number (in BASIC) does not
normally need to be included - hence we usually use the format on the
left. In machine code all is different. The stream number is
essential, and must be specified:
Stream #0 refers to channel "K" - the lower screen
Stream #1 refers to channel "K" - the lower screen also
Stream #2 refers to channel "S" - the main screen
Stream #3 refers to channel "P" - the printer

Although the channels which these stream numbers refer to may be
altered by the OPEN # command, to do so is not very usual. I shall
therefore assume they have not been altered, and shall continue to use
#0 for the lower screen, #2 for the main screen, and #3 for the printer.

Now - as you know - a computer can only ever do one thing at a time.
This means it can only ever print one thing at a time. It can't print
both to the screen and to a printer simultaneously. For this reason it
can only ever need to use one channel, and hence one stream, at a
time. The stream it is using at any particular moment is called the
current stream, and likewise the channel being used is the current
channel.

When we call a machine code subroutine the current channel is
unchanged. This means that the current channel remains as it was the
last time any printing or editing was done. Usually we find this is
channel "K" (the lower screen), but it may not be.

Assuming that your program hasn't used the printer (or a microdrive or
network, etc.) since the last time you entered a command (eg. RUN) or
input something, then the current channel will still be either "K" or
"P". I'm going to show you an ingenious little trick to switch between
the two. You see the only difference between these two channels is bit
zero of the system variable TVFLAG. This bit is reset for channel "S"
and set for channel "K". This means that we can switch between
channels "S" and "K" just by changing this bit. To demonstrate, try
running the two programs of Figure One to see exactly what difference
the first instruction makes. (Incidentally the values of the remaining
bits of TVFLAG are irrelevant at this point.)

Another trick is to switch between channels "K" and "S" and channel
"P". Again only one bit is needed to tell the difference. Bit one of
the system variable FLAGS is reset for channels "S" and "K" but set
for the ZX Printer. The program of Figure Two will print to the ZX
Printer using this simple trick. (But WARNING - The SPECTRUM 128 does
not allow the use of the ZX Printer when in 128K mode. The program
(Figure Two) must not be run on a Spectrum 128 in 128K mode or it will
cause a crash!)

But now for the general case - and this will work on a Spectrum 128 in
128K mode. We may change the current channel by selecting a new stream
number. All we have to do is to load the A register with the desired
stream number, and call a simple ROM subroutine. Figure Three shows
this being done to select the screen as the current channel. Remember
that this will always work, irrespective of whichever was previously
the current channel. Users of the Spectrum 128 should note that when
they are in 48K mode channel "P" is the ZX Printer, but when in 128K
mode channel "P" is the built-in RS232 interface at the left hand side
of the Spectrum case. It is safe to select channel "P" (ie. to select
stream number three) in either case by this method.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FIGURE ONE
(Program a)
     FD360200 DEMO_S    LD   (TVFLAG),00   ;Reset bit zero of TVFLAG
     3E2A     LOOP      LD   A,"*"         ;A contains the ASCII code for "*"
     D7                 RST  10            ;Print CHR$(A); to current channel
     18FB               JR   LOOP

(Program b)
     FD360201 DEMO_K    LD   (TVFLAG),01   ;Set bit zero of TVFLAG
     3E2A     LOOP      LD   A,"*" 
     D7                 RST  10            ;Print an asterisk
     18FB               JR   LOOP

FIGURE TWO
     FDCB01CE DEMO_P    SET  1,(FLAGS)
     0600               LD   B,00          ;Prepare for 256d asterisks
     3E2A     LOOP      LD   A,"*"
     D7                 RST  10            ;Print an asterisk
     10FB               DJNZ LOOP          ;Repeat until finished
     C9                 RET

FIGURE THREE
     3E02     GEN_S     LD   A,02          ;A contains desired stream number
     CD0116             CALL CHAN_OPEN     ;Select this as the current stream
     3E2A     LOOP      LD   A,"*"
     D7                 RST  10
     18FB               JR   LOOP
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

All of this is essential to know because Spectrum printing is achieved
with the machine code instruction RST 10, and RST 10 will only print
to the current channel. It is vital that you tell the computer where
and how you want to print before you tell it what to print.


RST 10

You can use the machine code instruction RST 10 to print any single
character, be it an ASCII character, a graphics character, or a token.
Simply load the A register with the code of the character and then
execute RST 10. Note that ordinary characters when printed occupy only
one print position, but keyword characters between A5 and FF (between
A3 and FF on the Spectrum 128 in 128K mode) are expanded and printed
in full, using as many print positions as there are letters in the
keyword. In all cases RST 10 leaves all registers (except BC', DE' and
possibly A) unchanged.


PRINTING NUMBERS

Single digits between 0 and 9

To print a number between 0 and 9 either load A with the number and
CALL OUT_CODE (at address 15EF) (NOTE: this corrupts the E register),
or load A with the ASCII code for the digit and use RST 10. Note that
ADD A,30h / RST 10 uses exactly as many bytes as CALL OUT_CODE, but
does not corrupt the E register.


Integers between 0 and 9999

To print a positive number less than 10000d (or zero) simply load the
number into the BC register pair and CALL OUT_NUM_1 (at address 1A1B).


Integers between 0 and 65535

Two subroutine calls are required this time, once the integer has been
placed in the BC register pair. First CALL STACK_BC (address 2D2B),
then call PRINT_FP (address 2DE3). STACK_BC pushes the number onto the
calculator stack, and PRINT_FP is a general purpose routine to print
any number.


Negative Integers

Print a minus sign (by loading A with 2D and using RST 10) then print
ABS of the integer using one of the methods described above


All floating point numbers

Since it is not possible to store a full floating point number in a
single register or register pair it follows that a floating point
number can only arise as a result of using the calculator (see
separate series in this magazine). In such a case, the number to be
printed should be left at the top of the calculator stack, and then
CALL PRINT_FP (address 2DE3). In addition to printing the number the
PRINT_FP subroutine will also delete the floating point number from
the calculator stack. (Warning: If the number is strictly between -1
and +1, zero excluded, then a bug in the ROM causes an unwanted zero
to be left on the calculator stack at this point).


PRINTING STRINGS

There are (at least) three different ways to print a string. In each
case the text of the string must be stored at a fixed location in the
Spectrum's memory. The first method of printing strings is also the
simplest. DE must point to the first character of the string, and BC
must contain the length of the string. Then simply CALL PR_STRING (at
address 203C).

Secondly, note that a string can arise as a result of using the
calculator, in which case the string can be made to appear at the top
of the calculator stack. In this case it is possible to print it
straight from the stack by the procedure CP A followed by CALL
PR_STR_1 (address 2036).

Thirdly it is possible to print the (A+1)th string in a table of
strings. DE must point to the byte before the first character of the
first string - the byte pointed to must be between 80 and FF. Neither
graphics characters nor token keywords are allowed in such a string.
Each string in the table must be non-empty and must be terminated by
the last character of the string having bit seven set. Assuming such a
table has been constructed and registers A and DE assigned
accordingly, the required string may be printed by CALL PO_MSG (at
address 0C0A).


Things to watch out for

Note that if, at any stage, one of the predefined graphics characters
(whose code is between 80 and 8F) is printed, then system variable
addresses 5C92 to 5C99 will be corrupted. This corrupts calculator
memories zero and one (see calculator series). This is totally
unimportant unless you are either using the calculator or storing
information in these variables.

Also - if the subroutine PRINT_FP is called then all six of the
calculator's memories will be corrupted - that is the whole of the
system variable MEMBOT (addresses 5C92 to 5CAF). Again this is totally
unimportant unless you are using the calculator memories or storing
information in this area.

Finally, note that the system variable MEM normally contains the
address of MEMBOT. If the value of MEM has been altered (this is
highly unlikely but I include it for sake of completeness) then
PRINT_FP will not work correctly.


The control characters

In the Spectrum character set codes 20 to 7F are ASCII characters and
codes 80 to FF are graphics characters and keyword tokens This leaves
the codes 00 to 1F. These are control characters, and can be used to
perform PRINT AT and stuff like that.

Control 16h is the AT control (not to be confused with the keyword AT
which has code ACh). Control codes are operated by loading the code
into the A register and using RST 10. This means that they may be
"printed" just like any other character, and may be included in
strings. AT needs two parameters, and the control code is no
exception. The two parameters must be specified in the right order,
and these too must be "printed" with RST 10. Figure 4(a) shows the at
control code being used to simulate PRINT AT 5,4;.

Nor does it matter how many machine code instructions are executed
between the three occurrences of RST 10. The Spectrum will always
"remember" that the next two RST 10s will be AT parameters.

The use of the AT control character plays its most useful role when we
want to PRINT AT a variable location. Figure 4(b) will simulate PRINT
AT D,E;.

The next control code we meet is TAB, which is control 17h. This
control code also requires two parameters: the low byte, followed by
the high byte, of the required TAB column number. Of course, on the
screen (and the ZX Printer) there are only thirty-two columns - any
number higher than thirty-one is reduced to a number between zero and
thirty-one by continually subtracting thirty-two from the number. This
means that, effectively, the high byte is irrelevant (since 256d is a
multiple of 32d). This is not necessarily true when you print to a
printer other than the ZX (ie. via the RS232 interface). For this
reason, the second TAB parameter may only be considered arbitrary if
it is known in advance that the output will be sent to the screen or
the ZX Printer. Figure Five shows the tab control code in action. Here
I have considered the second byte to be important (just in case) and
so have assigned it with a correct value of zero. Normally, however,
the XOR A instruction could have been omitted.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FIGURE FOUR
(Program a)
     3E16     AT_5_4    LD   A,16
     D7                 RST  10            ;PRINT AT ...
     3E05               LD   A,05
     D7                 RST  10            ;5, ...
     3E04               LD   A,04
     D7                 RST  10            ;4;

(Program b)
     3E16     AT_D_E    LD   A,16
     D7                 RST  10            ;PRINT AT ...
     7A                 LD   A,D
     D7                 RST  10            ;D, ...
     7B                 LD   A,E
     D7                 RST  10            ;E;

FIGURE FIVE
     3E17     TAB_E     LD   A,17
     D7                 RST  10            ;PRINT TAB ...
     7B                 LD   A,E
     D7                 RST  10            ;E ...
     AF                 XOR  A
     D7                 RST  10            ;;
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

The COMMA control (code 06) may be used to simulate PRINT , (ie. TAB
to column 0 or column 16d) - it requires no parameters at all - simply
load the A register with 06 and use RST 10.

The ENTER control (code 0D) may be used to simulate PRINT ' (ie. print
a new line, which has the effect of moving the print position to the
start of the next line). Again, all we need do is load the A register
with 0D and use RST 10.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FIGURE SIX
     3E10     DEMO      LD   A,10
     D7                 RST  10            ;PRINT INK ...
     3E02               LD   A,02
     D7                 RST  10            ;2; ...
     3E11               LD   A,11
     D7                 RST  10            ;PAPER ...
     3E06               LD   A,06
     D7                 RST  10            ;6; ...
     3E2A               LD   A,"*"
     D7                 RST  10            ;"*";
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

RST 10 is a very versatile instruction. Not only will it do all I have
described above, but it can also be used to change the PAPER and INK
colours, and so on, just like in a BASIC PRINT statement. Suppose we
wanted to print a red asterisk on a yellow back ground. The program of
Figure Six will do just the job. Note that once the print colours have
been selected by this means they remain in force until they are
changed, or until the end of the BASIC statement which called the
machine code. Figure Seven shows all the control codes and what they
do, and what parameters they need.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FIGURE SEVEN

---------------------------------------------
CONTROL CODE   |   PARAMETERS NEEDED, IF ANY
---------------------------------------------
06 comma       |
08 backspace   |
0D enter       |
---------------------------------------------
10 ink         |   00 black
11 paper       |   01 blue
               |   02 red
               |   03 magenta
               |   04 green
               |   05 cyan
               |   06 yellow
               |   07 white
               |   08 transparent
               |   09 contrast
---------------------------------------------
12 flash       |   00 off
13 bright      |   01 on
               |   08 transparent
---------------------------------------------
14 inverse     |   00 off
15 over        |   01 on
---------------------------------------------
16 at          |   line number, column number
---------------------------------------------
17 tab         |   low byte, high byte
---------------------------------------------
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


Alternatives

For channels "K", "S" and "P" (the ZX Printer) it is possible to
achieve PRINT AT / PRINT TAB / PRINT , without using control codes at
all. Here's how:
PRINT AT B,C; can be achieved by CALL AT_B_C (address 0A9B)
PRINT TAB A; can be achieved by CALL TAB_A (address 0AC3)
[Actually called PO_FILL in the ROM disassembly. JimG]
PRINT, can be achieved by CALL PO_COMMA (address 0A5F)

There is also a rather easy way of changing both the paper and ink
colours at the same time. In fact this method will also specify the
current bright status and flash status while you're at it. All you
have to do is to change one system variable - it's called ATTR_T.
Simply construct an attribute byte with your required combination of
colours and load it into this variable. For instance, the single
instruction LD (ATTR_T),07 (FD365507) will change the colours to paper
black / ink white / bright off / flash off. To construct such an
attribute byte simply calculate (in decimal) 128*F + 64*B + 8*P + I
(where F=flash status, B=bright status, P=paper colour, and I=ink colour).

It is also easy to select "transparent" paper / ink / bright / flash,
as you can in BASIC (ie. PRINT PAPER 8;). You can do it either with
control codes, or by this method: Decide which of paper / ink / bright
/ flash you want to be transparent, add up the numbers from the list
below, and load the result into the system variable MASK_T. For INK
transparent: 07 For PAPER transparent: 38 For BRIGHT transparent: 40
For FLASH transparent: 80

For instance, the single instruction LD (MASK_T),3F (FD36563F) will
assign both paper and ink to be transparent.

Well, that's all from me for this month. Next month I'll continue the
series by talking about the process of POKEing and manipulating the
screen directly, without involving the use of PRINTing at all. See you then.
