namespace PicView.Core.Sizing; public static class ImageSizeCalculationHelper { private const int MinTitleWidth = 250; private const int MaxRotationAngle = 360; private const int MinRotationAngle = 0; public static ImageSize GetImageSize( double imageWidth, double imageHeight, ScreenSize screenSize, double minWidth, double minHeight, double interfaceSize, double rotationAngle, double dpiScaling, double uiTopSize, double uiBottomSize, double galleryHeight, double containerWidth, double containerHeight) { if (imageWidth <= 0 || imageHeight <= 0 || rotationAngle > MaxRotationAngle || rotationAngle < MinRotationAngle) { return ErrorImageSize(minWidth, minHeight, interfaceSize, containerWidth); } // When in fullscreen, we need to capture the entire screen estate var isFullscreen = Settings.WindowProperties.Fullscreen; // When in maximized mode, working area and interface size needs to be taken into consideration var isMaximized = Settings.WindowProperties.Maximized; var scrollEnabled = Settings.Zoom.ScrollEnabled; var stretchImage = Settings.ImageScaling.StretchImage; var autoFit = Settings.WindowProperties.AutoFit; var showBottomGallery = Settings.Gallery.IsBottomGalleryShown; var showInterface = Settings.UIProperties.ShowInterface; var showGalleryInHiddenUI = Settings.Gallery.ShowBottomGalleryInHiddenUI; var borderSpaceHeight = CalculateBorderSpaceHeight(isFullscreen, uiTopSize, uiBottomSize, galleryHeight); var borderSpaceWidth = isFullscreen ? 0 : screenSize.Margin; var workArea = CalculateWorkArea(screenSize, isFullscreen, borderSpaceWidth, borderSpaceHeight, isMaximized); var screenMargin = isFullscreen ? 0 : screenSize.Margin; var (maxAvailableWidth, maxAvailableHeight, adjustedContainerWidth, adjustedContainerHeight) = CalculateMaxImageSize(scrollEnabled, stretchImage, autoFit, workArea.width, workArea.height, screenMargin, imageWidth, imageHeight, dpiScaling, galleryHeight, containerWidth, containerHeight); var margin = CalculateGalleryMargin(showBottomGallery, showInterface, showGalleryInHiddenUI, galleryHeight); var aspectRatio = CalculateAspectRatio(rotationAngle, maxAvailableWidth, maxAvailableHeight, imageWidth, imageHeight); double displayedWidth, displayedHeight, scrollWidth, scrollHeight; if (scrollEnabled) { (displayedWidth, displayedHeight, scrollWidth, scrollHeight) = CalculateScrolledImageSize( isFullscreen, autoFit, screenSize, imageWidth, imageHeight, aspectRatio, adjustedContainerWidth, adjustedContainerHeight, containerHeight, margin, dpiScaling ); } else { displayedWidth = imageWidth * aspectRatio; displayedHeight = imageHeight * aspectRatio; scrollWidth = double.NaN; scrollHeight = double.NaN; } var titleMaxWidth = GetTitleMaxWidth(rotationAngle, displayedWidth, displayedHeight, minWidth, minHeight, interfaceSize, containerWidth); return new ImageSize(displayedWidth, displayedHeight, 0, scrollWidth, scrollHeight, titleMaxWidth, margin, aspectRatio); } private static (double width, double height) CalculateWorkArea(ScreenSize screenSize, bool fullscreen, double borderSpaceWidth, double borderSpaceHeight, bool maximized) { if (fullscreen) { return (screenSize.Width, screenSize.Height); } if (maximized) { return (screenSize.WorkingAreaWidth, screenSize.WorkingAreaHeight); } return (screenSize.WorkingAreaWidth - borderSpaceWidth, screenSize.WorkingAreaHeight - borderSpaceHeight); } private static double CalculateBorderSpaceHeight(bool fullscreen, double uiTop, double uiBottom, double gallery) => fullscreen ? 0 : uiTop + uiBottom + gallery; private static (double maxWidth, double maxHeight, double containerWidth, double containerHeight) CalculateMaxImageSize( bool scrollEnabled, bool stretchImage, bool autoFit, double workAreaWidth, double workAreaHeight, double margin, double width, double height, double dpiScaling, double galleryHeight, double containerWidth, double containerHeight) { if (scrollEnabled) { workAreaWidth -= SizeDefaults.ScrollbarSize * dpiScaling; containerWidth -= SizeDefaults.ScrollbarSize * dpiScaling; return (stretchImage ? workAreaWidth : Math.Min(workAreaWidth - margin, width), workAreaHeight, containerWidth, containerHeight); } // ReSharper disable once InvertIf if (autoFit) { var mw = stretchImage ? workAreaWidth - margin : Math.Min(workAreaWidth - margin, width); var mh = stretchImage ? workAreaHeight - margin : Math.Min(workAreaHeight - margin, height); return (mw, mh, containerWidth, containerHeight); } return ( stretchImage ? containerWidth : Math.Min(containerWidth, width), stretchImage ? containerHeight - galleryHeight : Math.Min(containerHeight - galleryHeight, height), containerWidth, containerHeight ); } private static double CalculateAspectRatio(double rotationAngle, double maxWidth, double maxHeight, double width, double height) { switch (rotationAngle) { case 0: case 180: return Math.Min(maxWidth / width, maxHeight / height); case 90: case 270: return Math.Min(maxWidth / height, maxHeight / width); default: var radians = rotationAngle * Math.PI / 180; var rotatedWidth = Math.Abs(width * Math.Cos(radians)) + Math.Abs(height * Math.Sin(radians)); var rotatedHeight = Math.Abs(width * Math.Sin(radians)) + Math.Abs(height * Math.Cos(radians)); return Math.Min(maxWidth / rotatedWidth, maxHeight / rotatedHeight); } } private static double CalculateGalleryMargin(bool isBottomGalleryShown, bool showInterface, bool showGalleryInHidden, double galleryHeight) { if (!isBottomGalleryShown) { return 0; } if (!showInterface) { return showGalleryInHidden && galleryHeight > 0 ? galleryHeight : 0; } return galleryHeight > 0 ? galleryHeight : 0; } private static (double width, double height, double scrollWidth, double scrollHeight) CalculateScrolledImageSize( bool fullscreen, bool autoFit, ScreenSize screenSize, double width, double height, double aspectRatio, double containerWidth, double containerHeight, double origContainerHeight, double margin, double dpiScaling) { if (fullscreen) { return (width * aspectRatio, height * aspectRatio, screenSize.Width, screenSize.Height); } if (autoFit) { var imgWidth = width * aspectRatio; var imgHeight = height * aspectRatio; var sw = Math.Max(imgWidth + SizeDefaults.ScrollbarSize, SizeDefaults.WindowMinSize + SizeDefaults.ScrollbarSize + screenSize.Margin + 16); var sh = origContainerHeight - margin; return (imgWidth, imgHeight, sw, sh); } var cWidth = containerWidth - SizeDefaults.ScrollbarSize + 10; var cHeight = height / width * cWidth; var sWidth = containerWidth + SizeDefaults.ScrollbarSize; var sHeight = origContainerHeight - margin; return (cWidth, cHeight, sWidth, sHeight); } public static ImageSize GetSideBySideImageSize( double width, double height, double secondaryWidth, double secondaryHeight, ScreenSize screenSize, double minWidth, double minHeight, double interfaceSize, double rotationAngle, double dpiScaling, double uiTopSize, double uiBottomSize, double galleryHeight, double containerWidth, double containerHeight) { if (width <= 0 || height <= 0 || secondaryWidth <= 0 || secondaryHeight <= 0 || rotationAngle > MaxRotationAngle || rotationAngle < MinRotationAngle) { return ErrorImageSize(minWidth, minHeight, interfaceSize, containerWidth); } // Get sizes for both images var firstSize = GetImageSize(width, height, screenSize, minWidth, minHeight, interfaceSize, rotationAngle, dpiScaling, uiTopSize, uiBottomSize, galleryHeight, containerWidth, containerHeight); var secondSize = GetImageSize(secondaryWidth, secondaryHeight, screenSize, minWidth, minHeight, interfaceSize, rotationAngle, dpiScaling, uiTopSize, uiBottomSize, galleryHeight, containerWidth, containerHeight); // Determine maximum height for both images var xHeight = Math.Max(firstSize.Height, secondSize.Height); // Recalculate the widths to maintain the aspect ratio with the new maximum height var xWidth1 = firstSize.Width / firstSize.Height * xHeight; var xWidth2 = secondSize.Width / secondSize.Height * xHeight; // Combined width of both images var combinedWidth = xWidth1 + xWidth2; if (Settings.WindowProperties.AutoFit) { var widthPadding = Settings.ImageScaling.StretchImage ? 4 : screenSize.Margin; var availableWidth = screenSize.WorkingAreaWidth - widthPadding; var availableHeight = screenSize.WorkingAreaHeight - (widthPadding + uiBottomSize + uiTopSize); if (rotationAngle is 0 or 180) { // If combined width exceeds available width, scale both images down proportionally if (combinedWidth > availableWidth) { var scaleFactor = availableWidth / combinedWidth; xWidth1 *= scaleFactor; xWidth2 *= scaleFactor; xHeight *= scaleFactor; combinedWidth = xWidth1 + xWidth2; } } else { if (combinedWidth > availableHeight) { var scaleFactor = availableHeight / combinedWidth; xWidth1 *= scaleFactor; xWidth2 *= scaleFactor; xHeight *= scaleFactor; combinedWidth = xWidth1 + xWidth2; } } } else { if (rotationAngle is 0 or 180) { if (combinedWidth > containerWidth) { var scaleFactor = containerWidth / combinedWidth; xWidth1 *= scaleFactor; xWidth2 *= scaleFactor; xHeight *= scaleFactor; combinedWidth = xWidth1 + xWidth2; } } else { if (combinedWidth > containerHeight) { var scaleFactor = containerHeight / combinedWidth; xWidth1 *= scaleFactor; xWidth2 *= scaleFactor; xHeight *= scaleFactor; combinedWidth = xWidth1 + xWidth2; } } } double scrollWidth, scrollHeight; if (Settings.Zoom.ScrollEnabled) { if (Settings.WindowProperties.AutoFit) { combinedWidth -= SizeDefaults.ScrollbarSize; scrollWidth = combinedWidth + SizeDefaults.ScrollbarSize + 8; var fullscreen = Settings.WindowProperties.Fullscreen || Settings.WindowProperties.Maximized; var borderSpaceHeight = fullscreen ? 0 : uiTopSize + uiBottomSize + galleryHeight; var workAreaHeight = screenSize.WorkingAreaHeight * dpiScaling - borderSpaceHeight; scrollHeight = Math.Min(xHeight, Settings.ImageScaling.StretchImage ? workAreaHeight : workAreaHeight - screenSize.Margin); } else { combinedWidth -= SizeDefaults.ScrollbarSize + 8; scrollWidth = double.NaN; scrollHeight = double.NaN; } } else { scrollWidth = double.NaN; scrollHeight = double.NaN; } var titleMaxWidth = GetTitleMaxWidth(rotationAngle, combinedWidth, xHeight, minWidth, minHeight, interfaceSize, containerWidth); var margin = firstSize.Height > secondSize.Height ? firstSize.Margin : secondSize.Margin; return new ImageSize(combinedWidth, xHeight, xWidth2, scrollWidth, scrollHeight, titleMaxWidth, margin, firstSize.AspectRatio); } public static double GetTitleMaxWidth(double rotationAngle, double width, double height, double monitorMinWidth, double monitorMinHeight, double interfaceSize, double containerWidth) { double titleMaxWidth; if (Settings.WindowProperties.AutoFit && !Settings.WindowProperties.Maximized) { switch (rotationAngle) { case 0 or 180: titleMaxWidth = Math.Max(width, monitorMinWidth); break; case 90 or 270: titleMaxWidth = Math.Max(height, monitorMinHeight); break; default: { var rotationRadians = rotationAngle * Math.PI / 180; var newWidth = Math.Abs(width * Math.Cos(rotationRadians)) + Math.Abs(height * Math.Sin(rotationRadians)); titleMaxWidth = Math.Max(newWidth, monitorMinWidth); break; } } titleMaxWidth = titleMaxWidth - interfaceSize < MinTitleWidth ? MinTitleWidth : titleMaxWidth - interfaceSize; } else { // Fix title width to window size titleMaxWidth = containerWidth - interfaceSize; } if (!Settings.Zoom.ScrollEnabled) { return titleMaxWidth; } if (Settings.ImageScaling.ShowImageSideBySide) { return titleMaxWidth + (SizeDefaults.ScrollbarSize + 10); } return titleMaxWidth + SizeDefaults.ScrollbarSize; } private static ImageSize ErrorImageSize(double monitorMinWidth, double monitorMinHeight, double interfaceSize, double containerWidth) => new(0, 0, 0, 0, 0, GetTitleMaxWidth(0, 0, 0, monitorMinWidth, monitorMinHeight, interfaceSize, containerWidth), 0, 0); }