使用C#从BitmapData裁剪区域

我有一个位图sourceImage.bmp

锁定它的位:

BitmapData dataOriginal = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 

做分析, 得到一个克隆

 Bitmap originalClone = AForge.Imaging.Image.Clone(dataOriginal); 

解锁位:

 sourceImage.UnlockBits(dataOriginal); 

是否可以指定要复制的“dataOriginal”的哪一部分(x,y,w,h)? 或者从dataOriginal创建新数据,指定X和Y坐标以及H和W?

目的是从该图像中复制一个小区域。 这个方法可能比DrawImage更快,这就是我不使用后者的原因。

编辑:

所以我拿了29 Mb位图并进行了一些硬核测试! 全尺寸裁剪(基本上是副本)+ 100次迭代。

http://i.minus.com/ibmcUsT1qUGw6f.png

码:

 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using AForge; using AForge.Imaging; using System.Diagnostics; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; namespace testCropClone { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private unsafe Bitmap Clone(Bitmap bmp, int startX, int startY, int width, int height) { Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); int origByteCount = rawOriginal.Stride * rawOriginal.Height; byte[] origBytes = new Byte[origByteCount]; Marshal.Copy(rawOriginal.Scan0, origBytes, 0, origByteCount); int BPP = 4; //4 Bpp = 32 bits, 3 = 24, etc. byte[] croppedBytes = new Byte[width * height * BPP]; //Iterate the selected area of the original image, and the full area of the new image for (int i = 0; i < height; i++) { for (int j = 0; j < width * BPP; j += BPP) { int origIndex = (startX * rawOriginal.Stride) + (i * rawOriginal.Stride) + (startY * BPP) + (j); int croppedIndex = (i * width * BPP) + (j); //copy data: once for each channel for (int k = 0; k < BPP; k++) { croppedBytes[croppedIndex + k] = origBytes[origIndex + k]; } } } //copy new data into a bitmap Bitmap croppedBitmap = new Bitmap(width, height); BitmapData croppedData = croppedBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length); bmp.UnlockBits(rawOriginal); croppedBitmap.UnlockBits(croppedData); return croppedBitmap; } private Bitmap cloneBitmap(Bitmap bmp, int startX, int startY, int width, int height) { Rectangle srcRect = Rectangle.FromLTRB(startX, startY, width, height); Bitmap cloneBitmap = bmp.Clone(srcRect, bmp.PixelFormat); return cloneBitmap; } private Bitmap cloneRectangle(Bitmap bmp, int startX, int startY, int width, int height) { Rectangle srcRect = Rectangle.FromLTRB(startX, startY, width, height); Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height); Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height); using (Graphics graphics = Graphics.FromImage(dest)) { graphics.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel); } return dest; } private Bitmap cloneAforge(Bitmap bmp, int startX, int startY, int width, int height) { BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); Bitmap cloneBitmap = AForge.Imaging.Image.Clone(rawOriginal); bmp.UnlockBits(rawOriginal); return cloneBitmap; } private void button1_Click(object sender, EventArgs e) { Bitmap source = new Bitmap(@"C:\9\01.bmp"); Stopwatch s1 = Stopwatch.StartNew(); for (int i = 0; i < 100; i++) { Bitmap Clone1 = cloneAforge(source, 0, 0, source.Width, source.Height); Clone1.Dispose(); } /*Bitmap Clone1 = cloneAforge(source, 0, 0, source.Width, source.Height); Clone1.Save(@"C:\9\01_aforge.bmp"); Clone1.Dispose();*/ s1.Stop(); source.Dispose(); textBox1.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms"); } private void button2_Click(object sender, EventArgs e) { Bitmap source = new Bitmap(@"C:\9\01.bmp"); Stopwatch s1 = Stopwatch.StartNew(); for (int i = 0; i < 100; i++) { Bitmap Clone1 = cloneBitmap(source, 0, 0, source.Width, source.Height); Clone1.Dispose(); } /*Bitmap Clone1 = cloneBitmap(source, 0, 0, source.Width, source.Height); Clone1.Save(@"C:\9\01_bitmap.bmp"); Clone1.Dispose();*/ s1.Stop(); source.Dispose(); textBox2.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms"); } private void button3_Click(object sender, EventArgs e) { Bitmap source = new Bitmap(@"C:\9\01.bmp"); Stopwatch s1 = Stopwatch.StartNew(); for (int i = 0; i < 100; i++) { Bitmap Clone1 = Clone(source, 0, 0, source.Width, source.Height); Clone1.Dispose(); } /*Bitmap Clone1 = Clone(source, 0, 0, source.Width, source.Height); Clone1.Save(@"C:\9\01_bits.bmp"); Clone1.Dispose();*/ s1.Stop(); source.Dispose(); textBox3.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms"); } private void button4_Click(object sender, EventArgs e) { Bitmap source = new Bitmap(@"C:\9\01.bmp"); Stopwatch s1 = Stopwatch.StartNew(); for (int i = 0; i < 100; i++) { Bitmap Clone1 = cloneRectangle(source, 0, 0, source.Width, source.Height); Clone1.Dispose(); } /*Bitmap Clone1 = cloneRectangle(source, 0, 0, source.Width, source.Height); Clone1.Save(@"C:\9\01_rect.bmp"); Clone1.Dispose();*/ s1.Stop(); source.Dispose(); textBox4.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms"); } } } 

Edit2 🙁 Aforge全尺寸裁剪..)方法Nr。 2

  for (int i = 0; i < 100; i++) { Crop crop = new Crop(new Rectangle(0, 0, source.Width, source.Height)); var source2 = crop.Apply(source); source2.Dispose(); } 

