                  Dragging Hotspots to Resize an Image


                           by Michael Bertrand



  Visual Basic allows programmers to resize images and other
controls at design time by dragging eight little black boxes at
the corners and middle edges of the rectangle defining the
control. The mouse cursor changes to a resizing shape as the
mouse passes over these little black boxes, or hotspots,
signalling the availability of resizing. Depressing the mouse
button and dragging from a hotspot leads to dragging a dotted
rectangle, moving the corner or edge defined by the hotspot, but
leaving the opposite corner or edge stationary. Then when the
mouse button is released, the image adjusts to the final shape of
the resized rectangle. In fact, many Windows applications employ
hotspot dragging to resize an image. This paper explains how to
implement hotspot dragging in a Visual Basic program, including
discussion of a small demonstration program, HotSpot.


                    The Picture Control and Metafiles


  Visual Basic uses a picture control to hold images, either
metafiles or bitmaps. Metafiles are the native Windows vector
format and usually have the extension WMF. Vector images scale
well to different sizes because they consist of a series of line
and polygon commands, including fill color, that are redrawn at
different sizes. Bitmaps, by contrast, store color codes for an
entire rectangle of pixels. Compressed bitmaps lose information,
while enlarged bitmaps appear jagged because of added pixels.
Windows bitmaps have extension BMP, and can be created and saved
in PC Paintbrush. Program HotSpot works with a metafile
representation of the Canadian maple leaf flag which scales
nicely as the user resizes it. A picture control holds the flag,
though it is not used to display it (the Visible property is set
to False, making the control invisible). Rather Visual Basic's
PaintPicture method is used to draw the image into the current
bounding box, which is reset each time the user resizes:

PaintPicture picFlag.Picture, Bbox.left, Bbox.top, Bbox.width, Bbox.height

The bounding box, Bbox, is a key global variable defining the
flag's size and location, which changes as the user scales and
moves the flag. The PaintPicture call is in the Form_Paint()
event handler, insuring that the flag is always shown even after
being obscured by other windows. 


                Hotspot Identification and Sizing Cursors


  Given a bounding box, the eight little black boxes constituting
the hot spots are well defined, and it is possible both to draw
the hot spots and to determine during mouse events whether the
mouse is in one of them. Actually, the bounding box itself is a
ninth hot spot, dragging which rigidly translates the bounding
box. Each hot spot corresponds to a different kind of drag, as
their constant names suggest:

Const TOP_LEFT  = 0
Const TOP_MID   = 1
Const TOP_RT    = 2
Const MID_RT    = 3
Const BOT_RT    = 4
Const BOT_MID   = 5
Const BOT_LEFT  = 6
Const MID_LEFT  = 7
Const TRANSLATE = 8
Const NO_HIT    = 9

  The top left hotspot, index 0, corresponds to a TOP_LEFT drag,
in which the top left corner of the bounding box moves, but the
opposite bottom right corner remains anchored. The TOP_MID drag
moves the top edge only, leaving the bottom edge unmoved. And so
too for the other corner and middle edge drags. TRANSLATE is
different, associated with the entire bounding box as a hotspot
triggering translation.

  Utility function PtInHotSpot() senses whether a given point is
within one of the hot spots determined by a bounding box and
returns a hotspot index from the list above: if the given point
is in the top left hotspot, PtInHotSpot() returns TOP_LEFT, and
so on for the other hotspots, with NO_HIT returned if the point
is in no hotspot. PtInHotSpot() implements a 'quick reject'
algorithm by first testing if the given point is outside the
'extended bounding box', a rectangle a bit larger than the
bounding box so as to include all the hot spots. The idea is that
most test points will lie outside this extended bounding box, so
the function returns NO_HIT right away in this case without
further calculation. If the test point is inside the extended
bounding box, it is likely to be in the bounding box proper, the
TRANSLATE hotspot; if so, PtInHotSpot() returns TRANSLATE without
further calculation. The great majority of test points are in one
of these two regions, leading to a quick determination. Points
surviving these tests lie in the thin strip between the bounding
box and the extended bounding box which contains the eight little
hotspots, so the function then checks them in turn.

  Each hotspot is also associated with a cursor, TOP_LEFT with
the 'NorthWest-SouthEast Sizing' cursor shown here, for example,
the same one used in Windows itself when resizing a window by
dragging its top left corner. The sizing cursors are available in
Visual Basic by setting the form's MousePointer property to the
desired cursor. Array Cursor() holds the MousePointer cursor
indices associated with each hotspot, so this line in the
MouseMove event handler sets the mouse cursor appropriately as it
moves over hotspots:

             MousePointer = Cursor(PtInHotSpot(Bbox, x, y))


              Restricting the Cursor and Managing the Drag


  PointInHotSpot() also appears in the MouseDown event handler,
where it senses if the mouse is in a hotspot when the mouse was
depressed:

                      hs = PtInHotSpot(Bbox, x, y)

Global variable hs holds the type of drag (including NO_HIT if
the cursor was not in a hotspot, so there is no drag). Code in
MouseDown restricts the cursor, then code in MouseMove manages
the drag, in each case adapting to the kind of drag as indicated
by hs.

  By way of example, assume that a TOP_LEFT drag has commenced,
as pictured in the figure. Right away in MouseDown HotSpot
restricts the cursor to a rectangle determined by the top left of
the entire client area and the bottom right of the bounding box.
This is to keep the bounding box on the screen and also to keep
the corner being dragged above and to the left of the anchored
corner opposite. This second restriction obviates messy special
cases arising from dragging the bounding box's left edge to the
right of its right edge (or its top below its bottom), no real
sacrifice because the user can achieve these effects by different
kinds of drags. HotSpot calls the Windows API call ClipCursor()
to restrict the cursor, first converting to 'screen coordinates'
as ClipCursor() requires. Screen coordinates are measured in
pixels from the top left of the entire screen, the conversion
effected by another Windows API call, ClientToScreen(). Setting
the form's ScaleMode to pixels facilitates this work, since
Windows API calls always deal in pixels.

  On an ensuing MouseMove, HotSpot erases the dotted line on
screen around the old bounding box, then updates the bounding box
at the current mouse position and redraws it. Setting DrawMode to
vbNotXorPen results in the box's being erased when it is redrawn
at its original location. Here is the updating logic:

        Bbox.width = Bbox.width + (Bbox.left - x)
        Bbox.height = Bbox.height + (Bbox.top - y)
        Bbox.left = x
        Bbox.top = y

(Bbox.left - x) is the change in the box's width relative to its
previous value, where x is the current mouse position, so adding
this amount to Bbox.width produces the new width. The height is
calculated similarly, while the box's left and top follow the
mouse.

  Similar principles apply to the other types of drag, each of
which has its own section in both the MouseDown and the MouseMove
event handlers. Each has a different cursor clip rectangle and
requires different adjustments to the bounding box as it is being
dragged. MouseUp finalizes the drag by calling Form_Paint(),
which calls PaintPicture to draw the image in its new bounding
box.


                              The Next Step


  Additional features might include pasting a metafile from the
clipboard, easy to implement considering Visual Basic's strong
clipboard support. This would enable HotSpot to show and resize
any metafile copied to the clipboard by a Windows application.
CAD and Draw programs typically copy metafiles to the clipboard,
as do spreadsheets for their graphs, all examples of vector
graphics. Nor would reading a metafile from disk be hard using
the File Open Common Dialog Box to get the file name from the
user.
                                                                
-------------------------------------------

Mike teaches Mathematics and programming at Madison Area
Technical College, Madison, WI 53704.
