The science of painting begins with the point, then comes the fine, the plane comes third, and the fourth the body in its vesture of planes. This is as far as the representation of objects goes.
Leonardo da Vinci, Trattato delta pittura
This is a book about the BBC computer, and how to use graphics which fit that machine.
This is not a book about graphics, which then uses the BBC computer merely as an example of how graphics can be implemented. What is it, then, that makes the BBC computer so different when we come to discuss the artistic uses of graphics? Not only is it the way in which its graphics are implemented; but also, more importantly, it is the way in which the programming language (BBC BASIC) differs from all other versions of BASIC on other microcomputers.
The single most important feature of BBC BASIC, and the feature which has to influence any implementation of any graphics system, is the ability to use procedures and functions with multiple parameters. This ability produces a highly effective, yet very compact system of great power and flexibility.
In this chapter, therefore, I will first discuss just why BASIC on the BBC computer is so different, then why and how this affects the way in which we produce a graphics system.
Suppose we wish to calculate the square root of the number 3, and we wish to store the value of the square root in a variable called SQUAREROOT. We can enter a fine in a program such as
1000 SQUAREROOT = SQR(3)
and the value of the square root is then stored in the desired variable.
If we wished to find the square roots of all the values from 1 to 10, we would use a loop from 1 to 10 and have a line
1000 PRINT SQR(I)
where I was the loop counter (ie FOR I = 1 TO 10). At each time the SQR function was encountered, the variable I would be replaced by the value stored there, and the square root calculated. The value stored in I would not be affected. In jargon, I was called by value and not by name. If a variable is called by name then it is possible to alter the value stored at that name: to call by value means that the function only knows about the value stored, and not the name.
The function SQR has only one parameter (ie there can only be one input value), so suppose that we need to define a function which will give the square root of the product of two values. To work out the square root of 2 multiplied by 3 we can simply enter
1000 PRINT SQR(2*3)
But suppose that we wanted to appear rather more sophisticated (note: I say only appear). We define a function of our own to produce the square root of the product -- the function we call FNsqr(X,Y).
Function FNsqr has two parameters (X and Y) but, before we discuss the status of the symbols X and Y, the function definition:
1000 DEF FNsqr (X,Y)
1010 X = X*Y
1020 = SQR(X)
1030 REM end of FNsqr
in which we see that (1000) the function is defined as having two parameters, X and Y. In fine 1010 the two values are multiplied together, and the result stored in the variable the function calls X (this variable has no relationship to any other X outside the function definition). In fine 1020 there is an assignment statement (ie there is an = ) with no variable to the left of the assignment: the system assumes, therefore, that the assignment is to the function.
If the function is then used for the variables I and J by
100 PRINT FNsqr(I,J), I, J
then (when this line is activated) the value stored at I is substituted into the temporary variable X in the function definition, and the value at J is stored at Y. The variables X and Y take values, and have an existence, which is "local" to that function. The values printed out for I (in particular) and J are not affected by the action of the function (ie I does not become equal to I* J).
If lines in the function definition are altered, eg:
1010 Z = X*Y
1020 = SQR(Z)
to then have a line
100 Z= 0 : PRINT FNsqr(I,J), Z
shows that the value Z is modified in the function. The value stored at Z is modified because it is not one of the parameters, and has not been defined as being local to the function: the scope of operation of Z is global, compared with the function. Finally add one line,
1005 LOCAL Z
and then activate line 100. In this case the global value of Z is unaltered, as the Z in the function definition has been explicitly defined as being local to that function.
A key difference between functions and procedures is that a function always produces a value (or, "delivers a result"). In many BASICs another key difference is that functions take parameters.
A few BASICs now allow multiple parameters for functions (and some even allow function definitions to extend over more than one statement) but the use of parameters in routines is rare. On mainframes and minicomputers (eg Hewlett-Packard) there are various very sophisticated BASIC systems, but BBC BASIC stands out from most other microcomputer BASICs in terms of its sophistication.
Nearly all of the comments we would wish to make about parameters for procedures have been already made about functions, but the distinctions between the various types of variable in procedures are far more important than they are for functions. After all, functions do not tend to be used as much as procedures, functions deliver a result, whereas procedures do something.
In BBC BASIC (and from now on this will be shortened to BB) the command to plot at a certain point is
PLOT 69, X, Y
where the coordinates are X and Y (see page 320 of the User Guide, or alternatively (UG page 386)
VDU 25, 69, X; Y;
VDU driver commands will be discussed in later chapters. To define a procedure which will plot a diagonal fine from coordinates s,s to f,f without using parameters is obviously possible, but to use parameters makes it so much simpler. Watch:
1000 DEF PROCdiag(s,f,inc)
1010 LOCAL i
1020 FOR i= s TO f STEP inc
1030 PLOT 69, i, i
1040 NEXT i
1050 ENDPROC : REM diag
and to draw an instant diagonal line from 0,0 to 1000,1000 we enter (in immediate mode, so no fine numbers)
PROCdiag (0, 1000, 10)
where the points are plotted in gaps of 10 units.
PROCdiag has three parameters s, f, and inc, the scope of which is merely that of the procedure; there is another variable i (used as the loop counter) whose scope is also defined as being local to the procedure; and there are no global variables. If there is a variable i in the main program (or s, f, or inc) the values of these variables are unaffected -- the procedure does not even recognise their existence.
If there were variables s and f in the main program (with the correct interpretation), it might be possible to define a different procedure, which did more than just draw a diagonal line of blobs. It might make the finish of the fine (f) the start of a new line (s):
1000 DEF PROCnewdiag(inc)
1010 LOCAL i
1020 FOR i = s TO f
1030 PLOT 69, i, i
1040 NEXT i
1050 s = f
1060 ENDPROC : REM newdiag
For this procedure both s and f are global to the procedure: they have to be so, because we need to modify their values. With PROCdiag we are able to use variables with names other than s or f in the main program. As long as the names are in the correct position in the list of parameters, the name does not matter.
Recursion
Sometimes (not very often) when writing a program, or a routine, there may be a case where what you want to do is fairly simple, but to achieve the result by normal methods seems to be overly tedious in terms of the mental commitment necessary to solve the problem (a classic example is the Kasner snowflake, discussed in the next chapter).
It is not that the solution is impossible to find by normal methods, it is just that the solution is difficult to code simply. One way sometimes used to solve such problems is called 'recursion'; and, though there is a great mystique surrounding the term, it is very simple to use recursion -- though remarkably wasteful.
Examine this function and routine:
1000 DEF FNwalk = RND(55) - 28
2000 DEF PROCrndwalk(x, y ,z)
2010 x = x + FNwalk : y = y + FNwalk
2020 PLOT z,x,y : PROCrndwalk(x,y,z)
2030 ENDPROC : REM rndwalk
where the function is without parameters because it does not depend upon any input value to produce its result.
The procedure has three parameters: two (x and y) are co-ordinates, and the third sets the style of plotting (U G page 319). The input coordinate values are modified by FNwalk up to a limit of plus or minus 27 units (independently); the new coordinates are then used to plot to new coordinates (with the style of plotting set by z); and then the new values are used as parameters for yet another call to the same procedure (ie PROCrnd walk)
The call to PROCrndwa1k within PROCrndwalk is what is termed a recursive call. If you use PROCrndwaIk in immediate mode, eg
MODE 4 : PROCrndwalk (500,500,5)
the screen will show randomly drawn lines, usually called a 'random walk' (similar to Brownian motion). After a short while the plotting will stop with an error message at line 1000: by repeatedly calling itself (and the BASIC system having to remember where it has been) the program runs out of room.
Repeating the above fine (still in Mode 4) for different values of z (eg 5, 6, 21, 22, 69, 70, 85, or 86) is a very good way of investigating the effects of the flexible plotting command. My favourite is 86, plot triangles in the logical inverse colour. Mode 5 is also worth trying at this stage, possibly using GCOL to change the graphics colour (UG pages 163, 262).
Using Modes 0,1, and 2, produces a surprise: the plotting is over much more quickly than for Modes 4 and 5. A glance at the memory map (UG page 500) shows that, as the graphics memory increases, so the memory available for BB is less. As the memory map shows, the BASIC stack reaches from the graphics memory boundary (ie HIMEM) until it reaches the BB program. When it reaches the program, we find there is no room at fine 1000.
Recursion can be fun, but -- unless you are very careful -- your program will crash, particularly with a large program in higher resolution modes. However, as I noted above, PROCrndwalk is a useful way of investigating plotting styles. Another useful exercise is to write PROCrndwalk 'iteratively' , ie by use of a FOR. . .NEXT loop (or possibly a REPEAT . . . UNTIL loop). Also worth trying is to write the (iterative) PROCdiag in a recursive manner.
It is interesting to note that in the definition of PROC (UG page 329) there is an example of recursion -- without any explanation -- so the designers of BB must have thought that recursion was important. The definition of recursion is, of course,
RECURSION : See Recursion
Screen resolution
To draw pictures on the screen we need to know something about the screen. Start by trying out this procedure
1000 DEF PROC_ORIGIN TO_(X,Y)
1010 MOVE 0,0 : DRAW X, Y
1020 ENDPROC : REM ORIGIN_TO_
and then use the procedure by entering (in direct mode, ie without line numbers)
PROC_ORIGIN_ TO_(800,800)
which will draw a fine from the bottom left corner of the screen to somewhere towards the top right corner. Holding the RETURN key down succeeds in moving the fine up the screen: there is no distinction between the drawing of lines on the screen, and the entering in of the characters. To repeat the call of the procedure is to draw a second fine, parallel to the first.
If nothing has happened, you are probably in mode 3, 6, or 7, none of which allow high resolution graphics: in fact, if in mode 7, the space symbol '_' will appear as a hyphen '-'. At this point it is worth turning to UG page 59, which gives the number of characters per line, and numbers of lines, for the various modes. Keeping note of the numbers there: Figure 0.1 Character Resolution
Figure 0.1 Character Resolution
MODE | CHARACTERS | LINES | TOTAL CHARS |
0 | 0 to 79 (80) | 0 to 31 (32) | = 2560 |
1 | 0 to 39 (40) | 0 to 31 (32) | = 1280 |
2 | 0 to 19 (20) | 0 to 31 (32) | = 640 |
3* | 0 to 79 (80) | 0 to 24 (25) | = 2000 |
4 | 0 to 39 (40) | 0 to 31 (32) | = 1280 |
5 | 0 to 19 (20) | 0 to 31 (32) | = 640 |
6* | 0 to 39 (40) | 0 to 24 (25) | = 1000 |
7* | 0 to 39 (40) | 0 to 24 (25) | = 1000 |
Note: The * indicates that this is not a graphics mode.
Figure 0.2 Graphics Resolution
MODE | RESOLUTION | COLOURS | |
0 | 640 x 256 | 2 | = 20K |
1 | 320 x 256 | 4 | = 20K |
2 | 160 x 256 | 16 | = 20K |
4 | 320 x 256 | 2 | = 10K |
5 | 160 x 256 | 4 | = 10K |
The total characters column shows that in mode 0 there can be 2560 characters on the screen at one time. As each character (U G page 170) is 8 elements wide by 8 elements high, then an 80 by 32 characters mode is the same as 640 by 256 elements.
If you now refer to the top of page 161 in the UG, you find that in the graphics modes the screen is divided up into imaginary rectangles: mode 0, we are told, has 640 x 256 squares. The number of squares correspond to what I termed elements, or what in other places are called 'pixels'. The 'higher' the resolution of the graphics (or the greater the number of characters on the screen) the larger the memory needed. Different modes also have different numbers of colours: the greater the number of colours which can be used on the screen at the same time, the greater the memory.
The memory requirements are those given at the bottom of page 160 of the UG, and it is worth noting how the calculation is made. Each group of 8 pixel/elements occupies one byte (one memory location); each bit within the byte can be set or not (two colours); if another byte is associated with that byte, there are now two bits per pixel (four colours); and to have 16 colours requires 4 bits per pixel. Therefore, to calculate the requirement for mode 2:
(160/8) x 256 x 4 20480 or 20K ( = 20480/1024)
which -- if not immediately obvious -- should be studied carefully.
The theoretical dimensions of the plotting screen are (in the same order as we gave the characters and fines) 0 to 1279 across and 0 to 1023 upwards (or downwards). For each mode, therefore, there is a minimum resolution: for mode 5 (with 160 pixels across) each pixel is 1280/160 units wide, and thus the maximum discrimination in the X direction is 8 units. The maximum discrimination in the Y direction is thus 1024/256 = 4 units: each pixel is of size 8 x 4. There are 32 fines of text, so each fine will be 1024/32 = 32 units wide.
Figure 0.3 Pixel Resolution
MODE | PIXEL SIZE | LINE WIDTH |
0 | 2 × 4 | 32 |
1 | 4 × 4 | 32 |
2 | 8 × 4 | 32 |
4 | 4 × 4 | 32 |
5 | 8 × 4 | 32 |
As there are two ways in which the screen may be used, there are two pointers to where we are using the screen. There is a text cursor, which points to where the next character is to be placed (usually flashing); and there is a graphics cursor which gives the start of the next graphics plot.
Normally the two cursors are distinct, but it is possible to use a command which allows text to be entered at the position of the graphics cursor. The VDU command
VDU 5
will mean that only one cursor is active (ie the graphics cursor), and text can be entered at any part of the screen -- without the screen scrolling up a line when on the bottom line.
As the normal discrimination for text in mode 5 is 1280/20= 64 units wide, characters can only be placed in increments of 64 units, whereas characters can be placed in increments of 8 units (see Figure 0.3) by use of the graphics cursor. On page 173 of the UG, an example is given of a rocket rising more smoothly, due to the increased discrimination obtained by using the graphics cursor. To separate the cursors we use VDU 4.
Though the association of text with the graphics cursor is useful, sometimes it is even more useful to make sure that text and graphics never occupy the same place on the screen. What is frequently needed is a 'text space' and a distinct 'graphics space'. Many computers (eg the Apple II) provide this distinction automatically. We need to use text windows and graphics windows (see pages 56-61, and 385-388 of the UG).
The question is: where should the two spaces be situated? Following the example of the Apple, and other computers, I propose that the best place is with the text at the bottom of the screen (often four fines of text), and graphics to fill the rest of the screen. The reason I like this arrangement is that this allows the user to enter, interactively, drawing commands and it allows the user to study the command (it does not disappear) if something goes wrong -- as it often does.
The origin for text is at the top left hand corner and (using mode 4 as an example) extends to 39 across (x axis) and 31 down (y axis). To place a window in the bottom four fines, we need to occupy lines 28, 29, 30, and 31; and to use the full width we need to occupy character positions 0 to 39.
Note that for all modes which use graphics (Figure 0.1) there are 32 fines, so that the only difference for other modes to mode 4 is in the numbers of characters across the screen.
To set the text window we use a VDU command
VDU 28, leftchar, bottomline, rightchar, topline
where the labels are as they say. To set a text window in the manner we have discussed, we enter
VDU 28, 0, 31, 39, 28
to give a window which extends from character 0 to 39, and fine 28 to 30. One ofthe following VDU commands is for mode 5, and one is for mode 0: work out which is which -
VDU 28, 0, 31, 79, 28
VDU 28, 0, 31, 19, 28
No answers supplied.
To set up the graphics window is similar, but different. The command is
VDU 24, leftcoord; bottomcoord; rightcoord; topcoord;
and, whereas in the text command we differentiated between fines and characters, we only have coordinates in the graphics command.
The most important distinction is that between the use of the comma ',' in the VDU 28 command, and the use of the semi-colon ';' in the VDU 24 command. The dimensions of the text window (in any direction) are never greater than 255 (check Figure 0.1 if you are not sure); whereas the dimensions of the coordinates often extend beyond 255. BB, therefore, makes a distinction between numbers of 255 or less (which can be treated as one byte -- 8 bits), and numbers which might be up to 65535 (two bytes of 16 bits). The difference is explained on page 386 of the UG, by reference to the command
VDU 24, 150; 300; 100; 700;
and then
VDU 25, 4, 100; 500;
which is actually equivalent to the command PLOT 4, 100, 500. The VDU 25 command given is (the UG claims) the same as
VDU 25, 4, 100, 0, 244, 1
because 100 = 100 × 1 + 0 × 255 and 500 = 244 × 1 + 1 × 255. The comma sends the preceding number to the system as if it were one byte (and if the number is greater than 255 it sends the value MOD 256). A semi-colon informs the system that the preceding number has to be sent as two bytes -- there is no need for the last comma, but BB always has to have the last semicolon.
The above VDU 25 could be written more explicitly as
VDU 25, 4, X MOD 256, X DIV 256, Y MOD 256, Y DIV 256
and, later -- Chapter 4 -- I will analyse the use of VDU commands in detail; for now, however, all we really need is the important difference 'twixt comma and semi-colon.
Each fine of characters corresponds to 32 units in graphics, so the graphics screen (if it is not to overlap with the text screen) will have to start up 4 x 32 units (ie 128) and then extend from side to side and to the top
VDU 24, 0; 128; 1279; 1023;
will do very nicely. To play with graphics constructively, therefore, we need to start with the two screens and then clear them both:
VDU 28, 0, 31, 39, 28 : CLS
VDU 24, 0; 128; 1279; 1023; : CLG
We are away, apart from one little extra chore.
If we now use PROC_ORIG IN_TO we would find that part of the line was missing: the origin is outside the graphics window . We change the graphics origin to a new point by use of VDU 29 (U G page 388). To set the origin to 640,566 (ie the middle of the graphics window) we
VDU 29, 640; 566;
and now we really are away.