In this issue:
Liberty Basic provides many graphics capabilities as simple Basic commands. However, there are times that you need to include more complex functions into your program - and then you need to use the API Functions available from Windows.
I have created a document using Windows Wordpad that gives an introduction to using the GDI Functions. For Windows 3.11 users, I believe that this should be compatible with Write.exe. If not, please let me know so that I can create a format for you that is compatible.
Also included in the attached zip, is a program written by Alyce to demonstrate many of the Functions discussed in the document.
This document is a tutorial for GDI functions - NOT a tutorial for making API calls.
If you haven't used the API functions before - please take note - you should download the API Tutorial from my site and study that before attempting to understand the contents of this document.
You will notice that many of the functions discussed are already available as simple commands in Liberty Basic. I have done this intentionally for three reasons.
First, it gives you some idea of the work done in the background by LB to make your programming life much easier.
Second, a clear understanding of the way these functions work will make it much easier for you to understand how to implement the additional capabilities that are not provided by LB.
Third, you will need to fully understand these functions so that you will be able to use the Bitmap functions that will be described in Part 2 of this series.
As always - I encourage you to post any questions that you have about the contents of this newsletter on the Newsletter Message Board at my site:
http://users.orac.net.au/~brosco
Newsletter written by: Brosco.
Comments, requests or corrections mailto:brosco@orac.net.au
Translated from Australian to English by an American:
Alyce Watson - Chief Editor. Thanks Alyce.
AN INTRODUCTION TO GDI - The Graphics Device Interface
Graphics in Windows are handled primarily by functions in the GDI.DLL. This module calls routines in the various driver files. When you install a printer or VGA Adapter card, Windows also installs a *.DRV file - either from the Windows installation disks, or from disks that were supplied with your new hardware. The driver accesses the hardware of the video display (or printer or plotter). Different video display adapters and printers require different driver files.
The GDI system is constructed so that Windows can determine from the driver what the driver can and cannot handle. For instance, if the video hardware includes a graphics coprocessor that can draw ellipses, then GDI can take advantage of that, otherwise, the GDI module itself must calculate the points of the ellipse and pass the points to the driver.
Because a large number of different display adapters can be attached to a PC, one of the primary goals of GDI is to support device-independant graphics. GDI accomplishes this goal by providing facilities to insulate your programs from the particular characteristics of different output devices.
THE DEVICE CONTEXT
When you want to draw on a graphics output device (such as a screen or a printer), you must first obtain a handle to the Device Context (or DC). In giving your program this handle, Windows is giving you permission to use the device. You then include this handle as a parameter in the GDI Functions to identify to Windows the device that you want to draw on.
The DC contains many current 'attributes' that determine how the GDI functions work on that device. For example, when you call TextOut, you need only specify the starting co-ordinates, the text and the length of the text. You don't need to specify the font, the colour of the text or the background behind the text, because these attributes are part of the Device Context. Of course, you can call other functions to change any of these attributes.
In Liberty Basic, you get the handle to the Device Context with the following statements:
hw = hwnd(#w.gb) ' #w.gb is the handle of the graphicBox in the window you will be drawing on Open "user.dll" for dll as #user CallDll #user "GetDC", hw as word, hdc as word 'hdc is the handle of the Device Context
NOTE: In this example I have used a GraphicBox - but these functions will work against any Window type except Spreadsheet. And you can simple use the hwnd of the Window. You dont need to have a GraphicBox for your drawing area.
When you have finished with the Device Contect you MUST release it:
CallDll #user, "ReleaseDC", hw as word, hdc as word Close #user
Once you have Released the DC, it is no longer available for you to use. All of your GDI calls must be placed between the GetDC and the ReleaseDC. You may GetDC and ReleaseDC several times in your program, but this only incurs an unnecessy overhead. If you are using this function in Liberty Basic, it is best that you GetDC at the start of your program and ReleaseDC at the end. Please remember also, that Windows only allows you to have five DC's at one time.
NOTE: All the following commands use the GDI.DLL. It will be assumed that you have issued the following statement at the beginning of your program:
Open "gdi.dll" for dll as #gdi
and that you have issued the following command at the end of your program:
Close #gdi
DRAWING PIXELS / POINTS
A pixel is a single 'dot' on your display screen. The colour of the pixel is determined by the amount of red, green and blue 'colour beams' (know as RGB) that are focused on this point. The amount of colour is expressed as a number from 0 to 255. 0 means none of this colour. 255 means 100% of this colour. Here are a few combinations
RED GREEN BLUE 0 0 0 = Black 255 0 0 = Pure Red 0 255 0 = Pure Green 0 0 255 = Pure Blue 255 255 255 = White
You can draw a single Pixel of a particular colour with the SetPixel function:
CallDll #gdi, "SetPixel", hdc as word, x as word, y as word, rgbcolour as long, actualColor as long
hdc is the handle to the device context that was set by GetDC
x and y are the screen coordinates of where you want to draw the pixel.
rgbcolor is a Long Integer - in Liberty Basic you can set this value by:
rgbcolour = redValue + greenValue * 256 + blueValue * 256*256
If your monitor cannot support the colour you have requested - the actual colour displayed will be returned in the field 'actualColour".
Theroretically, this is the only command you need to draw any shape you wish - because lines, circles, boxes, ellipses, etc - are all made from a series of SetPixel commands. However, this would be very inefficient, as GDI has all the formulas programmed for you. Additionally, because of the Device Context, GDI knows if there is an even faster way of doing these, using the hardware capabilities of your graphics adapter card.
You can also obtain the colour of a particular pixel with the call:
CallDll #gdi, "GetPixel", hdc as word, x as word, y as word, actualColour as long
To extract the RGB values using the following calculations
Blue = int(actualColour / (256*256)) Green = int((actualColor - Blue *256*256) / 256) Red = int(actualColour - Blue*256*256 - Green*256)
DRAWING LINES
After drawing points, the next step up is drawing lines. The GDI is capable of drawing straight lines and elliptical lines. An elliptical line is a curved line on the circumference of an ellipse. There are four functions available:
LineTo - straight lines
PolyLine - a series of connected lines (say, a triangle or a rectangle)
PolyPolyLine - multiple series of connected lines
Arc - elliptical lines
There are 5 attributes of the Device Context that affects the appearance of the lines that you draw using these functions:
Current pen position (used by LineTo only)
pen
background mode
background color
drawing mode.
The LineTo function is one of the few GDI functions that does not include the full dimensions of the object to be drawn. Instead, LineTo draws a line from the current pen position defined in the Device Context. Initially it is at position (0,0). If you use the LineTo function without first setting the pen position, it will draw a line starting at the upper left corner of the graphicsBox. To start at another location, you must use the MoveTo function to set the pen position:
CallDll #gdi, "MoveTo", hdc as word, xStart as word, yStart as word, result as word
MoveTo doesn't draw anything. It simply changes the current pen position. You can now use the LineTo to draw the line:
CallDll #gdi, "LineTo", hdc as word, xEnd as word, yEnd as word, result as word
This draws the line up to (but not including) the point (xEnd, yEnd). Following the call, the current pen position is set to (xEnd, yEnd).
You can also obtain the current pen position by calling:
CallDll #gdi, "GetCurrentPosition", hdc as word, xyPoint as long
The actual (x,y) coordinates can be obtained by:
y = int(xyPoint / (256*256) x = int(xyPoint - y*256*256)
USING PENS
When you use any of the line drawing functions, GDI uses the 'pen' currently selected in the Device Context to draw the line. The "pen' determines the line's colour, its width and its style - which can be solid, dotted or dashed. The pen in the default device context is BLACK_PEN. This pen draws a solid black line that is 1 pixel in width. There is also a WHITE_PEN and a NULL_PEN available in GDI. If you wish to use the WHITE_PEN rather than the BLACK_PEN you must first get the handle of the pen:
CallDll #gdi, "GetStockObject", _WHITE_PEN as word, hpen as word
To make this the currently selected pen in the device context, you must first select it using SelectObject:
CallDll #gdi, "SelectObject", hdc as word, hpen as word, result as word
After this call, any lines that you draw will use the white pen. To return to using the Black pen, we would get the handle of the black pen and SelectObject again.
CREATING, SELECTING & DELETING PENS
Although having these pens predefined is very useful - they don't give us a lot of variety. If you want something fancier - you must create your own pen. Don't worry, this isn't as hard as it sounds!
To create a new pen with different characteristics, we simply:
CallDll #gdi, "CreatePen", PenStyle as word, penWidth as word, rgbColour as long, hpen as word
PenStyle can have the following values: _PS_SOLID _PS_DASH _PS_DOT _PS_DASHDOT _PS_DASHDOTDOT _PS_NULL _PS_INSIDEFRAME
PenWidth is the width of the pen in pixels.
NOTE: If you select one of the dotted or dashed styles, the PenWidth must be set at 1 - otherwise GDI will revert to a solid style.
rgbColour is the colour of the pen (as discussed with Set/GetPixel).
Having created the pen - how do you think that we would get to use it in our Device Context?????
That's right - we just SelectObject!
CallDll #gdi, "SelectObject", hdc as word, hpen as word, result as word
Now, any drawing you do will use the pen that YOU created.
SOME WORDS OF WARNING.
When you create a pen, it doesn't 'belong' to your program. It belongs to GDI. And when you terminate your program, Windows is unable to remove objects that have been created within GDI. So - you MUST delete your pen when you are finished with it.
This rule applies to the 5 other types of objects that you can create:
Brushes, Bitmaps, Regions, Fonts and Palettes.
Also - you cannot delete an object if it is currently selected in a valid device context. You MUST Release the DC before deleting the object, or select the default object of the same type first.
One more rule - DON'T delete the StockObjects like BLACK_PEN!
The function to delete your created pen is:
CallDll #gdi, "DeleteObject", hpen as word, result as word
If you delete a GDI object while it is still selected in a Device Context and then try to draw some lines, Windows will respond with a fatal error because the DC doesn't contain a valid pen, so this bug will be fairly easy to find. However, failure to delete GDI Objects that you create, will be a little more difficult to find. What will happen is you will eventually run out of memory, because each Object that you create uses memory until it has been deleted.
FILLING IN THE GAPS
The use of dotted and dashed pens raises an interesting question. What happened to the gaps between the Dots and the Dashes?
The colouring of the gaps depends on both the background mode and the background colour attributes defined in the Device Context. The default background mode is OPAQUE, which means that Windows fills in the gaps with the background colour, which by default is White.
You can change the background colour that Windows uses to fill in the gaps by calling:
CallDll #gdi, "SetBkColor", hdc as word, rgbColour as long,result as long
You can also prevent Windows from filling in the gaps by changing the background mode to TRANSPARENT.
CallDll #gdi, "SetBkMode", hdc as word, _TRANSPARENT as word,result as short
By doing this, Windows will ignore the Background Colour and will not attempt to fill in the gaps. You can also obtain the current background mode by:
CallDll #gdi, "GetBkMode", hdc as word, mode as word
ADDITIONAL WAYS TO AFFECT THE DRAWING MODE
Imagine drawing a line that has a colour based not only on the colour of the pen, but also on the original colour of the display area where the line is drawn. For example, wouldn't it be handy to use the same pen to be able to draw a black line on a white surface and a white line on a black surface - without knowing what colour the surface is? Its possible - here's how:
When Windows uses a pen to draw a line it performs a BitWise Boolean operation. I will explain this shortly - but GDI refers to the Bitwise operation as a Raster Operation or ROP for short. Basically it involves using different techniques to combine two inputs to create the output. The two inputs that we are talking about are the line we are about to draw and the background colour of the display. The output that is created is the appearance of the line when it is drawn.
To explain this we will assume that a 0 represents black and a 1 represents white. (The ROP works with the millions of colours available - but I am just using these two colours to make the explanation easier to understand). If we use just these two colours, our PEN can have two values, black or white - and our background can also have the same two values. This gives us a total of 4 combinations:
PEN BACKGROUND 0 0 Both Black 0 1 Pen Black, BG White 1 0 Pen White, BG Black 1 1 Both White
The default ROP mode is _R2_COPYPEN. That is - simply ignore the values of the background and make the output equal to the Pen values.
Here are a couple more:
_R2_MERGEPEN combines the two values in such a way that if either (or both) our inputs are white - the output will be white. With the four combinations available, 3 of the combinations will produce a white line. Only when both the pen and the background are black will we get a black line. In Bitwise Boolean - this is an OR function. That is - if either input is white - then make the output white.
Still with me?
_R2_XORPEN This is my favourite ROP mode. It is extremely powerful and very useful. XOR is short for EXCLUSIVE OR. In Bitwise Boolean it means if either input is white, but NOT both, then make the output white. Out of our 4 combinations, 2 of them will produce white output.
What's so special about this? Ahah - Say my background is black and my pen is set to white, the XOR operation will result in a white line. And if my background is white and my pen is also white I will get a black line. But more importantly, in ALL cases, if I draw the same line to the same position again, the resultant image will revert back to the original display before anything was drawn. Did you follow that? I'll give you an example. :
If my display background is white and I draw a box with my pen set to black - I will get a box drawn with black lines. If I draw the same box in the same position, without changing any colors or pens etc. the box will disappear.
If you have used FreeForm you will notice that you can drag controls around the Window. This is the method used to achieve this. The ROP mode is set to XOR. When you first define the control, it is drawn in the default position. When you click on the control and move your mouse, the control moves around the window following your mouse movements. This is achieved by simply redrawing the control at the old position (to make it disappear) and then draw it again at the new position. This is continued until you release the mouse button. This is one of the most useful commands to achieve animation.
Here is a table of the ROP modes available:
1 1 0 0 Pen Values 1 0 1 0 Background values ----------------Results------------------------------ 0 0 0 0 _R2_BLACK 0 0 0 1 _R2_NOTMERGEPEN 0 0 1 0 _R2_MASKNOTPEN 0 0 1 1 _R2_NOTCOPYPEN 0 1 0 1 _R2_MASKPENNOT 0 1 0 1 _R2_NOT 0 1 1 0 _R2_XORPEN 0 1 1 1 _R2_MOTMASKPEN 1 0 0 0 _R2_MASKPEN 1 0 0 1 _R2_NOTXORPEN 1 0 1 0 _R2_NOP 1 0 1 1 _R2_MERGENOTPEN 1 1 0 0 _R2_COPYPEN 1 1 0 1 _R2_MERGEPENNOT 1 1 1 0 _R2_MERGEPEN 1 1 1 1 _R2_WHITE
You set the drawing mode in the Device Context by:
CallDll #gdi, "SetROP2", hdc as word, mode as word, result as word
And you can also determine the current mode by:
CallDll #gdi, "GetROP2", hdc as word, mode as word
ROP2 AND COLOUR
The drawing mode gets even more interesting - and more complex - when colours are introduced. Lets say you have a VGA capable of displaying only 8 pure colours. The pen can be any of these 8 colours. For simplicity, we will assume that the background can only display the same 8 colours. The eight colours are combinations of the bits in the Red, Green and Blue colour planes.
Red Green Blue - Colour 0 0 0 Black 0 0 1 Blue 0 1 0 Green 0 1 1 Cyan 1 0 0 Red 1 0 1 Magenta 1 1 0 Yellow 1 1 1 White
Each of the 3 colour planes is affected separately by the Raster operation. Say you have a Cyan background and a Magenta pen colour, and your drawing mode is _R2_MERGEPEN. What colour will the pen draw? Remember how _R2_MERGEPEN works - it says if either input is set, then set the ouput - in this case, we will end up with all colour planes set - and the pen will draw a WHITE line.
If we had a drawing mode _R2_XORPEN (only set the ouput if one if the inputs is set) We would get a Pen colour of Yellow.
NOTE: For old EGA monitors this is exactly how it works. For Hi-Colour and True Colour, this also works (and the combinations allows millions of colours). For 256-Colour monitors (standard VGA), there is a slight problem. For these, the number produced as a result of the operation is not a colour, it is the index number to a colour in the Palette Table. So the results will not be consistant.
DRAWING FILLED AREAS
Now lets take the next step up, from drawing lines to drawing shapes. Windows has seven drawing functions for drawing filled areas with borders:
Rectangle Rectangle with square corners Ellipse Ellipse RoundRect Rectangle with round corners Chord Arc on the circumference of an ellipse with the endpoints connected by a line Pie Pie wedge on the circumference of an ellipse Polygon Multisided figure PolyPolygon Multiple multisided figures
Windows will draw the figure with the currently selected pen. The current background mode, background colour and drawing mode are all used for this outline - just the same way they were used for drawing lines.
The figure is filled with the current Brush selected in the Device Context. By default, this is the stock object _WHITE_BRUSH. Windows defines 6 stock brushes: WHITE_BRUSH, _LTGRAY_BRUSH, _GRAY_BRUSH, _DKGRAY_BRUSH, _BLACK_BRUSH and _NULL_BRUSH. You can select one of the stock brushes into the Device Context in the same way you select a Stock Pen:
CallDll #gdi, "GetStockObject", _GRAY_BRUSH as word, hBrush as word
and then select it into the Device context calling SelectObject:
CallDll #gdi, "SelectObject", hdc as word, hBrush as word, result as word
Now, when you draw a shape, the interior will be filled with the colour of the Brush selected.
If you want to draw a shape without a border, select _NULL_PEN into the device context.
If you want to draw the outline of the figure without filling the interior, select the _NULL_BRUSH into the device context. You can also create customized brushes just as you can create your own pens.
The simplest filled object is a rectangle:
Calldll #gdi, "Rectangle", hdc as word, xLeft as word, _ yTop as word, xRight as word, yBottom as word, result as word
The point (xLeft, yTop) is the upper left corner of the rectangle, and (xRight, yBottom) is the lower right corner. NOTE: Windows always uses the approach for the second set of values - to draw up to but NOT including the Right and Bottom coordinates.
To draw an ellipse:
CallDll #gdi, "Ellipse", hdc as word, xLeft as word, yTop as word, xRight as word, yBottom as word
The function to draw a rectangle with rounded corners uses the same bounding box as the rectangle and the ellipse, but has two adiitional paraneters:
CallDll #gdi, "RoundRect", hdc as word, xLeft as word, yTop as word, _ xRight as word, yBottom as word, _ xCornerEllipse as word, yCornerEllipse as word, result as word
Windows uses the arc from a small ellipse to produce the rounded corners. Imagine an ellipse drawn using xCornerEllipse and yCornerEllipse as the parameters for width and height. Now split the ellipse into 4 segments and use each segment for the corners of the rectangle.
The functions for the other shapes are:
CallDll #gdi, "Arc", hdc as word, xLeft as word, yTop as word, _ xRight as word, yBottom as word, _ xEnd as word, yEnd as word, result as word CallDll #gdi, "Chord", hdc as word, xLeft as word, yTop as word, _ xRight as word, yBottom as word, _ xEnd as word, yEnd as word, result as word CallDll #gdi, "Pie", hdc as word, xLeft as word, yTop as word, _ xRight as word, yBottom as word, _ xEnd as word, yEnd as word, result as word
You can find more details about these functions in the API reference guide at Alyce's Restaurant.
CREATING YOUR OWN BRUSH
We have already used the stock brushes for filling our shapes - but Windows has three functions to allow you to create your own brush:
CallDll #gdi, "CreateSolidBrush", rgbColour as long, hBrush as word CallDll #gdi, "CreateHatchBrush", hatchStyle as word, rgbColour as long, hBrush as word Values for hatchStyle can be: _HS_HORIZONTAL, _HS_VERTICAL, _HS_FDIAGONAL, _HS_BDIAGONAL, _HS_CROSS and _HS_DIAGCROSS CallDll #gdi, "CreatePatternBrush", hBitmap as word, hBrush as word
The hBitmap parameter is the handle to an 8-by-8 bitmap. How you get this Bitmap handle will be covered in Part 2 of this Newsletter.
After creating your own brush, you must select it into the Device Context:
CallDll #gdi, "SelectObject", hdc as word, hBrush as word, result as word
After you are finished using it, you should delete it:
CallDll #gdi, "DeleteObject", hBrush as word, result as word
Remember - the same rules for deleting pens apply for deleting brushes. Do NOT delete a Brush that is currently selected.
CREATING REGIONS
A region is a description of an area on the display that is a combination of recangles, other polygons and ellipses. You can use these reagions for drawing or clipping. And guess what? You select a region by using SelectObject to make it available to the Device Context.
And just like Pens and Brushes, Regions are GDI objects, so they must be deleted when you are finished with them.
The simplest region is a rectangle:
CallDll #gdi, "CreateRectRgn", _ xLeft as word, yTop as word, _ xRight as word, yBottom as word, _ hRegion as word
You can create an elliptical region:
CallDll #gdi, "CreateEllipticRgn", _ xLeft as word, yTop as word, _ xRight as word, yBottom as word, _ hRegion as word
and a RoundRect Region:
CallDll #gdi, "RoundRectRgn", _ xLeft as word, yTop as word, _ xRight as word, yBottom as word, _ xCornerEllipse as word, yCornerEllipse as word, _ hRegion as word
Once you have created a Region, you can use four drawing functions:
CallDll #gdi, "FillRgn", hdc as word, hRegion as word, hBrush as word, result as word CallDll #gdi, "FrameRgn", hdc as word, hRegion as word, hBrush as word, _ xFrame as word, yFrame as word, result as word CallDll #gdi, "InvertRgn", hdc as word, hRegion as word, result as word CallDll #gdi, "PaintRgn", hdc as word, hRegion as word, result as word
The xFrame and yFrame parameters to FrameRgn are the width and height parameters for the Frame to be painted around the region.
The PaintRgn function fills in the region with the brush currently selected in the device context.
When you are finished with the region, you must delete it using DeleteObject.
PATBLT
In Part 2 of this series I will be discussing image manipulation. Many of the functions used revolve around a very powerful function BitBlt (pronounced Bit-Blit) which stands for Bit-Block-Transfer. BitBlt is a pixel mover, but the word Transfer doesn't do justice to the command. It does more than just transfer - it actually does a logical combination of three sets of pixels using 1 of 256 different types of raster operations.
To give you a taste of things to come, I will briefly introduce you to PatBlt, the simplest of the 3 Blt functions available.
Earlier in this article I discussed the drawing mode, and how it could be set to 1 of 16 different ROP2 modes. When you draw a line, the drawing mode determines the the type of logical operation that Windows performs on the Pen and the Pixels of the Device Context destination. PatBlt is similar to the drawing mode except that it alters a rectangular area of the Device Context destination, rather than just a single line. It performs a logical operation on the pixels in this rectangle and a "pattern". Now, this "pattern" is nothing new - its simply another name for a Brush. For this pattern, PatBlt uses the Brush currently selected in the Device Context.
The syntax for the Call is:
CallDll #gdi, "PatBlt", hdc as word, xDest as word, yDest as word, _ xWidth as word, yHeight as word, _ ROP as long, result as word
NOTE: The operation performed is determined by the ROP parameter. This is NOT the same values as we used for ROP2 (above).
Here are a couple of the ROP commands available:
_BLACKNESS Set the area to Black _WHITENESS Set the area to White _PATINVERT Inverts the colours in the rectangle
There are more commands than this - but I will cover those in Part 2.
Alyce has written a program to demonstrate many of the functions discussed here. It is called GDI.BAS.
I have written a program to demonstrate moving a rectangle around the display. It is called XOR.BAS.
Both programs are included in the ZIP file with this document.
To learn more about these and other functions - there are many sample programs, tutorials, reference materials, etc at:
Alyce's Restaurant - http://www.wctc.net/~awatson/liberty.html
and
Brosco's LB Resource Centre - http://users.orac.net.au/~brosco/
In Part 2 of this series I will discuss how to manipulate Bitmaps with the GDI DLL.