Graphics
Cursor Overlay  Lab Report
Example of overlaying a cursor on a bitmap multiple times

Purpose
The purpose of this project is to show how to convert a cursor to a bitmap and overlay its bitmap on another bitmap.

Motivation
Overlaying a cursor's bitmap on another bitmap could be confusing.  Windows goes out of its way to prevent this by not including the mouse cursor in a bitmap placed on the clipboard via a PrntScrn keystroke for the whole Window, or Alt-PrntScrn to capture only the active Window.   The "real" cursor and the "fixed" cursor overlaid onto another bitmap could be confusing if displayed on a screen.  

However, many times in writing documentation that is intended to be printed, or simply read on the screen, the cursor in documentation can be a guide showing a user where to go next.   This project shows how to take one of several "real" cursors and convert the cursor into a bitmap that can be drawn transparently over another bitmap.

Materials and Equipment

Software Requirements
Windows 95/98/2000
Delphi 3/4/5 (to recompile) 
CursorOverlay.EXE
pf24bit BMPs

Hardware Requirements
True color (24 or 32-bit) display.  Fails on many high color (16 bit) displays.

Procedure

  1. From Windows Explorer, double-click on the CursorOverlay.Exe icon to start the program.
  2. Press the Load button.
  3. Select a 24-bit .BMP and press Open.  
  4. If the bitmap is too large or small, select the Stretch checkbox to see the complete bitmap.
  5. Select one of the tool bar cursor buttons.
  6. Click on one or more points on the bitmap.  The cursor's bitmap is overlaid on the image.
  7. Repeat steps 5 and 6 for other cursor pointers, if desired.
  8. Save the bitmap with overlaid cursors by selecting the Save button.

Discussion

Background  information about cursors can be found in these locations:

On a Windows 2000 machine you can find some interesting cursors in the C:\WinNT directory, such as the four I used in this project:

On a Windows 98 machine, you might try the C:\Windows\Cursors directory, or search your machine for other .CUR files that you may have.

To use these four cursors shown above in this application, they are first put into a .RES resource file. Using an ASCII editor (such as WordPad), create the Cursors.RC file:  

Cursors.RC
LARGEARROW    CURSOR "arrow_l.cur"
INVERTEDARROW CURSOR "arrow_il.cur"
MOVE3D        CURSOR "3dgmove.cur"
ARROW3D       CURSOR "3dgarro.cur"

Note:  the resource names to the left above must be in uppercase only.

The GenRes.BAT file can be used to run the resource compiler, BRCC32, with the Cursors.RC input file:

GenRes.BAT
"C:\Program Files\Borland\Delphi5\Bin\BRCC32.EXE" Cursors.RC

From Windows Explorer, double click on the GenRes.Bat file icon to run BRCC32 and create the Cursors.RES output file.  Note:  NEVER use a filename with an .RC file that matches a Delphi unit name in your project.

Since writing the Cursors.RC and GenRes.BAT files above, I saw a neat trick in Neil Rubenking's Registry Detective utility in PC Magazine utility in which the .RC and .BAT files are combined in a single .BAT file.  Neil's approach would be this:

ResMak.BAT
/*
@ECHO OFF
REM Using BAT as .RC file is trick from Neil Rubenking in PC Magazine
"C:\Program Files\Borland\Delphi5\Bin\BRCC32.EXE" -fo Cursors.RES ResMake.bat
GOTO End
*/

LARGEARROW    CURSOR "arrow_l.cur"
INVERTEDARROW CURSOR "arrow_il.cur"
MOVE3D        CURSOR "3dgmove.cur"
ARROW3D       CURSOR "3dgarro.cur"

/*
:End */

To use the cursor resources in a Delphi application, add the {$R CURSORS.RES} statement to the implementation section, and define numeric constants for the resources:

ScreenCursorOverlay.Pas
...
implementation
{$R *.DFM}
{$R CURSORS.RES}
  CONST
    crLargeArrow         = 1;
    crInvertedLargeArrow = 2;
    cr3DMove             = 3;
    cr3DArrow            = 4;
...

