Direct+screen+memory+access

//by Richard Russell, March 2007//

Normally, your BASIC program doesn't have direct access to its //screen memory//, i.e. the contents of the bitmap holding the text and graphics output from the program. The only ways to write to that bitmap are by using BASIC statements and commands (e.g. **PRINT**, **PLOT**, ***MDISPLAY** etc.) or by means of Windows API functions that use the **@memhdc%** //system variable// (e.g. ). This is perfectly acceptable for most applications, but occasionally you may prefer to have direct memory access to the bitmap. An example might be an arcade-style game in which some special animation cannot straightforwardly (or quickly enough) be achieved in BASIC or by using the Windows API.

It is possible to set up your program so that you //can// access the bitmap memory. This allows you to write directly to that memory, for example using custom assembler code, to achieve effects that would otherwise be impractical. The first step is to decide what //format// you wish the bitmap to have; typical choices would be **8 bits-per-pixel** (a //paletted// format in which each pixel contains an index to a //colour table//) or **24 bits-per-pixel** (an RGB format containing true colour values). Other formats you might consider using are **16 bits-per-pixel** or **32 bits-per-pixel**.

The choice of format typically depends on a trade-off between the number of different colours available (in **8 bits-per-pixel** mode the total number of different colours is 256) and speed (in **24 bits-per-pixel** mode three times as much memory is required to hold a bitmap of the same dimensions).

The first step is to define the chosen bitmap format:

code format="bb4w" DIM BITMAPINFOHEADER{Size%, Width%, Height%, Planes{l&,h&}, BitCount{l&,h&}, \ \                   Compression%, SizeImage%, XPelsPerMeter%, YPelsPerMeter%, \ \                   ClrUsed%, ClrImportant%} DIM bmi{Header{} = BITMAPINFOHEADER{}, Palette%(255)} bmi.Header.Size% = DIM(BITMAPINFOHEADER{}) bmi.Header.Width% = @vdu%!208 bmi.Header.Height% = @vdu%!212 bmi.Header.Planes.l& = 1 bmi.Header.BitCount.l& = 8 code Here an 8 bits-per-pixel bitmap has been defined; if you want a different format set the value of **bmi.Header.BitCount.l&** accordingly. The code assumes that you want the bitmap to be the same size as the current window (as obtained from the //system variables// **@vdu%!208** and **@vdu%!212**); if you require a different width or height set the structure members accordingly.

Because the selected mode is **paletted** a colour table containing an appropriate set of colours must be created:

code format="bb4w" FOR I% = 0 TO 255 r% = I%       g% = I%        b% = I%        bmi.Palette%(I%) = b% + (g% << 8) + (r% << 16) NEXT code Here, for simplicity, a grey-scale has been created, where each palette entry contains the same values for red, green and blue. In practice it is more likely that your program will need a selection of colours. You might need to store the colours in, for example, **DATA** statements.

Now the bitmap format and colour table have been defined, you can create the bitmap itself:

code format="bb4w" SYS "CreateDIBSection", @memhdc%, bmi{}, 0, ^bits%, 0, 0 TO hbitmap% IF hbitmap% = 0 ERROR 100, "Couldn't create DIBSection" SYS "SelectObject", @memhdc%, hbitmap% TO oldhbm% SYS "DeleteObject", oldhbm% CLS code On successful completion of this code the variable **bits%** will contain the address in memory of the bitmap, hence **?bits%** would be the pixel value of the //bottom left// pixel in the window (i.e. it is a **bottom up** bitmap).

It is most likely that you will want to 'draw' into the bitmap using assembler code, but for the purposes of illustration here is some very simple BASIC code which draws a diagonal line:

code format="bb4w" bytesperpixel% = bmi.Header.BitCount.l& DIV 8 bytesperline% = ((bmi.Header.Width% * bytesperpixel%) + 3) AND -4 FOR I% = 0 TO 99 x% = I%       y% = I%        c% = 0 bits%?(y% * bytesperline% + x% * bytesperpixel%) = c%     NEXT SYS "InvalidateRect", @hwnd%, 0, 0 code The variable **bytesperpixel%** contains the number of bytes comprising each pixel (in this example **1**) and the variable **bytesperline%** contains the number of bytes in one line (row) of the bitmap, which must always be rounded up to a multiple of 4.

The **InvalidateRect** is necessary to cause the screen to be updated from the new bitmap contents. For best performance you should invalidate only the smallest rectangle containing the changed pixels, but for convenience the code shown invalidates the entire window. If you //do// want to invalidate only a rectangle use code similar to the following:

code format="bb4w" DIM rc{l%, t%, r%, b%} REM Load rectangle dimensions here (left, top, right, bottom) SYS "InvalidateRect", @hwnd%, rc{}, 0 code If you are performing some kind of animation, it is quite likely that you will want to force an immediate screen refresh:

code format="bb4w" *REFRESH code You can still use the majority of the standard BASIC or Windows API methods of writing to the output bitmap, in addition to the direct memory access provided by this technique. For example if you want to output text you can use **PRINT**.

Note that this technique may not work succesfully if the PC's display is itself set to a paletted (e.g. 256 colour) mode.