diff --git a/src/modules/PowerOCR/PowerOCR/Helpers/ImageMethods.cs b/src/modules/PowerOCR/PowerOCR/Helpers/ImageMethods.cs index 241e1f7433..59ddd4b2c2 100644 --- a/src/modules/PowerOCR/PowerOCR/Helpers/ImageMethods.cs +++ b/src/modules/PowerOCR/PowerOCR/Helpers/ImageMethods.cs @@ -47,51 +47,17 @@ internal sealed class ImageMethods return destination; } - internal static ImageSource GetWindowBoundsImage(Window passedWindow) + internal static ImageSource GetWindowBoundsImage(OCROverlay passedWindow) { - DpiScale dpi = VisualTreeHelper.GetDpi(passedWindow); - int windowWidth = (int)(passedWindow.ActualWidth * dpi.DpiScaleX); - int windowHeight = (int)(passedWindow.ActualHeight * dpi.DpiScaleY); - - System.Windows.Point absPosPoint = passedWindow.GetAbsolutePosition(); - int thisCorrectedLeft = (int)absPosPoint.X; - int thisCorrectedTop = (int)absPosPoint.Y; - - using Bitmap bmp = new(windowWidth, windowHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + Rectangle screenRectangle = passedWindow.GetScreenRectangle(); + using Bitmap bmp = new(screenRectangle.Width, screenRectangle.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); using Graphics g = Graphics.FromImage(bmp); - g.CopyFromScreen(thisCorrectedLeft, thisCorrectedTop, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy); + g.CopyFromScreen(screenRectangle.Left, screenRectangle.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy); return BitmapToImageSource(bmp); } - internal static Bitmap GetWindowBoundsBitmap(Window passedWindow) - { - DpiScale dpi = VisualTreeHelper.GetDpi(passedWindow); - int windowWidth = (int)(passedWindow.ActualWidth * dpi.DpiScaleX); - int windowHeight = (int)(passedWindow.ActualHeight * dpi.DpiScaleY); - - System.Windows.Point absPosPoint = passedWindow.GetAbsolutePosition(); - int thisCorrectedLeft = (int)absPosPoint.X; - int thisCorrectedTop = (int)absPosPoint.Y; - - Bitmap bmp = new( - windowWidth, - windowHeight, - System.Drawing.Imaging.PixelFormat.Format32bppArgb); - using Graphics g = Graphics.FromImage(bmp); - - g.CopyFromScreen( - thisCorrectedLeft, - thisCorrectedTop, - 0, - 0, - bmp.Size, - CopyPixelOperation.SourceCopy); - - return bmp; - } - - internal static Bitmap GetRegionAsBitmap(Window passedWindow, Rectangle selectedRegion) + internal static Bitmap GetRegionAsBitmap(OCROverlay passedWindow, Rectangle selectedRegion) { Bitmap bmp = new( selectedRegion.Width, @@ -99,15 +65,11 @@ internal sealed class ImageMethods System.Drawing.Imaging.PixelFormat.Format32bppArgb); using Graphics g = Graphics.FromImage(bmp); - - System.Windows.Point absPosPoint = passedWindow.GetAbsolutePosition(); - - int thisCorrectedLeft = (int)absPosPoint.X + selectedRegion.Left; - int thisCorrectedTop = (int)absPosPoint.Y + selectedRegion.Top; + Rectangle screenRectangle = passedWindow.GetScreenRectangle(); g.CopyFromScreen( - thisCorrectedLeft, - thisCorrectedTop, + screenRectangle.Left + selectedRegion.Left, + screenRectangle.Top + selectedRegion.Top, 0, 0, bmp.Size, @@ -117,7 +79,7 @@ internal sealed class ImageMethods return bmp; } - internal static async Task GetRegionsText(Window? passedWindow, Rectangle selectedRegion, Language? preferredLanguage) + internal static async Task GetRegionsText(OCROverlay? passedWindow, Rectangle selectedRegion, Language? preferredLanguage) { if (passedWindow is null) { @@ -130,17 +92,15 @@ internal sealed class ImageMethods return resultText != null ? resultText.Trim() : string.Empty; } - internal static async Task GetClickedWord(Window passedWindow, System.Windows.Point clickedPoint, Language? preferredLanguage) + internal static async Task GetClickedWord(OCROverlay passedWindow, System.Windows.Point clickedPoint, Language? preferredLanguage) { - DpiScale dpi = VisualTreeHelper.GetDpi(passedWindow); - Bitmap bmp = new((int)(passedWindow.ActualWidth * dpi.DpiScaleX), (int)(passedWindow.ActualHeight * dpi.DpiScaleY), System.Drawing.Imaging.PixelFormat.Format32bppArgb); + Rectangle screenRectangle = passedWindow.GetScreenRectangle(); + Bitmap bmp = new((int)screenRectangle.Width, (int)passedWindow.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); Graphics g = Graphics.FromImage(bmp); System.Windows.Point absPosPoint = passedWindow.GetAbsolutePosition(); - int thisCorrectedLeft = (int)absPosPoint.X; - int thisCorrectedTop = (int)absPosPoint.Y; - g.CopyFromScreen(thisCorrectedLeft, thisCorrectedTop, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy); + g.CopyFromScreen((int)absPosPoint.X, (int)absPosPoint.Y, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy); System.Windows.Point adjustedPoint = new(clickedPoint.X, clickedPoint.Y); diff --git a/src/modules/PowerOCR/PowerOCR/Helpers/OcrExtensions.cs b/src/modules/PowerOCR/PowerOCR/Helpers/OcrExtensions.cs index 3bce062178..c8a9df644a 100644 --- a/src/modules/PowerOCR/PowerOCR/Helpers/OcrExtensions.cs +++ b/src/modules/PowerOCR/PowerOCR/Helpers/OcrExtensions.cs @@ -62,7 +62,7 @@ namespace PowerOCR.Helpers } } - public static async Task GetRegionsTextAsTableAsync(Window passedWindow, Rectangle regionScaled, Language? language) + public static async Task GetRegionsTextAsTableAsync(OCROverlay passedWindow, Rectangle regionScaled, Language? language) { if (language is null) { diff --git a/src/modules/PowerOCR/PowerOCR/Helpers/WPFExtensionMethods.cs b/src/modules/PowerOCR/PowerOCR/Helpers/WPFExtensionMethods.cs index 5c7ad6a7b5..c038eae96d 100644 --- a/src/modules/PowerOCR/PowerOCR/Helpers/WPFExtensionMethods.cs +++ b/src/modules/PowerOCR/PowerOCR/Helpers/WPFExtensionMethods.cs @@ -37,4 +37,28 @@ public static class WPFExtensionMethods return new Point(r.X, r.Y); } + + public static DpiScale GetDpi(this System.Windows.Forms.Screen screen) + { + var point = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1); + var mon = MonitorFromPoint(point, 2/*MONITOR_DEFAULTTONEAREST*/); + GetDpiForMonitor(mon, DpiType.Effective, out uint dpiX, out uint dpiY); + return new DpiScale(dpiX / 96.0, dpiY / 96.0); + } + + // https://msdn.microsoft.com/library/windows/desktop/dd145062(v=vs.85).aspx + [DllImport("User32.dll")] + private static extern IntPtr MonitorFromPoint([In] System.Drawing.Point pt, [In] uint dwFlags); + + // https://msdn.microsoft.com/library/windows/desktop/dn280510(v=vs.85).aspx + [DllImport("Shcore.dll")] + private static extern IntPtr GetDpiForMonitor([In] IntPtr hmonitor, [In] DpiType dpiType, [Out] out uint dpiX, [Out] out uint dpiY); + + // https://msdn.microsoft.com/library/windows/desktop/dn280511(v=vs.85).aspx + public enum DpiType + { + Effective = 0, + Angular = 1, + Raw = 2, + } } diff --git a/src/modules/PowerOCR/PowerOCR/Helpers/WindowUtilities.cs b/src/modules/PowerOCR/PowerOCR/Helpers/WindowUtilities.cs index c62f900106..9d3fcdc605 100644 --- a/src/modules/PowerOCR/PowerOCR/Helpers/WindowUtilities.cs +++ b/src/modules/PowerOCR/PowerOCR/Helpers/WindowUtilities.cs @@ -24,8 +24,9 @@ public static class WindowUtilities Logger.LogInfo($"Adding Overlays for each screen"); foreach (Screen screen in Screen.AllScreens) { - Logger.LogInfo($"screen {screen}"); - OCROverlay overlay = new(screen.Bounds); + DpiScale dpiScale = screen.GetDpi(); + Logger.LogInfo($"screen {screen}, dpiScale {dpiScale.DpiScaleX}, {dpiScale.DpiScaleY}"); + OCROverlay overlay = new(screen.Bounds, dpiScale); overlay.Show(); ActivateWindow(overlay); diff --git a/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml b/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml index 100b5de9ef..19944b04bf 100644 --- a/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml +++ b/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml @@ -7,8 +7,6 @@ xmlns:p="clr-namespace:PowerOCR.Properties" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" Title="TextExtractor" - Width="200" - Height="200" ui:Design.Background="Transparent" AllowsTransparency="True" Background="Transparent" diff --git a/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml.cs b/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml.cs index 1c7f266169..ffce2e45e4 100644 --- a/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml.cs +++ b/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml.cs @@ -5,10 +5,12 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; +using System.Windows.Interop; using System.Windows.Media; using Common.UI; using ManagedCommon; @@ -42,11 +44,21 @@ public partial class OCROverlay : Window private bool isComboBoxReady; private const double ActiveOpacity = 0.4; private readonly UserSettings userSettings = new(new ThrottledActionInvoker()); + private System.Drawing.Rectangle screenRectangle; + private DpiScale dpiScale; - public OCROverlay(System.Drawing.Rectangle screenRectangle) + [DllImport("user32.dll", SetLastError = true)] + private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int nWidth, int nHeight, bool bRepaint); + + public OCROverlay(System.Drawing.Rectangle screenRectangleParam, DpiScale dpiScaleParam) { - Left = screenRectangle.Left >= 0 ? screenRectangle.Left : screenRectangle.Left + (screenRectangle.Width / 2); - Top = screenRectangle.Top >= 0 ? screenRectangle.Top : screenRectangle.Top + (screenRectangle.Height / 2); + screenRectangle = screenRectangleParam; + dpiScale = dpiScaleParam; + + Left = screenRectangle.Left; + Top = screenRectangle.Top; + Width = screenRectangle.Width / dpiScale.DpiScaleX; + Height = screenRectangle.Height / dpiScale.DpiScaleY; InitializeComponent(); @@ -106,7 +118,6 @@ public partial class OCROverlay : Window private void Window_Loaded(object sender, RoutedEventArgs e) { - WindowState = WindowState.Maximized; FullWindow.Rect = new Rect(0, 0, Width, Height); KeyDown += MainWindow_KeyDown; KeyUp += MainWindow_KeyUp; @@ -119,6 +130,12 @@ public partial class OCROverlay : Window #if DEBUG Topmost = false; #endif + IntPtr hwnd = new WindowInteropHelper(this).Handle; + + // The first move puts it on the correct monitor, which triggers WM_DPICHANGED + // The +1/-1 coerces WPF to update Window.Top/Left/Width/Height in the second move + MoveWindow(hwnd, (int)(screenRectangle.Left + 1), (int)screenRectangle.Top, (int)(screenRectangle.Width - 1), (int)screenRectangle.Height, false); + MoveWindow(hwnd, (int)screenRectangle.Left, (int)screenRectangle.Top, (int)screenRectangle.Width, (int)screenRectangle.Height, true); } private void Window_Unloaded(object sender, RoutedEventArgs e) @@ -476,4 +493,9 @@ public partial class OCROverlay : Window break; } } + + public System.Drawing.Rectangle GetScreenRectangle() + { + return screenRectangle; + } } diff --git a/src/modules/PowerOCR/PowerOCR/app.manifest b/src/modules/PowerOCR/PowerOCR/app.manifest index 8bd61651e3..5358e351b7 100644 --- a/src/modules/PowerOCR/PowerOCR/app.manifest +++ b/src/modules/PowerOCR/PowerOCR/app.manifest @@ -47,6 +47,12 @@ + + true/pm + + PerMonitor + +