MainForm.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. {
  2. Vampyre Imaging Library Demo
  3. FireMonkey Demo (class api, FMX interaction)
  4. This demo is a simple image viewer. On the left of the window is a list box with
  5. information and thumbnail of images loaded from file. Selecting item in
  6. list box displays the image in image viewer component that fills the rest of
  7. the app window. Loaded image can be saved back to disk in one the supported
  8. file formats.
  9. Demo uses ImagingFmx extension to convert between Imaging's and FireMonkey's
  10. image classes.
  11. Image is loaded from the file in a background thread while the UI shows
  12. progress animation.
  13. What more is there:
  14. - multi-page images show all the pages (with thumbnaiol and details) in the listbox
  15. and you can select whch one to display
  16. - you can zoom in and out the image (mouse wheel & Ctrl with +/-)
  17. - when zoomed in you can pan the image in the view with mouse drag or using scrollbars
  18. - image file path can be passed as a parameter when starting the executable to
  19. open it at start (to use as "Open with..." target)
  20. - there's a drop zone on the form where you can drop image files from file explorers
  21. - toolbar has zoom presets control
  22. Tested in Delphi 11 & 12. Windows and x86 macOS targets work fine.
  23. Android compiles and starts on the device fine but TOpenDialog etc. are not
  24. implemented in FMX. iOS and macOS on ARM, and FMX Linux are not tested (can you do that?).
  25. }
  26. unit MainForm;
  27. {$IF not Defined (DCC) or (CompilerVersion < 25.0)}
  28. {$MESSAGE FATAL 'Needs at least Delphi XE4'}
  29. {$IFEND}
  30. interface
  31. uses
  32. System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  33. FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.DialogService, FMX.Filter.Effects, FMX.Graphics,
  34. FMX.Layouts, FMX.ListBox, FMX.ExtCtrls, FMX.Objects, FMX.StdCtrls, FMX.Effects,
  35. FMX.Controls.Presentation, System.ImageList, FMX.ImgList, FMX.ComboEdit, FMX.Edit,
  36. ImagingTypes,
  37. Imaging,
  38. ImagingClasses,
  39. ImagingUtility,
  40. ImagingFmx;
  41. type
  42. TFormMain = class(TForm)
  43. Splitter: TSplitter;
  44. ToolBar: TToolBar;
  45. ListImages: TListBox;
  46. BtnOpenImage: TSpeedButton;
  47. ImageViewer: TImageViewer;
  48. StyleBook: TStyleBook;
  49. PanelBack: TPanel;
  50. AniIndicator: TAniIndicator;
  51. OpenDialog: TOpenDialog;
  52. BtnSaveImage: TSpeedButton;
  53. BtnAbout: TSpeedButton;
  54. SaveDialog: TSaveDialog;
  55. ImageList: TImageList;
  56. DropTarget: TDropTarget;
  57. BlurEffect: TGaussianBlurEffect;
  58. LayoutCombo: TLayout;
  59. ComboScale: TComboEdit;
  60. LayoutSide: TLayout;
  61. LineSep: TLine;
  62. procedure BtnOpenImageClick(Sender: TObject);
  63. procedure BtnAboutClick(Sender: TObject);
  64. procedure FormCreate(Sender: TObject);
  65. procedure FormDestroy(Sender: TObject);
  66. procedure BtnSaveImageClick(Sender: TObject);
  67. procedure ListImagesChange(Sender: TObject);
  68. procedure DropTargetDropped(Sender: TObject; const Data: TDragObject;
  69. const Point: TPointF);
  70. procedure DropTargetDragOver(Sender: TObject; const Data: TDragObject;
  71. const Point: TPointF; var Operation: TDragOperation);
  72. procedure FormKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char;
  73. Shift: TShiftState);
  74. procedure ComboScaleClosePopup(Sender: TObject);
  75. procedure ComboScaleKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char;
  76. Shift: TShiftState);
  77. procedure ImageViewerCalcContentBounds(Sender: TObject;
  78. var ContentBounds: TRectF);
  79. private
  80. FImage: TMultiImage;
  81. FFileName: string;
  82. FLoaderThread: TThread;
  83. procedure OpenFile(const FileName: string);
  84. procedure LoadingFinished(Success: Boolean; const ErrorMsg: string);
  85. procedure FillListBox(Image: TMultiImage);
  86. procedure SelectImage(Index: Integer);
  87. procedure UpdateScaleText;
  88. end;
  89. var
  90. FormMain: TFormMain;
  91. implementation
  92. uses
  93. AboutForm;
  94. {$R *.fmx}
  95. const
  96. ThumbMaxX = 106;
  97. ThumbMaxY = 92;
  98. type
  99. TImgLoaderThread = class(TThread)
  100. private
  101. type
  102. TFinishedHandler = reference to procedure(Success: Boolean; const ErrorMsg: string);
  103. private
  104. FFileName: string;
  105. FImageRef: TMultiImage;
  106. FFinishedHandler: TFinishedHandler;
  107. protected
  108. procedure Execute; override;
  109. public
  110. constructor Create(const FileName: string; ImageRef: TMultiImage;
  111. FinishedHandler: TFinishedHandler);
  112. end;
  113. procedure ClearImagesAndThumbs(Img: TMultiImage);
  114. var
  115. I: Integer;
  116. begin
  117. for I := 0 to Img.ImageCount - 1 do
  118. begin
  119. if Img.DataArray[I].Tag <> nil then
  120. TObject(Img.DataArray[I].Tag).Free;
  121. end;
  122. Img.ClearAll;
  123. end;
  124. { TImgLoaderThread }
  125. constructor TImgLoaderThread.Create(const FileName: string;
  126. ImageRef: TMultiImage; FinishedHandler: TFinishedHandler);
  127. begin
  128. FFileName := FileName;
  129. FImageRef := ImageRef;
  130. FFinishedHandler := FinishedHandler;
  131. FreeOnTerminate := True;
  132. inherited Create(False);
  133. end;
  134. procedure TImgLoaderThread.Execute;
  135. var
  136. I: Integer;
  137. Success: Boolean;
  138. ErrorMsg: string;
  139. Thumb: TSingleImage;
  140. begin
  141. TThread.NameThreadForDebugging('ImageLoaderThread');
  142. ErrorMsg := '';
  143. // Delete old images and thumbnails
  144. ClearImagesAndThumbs(FImageRef);
  145. try
  146. // Load image from file
  147. FImageRef.LoadMultiFromFile(FFileName);
  148. Success := FImageRef.AllImagesValid;
  149. // Generate thumbnails for subimages
  150. for I := 0 to FImageRef.ImageCount - 1 do
  151. begin
  152. Thumb := TSingleImage.Create;
  153. FImageRef.ActiveImage := I;
  154. FImageRef.ResizeToFit(ThumbMaxX, ThumbMaxY, rfBilinear, Thumb);
  155. FImageRef.DataArray[I].Tag := Thumb;
  156. end;
  157. except
  158. on E: Exception do
  159. begin
  160. Success := False;
  161. ErrorMsg := E.Message;
  162. end;
  163. end;
  164. Synchronize(
  165. procedure
  166. begin
  167. FFinishedHandler(Success, ErrorMsg);
  168. end);
  169. end;
  170. { TFormMain }
  171. procedure TFormMain.FormCreate(Sender: TObject);
  172. begin
  173. Caption := Caption + ' - ' + Imaging.SImagingLibTitle + ' ' + Imaging.GetVersionStr;
  174. FImage := TMultiImage.Create;
  175. // For panning the scaled up image with a mouse drag
  176. ImageViewer.AniCalculations.TouchTracking := [ttVertical, ttHorizontal];
  177. if (ParamCount > 0) and FileExists(ParamStr(1)) then
  178. OpenFile(ParamStr(1));
  179. end;
  180. procedure TFormMain.FormDestroy(Sender: TObject);
  181. begin
  182. ListImages.Clear;
  183. ClearImagesAndThumbs(FImage);
  184. FImage.Free;
  185. end;
  186. procedure TFormMain.FillListBox(Image: TMultiImage);
  187. var
  188. Item: TListBoxItem;
  189. I, ImgSize: Integer;
  190. Data: TImageData;
  191. Bmp: TBitmap;
  192. begin
  193. ListImages.Clear;
  194. Bmp := TBitmap.Create(0, 0);
  195. try
  196. for I := 0 to FImage.ImageCount - 1 do
  197. begin
  198. Data := FImage.DataArray[I];
  199. Item := TListBoxItem.Create(ListImages);
  200. Item.Parent := ListImages;
  201. Item.StyleLookup := 'ListBoxItem';
  202. ImgSize := Data.Size;
  203. if ImgSize > 8192 then
  204. ImgSize := ImgSize div 1024;
  205. ImagingFmx.ConvertImageToFmxBitmap(TSingleImage(Data.Tag), Bmp);
  206. Item.StylesData['ImgThumb'] := Bmp;
  207. Item.StylesData['TextTitle'] := Format('Image %d/%d', [I + 1, FImage.ImageCount]);
  208. Item.StylesData['TextInfo'] :=
  209. Format('Resolution: %dx%d', [Data.Width, Data.Height]) + sLineBreak +
  210. Format('Format: %s', [GetFormatName(Data.Format)]) + sLineBreak +
  211. Format('Size: %.0n %s', [ImgSize + 0.0, Iff(ImgSize = Data.Size, 'B', 'KiB')]);
  212. end;
  213. finally
  214. Bmp.Free;
  215. end;
  216. end;
  217. procedure TFormMain.UpdateScaleText;
  218. begin
  219. ComboScale.Text := FloatToStrFmt(ImageViewer.BitmapScale * 100, 0) + ' %';
  220. end;
  221. procedure TFormMain.FormKeyDown(Sender: TObject; var Key: Word;
  222. var KeyChar: Char; Shift: TShiftState);
  223. begin
  224. if ssCtrl in Shift then
  225. begin
  226. case Key of
  227. vkAdd: ImageViewer.BitmapScale := ImageViewer.BitmapScale * 1.1;
  228. vkSubtract: ImageViewer.BitmapScale := ImageViewer.BitmapScale * 0.9;
  229. end;
  230. end;
  231. end;
  232. procedure TFormMain.ImageViewerCalcContentBounds(Sender: TObject;
  233. var ContentBounds: TRectF);
  234. begin
  235. UpdateScaleText;
  236. end;
  237. procedure TFormMain.ListImagesChange(Sender: TObject);
  238. begin
  239. if ListImages.ItemIndex >= 0 then
  240. SelectImage(ListImages.ItemIndex);
  241. end;
  242. procedure TFormMain.ComboScaleClosePopup(Sender: TObject);
  243. const
  244. Scales: array[0..5] of Double = (0.25, 0.5, 1.0, 1.5, 2.0, 4.0);
  245. begin
  246. if ComboScale.ItemIndex <= 5 then
  247. ImageViewer.BitmapScale := Scales[ComboScale.ItemIndex]
  248. else if ComboScale.ItemIndex = ComboScale.Items.Count - 1 then
  249. ImageViewer.BestFit;
  250. ComboScale.ResetFocus;
  251. UpdateScaleText;
  252. end;
  253. procedure TFormMain.ComboScaleKeyUp(Sender: TObject; var Key: Word;
  254. var KeyChar: Char; Shift: TShiftState);
  255. var
  256. Scale: Integer;
  257. begin
  258. if Key = vkReturn then
  259. begin
  260. var Text := ComboScale.Text.Trim([' ', '%']);
  261. if TryStrToInt(Text, Scale) then
  262. begin
  263. if (Scale >= 1) and (Scale <= 1000) then
  264. ImageViewer.BitmapScale := Scale / 100;
  265. end;
  266. ComboScale.ResetFocus;
  267. UpdateScaleText;
  268. end;
  269. end;
  270. procedure TFormMain.LoadingFinished(Success: Boolean; const ErrorMsg: string);
  271. begin
  272. if Success then
  273. begin
  274. FillListBox(FImage);
  275. ListImages.ItemIndex := 0;
  276. end
  277. else
  278. begin
  279. MessageDlg('Error loading image: ' + ErrorMsg, TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0);
  280. FImage.ClearAll;
  281. end;
  282. AniIndicator.Visible := False;
  283. BlurEffect.Enabled := False;
  284. ToolBar.Enabled := True;
  285. end;
  286. procedure TFormMain.SelectImage(Index: Integer);
  287. begin
  288. FImage.ActiveImage := Index;
  289. ImageViewer.BeginUpdate;
  290. try
  291. ImagingFmx.ConvertImageToFmxBitmap(FImage, ImageViewer.Bitmap);
  292. ImageViewer.BestFit;
  293. ComboScale.Enabled := True;
  294. BtnSaveImage.Enabled := True;
  295. finally
  296. ImageViewer.EndUpdate;
  297. end;
  298. end;
  299. procedure TFormMain.BtnSaveImageClick(Sender: TObject);
  300. begin
  301. if not FImage.AllImagesValid then
  302. begin
  303. MessageDlg('No image is loaded.', TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0);
  304. Exit;
  305. end;
  306. SaveDialog.Filter := GetImageFileFormatsFilter(False);
  307. SaveDialog.FileName := ChangeFileExt(ExtractFileName(FFileName), '');
  308. SaveDialog.FilterIndex := GetFileNameFilterIndex(FFileName, False);
  309. if SaveDialog.Execute then
  310. begin
  311. FFileName := ChangeFileExt(SaveDialog.FileName, '.' + GetFilterIndexExtension(SaveDialog.FilterIndex, False));
  312. if FileExists(FFileName) and (MessageDlg(Format('Image file "%s" already exists. Do you want to overwrite it?',
  313. [FFileName]), TMsgDlgType.mtConfirmation, mbYesNo, 0, TMsgDlgBtn.mbNo) = mrNo) then
  314. begin
  315. Exit;
  316. end;
  317. FImage.SaveMultiToFile(FFileName)
  318. end;
  319. end;
  320. procedure TFormMain.DropTargetDragOver(Sender: TObject; const Data: TDragObject;
  321. const Point: TPointF; var Operation: TDragOperation);
  322. begin
  323. if (Length(Data.Files) > 0) and Imaging.IsFileFormatSupported(Data.Files[0]) then
  324. begin
  325. Operation := TDragOperation.Copy;
  326. end;
  327. end;
  328. procedure TFormMain.OpenFile(const FileName: string);
  329. begin
  330. FFileName := FileName;
  331. ListImages.Clear;
  332. ImageViewer.Bitmap.SetSize(0, 0);
  333. ToolBar.Enabled := False;
  334. AniIndicator.Visible := True;
  335. BlurEffect.Enabled := True;
  336. FLoaderThread := TImgLoaderThread.Create(FFileName, FImage, LoadingFinished);
  337. end;
  338. procedure TFormMain.DropTargetDropped(Sender: TObject; const Data: TDragObject;
  339. const Point: TPointF);
  340. begin
  341. if Length(Data.Files) > 0 then
  342. OpenFile(Data.Files[0]);
  343. end;
  344. procedure TFormMain.BtnOpenImageClick(Sender: TObject);
  345. begin
  346. OpenDialog.Filter := Imaging.GetImageFileFormatsFilter(True);
  347. if OpenDialog.Execute then
  348. OpenFile(OpenDialog.FileName);
  349. end;
  350. procedure TFormMain.BtnAboutClick(Sender: TObject);
  351. var
  352. X, Y: Integer;
  353. begin
  354. // Place it manually - poMainFormCenter etc. doesn't really work well
  355. X := Left + (Width - FormAbout.Width) div 2;
  356. Y := Top + (Height - FormAbout.Height) div 2;
  357. FormAbout.SetBounds(X, Y, FormAbout.Width, FormAbout.Height);
  358. FormAbout.ShowModal;
  359. end;
  360. end.