2012-05-02 21 views
5

Estoy haciendo un control simple basado en un TScrollingWinControl (y un código copiado de un TScrollBox) con un control TImage. De alguna manera hice que el zoom funcione, pero no necesariamente se acerca a un punto enfocado: las barras de desplazamiento no cambian en consecuencia para mantener enfocado el punto central.¿Acercar/alejar un TImage dentro de un TScrollBox a un foco particular?

Me gustaría poder decirle a este control ZoomTo(const X, Y, ZoomBy: Integer); para decirle dónde enfocar el zoom. Entonces, cuando se amplía, las coordenadas que pasé quedarán 'centradas'. Al mismo tiempo, también necesito tener un ZoomBy(const ZoomBy: Integer); que le indique que lo mantenga centrado en la vista actual.

Por ejemplo, habrá un escenario en el que el mouse apunta a un punto particular de la imagen, y cuando mantiene el control y desplaza el mouse hacia arriba, debe acercarse enfocando el puntero del mouse. Por otro lado, otro escenario sería deslizar un control para ajustar el nivel de zoom, en cuyo caso solo tiene que mantener enfocado el centro de la vista actual (no necesariamente el centro de la imagen).

El problema es que mi matemática se pierde en este momento, y no puedo encontrar la fórmula correcta para ajustar estas barras de desplazamiento. He intentado algunas formas diferentes de calcular, nada parece funcionar bien.

Aquí hay una versión reducida de mi control. Quité la mayoría a solo las cosas relevantes, la unidad original tiene más de 600 líneas de código. El procedimiento más importante a continuación es 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

Ni siquiera puedo empezar a imaginarme qué haría el "zoom to". Me gustaría "acercarme" a un rectángulo, no a un punto. No puedo adivinar cómo se ve la implementación de tu clase, así que no puedo adivinar qué matemática necesitas, ni nadie más. –

+0

@WarrenP Supongamos que se muestra una foto de varias personas, el mouse apunta al centro de la cara de una persona. Cuando el usuario sostiene la tecla de control y desplaza la rueda del mouse hacia arriba, se acerca a la cara de esa persona, con el puntero del mouse aún en la misma posición de la imagen. Es por eso que estoy acercando a un 'Punto' y no a un 'Rect'. Estoy bastante seguro de haber incluido todo el código relevante anterior para demostrar cómo manejo los eventos del mouse. –

Respuesta

4

No está claro lo que le gustaría hacer referencia X, Y al pasar a 'ZoomBy()'. Asumiré que has puesto un controlador 'OnMouseDown' para la imagen y las coordenadas hacen referencia a dónde haces clic en la imagen, es decir, no están relacionadas con las coordenadas del scrollbox. Si esto no es así, puede modificarlo usted mismo.

Olvidémonos de hacer zoom por un minuto, dejemos que nuestra tarea sea centrando el punto en el que hacemos clic en la imagen en el cuadro de desplazamiento. Fácil, sabemos que el centro del scrollbox está en (ScrollBox.ClientWidth/2, ScrollBox.ClientHeight/2). Piense horizontal, queremos para desplazarse hasta un punto tal manera que, si añadimos ClientWidth/2 a la misma, que será nuestro punto de clic:

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


Consideremos ahora el zoom. Todo lo que tenemos que hacer es calcular las posiciones X, Y en consecuencia, el tamaño del scrollbox no cambiará. CenterX := Center.X * ZoomFactor. Pero tenga cuidado, 'ZoomFactor' aquí no es el zoom efectivo, es el zoom que se aplicará cuando hagamos clic en la imagen. Voy a usar de la imagen antes y después de las dimensiones para determinar que:

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; 

Por supuesto, usted refactorizar en una sola línea para que usted llama Ronda() sólo una vez para reducir el error de redondeo.

Estoy seguro de que puede entrenar desde aquí usted mismo.

+0

Gracias, lo haré girar cuando regrese a casa. –

Cuestiones relacionadas