如何在WPF中正确渲染大型位图?

21

我没有预料到

RenderTargetBitmap.Render(visual)

除了改变位图数据本身,好像并没有任何副作用。但事实似乎并非如此,我无法在重复60次以上之后避免出现一些丑陋的渲染伪影。

如何在WPF中正确地呈现大量精灵?以下是复现问题的代码。

我用以下方法生成精灵:

    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;
    }

这里有一个画布可以用来渲染它们:

    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;
        }
    }

在绑定到Canvas属性的数据时,我可以看到这张奇怪的图片...

输入图片描述


为什么不将这70个瓷砖渲染成一个单独的DrawingVisual,然后只调用一次bitmap.Render呢?你只需要将for循环移到using (DrawingContext ...)块中即可。你还可以通过直接在"外部"DrawingContext上绘制线条来完全省去"内部"的RenderTargetBitmaps。 - Clemens
我无法直接渲染瓷砖,因为这意味着单个可视对象需要同时保留对70个或更多位图的引用。我会耗尽内存。不幸的是,由于瓷砖是我的应用程序的输入光栅数据,我确实需要“内部”RenderTargetBitmaps。 - Dmitry Nogin
  1. 即使 tiles = 2000,我也无法重现你的问题。
  2. 当所有精灵的引用都保留在单个视觉元素中时,我可以重现内存不足异常,但是大约在855个瓷砖而不是70个时。你使用什么样的图形硬件?
- dbc
4
看起来是一个版本问题,.NET Framework 4.0或更高版本存在这个问题。 - Dmitry Nogin
2
我可以在Win 8.1上进行复现(同时针对x86和x64)。即使只绘制一个已存储的精灵(每次不使用其他精灵)... 在约70次迭代后,您的代码开始显示伪影。 - Jcl
显示剩余6条评论
1个回答

3

这里有一个使用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;
            }
        }

这是我如何将它连接到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 }"/>

如果您需要更多信息,请告诉我。


2
实际上,我没有任何合理大小的持久化图像。我需要将多个高分辨率精灵(由WriteableBitmap表示)组合成单个低分辨率的BitmapSource。我的精灵非常巨大,因此它们只能一次生成一个。问题在于将精灵呈现到单个较小的RenderTargetBitmap(画布)以将它们组合起来;从.NET 4.0开始,它基本上无法正常工作。微软表示这似乎是一个错误,并指派产品团队开发人员进行修复。 - Dmitry Nogin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接