UnitMain.pas 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. unit UnitMain;
  2. interface
  3. uses
  4. System.Types, System.Diagnostics,
  5. System.SysUtils, System.Variants, System.Classes, System.Actions,
  6. Winapi.Windows, Winapi.Messages,
  7. Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls,
  8. Vcl.ActnList, Vcl.ComCtrls,
  9. GR32_Image,
  10. GR32_Layers;
  11. type
  12. TZoomMode = (zmAuto, zmSmall, zmLarge);
  13. type
  14. TFormMain = class(TForm)
  15. Image: TImage32;
  16. CheckBoxLayer: TCheckBox;
  17. RadioButtonSmall: TRadioButton;
  18. RadioButtonLarge: TRadioButton;
  19. ActionList: TActionList;
  20. ActionViewLayer: TAction;
  21. ActionImageSmall: TAction;
  22. ActionImageLarge: TAction;
  23. ActionImageCustom: TAction;
  24. RadioButtonCustom: TRadioButton;
  25. StatusBar: TStatusBar;
  26. CheckBoxAnimate: TCheckBox;
  27. ActionAnimate: TAction;
  28. TimerZoom: TTimer;
  29. procedure FormCreate(Sender: TObject);
  30. procedure ImageMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  31. procedure ImageMouseLeave(Sender: TObject);
  32. procedure ImageResize(Sender: TObject);
  33. procedure ActionImageSmallExecute(Sender: TObject);
  34. procedure ActionImageLargeExecute(Sender: TObject);
  35. procedure ActionViewLayerExecute(Sender: TObject);
  36. procedure ActionViewLayerUpdate(Sender: TObject);
  37. procedure ActionImageCustomExecute(Sender: TObject);
  38. procedure RadioButtonCustomDblClick(Sender: TObject);
  39. procedure ActionAnimateExecute(Sender: TObject);
  40. procedure ImageScaleChange(Sender: TObject);
  41. procedure TimerZoomTimer(Sender: TObject);
  42. private
  43. FNormalOffset: TPoint;
  44. FBitmapLayer: TBitmapLayer;
  45. FZoomed: boolean;
  46. FZoomMode: TZoomMode;
  47. FNormalScale: Double;
  48. FZoomScale: Double;
  49. FStopwatchAnimation: TStopwatch;
  50. private
  51. procedure LoadImage(const Filename: string; ZoomMode: TZoomMode = zmAuto);
  52. procedure CenterImage;
  53. procedure ZoomIn(const MousePos: TPoint);
  54. procedure ZoomOut(const MousePos: TPoint);
  55. public
  56. end;
  57. var
  58. FormMain: TFormMain;
  59. implementation
  60. {$R *.dfm}
  61. uses
  62. System.Math,
  63. amEasing,
  64. GR32.Examples,
  65. GR32.ImageFormats,
  66. GR32_PNG,
  67. GR32_PortableNetworkGraphic, // Required for inline expansion
  68. GR32;
  69. procedure TFormMain.ActionAnimateExecute(Sender: TObject);
  70. begin
  71. //
  72. end;
  73. procedure TFormMain.ActionImageCustomExecute(Sender: TObject);
  74. var
  75. Filename: string;
  76. Filter: string;
  77. begin
  78. Filename := TAction(Sender).Hint;
  79. Filter := ImageFormatManager.BuildFileFilter(IImageFormatReader, True);
  80. if (PromptForFileName(Filename, Filter)) then
  81. begin
  82. LoadImage(Filename);
  83. TAction(Sender).Caption := '&Custom: ' + Filename;
  84. TAction(Sender).Hint := Filename;
  85. end;
  86. end;
  87. procedure TFormMain.ActionImageLargeExecute(Sender: TObject);
  88. begin
  89. LoadImage(Graphics32Examples.MediaFolder+'\freetrainer5.jpg', zmLarge);
  90. end;
  91. procedure TFormMain.ActionImageSmallExecute(Sender: TObject);
  92. begin
  93. LoadImage(Graphics32Examples.MediaFolder+'\coffee.png', zmSmall);
  94. end;
  95. procedure TFormMain.ActionViewLayerExecute(Sender: TObject);
  96. begin
  97. //
  98. end;
  99. procedure TFormMain.ActionViewLayerUpdate(Sender: TObject);
  100. begin
  101. TAction(Sender).Enabled := (FZoomMode = zmSmall);
  102. end;
  103. procedure TFormMain.CenterImage;
  104. var
  105. r: TRect;
  106. begin
  107. if (FBitmapLayer = nil) then
  108. exit;
  109. // Center main bitmap
  110. Image.OffsetHorz := Round((Image.ClientWidth - Image.Bitmap.Width * FNormalScale) * 0.5);
  111. Image.OffsetVert := Round((Image.ClientHeight - Image.Bitmap.Height * FNormalScale) * 0.5);
  112. // Center layer in control
  113. r := FBitmapLayer.Bitmap.BoundsRect;
  114. r.Offset((Image.ClientWidth-r.Width) div 2, (Image.ClientHeight-r.Height) div 2);
  115. FBitmapLayer.Location := FloatRect(r);
  116. end;
  117. procedure TFormMain.FormCreate(Sender: TObject);
  118. begin
  119. // Semi-transparent, unscaled layer
  120. FBitmapLayer := Image.Layers.Add<TBitmapLayer>;
  121. FBitmapLayer.Scaled := False;
  122. FBitmapLayer.Visible := False;
  123. ImageScaleChange(nil);
  124. ActionImageSmall.Execute;
  125. end;
  126. procedure TFormMain.ImageMouseLeave(Sender: TObject);
  127. var
  128. MousePos: TPoint;
  129. begin
  130. MousePos := Image.ScreenToClient(Mouse.CursorPos);
  131. ZoomOut(MousePos)
  132. end;
  133. procedure TFormMain.ImageMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  134. var
  135. MousePos: TPoint;
  136. begin
  137. MousePos := Point(X, Y);
  138. if (Image.GetBitmapRect.Contains(MousePos)) then
  139. ZoomIn(MousePos)
  140. else
  141. ZoomOut(MousePos)
  142. end;
  143. procedure TFormMain.ImageResize(Sender: TObject);
  144. begin
  145. CenterImage;
  146. end;
  147. procedure TFormMain.ImageScaleChange(Sender: TObject);
  148. begin
  149. StatusBar.SimpleText := Format('Scale: %.3n', [Image.Scale]);
  150. end;
  151. procedure TFormMain.LoadImage(const Filename: string; ZoomMode: TZoomMode);
  152. var
  153. ResizeScaleX, ResizeScaleY: Double;
  154. begin
  155. // Load image
  156. FBitmapLayer.Bitmap.Assign(nil);
  157. Image.Bitmap.LoadFromFile(Filename);
  158. // Calculate zoom factors
  159. ResizeScaleX := Image.ClientWidth / Image.Bitmap.Width;
  160. ResizeScaleY := Image.ClientHeight / Image.Bitmap.Height;
  161. if (ZoomMode = zmAuto) then
  162. begin
  163. if (ResizeScaleX < 0.75) or (ResizeScaleY < 0.75) then
  164. ZoomMode := zmLarge
  165. else
  166. ZoomMode := zmSmall;
  167. end;
  168. FZoomMode := ZoomMode;
  169. case FZoomMode of
  170. // Bitmap is larger than viewport; Zoom is 1:1, Normal is fit to viewport
  171. zmLarge:
  172. begin
  173. FNormalScale := Min(ResizeScaleX, ResizeScaleY);
  174. FZoomScale := 1.0;
  175. end;
  176. // Bitmap is smaller than viewport; Normal is 1:1, Zoom is no less than 3
  177. zmSmall:
  178. begin
  179. FNormalScale := 1.0;
  180. FZoomScale := Max(3, Min(ResizeScaleX, ResizeScaleY));
  181. FBitmapLayer.Bitmap.Assign(Image.Bitmap);
  182. FBitmapLayer.Bitmap.MasterAlpha := 128;
  183. end;
  184. end;
  185. Image.Scale := FNormalScale;
  186. CenterImage;
  187. end;
  188. procedure TFormMain.RadioButtonCustomDblClick(Sender: TObject);
  189. begin
  190. ActionImageCustom.Execute;
  191. end;
  192. procedure TFormMain.TimerZoomTimer(Sender: TObject);
  193. var
  194. MousePos: TPoint;
  195. begin
  196. if (FZoomed) and (Image.Scale <> FZoomScale) then
  197. begin
  198. MousePos := Image.ScreenToClient(Mouse.CursorPos);
  199. ZoomIn(MousePos)
  200. end else
  201. TTimer(Sender).Enabled := False;
  202. end;
  203. procedure TFormMain.ZoomIn(const MousePos: TPoint);
  204. var
  205. BitmapPos: TPoint;
  206. Elapsed: int64;
  207. begin
  208. if (not FZoomed) then
  209. begin
  210. FZoomed := True;
  211. FBitmapLayer.Visible := (FZoomMode = zmSmall) and ActionViewLayer.Checked;
  212. // Save offset of bitmap with "normal" scale
  213. FNormalOffset := Image.GetBitmapRect.TopLeft;
  214. FStopwatchAnimation := TStopwatch.StartNew;
  215. Image.ForceFullInvalidate; // Work around for bug in repaint mechanism
  216. end;
  217. if (Image.Scale <> FZoomScale) then
  218. begin
  219. if (ActionAnimate.Checked) then
  220. begin
  221. // Animate the zoom using a "tween"
  222. Elapsed := FStopwatchAnimation.ElapsedMilliseconds;
  223. if (Elapsed < ZoomAnimateTime) then
  224. begin
  225. Image.Scale := FNormalScale + TEaseCubic.EaseInOut(Elapsed / ZoomAnimateTime) * (FZoomScale - FNormalScale);
  226. // Start a timer so we animate until the desired scale is reached
  227. TimerZoom.Enabled := True;
  228. end else
  229. Image.Scale := FZoomScale
  230. end else
  231. Image.Scale := FZoomScale;
  232. end;
  233. //
  234. // Pan so "position in bitmap" = "position in viewport".
  235. //
  236. // Looking at TCustomImage32.BitmapToControl we can see that the relationship
  237. // between bitmap and control position is:
  238. //
  239. // ViewportPos = BitmapPos * Scale + Offset
  240. //
  241. // Solving the above for Offset, given ViewportPos and BitmapPos:
  242. //
  243. // ViewportPos = BitmapPos * Scale + Offset
  244. // Offset = ViewportPos - BitmapPos * Scale
  245. //
  246. // Translate the position to bitmap coordinates, using the "normal" scale
  247. BitmapPos.X := Round((MousePos.X - FNormalOffset.X) / FNormalScale);
  248. BitmapPos.Y := Round((MousePos.Y - FNormalOffset.Y) / FNormalScale);
  249. // Calculate the offset from bitmap coordinates using the "zoomed" scale
  250. Image.OffsetHorz := MousePos.X - BitmapPos.X * Image.Scale;
  251. Image.OffsetVert := MousePos.Y - BitmapPos.Y * Image.Scale;
  252. end;
  253. procedure TFormMain.ZoomOut(const MousePos: TPoint);
  254. var
  255. Pivot: TPoint;
  256. begin
  257. if (not FZoomed) then
  258. exit;
  259. if (ActionAnimate.Checked) then
  260. begin
  261. // Animate zoom to normal
  262. Pivot := Image.ControlToBitmap(MousePos);
  263. Image.Zoom(FNormalScale, Pivot, True);
  264. end else
  265. Image.Scale := FNormalScale;
  266. FBitmapLayer.Visible := False;
  267. FZoomed := False;
  268. CenterImage;
  269. end;
  270. end.