.........1.........2.........3.........4.........5.........6.........7.........8

LIGHT SCREEN DESIGNER
ZX Computing, August/September 1984
Part 02 of 13

Toni Baker adds more to our great Spectrum graphics package


Hello again. This is part two of a very, very long program called
Light Screen Designer. When it's all finished, what you'll end up with
is a grand artwork program which will enable you to draw any kind of
picture on the screen with ease. At present the program is unusable as
such, and will continue to be completely unusable until you've got
parts one, two and three. However, in the meantime, I'd like to build
it up a little more.

Firstly, there are two mistakes in the last issue. In FIGURE ONE
(keyboard overlay diagram) the key marked "USR" should be marked
"TRIANGLE", the key marked "HIDE" should be marked "USR", and to the
right of the key incorrectly marked "HIDE" there should be another key
(yes, there are ten keys on each row) marked "HIDE". There is also a
bug at the end of part one, which was entirely my fault. We require
the A register to be preserved whilst the lower part of the screen is
cleared, since it contains the character input by the user. The
following code, overwriting the very last instruction, will cure this
bug. [Except that there was a printing error in the "cure". JimG]

-----------------------------------------------------------------------------
DD0D F5                 PUSH AF
DD0E CD6E0D             CALL CLS_LOWER
DD11 F1                 POP  AF
DD12 C9                 RET
-----------------------------------------------------------------------------

Sorry about that. I guess I am only human after all. In this article
what we are going to do is come up with some machine code which draws
a cursor at any pixel position on the screen. That's all - nothing too
exciting. Actually you may even find this article interesting even
without part one because it's more or less self contained.

You can have three different types of cursor in this program: an
invisible one (so that you can see the whole screen without being
distracted), a single dot (ie. just one pixel), or a crosswires symbol
- this is a cross formed by a horizontal and a vertical line, each
nine pixels in total length, intersecting at the cursor position. The
choice of which cursor to draw is determined by one of our very own
system variables. It's called JFLAGS. [Referred to as J_FLAGS in some
later parts, but I've used JFLAGS throughout. JimG]

JFLAGS is a two byte program variable which lives at address DB40.
(Note. all of the program variables lie between DB00 and DB41). Each
of its sixteen bits serves a different purpose. Bit fifteen tells us
whether or not the cursor is hidden (0 if it is visible, 1 if it is
hidden), and bit fourteen tells us which type of cursor to use (0 for
crosswires, 1 for single dot). To make life easier for us in accessing
these variables, the register IX is used and is assumed to contain the
value DB40, thus IX points directly to JFLAGS, and we can use (IX+0)
to refer to the low part, or (IX+1) to refer to the high part.

Before we actually embark on any kind of programming spree, you may be
interested in the way that the cursor position is actually stored. It
has a rather special format which requires four separate registers: B,
C, H and L.

B contains the row number of the cursor position. If it is at the very
top of the screen then the row number is zero. One pixel down and the
row number is one, and so on. The very bottom of the screen has pixel
number BF (hex). C contains the column number of the cursor position;
the very left-hand edge of the screen has column number zero, and the
very right of the screen has column number FF.

Finally, the HL register pair contains the address of the byte within
the display file which actually contains the pixel at the cursor
position. Any byte in the display file, of course, contains not one
but eight pixels, and so HL on its own will not determine the exact
cursor position (although BC will).


Left/Right Subroutines

Let's cover the first two subroutines now shall we? They are called
LEFT_PIX and RIGHT_PIX. What they do is this: assuming that HL and BC
are properly defined as described above, then they will alter HL and
BC to point to the pixel immediately to the left or right of the one
indicated. Normally the carry will be reset by this process, but if we
try to move off the left or right edge of the screen then HL and BC
will remain unchanged and the carry will be set to signal the error.
How these two subroutines work is very simple, but watch what happens
when we move the pixel position across from one square to another.

-----------------------------------------------------------------------------
;Left Pix
DD13 79       LEFT_PIX  LD   A,C           ;A= column number
DD14 A7                 AND  A
DD15 37                 SCF
DD16 C8                 RET  Z             ;Return with carry set if at left
                                           ;margin
DD17 0D                 DEC  C             ;Decrement column number
DD18 79                 LD   A,C           ;A= new column number
DD19 F6F8               OR   #F8           ;Isolate bits 2, 1 and 0
DD1B 3C                 INC  A
DD1C C0                 RET  NZ            ;Return unless we have moved
                                           ;across two squares
DD1D 2D                 DEC  L             ;Amend print position
DD1E C9                 RET

;Right Pix
DD1F 79       RIGHT_PIX LD   A,C           ;A= column number
DD20 3C                 INC  A
DD21 37                 SCF
DD22 C8                 RET  Z             ;Return with carry set if at right
                                           ;margin
DD23 4F                 LD   C,A           ;Column number incremented
DD24 E607               AND  #07           ;Isolate bits 2, 1 and 0
DD26 C0                 RET  NZ            ;Return unless we have moved
                                           ;across two squares
