Scaling to / from TImage inside a TScrollBox with special focus?

I am making a simple control based on TScrollingWinControl(and code copied from TScrollBox) using a control TImage. I zoomed in a bit, but it doesn’t necessarily get closer to the focused point — the scroll bars do not change accordingly to keep the center point in focus.

I would like to tell this control ZoomTo(const X, Y, ZoomBy: Integer);to indicate where to increase focus. Therefore, when it is scaled, the coordinates that I transmitted will remain “centered”. At the same time, I also need to have ZoomBy(const ZoomBy: Integer);one that tells him to be centered in its current form.

For example, there will be one scenario in which the mouse will be directed to a certain point in the image, and while holding control and scrolling up the mouse, it should increase the focus on the mouse pointer. On the other hand, a sliding control to adjust the zoom level will be another scenario, in which case it just needs to focus the center of the current view (not necessarily the center of the image).

The problem is that my math is lost at this moment, and I cannot figure out the correct formula to adjust these scrollbars. I tried several different calculation methods, nothing works correctly.

Here is a split version of my control. I deleted most only for the relevant materials, the original unit is more than 600 lines of code. The following is the most important procedure:SetZoom(const Value: Integer);

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 > 0 then
      NewScrollPos := Zoom + 5
    else
      NewScrollPos:= Zoom - 5;
    if NewScrollPos >= 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 < 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 <> Value then begin
    FZoomBy := EnsureRange(Value, 1, 100);
    Paint;
  end;
end;

end.
+5
1

, X, Y "ZoomBy()". , "OnMouseDown" , , , .. . , .

, , . , , (ScrollBox.ClientWidth/2, ScrollBox.ClientHeight/2). , , , ClientWidth/2, :

procedure ScrollTo(CenterX, CenterY: Integer);
begin
  ScrollBox.HorzScrollBar.Position := CenterX - Round(ScrollBox.ClientWidth / 2);
  ScrollBox.VertScrollBar.Position := CenterY - Round(ScrollBox.ClientHeight / 2);
end;


. , , X, Y , . CenterX := Center.X * ZoomFactor. ," ZoomFactor" , , , . , , :

procedure ZoomTo(CenterX, CenterY, ZoomBy: Integer);
var
  OldWidth, OldHeight: Integer;
begin
  OldWidth := FImage.Width;
  OldHeight := FImage.Height;

  // zoom the image, we have new image size and scroll range

  CenterX := Round(CenterX * FImage.Width / OldWidth);
  ScrollBox.HorzScrollBar.Position := CenterX - Round(ScrollBox.ClientWidth / 2);

  CenterY := Round(CenterY * FImage.Height / OldHeight);
  ScrollBox.VertScrollBar.Position := CenterY - Round(ScrollBox.ClientHeight / 2);
end; 

, , Round() , .

, .

+4

All Articles