Interactive gestures - how to use them for viewing images

Many of our commercial apps work with images - as a part of studying materials or for documentation purposes. Using TImage component is a very comfortable and logical way for displaying and manipulating images, like panning, zooming etc.

There are some limitations as for a size of the image on mobile device displaying units. Thanks to hundreds of various models, we never know in advance how large or small screen will be used for viewing the images; and to be frank, the simplest way (it means solely displaying the picture within TImage) does not have to be sufficient in cases of small screen units. So using interactive gestures seems to be a good solution of the issue, namely Zoom and Pan.

I skimmed over the documentation and understood the mechanism of gestures, especially interactive ones, and how to implement them. For my purposes I checked Zoom and Pan in Touch - Interactive Gestures of the main form and implemented OnGesture event. Manipulating the image became more interactive and I tried out zooming and panning it. Meanwhile zooming the image worked as expected, panning did not provide a satisfactory outcome because it was possible only in cases when the image size was smaller than a parental container (TPanel). It was extremely uncomfortable and I expected behaviour like photo app on my smartphone has - i.e. panning the zoomed image even in cases when zoomed image's dimensions are greater than available area of parental container (out of the bounds, simply sadid).

RAD Studio inline help system is equipped well with many code samples and snippets. However, some of them are erroneous or incomplete. This was also the case when I used the code snippet focused on using Interactive Gestures. Original code for Panning (igiPan) is here"


if EventInfo.GestureID = igiPan then
 begin
  LObj := Self.ObjectAtPoint(ClientToScreen(EventInfo.Location));
  if (LObj is TImage) then if (LObj as TImage).Tag<>-1 then
  begin
   (LObj as TImage).Align:=TAlignLayOut.None;
   if not(TInteractiveGestureFlag.gfBegin in EventInfo.Flags) then
   begin
    LImage := TImage(LObj.GetObject);
    LImage.Position.X := LImage.Position.X + (EventInfo.Location.X - FLastPosition.X);
    if LImage.Position.X < 0 then
    LImage.Position.X := 0;
    if LImage.Position.X > (paDetailImg.Width - LImage.Width) then
    LImage.Position.X := paDetailImg.Width - LImage.Width;
    LImage.Position.Y := LImage.Position.Y + (EventInfo.Location.Y - FLastPosition.Y);
    if LImage.Position.Y < 0 then
    LImage.Position.Y := 0;
    if LImage.Position.Y > (paDetailImg.Height - LImage.Height) then
    LImage.Position.Y := paDetailImg.Height - LImage.Height;
   end;
   FLastPosition := EventInfo.Location;
  end;
 end;

However, the app using this code suffers from impossible panning when zoomed image is larger than its parental area. I found an extremely simple but ellegant solution - I commented out the code blocks responsible for checking image position (X and Y coordinates):


if EventInfo.GestureID = igiPan then
 begin
  LObj := Self.ObjectAtPoint(ClientToScreen(EventInfo.Location));
  if (LObj is TImage) then if (LObj as TImage).Tag<>-1 then
  begin
   (LObj as TImage).Align:=TAlignLayOut.None;
   if not(TInteractiveGestureFlag.gfBegin in EventInfo.Flags) then
   begin
    LImage := TImage(LObj.GetObject);
    LImage.Position.X := LImage.Position.X + (EventInfo.Location.X - FLastPosition.X);
    {if LImage.Position.X < 0 then
    LImage.Position.X := 0;
    if LImage.Position.X > (paDetailImg.Width - LImage.Width) then
    LImage.Position.X := paDetailImg.Width - LImage.Width;}
    LImage.Position.Y := LImage.Position.Y + (EventInfo.Location.Y - FLastPosition.Y);
    {if LImage.Position.Y < 0 then
    LImage.Position.Y := 0;
    if LImage.Position.Y > (paDetailImg.Height - LImage.Height) then
    LImage.Position.Y := paDetailImg.Height - LImage.Height;}
   end;
   FLastPosition := EventInfo.Location;
  end;
 end; 

This modification allows practically unlimited panning and zooming the image. The second issue I faced was working with the image Align property. I needed displaying my images using TAlignLayout.Client initially but zooming and panning is not possible when Align property of the TImage is set to Client. So I added a simple line of code into OnGesture event:


(LObj as TImage).Align:=TAlignLayOut.None; 

And vice versa, when user finishes viewing and manipulating image, image layout is set back to TAlignLayOut.Client. Complete code for OnGesture event taking panning and zooming into consideration is here:


procedure TMainForm.FormGesture(Sender: TObject;
  const EventInfo: TGestureEventInfo; var Handled: Boolean);
var
 LObj: IControl;
 LImage: TImage;
 LImageCenter: TPointF;
begin
 if EventInfo.GestureID = igiZoom then
 begin
  LObj := Self.ObjectAtPoint(ClientToScreen(EventInfo.Location));
  if (LObj is TImage) then if (LObj as TImage).Tag<>-1 then
  begin
   (LObj as TImage).Align:=TAlignLayOut.None;
   if (not(TInteractiveGestureFlag.gfBegin in EventInfo.Flags)) and
   (not(TInteractiveGestureFlag.gfEnd in EventInfo.Flags)) then
    begin
     LImage := TImage(LObj.GetObject);
     LImageCenter := LImage.Position.Point + PointF(LImage.Width / 2,
     LImage.Height / 2);
     LImage.Width := LImage.Width + (EventInfo.Distance - FLastDistance);
     LImage.Height := LImage.Height + (EventInfo.Distance - FLastDistance);
     LImage.Position.X := LImageCenter.X - LImage.Width / 2;
     LImage.Position.Y := LImageCenter.Y - LImage.Height / 2;
    end;
    FLastDistance := EventInfo.Distance;
  end;
 end else

 if EventInfo.GestureID = igiPan then
 begin
  LObj := Self.ObjectAtPoint(ClientToScreen(EventInfo.Location));
  if (LObj is TImage) then if (LObj as TImage).Tag<>-1 then
  begin
   (LObj as TImage).Align:=TAlignLayOut.None;
   if not(TInteractiveGestureFlag.gfBegin in EventInfo.Flags) then
   begin
    LImage := TImage(LObj.GetObject);
    LImage.Position.X := LImage.Position.X + (EventInfo.Location.X - FLastPosition.X);
    LImage.Position.Y := LImage.Position.Y + (EventInfo.Location.Y - FLastPosition.Y);
   end;
   FLastPosition := EventInfo.Location;
  end;
 end;
end;

About the Author

Ing. Karel Janecek, MBA, MSc.
www.torry.net