Using GEM from the C Language

A tutorial series by Steve Pedler

 

Issue 28

Jul/Aug 87

Next Article >>

<< Prev Article

 

 

Having acquired an Atari ST, I went through the phases I imagine many new owners experience – starting off by finding out how to get the computer powered up, playing a few commercial games and trying some public domain software and finally, when the immediate euphoria wears off, actually attempting to program the machine yourself. It is at this point that you discover that ST BASIC is not exactly the world's ideal programming environment – not that it isn't a powerful interpretation of the language, but more that the screen editor provided must be one of the slowest and most unfriendly ever written, and that you are denied simple access to one of the features for which the ST is best known – the GEM interface. At that point I decided that I needed a more powerful language than ST BASIC, and with a certain amount of trepidation opted for the one that many others seemed to be using, particularly in the USA, the C programming language. I haven't regretted my choice for one moment. Using C to program the ST and make full use of GEM is so easy and produces such fast programs that I never want to return to ST BASIC again.

It occurred to me that Page 6 readers who either own a C compiler and have not yet started to use GEM, or those of you thinking about buying a compiler might be interested to see how it's done and so hopefully avoid some of the traps I fell into and so this first article came into being. I hope that you will learn with me as we examine the ways in which C can be used to program the graphics of the ST. I hasten to add that I am very much a novice in both C and GEM, and I hope more experienced programmers will forgive the need to state facts and principles which to them must seem obvious. Many ST owners, however, are still at the novice stage as regards C programming and it is for them, primarily, that this series is written.

THE DEMO PROGRAM

I have always felt that articles of this nature are likely to prove more interesting if a useful program is the eventual result. The aim of what I hope will be a series of articles is to produce a comprehensive demonstration of the graphics capabilities of the ST which you can use for your own pleasure or to show your family, colleagues and friends. To this end, I would welcome suggestions from readers concerning aspects you would like to see covered in these articles, or suggestions for graphic demonstrations. Often the hardest part is not the actual coding but thinking of something which will adequately demonstrate a particular point.

I should point out that this is intended to be a demonstration of GEM rather than how to program in C and some rudimentary knowledge of C will be helpful, however even those who do not know the language should find the discussion of the various GEM functions useful, as the principle of using them from ST BASIC is similar.

The demonstrations will take the form of a single program which carries out a series of demos in turn. This will save you having to type in the necessary initialisation code each time. In this first instalment we will set up the system for the demos and include a simple demonstration of animation using colour rotation. This powerful technique is used in numerous graphic demos and once mastered will allow you to write spectacular demos of your own.

  Demo Program source code 

TYPING IN AND RUNNING THE DEMO

This first part of the program does not use any of the more advanced features of C such as floating point numbers, structures or unions, so it should be compatible with all the currently available C compilers. One point to note is the #define in the first few lines which defines 'WORD' as a short integer variable. This is necessary because GEM functions expect to be passed 16-bit integers and the compiler used to write this program, Metacomco's Lattice C, uses a 32-bit integer as standard. In Lattice, a short is 16 bits long as it almost certainly will be in any other compiler, regardless of the integer size. However, check your compiler, if a short is not 16 bits long you only have to change the #define, not every occurrence of the short keyword in the program. To run the completed program, double click on its icon and click on the button of the alert box when it appears. You must be in low resolution for this program, if you are not, the program will tell you so. You can increase and decrease the rate of spin of the disk by holding down the left mouse button; holding down the right button will rerun the demo, while holding both buttons will return you to the desktop.

PROGRAM INITIALISATION.

The listing starts with a couple of #include files, and two #defines, one of which defines SOLID as the number one. This is a constant used in selecting an interior fill pattern, and it is included simply because SOLID means a lot more to a person reading the listing than trying to figure out what significance a 1 might have when passed to a function. This is followed by a series of declarations; note particularly that the function demo1(), our first demonstration, is declared as returning a short variable, rather than the default int. The reason for this will become apparent.

