How to properly render large bitmaps in WPF?
Asked Answered
F

1

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

Flyboat answered 10/12, 2014 at 4:40 Comment(11)
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.Hypermeter
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.Flyboat
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?Beebread
Can't reproduce with tiles = 10000 either.Beebread
I've placed a small demo project to 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.Flyboat
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.Beebread
It looks like a version thing. .NET Framework 4.0 or later has this problem.Flyboat
So your real application is not really drawing lines and rectangles? If all you need is lines and rectangles use WritableBitmapEx.Roundfaced
Actually I need to scale down and cobine a significant amount of high resolution images.Flyboat
have you tried looking at InteropBitmapHelper()?Slashing
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.Closelipped
S
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.

Slashing answered 24/12, 2014 at 19:19 Comment(1)
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.Flyboat

© 2022 - 2024 — McMap. All rights reserved.