如何在不占用大量内存的情况下显示快速更新的图像?

发布于 2024-07-17 19:29:42 字数 940 浏览 6 评论 0原文

我在超声波机器上有一个 WPF 应用程序,它以每秒 30 帧以上的速度显示用 C++ 生成的超声波图像。

据我了解,在 WPF 中显示图像的正常过程是为图像创建 BitmapSource 并设置 Image 的 Source,然后使其无效并显示。

由于 BitmapSources 没有实现 IDisposable,因此使用此方法迫使我每秒创建 30 个 BitmapSources。 对于 32bppArgb 格式的 640x480 图像,每秒分配大约 30MB 的内存,然后每 10 秒处理一次垃圾,导致明显的滞后。 显然不是一个可以接受的解决方案。

我当前的解决方案是:

在 C++ 中: 我在托管 C++ 中创建一个 System.Drawing.Bitmap (WinForms 位图),从指针执行 memcpy 来填充图片,使用 Graphics 对象执行一些附加操作绘制我需要的图形,并在 ImageReceived 事件期间将其传递到 C#/WPF。

在 C# 中 Image.Source 设置为 BitmapBuffer 生成的源,这是访问位图源的原始数据的一种 hack 方式:请参阅 此链接。 我执行 CopyMemory 的 P/Invoke 以从Bitmap.Scan0 进入 BitmapBuffer。 然后,我使 Image 无效以更新屏幕,并 Dispose() Drawing.Bitmap 对象以释放内存。

虽然这种方法已经工作了一段时间,但它看起来很老套,我发现很难相信除了通过反射之外没有其他“合适”的方法来做到这一点。

问题:有更好的方法吗?

I've got a WPF application on an ultrasound machine that displays ultrasound images generated in C++ at a speed upwards of 30 frames per second.

From what I understand, the normal process for displaying images in WPF is to create a BitmapSource for your image and set the Source for your Image, which then causes it to invalidate and display.

Since BitmapSources do not implement IDisposable, using this method forced me to create 30 BitmapSources a second. For a 640x480 image with 32bppArgb format, this is around 30MB/sec of memory being allocated a second and then garbage disposed every 10 seconds, causing visible lag. Obviously not an acceptable solution.

My currently solution is:

In C++: I create a System.Drawing.Bitmap (WinForms bitmap) in Managed C++, do a memcpy from a pointer to populate the picture, use the Graphics object to do some additional drawing I need, and pass this into the C#/WPF during an ImageReceived event.

In C# Image.Source is set to a source generated by BitmapBuffer, which is a hack-way of accessing the raw data of the bitmap source: See this link. I do a P/Invoke of CopyMemory to copy the data from the Bitmap.Scan0 into the BitmapBuffer. I then invalidate the Image to update the screen, and Dispose() the Drawing.Bitmap object to free the memory.

While this method has worked for a while, it seems very hacky and I find it hard to believe that there is no other "appropriate" way to do this than through reflection.

Question: Is there a better way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

渔村楼浪 2024-07-24 19:29:43

如果您使用的是最新的 WPF 位,请查看 WriteableBitmap ,你将不得不做更多的跑腿工作,但你的更新速度会非常快。

快速谷歌一下,你会得到一些样本。

If you are using the lastest WPF bits check out WriteableBitmap, you'll have to do more of the leg work but you'll really fast updates.

Do a quick google and you'll get some samples.

长途伴 2024-07-24 19:29:43

这是我编写的一些代码*,用于 WPF BitmapSource 和 GDI Bitmap(对于我自己的项目)之间的别名(共享内存),

显然,您需要根据自己的需要对其进行调整,最终可能会得到更少的“最终感觉“hacky”。

class AliasedBitmapSource : BitmapSource {
    private Bitmap source;
    public AliasedBitmapSource(Bitmap source) {
        this.source = source;
        this.pixelHeight = source.Height;
        this.pixelWidth = source.Width;
        this.dpiX = source.HorizontalResolution;
        this.dpiY = source.VerticalResolution;
    }

    public override event EventHandler DownloadCompleted;
    public override event EventHandler<ExceptionEventArgs> DownloadFailed;
    public override event EventHandler<ExceptionEventArgs> DecodeFailed;

    protected override Freezable CreateInstanceCore() {
        throw new NotImplementedException();
    }

