QuickLoad.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. using Avalonia.Threading;
  2. using ImageMagick;
  3. using PicView.Avalonia.Gallery;
  4. using PicView.Avalonia.ImageHandling;
  5. using PicView.Avalonia.Navigation;
  6. using PicView.Avalonia.UI;
  7. using PicView.Avalonia.ViewModels;
  8. using PicView.Avalonia.WindowBehavior;
  9. using PicView.Core.FileHandling;
  10. using PicView.Core.FileHistory;
  11. using PicView.Core.Gallery;
  12. using PicView.Core.ImageDecoding;
  13. namespace PicView.Avalonia.StartUp;
  14. public static class QuickLoad
  15. {
  16. public static async Task QuickLoadAsync(MainViewModel vm, string file)
  17. {
  18. var fileInfo = new FileInfo(file);
  19. if (!fileInfo.Exists) // If not file, try to load if URL, base64 or directory
  20. {
  21. vm.IsLoading = true;
  22. await NavigationManager.LoadPicFromStringAsync(file, vm).ConfigureAwait(false);
  23. return;
  24. }
  25. if (file.IsArchive()) // Handle if file exist and is an archive
  26. {
  27. vm.IsLoading = true;
  28. await NavigationManager.LoadPicFromArchiveAsync(file, vm).ConfigureAwait(false);
  29. return;
  30. }
  31. var magickImage = new MagickImage();
  32. magickImage.Ping(fileInfo);
  33. vm.PicViewer.FileInfo = fileInfo;
  34. var isLargeImage = magickImage.Width * magickImage.Height > 5000000; // ~5 megapixels threshold
  35. if (isLargeImage || Settings.ImageScaling.ShowImageSideBySide)
  36. {
  37. // Don't show loading indicator if image is too small
  38. vm.IsLoading = true;
  39. }
  40. if (Settings.ImageScaling.ShowImageSideBySide)
  41. {
  42. await SideBySideLoadingAsync(vm, fileInfo, magickImage).ConfigureAwait(false);
  43. }
  44. else
  45. {
  46. await SingeImageLoadingAsync(vm, fileInfo, magickImage).ConfigureAwait(false);
  47. }
  48. vm.IsLoading = false;
  49. }
  50. private static async Task SingeImageLoadingAsync(MainViewModel vm, FileInfo fileInfo, MagickImage? magickImage)
  51. {
  52. var cancellationTokenSource = new CancellationTokenSource();
  53. ImageModel? imageModel = null;
  54. await Task.WhenAll(
  55. Task.Run(() => { NavigationManager.InitializeImageIterator(vm); }, cancellationTokenSource.Token),
  56. Task.Run(async () => imageModel = await SetSingleImageAsync(vm, fileInfo, magickImage), cancellationTokenSource.Token))
  57. .ConfigureAwait(false);
  58. if (TiffManager.IsTiff(imageModel.FileInfo.FullName))
  59. {
  60. TitleManager.TrySetTiffTitle(imageModel, vm);
  61. }
  62. else
  63. {
  64. TitleManager.SetTitle(vm, imageModel);
  65. }
  66. await StartPreloaderAndGalleryAsync(vm, imageModel, fileInfo);
  67. cancellationTokenSource.Dispose();
  68. }
  69. private static async Task<ImageModel> SetSingleImageAsync(MainViewModel vm, FileInfo fileInfo, MagickImage? magickImage)
  70. {
  71. var imageModel = await GetImageModel.GetImageModelAsync(fileInfo, magickImage).ConfigureAwait(false);
  72. SetPicViewerValues(vm, imageModel, fileInfo);
  73. vm.IsLoading = false;
  74. await Dispatcher.UIThread.InvokeAsync(() =>
  75. {
  76. WindowResizing.SetSize(imageModel.PixelWidth, imageModel.PixelHeight, vm);
  77. }, DispatcherPriority.Send);
  78. await RenderingFixes(vm, imageModel, null);
  79. return imageModel;
  80. }
  81. private static async Task SideBySideLoadingAsync(MainViewModel vm, FileInfo fileInfo, MagickImage? magickImage)
  82. {
  83. NavigationManager.InitializeImageIterator(vm);
  84. var imageModel = await GetImageModel.GetImageModelAsync(fileInfo, magickImage);
  85. var secondaryPreloadValue = await NavigationManager.GetNextPreLoadValueAsync();
  86. vm.PicViewer.SecondaryImageSource = secondaryPreloadValue?.ImageModel?.Image;
  87. SetPicViewerValues(vm, imageModel, fileInfo);
  88. await RenderingFixes(vm, imageModel, secondaryPreloadValue.ImageModel);
  89. TitleManager.SetSideBySideTitle(vm, imageModel, secondaryPreloadValue?.ImageModel);
  90. // Sometimes the images are not rendered in side by side, this fixes it
  91. // TODO: Improve and fix side by side and remove this hack
  92. Dispatcher.UIThread.Post(() =>
  93. {
  94. vm.ImageViewer?.MainImage?.InvalidateVisual();
  95. });
  96. await StartPreloaderAndGalleryAsync(vm, imageModel, fileInfo);
  97. }
  98. private static void SetPicViewerValues(MainViewModel vm, ImageModel imageModel, FileInfo fileInfo)
  99. {
  100. if (imageModel.ImageType is ImageType.AnimatedGif or ImageType.AnimatedWebp)
  101. {
  102. vm.ImageViewer.MainImage.InitialAnimatedSource = fileInfo.FullName;
  103. }
  104. vm.PicViewer.ImageSource = imageModel.Image;
  105. vm.PicViewer.ImageType = imageModel.ImageType;
  106. vm.ZoomValue = 1;
  107. vm.PicViewer.PixelWidth = imageModel.PixelWidth;
  108. vm.PicViewer.PixelHeight = imageModel.PixelHeight;
  109. vm.PicViewer.ExifOrientation = imageModel.EXIFOrientation;
  110. vm.GetIndex = NavigationManager.GetNonZeroIndex;
  111. }
  112. private static async Task RenderingFixes(MainViewModel vm, ImageModel imageModel, ImageModel? secondaryModel)
  113. {
  114. // When width and height are the same, it renders image incorrectly at startup,
  115. // so need to handle it specially
  116. var is1To1 = imageModel.PixelWidth == imageModel.PixelHeight;
  117. await Dispatcher.UIThread.InvokeAsync(() =>
  118. {
  119. vm.ImageViewer.SetTransform(imageModel.EXIFOrientation, false);
  120. if (Settings.WindowProperties.AutoFit && !Settings.Zoom.ScrollEnabled)
  121. {
  122. SetSize(vm, imageModel, secondaryModel);
  123. WindowFunctions.CenterWindowOnScreen();
  124. }
  125. else if (is1To1)
  126. {
  127. var size = WindowResizing.GetSize(imageModel.PixelWidth, imageModel.PixelHeight,
  128. secondaryModel?.PixelWidth ?? 0, secondaryModel?.PixelHeight ?? 0, vm.RotationAngle, vm);
  129. if (!size.HasValue)
  130. {
  131. #if DEBUG
  132. Console.WriteLine($"{nameof(QuickLoadAsync)} {nameof(size)} is null");
  133. #endif
  134. ErrorHandling.ShowStartUpMenu(vm);
  135. return;
  136. }
  137. WindowResizing.SetSize(size.Value, vm);
  138. vm.ImageViewer.MainBorder.Height = size.Value.Width;
  139. vm.ImageViewer.MainBorder.Width = size.Value.Height;
  140. }
  141. else if (imageModel.PixelWidth <= UIHelper.GetMainView.Bounds.Width &&
  142. imageModel.PixelHeight <= UIHelper.GetMainView.Bounds.Height)
  143. {
  144. SetSize(vm, imageModel, secondaryModel);
  145. }
  146. }, DispatcherPriority.Send);
  147. if (Settings.Zoom.ScrollEnabled)
  148. {
  149. // Bad fix for scrolling
  150. // TODO: Implement proper startup scrolling fix
  151. Settings.Zoom.ScrollEnabled = false;
  152. await Dispatcher.UIThread.InvokeAsync(() => SetSize(vm, imageModel, secondaryModel), DispatcherPriority.Render);
  153. Settings.Zoom.ScrollEnabled = true;
  154. await Dispatcher.UIThread.InvokeAsync(() => SetSize(vm, imageModel, secondaryModel), DispatcherPriority.Send);
  155. if (Settings.WindowProperties.AutoFit)
  156. {
  157. await Dispatcher.UIThread.InvokeAsync(() => WindowFunctions.CenterWindowOnScreen());
  158. }
  159. }
  160. }
  161. private static async Task StartPreloaderAndGalleryAsync(MainViewModel vm, ImageModel imageModel, FileInfo fileInfo)
  162. {
  163. // Add recent files, except when browsing archive
  164. if (string.IsNullOrWhiteSpace(TempFileHelper.TempFilePath))
  165. {
  166. FileHistoryManager.Add(fileInfo.FullName);
  167. }
  168. NavigationManager.AddToPreloader(NavigationManager.GetCurrentIndex, imageModel);
  169. var tasks = new List<Task>();
  170. if (NavigationManager.GetCount > 1)
  171. {
  172. if (Settings.UIProperties.IsTaskbarProgressEnabled)
  173. {
  174. await Dispatcher.UIThread.InvokeAsync(() =>
  175. {
  176. vm.PlatformService.SetTaskbarProgress((ulong)NavigationManager.GetCurrentIndex, (ulong)NavigationManager.GetCount);
  177. });
  178. }
  179. tasks.Add(NavigationManager.PreloadAsync());
  180. }
  181. if (Settings.Gallery.IsBottomGalleryShown)
  182. {
  183. bool loadGallery;
  184. if (!vm.IsUIShown)
  185. {
  186. loadGallery = Settings.Gallery.ShowBottomGalleryInHiddenUI;
  187. }
  188. else
  189. {
  190. loadGallery = true;
  191. }
  192. if (loadGallery)
  193. {
  194. vm.GalleryMode = GalleryMode.BottomNoAnimation;
  195. tasks.Add(GalleryLoad.LoadGallery(vm, fileInfo.DirectoryName));
  196. }
  197. }
  198. await Task.WhenAll(tasks).ConfigureAwait(false);
  199. }
  200. private static void SetSize(MainViewModel vm, ImageModel imageModel, ImageModel? secondaryModel)
  201. {
  202. var size = WindowResizing.GetSize(imageModel.PixelWidth, imageModel.PixelHeight, secondaryModel?.PixelWidth ?? 0, secondaryModel?.PixelHeight ?? 0, vm.RotationAngle, vm);
  203. if (!size.HasValue)
  204. {
  205. #if DEBUG
  206. Console.WriteLine($"{nameof(QuickLoadAsync)} {nameof(size)} is null");
  207. #endif
  208. ErrorHandling.ShowStartUpMenu(vm);
  209. return;
  210. }
  211. WindowResizing.SetSize(size.Value, vm);
  212. }
  213. }