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.
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.
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 |