Programming AmigaOS in C

29. Line Graphics

It is possible to do normal line graphics on the Amiga using the Graphics.library. There are 100s of functions available from setting colour, drawing lines, rectangles, circles, patterns, area filling and so on. To use graphics you need a window open which will have an appropiate bitmap and Raster area to draw on. Most functions require a pointer to a RastPort which is a special structure that is created from a Window or a Layer, referenced by Window->RPort.

30. Simple Line Graphics Commands

The following commands can do some simple line graphics on a specified rastport of a Window or Layer. For example:

void SetAPen( struct RastPort *rp, unsigned long pen ) - Set foreground colour using preset colour pens
void SetBPen( struct RastPort *rp, unsigned long pen ) -
Set background colour using preset colour pens
void Move(struct RastPort rp, long x, long y)
- Move to co-ordinates row x, column y in a Rastport (rp) where 0,0 is the top left of window.
void Draw(struct RastPort rp, long x, long y) - Draws a line from current position to row x, column y.
void DrawEllipse( struct RastPort *rp, long xCenter, long yCenter, long a, long b ) - Draw an Ellipse
ULONG ReadPixel( struct RastPort *rp, long x, long y ) - Read a pixel value
LONG WritePixel( struct RastPort *rp, long x, long y )
- Write a pixel
void PolyDraw( struct RastPort *rp, long count, WORD *polyTable ) - Draw a multi-lined polygon using an array of x,y points.

For example, the following program will draw some random sized coloured squares in a window:

/* Graphics Example 1 */
#include <proto/intuition.h>
#include <proto/gadtools.h>
#include <proto/graphics.h>
#include <proto/layers.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <intuition/intuition.h>
#include <stdlib.h>
/* Random no between 0 and n */
   int randomNum(int n) {
     return (rand() % n);
   }
int main(void) {
    struct Window *myWindow;
    struct RastPort *rp;
    int closewin = FALSE, num;
    struct IntuiMessage *msg;
    ULONG msgClass;
    long startx,starty,width;
   
    myWindow = OpenWindowTags(NULL,
      WA_Left, 20, WA_Top, 20,
      WA_Width, 200, WA_Height, 150,
      WA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_REFRESHWINDOW,
      WA_Flags, WFLG_SIZEGADGET | WFLG_DRAGBAR | WFLG_DEPTHGADGET    | WFLG_CLOSEGADGET | WFLG_ACTIVATE | 
      WFLG_SMART_REFRESH,
      WA_Title, "My Window",
      WA_PubScreenName, "Workbench",
      TAG_DONE);
    /* Get Window's Rastport */
    rp = myWindow->RPort;
   
    /* Draw some random colour boxes */
    for(num=1; num<=10; num++) {
       /* Set foreground colour */
      SetAPen(rp, (ULONG)randomNum(32)+1);
      startx = 10 + randomNum(50);
      starty = 10 + randomNum(50);
      width = 20 + randomNum(50);
      /* Move to start position and draw 4 lines */
      Move(rp, startx, starty);
      Draw(rp, startx + width, starty + 0);
      Draw(rp, startx + width, starty + width);
      Draw(rp, startx + 0, starty + width);
      Draw(rp, startx + 0, starty + 0);
    }
   
    while (closewin == FALSE) {
       Wait(1L << myWindow->UserPort->mp_SigBit);
       msg = GT_GetIMsg(myWindow->UserPort);
       msgClass = msg->Class;
       GT_ReplyIMsg(msg);
       if (msgClass == IDCMP_CLOSEWINDOW) {
          CloseWindow(myWindow);
          closewin = TRUE;
       }
       if (msgClass == IDCMP_REFRESHWINDOW)
          RefreshWindowFrame(myWindow);
    }
  return(0);
}
 


NB: for AmigaOS 4: Prefix OpenWindowTags, CloseWindow and RefreshWindowFrame with 'IIntuition->' Prefix Wait with 'IExec->'. Prefix GT_GetIMsg, GT_ReplyIMsg, with 'IGadTools->'.
For the SetAPen, Move and Draw functions, prefix them with 'IGraphics->'.

I have had to create a new function called randomNum() which uses the ANSI function rand() and to get a random value between 0 and n, I use the modulus (%) operator to get the appropiate value from rand() and a given value. Once a window is opened, I can get the RastPort address using myWindow->RPort which is a pointer to a RastPort structure. This value can then be used for the SetAPen(), Move() and Draw() commands. I have had to use a starting position so that the Window borders are not overwritten and then used random values to create random start position of the top left of the square and a random size to draw a square. The values for Draw are not relative but absolute positions, I used the startx+value and starty+value to make it easier to draw a square. Once the draw loop is done, the program will wait until the Window close gadget is selected and program ends as normal.

21. Drawing with Areas

It is possible to produce filled polygon 'areas' using the AreaMove, AreaDraw and AreaEnd functions which can plot out an outline of a shape and flood fill it with a specified colour. The syntax of the functions are as follows:

struct TmpRas *InitTmpRas( struct TmpRas *tmpRas, PLANEPTR buffer, long size )
void InitArea( struct AreaInfo *areaInfo, APTR vectorBuffer, long maxVectors )
ULONG SetOutlinePen( struct RastPort *rp, unsigned long pen )
LONG AreaMove( struct RastPort *rp, long x, long y )
LONG AreaDraw( struct RastPort *rp, long x, long y )
LONG AreaEnd( struct RastPort *rp )