    private readonly double dpiX;
    public override double DpiX {
        get {
            return dpiX;
        }
    }

    private readonly double dpiY;
    public override double DpiY {
        get {
            return dpiY;
        }
    }

    private readonly int pixelHeight;
    public override int PixelHeight {
        get {
            return pixelHeight;
        }
    }

    private readonly int pixelWidth;
    public override int PixelWidth {
        get {
            return pixelWidth;
        }
    }

    public override System.Windows.Media.PixelFormat Format {
        get {
            return PixelFormats.Bgra32;
        }
    }

    public override BitmapPalette Palette {
        get {
            return null;
        }
    }

    public unsafe override void CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int offset) {
        BitmapData sourceData = source.LockBits(
        sourceRect.ToRectangle(),
        ImageLockMode.ReadWrite,
        System.Drawing.Imaging.PixelFormat.Format32bppArgb);

        fixed (byte* _ptr = &((byte[])pixels)[0]) {
            byte* dstptr = _ptr;
            byte* srcptr = (byte*)sourceData.Scan0;

            for (int i = 0; i < pixels.Length; ++i) {
                *dstptr = *srcptr;
                ++dstptr;
                ++srcptr;
            }
        }

        source.UnlockBits(sourceData);
    }
}

public static class Extensions {
    public static Rectangle ToRectangle(this Int32Rect me) {
        return new Rectangle(
        me.X,
        me.Y,
        me.Width,
        me.Height);
    }

    public static Int32Rect ToInt32Rect(this Rectangle me) {
        return new Int32Rect(
        me.X,
        me.Y,
        me.Width,
        me.Height);
    }
}

*“写”的意思是“10 分钟内拼凑起来”

Here's some code I wrote* for aliasing (sharing memory) between a WPF BitmapSource and a GDI Bitmap (for my own project)

Obviously, you'll need to tweak it for your own needs, it'll probably end up with a less "hacky" feel in the end.

class AliasedBitmapSource : BitmapSource {
    private Bitmap source;
    public AliasedBitmapSource(Bitmap source) {
        this.source = source;
        this.pixelHeight = source.Height;
        this.pixelWidth = source.Width;
        this.dpiX = source.HorizontalResolution;
        this.dpiY = source.VerticalResolution;
    }

    public override event EventHandler DownloadCompleted;
    public override event EventHandler<ExceptionEventArgs> DownloadFailed;
    public override event EventHandler<ExceptionEventArgs> DecodeFailed;

    protected override Freezable CreateInstanceCore() {
        throw new NotImplementedException();
    }

    private readonly double dpiX;
    public override double DpiX {
        get {
            return dpiX;
        }
    }

    private readonly double dpiY;
    public override double DpiY {
        get {
            return dpiY;
        }
    }

    private readonly int pixelHeight;
    public override int PixelHeight {
        get {
            return pixelHeight;
        }
    }

    private readonly int pixelWidth;
    public override int PixelWidth {
        get {
            return pixelWidth;
        }
    }

    public override System.Windows.Media.PixelFormat Format {
        get {
            return PixelFormats.Bgra32;
        }
    }

    public override BitmapPalette Palette {
        get {
            return null;
        }
    }

    public unsafe override void CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int offset) {
        BitmapData sourceData = source.LockBits(
        sourceRect.ToRectangle(),
        ImageLockMode.ReadWrite,
        System.Drawing.Imaging.PixelFormat.Format32bppArgb);

        fixed (byte* _ptr = &((byte[])pixels)[0]) {
            byte* dstptr = _ptr;
            byte* srcptr = (byte*)sourceData.Scan0;

            for (int i = 0; i < pixels.Length; ++i) {
                *dstptr = *srcptr;
                ++dstptr;
                ++srcptr;
            }
        }

        source.UnlockBits(sourceData);
    }
}

public static class Extensions {
    public static Rectangle ToRectangle(this Int32Rect me) {
        return new Rectangle(
        me.X,
        me.Y,
        me.Width,
        me.Height);
    }

    public static Int32Rect ToInt32Rect(this Rectangle me) {
        return new Int32Rect(
        me.X,
        me.Y,
        me.Width,
        me.Height);
    }
}

*by "wrote" I mean "threw together in 10 minutes"

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文