DD27 2C                 INC  L             ;Amend print position
DD28 C9                 RET
-----------------------------------------------------------------------------

The next two subroutines perform a similar function for the vertical
direction. they are called DOWN_PIX and UP_PIX. Again they alter HL
and BC to point to the pixel immediately below or above the one
specified, and again the carry flag will detect any error. Note,
however, something slightly different. Since the procedure for
altering HL gets very complicated when we try to cross from one square
to another, this is handled entirely separately by another subroutine.

-----------------------------------------------------------------------------
;Up Pix
DD29 78       UP_PIX    LD   A,B           ;B= row number
DD2A A7                 AND  A
DD2B 37                 SCF
DD2C C8                 RET  Z             ;Return with carry set if at top
                                           ;margin
DD2D 05                 DEC  B             ;Decrement row number
DD2E 25                 DEC  H             ;Amend print position
DD2F 78                 LD   A,B           ;A= new row number
DD30 F6F8               OR   #F8           ;Isolate bits 2, 1 and 0
DD32 3C                 INC  A
DD33 280C               JR   Z,PIX_ADDR    ;Jump if we have moved across
                                           ;two squares
DD35 C9                 RET

;Down Pix
DD36 78       DOWN_PIX  LD   A,B           ;A= row number
DD37 3C                 INC  A
DD38 FEB0               CP   #B0
DD3A 37                 SCF
DD3B C8                 RET  Z             ;Return with carry set if at bottom
                                           ;margin
DD3C 47                 LD   B,A           ;Row number incremented
DD3D 24                 INC  H             ;Amend print position
DD3E E607               AND  #07           ;Isolate bits 2, 1 and 0
DD40 C0                 RET  NZ            ;Return unless we have moved
                                           ;across two squares
-----------------------------------------------------------------------------

Notice that both of the above subroutines lead to an address labelled
PIX_ADDR if we try to cross from one square to another; this is a
subroutine which will be listed next. It actually performs more
functions than are strictly necessary here, because the sub-routine
may be called separately from elsewhere in the program. The purpose of
the subroutine is to assign HL as required, given that B and C are
correct. In other words, if B contains the row number, and C contains
the column number, then this sub- routine will work out HL - the
display file byte which contains the specified pixel - or the "print
position".

In order to see how it works, some knowledge of how the screen is
mapped out will be necessary.

Take a look at Figure 1 [PART02.GIF]. It shows the screen divided into
three segments, with each segment divided into eight lines, each line
divided into thirty-two squares, each square divided into eight rows,
and each row divided into eight pixels. If we write each such number
in binary we can adopt the convention that, in general:

ss=	segment number
lll=	line number
qqqqq=	square number
rrr=	row number
ppp=	pixel number

We can work out some binary numbers straight away, for instance:

overall row number (B)     = ss lll rrr
overall column number (C)  = qqqqq ppp

What is not so obvious is the address of such a point in the display
file. It is this:

print position (HL)        = 00 ss rrr lll qqqqq

This arises because the display file is mapped out in such an
unconventional way. Nonetheless, since we now know where all the bits
go in a print position address we should be able to transform values
held in B and C into a value for HL. Notice that three of the bits of
C (ppp) are not used in HL, since HL points to all eight pixels within
the byte. The subroutine below is called PIX_ADDR and performs such a
transformation. Watch out for the three instruction sequence XOR B /
AND mask / XOR B which creates a new byte by taking some bits from A
and some bits from B according to the "mask" byte in the AND instruction.

-----------------------------------------------------------------------------
;Pix Addr
DD41 79       PIX_ADDR  LD   A,C           ;A= column number
DD42 07                 RLCA               ;Move bits 7-3 to positions
DD43 07                 RLCA               ;2, 1, 0, 7 and 6
DD44 07                 RLCA
DD45 A8                 XOR  B
DD46 E6C7               AND  #C7
DD48 A8                 XOR  B             ;Take bits 5, 4 and 3 from B
DD49 07                 RLCA
DD4A 07                 RLCA               ;Move all bits into final position
DD4B 6F                 LD   L,A           ;Assign low part of print position
DD4C 78                 LD   A,B           ;A= row number
DD4D 0F                 RRCA
DD4E 0F                 RRCA               ;Move bits 7 and 6 to positions
DD4F 0F                 RRCA               ;4 and 3
DD50 E618               AND  #18           ;Isolate these bits
DD52 FE18               CP   #18
DD54 C8                 RET  Z             ;Return if both bits are set
DD55 F640               OR   #40           ;Assign bits 7, 6 and 5
DD57 A8                 XOR  B
DD58 E6F8               AND  #F8
DD5A A8                 XOR  B             ;Take bits 2, 1 and 0 from B
DD5B 67                 LD   H,A           ;Assign high part of print position
DD5C C9                 RET
-----------------------------------------------------------------------------

