Pascal Drawing, Sound, and Input
From SwinBrain
In this HowTo we will step through the use of WinGraph to paint to a graphics window, WinMouse to receive mouse input, WinCRT to receive keyboard input, and OpenAL for playing sounds.
Download the required WinGraph, WinMouse, WinCRT, and OpenAL units here.
Contents |
WinGraph
WinGraph is a unit that you can include in your Pascal applications that enable them to easily create a window that can be drawn in. You can use it to draw primitives such as rectangles, to draw text, and to draw bitmaps.
To use WinGraph paste the wingraph.ppu and wingraph.o files in the same directory as your .pas file and add WinGraph to your uses statement:
uses WinGraph;Opening a Graphics Window
To open a Graphics Window you need to use the procedure InitGraph(). InitGraph() takes three parameters:
- Colour Depth. Important ones:
-
SVGA= 256 colours -
Detect= Retrieves the current display settings -
NoPalette- no palette, all colors in the system are available
-
- Window Size. Important ones:
m640x480- 640x480 pixels-
m800x600- 800x600 pixels -
m1024x768- 1024x768 pixels -
m1280x1024- 1280x1024 pixels -
mMaximized- Maximised window -
mFullScr- Full screen maximised window -
mCustom- Custom size (useSetWindowSize()to change the window size)
- Window Caption Text
After opening a window you should check for errors with the GraphResult() function. The GraphErrorMsg() method will take the integer returned and turn it into a readable error string.
Before your program closes you need to close the Graphics Window. Call the CloseGraph() procedure to close the window.
The sample below will show a typical program structure for opening and closing a graphics window:
program GraphWindow; uses WinGraph; procedure Initialise(); var colourDepth, resolution, errorCode : SmallInt; begin //Open the Graphics Window colourDepth := SVGA; resolution := m800x600; InitGraph(colourDepth, resolution, 'Graph Window Example'); //Check for errors errorCode := GraphResult(); if errorCode <> grOK then begin WriteLn('Error when initialising the graphics window! Code: ', errorCode, '. Message: ', GraphErrorMsg(errorCode)); ReadLn(); Halt(1); end; end; procedure Finalise(); begin CloseGraph(); end; procedure Main(); begin Initialise(); //...Do stuff here... Finalise(); end; begin Main(); end.
Drawing Bitmaps
There are three steps in drawing bitmaps that you must perform:
- Load the bitmap into memory
- Draw the bitmap to the screen
- Free the bitmap from memory (to prevent memory leaks)
Loading an image is simple; you call the LoadImage() function that takes the filename as its only parameter and it will return an Integer that is the handle to the bitmap. Normally you load the image in the some Initialise procedure during the program startup.
You can then paint the bitmap to the screen with DrawImage() procedure. The procedure takes four parameters:
- X position (Integer): The x-coordinate of the position of where the top left hand corner of the bitmap will be painted.
- Y position (Integer): The y-coordinate of the position of where the top left hand corner of the bitmap will be painted.
- The bitmap handle (Integer): The handle to the bitmap that you got from
LoadImage(). - How the bitmap will be drawn:
-
CopyPut- copies the bitmap directly to the screen -
XorPut- combines the colors of the bitmap and the screen by using the logical XOR operator -
OrPut- combines the colors of the bitmap and the screen by using the logical OR operator -
AndPut- combines the colors of the bitmap and the screen by using the logical AND operator -
NotPut- copies the inverted bitmap to the screen -
NotOrPut- combines the colors of the bitmap and the screen by using the logical OR operator and then inverts the resultant color -
InvBitOrPut- combines the colors of the inverted bitmap with the colors of the screen by using the logical OR operator -
InvScrAndPut- combines the inverted colors of the screen with the colors of the bitmap by using the logical AND operator
-
If you want to paint an image with transparency, you should call DrawTransparentImage(). It takes four parameters:
- X position (Integer): The x-coordinate of the position of where the top left hand corner of the bitmap will be painted.
- Y position (Integer): The y-coordinate of the position of where the top left hand corner of the bitmap will be painted.
- The bitmap handle (Integer): The handle to the bitmap that you got from
LoadImage(). - The colour in the image that will be made transparent (Integer).
When your program is shutting down, you must delete the loaded bitmap from memory. To do this you must call FreeImage() on all Integer handles you got from LoadImage().
Below is some sample code that illustrates the painting of bitmaps (download the bitmaps it uses here):
program GraphWindow; uses WinGraph; const //Green - Hex: 00FF00 TRANSPARENTCOLOUR = 65280; var images : Array [0..1] of Integer; procedure Initialise(); var colourDepth, resolution, errorCode : SmallInt; begin //Open the Graphics Window colourDepth := SVGA; resolution := m800x600; InitGraph(colourDepth, resolution, 'Graph Window Example'); //Check for errors errorCode := GraphResult(); if errorCode <> grOK then begin WriteLn('Error when initialising the graphics window! Code: ', errorCode, '. Message: ', GraphErrorMsg(errorCode)); ReadLn(); Halt(1); end; //Load the images images[0] := LoadImage('bitmap1.bmp'); images[1] := LoadImage('bitmap2.bmp'); end; procedure Finalise(); begin //Free the images FreeImage(images[0]); FreeImage(images[1]); CloseGraph(); end; procedure Main(); begin Initialise(); //Draw the tranparent image, using green for the transparent colour DrawTransparentImage(0, 0, images[0], TRANSPARENTCOLOUR); //Draw the same image directly below it, without transparency DrawImage(0, 32, images[0], CopyPut); //Draw the other image DrawImage(32, 0, images[1], CopyPut); //..Do stuff here (currently the program will close instantly).. Finalise(); end; begin Main(); end.
Drawing Primitives
To draw a primitive such as a rectangle, you first need to set the foreground colour you want to draw the primitive with.
To set the foreground colour use the SetColor() procedure which takes a longint typed variable as a parameter. A background colour can be set with SetBkColor() which also takes a longint typed variable as a parameter. The background colour is used when you clear the screen's contents (with ClearViewPort()).
longint for a certain colour by using the GetRGBColor() function or by using one of the predefined constants that are documented on the WinGraph Routines Overview page under "Alphabetical Color Names (longword)".To actually draw the rectangle, call the Rectangle() procedure, which takes four parameters:
- The top-left corner's x-coordinate (integer)
- The top-left corner's y-coordinate (integer)
- The bottom-right corner's x-coordinate (integer)
- The bottom-right corner's y-coordinate (integer)
The rectangle's colour will be the foreground colour you set earlier. However, to draw a rectangle that is filled with color (not just a border), you need to call the FillRect() procedure which has the same arguments as Rectangle(). The foreground colour is used for a border around the rectangle. To change the colour that the filled rectangle is filled with, use the SetFillStyle() procedure. That procedure also allows you to change the style of fill that you are drawing with (eg vertical hatch etc). The parameters for the SetFillStyle() procedure are:
- Fill Style
-
EmptyFill- background color hatch -
SolidFill- solid hatch -
LineFill- horizontal hatch -
ColFill- vertical hatch -
HatchFill- horizontal and vertical cross-hatch -
SlashFill- 45-degree upward, left-to-right hatch -
BkSlashFill- 45-degree downward, left-to-right hatch -
XHatchFill- 45-degree cross-hatch -
UserFill- user-defined hatch (set withSetFillPattern()) -
NoFill- no hatch
-
- Fill Colour (longint)
The SetLineStyle() procedure can be used to change the thickness and style of the borders drawn. Check out its documentation for more information.
NormWidth, the line style changes back to SolidLn when drawing using Rectangle().Below is a sample that demonstrates primitive painting:
program GraphWindow; uses WinGraph; procedure Initialise(); var colourDepth, resolution, errorCode : SmallInt; begin //Open the Graphics Window colourDepth := SVGA; resolution := m800x600; InitGraph(colourDepth, resolution, 'Graph Window Example'); //Check for errors errorCode := GraphResult(); if errorCode <> grOK then begin WriteLn('Error when initialising the graphics window! Code: ', errorCode, '. Message: ', GraphErrorMsg(errorCode)); ReadLn(); Halt(1); end; end; procedure Finalise(); begin CloseGraph(); end; procedure Main(); begin Initialise(); //Set the border colours to red SetColor(red); //Set the fill to solid green SetFillStyle(SolidFill, green); //Set the line style to solid 3px width SetLineStyle(SolidLn, 0, TripleWidth); //Draw a filled rectangle FillRect(0,0,400,400); //Set the border colour to blue SetColor(blue); //Set the line style to dashed-dot with 1px width SetLineStyle(DashDotLn, 0, NormWidth); //Draw a rectangle Rectangle(100,100,500,500); //..Do stuff here (currently the program will close instantly).. Finalise(); end; begin Main(); end.
There are lots more primitives you can draw and most operate on a similar basis to Rectangle(); check them out under the headings "Color management routines", "Drawing primitives routines", and "Filled drawings routines" in the WinGraph documentation.
Drawing Text
To draw text to the graphics window you want to use the OutTextXY() procedure. It takes three parameters:
- X-coordinate of the text (integer)
- Y-coordinate of the text (integer)
- The string to output (string)
You probably want to change the font. WinGraph has some preset fonts that you can use:
-
CourierNewFont -
SystemFont -
MSSansSerifFont -
TimesNewRomanFont -
ArialFont -
LucidaConsoleFont
If none of those are to your fancy, you can add your own fonts. To do this you need to call the InstallUserFont() function. This function takes the name of the font as its only parameter and will return an integer that you need to keep to reference the font in the future.
You can do a binary OR with this number or any of the predefined constants against these constants to change the style:
-
ItalicFont -
UnderlineFont -
BoldFont
To actually change the font, you can call the SetTextStyle() procedure, which takes three parameters:
- The font. (See above)
- The direction of the text in degrees or one of these two constants:
-
HorizDir -
VertDir
-
- The character size. Between 1 and 5 it represents the magnification of the characters with a standard font size of 8 pixels. Above 6 it represents an absolute font size. Default is set to 16.
Whatever the foreground colour is set to is what the text is painted with (remember to set the foreground colour use SetColor).
program GraphWindow; uses WinGraph; procedure Initialise(); var colourDepth, resolution, errorCode : SmallInt; begin //Open the Graphics Window colourDepth := SVGA; resolution := m800x600; InitGraph(colourDepth, resolution, 'Graph Window Example'); //Check for errors errorCode := GraphResult(); if errorCode <> grOK then begin WriteLn('Error when initialising the graphics window! Code: ', errorCode, '. Message: ', GraphErrorMsg(errorCode)); ReadLn(); Halt(1); end; end; procedure Finalise(); begin CloseGraph(); end; procedure Main(); var font : Integer; begin Initialise(); //Install a new font font := InstallUserFont('Comic Sans MS'); //Set the font (Comic Sans MS in Italics) SetTextStyle(font or ItalicFont, HorizDir, 60); //Set the text colour (the foreground colour) SetColor(Green); //Paint some text OutTextXY(250,200,'WinGraph 4tw!'); //..Do stuff here (currently the program will close instantly).. Finalise(); end; begin Main(); end.
There are more functions that you can use that allow a greater degree of control over the text painting. To find out more see the "Text and font management routines" section under the WinGraph Documentation.
WinMouse
WinMouse is small unit that you can include in your Pascal applications that will enable it to track mouse input across a graphics window created by WinGraph. To use this unit you need to place the winmouse.o and winmouse.ppu files in the same directory as your .pas source file. You also need to add WinMouse to your uses statement.
uses WinMouse;The WinMouse Queue
WinMouse listens for mouse input over the graphics window. When a mouse input event occurs (such as a mouse button click) WinMouse will log and add that event to a queue.
You can check to see whether the queue has any input events by calling the PollMouseEvent() function. It will return true if there are mouse events on the queue. You can peek (look at without removing from the queue) at the top item in the queue by passing in a MouseEventType variable to PollMouseEvent()'s only parameter (which is an out parameter).
You can remove the top mouse event off the queue by calling the GetMouseEvent() procedure and passing in an out variable of MouseEventType type.
Interpreting Mouse Events
When you have received a mouse event and got a variable of type MouseEventType (see above) you can check to see which button was clicked.
To check which mouse button was clicked you do this:
procedure ProcessMouseEvents(); var mouseEvent : MouseEventType; begin GetMouseEvent(mouseEvent); if mouseEvent.buttons and MouseLeftButton <> 0 then begin //..Do something.. end else if mouseEvent.buttons and MouseRightButton <> 0 then begin //..Do something.. end; end;
On Demand Mouse Information
To read the mouse cursor's current position you call two functions: GetMouseX() and GetMouseY(). These two functions get you the X and Y coordinates of the mouse cursor's current position in relation to the graphics window. The top left corner of the graphics window is coordinates (0,0).
The other on demand mouse input information you can get is what button of the mouse is currently depressed. You call the GetMouseButtons() function which returns one or and binary ORed combination of these three constants:
-
MouseLeftButton- the left mouse button is held down -
MouseRightButton- the right mouse button is held down -
MouseMiddleButton- the middle mouse button is held down
If the function returns 0 then no mouse buttons are currently depressed.
More?
There are couple more less significant mouse functions that haven't been mentioned here. You can find them over in the WinGraph/Mouse documentation under the section "Mouse management routines".
WinCRT
WinCRT is a unit that you can include in your Pascal applications that provides functions to deal with reading keyboard key presses. To use it make sure the wincrt.o and wincrt.ppu files are in the same folder as your .pas source file and add WinCRT to your uses statement.
uses WinCRT;Reading Key Presses
WinCRT implements an event queue in the same manner that WinMouse does. It monitors for events such as key presses and adds each one to a queue.
To check to see whether there are any events on the queue, call the KeyPressed() function which will return true if there are. If there are events on the queue, you can remove the topmost event off the queue by calling the ReadKey() function. This function will return a char variable that is the character of the key that was pressed.
Below is an example that shows a procedure that checks if there is any event waiting on the queue, and if so, makes a decision about what to depending on which key was pressed:
procedure ProcessKeyboardEvents(); var input : Char; begin if KeyPressed() then begin input := ReadKey(); if input = 'd' then begin //..Do stuff.. end else if input = 'c' then begin //..Do stuff.. end else if input = 's' then begin //..Do stuff.. end; end; end;
There are other less significant keyboard input functions available from WinCRT; to find out about these look at the WinGraph\CRT documentation under the section "Keyboard management routines".
OpenAL
OpenAL is a sound library that allows you to play and mix sound in your applications. You can use it in Pascal applications by using the OpenAL unit. To do this make sure the OpenAL.dll, OpenAL.o and OpenAL.ppu files are in the same directory as your .pas source file. Also add OpenAL to your uses statement.
uses OpenAL;Initialising OpenAL
Before you can use the OpenAL sound library you must initialise it. There are two procedure calls for this: InitOpenAL() and AlutInit(). Here is an example:
procedure InitSound(); var argv: Array of PalByte; begin InitOpenAL(); AlutInit(nil,argv); end;
When your program closes you must unload the OpenAL library with a call to AlutExit().
Loading a Wave Sound File into a Buffer
The first thing you must do when you want to play sound is to load the sound file into memory. This HowTo will show you how to load standard .wav Wave files for playback. The below example loads the "tada.wav" file into the buffer soundBuffer:
var soundBuffer : TALuint; procedure LoadSound(); var format: TALEnum; size: TALSizei; freq: TALSizei; loop: TALInt; data: TALVoid; begin //Load the sound buffer AlGenBuffers(1, @soundBuffer); AlutLoadWavFile('tada.wav', format, data, size, freq, loop); AlBufferData(soundBuffer, format, data, size, freq); AlutUnloadWav(format, data, size, freq); end;
Normally, in small scale applications, it is safe to load all the sounds at program startup. Of course, in any unmanaged language, you are required to clean up your memory before the program ends. This means you must delete used buffers:
procedure UnloadSound(); begin AlDeleteBuffers(1, @soundBuffer); end;
Creating a Source for the Sound
A buffer contains the sound in memory. A "Source", however, dictates "where" (in virtual 3D space) and "how" the sound will be played. Another way of looking at it is that a source plays a buffer in a certain manner. Many sources can use a single buffer.
To help us manage sources you should create a record type that contains a source's data:
type SoundSource = record pBuffer : ^TALuint; source : TALuint; sourcepos: array [0..2] of TALfloat; sourcevel: array [0..2] of TALfloat; listenerpos: array [0..2] of TALfloat; listenervel: array [0..2] of TALfloat; listenerori: array [0..5] of TALfloat; end;
Note that it contains a pointer to a buffer, not an actual buffer. This is because multiple sources can share a single buffer.
The below example shows how to create a source (it will create a source for the soundBuffer that we loaded in the last section):
//source is a global variable of type SoundSource //soundBuffer is a global variable and was set in the last section procedure CreateSources(); begin //Set the pointer to the buffer source.pBuffer := @soundBuffer; //Set Sound and listener position, velocity, and origin values //to some simple settings (the centre of 3D space) source.sourcepos[0] := 0.0; source.sourcepos[1] := 0.0; source.sourcepos[2] := 0.0; source.sourcevel[0] := 0.0; source.sourcevel[1] := 0.0; source.sourcevel[2] := 0.0; source.listenerpos[0] := 0.0; source.listenerpos[1] := 0.0; source.listenerpos[2] := 0.0; source.listenervel[0] := 0.0; source.listenervel[1] := 0.0; source.listenervel[2] := 0.0; source.listenerori[0] := 0.0; source.listenerori[1] := 0.0; source.listenerori[2] := -1.0; source.listenerori[3] := 0.0; source.listenerori[4] := 1.0; source.listenerori[5] := 0.0; //Make the sound source AlGenSources(1, @(source.source)); AlSourcei ( source.source, AL_BUFFER, source.pBuffer^); AlSourcef ( source.source, AL_PITCH, 1.0 ); AlSourcef ( source.source, AL_GAIN, 1.0 ); AlSourcefv ( source.source, AL_POSITION, @(source.sourcepos)); AlSourcefv ( source.source, AL_VELOCITY, @(source.sourcevel)); AlSourcei ( source.source, AL_LOOPING, AL_FALSE); //Specify where the sound is heard AlListenerfv ( AL_POSITION, @(source.listenerpos)); AlListenerfv ( AL_VELOCITY, @(source.listenervel)); AlListenerfv ( AL_ORIENTATION, @(source.listenerori)); end;
You must delete your sources before the application closes:
AlDeleteSources(1, @(source.source));
pBuffer pointer to nil
Playing the Source
To play a source is very simple; all the work has already been done in its setting up. To play, all you must do is:
AlSourcePlay(source.source);
Pausing is similar:
AlSourcePause(source.source);
And so is stopping:
AlSourceStop(source.source);
Any two or more sources playing at the one time are automatically mixed together by the OpenAL library.
Complete Example
A complete example of all the concepts explained in this HowTo is available here.