The main() function comes next, and the first thing this does is call the init_gem() function. This carries the necessary initialisation code both for GEM and our demonstration. It starts by calling three GEM functions. The first, appl_init(), initialises GEM for a new application. The second, graf_handle(), returns a very important variable, the name or device handle of the current workstation. It also returns four other items of information about the workstation, but we don't need these so we can just use a dummy variable to put them in. Before we can open a new workstation to draw in, we have to be able to pass certain parameters to GEM. These are passed in intin[], and information received from GEM about the new workstation will be found in intout[]. As you can see, elements 1 – 9 of intin[] all contain the value 1. Table 1 gives the meaning of these values for GEM. The function v_opnvwk() will open our new workstation. It must be given the address of work_handle so it can be modified for the new workstation.

Table 1. Values passed to GEM in v_opnvwk().

intin element

value

attribute affected

result

1

1

polyline type

solid line

2

1

polyline colour

black

3

1

polymarker type

dot

4

1

polymarker colour

black

5

1

text font

system font

6

1

text colour

black

7

1

fill style

solid

8

1

fill style index

no effect as solid

fill chosen above

9

1

fill colour

black

Element 10 of intin[] is set to 2. This is an instruction to GEM to use raster screen coordinates, i.e. conventional computer graphic coordinates with point 0, 0 in the top left corner and a resolution of whatever the computer concerned can offer. The alternative is Normalized Device Coordinates (NDC), which is based on a standardised screen of 32767 pixels vertically and horizontally. This offers the possibility of writing graphic applications which are independent of a particular computer's hardware.

Much of the information returned in intout[] will not be of interest to us if we are writing programs specifically for the ST. However, intout[] elements 0 and 1 will return the maximum horizontal and vertical resolution respectively, useful if you are writing software that will run in more than one resolution.

The rest of init_gem() contains code specific for our application. The demonstration will use colour rotation so the first thing we do is to move the colour palette into an area of memory where we can manipulate it. The Setcolor() function is not a GEM function but a call to the ST's operating system (it is an XBIOS call). The general format of Setcolor() is as follows:

Setcolor(colour number, colour value)

where colour number ranges from 0 to 15 and colour value is a 16 bit number representing the RGB value of the colour. But Setcolor() does more than this. It also returns the old colour value to the user, and if the colour value supplied to it is negative, it is ignored. In other words, Setcolor() as used here has the effect of changing none of the colours but copies the present 16-colour palette into the array old_pal[]. This array will be used to reset the palette to its original value on leaving the program. The next two lines simply copy old_pal[] into new_pal[] which will be used for the actual colour rotation.

The next statement is also an XBIOS call. Getrez() does what its name implies – it returns the current screen resolution. This is important to us, since this program will only work in low resolution. The values returned are as follows:

0 low resolution
1 medium resolution
2 high resolution

Having determined the resolution, the program checks to see if the user is in fact in the required resolution. If not, an alert box is put on the screen to inform the user and the variable 'finished' is set to 0. Alert boxes are printed using the GEM function form–alert, and are simplicity itself to use. The general format is as follows:
 
button= form_alert(default button, alert string)

where button is the number of the button the user clicked on and which can range from 1 to 3. The default button is the button which is selected by pressing the Return key as well as clicking with the mouse; it too can range from 1 to 3, or be zero in which case there is no default. Examples of the alert string can be seen as res_alert and hello_box. The general format is:

(icon)(text with lines separated by the : character)( button 1 : button 2 : button 3)

where icon is the icon displayed on the left side of the box. Possible values for this are:

1 exclamation mark
2 question mark
3 stop sign

You can have a minimum of one and a maximum of three buttons. Since we only have one button in each of these boxes we don't need to know which one was clicked on. If the screen resolution is anything other than low, the string res_alert appears in the box; otherwise hello_box is used. On return from init_gem(), main() checks the value of the variable 'finished'. If this is anything other than zero, the statements in the while loop are executed; if it is zero, these statements are not executed and the function finish_gem() is called. Assuming the loop is executed, the demo is called as demo1(), which will return a short in the variable 'c'. If c is returned as greater than 2 the program will break out of the loop, set finish to zero so that the loop is not executed again, and call finish_gem() to exit the application.

LET'S START THE DEMO!

