123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- unit UnitMain;
- interface
- uses
- System.Types, System.Diagnostics,
- System.SysUtils, System.Variants, System.Classes, System.Actions,
- Winapi.Windows, Winapi.Messages,
- Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls,
- Vcl.ActnList, Vcl.ComCtrls,
- GR32_Image,
- GR32_Layers;
- type
- TZoomMode = (zmAuto, zmSmall, zmLarge);
- type
- TFormMain = class(TForm)
- Image: TImage32;
- CheckBoxLayer: TCheckBox;
- RadioButtonSmall: TRadioButton;
- RadioButtonLarge: TRadioButton;
- ActionList: TActionList;
- ActionViewLayer: TAction;
- ActionImageSmall: TAction;
- ActionImageLarge: TAction;
- ActionImageCustom: TAction;
- RadioButtonCustom: TRadioButton;
- StatusBar: TStatusBar;
- CheckBoxAnimate: TCheckBox;
- ActionAnimate: TAction;
- TimerZoom: TTimer;
- procedure FormCreate(Sender: TObject);
- procedure ImageMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
- procedure ImageMouseLeave(Sender: TObject);
- procedure ImageResize(Sender: TObject);
- procedure ActionImageSmallExecute(Sender: TObject);
- procedure ActionImageLargeExecute(Sender: TObject);
- procedure ActionViewLayerExecute(Sender: TObject);
- procedure ActionViewLayerUpdate(Sender: TObject);
- procedure ActionImageCustomExecute(Sender: TObject);
- procedure RadioButtonCustomDblClick(Sender: TObject);
- procedure ActionAnimateExecute(Sender: TObject);
- procedure ImageScaleChange(Sender: TObject);
- procedure TimerZoomTimer(Sender: TObject);
- private
- FNormalOffset: TPoint;
- FBitmapLayer: TBitmapLayer;
- FZoomed: boolean;
- FZoomMode: TZoomMode;
- FNormalScale: Double;
- FZoomScale: Double;
- FStopwatchAnimation: TStopwatch;
- private
- procedure LoadImage(const Filename: string; ZoomMode: TZoomMode = zmAuto);
- procedure CenterImage;
- procedure ZoomIn(const MousePos: TPoint);
- procedure ZoomOut(const MousePos: TPoint);
- public
- end;
- var
- FormMain: TFormMain;
- implementation
- {$R *.dfm}
- uses
- System.Math,
- amEasing,
- GR32.Examples,
- GR32.ImageFormats,
- GR32_PNG,
- GR32_PortableNetworkGraphic, // Required for inline expansion
- GR32;
- procedure TFormMain.ActionAnimateExecute(Sender: TObject);
- begin
- //
- end;
- procedure TFormMain.ActionImageCustomExecute(Sender: TObject);
- var
- Filename: string;
- Filter: string;
- begin
- Filename := TAction(Sender).Hint;
- Filter := ImageFormatManager.BuildFileFilter(IImageFormatReader, True);
- if (PromptForFileName(Filename, Filter)) then
- begin
- LoadImage(Filename);
- TAction(Sender).Caption := '&Custom: ' + Filename;
- TAction(Sender).Hint := Filename;
- end;
- end;
- procedure TFormMain.ActionImageLargeExecute(Sender: TObject);
- begin
- LoadImage(Graphics32Examples.MediaFolder+'\freetrainer5.jpg', zmLarge);
- end;
- procedure TFormMain.ActionImageSmallExecute(Sender: TObject);
- begin
- LoadImage(Graphics32Examples.MediaFolder+'\coffee.png', zmSmall);
- end;
- procedure TFormMain.ActionViewLayerExecute(Sender: TObject);
- begin
- //
- end;
- procedure TFormMain.ActionViewLayerUpdate(Sender: TObject);
- begin
- TAction(Sender).Enabled := (FZoomMode = zmSmall);
- end;
- procedure TFormMain.CenterImage;
- var
- r: TRect;
- begin
- if (FBitmapLayer = nil) then
- exit;
- // Center main bitmap
- Image.OffsetHorz := Round((Image.ClientWidth - Image.Bitmap.Width * FNormalScale) * 0.5);
- Image.OffsetVert := Round((Image.ClientHeight - Image.Bitmap.Height * FNormalScale) * 0.5);
- // Center layer in control
- r := FBitmapLayer.Bitmap.BoundsRect;
- r.Offset((Image.ClientWidth-r.Width) div 2, (Image.ClientHeight-r.Height) div 2);
- FBitmapLayer.Location := FloatRect(r);
- end;
- procedure TFormMain.FormCreate(Sender: TObject);
- begin
- // Semi-transparent, unscaled layer
- FBitmapLayer := Image.Layers.Add<TBitmapLayer>;
- FBitmapLayer.Scaled := False;
- FBitmapLayer.Visible := False;
- ImageScaleChange(nil);
- ActionImageSmall.Execute;
- end;
- procedure TFormMain.ImageMouseLeave(Sender: TObject);
- var
- MousePos: TPoint;
- begin
- MousePos := Image.ScreenToClient(Mouse.CursorPos);
- ZoomOut(MousePos)
- end;
- procedure TFormMain.ImageMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
- var
- MousePos: TPoint;
- begin
- MousePos := Point(X, Y);
- if (Image.GetBitmapRect.Contains(MousePos)) then
- ZoomIn(MousePos)
- else
- ZoomOut(MousePos)
- end;
- procedure TFormMain.ImageResize(Sender: TObject);
- begin
- CenterImage;
- end;
- procedure TFormMain.ImageScaleChange(Sender: TObject);
- begin
- StatusBar.SimpleText := Format('Scale: %.3n', [Image.Scale]);
- end;
- procedure TFormMain.LoadImage(const Filename: string; ZoomMode: TZoomMode);
- var
- ResizeScaleX, ResizeScaleY: Double;
- begin
- // Load image
- FBitmapLayer.Bitmap.Assign(nil);
- Image.Bitmap.LoadFromFile(Filename);
- // Calculate zoom factors
- ResizeScaleX := Image.ClientWidth / Image.Bitmap.Width;
- ResizeScaleY := Image.ClientHeight / Image.Bitmap.Height;
- if (ZoomMode = zmAuto) then
- begin
- if (ResizeScaleX < 0.75) or (ResizeScaleY < 0.75) then
- ZoomMode := zmLarge
- else
- ZoomMode := zmSmall;
- end;
- FZoomMode := ZoomMode;
- case FZoomMode of
- // Bitmap is larger than viewport; Zoom is 1:1, Normal is fit to viewport
- zmLarge:
- begin
- FNormalScale := Min(ResizeScaleX, ResizeScaleY);
- FZoomScale := 1.0;
- end;
- // Bitmap is smaller than viewport; Normal is 1:1, Zoom is no less than 3
- zmSmall:
- begin
- FNormalScale := 1.0;
- FZoomScale := Max(3, Min(ResizeScaleX, ResizeScaleY));
- FBitmapLayer.Bitmap.Assign(Image.Bitmap);
- FBitmapLayer.Bitmap.MasterAlpha := 128;
- end;
- end;
- Image.Scale := FNormalScale;
- CenterImage;
- end;
- procedure TFormMain.RadioButtonCustomDblClick(Sender: TObject);
- begin
- ActionImageCustom.Execute;
- end;
- procedure TFormMain.TimerZoomTimer(Sender: TObject);
- var
- MousePos: TPoint;
- begin
- if (FZoomed) and (Image.Scale <> FZoomScale) then
- begin
- MousePos := Image.ScreenToClient(Mouse.CursorPos);
- ZoomIn(MousePos)
- end else
- TTimer(Sender).Enabled := False;
- end;
- procedure TFormMain.ZoomIn(const MousePos: TPoint);
- var
- BitmapPos: TPoint;
- Elapsed: int64;
- begin
- if (not FZoomed) then
- begin
- FZoomed := True;
- FBitmapLayer.Visible := (FZoomMode = zmSmall) and ActionViewLayer.Checked;
- // Save offset of bitmap with "normal" scale
- FNormalOffset := Image.GetBitmapRect.TopLeft;
- FStopwatchAnimation := TStopwatch.StartNew;
- Image.ForceFullInvalidate; // Work around for bug in repaint mechanism
- end;
- if (Image.Scale <> FZoomScale) then
- begin
- if (ActionAnimate.Checked) then
- begin
- // Animate the zoom using a "tween"
- Elapsed := FStopwatchAnimation.ElapsedMilliseconds;
- if (Elapsed < ZoomAnimateTime) then
- begin
- Image.Scale := FNormalScale + TEaseCubic.EaseInOut(Elapsed / ZoomAnimateTime) * (FZoomScale - FNormalScale);
- // Start a timer so we animate until the desired scale is reached
- TimerZoom.Enabled := True;
- end else
- Image.Scale := FZoomScale
- end else
- Image.Scale := FZoomScale;
- end;
- //
- // Pan so "position in bitmap" = "position in viewport".
- //
- // Looking at TCustomImage32.BitmapToControl we can see that the relationship
- // between bitmap and control position is:
- //
- // ViewportPos = BitmapPos * Scale + Offset
- //
- // Solving the above for Offset, given ViewportPos and BitmapPos:
- //
- // ViewportPos = BitmapPos * Scale + Offset
- // Offset = ViewportPos - BitmapPos * Scale
- //
- // Translate the position to bitmap coordinates, using the "normal" scale
- BitmapPos.X := Round((MousePos.X - FNormalOffset.X) / FNormalScale);
- BitmapPos.Y := Round((MousePos.Y - FNormalOffset.Y) / FNormalScale);
- // Calculate the offset from bitmap coordinates using the "zoomed" scale
- Image.OffsetHorz := MousePos.X - BitmapPos.X * Image.Scale;
- Image.OffsetVert := MousePos.Y - BitmapPos.Y * Image.Scale;
- end;
- procedure TFormMain.ZoomOut(const MousePos: TPoint);
- var
- Pivot: TPoint;
- begin
- if (not FZoomed) then
- exit;
- if (ActionAnimate.Checked) then
- begin
- // Animate zoom to normal
- Pivot := Image.ControlToBitmap(MousePos);
- Image.Zoom(FNormalScale, Pivot, True);
- end else
- Image.Scale := FNormalScale;
- FBitmapLayer.Visible := False;
- FZoomed := False;
- CenterImage;
- end;
- end.
|