Image Processing
Single-Bit Bitmaps  Lab Report
 

Correction:  The constant BitsPerPixel = 8 in the program should have been
named PixelsPerByte = 8.  The value of the constant is correct, but the 
name is misleading.  efg, 30 Aug 2007. 


See Bottom of Page for
Modified Version by Jean-Pierre Jouandet

Show how to work with single-bit (pf1bit) bitmaps
Screenpf1bit.jpg (30728 bytes)

Purpose
The purpose of this program, pf1Bit.EXE, is to show how to use Scanline and a pByteArray to create and manipulate pf1Bit bitmaps.

Materials and Equipment

Software Requirements
Windows 95/98
Delphi 3/4/5 (to recompile)
SingleBitBitmaps.EXE

Hardware Requirements
VGA display

Procedure

  1. Double click on the SingleBitBitmaps.EXE icon to start the program.
  2. Select any of the buttons across the top for display of various 1-bit bitmaps, which are by default displayed in black and white.
  3. Check "Define Palette" to use an alternate palette with the pf1bit bitmap.  Click on either TShape rectangle to define any color using the TColorDialog.   Once checked, select any of the buttons at the top to define a 1-bit bitmap with the specified colors.
  4. If desired, invert any bitmap by pressing the Invert button.
  5. Deselect/select the "Stretch" checkbox to change whether the bitmap is stretched to the full size of the TImage.

Discussion
There are 8 pixels stored in each byte in a pf1bit bitmap since each pixel is only a single bit.

A "0" pixel is normally black in a pf1bit bitmap, while a "1" pixel is normally white.  However, a palette can be attached to a pf1bit bitmap to define any two colors.

The following method is called when the Checker button is clicked (and during the FormCreate):

procedure TFormPf1bit.ButtonCheckerClick(Sender: TObject);
  VAR
    Bitmap:  TBitmap;
    i     :  INTEGER;
    j     : INTEGER;
    Row   :  pByteArray;
begin
  // Checkerboard pattern

  Bitmap := TBitmap.Create;
  TRY
    WITH Bitmap DO
    BEGIN
      Width := 32;
      Height := 32;

      // Unclear why this must follow width/height to work correctly.
      // If PixelFormat precedes width/height, bitmap will always be black.
      PixelFormat := pf1bit;

      IF   CheckBoxPalette.Checked
      THEN Bitmap.Palette := GetTwoColorPalette
    END;

    FOR j := 0 TO Bitmap.Height-1 DO
    BEGIN
      Row := pByteArray(Bitmap.Scanline[j]);
      FOR i := 0 TO (Bitmap.Width DIV BitsPerPixel)-1 DO
      BEGIN
        IF       j MOD 2 = 0
        THEN Row[i] := $AA //  1010 1010
        ELSE Row[i] := $55  //  0101 0101
      END
    END;

    ImageBits.Picture.Graphic := Bitmap
  FINALLY
    Bitmap.Free
  END
end;

Note that after the TBitmap is created, its Width and Height must be specified before the PixelFormat.  If not, the pf1bit bitmap will display only in black  (This pf1bit Scanline Enigma was described in a UseNet Post.)  

When the "Define Palette" checkbox is checked, a two-color palette is attached to the bitmap before the scanlines are defined.    The GetTwoColorPalette function is defined by the brush colors of the two TShapes.  The colors of these TShapes can be changed using the TColorDialog by clicking on either of them:

// Based on 6 Feb 1999 post to borland.public.delphi.graphics by
// David Ullrich in UseNet Post
FUNCTION TFormPf1bit.GetTwoColorPalette: hPalette;
  VAR
    Palette: TMaxLogPalette;
BEGIN
  Palette.palVersion := $300;
  Palette.palNumEntries := 2;

  WITH Palette.palPalEntry[0] DO
  BEGIN
    peRed := GetRValue(ShapeZero.Brush.Color);
    peGreen := GetGValue(ShapeZero.Brush.Color);
    peBlue := GetBValue(ShapeZero.Brush.Color);
    peFlags := 0
  END;

  WITH Palette.palPalEntry[1] DO
  BEGIN
    peRed := GetRValue(ShapeOne.Brush.Color);
    peGreen := GetGValue(ShapeOne.Brush.Color);
    peBlue := GetBValue(ShapeOne.Brush.Color);
    peFlags := 0
  END;

  RESULT := CreatePalette(pLogPalette(@Palette)^)
END {GetTwoColorPalette};

The Black (0), White(1) and Stripes buttons all call the same method:  ButtonTagFillClick.  The tag field of these buttons define the fill value for each byte in the pf1bit Scanline.

The "g", Arrow and Smiley buttons all call separate methods that define the Scanlines with constants.

pf1bitg.gif (955 bytes) pff1bitArrow.gif (928 bytes) pf1bitSmiley.gif (1028 bytes)

Both the "g" and the "arrow" bitmaps are defined as a CONST ARRAY[0..31, 0..3] OF BYTE. These arrays are transferred to a pByteArray Scanline to create a pf1Bit bitmap.

Here's the constant definition for the Arrow array:

