使用LockBits复制位图的矩形部分

5
我使用以下代码来锁定位图的矩形区域:
Recangle rect = new rect(X,Y,width,height);
BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadOnly,
                        bitmap.PixelFormat);

问题似乎在于bitmapData.Scan0给我提供了矩形左上角的IntPtr。当我使用memcpy时,它复制了内存中指定长度的连续区域。

memcpy(bitmapdest.Scan0, bitmapData.Scan0, 
             new UIntPtr((uint (rect.Width*rect.Height*3)));

如果以下是我的位图数据:
a b c d e
f g h i j
k l m n o
p q r s t

如果矩形是 (2, 1, 3, 3),即该区域

g h i
l m n
q r s

使用 memcpy 会给我一个带有以下区域的位图。
g h i
j k l
m n o

我理解为什么它会复制连续的内存区域。底线是我想使用Lockbits来复制一个矩形区域。

编辑: 我使用了Bitmap.Clone

using (Bitmap bitmap= (Bitmap)Image.FromFile(@"Data\Edge.bmp"))
{
     bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
     Rectangle cropRect = new Rectangle(new Point(i * croppedWidth, 0),new Size(croppedWidth, _original.Height));
     _croppedBitmap= bitmap.Clone(cropRect, bitmap.PixelFormat);
}

但是当我翻转了Y时,速度更快(少于500ms

bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);

当我没有翻转Y轴时,它的速度非常慢(30秒)。

使用的图像大小为60000x1500


2
没有一个好的、最小的、完整的代码示例能够可靠地重现问题,就不可能回答。话虽如此,LockBits() 不能神奇地重新格式化你的位图后面的数据,因此我不认为直接使用 memcpy() 可行。你可能需要逐行复制数据;你检查过 BitmapData.Stride 属性了吗?它应该告诉你每一行需要添加到 Scan0 上的内容。 - Peter Duniho
@PeterDuniho 我明白为什么memcpy不起作用。所以你的意思是逐行复制是完成这个任务的唯一方法吗?你能解释一下为什么Bitmap.Clone在这两种情况下需要不同的时间吗? - Dinesh
不使用DrawImage有什么原因吗? - TaW
@TaW 我不想创建一个新的位图对象。原因是我正在使用OpenGL。当我说我要memcpy时,我正在将位图数据复制到像素缓冲区对象中。因此,创建一个新的位图并将该数据复制到缓冲区对象中实际上是拥有两个副本,即裁剪和原始位图的相同副本。 - Dinesh
使用翻转克隆比不翻转要快得多 - 60倍的差异似乎特别不合理。但是我觉得汉斯是在说,默认情况下,底层位图对象存储为内存中数据的第一行是底部像素的行。我不知道为什么会这样,也不知道为什么目标位图默认为相反(这是翻转副本更快的唯一方式),但这能解释您看到的行为(从而符合我多年前在Connect上报告的GDI+绘图错误...现在甚至不在该网站上)。 - Peter Duniho
显示剩余2条评论
1个回答

1

我不是很明白你的问题是什么。下面的代码将正确的位图区域复制到托管数组中(我使用了32bpp,当然对于24bpp位图,alpha始终为255):

int x = 1;
int y = 1;
int w = 2;
int h = 2;

Bitmap bmp = new Bitmap(@"path\to\bitmap.bmp");

Rectangle rect = new Rectangle(x, y, w, h);
System.Drawing.Imaging.BitmapData bmpData =
    bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly,
    System.Drawing.Imaging.PixelFormat.Format32bppArgb);

IntPtr ptr = bmpData.Scan0;

int bytes = 4 * w * h;
byte[] rgbValues = new byte[bytes];

System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

bmp.UnlockBits(bmpData);

你的解决方案在尝试复制位图上的矩形区域时可能会出现问题,就像这里讨论的一样(https://dev59.com/PWTWa4cB1Zd3GeqPB0tW)。我已经学到了通过使用“LockBits”来完成这个任务的唯一方法是通过带有步幅偏移的整个位图进行迭代,就像上面链接中讨论的解决方案一样。 - Dinesh
我用一个4x4像素位图测试了这段代码。上面的代码可以无误地复制正确的内部2x2像素块。你也可以更改并重新插入这个2x2块到位图中而不会出问题。MSDN上可以找到类似的代码示例。您可以在“LockBits”调用中选择所需的像素格式,这也会影响步幅。说实话,我不知道为什么这样能行。也许“Marshal.Copy”在内部做了一些魔法? - Robert S.
当步幅/“每像素字节数”==“图像宽度”时,它将无法工作。通常情况下这是正确的,这就是为什么你的测试能够正常工作的原因。否则,您将复制真实图像数据到“边界内存优化垃圾”,反之亦然。 - Pedro77
据我理解,步幅是“每像素字节数*图像宽度”,因此您的“不起作用”仅在每像素字节数为1时才为真。 Format32bppArgb将确保stride = 4 * image width。 如果我错了,请纠正我。 - Robert S.

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