平均值= 62ms(比第一个Aforge方法少40ms)

结果:

  1. BitmapClone(0毫秒)?? (作弊,不是吗?)
  2. Aforge#2(65毫秒)
  3. Aforge#1(105毫秒)
  4. 矩形(170毫秒)
  5. 锁定位(803 ms)(等待修复/新测试结果..)

我掀起了一个快速(并且粗略地说)粗略的手动解决方案,该解决方案演示了如何使用锁定的位图执行此操作。 它应该比替代方法快得多,但确实涉及更多代码。

  Bitmap bmp = new Bitmap(@"C:\original.jpg"); Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); int origByteCount = rawOriginal.Stride * rawOriginal.Height; byte[] origBytes = new Byte[origByteCount]; Marshal.Copy(rawOriginal.Scan0, origBytes, 0, origByteCount); //I want to crop a 100x100 section starting at 15, 15. int startX = 15; int startY = 15; int width = 100; int height = 100; int BPP = 4; //4 Bpp = 32 bits, 3 = 24, etc. byte[] croppedBytes = new Byte[width * height * BPP]; //Iterate the selected area of the original image, and the full area of the new image for (int i = 0; i < height; i++) { for (int j = 0; j < width * BPP; j += BPP) { int origIndex = (startX * rawOriginal.Stride) + (i * rawOriginal.Stride) + (startY * BPP) + (j); int croppedIndex = (i * width * BPP) + (j); //copy data: once for each channel for (int k = 0; k < BPP; k++) { croppedBytes[croppedIndex + k] = origBytes[origIndex + k]; } } } //copy new data into a bitmap Bitmap croppedBitmap = new Bitmap(width, height); BitmapData croppedData = croppedBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length); bmp.UnlockBits(rawOriginal); croppedBitmap.UnlockBits(croppedData); croppedBitmap.Save(@"C:\test.bmp"); 

我使用了这张原始图片:

原版的

要输出此图像,裁剪为100x100 @ 15,15:

裁剪

显然,如果您使用此代码,您将需要稍微清理它并添加error handling。 如果我正确理解你的问题,那么按照这种方式做事就不需要使用AForge了。

你可以尝试这样的事情:

 public static Bitmap CropBitmap(Bitmap bitmap, int x, int y, int w, int h) { Rectangle rect = new Rectangle(x, y, w, h); Bitmap cropped = bitmap.Clone(rect, bitmap.PixelFormat); return cropped; } 

并在你的代码(样本)中做这样的事情:

 var croppedImagem = CropBitmap(dataOriginal, 0, 0, 100, 100); 

我希望它有所帮助!