The above method to include cursors in a .RES file works fine in Delphi 3 and 4.  But in Delphi 5 the inclusion of a .RC resource file is much easier.  In Delphi 5 create the .RC file, like the Cursors.RC shown above.  In the Delphi 5 IDE select the "+" (Add to project) speedbutton, select files of type "Resource files (*.rc)", and then select the .RC file.  In Delphi 5 there is no need to add the resource include statement, like the statement {$R CURSORS.RES} shown above.  [Thanks to Dan Strandberg for this tip about resource files in Delphi 5.  efg, 19 Feb 2001.]

Use LoadCursor in the FormCreate to extract the cursors from the resource file and associate them with the constants defined above.

procedure TFormCursorOverlay.FormCreate(Sender: TObject);
begin
  Screen.Cursors[crLargeArrow]        := LoadCursor(hInstance, 'LARGEARROW');
  Screen.Cursors[crInvertedLargeArrow]:= LoadCursor(hInstance, 'INVERTEDARROW');
  Screen.Cursors[cr3dMove]            := LoadCursor(hInstance, 'MOVE3D');
  Screen.Cursors[cr3DArrow]           := LoadCursor(hInstance, 'ARROW3D');
  AssignSpeedButton(0, SpeedButton1);
  AssignSpeedButton(1, SpeedButton2);
  AssignSpeedButton(2, SpeedButton3);
  AssignSpeedButton(3, SpeedButton4);
  AssignSpeedButton(4, SpeedButton5);
  CursorBitmap := TBitmap.Create;
  UpdateCursor(0);
  DisplayBitmap := TBitmap.Create;
end;       

AssignSpeedButton extracts a bitmap from each cursor and displays the bitmap in the specified TSpeedButton.  Notice the constant 0 refers to the "default" Windows' cursor in the AssignSpeedButton call:

PROCEDURE TFormCursorOverlay.AssignSpeedButton(CONST index:  INTEGER; 
                                               CONST SpeedButton:  TSpeedButton);
  VAR
    Bitmap :  TBitmap;
    hCursor:  THandle;
BEGIN
  Bitmap := TBitmap.Create;
  TRY
    Bitmap.Width := 32;
    Bitmap.Height := 32;
    Bitmap.PixelFormat := pf24bit;   // avoid working with palettes
    hCursor := Screen.Cursors[index];
    DrawIconEx(Bitmap.Canvas.Handle, 0,0, hCursor, 32,32, 0,0, DI_NORMAL);
    // Flood fill cursor from the "outside" so a white cursor will
    // appear white.  Assume right-top position will work for floodfilling
    // the whole area.  Assume RGB(250,250,250) will work as transparency color.
    Bitmap.Canvas.Brush.Color := RGB(250,250,250);
    Bitmap.Canvas.FloodFill(31,0, clWhite, fsSurface);
    // Kludge fix for 3DMove cursor since upper left corner is "blocked"
    // during flood fill.
    Bitmap.Canvas.FloodFill(0,0, clWhite, fsSurface);
    SpeedButton.Glyph := Bitmap;
    SpeedButton.Tag   := index;      // could be set in design mode
  FINALLY
    Bitmap.Free
  END
END {AssignSpeedButton};         

Some of the details used in AssignSpeedButton above are explained below since some of the same operations are used in UpdateCursor, which is explained next.

The FormCreate and the SpeedButtonCursorClick methods both result in a call to UpdateCursor.  This procedure creates a new CursorBitmap, which is later used to draw the cursor transparently onto the bitmap.  The CursorBitmap is created using the DrawIconEx API call.   The GetIconInfo API call is used to get the position of the cursor's "hot spot," which is an adjustment that is needed before overlaying the cursor's bitmap on the desired bitmap.

PROCEDURE TFormCursorOverlay.UpdateCursor(CONST index:  INTEGER);
  VAR
    Bitmap  :  TBitmap;
    hCursor :  THandle;
    IconInfo:  TIconInfo;
