2012-05-02 23 views
5

Wykonuję prostą kontrolę na podstawie TScrollingWinControl (i kodu skopiowanego z TScrollBox) z kontrolką TImage. Trochę się zbliża do pracy, ale niekoniecznie zbliża się do punktu skupienia - paski przewijania nie zmieniają się odpowiednio, aby zachować punkt środkowy.Powiększanie/zmniejszanie TImage wewnątrz TScrollBox do konkretnego fokusa?

Chciałbym móc powiedzieć tej kontroli ZoomTo(const X, Y, ZoomBy: Integer);, aby powiedzieć mu, gdzie należy ustawić zoom. Gdy zbliża się, współrzędne, które minęły, pozostaną "wyśrodkowane". W tym samym czasie muszę również mieć ZoomBy(const ZoomBy: Integer);, który mówi, aby zachować go wyśrodkowany w bieżącym widoku.

Na przykład, będzie jeden scenariusz, w którym mysz zostanie wskazana w określonym punkcie obrazu, a gdy przytrzymamy sterowanie i przewijamy myszką w górę, powinna ona powiększyć skupienie na wskaźniku myszy. Z drugiej strony, kolejnym scenariuszem byłoby przesuwanie kontrolki w celu dostosowania poziomu zoomu, w takim przypadku wystarczy, aby skupić się na środku bieżącego widoku (niekoniecznie na środku obrazu).

Problem polega na tym, że moja matematyka zostaje utracona w tym momencie i nie mogę znaleźć odpowiedniej formuły do ​​dostosowania tych pasków przewijania. Próbowałem kilku różnych sposobów obliczania, nic nie działa poprawnie.

Oto pozbawiona wersji moja kontrola. Usunąłem większość tylko do odpowiednich rzeczy, oryginalna jednostka ma ponad 600 linii kodu. Najważniejszą poniżej procedura jest 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

Nie mogę sobie nawet wyobrazić, co zrobi punkt "przybliżania". Chciałbym "powiększyć" prostokąta, a nie punkt. Nie potrafię odgadnąć, jak wygląda twoja klasa, więc nie mogę zgadnąć, jakiej matematyki potrzebujesz, ani której nie możesz mieć. –

+0

@WarrenP Załóżmy, że wyświetlane jest zdjęcie wielu osób, mysz jest wycelowana w środek twarzy osoby. Gdy użytkownik przytrzyma klawisz sterujący i przewiń tarczę myszy w górę, przybliża twarz tej osoby, a wskaźnik myszy nadal znajduje się w tej samej pozycji obrazu. Właśnie dlatego przybliżam się do "Point", a nie "Rect". Jestem prawie pewien, że zawarłem wszystkie odpowiednie kody powyżej, aby pokazać, jak radzę sobie ze zdarzeniami myszy. –

Odpowiedz

4

Nie jest jasne, co chcesz odnieść do X, Y przy przejściu do „ZoomBy()”. Zakładam, że wstawiłeś obsługę "OnMouseDown" dla obrazu, a współrzędne odnoszą się do miejsca, w którym klikniesz obraz, tj. Nie są one względne do współrzędnych pola przewijania. Jeśli tak nie jest, możesz to zmienić samodzielnie.

Zapomnijmy o powiększeniu na minutę, pozwól, aby nasze zadanie było wycentrowanie punktu, w którym klikamy obraz w przewijanym polu. Łatwo wiemy, że środek scrollboksa znajduje się w (ScrollBox.ClientWidth/2, ScrollBox.ClientHeight/2). Pomyśl poziome, chcemy, aby przewinąć do pewnego stopnia tak, że jeśli dodamy clientWidth/2 do niego, to będzie nasz punkt kliknięcie:

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


Rozważmy teraz powiększanie. Wszystko, co musimy zrobić, to obliczyć odpowiednio pozycje X, Y, rozmiar scrollboxa się nie zmieni. CenterX := Center.X * ZoomFactor. Ale bądź ostrożny, "ZoomFactor" nie jest tutaj efektywnym zoomem, jest to zoom, który zostanie zastosowany po kliknięciu obrazu. Użyję przed i po wymiarów obrazka, aby ustalić, że:

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; 

Oczywiście, można by byłaby ich w jednej linii, tak aby wywołać round() tylko jeden raz, aby zmniejszyć błąd zaokrąglenia.

Jestem pewien, że możesz ćwiczyć tutaj.

+0

Dzięki, dam mu spin, kiedy wrócę do domu. –