当我们用Marsc.copy替换memcpy时,Fopedush的答案会大大受益,因为我们不必通过byte []数组复制它。 这样,内存只被复制一次,而不是三次!

 [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] static unsafe extern int memcpy(byte* dest, byte* src, long count); static public Bitmap cropBitmap(Bitmap sourceImage, Rectangle rectangle) { const int BPP = 4; //4 Bpp = 32 bits; argb var sourceBitmapdata = sourceImage.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); var croppedImage = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppArgb); var croppedBitmapData = croppedImage.LockBits(new Rectangle(0, 0, rectangle.Width, rectangle.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); unsafe { croppedBitmapData.Stride = sourceBitmapdata.Stride; byte* sourceImagePointer = (byte*)sourceBitmapdata.Scan0.ToPointer(); byte* croppedImagePointer = (byte*)croppedBitmapData.Scan0.ToPointer(); memcpy(croppedImagePointer, sourceImagePointer, Math.Abs(croppedBitmapData.Stride) * rectangle.Height); } sourceImage.UnlockBits(sourceBitmapdata); croppedImage.UnlockBits(croppedBitmapData); return croppedImage; } 

我的结果是:

 BitmapClone: 1823 ms LockBits: 4857 ms Rectangle: 1479 ms My method: 559 ms My method with LockBits on source image done only once (before loop): 160 ms 

我没有AForge,所以我没有把它包括在内,但通过查看op的结果,它会慢于此。 我正在测试将图像裁剪成两半。

请注意,如果我们要与memcpy交换:

 for (int k = 0; k < Math.Abs(croppedBitmapData.Stride) * rectangle.Height; k++) *(croppedImagePointer++) = *(sourceImagePointer++); 

它慢了10倍!

我是新用户,不能投票,否则我会赞成Korwin80的答案,因为它提供了最有效的工作解决方案,在我看来。 trakos的解决方案可能会执行得更快,但会产生混乱的图像,至少对我而言。 以下是我在自己的代码中应用Korwin80的解决方案的一些小改进:

 [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] private unsafe static extern int memcpy(byte* dest, byte* src, long count); private unsafe Bitmap Crop(Bitmap srcImg, Rectangle rectangle) { if ((srcImg.Width == rectangle.Width) && (srcImg.Height == rectangle.Height)) return srcImg; var srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat); var bpp = srcImgBitmapData.Stride / srcImgBitmapData.Width; // 3 or 4 var srcPtr = (byte*)srcImgBitmapData.Scan0.ToPointer() + rectangle.Y * srcImgBitmapData.Stride + rectangle.X * bpp; var srcStride = srcImgBitmapData.Stride; var dstImg = new Bitmap(rectangle.Width, rectangle.Height, srcImg.PixelFormat); var dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat); var dstPtr = (byte*)dstImgBitmapData.Scan0.ToPointer(); var dstStride = dstImgBitmapData.Stride; for (int y = 0; y < rectangle.Height; y++) { memcpy(dstPtr, srcPtr, dstStride); srcPtr += srcStride; dstPtr += dstStride; } srcImg.UnlockBits(srcImgBitmapData); dstImg.UnlockBits(dstImgBitmapData); return dstImg; } 
 internal unsafe sealed class FastImageCroper : IDisposable { private readonly Bitmap _srcImg; private readonly BitmapData _srcImgBitmapData; private readonly int _bpp; private readonly byte* _srtPrt; public FastImageCroper(Bitmap srcImg) { _srcImg = srcImg; _srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat); _bpp = _srcImgBitmapData.Stride / _srcImgBitmapData.Width; // == 4 _srtPrt = (byte*)_srcImgBitmapData.Scan0.ToPointer(); } public Bitmap Crop(Rectangle rectangle) { Bitmap dstImg = new Bitmap(rectangle.Width, rectangle.Height, _srcImg.PixelFormat); BitmapData dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat); byte* dstPrt = (byte*)dstImgBitmapData.Scan0.ToPointer(); byte* srcPrt = _srtPrt + rectangle.Y*_srcImgBitmapData.Stride + rectangle.X*_bpp; for (int y = 0; y < rectangle.Height; y++) { int srcIndex = y * _srcImgBitmapData.Stride; int croppedIndex = y * dstImgBitmapData.Stride; memcpy(dstPrt + croppedIndex, srcPrt + srcIndex, dstImgBitmapData.Stride); } dstImg.UnlockBits(dstImgBitmapData); return dstImg; } public void Dispose() { _srcImg.UnlockBits(_srcImgBitmapData); } [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] private static extern int memcpy(byte* dest, byte* src, long count); }