Wpf: RenderTargetBitmap with PngBitmapEncoder Generates Empty Image in Windows Service

Created on 26 Mar 2020  路  3Comments  路  Source: dotnet/wpf

  • .NET Core Version: 3.1.102
  • Windows version: 1903
  • Does the bug reproduce also in WPF for .NET Framework 4.8?: No
  • Is this bug related specifically to tooling in Visual Studio (e.g. XAML Designer, Code editing, etc...)? No

    Problem description:
    I use RenderTargetBitmap with PngBitmapEncoder to XML to a PNG. When trying to do this as a Windows Service using Core the output image is empty. It works with .Net Framework with identical code. It works in Core if running as console app.

The use case is part of some enterprise software that generates reports to PDFs. As part of a technology migration we are changing these to be generated on-demand from APIs. One component of the system needs to create individual parts as PNGs. This has to run as a Windows Service. The general concept works fine in .Net Framework as well as a Console App in .Net Core. It only doesn't work when operating as a Windows Service with Core.

Does not appear permissions related - have even attempted to run as NT AUTHORITY\SYSTEM and granted "Interactive Desktop" service (plus associated registry key) in attempt to diagnose.

The attached code is a much more basic cut-down version that is reproducible when attached to a service in .Net Core or Framework.

Actual behavior:
Generated PNG is an empty image - fully transparent - but of correct size.

Expected behavior:
The WPF controls should have been rendered as expected.

Minimal repro:
This example is a very basic ServiceBase implementation that can be used in Framework or Core (with Microsoft.Windows.Compatibility). It expects you to put some text in a new file in c:\rendertests\core named something.req and it will then output something.png.

Note: Although using Microsoft.Windows.Compatibility for Core here for ease of test the same result is observed if using workers/host builder and UseWindowsServce().

```using System;
using System.IO;
using System.ServiceProcess;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace RenderCore
{
public class Renderer : ServiceBase
{
FileSystemWatcher fsw;
public Renderer()
{
ServiceName = "Core Render Test";

        fsw = new FileSystemWatcher(@"c:\rendertests\core", "*.req");
        fsw.Created += OnFileCreate;
    }

    public void Start()
    {
        fsw.EnableRaisingEvents = true;
    }

    protected override void OnStart(string[] args)
    {
        Start();
    }

    protected override void OnStop()
    {
        fsw.EnableRaisingEvents = false;
    }

    private void OnFileCreate(object sender, FileSystemEventArgs e)
    {
        var outputPath = @"c:\rendertests\core\" + Path.GetFileNameWithoutExtension(e.FullPath) + ".png";
        System.Threading.Thread.Sleep(100);
        var text = File.ReadAllText(e.FullPath);
        DoRender(500, 500, text, outputPath);
        File.Delete(e.FullPath);
    }

    private  void DoRender(int width, int height, string text, string outputPath)
    {

        var thread = new Thread(
            () =>
            {
                var grid = new Grid
                {
                    Background = Brushes.Red,
                    Width = width,
                    Height = height
                };
                Canvas.SetLeft(grid, 0);
                Canvas.SetTop(grid, 0);

                var textBlock = new TextBlock
                {
                    Text = "Test: " + text,
                    Margin = new Thickness(10),
                    FontSize = 32
                };
                grid.Children.Add(textBlock);

                grid.Measure(new Size(grid.Width, grid.Height));
                grid.Arrange(new Rect(new Size(grid.Width, grid.Height)));
                grid.UpdateLayout();

                var renderTargetBitmap = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Default);
                renderTargetBitmap.Render(grid);
                var pngBitmapEncoder = new PngBitmapEncoder();
                pngBitmapEncoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
                byte[] fileBytes;
                using (var stream = new MemoryStream())
                {
                    pngBitmapEncoder.Save(stream);
                    fileBytes = stream.ToArray();
                }

                grid.DataContext = null;
                GC.Collect();

                File.WriteAllBytes(outputPath, fileBytes);
            });
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        thread.Join();
    }
}

}
```

issue-type-bug tenet-compatibility

Most helpful comment

Are aware that running WPF inside a Windows Service is not fully supported ?

