6

TScrollingWinControlコントロールを使用して、 (およびからコピーされたコードTScrollBox)に基づいて単純なコントロールを作成していTImageます。ズームが機能するようになりましたが、必ずしも焦点が合っているとは限りません。スクロールバーは、中心点に焦点を合わせ続けるためにそれに応じて変化しません。

ZoomTo(const X, Y, ZoomBy: Integer);このコントロールに、フォーカスをどこにズームするかを指示できるようにしたいと思います。したがって、ズームすると、渡した座標は「中心」のままになります。同時に、ZoomBy(const ZoomBy: Integer);現在のビューの中央に保持するように指示する も必要です。

たとえば、マウスが画像の特定のポイントを指しているシナリオが 1 つあります。コントロールを押したままマウスを上にスクロールすると、マウス ポインターにフォーカスしてズームインする必要があります。一方、別のシナリオでは、コントロールをスライドさせてズーム レベルを調整します。この場合、現在のビューの中心 (必ずしも画像の中心である必要はありません) を維持する必要があります。

問題は、この時点で数学が失われてしまい、これらのスクロール バーを調整するための正しい数式を理解できないことです。私はいくつかの異なる計算方法を試しましたが、何も正しく機能していないようです。

これが私のコントロールの削除されたバージョンです。関連するものだけを削除しました。元のユニットは 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.
4

1 に答える 1

4

'ZoomBy()'に渡すときに、X、Yについて何を参照するかが明確ではありません。画像に「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()を1回だけ呼び出すように、それらを1行にリファクタリングします。

ここから自分でトレーニングできると確信しています。

于 2012-05-03T23:18:24.270 に答える