title: Zooming in/out of a TImage inside a TScrollBox to a particular focus?
body: <p>I'm making a simple control based on a <code>TScrollingWinControl</code> (and code copied from a <code>TScrollBox</code>) with a <code>TImage</code> control. I somewhat got the zooming to work, but it doesn't necessarily zoom to a focused point - the scrollbars don't change accordingly to keep the center point in focus.</p>
  
  <p>I would like to be able to tell this control <code>ZoomTo(const X, Y, ZoomBy: Integer);</code> to tell it where to zoom the focus to. So when it zooms, the coordinates I passed will stay 'centered'. At the same time, I also need to have a <code>ZoomBy(const ZoomBy: Integer);</code> which tells it to keep it centered in the current view.</p>
  
  <p>For example, there will be one scenario where the mouse is pointed at a particular point of the image, and when holding control and scrolling the mouse up, it should zoom in focused on the mouse pointer. On the other hand, another scenario would be sliding a control to adjust the zoom level, in which case it just needs to keep the center of the current view (not necessarily center of the image) focused.</p>
  
  <p>The problem is my math gets lost at this point, and I can't figure out the right formula to adjust these scroll bars. I've tried a few different ways of calculating, nothing seems to work right.</p>
  
  <p>Here's a stripped version of my control. I removed most to only the relevant stuff, original unit is over 600 lines of code. The most important procedure below is <code>SetZoom(const Value: Integer);</code></p>
  
  <pre><code>unit JD.Imaging;
  
  interface
  
  uses
    Windows, Classes, SysUtils, Graphics, Jpeg, PngImage, Controls, Forms,
    ExtCtrls, Messages;
  
  type
    TJDImageBox = class;
  
    TJDImageZoomEvent = procedure(Sender: TObject; const Zoom: Integer) of object;
  
    TJDImageBox = class(TScrollingWinControl)
    private
      FZoom: Integer; //level of zoom by percentage
      FPicture: TImage; //displays image within scroll box
      FOnZoom: TJDImageZoomEvent; //called when zoom occurs
      FZoomBy: Integer; //amount to zoom by (in pixels)
      procedure MouseWheel(Sender: TObject; Shift: TShiftState;
        WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
      procedure SetZoom(const Value: Integer);
      procedure SetZoomBy(const Value: Integer);
    public
      constructor Create(AOwner: TComponent); override;
    published
      property Zoom: Integer read FZoom write SetZoom;
      property ZoomBy: Integer read FZoomBy write SetZoomBy;
      property OnZoom: TJDImageZoomEvent read FOnZoom write FOnZoom;
    end;
  
  implementation
  
  { TJDImageBox }
  
  constructor TJDImageBox.Create(AOwner: TComponent);
  begin
    inherited Create(AOwner);
    OnMouseWheel:= MouseWheel;
    ControlStyle := [csAcceptsControls, csCaptureMouse, csClickEvents,
      csSetCaption, csDoubleClicks, csPannable, csGestures];
    AutoScroll := True;
    TabStop:= True;
    VertScrollBar.Tracking:= True;
    HorzScrollBar.Tracking:= True;
    Width:= 100;
    Height:= 100;
    FPicture:= TImage.Create(nil);
    FPicture.Parent:= Self;
    FPicture.AutoSize:= False;
    FPicture.Stretch:= True;
    FPicture.Proportional:= True;
    FPicture.Left:= 0;
    FPicture.Top:= 0;
    FPicture.Width:= 1;
    FPicture.Height:= 1;
    FPicture.Visible:= False;
    FZoom:= 100;
    FZoomBy:= 10;
  end;
  
  destructor TJDImageBox.Destroy;
  begin
    FImage.Free;
    FPicture.Free;
    inherited;
  end;
  
  procedure TJDImageBox.MouseWheel(Sender: TObject; Shift: TShiftState;
    WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
  var
    NewScrollPos: Integer;
  begin
    if ssCtrl in Shift then begin
      if WheelDelta &gt; 0 then
        NewScrollPos := Zoom + 5
      else
        NewScrollPos:= Zoom - 5;
      if NewScrollPos &gt;= 5 then
        Zoom:= NewScrollPos;
    end else
    if ssShift in Shift then begin
      NewScrollPos := HorzScrollBar.Position - WheelDelta;
      HorzScrollBar.Position := NewScrollPos;
    end else begin
      NewScrollPos := VertScrollBar.Position - WheelDelta;
      VertScrollBar.Position := NewScrollPos;
    end;
    Handled := True;
  end;
  
  procedure TJDImageBox.SetZoom(const Value: Integer);
  var
    Perc: Single;
  begin
    FZoom := Value;
    if FZoom &lt; FZoomBy then
      FZoom:= FZoomBy;
    Perc:= FZoom / 100;
    //Resize picture to new zoom level
    FPicture.Width:= Trunc(FImage.Width * Perc);
    FPicture.Height:= Trunc(FImage.Height * Perc);
    //Move scroll bars to properly position the center of the view
    //This is where I don't know how to calculate the 'center'
    //or by how much I need to move the scroll bars.
    HorzScrollBar.Position:= HorzScrollBar.Position - (FZoomBy div 2);
    VertScrollBar.Position:= VertScrollBar.Position - (FZoomBy div 2);
    if assigned(FOnZoom) then
      FOnZoom(Self, FZoom);
  end;
  
  procedure TJDImageBox.SetZoomBy(const Value: Integer);
  begin
    if FZoomBy &lt;&gt; Value then begin
      FZoomBy := EnsureRange(Value, 1, 100);
      Paint;
    end;
  end;
  
  end.
  </code></pre>