0

I want to create an image from a win32 control (ie WindowsFormsHost, Hwndhost , webview2) being displayed in a WPF window.

  1. Am I correct that RenderTargetBitmap will not work for these kinds of controls?

Assuming I am correct, I am looking at the methods used in this GitHub project to make the image. The project wraps the webbrowser control in a ContenControl. It then gets the position of this content control on the screen and uses the Graphics class method CopyFromScreen.

private void CreateScreenshotFromContent()
{
    Point upperLeftPoint = _airspaceContent.PointToScreen(new Point(0, 0));
    var bounds = new System.Drawing.Rectangle((int)(upperLeftPoint.X * _scalingFactor),
                                              (int)(upperLeftPoint.Y * _scalingFactor),
                                              (int)(_airspaceContent.RenderSize.Width * _scalingFactor),
                                              (int)(_airspaceContent.RenderSize.Height * _scalingFactor));

    using (var bitmap = new System.Drawing.Bitmap((int)bounds.Width, (int)bounds.Height))
    {
        using (var g = System.Drawing.Graphics.FromImage(bitmap))
        {
            g.CopyFromScreen(new System.Drawing.Point(bounds.Left, bounds.Top),
                             System.Drawing.Point.Empty,
                             new System.Drawing.Size((int)bounds.Width, (int)bounds.Height));
        }

        _airspaceScreenshot.Source = GetImageSourceFromBitmap(bitmap);
    }
}

// https://stackoverflow.com/questions/5977445/how-to-get-windows-display-settings
[DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
private void GetScalingFactor()
{
    var g = System.Drawing.Graphics.FromHwnd(IntPtr.Zero);
    IntPtr desktop = g.GetHdc();
    int LogicalScreenHeight = GetDeviceCaps(desktop, 10);
    int PhysicalScreenHeight = GetDeviceCaps(desktop, 117);

    float ScreenScalingFactor = (float)PhysicalScreenHeight / (float)LogicalScreenHeight;

    _scalingFactor = ScreenScalingFactor; // 1.25 = 125%
}

public ImageSource GetImageSourceFromBitmap(System.Drawing.Bitmap bitmap)
{
    using (var memory = new MemoryStream())
    {
        bitmap.Save(memory, ImageFormat.Png);
        memory.Position = 0;
        BitmapImage bitmapImage = new BitmapImage();
        bitmapImage.BeginInit();
        bitmapImage.StreamSource = memory;
        bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
        bitmapImage.EndInit();
        return bitmapImage;
    }
}

The GitHub project uses the WebBrowser control. One small complication is I am updating this to the WebView2 control which is newer but is also just a win32 control in WPF.

All works fine if the PC's scaling is 100%. The resulting image almost perfectly matches what the control displays. Unfortunately this does not if scaling is changed.

This is how the window looks with the Webbrowser control with 200% scaling. The WebBrowser control.

The image however is considerable enlarged when the scaling is 200%. The Image at 200%

My project now targets 4.7.2 .Net Framework which if I understand correctly means it is now DPI aware thus I understand I do not need to do anything extra with the app config or manifest. Am I correct Manifest changes like below are no longer needed?

<application xmlns="urn:schemas-microsoft-com:asm.v3">
  <windowsSettings>
    <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
  </windowsSettings>
</application>
emoacht
  • 2,764
  • 1
  • 13
  • 24
darbid
  • 2,545
  • 23
  • 55
  • I am pretty sure you can't use both mechanisms for trying to find screen resolution. Either your application is DPI aware or it isn't. If you set up your app to be DPI aware via manifest, then you *wouldn't* use `GetDeviceCaps` to determine the scale factor, and vice-versa: If your app *isn't* set up to be DPI aware via manifest, then you'd use `GetDeviceCaps`. For example, take a look at this answer: https://stackoverflow.com/a/63387982/1204153 – Andy Mar 05 '21 at 15:51

1 Answers1

0

Assuming _scalingFactor is problematic, I would recommend to use VisualTreeHelper.GetDpi method. In this case, GetScalingFactor method will be like the following:

public void GetScalingFactor()
{
    _scalingFactor = (float)VisualTreeHelper.GetDpi(_airspaceContent).DpiScaleX;
}

I think this class has much room for refactoring though.

emoacht
  • 2,764
  • 1
  • 13
  • 24