ImageSizeCalculationHelper.cs 15 KB

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