CONST
  // The "arrow" bitmap was adapted from the LaserJet IIP Printer
  // Tech Ref Manual
  Arrow: ARRAY[0..31, 0..3] OF BYTE =
{ 0} ( ($00, $00, $80, $00), {00000000 00000000 10000000 00000000}
{ 1}   ($00, $00, $C0, $00), {00000000 00000000 11000000 00000000}
{ 2}   ($00, $00, $E0, $00), {00000000 00000000 11100000 00000000}
{ 3}   ($00, $00, $F0, $00), {00000000 00000000 11110000 00000000}
{ 4}   ($00, $00, $F8, $00), {00000000 00000000 11111000 00000000}
{ 5}   ($00, $00, $FC, $00), {00000000 00000000 11111100 00000000}
{ 6}   ($00, $00, $FE, $00), {00000000 00000000 11111110 00000000}
{ 7}   ($00, $00, $FF, $00), {00000000 00000000 11111111 00000000}
{ 8}   ($00, $00, $FF, $80), {00000000 00000000 11111111 10000000}
{ 9}   ($FF, $FF, $FF, $C0), {11111111 11111111 11111111 11000000}
{10}   ($FF, $FF, $FF, $E0), {11111111 11111111 11111111 11100000}
{11}   ($FF, $FF, $FF, $F0), {11111111 11111111 11111111 11110000}
{12}   ($FF, $FF, $FF, $F8), {11111111 11111111 11111111 11111000}
{13}   ($FF, $FF, $FF, $FC), {11111111 11111111 11111111 11111100}
{14}   ($FF, $FF, $FF, $FE), {11111111 11111111 11111111 11111110}
{15}   ($FF, $FF, $FF, $FF), {11111111 11111111 11111111 11111111}
{16}   ($FF, $FF, $FF, $FF), {11111111 11111111 11111111 11111111}
{17}   ($FF, $FF, $FF, $FE), {11111111 11111111 11111111 11111110}
{18}   ($FF, $FF, $FF, $FC), {11111111 11111111 11111111 11111100}
{19}   ($FF, $FF, $FF, $F8), {11111111 11111111 11111111 11111000}
{20}   ($FF, $FF, $FF, $F0), {11111111 11111111 11111111 11110000}
{21}   ($FF, $FF, $FF, $E0), {11111111 11111111 11111111 11100000}
{22}   ($FF, $FF, $FF, $C0), {11111111 11111111 11111111 11000000}
{23}   ($00, $00, $FF, $80), {00000000 00000000 11111111 10000000}
{24}   ($00, $00, $FF, $00), {00000000 00000000 11111111 00000000}
{25}   ($00, $00, $FE, $00), {00000000 00000000 11111110 00000000}
{26}   ($00, $00, $FC, $00), {00000000 00000000 11111100 00000000}
{27}   ($00, $00, $F8, $00), {00000000 00000000 11111000 00000000}
{28}   ($00, $00, $F0, $00), {00000000 00000000 11110000 00000000}
{29}   ($00, $00, $E0, $00), {00000000 00000000 11100000 00000000}
{30}   ($00, $00, $C0, $00), {00000000 00000000 11000000 00000000}
{31}   ($00, $00, $80, $00));{00000000 00000000 10000000 00000000}

For the details, look at the source code for ButtonArrowClick, ButtonGClick, and ButtonSmileyClick.  The ButtonRandomClick method fills the Scanlines with random values.  Try pressing this button multiple times.

The Invert button forms the logical "Not" of all the binary pixel data while maintaining the same palette.  The ButtonInvertClick method shows the details:

procedure TFormPf1bit.ButtonInvertClick(Sender: TObject);
  VAR
    Bitmap: TBitmap;
    i: INTEGER;
    j: INTEGER;
    RowIn  : pByteArray;
    RowOut: pByteArray;
begin
  Bitmap := TBitmap.Create;
  TRY
    WITH Bitmap DO
    BEGIN
      Width := ImageBits.Picture.Bitmap.Width;
      Height := ImageBits.Picture.Bitmap.Height;

      // Unclear why this must follow width/height to work correctly.
      // If PixelFormat precedes width/height, bitmap will always be black.
      PixelFormat := pf1bit;

      IF   CheckBoxPalette.Checked
      THEN Bitmap.Palette := GetTwoColorPalette
    END;

    FOR j := 0 TO Bitmap.Height-1 DO
    BEGIN
      RowOut := pByteArray(Bitmap.Scanline[j]);
      RowIn := pByteArray(ImageBits.Picture.Bitmap.Scanline[j]);
      FOR i := 0 TO (Bitmap.Width DIV BitsPerPixel)-1 DO
      BEGIN
        RowOut[i] := NOT RowIn[i]
      END
    END;

    ImageBits.Picture.Graphic := Bitmap
  FINALLY
    Bitmap.Free
  END;
end;

An alternative Invert approach would have been to swap the palette entries.

Conclusions
Single bit bitmaps may be very useful for masks. pf1bit bitmaps can save a considerable amount of memory.


Modified Version by Jean-Pierre Jouandet

Includes ability to rotate pf1bit bitmap by 90, 180 or 270 degrees

ScreenJeanPierreJouandet.jpg (42712 bytes)
Updated 19 March 2000


Keywords
Scanline, PixelFormat, pf1Bit, pByteArray, palette, CreatePalette, TMaxLogPalette, Bitmap Rotation

Download
Delphi 3/4/5 Source and EXE (120 KB): pf1bit.zip
Compiles without any changes in D4 or D5. D3 EXE is 219 KB; D4 EXE is 311 KB; D5 EXE is 321 KB.

Modified version includes 1652-by-2348 pf1bit bitmap (D3/4/5):   JeanPierreJouandetPf1bit.ZIP


Postscript
efg's UseNet Post about converting 256-color bitmap to pf1bit


Updated 30 August 2007


since 1 Nov 1998