uzoom.pas 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit UZoom;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. Classes, SysUtils, Forms, StdCtrls, BGRABitmapTypes;
  7. type
  8. TZoom = class;
  9. TOnZoomChangedHandler = procedure(sender: TZoom; ANewZoom: single) of object;
  10. TCustomMainFormLayout = class
  11. protected
  12. function GetWorkArea: TRect; virtual; abstract;
  13. public
  14. property WorkArea: TRect read GetWorkArea;
  15. end;
  16. { TZoom }
  17. TZoom = class
  18. private
  19. FLayout: TCustomMainFormLayout;
  20. FLabelCurrentZoom: TLabel;
  21. FEditZoom: TEdit;
  22. FMaxFactor: single;
  23. FMinFactor: single;
  24. FZoomFactor: single;
  25. FOnZoomChangedHandler : TOnZoomChangedHandler;
  26. FBitmapPosition: TPointF;
  27. FMousePosition: TPoint;
  28. FOnCenterQuery: TNotifyEvent;
  29. function GetEditingZoom: boolean;
  30. function GetPositionDefined: boolean;
  31. function GetZoomFactor: single;
  32. procedure SetEditingZoom(AValue: boolean);
  33. procedure SetMaxFactor(AValue: single);
  34. procedure SetMinFactor(AValue: single);
  35. procedure SetZoomFactor(AValue: single); overload;
  36. procedure SetZoomFactor(AValue: single; ACenter: boolean); overload;
  37. protected
  38. procedure EditZoom_KeyPress(Sender: TObject; var Key: char);
  39. procedure EditZoom_ZoomExit(Sender: TObject);
  40. procedure LabelCurrentZoom_Click(Sender: TObject);
  41. procedure UpdateLabel;
  42. function RoundZoom(AValue: single): single;
  43. public
  44. constructor Create(ALabelCurrentZoom: TLabel; AEditZoom: TEdit);
  45. destructor Destroy; override;
  46. procedure ZoomOriginal;
  47. procedure ZoomFit(AImageWidth,AImageHeight: integer);
  48. procedure ZoomIn(AFine: boolean = false);
  49. procedure ZoomOut(AFine: boolean = false);
  50. procedure SetPosition(ABitmapPosition: TPointF; AMousePosition: TPoint);
  51. procedure ClearPosition;
  52. procedure DoAction(const AName: string);
  53. function GetScaledArea(const AWorkArea: TRect; AImageWidth, AImageHeight: integer; var AViewOffset: TPoint): TRect;
  54. property Layout: TCustomMainFormLayout read FLayout write FLayout;
  55. property EditingZoom: boolean read GetEditingZoom write SetEditingZoom;
  56. property Factor: single read GetZoomFactor write SetZoomFactor;
  57. property OnZoomChanged: TOnZoomChangedHandler read FOnZoomChangedHandler write FOnZoomChangedHandler;
  58. property MaxFactor: single read FMaxFactor write SetMaxFactor;
  59. property MinFactor: single read FMinFactor write SetMinFactor;
  60. property BitmapPosition: TPointF read FBitmapPosition;
  61. property MousePosition: TPoint read FMousePosition;
  62. property PositionDefined: boolean read GetPositionDefined;
  63. property OnCenterQuery: TNotifyEvent read FOnCenterQuery write FOnCenterQuery;
  64. end;
  65. implementation
  66. uses Math, Dialogs, LazPaintType;
  67. { TZoom }
  68. function TZoom.GetEditingZoom: boolean;
  69. begin
  70. result := FEditZoom.Visible;
  71. end;
  72. function TZoom.GetPositionDefined: boolean;
  73. begin
  74. result := not isEmptyPointF(FBitmapPosition);
  75. end;
  76. function TZoom.GetZoomFactor: single;
  77. begin
  78. result := FZoomFactor;
  79. end;
  80. procedure TZoom.SetEditingZoom(AValue: boolean);
  81. begin
  82. if AValue <> FEditZoom.Visible then
  83. begin
  84. if AValue then
  85. begin
  86. FEditZoom.Text := IntToStr(round(FZoomFactor*100));
  87. FEditZoom.Visible := true;
  88. FLabelCurrentZoom.Visible := false;
  89. SafeSetFocus(FEditZoom);
  90. end else
  91. begin
  92. FLabelCurrentZoom.Visible := not AValue;
  93. FEditZoom.Visible := AValue
  94. end;
  95. end;
  96. end;
  97. procedure TZoom.SetMaxFactor(AValue: single);
  98. begin
  99. if FMaxFactor=AValue then Exit;
  100. FMaxFactor:=AValue;
  101. end;
  102. procedure TZoom.SetMinFactor(AValue: single);
  103. begin
  104. if FMinFactor=AValue then Exit;
  105. FMinFactor:=AValue;
  106. end;
  107. procedure TZoom.SetZoomFactor(AValue: single);
  108. begin
  109. SetZoomFactor(AValue, false);
  110. end;
  111. procedure TZoom.SetZoomFactor(AValue: single; ACenter: boolean);
  112. begin
  113. if (FMinFactor <> 0) and (AValue < FMinFactor) then AValue := FMinFactor;
  114. if (FMaxFactor <> 0) and (AValue > FMaxFactor) then AValue := FMaxFactor;
  115. EditingZoom:= False;
  116. FZoomFactor:= AValue;
  117. if ACenter and Assigned(OnCenterQuery) then OnCenterQuery(self);
  118. if Assigned(FOnZoomChangedHandler) then
  119. FOnZoomChangedHandler(self, AValue);
  120. UpdateLabel;
  121. end;
  122. procedure TZoom.EditZoom_ZoomExit(Sender: TObject);
  123. begin
  124. EditingZoom:= false;
  125. end;
  126. procedure TZoom.LabelCurrentZoom_Click(Sender: TObject);
  127. begin
  128. EditingZoom := true;
  129. end;
  130. procedure TZoom.UpdateLabel;
  131. begin
  132. if Factor < 3 then
  133. FLabelCurrentZoom.Caption := inttostr(round(Factor*100))+'%' else
  134. FLabelCurrentZoom.Caption := 'x'+FloatToStr(round(Factor*100)/100);
  135. end;
  136. function TZoom.RoundZoom(AValue: single): single;
  137. var zoomFactorLog,halfZoom,sign: single;
  138. begin
  139. halfZoom := ln(1.5)/ln(2);
  140. zoomFactorLog := ln(AValue)/ln(2);
  141. if zoomFactorLog < 0 then
  142. begin
  143. sign := -1;
  144. zoomFactorLog:= -zoomFactorLog;
  145. end else
  146. sign := 1;
  147. if frac(zoomFactorLog) >= (halfZoom+1)/2 then
  148. zoomFactorLog:= ceil(zoomFactorLog)
  149. else
  150. if frac(zoomFactorLog) >= halfZoom/2 then
  151. zoomFactorLog:= floor(zoomFactorLog)+halfZoom
  152. else
  153. zoomFactorLog:= floor(zoomFactorLog);
  154. result := exp(sign*zoomFactorLog*ln(2));
  155. end;
  156. procedure TZoom.EditZoom_KeyPress(Sender: TObject; var Key: char);
  157. begin
  158. if Key = #13 then
  159. begin
  160. Key := #0;
  161. EditingZoom:= false;
  162. if length(FEditZoom.Text) > 0 then
  163. begin
  164. try
  165. Factor:= StrToInt(FEditZoom.Text)/100;
  166. except
  167. on ex:exception do
  168. begin end;
  169. end;
  170. end;
  171. end else
  172. if Key = #27 then
  173. begin
  174. Key := #0;
  175. EditingZoom := false;
  176. end else
  177. if not (Key in['0'..'9',#8]) then Key := #0;
  178. end;
  179. constructor TZoom.Create(ALabelCurrentZoom: TLabel; AEditZoom: TEdit);
  180. begin
  181. inherited Create;
  182. FLayout := nil;
  183. FLabelCurrentZoom := ALabelCurrentZoom;
  184. FLabelCurrentZoom.OnClick := @LabelCurrentZoom_Click;
  185. FEditZoom := AEditZoom;
  186. FEditZoom.Top := FLabelCurrentZoom.Top-1;
  187. FEditZoom.OnExit := @EditZoom_ZoomExit;
  188. FEditZoom.OnKeyPress:= @EditZoom_KeyPress;
  189. FZoomFactor:= 1;
  190. FMinFactor := 0;
  191. FMaxFactor := 0;
  192. ClearPosition;
  193. UpdateLabel;
  194. end;
  195. destructor TZoom.Destroy;
  196. begin
  197. FLabelCurrentZoom.OnClick := nil;
  198. FEditZoom.OnExit := nil;
  199. FEditZoom.OnKeyPress := nil;
  200. inherited Destroy;
  201. end;
  202. procedure TZoom.ZoomOriginal;
  203. begin
  204. Factor := 1;
  205. end;
  206. procedure TZoom.ZoomFit(AImageWidth, AImageHeight: integer);
  207. const pixelMargin = 0;
  208. var zx,zy: single;
  209. pictureArea: TRect;
  210. begin
  211. if FLayout = nil then exit;
  212. pictureArea := FLayout.WorkArea;
  213. if (AImageWidth = 0) or (AImageHeight = 0) or (pictureArea.right-pictureArea.Left <= pixelMargin)
  214. or (pictureArea.Bottom-pictureArea.top <= pixelMargin) then exit;
  215. try
  216. zx := (pictureArea.right-pictureArea.left-pixelMargin)/AImageWidth;
  217. zy := (pictureArea.bottom-pictureArea.top-pixelMargin)/AImageheight;
  218. SetZoomFactor(min(zx,zy), true);
  219. except
  220. on ex:Exception do
  221. begin end;
  222. end;
  223. end;
  224. procedure TZoom.ZoomIn(AFine: boolean);
  225. begin
  226. if AFine then
  227. Factor := Factor*1.1
  228. else if RoundZoom(Factor) > Factor then
  229. Factor := RoundZoom(Factor)
  230. else
  231. Factor := RoundZoom(Factor*sqrt(2));
  232. end;
  233. procedure TZoom.ZoomOut(AFine: boolean);
  234. begin
  235. if AFine then
  236. Factor := Factor/1.1
  237. else if RoundZoom(Factor) < Factor then
  238. Factor := RoundZoom(Factor)
  239. else
  240. Factor := RoundZoom(Factor/sqrt(2));
  241. end;
  242. procedure TZoom.SetPosition(ABitmapPosition: TPointF; AMousePosition: TPoint);
  243. begin
  244. FBitmapPosition := ABitmapPosition;
  245. FMousePosition := AMousePosition;
  246. end;
  247. procedure TZoom.ClearPosition;
  248. begin
  249. SetPosition(EmptyPointF,Point(0,0));
  250. end;
  251. procedure TZoom.DoAction(const AName: string);
  252. begin
  253. if AName = 'ViewZoomIn' then ZoomIn else
  254. if AName = 'ViewZoomOriginal' then ZoomOriginal else
  255. if AName = 'ViewZoomOut' then ZoomOut;
  256. end;
  257. function TZoom.GetScaledArea(const AWorkArea: TRect; AImageWidth, AImageHeight: integer; var AViewOffset: TPoint): TRect;
  258. var
  259. scaledWidth,scaledHeight: integer;
  260. maxOffset, minOffset: TPoint;
  261. temp: integer;
  262. begin
  263. scaledWidth := round(AImageWidth*Factor);
  264. if scaledWidth = 0 then scaledWidth := 1;
  265. scaledHeight := round(AImageHeight*Factor);
  266. if scaledHeight = 0 then scaledHeight := 1;
  267. result.Left := (AWorkArea.Left+AWorkArea.Right-scaledWidth) div 2;
  268. result.Top := (AWorkArea.Top+AWorkArea.Bottom-scaledHeight) div 2;
  269. maxOffset := point(floor((AWorkArea.Right-(result.Left+scaledWidth))/Factor),
  270. floor((AWorkArea.Bottom-(result.Top+scaledHeight))/Factor));
  271. minOffset := point(ceil((AWorkArea.Left-result.Left)/Factor),
  272. ceil((AWorkArea.Top-result.Top)/Factor));
  273. if maxOffset.X < minOffset.X then
  274. begin
  275. temp := maxOffset.X;
  276. maxOffset.X := minOffset.X;
  277. minOffset.X := temp;
  278. end;
  279. if maxOffset.Y < minOffset.Y then
  280. begin
  281. temp := maxOffset.Y;
  282. maxOffset.Y := minOffset.Y;
  283. minOffset.Y := temp;
  284. end;
  285. if minOffset.X > -AImageWidth div 2 then minOffset.X := -AImageWidth div 2;
  286. if minOffset.Y > -AImageHeight div 2 then minOffset.Y := -AImageHeight div 2;
  287. if maxOffset.X < AImageWidth div 2 then maxOffset.X := AImageWidth div 2;
  288. if maxOffset.Y < AImageHeight div 2 then maxOffset.Y := AImageHeight div 2;
  289. if AViewOffset.X < minOffset.X then AViewOffset.X := minOffset.X else
  290. if AViewOffset.X > maxOffset.X then AViewOffset.X := maxOffset.X;
  291. if AViewOffset.Y < minOffset.Y then AViewOffset.Y := minOffset.Y else
  292. if AViewOffset.Y > maxOffset.Y then AViewOffset.Y := maxOffset.Y;
  293. if AImageWidth <> 0 then result.Left += round(AViewOffset.X*scaledWidth/AImageWidth);
  294. if AImageHeight <> 0 then result.Top += round(AViewOffset.Y*scaledHeight/AImageHeight);
  295. result.Right := result.Left + scaledWidth;
  296. result.Bottom := result.Top + scaledHeight;
  297. end;
  298. end.