The Liberty Basic Newsletter - Issue #15 - JUL 1998

© 2003, http://groups.yahoo.com/group/lbnews/

All Rights Reserved

Individual authors retain copyrights to their works.

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.