托管位图 2






4.77/5 (15投票s)
本文介绍了用完全托管的代码表示位图的类。
背景
我过去直接在内存中操作图像。使用 C#,图像操作,或至少像素操作,并不那么简单。或者你切换到不安全代码,可能会遇到不受信任的环境问题,或者你使用 Bitmap 的 GetPixel
和 SetPixels
,它们真的很慢。然后我决定创建 ManagedBitmaps
,我已经在文章中介绍过。不受信任的问题仍然存在,因为 DLL 使用了不安全代码,但如果 DLL 是受信任的,一切都没问题,因为它的用户不需要使用不安全代码……但这还不够。这些类,按照它们被呈现的方式,对我没有帮助……所以我重新创建了它们。
变更
早些时候,我创建了 ColorBitmap
,它所有的像素都是 Color
类型,并提供了复制到和从 System.Drawing.Bitmap
的方法。ArgbBitmap
是 ColorBitmap
的另一个版本。但像素被视为 32 位 int
值,以及 GrayscaleBitmap
,它使用字节来表示颜色索引/强度。
这可行,但当我决定创建一个简单的生命模拟程序时,我想要:使用红色作为力量,绿色作为速度,蓝色作为抵抗力……并在像素中拥有另一个特征,它不能是“alpha”值。为了实现这一点,我被迫创建了另一个位图副本,带有我自己的数据类型……我几乎要放弃 ManagedBitmaps
了……所以,我想:是否有可能使用“二维数组”,只使用不安全代码来执行块复制以及颜色到实际数据类型的转换委托?经过一些测试,我发现这是可能的,并且速度仍然很快。
所以,新类只有一个:ManagedBitmap
,但是是泛型的。
你可以创建一个 ManagedBitmap<byte>
来表示索引位图,ManagedBitmap<Color>
,或者像我一样,ManagedBitmap<Life>
。然后,在创建系统位图、复制到和从中复制时,你只需要提供一个委托来进行转换。快速,简单……但还不够。
我以前的例子中有一点我不喜欢的是,我需要将数据复制到一个托管位图,然后将数据复制到实际位图。有没有办法直接操作位图,而不使用不安全代码?说实话,这是我最初的想法,但由于缺乏测试,我得出结论,切换到和从不安全代码是真正的问题。但事实并非如此。所以,我又创建了一个类……或者更确切地说,是一堆类。
LockBitmap32
将一个 32 位位图锁定在内存中,并允许访问其像素。这个类使用了不安全代码,但当你使用它时,所有的边界检查都完成了,所以使用它没有任何不安全之处。另外还有两个附加版本,一个只读,一个只写……另外还有 8 位和 24 位位图的相同类。在我的测试中,仍然比完全不安全代码慢,但比使用 Bitmap.GetPixel
/SetPixel
快约 35 倍。
那么,类是如何工作的?
为了介绍这些类,我需要将 ManagedBitmap
和“Lock
”类分开。所以,我将从 Lock
类开始。
Lock 类
Lock
类实际上非常简单。在创建它们时,你需要将一个 System.Drawing.Bitmap
作为参数传递。在构造过程中,将为整个位图或你指定的矩形调用 LockBits
,使用读/写权限,并检查 PixelFormat
是否正确。终结器和 Dispose
将调用 UnlockBits
获得 BitmapData
。索引器将简单地检查边界,如果它们是正确的,则直接获取或设置值。
很简单,不是吗?
想要一些代码吗?这是 PfzDrawingSample
的一部分。它将保持源位图不变,并将始终生成一个新的目标位图,对源位图进行给定的红色、绿色和蓝色更改。
using(var sourcePixels = new LockBitmapRgb24Read(fOriginalBitmap.AsSystemBitmap))
{
using (var destPixels = new LockBitmapRgb24Write(fBitmap.AsSystemBitmap))
{
for (int y=0; y<height; y++)
{
for(int x=0; x<width; x++)
{
Rgb24 color = sourcePixels[x, y];
byte r = p_Calculate(color.Red, trackBarRedValue);
byte g = p_Calculate(color.Green, trackBarGreenValue);
byte b = p_Calculate(color.Blue, trackBarBlueValue);
color = new Rgb24(r, g, b);
destPixels[x, y] = color;
}
}
}
}
由于原始位图不会改变,我可以使用 LockRgb24Read
。由于目标位图只写入而不读取,我可以使用 LockRgb24Write
。如果我打算使用灰度/索引位图,我可以使用 LockIndexed8Read
和 LockIndexed8Write
类。
与 GetPixel
和 SetPixel
进行比较
for (int y=0; y<height; y++)
{
for(int x=0; x<width; x++)
{
Color color = fOriginalSystemBitmap.GetPixel(x, y);
int r = p_Calculate(color.R, trackBarRedValue);
int g = p_Calculate(color.G, trackBarGreenValue);
int b = p_Calculate(color.B, trackBarBlueValue);
fSystemBitmap.SetPixel(x, y, Color.FromArgb(r, g, b));
}
}
除了两个用于 lock
的 using
语句外,代码是相同的。但是,Bitmap GetPixel
/SetPixel
确实慢得多。
ManagedBitmap
ManagedBitmap
对于最初的目的已经过时了,但它仍然非常有用。它比 Locks 快,但在许多情况下需要复制到和从实际位图,所以这可能是一个问题。我在我的简单生命模拟示例中使用了 ManagedBitmap<Life>
,因为我需要在像素中包含额外的信息。但是,感谢 Karol Kolenda,我现在可以说 ManagedBitmap
真的很有用了。如果你正在生成位图,你可以使用其中一个专门的 ManagedBitmaps
(ArgbBitmap
或 Indexed8Bitmap
[通常用于灰度图像])。这两个 ManagedBitmap
类还有一个名为“AsSystemBitmap
”的属性。这种属性的好处是,它是一个直接从 ManagedBitmap
数组构建的 System.Drawing.Bitmap
。因此,对该位图的更改将反映在 ManagedBitmap
中(所以你可以使用所有 Graphics 函数),并且像素的更改也将自动反映在该系统位图中。性能良好,并且可以使用 GDI+ 图形。还需要什么吗?
如何将 Life
实例转换为 Color
?
PixelMatrix.CopyToRgb24
(
fSystemBitmap,
(life) =>
{
if (life == null)
return new Rgb24();
return new Rgb24((byte)life.Strength, (byte)life.Speed, (byte)life.Resistence);
}
);
代码很小,而且现在很简单。我创建了一个 Rgb24 表示,使用 Strength 作为 Red,Speed 作为 Green,Resistance 作为 Blue。
好了,就是这样。
将来,我想创建一些类来直接加载图像(无论是简单的 .bmp 还是复杂的 .jpg)到 ManagedBitmaps
中,而无需进入非托管代码。但是,我必须警告你,当前的类可能会获得很多新方法,其中一些可能存在破坏性更改。
历史
- 2010 年 2 月 8 日
- 初始版本
- 2010 年 3 月 3 日
- 添加了
ArgbBitmap
和Indexed8Bitmap
,它们具有System.Drawing.Bitmap
表示,无需复制 - 还使
Lock
类更加通用,因此LockBitmap32
可以处理任何 32 位像素格式 - 2010 年 3 月 11 日
- 添加了 24 位位图支持,并更改了示例以使用它们
- 修正了某些
CopyTo
/CopyFrom
方法中的一个 bug,其中 X 索引被忽略了