BTW, Please take a look at CoreAppContextSwitches.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable and report back if that helps.

runtimeconfig.template.json snippet:

    // Desktop/Interactive Window Stations:
    //  Rendering will be throttled back/stopped when no display devices are available. For e.g., when a TS 
    //  session is in WTSDisconnected state, the OS may not provide any display devices in response to our enumeration.
    //
    //  If an application would like to continue rendering in the absence of display devices (accepting that 
    //  it can lead to a CPU spike), it can set 
    //  to true.
    //
    // Service/Non-interactive Window Stations
    //  Rendering will continue by default, irrespective of the presence of display devices.Unless the WPF
    //  API's being used are short-lived (like rendering to a bitmap), it can lead to a CPU spike. 
    //  If an application running inside a service would like to receive the 'default' WPF behavior, 
    //  i.e., no rendering in the absence of display devices, then it should set
    //  to true
    //
    // In pseudo-code, 
    //  IsNonInteractiveWindowStation = !Environment.UserInteractive
    //    IF DisplayDevicesNotFound() THEN
    //      IF IsNonInteractiveWindowStation THEN 
    //        // We are inside a SCM service
    //        // Default = True, AppContext switch can override it to False
    //        ShouldRender = !CoreAppContextSwitches.ShouldNotRenderInNonInteractiveWindowStation
    //      ELSE 
    //        // Desktop/interactive mode, including WTSDisconnected scenarios
    //        // Default = False, AppContext switch can override it to True
    //        ShouldRender = CoreAppContextSwitches.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable
    //        END IF
    //      END IF"
    "Switch.System.Windows.Media.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable": false,
    "Switch.System.Windows.Media.ShouldNotRenderInNonInteractiveWindowStation": false,

Related discussion: https://github.com/dotnet/winforms/issues/2952#issuecomment-595880301.

All 3 comments

Are aware that running WPF inside a Windows Service is not fully supported ?

BTW, Please take a look at CoreAppContextSwitches.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable and report back if that helps.

runtimeconfig.template.json snippet:

    // Desktop/Interactive Window Stations:
    //  Rendering will be throttled back/stopped when no display devices are available. For e.g., when a TS 
    //  session is in WTSDisconnected state, the OS may not provide any display devices in response to our enumeration.
    //
    //  If an application would like to continue rendering in the absence of display devices (accepting that 
    //  it can lead to a CPU spike), it can set 
    //  to true.
    //
    // Service/Non-interactive Window Stations
    //  Rendering will continue by default, irrespective of the presence of display devices.Unless the WPF
    //  API's being used are short-lived (like rendering to a bitmap), it can lead to a CPU spike. 
    //  If an application running inside a service would like to receive the 'default' WPF behavior, 
    //  i.e., no rendering in the absence of display devices, then it should set
    //  to true
    //
    // In pseudo-code, 
    //  IsNonInteractiveWindowStation = !Environment.UserInteractive
    //    IF DisplayDevicesNotFound() THEN
    //      IF IsNonInteractiveWindowStation THEN 
    //        // We are inside a SCM service
    //        // Default = True, AppContext switch can override it to False
    //        ShouldRender = !CoreAppContextSwitches.ShouldNotRenderInNonInteractiveWindowStation
    //      ELSE 
    //        // Desktop/interactive mode, including WTSDisconnected scenarios
    //        // Default = False, AppContext switch can override it to True
    //        ShouldRender = CoreAppContextSwitches.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable
    //        END IF
    //      END IF"
    "Switch.System.Windows.Media.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable": false,
    "Switch.System.Windows.Media.ShouldNotRenderInNonInteractiveWindowStation": false,

Related discussion: https://github.com/dotnet/winforms/issues/2952#issuecomment-595880301.

@vatsan-madhavan Thank you. I was not aware of the CoreAppContextSwitches.

Setting ShouldRenderEvenWhenNoDisplayDevicesAreAvailable to true resolves the issue.

Aware that WPF in this scenario isn't fully supported but aiming to minimise Framework components remaining before we fully migrate away from WPF for this scenario.

@guytp Great to hear that you are unblocked!

Was this page helpful?
0 / 5 - 0 ratings