| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 |
- {
- Vampyre Imaging Library Demo
- FireMonkey Demo (class api, FMX interaction)
- This demo is a simple image viewer. On the left of the window is a list box with
- information and thumbnail of images loaded from file. Selecting item in
- list box displays the image in image viewer component that fills the rest of
- the app window. Loaded image can be saved back to disk in one the supported
- file formats.
- Demo uses ImagingFmx extension to convert between Imaging's and FireMonkey's
- image classes.
- Image is loaded from the file in a background thread while the UI shows
- progress animation.
- What more is there:
- - multi-page images show all the pages (with thumbnaiol and details) in the listbox
- and you can select whch one to display
- - you can zoom in and out the image (mouse wheel & Ctrl with +/-)
- - when zoomed in you can pan the image in the view with mouse drag or using scrollbars
- - image file path can be passed as a parameter when starting the executable to
- open it at start (to use as "Open with..." target)
- - there's a drop zone on the form where you can drop image files from file explorers
- - toolbar has zoom presets control
- Tested in Delphi 11 & 12. Windows and x86 macOS targets work fine.
- Android compiles and starts on the device fine but TOpenDialog etc. are not
- implemented in FMX. iOS and macOS on ARM, and FMX Linux are not tested (can you do that?).
- }
- unit MainForm;
- {$IF not Defined (DCC) or (CompilerVersion < 25.0)}
- {$MESSAGE FATAL 'Needs at least Delphi XE4'}
- {$IFEND}
- interface
- uses
- System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
- FMX.Types, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.DialogService, FMX.Filter.Effects, FMX.Graphics,
- FMX.Layouts, FMX.ListBox, FMX.ExtCtrls, FMX.Objects, FMX.StdCtrls, FMX.Effects,
- FMX.Controls.Presentation, System.ImageList, FMX.ImgList, FMX.ComboEdit, FMX.Edit,
- ImagingTypes,
- Imaging,
- ImagingClasses,
- ImagingUtility,
- ImagingFmx;
- type
- TFormMain = class(TForm)
- Splitter: TSplitter;
- ToolBar: TToolBar;
- ListImages: TListBox;
- BtnOpenImage: TSpeedButton;
- ImageViewer: TImageViewer;
- StyleBook: TStyleBook;
- PanelBack: TPanel;
- AniIndicator: TAniIndicator;
- OpenDialog: TOpenDialog;
- BtnSaveImage: TSpeedButton;
- BtnAbout: TSpeedButton;
- SaveDialog: TSaveDialog;
- ImageList: TImageList;
- DropTarget: TDropTarget;
- BlurEffect: TGaussianBlurEffect;
- LayoutCombo: TLayout;
- ComboScale: TComboEdit;
- LayoutSide: TLayout;
- LineSep: TLine;
- procedure BtnOpenImageClick(Sender: TObject);
- procedure BtnAboutClick(Sender: TObject);
- procedure FormCreate(Sender: TObject);
- procedure FormDestroy(Sender: TObject);
- procedure BtnSaveImageClick(Sender: TObject);
- procedure ListImagesChange(Sender: TObject);
- procedure DropTargetDropped(Sender: TObject; const Data: TDragObject;
- const Point: TPointF);
- procedure DropTargetDragOver(Sender: TObject; const Data: TDragObject;
- const Point: TPointF; var Operation: TDragOperation);
- procedure FormKeyDown(Sender: TObject; var Key: Word; var KeyChar: Char;
- Shift: TShiftState);
- procedure ComboScaleClosePopup(Sender: TObject);
- procedure ComboScaleKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char;
- Shift: TShiftState);
- procedure ImageViewerCalcContentBounds(Sender: TObject;
- var ContentBounds: TRectF);
- private
- FImage: TMultiImage;
- FFileName: string;
- FLoaderThread: TThread;
- procedure OpenFile(const FileName: string);
- procedure LoadingFinished(Success: Boolean; const ErrorMsg: string);
- procedure FillListBox(Image: TMultiImage);
- procedure SelectImage(Index: Integer);
- procedure UpdateScaleText;
- end;
- var
- FormMain: TFormMain;
- implementation
- uses
- AboutForm;
- {$R *.fmx}
- const
- ThumbMaxX = 106;
- ThumbMaxY = 92;
- type
- TImgLoaderThread = class(TThread)
- private
- type
- TFinishedHandler = reference to procedure(Success: Boolean; const ErrorMsg: string);
- private
- FFileName: string;
- FImageRef: TMultiImage;
- FFinishedHandler: TFinishedHandler;
- protected
- procedure Execute; override;
- public
- constructor Create(const FileName: string; ImageRef: TMultiImage;
- FinishedHandler: TFinishedHandler);
- end;
- procedure ClearImagesAndThumbs(Img: TMultiImage);
- var
- I: Integer;
- begin
- for I := 0 to Img.ImageCount - 1 do
- begin
- if Img.DataArray[I].Tag <> nil then
- TObject(Img.DataArray[I].Tag).Free;
- end;
- Img.ClearAll;
- end;
- { TImgLoaderThread }
- constructor TImgLoaderThread.Create(const FileName: string;
- ImageRef: TMultiImage; FinishedHandler: TFinishedHandler);
- begin
- FFileName := FileName;
- FImageRef := ImageRef;
- FFinishedHandler := FinishedHandler;
- FreeOnTerminate := True;
- inherited Create(False);
- end;
- procedure TImgLoaderThread.Execute;
- var
- I: Integer;
- Success: Boolean;
- ErrorMsg: string;
- Thumb: TSingleImage;
- begin
- TThread.NameThreadForDebugging('ImageLoaderThread');
- ErrorMsg := '';
- // Delete old images and thumbnails
- ClearImagesAndThumbs(FImageRef);
- try
- // Load image from file
- FImageRef.LoadMultiFromFile(FFileName);
- Success := FImageRef.AllImagesValid;
- // Generate thumbnails for subimages
- for I := 0 to FImageRef.ImageCount - 1 do
- begin
- Thumb := TSingleImage.Create;
- FImageRef.ActiveImage := I;
- FImageRef.ResizeToFit(ThumbMaxX, ThumbMaxY, rfBilinear, Thumb);
- FImageRef.DataArray[I].Tag := Thumb;
- end;
- except
- on E: Exception do
- begin
- Success := False;
- ErrorMsg := E.Message;
- end;
- end;
- Synchronize(
- procedure
- begin
- FFinishedHandler(Success, ErrorMsg);
- end);
- end;
- { TFormMain }
- procedure TFormMain.FormCreate(Sender: TObject);
- begin
- Caption := Caption + ' - ' + Imaging.SImagingLibTitle + ' ' + Imaging.GetVersionStr;
- FImage := TMultiImage.Create;
- // For panning the scaled up image with a mouse drag
- ImageViewer.AniCalculations.TouchTracking := [ttVertical, ttHorizontal];
- if (ParamCount > 0) and FileExists(ParamStr(1)) then
- OpenFile(ParamStr(1));
- end;
- procedure TFormMain.FormDestroy(Sender: TObject);
- begin
- ListImages.Clear;
- ClearImagesAndThumbs(FImage);
- FImage.Free;
- end;
- procedure TFormMain.FillListBox(Image: TMultiImage);
- var
- Item: TListBoxItem;
- I, ImgSize: Integer;
- Data: TImageData;
- Bmp: TBitmap;
- begin
- ListImages.Clear;
- Bmp := TBitmap.Create(0, 0);
- try
- for I := 0 to FImage.ImageCount - 1 do
- begin
- Data := FImage.DataArray[I];
- Item := TListBoxItem.Create(ListImages);
- Item.Parent := ListImages;
- Item.StyleLookup := 'ListBoxItem';
- ImgSize := Data.Size;
- if ImgSize > 8192 then
- ImgSize := ImgSize div 1024;
- ImagingFmx.ConvertImageToFmxBitmap(TSingleImage(Data.Tag), Bmp);
- Item.StylesData['ImgThumb'] := Bmp;
- Item.StylesData['TextTitle'] := Format('Image %d/%d', [I + 1, FImage.ImageCount]);
- Item.StylesData['TextInfo'] :=
- Format('Resolution: %dx%d', [Data.Width, Data.Height]) + sLineBreak +
- Format('Format: %s', [GetFormatName(Data.Format)]) + sLineBreak +
- Format('Size: %.0n %s', [ImgSize + 0.0, Iff(ImgSize = Data.Size, 'B', 'KiB')]);
- end;
- finally
- Bmp.Free;
- end;
- end;
- procedure TFormMain.UpdateScaleText;
- begin
- ComboScale.Text := FloatToStrFmt(ImageViewer.BitmapScale * 100, 0) + ' %';
- end;
- procedure TFormMain.FormKeyDown(Sender: TObject; var Key: Word;
- var KeyChar: Char; Shift: TShiftState);
- begin
- if ssCtrl in Shift then
- begin
- case Key of
- vkAdd: ImageViewer.BitmapScale := ImageViewer.BitmapScale * 1.1;
- vkSubtract: ImageViewer.BitmapScale := ImageViewer.BitmapScale * 0.9;
- end;
- end;
- end;
- procedure TFormMain.ImageViewerCalcContentBounds(Sender: TObject;
- var ContentBounds: TRectF);
- begin
- UpdateScaleText;
- end;
- procedure TFormMain.ListImagesChange(Sender: TObject);
- begin
- if ListImages.ItemIndex >= 0 then
- SelectImage(ListImages.ItemIndex);
- end;
- procedure TFormMain.ComboScaleClosePopup(Sender: TObject);
- const
- Scales: array[0..5] of Double = (0.25, 0.5, 1.0, 1.5, 2.0, 4.0);
- begin
- if ComboScale.ItemIndex <= 5 then
- ImageViewer.BitmapScale := Scales[ComboScale.ItemIndex]
- else if ComboScale.ItemIndex = ComboScale.Items.Count - 1 then
- ImageViewer.BestFit;
- ComboScale.ResetFocus;
- UpdateScaleText;
- end;
- procedure TFormMain.ComboScaleKeyUp(Sender: TObject; var Key: Word;
- var KeyChar: Char; Shift: TShiftState);
- var
- Scale: Integer;
- begin
- if Key = vkReturn then
- begin
- var Text := ComboScale.Text.Trim([' ', '%']);
- if TryStrToInt(Text, Scale) then
- begin
- if (Scale >= 1) and (Scale <= 1000) then
- ImageViewer.BitmapScale := Scale / 100;
- end;
- ComboScale.ResetFocus;
- UpdateScaleText;
- end;
- end;
- procedure TFormMain.LoadingFinished(Success: Boolean; const ErrorMsg: string);
- begin
- if Success then
- begin
- FillListBox(FImage);
- ListImages.ItemIndex := 0;
- end
- else
- begin
- MessageDlg('Error loading image: ' + ErrorMsg, TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0);
- FImage.ClearAll;
- end;
- AniIndicator.Visible := False;
- BlurEffect.Enabled := False;
- ToolBar.Enabled := True;
- end;
- procedure TFormMain.SelectImage(Index: Integer);
- begin
- FImage.ActiveImage := Index;
- ImageViewer.BeginUpdate;
- try
- ImagingFmx.ConvertImageToFmxBitmap(FImage, ImageViewer.Bitmap);
- ImageViewer.BestFit;
- ComboScale.Enabled := True;
- BtnSaveImage.Enabled := True;
- finally
- ImageViewer.EndUpdate;
- end;
- end;
- procedure TFormMain.BtnSaveImageClick(Sender: TObject);
- begin
- if not FImage.AllImagesValid then
- begin
- MessageDlg('No image is loaded.', TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0);
- Exit;
- end;
- SaveDialog.Filter := GetImageFileFormatsFilter(False);
- SaveDialog.FileName := ChangeFileExt(ExtractFileName(FFileName), '');
- SaveDialog.FilterIndex := GetFileNameFilterIndex(FFileName, False);
- if SaveDialog.Execute then
- begin
- FFileName := ChangeFileExt(SaveDialog.FileName, '.' + GetFilterIndexExtension(SaveDialog.FilterIndex, False));
- if FileExists(FFileName) and (MessageDlg(Format('Image file "%s" already exists. Do you want to overwrite it?',
- [FFileName]), TMsgDlgType.mtConfirmation, mbYesNo, 0, TMsgDlgBtn.mbNo) = mrNo) then
- begin
- Exit;
- end;
- FImage.SaveMultiToFile(FFileName)
- end;
- end;
- procedure TFormMain.DropTargetDragOver(Sender: TObject; const Data: TDragObject;
- const Point: TPointF; var Operation: TDragOperation);
- begin
- if (Length(Data.Files) > 0) and Imaging.IsFileFormatSupported(Data.Files[0]) then
- begin
- Operation := TDragOperation.Copy;
- end;
- end;
- procedure TFormMain.OpenFile(const FileName: string);
- begin
- FFileName := FileName;
- ListImages.Clear;
- ImageViewer.Bitmap.SetSize(0, 0);
- ToolBar.Enabled := False;
- AniIndicator.Visible := True;
- BlurEffect.Enabled := True;
- FLoaderThread := TImgLoaderThread.Create(FFileName, FImage, LoadingFinished);
- end;
- procedure TFormMain.DropTargetDropped(Sender: TObject; const Data: TDragObject;
- const Point: TPointF);
- begin
- if Length(Data.Files) > 0 then
- OpenFile(Data.Files[0]);
- end;
- procedure TFormMain.BtnOpenImageClick(Sender: TObject);
- begin
- OpenDialog.Filter := Imaging.GetImageFileFormatsFilter(True);
- if OpenDialog.Execute then
- OpenFile(OpenDialog.FileName);
- end;
- procedure TFormMain.BtnAboutClick(Sender: TObject);
- var
- X, Y: Integer;
- begin
- // Place it manually - poMainFormCenter etc. doesn't really work well
- X := Left + (Width - FormAbout.Width) div 2;
- Y := Top + (Height - FormAbout.Height) div 2;
- FormAbout.SetBounds(X, Y, FormAbout.Width, FormAbout.Height);
- FormAbout.ShowModal;
- end;
- end.
|