BEGIN
  CursorBitmap.Free;
  // Convert existing cursor to bitmap and display
  CursorBitmap := TBitmap.Create;
  CursorBitmap.Width := 32;
  CursorBitmap.Height := 32;
  CursorBitmap.PixelFormat := pf24bit;  // avoid working with palettes
  hCursor := Screen.Cursors[index];
  GetIconInfo(hCursor, IconInfo);
  TRY
    ...
    DrawIconEx(CursorBitmap.Canvas.Handle, 0,0, hCursor, 32,32, 0,0, DI_NORMAL);
    ImageCursor.Picture.Graphic := CursorBitmap;
    // Flood fill cursor from the "outside" so a white cursor will
    // appear white.  Assume right-top position will work for floodfilling
    // the whole area.  Assume RGB(250,250,250) will work as transparency color.
    CursorBitmap.Canvas.Brush.Color := RGB(250,250,250);
    CursorBitmap.Canvas.FloodFill(31,0, clWhite, fsSurface);
    // Kludge fix for 3DMove cursor since upper left corner is "blocked"
    // during flood fill.
    CursorBitmap.Canvas.FloodFill(0,0, clWhite, fsSurface);
    CursorBitmap.TransparentMode := tmFixed;
    CursorBitmap.TransparentColor := RGB(250,250,250);
    CursorBitmap.Transparent := TRUE;
    xOffset := IconInfo.xHotSpot;
    yOffset := IconInfo.yHotSpot;
    LabelHotSpot.Caption := 'Hot Spot = ' +
                            '(' + IntToStr(xOffset) + ', ' +
                                  IntToStr(xOffset) + ')';
  FINALLY
    DeleteObject(IconInfo.hbmMask);
    DeleteObject(IconInfo.hbmColor)
  END
END {UpdateCursor};

After the CursorBitmap is drawn with DrawIconEx, FloodFill is used to fill the "outside" of the cursor with a color that is not likely to be in the bitmap.  This unlikely color, RGB(250,250,250) in this case, is set as the transparency color by assignments to TransparentColor and TransparentMode.  Transparency only "works" with a bitmap after the Transparent property is set True, and the bitmap is drawn onto a canvas.  

The omission above after the first TRY statement is shown next.  The GetIconInfo call returns the IconInfo in a record.  Two of the fields in that record are handles to the "and" mask and the "or" bitmap associated with the cursor.  Given these handles, the following shows how to convert these into a TBitmap for display in a TImage under Windows 2000.  Note the use of the Bitmap.Dormant statement, which is a bit unusual.  Without this Dormant statement, the DeleteObject statements in the FINALLY clause would have released the bitmaps of interest and they would not be displayed.

  GetIconInfo(hCursor, IconInfo);
  TRY
    // Extract "AND" mask for cursor
    Bitmap := TBitmap.Create;
    TRY
      Bitmap.Handle := IconInfo.hbmMask;
      // "Dormant" creates a memory bitmap image in order to
      // release the bitmap handle
      Bitmap.Dormant;
      ImageAND.Picture.Graphic := Bitmap
    FINALLY
      Bitmap.Free
    END;
    // Extract "OR" color bitmap for cursor
    Bitmap := TBitmap.Create;
    TRY
      Bitmap.Handle := IconInfo.hbmColor;
      Bitmap.Dormant;
      ImageOR.Picture.Graphic := Bitmap
    FINALLY
      Bitmap.Free
    END;
    ...
  FINALLY
    // If "Dormant" not used above, the bitmaps were 
    // lost with these deletes.
    DeleteObject(IconInfo.hbmMask);
    DeleteObject(IconInfo.hbmColor)
  END                                 

The code above works fine on a Windows 2000 machine, however there is something peculiar about GetIconInfo on Windows 95/98 machines.  The speedbuttons and the overlayed bitmaps appear with unexplained backgrounds with the above code in Windows 95/98.  

The only reason GetIconInfo is needed is to get the xOffset, yOffset "hotspot" values so the overlaid cursor appears in exactly the correct position.  A side effect of GetIconInfo is the IconInfo information, which must be handled correctly to avoid a resource leak.  The DeleteObject calls are very necessary to avoid resource leaks with GetIconInfo.

