| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- // SPDX-License-Identifier: GPL-3.0-only
- unit UZoom;
- {$mode objfpc}{$H+}
- interface
- uses
- Classes, SysUtils, Forms, StdCtrls, BGRABitmapTypes;
- type
- TZoom = class;
- TOnZoomChangedHandler = procedure(sender: TZoom; ANewZoom: single) of object;
- TCustomMainFormLayout = class
- protected
- function GetWorkArea: TRect; virtual; abstract;
- public
- property WorkArea: TRect read GetWorkArea;
- end;
- { TZoom }
- TZoom = class
- private
- FLayout: TCustomMainFormLayout;
- FLabelCurrentZoom: TLabel;
- FEditZoom: TEdit;
- FMaxFactor: single;
- FMinFactor: single;
- FZoomFactor: single;
- FOnZoomChangedHandler : TOnZoomChangedHandler;
- FBitmapPosition: TPointF;
- FMousePosition: TPoint;
- FOnCenterQuery: TNotifyEvent;
- function GetEditingZoom: boolean;
- function GetPositionDefined: boolean;
- function GetZoomFactor: single;
- procedure SetEditingZoom(AValue: boolean);
- procedure SetMaxFactor(AValue: single);
- procedure SetMinFactor(AValue: single);
- procedure SetZoomFactor(AValue: single); overload;
- procedure SetZoomFactor(AValue: single; ACenter: boolean); overload;
- protected
- procedure EditZoom_KeyPress(Sender: TObject; var Key: char);
- procedure EditZoom_ZoomExit(Sender: TObject);
- procedure LabelCurrentZoom_Click(Sender: TObject);
- procedure UpdateLabel;
- function RoundZoom(AValue: single): single;
- public
- constructor Create(ALabelCurrentZoom: TLabel; AEditZoom: TEdit);
- destructor Destroy; override;
- procedure ZoomOriginal;
- procedure ZoomFit(AImageWidth,AImageHeight: integer);
- procedure ZoomIn(AFine: boolean = false);
- procedure ZoomOut(AFine: boolean = false);
- procedure SetPosition(ABitmapPosition: TPointF; AMousePosition: TPoint);
- procedure ClearPosition;
- procedure DoAction(const AName: string);
- function GetScaledArea(const AWorkArea: TRect; AImageWidth, AImageHeight: integer; var AViewOffset: TPoint): TRect;
- property Layout: TCustomMainFormLayout read FLayout write FLayout;
- property EditingZoom: boolean read GetEditingZoom write SetEditingZoom;
- property Factor: single read GetZoomFactor write SetZoomFactor;
- property OnZoomChanged: TOnZoomChangedHandler read FOnZoomChangedHandler write FOnZoomChangedHandler;
- property MaxFactor: single read FMaxFactor write SetMaxFactor;
- property MinFactor: single read FMinFactor write SetMinFactor;
- property BitmapPosition: TPointF read FBitmapPosition;
- property MousePosition: TPoint read FMousePosition;
- property PositionDefined: boolean read GetPositionDefined;
- property OnCenterQuery: TNotifyEvent read FOnCenterQuery write FOnCenterQuery;
- end;
- implementation
- uses Math, Dialogs, LazPaintType;
- { TZoom }
- function TZoom.GetEditingZoom: boolean;
- begin
- result := FEditZoom.Visible;
- end;
- function TZoom.GetPositionDefined: boolean;
- begin
- result := not isEmptyPointF(FBitmapPosition);
- end;
- function TZoom.GetZoomFactor: single;
- begin
- result := FZoomFactor;
- end;
- procedure TZoom.SetEditingZoom(AValue: boolean);
- begin
- if AValue <> FEditZoom.Visible then
- begin
- if AValue then
- begin
- FEditZoom.Text := IntToStr(round(FZoomFactor*100));
- FEditZoom.Visible := true;
- FLabelCurrentZoom.Visible := false;
- SafeSetFocus(FEditZoom);
- end else
- begin
- FLabelCurrentZoom.Visible := not AValue;
- FEditZoom.Visible := AValue
- end;
- end;
- end;
- procedure TZoom.SetMaxFactor(AValue: single);
- begin
- if FMaxFactor=AValue then Exit;
- FMaxFactor:=AValue;
- end;
- procedure TZoom.SetMinFactor(AValue: single);
- begin
- if FMinFactor=AValue then Exit;
- FMinFactor:=AValue;
- end;
- procedure TZoom.SetZoomFactor(AValue: single);
- begin
- SetZoomFactor(AValue, false);
- end;
- procedure TZoom.SetZoomFactor(AValue: single; ACenter: boolean);
- begin
- if (FMinFactor <> 0) and (AValue < FMinFactor) then AValue := FMinFactor;
- if (FMaxFactor <> 0) and (AValue > FMaxFactor) then AValue := FMaxFactor;
- EditingZoom:= False;
- FZoomFactor:= AValue;
- if ACenter and Assigned(OnCenterQuery) then OnCenterQuery(self);
- if Assigned(FOnZoomChangedHandler) then
- FOnZoomChangedHandler(self, AValue);
- UpdateLabel;
- end;
- procedure TZoom.EditZoom_ZoomExit(Sender: TObject);
- begin
- EditingZoom:= false;
- end;
- procedure TZoom.LabelCurrentZoom_Click(Sender: TObject);
- begin
- EditingZoom := true;
- end;
- procedure TZoom.UpdateLabel;
- begin
- if Factor < 3 then
- FLabelCurrentZoom.Caption := inttostr(round(Factor*100))+'%' else
- FLabelCurrentZoom.Caption := 'x'+FloatToStr(round(Factor*100)/100);
- end;
- function TZoom.RoundZoom(AValue: single): single;
- var zoomFactorLog,halfZoom,sign: single;
- begin
- halfZoom := ln(1.5)/ln(2);
- zoomFactorLog := ln(AValue)/ln(2);
- if zoomFactorLog < 0 then
- begin
- sign := -1;
- zoomFactorLog:= -zoomFactorLog;
- end else
- sign := 1;
- if frac(zoomFactorLog) >= (halfZoom+1)/2 then
- zoomFactorLog:= ceil(zoomFactorLog)
- else
- if frac(zoomFactorLog) >= halfZoom/2 then
- zoomFactorLog:= floor(zoomFactorLog)+halfZoom
- else
- zoomFactorLog:= floor(zoomFactorLog);
- result := exp(sign*zoomFactorLog*ln(2));
- end;
- procedure TZoom.EditZoom_KeyPress(Sender: TObject; var Key: char);
- begin
- if Key = #13 then
- begin
- Key := #0;
- EditingZoom:= false;
- if length(FEditZoom.Text) > 0 then
- begin
- try
- Factor:= StrToInt(FEditZoom.Text)/100;
- except
- on ex:exception do
- begin end;
- end;
- end;
- end else
- if Key = #27 then
- begin
- Key := #0;
- EditingZoom := false;
- end else
- if not (Key in['0'..'9',#8]) then Key := #0;
- end;
- constructor TZoom.Create(ALabelCurrentZoom: TLabel; AEditZoom: TEdit);
- begin
- inherited Create;
- FLayout := nil;
- FLabelCurrentZoom := ALabelCurrentZoom;
- FLabelCurrentZoom.OnClick := @LabelCurrentZoom_Click;
- FEditZoom := AEditZoom;
- FEditZoom.Top := FLabelCurrentZoom.Top-1;
- FEditZoom.OnExit := @EditZoom_ZoomExit;
- FEditZoom.OnKeyPress:= @EditZoom_KeyPress;
- FZoomFactor:= 1;
- FMinFactor := 0;
- FMaxFactor := 0;
- ClearPosition;
- UpdateLabel;
- end;
- destructor TZoom.Destroy;
- begin
- FLabelCurrentZoom.OnClick := nil;
- FEditZoom.OnExit := nil;
- FEditZoom.OnKeyPress := nil;
- inherited Destroy;
- end;
- procedure TZoom.ZoomOriginal;
- begin
- Factor := 1;
- end;
- procedure TZoom.ZoomFit(AImageWidth, AImageHeight: integer);
- const pixelMargin = 0;
- var zx,zy: single;
- pictureArea: TRect;
- begin
- if FLayout = nil then exit;
- pictureArea := FLayout.WorkArea;
- if (AImageWidth = 0) or (AImageHeight = 0) or (pictureArea.right-pictureArea.Left <= pixelMargin)
- or (pictureArea.Bottom-pictureArea.top <= pixelMargin) then exit;
- try
- zx := (pictureArea.right-pictureArea.left-pixelMargin)/AImageWidth;
- zy := (pictureArea.bottom-pictureArea.top-pixelMargin)/AImageheight;
- SetZoomFactor(min(zx,zy), true);
- except
- on ex:Exception do
- begin end;
- end;
- end;
- procedure TZoom.ZoomIn(AFine: boolean);
- begin
- if AFine then
- Factor := Factor*1.1
- else if RoundZoom(Factor) > Factor then
- Factor := RoundZoom(Factor)
- else
- Factor := RoundZoom(Factor*sqrt(2));
- end;
- procedure TZoom.ZoomOut(AFine: boolean);
- begin
- if AFine then
- Factor := Factor/1.1
- else if RoundZoom(Factor) < Factor then
- Factor := RoundZoom(Factor)
- else
- Factor := RoundZoom(Factor/sqrt(2));
- end;
- procedure TZoom.SetPosition(ABitmapPosition: TPointF; AMousePosition: TPoint);
- begin
- FBitmapPosition := ABitmapPosition;
- FMousePosition := AMousePosition;
- end;
- procedure TZoom.ClearPosition;
- begin
- SetPosition(EmptyPointF,Point(0,0));
- end;
- procedure TZoom.DoAction(const AName: string);
- begin
- if AName = 'ViewZoomIn' then ZoomIn else
- if AName = 'ViewZoomOriginal' then ZoomOriginal else
- if AName = 'ViewZoomOut' then ZoomOut;
- end;
- function TZoom.GetScaledArea(const AWorkArea: TRect; AImageWidth, AImageHeight: integer; var AViewOffset: TPoint): TRect;
- var
- scaledWidth,scaledHeight: integer;
- maxOffset, minOffset: TPoint;
- temp: integer;
- begin
- scaledWidth := round(AImageWidth*Factor);
- if scaledWidth = 0 then scaledWidth := 1;
- scaledHeight := round(AImageHeight*Factor);
- if scaledHeight = 0 then scaledHeight := 1;
- result.Left := (AWorkArea.Left+AWorkArea.Right-scaledWidth) div 2;
- result.Top := (AWorkArea.Top+AWorkArea.Bottom-scaledHeight) div 2;
- maxOffset := point(floor((AWorkArea.Right-(result.Left+scaledWidth))/Factor),
- floor((AWorkArea.Bottom-(result.Top+scaledHeight))/Factor));
- minOffset := point(ceil((AWorkArea.Left-result.Left)/Factor),
- ceil((AWorkArea.Top-result.Top)/Factor));
- if maxOffset.X < minOffset.X then
- begin
- temp := maxOffset.X;
- maxOffset.X := minOffset.X;
- minOffset.X := temp;
- end;
- if maxOffset.Y < minOffset.Y then
- begin
- temp := maxOffset.Y;
- maxOffset.Y := minOffset.Y;
- minOffset.Y := temp;
- end;
- if minOffset.X > -AImageWidth div 2 then minOffset.X := -AImageWidth div 2;
- if minOffset.Y > -AImageHeight div 2 then minOffset.Y := -AImageHeight div 2;
- if maxOffset.X < AImageWidth div 2 then maxOffset.X := AImageWidth div 2;
- if maxOffset.Y < AImageHeight div 2 then maxOffset.Y := AImageHeight div 2;
- if AViewOffset.X < minOffset.X then AViewOffset.X := minOffset.X else
- if AViewOffset.X > maxOffset.X then AViewOffset.X := maxOffset.X;
- if AViewOffset.Y < minOffset.Y then AViewOffset.Y := minOffset.Y else
- if AViewOffset.Y > maxOffset.Y then AViewOffset.Y := maxOffset.Y;
- if AImageWidth <> 0 then result.Left += round(AViewOffset.X*scaledWidth/AImageWidth);
- if AImageHeight <> 0 then result.Top += round(AViewOffset.Y*scaledHeight/AImageHeight);
- result.Right := result.Left + scaledWidth;
- result.Bottom := result.Top + scaledHeight;
- end;
- end.
|