ImageViewer.axaml.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. using Avalonia.Animation;
  2. using Avalonia.Controls;
  3. using Avalonia.Controls.Primitives;
  4. using Avalonia.Input;
  5. using Avalonia.Interactivity;
  6. using Avalonia.Media;
  7. using Avalonia.Media.Imaging;
  8. using Avalonia.Threading;
  9. using PicView.Avalonia.ImageTransformations;
  10. using PicView.Avalonia.Navigation;
  11. using PicView.Avalonia.ViewModels;
  12. using PicView.Avalonia.WindowBehavior;
  13. using PicView.Core.ImageDecoding;
  14. using PicView.Core.ImageTransformations;
  15. namespace PicView.Avalonia.Views;
  16. public partial class ImageViewer : UserControl
  17. {
  18. public ImageViewer()
  19. {
  20. InitializeComponent();
  21. TriggerScalingModeUpdate(false);
  22. AddHandler(PointerWheelChangedEvent, PreviewOnPointerWheelChanged, RoutingStrategies.Tunnel);
  23. AddHandler(Gestures.PointerTouchPadGestureMagnifyEvent, TouchMagnifyEvent, RoutingStrategies.Bubble);
  24. Loaded += delegate
  25. {
  26. InitializeZoom();
  27. LostFocus += (_, _) =>
  28. {
  29. Zoom.Release();
  30. };
  31. };
  32. }
  33. public void TriggerScalingModeUpdate(bool invalidate)
  34. {
  35. var scalingMode = Settings.ImageScaling.IsScalingSetToNearestNeighbor
  36. ? BitmapInterpolationMode.LowQuality
  37. : BitmapInterpolationMode.HighQuality;
  38. RenderOptions.SetBitmapInterpolationMode(MainImage,scalingMode);
  39. if (invalidate)
  40. {
  41. MainImage.InvalidateVisual();
  42. }
  43. }
  44. private void TouchMagnifyEvent(object? sender, PointerDeltaEventArgs e)
  45. {
  46. Zoom.ZoomTo(e.GetPosition(this), e.Delta.X > 0, DataContext as MainViewModel);
  47. }
  48. public async Task PreviewOnPointerWheelChanged(object? sender, PointerWheelEventArgs e)
  49. {
  50. e.Handled = true;
  51. await Main_OnPointerWheelChanged(e);
  52. }
  53. private async Task Main_OnPointerWheelChanged(PointerWheelEventArgs e)
  54. {
  55. if (DataContext is not MainViewModel mainViewModel)
  56. return;
  57. if (Settings.Zoom.IsUsingTouchPad)
  58. {
  59. // Use touch gestures for zooming
  60. return;
  61. }
  62. var ctrl = e.KeyModifiers == KeyModifiers.Control;
  63. var shift = e.KeyModifiers == KeyModifiers.Shift;
  64. var reverse = e.Delta.Y < 0;
  65. if (Settings.Zoom.ScrollEnabled)
  66. {
  67. if (!shift)
  68. {
  69. if (ctrl && !Settings.Zoom.CtrlZoom)
  70. {
  71. await LoadNextPic();
  72. return;
  73. }
  74. if (ImageScrollViewer.VerticalScrollBarVisibility is ScrollBarVisibility.Visible or ScrollBarVisibility.Auto)
  75. {
  76. if (reverse)
  77. {
  78. ImageScrollViewer.LineDown();
  79. }
  80. else
  81. {
  82. ImageScrollViewer.LineUp();
  83. }
  84. }
  85. else
  86. {
  87. await LoadNextPic();
  88. }
  89. return;
  90. }
  91. }
  92. if (Settings.Zoom.CtrlZoom)
  93. {
  94. if (ctrl)
  95. {
  96. if (reverse)
  97. {
  98. ZoomOut(e);
  99. }
  100. else
  101. {
  102. ZoomIn(e);
  103. }
  104. }
  105. else
  106. {
  107. await ScrollOrNavigate();
  108. }
  109. }
  110. else
  111. {
  112. if (ctrl)
  113. {
  114. await ScrollOrNavigate();
  115. }
  116. else
  117. {
  118. if (reverse)
  119. {
  120. ZoomOut(e);
  121. }
  122. else
  123. {
  124. ZoomIn(e);
  125. }
  126. }
  127. }
  128. return;
  129. async Task ScrollOrNavigate()
  130. {
  131. if (!Settings.Zoom.ScrollEnabled || e.KeyModifiers == KeyModifiers.Shift)
  132. {
  133. await LoadNextPic();
  134. }
  135. else
  136. {
  137. if (ImageScrollViewer.VerticalScrollBarVisibility is ScrollBarVisibility.Visible or ScrollBarVisibility.Auto)
  138. {
  139. if (reverse)
  140. {
  141. ImageScrollViewer.LineDown();
  142. }
  143. else
  144. {
  145. ImageScrollViewer.LineUp();
  146. }
  147. }
  148. else
  149. {
  150. await LoadNextPic();
  151. }
  152. }
  153. }
  154. async Task LoadNextPic()
  155. {
  156. bool next;
  157. if (reverse)
  158. {
  159. next = Settings.Zoom.HorizontalReverseScroll;
  160. }
  161. else
  162. {
  163. next = !Settings.Zoom.HorizontalReverseScroll;
  164. }
  165. await NavigationManager.Navigate(next, mainViewModel).ConfigureAwait(false);
  166. }
  167. }
  168. #region Zoom
  169. private void InitializeZoom() => Zoom.InitializeZoom(MainBorder);
  170. public void ZoomIn(PointerWheelEventArgs e) => Zoom.ZoomIn(e, this, MainImage, DataContext as MainViewModel);
  171. public void ZoomOut(PointerWheelEventArgs e) => Zoom.ZoomOut(e, this, MainImage, DataContext as MainViewModel);
  172. public void ZoomIn() => Zoom.ZoomIn(DataContext as MainViewModel);
  173. public void ZoomOut() => Zoom.ZoomOut( DataContext as MainViewModel);
  174. public void ResetZoom(bool enableAnimations = true) => Zoom.ResetZoom(enableAnimations, DataContext as MainViewModel);
  175. #endregion
  176. #region Rotation and Flip
  177. public void Rotate(bool clockWise)
  178. {
  179. if (DataContext is not MainViewModel vm)
  180. return;
  181. if (MainImage.Source is null)
  182. {
  183. return;
  184. }
  185. if (RotationHelper.IsValidRotation(vm.RotationAngle))
  186. {
  187. var nextAngle = RotationHelper.Rotate(vm.RotationAngle, clockWise);
  188. vm.RotationAngle = nextAngle switch
  189. {
  190. 360 => 0,
  191. -90 => 270,
  192. _ => nextAngle
  193. };
  194. }
  195. else
  196. {
  197. vm.RotationAngle = RotationHelper.NextRotationAngle(vm.RotationAngle, true);
  198. }
  199. var rotateTransform = new RotateTransform(vm.RotationAngle);
  200. if (Dispatcher.UIThread.CheckAccess())
  201. {
  202. ImageLayoutTransformControl.LayoutTransform = rotateTransform;
  203. }
  204. else
  205. {
  206. Dispatcher.UIThread.Invoke(() =>
  207. {
  208. ImageLayoutTransformControl.LayoutTransform = rotateTransform;
  209. });
  210. }
  211. WindowResizing.SetSize(vm);
  212. MainImage.InvalidateVisual();
  213. }
  214. public void Rotate(double angle)
  215. {
  216. Dispatcher.UIThread.Invoke(() =>
  217. {
  218. var rotateTransform = new RotateTransform(angle);
  219. ImageLayoutTransformControl.LayoutTransform = rotateTransform;
  220. WindowResizing.SetSize(DataContext as MainViewModel);
  221. MainImage.InvalidateVisual();
  222. });
  223. }
  224. public void Flip(bool animate)
  225. {
  226. if (DataContext is not MainViewModel vm)
  227. return;
  228. if (MainImage.Source is null)
  229. {
  230. return;
  231. }
  232. int prevScaleX;
  233. vm.PicViewer.ScaleX = vm.PicViewer.ScaleX == -1 ? 1 : -1;
  234. if (vm.PicViewer.ScaleX == 1)
  235. {
  236. prevScaleX = 1;
  237. vm.PicViewer.ScaleX = -1;
  238. vm.Translation.IsFlipped = vm.Translation.UnFlip;
  239. }
  240. else
  241. {
  242. prevScaleX = -1;
  243. vm.PicViewer.ScaleX = 1;
  244. vm.Translation.IsFlipped = vm.Translation.Flip;
  245. }
  246. if (animate)
  247. {
  248. var flipTransform = new ScaleTransform(prevScaleX, 1)
  249. {
  250. Transitions =
  251. [
  252. new DoubleTransition { Property = ScaleTransform.ScaleXProperty, Duration = TimeSpan.FromSeconds(.2) },
  253. ]
  254. };
  255. ImageLayoutTransformControl.RenderTransform = flipTransform;
  256. flipTransform.ScaleX = vm.PicViewer.ScaleX;
  257. }
  258. else
  259. {
  260. var flipTransform = new ScaleTransform(vm.PicViewer.ScaleX, 1);
  261. ImageLayoutTransformControl.RenderTransform = flipTransform;
  262. }
  263. }
  264. public void SetTransform(int scaleX, int rotationAngle)
  265. {
  266. if (DataContext is not MainViewModel vm)
  267. return;
  268. vm.PicViewer.ScaleX = scaleX;
  269. vm.RotationAngle = rotationAngle;
  270. var flipTransform = new ScaleTransform(vm.PicViewer.ScaleX, 1);
  271. ImageLayoutTransformControl.RenderTransform = flipTransform;
  272. var rotateTransform = new RotateTransform(rotationAngle);
  273. ImageLayoutTransformControl.LayoutTransform = rotateTransform;
  274. if (Zoom.IsZoomed)
  275. {
  276. ResetZoom(false);
  277. }
  278. }
  279. public void SetTransform(EXIFHelper.EXIFOrientation? orientation)
  280. {
  281. if (Dispatcher.UIThread.CheckAccess())
  282. {
  283. Set();
  284. }
  285. else
  286. {
  287. Dispatcher.UIThread.InvokeAsync(Set, DispatcherPriority.Send);
  288. }
  289. return;
  290. void Set()
  291. {
  292. if (Settings.Zoom.ScrollEnabled)
  293. {
  294. ImageScrollViewer.ScrollToHome();
  295. }
  296. switch (orientation)
  297. {
  298. case null:
  299. default:
  300. case EXIFHelper.EXIFOrientation.None:
  301. case EXIFHelper.EXIFOrientation.Horizontal:
  302. ResetZoom();;
  303. return;
  304. case EXIFHelper.EXIFOrientation.MirrorHorizontal:
  305. SetTransform(-1, 0);
  306. break;
  307. case EXIFHelper.EXIFOrientation.Rotate180:
  308. SetTransform(1, 180);
  309. break;
  310. case EXIFHelper.EXIFOrientation.MirrorVertical:
  311. SetTransform(-1, 180);
  312. break;
  313. case EXIFHelper.EXIFOrientation.MirrorHorizontalRotate270Cw:
  314. SetTransform(-1, 90); // should be 270, but it's not working
  315. break;
  316. case EXIFHelper.EXIFOrientation.Rotate90Cw:
  317. SetTransform(1, 90);
  318. break;
  319. case EXIFHelper.EXIFOrientation.MirrorHorizontalRotate90Cw:
  320. SetTransform(-1, 270); // should be 90, but it's not working
  321. break;
  322. case EXIFHelper.EXIFOrientation.Rotated270Cw:
  323. SetTransform(1, 270);
  324. break;
  325. }
  326. }
  327. }
  328. #endregion Rotation and Flip
  329. #region Events
  330. private void ImageScrollViewer_OnScrollChanged(object? sender, ScrollChangedEventArgs e)
  331. {
  332. e.Handled = true;
  333. }
  334. private void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e)
  335. {
  336. if (e.ClickCount == 2)
  337. {
  338. ResetZoom();
  339. }
  340. }
  341. private void MainImage_OnPointerPressed(object? sender, PointerPressedEventArgs e)
  342. {
  343. if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
  344. {
  345. return;
  346. }
  347. if (e.ClickCount == 2)
  348. {
  349. ResetZoom();
  350. }
  351. else
  352. {
  353. Pressed(e);
  354. }
  355. }
  356. private void MainImage_OnPointerMoved(object? sender, PointerEventArgs e) => Zoom.Pan(e, this);
  357. private void Pressed(PointerEventArgs e)
  358. {
  359. if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
  360. {
  361. return;
  362. }
  363. Zoom.Capture(e);
  364. }
  365. private void MainImage_OnPointerReleased(object? sender, PointerReleasedEventArgs e) => Zoom.Release();
  366. #endregion Events
  367. }