Skip to content

Bitmaps in Dyalog APL/W

Vector 19:1, page 138

graphics system interfaces

Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 138

It used to work…

Dyalog APL/W provides a workspace called BMED that contains two basic but quite useful utilities. The first is also called BMED to create or edit the the Bits to define a Bitmap. The second is called BTNED to create or edit the Bits needed to define the three Bitmaps identified in the BtnPix Property of a Push Button Object, each providing the image for its different states (Out, In and Inactive). In both cases, the other important ingredient, the Colour Map (CMap) assumes the standard 4-bit (16 colour) Windows default.

[For those unfamiliar with Bitmaps, the CMap Property is a 3-column matrix whose rows each define the Red-Green-Blue intensity levels that when combined form a particular colour, the intensity level being measured on a scale from 0 (colour absent) to 255 (full saturation). Thus pure red is 255 0 0, black is 0 0 0, and white is 255 255 255. The Bits Property is a matrix of the size of the Bitmap in pixels where each element is a 0-origin index into a row of CMap.]

The dialogs for the BMED utilities present an editing area wherein a Bitmap is represented by a grid in which each pixel is blown up to a size of, say, 10 by 10 display pixels, or whatever the user prefers. These squares may then be rendered from a palette of colours using mouse clicks , the rather limited set of the 16 standard Windows colours in this instance. In addition, a number of tools are provided including some to draw graphical objects (lines, rectangles and ellipses). An ellipse would look nice, I thought. Just click on the Ellipse tool, click on a starting "pixel" and then drag the bounding rectangle. The result? No ellipse, nor any change at all to my work or art! Yet I felt sure that this used to work.

What went wrong?

The technique takes advantage of the Ellipse Object to actualy draw the ellipse, illustrated as follows:

'ff'⎕WC'Form' '' (10 10)(100 100)('Coord' 'Pixel')('Bitmap' 'ff.bm')

This line is not part of the code. It just provides a visual display of what is happening if you wish to try this for yourself. These are the important lines:

'ff.bm'⎕WC'Bitmap' ('Bits' (24 24⍴0))
'ff.bm.'⎕WC'Ellipse'(7 4)(10 16)('FCol' 255 255 255)
bits←0≠'ff.bm'⎕WG'Bits' 'CMap'

First create a Bitmap Object of the actual required size for the finished product but with all elements set to 0, the index of black in the default Colour Map, though the actual colour is not important. Then draw an Ellipse Object into the Bitmap from the desired start point (7,4) and size (10,16) that define the bounding rectangle for the ellipse. Specify a colour for the resulting ellipse; 255 255 255 is white though again this does not matter as long as it differs from the background. Note that it is not necessary to give the Ellipse Object a name thereby saving Windows resources, a useful property of drawing into a Bitmap. Any number of graphical objects may be added to the image. In this example, the Form will display an ellipse with a white outline against a black background.

To obtain the outline of the ellipse, simply get the Bits Property and "not-equal" to 0. This result may then be used to map the 0-origin index of the actual colour we wish to use, 9 for Red, say, into the working Bitmaps’s Bits variable, BITS say, and then into the editing display. For example:

((,bits≠0/,BITS)←9

Unfortunately, this did not work. Investigation revealed…

⍴¨'ff.bm'⎕WG'Bits' 'CMap'
 0 0  0 3

And the reason?

Like most people these days, the colour display resolution for my screen is set to True Colour (24-bit colour). When reduced to 256 colours (8-bit colour), the method indeed worked. So my memory was not at fault!

It turns out that Dyalog APL does not fully support Bitmaps with 24-bit colour. In the above, when the initial Bitmap is defined with Bits set to all zeroes, a Colour Map (CMap) with just 2 colours is initialised, actually the first two from the standard Windows 4-bit colour table. Both Bits and CMap may be successfully retrieved. However, as soon as a child object is drawn into the Bitmap image, the colour representation converts, it would seem, according to the current colour resolution of the display. Whether this is done by Dyalog or Windows, I cannot say. With a colour resolution of 8-bits, the Colour Map converts to 256 colours. For True Colour, a 24-bit colour scheme is adopted. With an 8-bit setting or lower, both Bits and CMap may be retrieved from the Bitmap Object. However, the way in which colour is handled in a Bitmap resource alters for a 24-bit scheme, as will be discussed further.

Dyalog APL can display a Bitmap with 24-bit colour, as confirmed by the Form display above. It can also read and display a Bitmap with 24-bit colour from a .BMP file. However, retrieving Bits and CMap using ⎕WG gives results with shape 0 0 and 0 3 respectively. Not very useful.

More about Bitmaps

The full specification for a Bitmap supports 1-bit (2 colours), 4-bit (16 colours), 8-bit (256 colours) and 24-bit colour ( about 16.5 million colours, also known as True Colour). Up to 8-bit colour, the actual colours are stored in a separate Colour Map and the values of the Bitmap Bits are 0-origin indices into this map. For 24-bit colour, there is no Colour Map. Instead, each element of Bits is an encoding of the Red-Green-Blue (RGB) intensity values on a scale from 0 (colour absent) to 255 (full saturation).

A Bitmap .BMP file [1] has the following structure which is more formally documented in the leading comments for function BM_Read in the Appendix:

  • BITMAPFILEHEADER

File header information giving the file identification ('BM'), total file size, and offset to the Bits data.

  • BITMAPINFOHEADER

Information about the Bitmap, including its width and height, colour scheme (1, 4, 8 or 24 bits), and actual number of colours used in the Colour Map (this defaults to 2, 16 or 256 if not specified).

  • RGBQUAD

The Colour Map specifying the RGB colour intensities. This is absent for 24-bit colour. Each colour is represented in 4 bytes in the order Blue- Green-Red with the 4th byte set to 0.

  • Bitmap Bits

The Bits data for each row of pixels starting at the last row with each row padded out to a 4-byte boundary if necessary. Each element takes 1, 4, 8 or 24 bits depending on the colour scheme in use. In the first three cases, the values represent 0-origin indices into the Colour Map; in the latter case, the 24-bits represent 8-bits per primary colour in the order Blue-Green-Red.

A Bitmap resource is represented by Windows using the same structure but omitting the BITMAPFILEHEADER structure.

Using this knowledge, I devised a method to overcome the True Colour problem. The function BM_Read will read a given .BMP file and extract the Bits and CMap information. It returns a 2-item vector whose first element is a boolean flag, 1 for success or 0 for failure, and the second contains the Bits and CMap results which have shape (0 0) and (0 3) respectively in the event of failure, eg. if the file does not exist or it is not a Bitmap file.

If the file contains a 24-bit colour Bitmap, it is reduced if possible to use the smallest Colour Map attainable depending on the number of unique colours actually found. If there are more than 256 colours, Bits contains RGB values encoded to base 256 256 256 in the order Red-Green-Blue which is consistent with the CMap order. In this case, CMap has shape (0 3) which should be used to check the meaning of elements of Bits. Unfortunately, although as noted above the Bitmap can be defined directly from the .BMP file, Dyalog APL will not accept Bits with RGB values along with an empty CMap to define a Bitmap, Cursor or Icon (a DOMAIN ERROR is reported) , perhaps something that might be rectified by the vendors? However, we now have access to and can utilise the Bits information in whichever form it is returned.

A work-around solution

Restating the example, we can now write a code fragment as follows:

'ff.bm'⎕WC'Bitmap'('Bits'(24 24⍴0))
'ff.bm.'⎕WC'Ellipse' (7 4)(10 16)('FCol' 255 255 255)
:If 0∊⍴bits←'pn.bm'⎕WG'Bits'
'ff.bm'⎕WS'File' 'tmp.bmp' ⋄ 1 ⎕NQ 'ff.bm' 'FileWrite'
bits cmap←2⊃BM_Read'tmp.bmp'
erase 'tmp.bmp'
:If 0<⊃⍴cmap
    bits←bits≠¯1+(↓cmap)⍳⊂0 0 0
:EndIf
:EndIf
bits←bits≠0

After creating the Bitmap with an ellipse, a test determines whether the Bitmap is usable, which is the case only if the screen colour resolution is 8-bit or less. If not, we create a .BMP file from the Bitmap. Examination of this file would verify that it has no Colour Map and that the Bits are RGB values, even though there are only 2 colours actually used. Reading the file with function BM_Read, the Bitmap is converted to use a 2-bit Colour Map. Checking the shape of CMap is redundant here, though required in a more general application.

Just to confirm…

      ⍴bits
24 24
      ⍴cmap
2 3

I expect there is a slicker way of doing this using a Windows API call through ⎕NA though the principle remains the same and this method seems quick enough on my pre-historic 266MHz Pentium.

A concluding thought

The same methodology could be used in reverse. Supposing you wished to create a Bitmap Object with 24-bit colour from RGB-encoded Bits that you have perhaps generated. Dyalog APL will not allow you to simply define the Bitmap Object using ⎕WC. However, one could write a complementary function to BM_Read , BM_Write say, using similar logic that would create a Bitmap .BMP file. Dyalog APL can then generate the required Bitmap Object from this file using ⎕WC.

Reference

[1] Charles Petzold, Programming Windows 3.1, Microsoft Press 1992, p607-609