Home
Archives
About us...
Advertising
Contacts
Site Map
 

ruby in steel

 

A DELPHI SCREENGRAB UTILITY #2

Last month we grabbed the entire screen. This month we find a simple way of marking and capturing a rectangular area of the screen
by Huw Collingbourne

Requirements:
Borland's Delphi 7 or above

 

Download The Source Code:
delphi2src.zip

 

See also Part One - 'A Simple Way To Grab A Screen'
and Part Three - 'Hotkeys and Saving Images'

Part Two :: How To Grab A Selected Area

The screen capture utility which I programmed last month was capable of taking snapshots of the entire screen or of a selected window with or without its bounding frame. Most of this was accomplished using Windows API functions. Often, however, it is useful to be able to grab a selected portion of the screen by marking off a rectangle using the mouse. There is no API function that does this so I have no alternative but to program this feature from scratch.


By default we use a one pixel thick blue Pen with the psDashDotDot style for our selection rectangle. The pmNotXor mode helps us to invert the rectangle after use

The first problem that has to be solved is how to ‘freeze’ the screen so that the mouse can move over it and freely select a portion of the screen image. I must admit that, at first, I couldn’t think of any simple way in which this could be done. After all, if you start clicking and dragging your mouse over different windows, you will end up bringing those windows to the front and performing mouse actions within each individual window rather than drawing selection a rectangle over the screen. Moreover, even if you find a way of drawing rectangles over the screen, you will then face the problem of getting the various windows to repaint themselves afterwards. In short, messing about with the active screen in this way would be both complicated and error prone.

My solution to this problem is to start by taking a screen grab of the entire Windows desktop. When I then go on to make a selection with the mouse, I will actually be drawing a selection box on my screengrab rather than over the Windows desktop itself.

Now you might think that this is a cheat. After all, when you use a commercial screen grabbing tool, you can grab a selection from the ‘active’ screen itself, can’t you? Well, maybe. To be honest, I don’t know whether or not this is the case since I have no special knowledge of the inner workings of commercial screen grabbers. All I know for sure is that they make it appear that you are grabbing a selection from the active screen. Appearances, of course, can be deceptive. In my own application, I shall satisfy myself with fooling the user into thinking that a selection can be made from the active screen. While this is not literally true, if the user is deceived, that’s fine by me.

Taking A Snapshot

Load up the ScreenGrab4.bdsproj project. If you are using Delphi 2005, make sure that you load it as a Delphi For Win32 project. This now includes a ‘Grab Selected Area’ menu item. When clicked, this runs the GrabSelectedAreaClick() method. If you followed last month’s project, much of the code here will look familiar. It simply hides the form to show the underlying screen, pausing briefly to make sure that the form has enough time to be hidden before commencing the capture. Then it calls my Grab() method to capture the entire screen and save it as a bitmap on the image1 component on the form. It calls my MaximizeForm() method to display image in full screen. Finally it brings the form in front of any other windows by setting its FormStyle property to fsStayOnTop and then it redisplays the form with the Form1.Show() method.


To capture an area of screen you can use the Grab menu or press the CTRL+A hotkey. The form will then seem to disappear so that you can select an area

The end result is that a grab of the entire screen is taken and this grab is displayed in full screen. This provides us with the ‘frozen’ picture of the screen from which a selection can be made. Of course, as it is my intention to fool the user into thinking that the selection is going to be made from the active screen, I don’t want it to be obvious that I have made this initial screen grab. The only real giveaway is that, by default, there will be a brief screen flicker as the form is repainted. Fortunately, I can reduce this flicker by making the form ‘double buffered’. I have done this with a simple property assignment in FormCreate(). For an explanation of this, refer to the section below: Double Buffering.

Selecting An Area

Having obtained an image of the full screen, I now have to find some way for the user to select a rectangular area. The actual selection process itself is quite simple. I just save the initial X and Y coordinates in response to the MouseDown event when the left mouse button is pressed and the final X and Y coordinates in response to the MouseUp event when the button is released. The X and Y coordinates are provided by arguments that are sent to the two event-handlers. In this project, I save the initial coordinates in the variables SelX and SelY and the final coordinates in SelX1 and SelY1. To see how these coordinates are used to save the bitmap selection, find the Image1MouseUp() method.

First of all this code checks to see if the final coordinates are greater or smaller than the initial coordinates. Although they are most likely to be greater, we cannot assume that the user has drawn a rectangle from top-left to bottom right. The rectangle may have been drawn from right to left or bottom to top in which case I will have to swap the values of the selection coordinates. This is because I shall shortly be using these coordinates to create TRect objects. I shall need to supply the top-left corner in the first two arguments. If the user has drawn a selection from bottom-right to top-left, these arguments will be provided by the final coordinates, otherwise they will be provided by the initial coordinates.

The SourceRect object is initialised to the selection rectangle. The DestRect object defines a rectangle into which the selected area will be copied in Image1. The copying itself is performed by the CopyRect() method of the image’s Canvas object:

Image1.Canvas.CopyRect(DestRect,Image1.Canvas,SourceRect);

Finally, I resize the Image around the grabbed selection. This is all reasonably straightforward. However, there is also one other tricky little feature that I have had to code. In order to make a selection, the user needs to have some visual feedback showing precisely what has been selected. Usually this is done by drawing a dotted box around the selected area and stretching this box as the user moves the mouse.

Drawing The Selection Box

