2012-05-02 17 views
5

Sto facendo un semplice controllo basato su un TScrollingWinControl (e un codice copiato da un TScrollBox) con un controllo TImage. Ho in qualche modo ottenuto lo zoom per funzionare, ma non necessariamente lo zoom su un punto focalizzato - le barre di scorrimento non cambiano di conseguenza per mantenere il punto centrale a fuoco.Ingrandimento/riduzione di una tImmagine all'interno di un TScrollBox per un particolare focus?

Vorrei essere in grado di dire a questo controllo ZoomTo(const X, Y, ZoomBy: Integer); di indicare dove ingrandire lo stato attivo. Quindi quando zooma, le coordinate che ho passato rimarranno 'centrate'. Allo stesso tempo, ho anche bisogno di avere un ZoomBy(const ZoomBy: Integer); che gli dica di tenerlo centrato nella vista corrente.

Ad esempio, ci sarà uno scenario in cui il mouse è puntato su un particolare punto dell'immagine, e quando si tiene premuto il controllo e si scorre il mouse verso l'alto, lo zoom deve essere focalizzato sul puntatore del mouse. D'altra parte, un altro scenario sarebbe lo scorrimento di un controllo per regolare il livello di zoom, nel qual caso è sufficiente mantenere focalizzato il centro della vista corrente (non necessariamente il centro dell'immagine).

Il problema è che la mia matematica si perde a questo punto e non riesco a capire la formula giusta per regolare queste barre di scorrimento. Ho provato diversi metodi di calcolo, niente sembra funzionare correttamente.

Ecco una versione ridotta del mio controllo. Ho rimosso la maggior parte solo per il materiale pertinente, l'unità originale ha oltre 600 righe di codice. La procedura più importante qui di seguito è 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. 
+1

Non riesco nemmeno ad immaginare quale sarebbe il "punto di zoom in avanti". Vorrei "zoomare" su un rettangolo, non su un punto. Non riesco a indovinare quale sia l'aspetto della tua classe, quindi non posso indovinare di quale matematica hai bisogno, né potrei qualcun altro. –

+0

@WarrenP Supponiamo di visualizzare una foto di più persone, il mouse è puntato al centro del volto di una persona. Quando l'utente tiene premuto il tasto di controllo e fa scorrere la rotella del mouse verso l'alto, si ingrandirà il volto di quella persona, con il puntatore del mouse ancora nella stessa posizione dell'immagine. Questo è il motivo per cui sto facendo lo zoom su un 'Punto' e non su un' Retto'. Sono abbastanza sicuro di aver incluso tutto il codice pertinente sopra per dimostrare come gestisco gli eventi del mouse. –

risposta

4

Non è chiaro cosa ti piacerebbe fare riferimento per X, Y caso di passaggio da 'Zoomby()'. Immagino che tu abbia messo un gestore di 'OnMouseDown' per l'immagine e le coordinate si riferiscono a dove fai clic sull'immagine, cioè non sono relative alle coordinate della casella di scorrimento. Se non è così, puoi modificarlo da solo.

Dimentichiamo lo zoom per un minuto, concentriamoci sul punto in cui clicchiamo sull'immagine nella casella di scorrimento. Facile, sappiamo che il centro della casella di scorrimento è in (ScrollBox.ClientWidth/2, ScrollBox.ClientHeight/2). Pensate orizzontale, vogliamo scorrere verso l'alto ad un punto in modo che, se aggiungiamo clientWidth/2 ad esso, sarà il nostro punto di clic:

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


Consideriamo ora lo zoom. Tutto quello che dobbiamo fare è calcolare le posizioni X, Y di conseguenza, la dimensione della casella di scorrimento non cambierà. CenterX := Center.X * ZoomFactor. Ma fai attenzione, "ZoomFactor" qui non è lo zoom effettivo, è lo zoom che verrà applicato quando clicchiamo sull'immagine. Userò prima e dopo dimensioni dell'immagine per determinare che:

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; 

Naturalmente, si sarebbe li rifattorizziamo in una linea in modo che si chiama Round() una sola volta per ridurre l'errore di arrotondamento.

Sono sicuro che puoi allenarti da qui da solo.

+0

Grazie, farò un giro quando torno a casa. –