如何使用.NET快速生成图像
我已经相当熟悉 System.Drawing 命名空间,了解如何生成图像、在其上绘图、在其上写入文本等的基本步骤。但是,对于任何接近打印质量的东西来说,它都太慢了。我看到了一些使用 COM 与本机 Windows GDI 对话以更快地完成此操作的建议,但我想知道是否可以进行任何优化以实现高速、高质量的图像生成。我尝试过使用抗锯齿选项,这些选项可立即用于图形、位图和图像对象,但是我可以采用任何其他技术来实现如此高的速度吗?
写这篇文章时,我只是想到使用 .Net 4 中的任务库来完成更多工作,尽管每个生成任务不会更快。
无论如何,想法和评论表示赞赏。
谢谢!
I've become rather familiar with the System.Drawing namespace in terms of knowing the basic steps of how to generate an image, draw on it, write text on it, etc. However, it's so darn slow for anything approaching print-quality. I have seen some suggestions using COM to talk to native Windows GDI to do this quicker but I was wondering if there were any optimizations I could make to allow for high speed, high quality image generation. I have tried playing with the anti-aliasing options and such immediately available to the Graphics, Bitmap and Image objects but are there any other techniques I can employ to do this high speed?
Writing this I just had the thought to use the task library in .Net 4 to do MORE work even though each generation task wouldn't be any quicker.
Anyway, thoughts and comments appreciated.
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
如果您想要原始速度,最好的选择是使用 DirectX。下一个最好的方法可能是使用 C++ 中的 GDI 并提供一个托管接口来调用它。然后可能直接从 C# p/invoke 到 GDI,最后在 C# 中使用 GDI+。但根据您所做的事情,您可能看不到巨大的差异。如果您受到 GDI+ 驱动显卡的速度的限制,多线程可能对您没有帮助,但如果您在计算“绘制什么”时受到处理器限制,那么多线程可能会很有帮助。如果您要按顺序打印许多图像,则可以通过在单独的线程上运行预先计算、渲染和打印“阶段”来获益。
但是,您可以采取多种措施来优化重绘速度,包括优化和折衷,这些方法将适用于您选择的任何渲染系统。事实上,其中大部分都源于优化代码时使用的相同原则。
如何才能最大限度地减少提取的金额?
首先,消除不必要的工作。想想你正在绘制的每个元素 - 真的有必要吗?通常,更简单的设计实际上看起来更好,同时节省大量渲染工作。考虑是否可以用平面填充代替渐变填充,或者圆角矩形看起来是否可以接受为普通矩形(并在扔掉它之前测试这样做是否会给您的硬件带来任何速度优势!)
挑战您的假设 - 例如“高分辨率”要求 - 通常如果您在染料子打印机(这是一个引入一点颜色渗色的过程)或使用任何形式的抖动来混合颜色的 CMYK 打印机(它具有实际分辨率远低于打印机可以解析的点距),分辨率相对较低的抗锯齿图像通常可以产生与超高分辨率图像一样好的结果。如果您输出到 2400dpi 黑白打印机,您可能仍然会发现 1200dpi 甚至 600dpi 是可以接受的(随着分辨率的提高,您获得的回报会不断减少,并且大多数人不会注意到 600dpi 和 2400dpi 之间的差异) 。只需使用不同的源分辨率打印一些典型示例即可查看结果如何。如果您可以将分辨率减半,渲染速度可能会提高 4 倍。
一般来说,尽量避免过度绘制同一区域 - 如果你想在一个区域周围绘制一个黑色框架,你可以在黑色矩形内绘制一个白色矩形,但这意味着将中间的所有像素填充两次。您可以通过在外部绘制 4 个黑色矩形来精确绘制框架来提高性能。相反,如果您有很多绘图图元,您可以减少正在绘制的图元数量吗?例如,如果要绘制很多条纹,则可以绘制不同颜色的交替矩形(= 2n 个矩形),或者可以用一种颜色填充整个背景,然后只绘制第二种颜色的矩形(= n+1 个矩形) )。减少对 GDI+ 方法的单独调用次数通常可以带来显着的收益,尤其是在您拥有快速图形硬件的情况下。
如果多次绘制图像的任何部分,请考虑对其进行缓存(将其渲染为位图,然后在需要时将其位图传输到最终图像)。该子图像越复杂,缓存它就越有可能获得回报。例如,如果您有像标尺这样的重复图案,则不要将每个标尺标记绘制为单独的线 - 渲染标尺的重复部分(例如 10 条线或 50 条线),然后将此预渲染的 blit 只绘制几次以进行绘制最终的统治者。
同样,避免做大量不必要的工作(例如许多 MeasureString 调用可以预先计算一次甚至近似的值。或者,如果您要逐步执行大量 Y 值,请尝试通过在每次迭代时添加偏移量来实现,而不是每次都使用倍数重新计算绝对位置)。
尝试“批量”绘制以最大程度地减少必要的状态更改和/或绘制方法调用的数量 - 例如,在转到下一种颜色之前绘制一种颜色/纹理/画笔的所有元素。使用“批量”渲染调用(例如,绘制一次多段线图元,而不是调用 DrawLine 100 次)。
如果您正在进行任何逐像素操作,那么获取原始图像缓冲区并直接操作它通常比调用 GetPixel/SetPixel 方法要快得多。
正如您已经提到的,您可以关闭昂贵的操作,例如抗锯齿和字体平滑,这些操作在您的特定情况下不会有任何/太多好处。
当然,查看您正在渲染的代码 - 分析它并应用通常的优化以帮助它有效地流动。
最后,您应该考虑硬件升级是否是一种廉价而有效的解决方案 - 如果您的电脑速度较慢且显卡低端,那么只需购买一台具有以下功能的新电脑即可获得显着的收益:里面有更好的显卡。或者,如果图像很大,您可能会发现多 GB 的 RAM 可以消除虚拟内存分页开销。这听起来可能很昂贵,但到了一定程度,新硬件的成本/收益比投入更多资金进行额外的优化工作(及其不断下降的回报)要好。
If you want raw speed, the best option is to use DirectX. The next best is probably to use GDI from C++ and provide a managed interface to call it. Then probably to p/invoke to GDI directly from C#, and last to use GDI+ in C#. But depending on what you're doing you may not see a huge difference. Multithreading may not help you if you're limited by the speed at which the graphics card can be driven by GDI+, but could be beneficial if you're processor bound while working out "what to draw". If you're printing many images in sequence, you may gain by running precalculation, rendering, and printing "phases" on separate threads.
However, there are a number of things you can do to optimise redraw speed, both optimisations and compromises, and these approaches will apply to any rendering system you choose. Indeed, most of these stem from the same principles used when optimising code.
How can you minimise the amount that you draw?
Firstly, eliminate unnecessary work. Think about each element you are drawing - is it really necessary? Often a simpler design can actually look better while saving a lot of rendering effort. Consider whether a grad fill can be replaced by a flat fill, or whether a rounded rectangle will look acceptable as a plain rectangle (and test whether doing this even provides any speed benefit on your hardware before throwing it away!)
Challenge your assumptions - e.g. the "high resolution" requirement - often if you're printing on something like a dye-sub printer (which is a process that introduces a bit of colour bleed) or a CMYK printer that uses any form of dithering to mix colours (which has a much lower practical resolution than the dot pitch the printer can resolve), a relatively low resolution anti-aliased image can often produce just as good a result as a super-high-res one. If you're outputting to a 2400dpi black and white printer, you may still find that 1200dpi or even 600dpi is acceptable (you get ever decreasing returns as you increase the resolution, and most people won't notice the difference between 600dpi and 2400dpi). Just print some typical examples out using different source resolutions to see what the results are like. If you can halve the resolution you could potentially render as much as 4x faster.
Generally try to avoid overdrawing the same area - If you want to draw a black frame around a region, you could draw a white rectangle inside a black rectangle, but this means filling all the pixels in the middle twice. You may improve the performance by drawing 4 black rectangles around the outside to draw the frame exactly. Conversely, if you have a lot of drawing primitives, can you reduce the number of primitives you're drawing? e.g. If you are drawing a lot of stripes, you can draw alternating rectangles of different colours (= 2n rectangles), or you can fill the entire background with one colour and then only draw the rectangles for the second colour (= n+1 rectangles). Reducing the number of individual calls to GDI+ methods can often provide significant gains, especially if you have fast graphics hardware.
If you draw any portion of the image more than once, consider caching it (render it into a bitmap and then blitting it to your final image when needed). The more complex this sub-image is, the more likely it is that caching it will pay off. For example, if you have a repeating pattern like a ruler, don't draw every ruler marking as a separate line - render a repeating section of the ruler (e.g. 10 lines or 50) and then blit this prerendered only a few times to draw the final ruler.
Similarly, avoid doing lots of unnecessary work (like many MeasureString calls for values that could be precalculated once or even approximated. Or if you're stepping through a lot of Y values, try to do it by adding an offset on each iteration rather than recaclualting the absolute position using mutliples every time).
Try to "batch" drawing to minimise the number of state changes and/or drawing method calls that are necessary - e.g. draw all the elements that are in one colour/texture/brush before you move on to the next colour. Use "batch" rendering calls (e.g. Draw a polyline primitive once rather than calling DrawLine 100 times).
If you're doing any per-pixel operations, then it's usually a lot faster to grab the raw image buffer and manipulate it directly than to call GetPixel/SetPixel methods.
And as you've already mentioned, you can turn off expensive operations such as anti-aliasing and font smoothing that won't be of any/much benefit in your specific case.
And of course, look at the code you're rendering with - profile it and apply the usual optimisations to help it flow efficiently.
Lastly, there comes a point where you should consider whether a hardware upgrade might be a cheap and effective solution - if you have a slow PC and a low end graphics card there may be a significant gain to be had by just buying a new PC with a better graphics card in it. Or if the images are huge, you may find a couple of GB more RAM eliminates virtual memory paging overheads. It may sound expensive, but there comes a point where the cost/benefit of new hardware is better than ploughing more money into additional work on optimisations (and their ever decreasing returns).
我有一些想法:
查看 Paint.net 中的代码。它是一个用 C# 编写的开源绘图程序。它可以给你一些好主意。您当然可以结合想法 2 和 3 来做到这一点。
如果需要以“立即”方式完成作业,您可以使用异步方法来创建图像。根据整个应用程序的范围,您甚至可以使用 NServiceBus 之类的工具将图像处理组件与任务排队。任务完成后,发送组件将通过订阅完成后发布的消息来接收通知。
基于任务的解决方案有利于延迟处理。您可以批量创建图像并使用任务方法或称为 Quartz.net (http://quartznet.sourceforge.net) 的方法。它是一个开源作业调度程序,我将其用于所有基于时间的作业。
I have a few ideas:
Look at the code in Paint.net. It is an open source paint program written in C#. It could give you some good ideas. You could certainly do this in conjunction with ideas 2 and 3.
If the jobs need to be done in an "immediate" way, you could use something asynchronous to create the images. Depending on the scope of the overall application, you might even use something like NServiceBus to queue an image processing component with the task. Once the task is completed, the sending component would receive a notification via subscribing to the message published upon completion.
The task based solution is good for delayed processing. You could batch the creation of the images and use either the Task approach or something called Quartz.net (http://quartznet.sourceforge.net). It's an open source job scheduler that I use for all my time based jobs.
您可以创建一个新的位图图像并执行 LockBits(...),指定所需的像素格式。一旦你有了这些位,如果你想使用非托管代码来绘制它,请引入库,将数据固定在内存中,然后使用该库。我认为您可以对原始像素数据使用 GDI+,但我的印象是 System.Drawing 已经是 GDI+ 之上的薄层。无论如何,无论我是否错了,使用 LockBits,您都可以直接访问像素数据,其速度可以与您编程的速度一样快或很慢。
完成绘图后,您可以解锁位并获得新图像。
You can create a new Bitmap image and do LockBits(...), specifying the pixel format you want. Once you have the bits, if you want to use unmanaged code to draw into it, bring in the library, pin the data in memory, and use that library against it. I think you can use GDI+ against raw pixel data, but I was under the impression that System.Drawing is already a thin layer on top of GDI+. Anyways, whether or not I'm wrong about that, with LockBits, you have direct access to pixel data, which can be as fast or slow as you program it.
Once you're done with drawing, you can UnlockBits and viola you have the new image.