放大/缩小TScrollBox内的TImage到特定焦点?

Jer*_*dge 6 delphi controls zoom mousewheel delphi-xe2

我正在使用控件基于a TScrollingWinControl(和从a复制的代码TScrollBox)进行简单的TImage控制.我有点缩放工作,但它不一定缩放到焦点 - 滚动条不会相应改变以保持焦点的中心点.

我希望能够ZoomTo(const X, Y, ZoomBy: Integer);告诉它这个控件告诉它将焦点放大到哪里.因此,当它变焦时,我传递的坐标将保持"居中".与此同时,我还需要有一个ZoomBy(const ZoomBy: Integer);告诉它将它保持在当前视图中心.

例如,将存在一种情况,其中鼠标指向图像的特定点,并且当按住控制并向上滚动鼠标时,它应该聚焦于鼠标指针.另一方面,另一种情况是滑动控件以调整缩放级别,在这种情况下,它只需要保持当前视图的中心(不一定是图像的中心)聚焦.

问题是我的数学在这一点上迷失了,我无法弄清楚调整这些滚动条的正确公式.我尝试了几种不同的计算方法,似乎没有什么工作正常.

这是我的控件的剥离版本.我删除了大多数只有相关的东西,原始单位超过600行代码.下面最重要的程序是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.
Run Code Online (Sandbox Code Playgroud)

Ser*_*yuz 4

目前尚不清楚在传递给“ZoomBy()”时您希望引用什么 X、Y。我假设您已经为图像放置了一个“OnMouseDown”处理程序,并且坐标指的是您单击图像的位置,即它们与滚动框坐标无关。如果不是这样,您可以自行调整。

\n\n

让我们暂时忘记缩放,让我们的任务集中在滚动框中单击图像的点上。很简单,我们知道滚动框的中心位于(ScrollBox.ClientWidth/2,\xc2\xa0ScrollBox.ClientHeight/2)。考虑水平方向,我们想要向上滚动到一个点,这样,如果我们将 ClientWidth/2 添加到其中,它将成为我们的点击点:

\n\n
procedure ScrollTo(CenterX, CenterY: Integer);\nbegin\n  ScrollBox.HorzScrollBar.Position := CenterX - Round(ScrollBox.ClientWidth / 2);\n  ScrollBox.VertScrollBar.Position := CenterY - Round(ScrollBox.ClientHeight / 2);\nend;\n
Run Code Online (Sandbox Code Playgroud)\n\n


现在考虑缩放。我们要做的就是相应地计算X、Y位置,滚动框的大小不会改变。CenterX\xc2\xa0:=\xc2\xa0Center.X\xc2\xa0*\xc2\xa0ZoomFactor。但要注意,这里的“ZoomFactor”不是有效缩放,而是当我们点击图像时将应用的缩放。我将使用图像的前后尺寸来确定:

\n\n
procedure ZoomTo(CenterX, CenterY, ZoomBy: Integer);\nvar\n  OldWidth, OldHeight: Integer;\nbegin\n  OldWidth := FImage.Width;\n  OldHeight := FImage.Height;\n\n  // zoom the image, we have new image size and scroll range\n\n  CenterX := Round(CenterX * FImage.Width / OldWidth);\n  ScrollBox.HorzScrollBar.Position := CenterX - Round(ScrollBox.ClientWidth / 2);\n\n  CenterY := Round(CenterY * FImage.Height / OldHeight);\n  ScrollBox.VertScrollBar.Position := CenterY - Round(ScrollBox.ClientHeight / 2);\nend; \n
Run Code Online (Sandbox Code Playgroud)\n\n

当然,您可以将它们重构为一行,以便仅调用 Round() 一次以减少舍入误差。

\n\n

我相信您可以自己从这里锻炼。

\n