The selection box is coded in three mouse event methods of Image1. It all starts in Image1MouseDown(). Assuming the variable DoSelect is set to True, which happens when the user initiates a selection grab, the Pen of Image1.Canvas is set to one of the available styles - here psDashDotDot. This style defaults to 1 pixel in width, so setting Pen.Width to any value other than 1 has no effect. If you want a broader line, you can set Pen.Style to psSolid. The Pen.Mode property is set to pmNotXor. The Pen.Mode property controls how the Pen interacts with the graphic over which it draws. Setting it to pmNotXor causes the colours of the pen and its background to be combined. This is useful as it gives us the ability to draw the selection rectangle back over itself in order to inverse the colours and, in effect, remove the rectangle from view. We need to do this when we drag out the selection rectangle. If we failed to invert the previous rectangles, we would end up drawing multiple rectangles on screen. We shall look at the code for this operation presently.


Here our screen grabber is marking off a rectangular area to be grabbed. We are using a thick, solid pen here so that selection rectangle can be seen clearly

The Brush.Style property is set to bsClear so that the contents of the selection rectangle are displayed as normal. If you fail to set this property, the colours of the contents will be inverted. Finally we set the Pen.Color property to clBlue. This is the colour of the dotted line around the selection rectangle. You could, in fact, achieve the same effect by setting Pen.Mode to pmXor and Pen.Color to clYellow. This is because pmXor is the inverse of pmNotXor and clYellow is the inverse of clBlue.

The action now transfers to the Image1MouseMove() method which executes repeatedly as the user moves the mouse pointer. This method simply draws a new rectangle over the current selection rectangle. Thanks to the pmNotXor (or pmXor) pen mode this inverts the colours of the rectangle. As the existing colours are themselves inverted, this has the effort of restoring the background colours, causing the rectangle to disappear. Now, all I have to do is draw another rectangle using the new X and Y mouse coordinates.


By tinkering with the properties of the Pen we can significantly alter the appearance of selection rectangles. Here, for instance, it is shown as a red dotted line

So, with this combination of simple techniques and a bit of deceptive sleight of hand, I have been able to fool the user into thinking that my screen grabber can capture a rectangular section of the active desktop. There are a few more things I need to add to complete this program. In particular, I need to capture the cursor and save the screen grabs to disk. For information on capturing the cursor, see the section below: ‘Grabbing The Mouse Pointer’. Saving the grabs to disk is one of the features I shall be adding next month.


GRABBING THE MOUSE POINTER

Capturing the mouse pointer is a case of ‘anything Icon do’…

By default, when we make a copy of the screen in the Form1.Grab() method, the mouse pointer is not included. In some circumstances - say if you are grabbing screens to be used in a software tutorial - it is very useful to be able to show the mouse pointer. In order to do this, I have had to do some extra coding which you will find in the DrawCursor() method. This takes three arguments, the captured bitmap and the X and Y coordinates of the mouse pointer.


Here we have grabbed a non-standard cursor (the hand) and grabbed it again when we grabbed a picture of the original grab!

The DrawCursor() method obtains information on the current mouse pointer or ‘cursor’ using the GetCursorInfo() function which is supplied by the Windows API but (Note: in some versions of the Delphi help system this is not documented at all; at any rate, it is not documented in Pascal code). This function returns a cursorinfo structure which is defined (in the Windows SDK) as follows:

typedef struct {
    DWORD cbSize;
    DWORD flags;
    HCURSOR hCursor;
    POINT ptScreenPos;
} CURSORINFO, *PCURSORINFO, *LPCURSORINFO;

I verify that the cursor is visible by testing if the flags field equals the predefined constant, CURSOR_SHOWING. If so, I obtain the handle to the cursor, hCursor, and initialise an Icon structure from this, using the GetIconInfo() function. The GetIconInfo() function retrieves the handle to either an icon or a cursor in its first argument and an IconInfo structure containing X and Y coordinates and other fields as its second argument. I am now ready to draw the current image for a cursor into a device context using the DrawIcon() or DrawIconEx() functions. For our purposes, the simpler DrawIcon() function suffices.

The DrawIcon() function takes four arguments: the handle to the device context into which the icon will be drawn, the X and Y coordinates of the icon’s top-left corner and the handle to the icon. You can examine my code to see how I have calculated the coordinates and drawn the cursor specified by Icon.Handle in the appropriate position on the captured bitmap.


DOUBLE BUFFERING

To avoid the curse of screen flicker you need to run up against the buffers

In the FormCreate() method you will see that I have set the form’s DoubleBuffered property to True. To appreciate the effect of this, comment out this line, run the program and press CTRL+A to grab an area of the screen. You will see a noticeable flicker at the outset. This is because you are now grabbing the entire screen at the start of this process. The flicker is caused as the image is displayed and the form is repainted. Double buffering describes a method of avoiding this flicker by ‘painting’ the bitmap into a non-visible block of memory and then doing a fast copy of this into the visible image. In the past, double-buffering had to be implemented by the programmer. However, it is now simpler to let Delphi take care of the messy details by setting the form’s DoubleBuffered property. When this is done, Delphi implements the double buffering when it handles the Windows Paint message. It paints into a bitmap in memory and then does a ‘bit block transfer’ with the BitBlt() function to copy and display the bitmap on screen so rapidly that no flicker can be seen. 

Next month I shall be finishing off our screen grab utility next month by adding a file saving capability plus a few extra goodies.

 

June 2005

 


Home | Archives | Contacts

Copyright © 2006 Dark Neon Ltd. :: not to be reproduced without permission