Now for the demo itself. The first thing to do is reset the colour palette so that we can manipulate it. This is done with another XBIOS call, Setpalette(), which is provided with the address of the new palette as its argument. We then hide the mouse cursor since we don't want it on the screen, with the GEM function v_hide_c(). Function v_clrwk() then clears the screen using the current background colour (always found in colour 0 of the palette). To be absolutely sure, although we have already done it during the open workstation routine, the interior fill style is set to solid colour with the call to vsf_interior(). The format of this call is as follows:

vsf_interior(device handle, style)

where style can have one of the following values:

style

result

0 hollow – i.e. outline only, no fill
1 solid colour
2 pattern
3 hatch
4 user defined pattern

If pattern or hatch styles are chosen you can choose from a variety of preset patterns using another function, vsf_style(). We will look in more detail at this at another time. You can even define your own fill pattern using function vsf_udpat().

The aim of the demo is to draw a disk composed of segments of different colours so that by rotating the colour palette we give the illusion of a spinning disk. The function which draws the circle segments is v_pieslice, which uses the following format:

v_pieslice(device handle, circle centre x-coordinate, circle centre y-coordinate, circle radius, start angle, stop angle)

Most of this is self-explanatory, except perhaps for the question of start and stop angles, which determine the position of the segment in relation to the centre of the circle. These are expressed in tenths of degrees, and can therefore range from 0 to 3600. An angle of 0 is due east, while 900 will be due north. By incrementing these variables by a set amount each pass through the loop, we draw a filled circle composed of a number of segments. Before we can do this however, we have to set the colour to fill the segment with, and here we run into a slight problem. The 16 colours used in the low resolution colour palette are numbered 0 to 15, with colour 0 being the background colour. Unfortunately, the GEM functions use colour indices, also numbered 0 to 15, but which do not correspond with their counterparts in the palette. In other words (for example) colour index 1 does not use colour 1 in the palette, but colour 15. If we draw with colour indices 1 to 6 therefore, but rotate colours 1 to 6 in the palette, we shall not be rotating the correct colours. To avoid this, the array col_index[] was set up which contains the colour indices in the order of the colours they correspond to in the palette.

Having drawn the disk, the next task is to print the title of the demo using the function v_gtext(). I don't want to say anything more about this at this time, except that the height of the text (in pixels) is selected by using the vst_height call. More about this next time. Finally the colours are rotated by rotating the contents of new_pal[] and calling Setpalette() each time the array is changed (changing the array without calling Setpalette() will have no effect).

The program incorporates a delay loop to slow things down to a reasonable level. In order that you can see the effect of altering the delay, the mouse buttons are checked using graf_mkstate(), which returns the current mouse attributes. This has the following format:

graf_mkstate(mouse x-coordinate, mouse y-coordinate, mouse button, shift/control/alternate key state)

We are only interested in the mouse buttons here so the other values go into our dummy variable. The mouse button returns a 1 for the left button, a 2 for the right button and a 3 for both buttons at once. If the left button is pressed and held down, the delay is increased by the value in step until a limit is reached when it starts to decrease again. Holding down the right button or both buttons at once will exit from the demo loop and return the button value to main(). If this is 2 (right button only) the demo is rerun; if 3 (both buttons together) the program will return to the desktop, terminating by calling finish_gem().

The finish_gem function is very straightforward. We first bring back the cursor using v_show_c() which has this format:

v_show_c(device handle, reset flag)

where the reset flag is either zero or non-zero. If zero, the cursor is redisplayed regardless of how many v_hide_c() calls were made; if non-zero, the number of v_show_c() calls must match that of the number of v_hide_c() calls for the cursor to be redisplayed. The old colour palette is restored with Setpalette(), and the workstation closed with v_clsvwk(). The application is terminated with a call to appl_exit(), and the whole program returned to the desktop by coming to the end of the main() function.

ALL FINISHED!

And that is it! You might say that it is a lot of effort for a very simple demonstration and I would agree, but don't forget that GEM gives us enormous graphic power that is generally very easy to use. Regrettably the price we pay is an increase in the amount of code needed to set things up. Although I have deliberately used C to demonstrate some simple GEM functions in this article, all these functions are also available to BASIC programmers, who might like to try implementing the demo in BASIC. Next time we will look at some more GEM functions and add another demo or two to the program.

top