And now an easy subroutine. This one is designed to PLOT OVER the
pixel which is addressed by B, C and HL (as before). Even this,
however, has a little trick involved which you may find useful. The
trick is the very short loop which manipulates the A register. Notice
how is starts off with all bits reset except for one; this "set bit"
is then moved by the loop until it falls in the right position. It is
the XOR (HL) instruction which causes the plot to be OVER.

-----------------------------------------------------------------------------
;Plot Over subroutine
DD5D C5       PLOT_PIX  PUSH BC
DD5E 79                 LD   A,C           ;A= column number
DD5F E607               AND  #07           ;A= pixel number within square
DD61 47                 LD   B,A
DD62 04                 INC  B             ;B= position within row segment
                                           ;of pixel to alter
DD63 3E01               LD   A,#01
DD65 0F       PPX_LOOP  RRCA
DD66 10FD               DJNZ PPX_LOOP      ;Move set bit into position
DD68 AE                 XOR  (HL)
DD69 77                 LD   (HL),A        ;Alter designated bit
DD6A C1                 POP  BC
DD6B C9                 RET
-----------------------------------------------------------------------------

And finally, the last subroutine for today (the one which ties all the
others together). It is called DR_CURSOR, which stands for DRAW
CURSOR. As you can see it makes use of the bits of JFLAGS which we
talked about earlier. Notice especially the code above the label
DRA_RET which effectively manages to CALL a subroutine at any address
stored in DE without disturbing any of the registers. One point to
mention is that although the subroutine relies upon IX being DB40 it
does not in fact assign it.

-----------------------------------------------------------------------------
;Draw Cursor subroutine
DD6C DDCB017E DR_CURSOR BIT  7,(JFLAGS) hi
DD70 C0                 RET  NZ            ;Return if cursor not required
DD71 CD5DDD             CALL PLOT_PIX      ;Plot centre of cursor
DD74 DDCB0176           BIT  6,(JFLAGS) hi
DD78 C0                 RET  NZ            ;Return if crosswires not required
DD79 1113DD             LD   DE,LEFT_PIX
DD7C CD8BDD             CALL DR_ARM        ;Draw left arm of crosswires
DD7F 1E1F               LD   E,RIGHT_PIX lo
DD81 CD8BDD             CALL DR_ARM        ;Draw right arm of crosswires
DD84 1E29               LD   E,DOWN_PIX lo 
DD86 CD8BDD             CALL DR_ARM        ;Draw lower arm of crosswires
DD89 1E36               LD   E,UP_PIX lo
DD8B C5       DR_ARM    PUSH BC
DD8C E5                 PUSH HL
DD8D 3E04               LD   A,#04         ;A determines size of crosswires
DD8F 08       DRA_LOOP  EX   AF,AF'        ;A' stores loop count
DD90 E5                 PUSH HL
DD91 2197DD             LD   HL,DRA_RET
DD94 E3                 EX   (SP),HL       ;Push address DRA_RET on stack
DD95 D5                 PUSH DE
DD96 C9                 RET                ;Call subroutine at address DE
DD97 3807     DRA_RET   JR   C,DRA_EXIT    ;Exit loop if margin reached
DD99 CD5DDD             CALL PLOT_PIX      ;Plot next pixel of arm
DD9C 08                 EX   AF,AF'
DD9D 3D                 DEC  A
DD9E 20EF               JR   NZ,DRA_LOOP   ;Repeat until whole arm drawn
DDA0 E1       DRA_EXIT  POP  HL
DDA1 C1                 POP  BC
DDA2 C9                 RET
-----------------------------------------------------------------------------

We now have everything we need. You can test this program with the
following BASIC and machine code [Run "test02" in LSD.TAP]. You can
write the machine code to any address you like, and the question marks
in the BASIC refer to the machine code label TEST. To test the program
you merely have to input the row and column numbers of the cursor
position you want to see and voila - you're away. If it works, SAVE
all of this month's material (you don't need to save the test routine)
together with part one (which was printed in the last issue) and wait
impatiently for the next instalment ...

Toni Baker


BASIC test program

10 LET test=????                           Points to machine code label TEST
20 INPUT b,c                               B= row number, C= column number
30 POKE test+5,c                           Overwrite LD BC instruction
40 POKE test+6,b                               "       "        "
50 RANDOMIZE USR test                      Draw cursor as required
60 INPUT b,c                               Input new coordinates
70 RANDOMIZE USR test                      Erase previous cursor
80 GO TO 30                                And repeat ad infinitum


Machine Code test program

     DD2140DB TEST      LD   IX,DB40
     010000             LD   BC,0000       ;These values are overwritten
                                           ;by the BASIC
     CD41DD             CALL PIX_ADDR      ;Assign HL accordingly
     CD6CDD             CALL DR_CURSOR     ;Draw cursor at required position
     C9                 RET                ;Return to BASIC
