ImageSizeCalculationHelper.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. using System.Runtime.InteropServices;
  2. namespace PicView.Core.Sizing;
  3. public static class ImageSizeCalculationHelper
  4. {
  5. private const int MinTitleWidth = 250;
  6. private const int Padding = 45;
  7. /// <summary>
  8. /// Returns the interface size of the titlebar based on OS
  9. /// </summary>
  10. public static double GetInterfaceSize()
  11. {
  12. // TODO: find a more elegant solution
  13. return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 165 : 215;
  14. }
  15. public static double GetWindowMargin()
  16. {
  17. // TODO: find a more elegant solution
  18. return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 7 : 0;
  19. }
  20. public static ImageSize GetImageSize(double width,
  21. double height,
  22. ScreenSize screenSize,
  23. double minWidth,
  24. double minHeight,
  25. double interfaceSize,
  26. double rotationAngle,
  27. double dpiScaling,
  28. double uiTopSize,
  29. double uiBottomSize,
  30. double galleryHeight,
  31. double containerWidth,
  32. double containerHeight)
  33. {
  34. if (width <= 0 || height <= 0 || rotationAngle > 360 || rotationAngle < 0)
  35. {
  36. return ErrorImageSize(minWidth, minHeight, interfaceSize, containerWidth);
  37. }
  38. double aspectRatio;
  39. double maxWidth, maxHeight;
  40. var margin = 0d;
  41. var fullscreen = Settings.WindowProperties.Fullscreen ||
  42. Settings.WindowProperties.Maximized;
  43. var borderSpaceHeight = fullscreen ? 0 : uiTopSize + uiBottomSize + galleryHeight;
  44. var borderSpaceWidth = fullscreen ? 0 : Padding;
  45. var workAreaWidth = screenSize.WorkingAreaWidth - borderSpaceWidth;
  46. var workAreaHeight = screenSize.WorkingAreaHeight - borderSpaceHeight;
  47. if (Settings.Zoom.ScrollEnabled)
  48. {
  49. workAreaWidth -= SizeDefaults.ScrollbarSize * dpiScaling;
  50. containerWidth -= SizeDefaults.ScrollbarSize * dpiScaling;
  51. maxWidth = Settings.ImageScaling.StretchImage
  52. ? workAreaWidth
  53. : Math.Min(workAreaWidth - Padding, width);
  54. maxHeight = workAreaHeight;
  55. }
  56. else if (Settings.WindowProperties.AutoFit)
  57. {
  58. maxWidth = Settings.ImageScaling.StretchImage
  59. ? workAreaWidth - Padding
  60. : Math.Min(workAreaWidth - Padding, width);
  61. maxHeight = Settings.ImageScaling.StretchImage
  62. ? workAreaHeight - Padding
  63. : Math.Min(workAreaHeight - Padding, height);
  64. }
  65. else
  66. {
  67. maxWidth = Settings.ImageScaling.StretchImage
  68. ? containerWidth
  69. : Math.Min(containerWidth, width);
  70. maxHeight = Settings.ImageScaling.StretchImage
  71. ? containerHeight - galleryHeight
  72. : Math.Min(containerHeight - galleryHeight, height);
  73. }
  74. if (Settings.Gallery.IsBottomGalleryShown)
  75. {
  76. if (!Settings.UIProperties.ShowInterface)
  77. {
  78. if (Settings.Gallery.ShowBottomGalleryInHiddenUI)
  79. {
  80. margin = galleryHeight > 0 ? galleryHeight : 0;
  81. }
  82. else
  83. {
  84. margin = 0;
  85. }
  86. }
  87. else
  88. {
  89. margin = galleryHeight > 0 ? galleryHeight : 0;
  90. }
  91. }
  92. // aspect ratio calculation
  93. switch (rotationAngle)
  94. {
  95. case 0:
  96. case 180:
  97. aspectRatio = Math.Min(maxWidth / width, maxHeight / height);
  98. break;
  99. case 90:
  100. case 270:
  101. aspectRatio = Math.Min(maxWidth / height, maxHeight / width);
  102. break;
  103. default:
  104. var rotationRadians = rotationAngle * Math.PI / 180;
  105. var newWidth = Math.Abs(width * Math.Cos(rotationRadians)) +
  106. Math.Abs(height * Math.Sin(rotationRadians));
  107. var newHeight = Math.Abs(width * Math.Sin(rotationRadians)) +
  108. Math.Abs(height * Math.Cos(rotationRadians));
  109. aspectRatio = Math.Min(maxWidth / newWidth, maxHeight / newHeight);
  110. break;
  111. }
  112. // Fit image by aspect ratio calculation
  113. // and update values
  114. double scrollWidth, scrollHeight, xWidth, xHeight;
  115. if (Settings.Zoom.ScrollEnabled)
  116. {
  117. if (fullscreen)
  118. {
  119. xWidth = width * aspectRatio;
  120. xHeight = height * aspectRatio;
  121. scrollWidth = screenSize.Width;
  122. scrollHeight = screenSize.Height;
  123. }
  124. else if (Settings.WindowProperties.AutoFit)
  125. {
  126. xWidth = width * aspectRatio;
  127. xHeight = height * aspectRatio;
  128. scrollWidth = Math.Max(xWidth + SizeDefaults.ScrollbarSize, SizeDefaults.WindowMinSize + SizeDefaults.ScrollbarSize + Padding + 16);
  129. scrollHeight = containerHeight - margin;
  130. }
  131. else
  132. {
  133. xWidth = containerWidth - SizeDefaults.ScrollbarSize + 10;
  134. xHeight = height / width * xWidth;
  135. scrollWidth = containerWidth + SizeDefaults.ScrollbarSize;
  136. scrollHeight = containerHeight - margin;
  137. }
  138. }
  139. else
  140. {
  141. scrollWidth = double.NaN;
  142. scrollHeight = double.NaN;
  143. xWidth = width * aspectRatio;
  144. xHeight = height * aspectRatio;
  145. }
  146. var titleMaxWidth = GetTitleMaxWidth(rotationAngle, xWidth, xHeight, minWidth, minHeight,
  147. interfaceSize, containerWidth);
  148. return new ImageSize(xWidth, xHeight, 0, scrollWidth, scrollHeight, titleMaxWidth, margin, aspectRatio);
  149. }
  150. public static ImageSize GetSideBySideImageSize(double width,
  151. double height,
  152. double secondaryWidth,
  153. double secondaryHeight,
  154. ScreenSize screenSize,
  155. double minWidth,
  156. double minHeight,
  157. double interfaceSize,
  158. double rotationAngle,
  159. double dpiScaling,
  160. double uiTopSize,
  161. double uiBottomSize,
  162. double galleryHeight,
  163. double containerWidth,
  164. double containerHeight)
  165. {
  166. if (width <= 0 || height <= 0 || secondaryWidth <= 0 || secondaryHeight <= 0 || rotationAngle > 360 ||
  167. rotationAngle < 0)
  168. {
  169. return ErrorImageSize(minWidth, minHeight, interfaceSize, containerWidth);
  170. }
  171. // Get sizes for both images
  172. var firstSize = GetImageSize(width, height, screenSize, minWidth, minHeight,
  173. interfaceSize, rotationAngle, dpiScaling, uiTopSize, uiBottomSize, galleryHeight,
  174. containerWidth,
  175. containerHeight);
  176. var secondSize = GetImageSize(secondaryWidth, secondaryHeight, screenSize, minWidth,
  177. minHeight, interfaceSize, rotationAngle, dpiScaling, uiTopSize, uiBottomSize,
  178. galleryHeight,
  179. containerWidth, containerHeight);
  180. // Determine maximum height for both images
  181. var xHeight = Math.Max(firstSize.Height, secondSize.Height);
  182. // Recalculate the widths to maintain the aspect ratio with the new maximum height
  183. var xWidth1 = firstSize.Width / firstSize.Height * xHeight;
  184. var xWidth2 = secondSize.Width / secondSize.Height * xHeight;
  185. // Combined width of both images
  186. var combinedWidth = xWidth1 + xWidth2;
  187. if (Settings.WindowProperties.AutoFit)
  188. {
  189. var widthPadding = Settings.ImageScaling.StretchImage ? 4 : Padding;
  190. var availableWidth = screenSize.WorkingAreaWidth - widthPadding;
  191. var availableHeight = screenSize.WorkingAreaHeight - (widthPadding + uiBottomSize + uiTopSize);
  192. if (rotationAngle is 0 or 180)
  193. {
  194. // If combined width exceeds available width, scale both images down proportionally
  195. if (combinedWidth > availableWidth)
  196. {
  197. var scaleFactor = availableWidth / combinedWidth;
  198. xWidth1 *= scaleFactor;
  199. xWidth2 *= scaleFactor;
  200. xHeight *= scaleFactor;
  201. combinedWidth = xWidth1 + xWidth2;
  202. }
  203. }
  204. else
  205. {
  206. if (combinedWidth > availableHeight)
  207. {
  208. var scaleFactor = availableHeight / combinedWidth;
  209. xWidth1 *= scaleFactor;
  210. xWidth2 *= scaleFactor;
  211. xHeight *= scaleFactor;
  212. combinedWidth = xWidth1 + xWidth2;
  213. }
  214. }
  215. }
  216. else
  217. {
  218. if (rotationAngle is 0 or 180)
  219. {
  220. if (combinedWidth > containerWidth)
  221. {
  222. var scaleFactor = containerWidth / combinedWidth;
  223. xWidth1 *= scaleFactor;
  224. xWidth2 *= scaleFactor;
  225. xHeight *= scaleFactor;
  226. combinedWidth = xWidth1 + xWidth2;
  227. }
  228. }
  229. else
  230. {
  231. if (combinedWidth > containerHeight)
  232. {
  233. var scaleFactor = containerHeight / combinedWidth;
  234. xWidth1 *= scaleFactor;
  235. xWidth2 *= scaleFactor;
  236. xHeight *= scaleFactor;
  237. combinedWidth = xWidth1 + xWidth2;
  238. }
  239. }
  240. }
  241. double scrollWidth, scrollHeight;
  242. if (Settings.Zoom.ScrollEnabled)
  243. {
  244. if (Settings.WindowProperties.AutoFit)
  245. {
  246. combinedWidth -= SizeDefaults.ScrollbarSize;
  247. scrollWidth = combinedWidth + SizeDefaults.ScrollbarSize + 8;
  248. var fullscreen = Settings.WindowProperties.Fullscreen ||
  249. Settings.WindowProperties.Maximized;
  250. var borderSpaceHeight = fullscreen ? 0 : uiTopSize + uiBottomSize + galleryHeight;
  251. var workAreaHeight = screenSize.WorkingAreaHeight * dpiScaling - borderSpaceHeight;
  252. scrollHeight = Math.Min(xHeight,
  253. Settings.ImageScaling.StretchImage ? workAreaHeight : workAreaHeight - Padding);
  254. }
  255. else
  256. {
  257. combinedWidth -= SizeDefaults.ScrollbarSize + 8;
  258. scrollWidth = double.NaN;
  259. scrollHeight = double.NaN;
  260. }
  261. }
  262. else
  263. {
  264. scrollWidth = double.NaN;
  265. scrollHeight = double.NaN;
  266. }
  267. var titleMaxWidth = GetTitleMaxWidth(rotationAngle, combinedWidth, xHeight, minWidth,
  268. minHeight, interfaceSize, containerWidth);
  269. var margin = firstSize.Height > secondSize.Height ? firstSize.Margin : secondSize.Margin;
  270. return new ImageSize(combinedWidth, xHeight, xWidth2, scrollWidth, scrollHeight, titleMaxWidth, margin,
  271. firstSize.AspectRatio);
  272. }
  273. public static double GetTitleMaxWidth(double rotationAngle, double width, double height, double monitorMinWidth,
  274. double monitorMinHeight, double interfaceSize, double containerWidth)
  275. {
  276. if (Settings.WindowProperties.Fullscreen)
  277. {
  278. // Title bar is not shown in fullscreen
  279. return 0;
  280. }
  281. double titleMaxWidth;
  282. if (Settings.WindowProperties.AutoFit && !Settings.WindowProperties.Maximized)
  283. {
  284. switch (rotationAngle)
  285. {
  286. case 0 or 180:
  287. titleMaxWidth = Math.Max(width, monitorMinWidth);
  288. break;
  289. case 90 or 270:
  290. titleMaxWidth = Math.Max(height, monitorMinHeight);
  291. break;
  292. default:
  293. {
  294. var rotationRadians = rotationAngle * Math.PI / 180;
  295. var newWidth = Math.Abs(width * Math.Cos(rotationRadians)) +
  296. Math.Abs(height * Math.Sin(rotationRadians));
  297. titleMaxWidth = Math.Max(newWidth, monitorMinWidth);
  298. break;
  299. }
  300. }
  301. titleMaxWidth = titleMaxWidth - interfaceSize < MinTitleWidth
  302. ? MinTitleWidth
  303. : titleMaxWidth - interfaceSize;
  304. }
  305. else
  306. {
  307. if (Settings.WindowProperties.Maximized)
  308. {
  309. interfaceSize += GetWindowMargin() * 2;
  310. }
  311. // Fix title width to window size
  312. titleMaxWidth = containerWidth - interfaceSize;
  313. }
  314. if (!Settings.Zoom.ScrollEnabled)
  315. {
  316. return titleMaxWidth;
  317. }
  318. if (Settings.ImageScaling.ShowImageSideBySide)
  319. {
  320. return titleMaxWidth + (SizeDefaults.ScrollbarSize + 10);
  321. }
  322. return titleMaxWidth + SizeDefaults.ScrollbarSize;
  323. }
  324. private static ImageSize ErrorImageSize(double monitorMinWidth, double monitorMinHeight, double interfaceSize, double containerWidth)
  325. => new(0, 0, 0, 0, 0, GetTitleMaxWidth(0, 0, 0, monitorMinWidth,
  326. monitorMinHeight, interfaceSize, containerWidth), 0, 0);
  327. public readonly struct ImageSize(
  328. double width,
  329. double height,
  330. double secondaryWidth,
  331. double scrollViewerWidth,
  332. double scrollViewerHeight,
  333. double titleMaxWidth,
  334. double margin,
  335. double aspectRatio)
  336. {
  337. public double TitleMaxWidth { get; } = titleMaxWidth;
  338. public double Width { get; } = width;
  339. public double Height { get; } = height;
  340. public double ScrollViewerWidth { get; } = scrollViewerWidth;
  341. public double ScrollViewerHeight { get; } = scrollViewerHeight;
  342. public double SecondaryWidth { get; } = secondaryWidth;
  343. public double Margin { get; } = margin;
  344. public double AspectRatio { get; } = aspectRatio;
  345. }
  346. }