65.9K
CodeProject 正在变化。 阅读更多。
Home

System.Drawing 快速绘制非 32bpp 图像

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (20投票s)

2006 年 11 月 15 日

4分钟阅读

viewsIcon

91961

downloadIcon

1791

避免在使用 System.Drawing 将图像的一部分绘制到屏幕时通常会发生的不必要的像素格式转换。

引言

众所周知,当将像素格式与屏幕不同的图像绘制到屏幕时,必须进行格式转换。GDI+ 提供了 CachedBitmap 类来方便地缓存位图的已转换版本。然而,.NET 中并未公开此功能,因此通常在每次绘制调用时都会转换与屏幕格式不匹配的位图。

项目

乍一看,即使对于大型图像,仅绘制使用 Graphics.DrawImage() 绘制的图像中可见或无效的区域,似乎也不会对性能造成太大影响。然而,进一步的性能分析显示,Graphics.DrawImage() 的速度始终与整个位图的大小相关,而不是要绘制的区域的大小。如果您告诉 GDI+ 绘制一个与屏幕像素格式不匹配的位图的 1x1 像素的部分,它会在完成绘制调用之前将整个位图转换为屏幕像素格式!

这显然是不可接受的,特别是考虑到数码相机图像通常是 24bpp。因此,我着手编写一个例程,该例程将创建一个与需要绘制的区域大小相同的新位图。然后,它会将源位图的正确部分复制到该区域并将其绘制到屏幕上。这将提供更好的性能。

然而,我也想看看是否可以不通过额外的复制例程的开销,让 GDI+ 直接转换相关的位图区域。我意识到,通过我的 EditableBitmap 类,我可以创建一个 EditableBitmap 作为另一个 EditableBitmap 位集合的视图。然后,我就可以向 GDI+ 报告“步长”是源位图步长的大小。因此,将有一个 GDI+ 位图信息类供 GDI+ 使用,但不会有额外的位图相关的位内存开销。也不会有与每次绘制调用复制位到已复制位图部分相关的性能下降,无论多么微小。我添加了一个名为 CreateView() 的方法,该方法接受一个指定视图边界的矩形。

public EditableBitmap CreateView(Rectangle viewArea)
{
    if(disposed)
        throw new ObjectDisposedException("this");
    return new EditableBitmap(this, viewArea);
}

它将“魔法”委托给一个受保护的构造函数。

protected EditableBitmap(EditableBitmap source, Rectangle viewArea)
{
    owner=source;
    pixelFormatSize=source.pixelFormatSize;
    byteArray=source.byteArray;
    byteArray.AddReference();
    stride = source.stride;
    
    try
    {
        startOffset=source.startOffset+(stride*viewArea.Y)+
            (viewArea.X*pixelFormatSize);
        bitmap = new Bitmap(viewArea.Width, viewArea.Height, 
            stride, source.Bitmap.PixelFormat, 
            (IntPtr)(((int)byteArray.bitPtr)+startOffset));
    }
    finally
    {   
        if(bitmap==null)
            byteArray.ReleaseReference();
    }
}

构造函数会复制源位图中将保持相同的属性。它会在 Owner 属性中存储一个它正在查看的位图的引用。然后,它会计算从所有者字节数组的开始到视图位图的第一个像素的字节偏移量。然后,它会创建一个 GDI+ Bitmap 对象,并传入偏移字节指针以及视图的宽度和高度。到目前为止,一切顺利!我们得到了一个位图对象,它欺骗 GDI+,让它认为它是一个独立的位图,但实际上它指向一个更大的位图的一部分。

然而,我们仍然需要担心那些棘手的资源管理问题。EditableBitmap 类依赖于一个固定的字节数组来存储像素数据。对于位图视图,该字节数组由多个位图共享。我们希望视图在根位图被释放时仍然可用,反之亦然。因此,我们必须确保只有在不再有使用它的 EditableBitmap 实例时才销毁字节数组。为此,我添加了一个新类 SharedPinnedByteArray,它管理字节数组并维护一个引用计数。当引用计数达到零或它被最终确定时,它会取消固定字节数组。

最终结果是,对于大型、非屏幕格式的位图,渲染速度大大加快。包含的演示项目展示了速度差异。运行它时,它会要求您通过文件对话框选择一个位图。选择一个大的位图。尝试滚动,特别是拖动滚动条的拇指。您很可能会看到“撕裂”:区域绘制速度太慢,以至于您可以轻松地看到缺失的区域。现在点击位图显示区域,以便窗体的标题栏显示“新方法”。再次尝试滚动,它应该非常顺畅。

历史

  • 2007 年 6 月 5 日 -- 文章已编辑并发布到 CodeProject.com 主文章库。
  • 2006 年 11 月 15 日 -- 发布原始版本
© . All rights reserved.