An area is initialised with the InitTmpRas which sets up a working area for areas and flood fills in the Raster and InitArea which sets up a vector buffer to store area co-ordinates, the pointers are then associated with the Window's Raster structure to be eventually rendered on the screen. To create an area, use AreaMove to set the start postion, then each subsequent position is then drawn with AreaDraw using absolute x,y co-ordinates, you do not need to join the last co-ordinate with the first position, as AreaEnd will do this for you. Notice that I have used SetAPen and SetOutlinePen to set the colour of the area and the lines around the shapes. The InitTmpRas requires a pointer to a TmpRas structure, a pointer to a buffer in Chip memory (which is allocated using AllocMem) for the area of the window (width height x 8) and the size of the buffer. AreaInfo requires a pointer to a AreaInfo structure, a buffer to store x,y vectors and a max number of vectors (buffer * 2 /5) which uses Word values rather than UBYTE as it has to be word aligned. The structures are then assigned to the Rastport e.g. rp->AreaInfo and rp->TmpRas before using the areas.

An example below, draws a simple corridor using filled areas:

/* Graphics area example */
#include <proto/intuition.h> #include <proto/gadtools.h> #include <proto/graphics.h> #include <proto/exec.h> #include <proto/dos.h> #include <intuition/intuition.h> #include <stdlib.h> #include <exec/memory.h>
int main(void) {
     struct Window *myWindow;
     struct RastPort *rp;
     int closewin = FALSE ,i;
     struct IntuiMessage *msg;
     ULONG msgClass;
     struct AreaInfo ainfo = {0};
     WORD areabuf[200];
     struct TmpRas Tmp;
     APTR tmpbuf;
   
     /* Clear area buffer */ 
     for (i=0; i<200; i++)
        areabuf[i] = 0;
   
     myWindow = OpenWindowTags(NULL,
        WA_Left, 20, WA_Top, 20,
        WA_Width, 200, WA_Height, 150,
        WA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_REFRESHWINDOW,
        WA_Flags, WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_CLOSEGADGET    | WFLG_ACTIVATE | 
        WFLG_SMART_REFRESH,
        WA_Title, "My Window",
        WA_PubScreenName, "Workbench",
        TAG_DONE);
     /* Get Window's Rastport */
     rp = myWindow->RPort;
     /* Set AreaInfo pointer in RastPort */
     rp->AreaInfo = &ainfo;
     /* Allocate temp area for Raster to work in */ 
     if (!(tmpbuf = (APTR)AllocMem(200 * 150 * 8, MEMF_CHIP | MEMF_CLEAR)))
          return(5); /* return with warning if unable to alloc memory */
     InitTmpRas(&Tmp, tmpbuf, 200 * 150 * 8);
     rp->TmpRas = &Tmp;
   
     /* Set foreground colour */
     InitArea(&ainfo, areabuf, 200 * 2 / 5); 
     SetAPen(rp, 3);
     SetOutlinePen(rp, 3);

     /* Draw a corrider */
     AreaMove(rp, 90, 12);
     AreaDraw(rp, 90, 40);
     AreaDraw(rp, 30, 135);
     AreaDraw(rp, 30, 75);
     AreaEnd(rp);
   
     SetAPen(rp, 6);
     SetOutlinePen(rp, 6);
     AreaMove(rp, 4,  75);
     AreaDraw(rp, 30, 75);
     AreaDraw(rp, 30, 135);
     AreaDraw(rp, 4,  135);
     AreaEnd(rp);
   
     SetAPen(rp, 3);
     SetOutlinePen(rp, 3);
     AreaMove(rp, 110, 12);
     AreaDraw(rp, 170, 75);
     AreaDraw(rp, 170, 135);
     AreaDraw(rp, 110, 40);
     AreaEnd(rp);
   
     SetAPen(rp, 6);
     SetOutlinePen(rp, 6);
     AreaMove(rp, 170, 75);
     AreaDraw(rp, 195, 75);
     AreaDraw(rp, 195, 135);
     AreaDraw(rp, 170, 135);
     AreaEnd(rp);
   
     /* Finish off with some lines */
     SetAPen(rp, 1);
     Move(rp, 90, 40);
     Draw(rp, 110, 40);
     Move(rp, 30, 135);
     Draw(rp, 170, 135);
   
     while (closewin == FALSE) {
        Wait(1L << myWindow->UserPort->mp_SigBit);
        msg = GT_GetIMsg(myWindow->UserPort);
        msgClass = msg->Class;
        GT_ReplyIMsg(msg);
        if (msgClass == IDCMP_CLOSEWINDOW) {
           CloseWindow(myWindow);
           closewin = TRUE;
        }
     }
     /* Clean up */
     if (tmpbuf) FreeMem(tmpbuf, 200*150*8);
     return(0);
 }


NB: for AmigaOS 4: Prefix OpenWindowTags, CloseWindow and RefreshWindowFrame with 'IIntuition->' Prefix Wait with 'IExec->'. Prefix GT_GetIMsg, GT_ReplyIMsg, with 'IGadTools->'.For the IniTmpRas, InitArea, SetAPen, Move, Draw, SetOutlinePen, AreaMove, AreaDraw, AreaEnd functions, prefix them with 'IGraphics->'.
For AmigaOS 4, AllocMem and FreeMem are obsolete, so replace them with new functions AllocVecTags and FreeVec. Use the new MEMF_SHARED memory type. e.g. tmpbuf = (APTR)IExec->AllocVecTags(200*150*8, AVT_Type, MEMF_SHARED, TAG_DONE) and IExec->FreeVec(tmpbuf).

Next Page