21

I did not expect

RenderTargetBitmap.Render(visual)

to have any side effects excerpt changing the bitmap data itself. It looks like it is not true. I am unable to repeat it more than 60 times before some ugly rendering artifacts start to happen.

How to properly render a lot of sprites in WPF? Below is the code to reproduce the problem.

I generate sprites this way:

    BitmapSource Sprite()
    {
        var bitmap = new RenderTargetBitmap(
            500, 500,
            96, 96,
            PixelFormats.Default);

        var visual = new DrawingVisual();
        var rect = new Rect(
                    new Size(
                        bitmap.Width,
                        bitmap.Height));

        using (DrawingContext context = visual.RenderOpen())
            context.DrawLine(
                new Pen(Brushes.Red, 100),
                rect.TopLeft,
                rect.BottomRight);

        bitmap.Render(visual);
        bitmap.Freeze();
        return bitmap;
    }

Here is the canvas to render many of them:

    public BitmapSource Canvas
    {
        get
        {
            var bitmap = new RenderTargetBitmap(
                1980, 1080,
                96, 96,
                PixelFormats.Default);

            var tiles = 70;
            for (int i = 0; i < tiles; i++)
            {
                var visual = new DrawingVisual();
                var rect = new Rect(
                    bitmap.Width / tiles * i,
                    0,
                    bitmap.Width / tiles,
                    bitmap.Height);

                using (DrawingContext context = visual.RenderOpen())
                    context.DrawImage(Sprite(), rect);

                bitmap.Render(visual);
            }

            bitmap.Freeze();
            return bitmap;
        }
    }

I can see this strange picture while being data bound to Canvas property ...

enter image description here

Dmitry Nogin
  • 3,670
  • 1
  • 19
  • 35
  • Why don't you render those 70 tiles into a single DrawingVisual and then call bitmap.Render only once? You'd just have to move the `for` loop into the `using (DrawingContext ...)` block. You could also save the "inner" RenderTargetBitmaps completely by drawing lines directly to the "outer" DrawingContext. – Clemens Dec 10 '14 at 08:56
  • I cannot render tiles directly, as it means for a single Visual to keep references to 70 bitmaps or more the same time. I will run out of memory. Unfortunately, I do need "inner" RenderTargetBitmaps because of Tiles being input raster data for my application. – Dmitry Nogin Dec 10 '14 at 16:58
  • 1) I can't reproduce your problem, even with `tiles = 2000`. 2) I can reproduce the out-of-memory exception when keeping all references to sprites in a single Visual -- but at around 855 tiles rather than 70. What kind of graphics hardware are you using? – dbc Dec 10 '14 at 18:54
  • Can't reproduce with `tiles = 10000` either. – dbc Dec 10 '14 at 19:45
  • I've placed a small demo project to http://nogin.info/bug.zip I was able to reproduce it on my two computers (Windows 8.1 x64). My actual tiles are much bigger than that (Sonar Images), but this code demonstrates the same behavior. – Dmitry Nogin Dec 10 '14 at 20:24
  • I'm stuck on Win 7/x64/.Net 3.5 for various reasons. I grabbed your project, backported it to VS2008, built, and ran -- and could not reproduce the problem, even with 10000 tiles. – dbc Dec 10 '14 at 21:12
  • 4
    It looks like a version thing. .NET Framework 4.0 or later has this problem. – Dmitry Nogin Dec 10 '14 at 23:10
  • So your real application is not really drawing lines and rectangles? If all you need is lines and rectangles use WritableBitmapEx. – Brannon Dec 22 '14 at 06:34
  • Actually I need to scale down and cobine a significant amount of high resolution images. – Dmitry Nogin Dec 22 '14 at 06:40
  • have you tried looking at InteropBitmapHelper()? – Gilad Dec 24 '14 at 19:05
  • 2
    I can reproduce on Win 8.1 (targeting both x86 and x64). Even drawing a single stored sprite (not a different one each time)... at around 70 iterations it begins to show artifacts in your code. – Jcl Dec 30 '14 at 18:01

1 Answers1

3

Here is an example for using InteropBitmap:

 public InteropBitmapHelper(ColorSpace colorSpace, int bpp, int width, int height, uint byteCount)
        {
            _currentColorSpace = colorSpace;
            _pixelFormat = GetPixelFormat(colorSpace, bpp);

            if (_pixelFormat == PixelFormats.Rgb24 || _pixelFormat == PixelFormats.Rgb48 || _pixelFormat == PixelFormats.Bgr32 ||
                _pixelFormat == PixelFormats.Bgr24 || _pixelFormat == PixelFormats.Bgr565 ||
                _pixelFormat == PixelFormats.Gray16 || _pixelFormat == PixelFormats.Gray8)
            {
                int strideWidth = (width % 4 == 0) ? width : width - width % 4 + 4;
                if (byteCount != strideWidth * height * (_pixelFormat.BitsPerPixel / 8))
                {
                    strideWidth = width;
                }
                _stride = strideWidth * _pixelFormat.BitsPerPixel / 8;

                _byteCount = (uint)((_stride) * height * ((short)bpp).NumberOfBytes());

                ColorFileMapping = CreateFileMapping(new IntPtr(-1), IntPtr.Zero, 0x04, 0, _byteCount, null);

                if (ColorFileMapping == IntPtr.Zero)
                {
                    var res=GetLastError();
                    IPDevLoggerWrapper.Error("Could not generate InteropBitmap "+res);
                    return;
                }
                ViewerImageData = MapViewOfFile(ColorFileMapping, 0xF001F, 0, 0, _byteCount);
                InteropBitmap = Imaging.CreateBitmapSourceFromMemorySection(ColorFileMapping,
                                                                            width,
                                                                            height,
                                                                            _pixelFormat,
                                                                            _stride,
                                                                            0) as InteropBitmap;
            }
            else
            {
                LoggerWrapper.Error("The image format is not supported");
                return;
            }
        }

Here is how I connect it to the xaml.

<Canvas     
    x:Name="content" 
    Width="{Binding ElementName=MainImage, Path=ActualWidth}" 
    Height="{Binding ElementName=MainImage, Path=ActualHeight}" 
    Background="Transparent"
    MouseEnter="ImageMouseEnter" 
    MouseLeave="ImageMouseLeave"
    RenderTransformOrigin ="0.5,0.5">
    <Image x:Name="MainImage"  Source="{Binding Source}" RenderOptions.BitmapScalingMode="{Binding ViewerRenderingMode }"/>

Please let me know if you need more info.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Gilad
  • 6,437
  • 14
  • 61
  • 119
  • 2
    Actually I don’t have any persisted images of reasonable size. I need to combine multiple high resolution sprites (represented by WriteableBitmap) to a single low resolution BitmapSource. My sprites are really huge so they need to be generated on one at a time basis only. The problem is about rendering sprites to the single smaller RenderTargetBitmap (Canvas) to combine them; it basically does not work properly starting from .NET 4.0. Microsoft said that it looks like a bug and assigned product team developers to fix it. – Dmitry Nogin Dec 25 '14 at 03:55