The code below works in Windows 95/98/2000 but I cannot explain why it is needed instead of the code shown above that only works in Windows 2000.  The hbmMask and hbmColor bitmap handles only seem to work correctly if assigned to a temporary bitmap and used to create a copy of the cursor bitmaps.  These bitmaps are not used in this program but are shown for informational purposes.  [Thanks to Dan Strandberg for bringing this problem to my attention.  efg, 19 Feb 2001.]  

  ...
  GetIconInfo(hCursor, IconInfo);
  TRY
    // Extract "AND" mask for cursor
    Bitmap := TBitmap.Create;
    TRY
      Bitmap.Width  := 32;
      Bitmap.Height := 32;
      Bitmap.Monochrome := TRUE;
      Temp := TBitmap.Create;
      TRY
        Temp.Handle := IconInfo.hbmMask;
        Bitmap.Canvas.Draw(0,0, Temp)
      FINALLY
        Temp.Free
      END;
      ImageAND.Picture.Graphic := Bitmap;
    FINALLY
      Bitmap.Free
    END;
    // Extract "OR" color bitmap for cursor
    Bitmap := TBitmap.Create;
    TRY
      Bitmap.Width  := 32;
      Bitmap.Height := 32;
      Temp := TBitmap.Create;
      TRY
        Temp.Handle := IconInfo.hbmColor;
        Bitmap.Canvas.Draw(0,0, Temp);
      FINALLY
        Temp.Free
      END;
      ImageOR.Picture.Graphic := Bitmap;
    FINALLY
      Bitmap.Free
    END;
   ...
  FINALLY
    DeleteObject(IconInfo.hbmMask);
    DeleteObject(IconInfo.hbmColor)
  END                                                             

In any ImageMouseDown event once an image is loaded, the CusorBitmap is drawn with transparency on top of the bitmap using the Canvas.Draw method.  Note some coordinate adjustments (rescaling) are needed in case the bitmap is stretched to fit in the TImage.  

procedure TFormCursorOverlay.ImageMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  VAR
    xActual:  Integer;
    yActual:  Integer;
begin
  IF   CheckBoxStretch.Checked
  THEN BEGIN
    xActual := MulDiv(X, DisplayBitmap.Width,  Image.Width);
    yActual := MulDiv(Y, DisplayBitmap.Height, Image.Height)
  END
  ELSE BEGIN
    xActual := X;
    yActual := Y
  END;
  // Adjust for Hot Spot not at same point
  xActual := xActual - xOffset;
  yActual := yActual - yOffset;
  DisplayBitmap.Canvas.Draw(xActual, yActual, CursorBitmap);
  Image.Picture.Graphic := DisplayBitmap
end;

Normally only a single cursor is overlaid onto a screen's image for documentation purpose.  A series of cursor overlays could be used in a dynamic display of a mouse trail.

The technique described above could be used with ICOs and BMPs to overlay those types of graphics on another bitmap.  An example of converting a TIcon to a TBitmap can be found here.

When you run CursorOverlay on older hardware with 16-bit color displays or less, transparency does not work correctly as shown below:

Problems may occur with a high color display mode

Note that the first two cursors, which are normally white, do not appear correctly in the speedbuttons above in a 16-bit display mode.  The overlays of the cursors on the image at the bottom do not have correct transparency.  In 16-bit color mode under Windows 2000 I do not see the above problem, but it did occur on three out of three Windows 95/98 machines in 16-bit display mode.

Alternative Approach.  Milen Boev in a UseNet Post wanted to take a screen capture and automatically add the current cursor to the captured bitmap.  Michael Winter provided a solution and in a UseNet Post explained how this might be done.  Based on that posting, I created a ShowCursor example to verify the technique worked, and to find out what happened with animated cursors (the first frame was captured).

Conclusions
A cursor overlaid on a bitmap may provide a better way to create Windows documentation when step-by-step mouse actions are described and shown in the accompanying graphics.


Keywords: 
Cursors, .CUR files, CUR-to-BMP conversion, hot spot,.RC and .RES resource files, resource compiler BRCC32,  LoadCursor, GetIconInfo, DrawIconEx, Transparency, TransparentMode, TransparentColor, FloodFill, Dormant, "AND" mask, "OR" color bitmap, Windows 95/98/2000, Delphi 3/4/5

Download
Delphi 3/4/5 Source and EXE (146 KB):  CursorOverlay.ZIP


Updated 14 Jun 2009
Since 13 Feb 2001