RenderTargetBitmap messes up the WebView scaling

317 Views Asked by At

I have a very simple repro case of RenderTargetBitmap.RenderAsync overload messing up the WebView scaling. All I have on the MainPage is a WebView and a button:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <WebView Source="http://bing.com"></WebView>
    <Button Content="Render me"
            HorizontalAlignment="Right"
            VerticalAlignment="Bottom"
            Click="ButtonBase_OnClick" />
</Grid>

In code behind there's only a simple event handler

private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    RenderTargetBitmap rtb = new RenderTargetBitmap();
    await rtb.RenderAsync(this, 1280, 720);
}

This is what the page looks like before RenderAsync call:

Before RenderAsync

and this is what happens after the call:

After RenderAsync

Any idea why and how to prevent this? Note that it only happens if I call

await rtb.RenderAsync(this, 1280, 720);

but NOT if I call the overload without the scaling

await rtb.RenderAsync(this);

EDIT: Due to the first answer I received, I wanted to clarify why the aspect ratio is not the problem here, but only serves the purpose of proving that there actually is a problem. Think of the following scenario - very high DPI screen where only a lower resolution screenshot is needed - even if you scale it down with the RIGHT ratio, it still messes up the WebView. Also, for my scenario, resizing the screenshot manually afterwards is not an option - the RenderAsync overload with scaled dimensions is much much faster and I would really prefer to use that method.

3

There are 3 best solutions below

2
On

Instead of using fixed values, use VisibleBounds to get the current window size.

Here's the code:

private async void pressMe_Click(object sender, RoutedEventArgs e)
{
    var windowBounds = ApplicationView.GetForCurrentView().VisibleBounds;
    RenderTargetBitmap rtb = new RenderTargetBitmap();
    await rtb.RenderAsync(this, (int)windowBounds.Width, (int)windowBounds.Height);
}
1
On

Very strange behavior... I found one very dirty (!) fix to this. I basically hide and show the webview (wv) again.

private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    RenderTargetBitmap rtb = new RenderTargetBitmap();
    await rtb.RenderAsync(wv, 1280, 720);
    wv.Visibility = Visibility.Collapsed;
    await Task.Delay(100);
    wv.Visibility = Visibility.Visible;
}

I'm not proud of this solution and the webview flashes, but at least it's not 'blown up' any more...

0
On

This is a bit of a hack too, but I found that if you set the contents of another control through a WebViewBrush and then render that control, then the source WebView doesn't get any scaling. I have modified the XAML you provided so it looks like this:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <Border x:Name="Target" Width="1280" Height="720" />
    <WebView x:Name="webView"  Source="http://bing.com" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></WebView>

    <Button Content="Render me" Grid.Row="1"
        HorizontalAlignment="Right"
        VerticalAlignment="Bottom"
        Click="ButtonBase_OnClick" />

</Grid>

In your case, you should opt to set the Border control behind your WebView (however, don't change its Visibility or put it outside of the window bounds, as RenderAsync will fail). Then, on the code behind, set the Background of the target control to an instance of a WebViewBrush that feeds on the WebView:

    private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        WebViewBrush brush = new WebViewBrush();
        brush.SetSource(webView);
        Target.Background = brush;
        Target.InvalidateMeasure();
        Target.InvalidateArrange();

        RenderTargetBitmap rtb = new RenderTargetBitmap();
        await rtb.RenderAsync(Target, 1280, 720);
        var pixels = await rtb.GetPixelsAsync();
    }

You will get your final image without any issues caused to the source WebView (however, note that the final image will look distorted since the aspect ratios don't match). However this comes with a few caveats, the most important one being that the WebView size must match the one of the RenderTargetBitmap or you will get empty areas.