快速使用C#中的位图
我需要访问位图的每个像素,使用它们,然后将它们保存到位图。
使用Bitmap.GetPixel()
和Bitmap.SetPixel()
,我的程序运行缓慢。
我怎样才能快速转换Bitmap
到byte[]
然后回来?
我需要一个length = (4 * width * height)
的byte[]
,其中包含每个像素的RGBA数据。
你可以通过几种不同的方法来完成。 您可以使用unsafe
方式直接访问数据,也可以使用封送来回复制数据。 不安全的代码更快,但编组不需要不安全的代码。 这是我之前做过的一次性能比较 。
这是一个使用lockbits的完整示例:
/*Note unsafe keyword*/ public unsafe Image ThresholdUA(float thresh) { Bitmap b = new Bitmap(_image);//note this has several overloads, including a path to an image BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat); byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat); /*This time we convert the IntPtr to a ptr*/ byte* scan0 = (byte*)bData.Scan0.ToPointer(); for (int i = 0; i < bData.Height; ++i) { for (int j = 0; j < bData.Width; ++j) { byte* data = scan0 + i * bData.Stride + j * bitsPerPixel / 8; //data is a pointer to the first byte of the 3-byte color data } } b.UnlockBits(bData); return b; }
以下是同样的事情,但用编组:
/*No unsafe keyword!*/ public Image ThresholdMA(float thresh) { Bitmap b = new Bitmap(_image); BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat); /* GetBitsPerPixel just does a switch on the PixelFormat and returns the number */ byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat); /*the size of the image in bytes */ int size = bData.Stride * bData.Height; /*Allocate buffer for image*/ byte[] data = new byte[size]; /*This overload copies data of /size/ into /data/ from location specified (/Scan0/)*/ System.Runtime.InteropServices.Marshal.Copy(bData.Scan0, data, 0, size); for (int i = 0; i < size; i += bitsPerPixel / 8 ) { double magnitude = 1/3d*(data[i] +data[i + 1] +data[i + 2]); //data[i] is the first of 3 bytes of color } /* This override copies the data back into the location specified */ System.Runtime.InteropServices.Marshal.Copy(data, 0, bData.Scan0, data.Length); b.UnlockBits(bData); return b; }
你想要LockBits 。 然后,您可以从它提供的BitmapData对象中提取所需的字节。
您可以使用Bitmap.LockBits方法。 另外如果你想使用并行任务执行,你可以在System.Threading.Tasks命名空间中使用Parallel类。 以下链接有一些样本和解释。
build立在@notJim的答案(在http://www.bobpowell.net/lockingbits.htm的帮助下),我开发了以下内容,使我的生活变得更加容易,因为我最终得到了一组数组,通过它的;x
和y
坐标跳转到一个像素。 当然, x
坐标需要按照每个像素的字节数进行校正,但这是一个简单的扩展。
Dim bitmapData As Imaging.BitmapData = myBitmap.LockBits(New Rectangle(0, 0, myBitmap.Width, myBitmap.Height), Imaging.ImageLockMode.ReadOnly, myBitmap.PixelFormat) Dim size As Integer = Math.Abs(bitmapData.Stride) * bitmapData.Height Dim data(size - 1) As Byte Marshal.Copy(bitmapData.Scan0, data, 0, size) Dim pixelArray(myBitmap.Height)() As Byte 'we have to load all the opacity pixels into an array for later scanning by column 'the data comes in rows For y = myBitmap.Height - 1 To 0 Step -1 Dim rowArray(bitmapData.Stride) As Byte Array.Copy(data, y * bitmapData.Stride, rowArray, 0, bitmapData.Stride) 'For x = myBitmap.Width - 1 To 0 Step -1 ' Dim i = (y * bitmapData.Stride) + (x * 4) ' Dim B = data(i) ' Dim G = data(i + 1) ' Dim R = data(i + 2) ' Dim A = data(i + 3) 'Next pixelArray(y) = rowArray Next
还有另一种方式更快,更方便。 如果你看看位图构造函数,你会发现一个以IntPtr作为最后一个参数。 IntPtr用于保存像素数据。 那么你如何使用它?
Dim imageWidth As Integer = 1920 Dim imageHeight As Integer = 1080 Dim fmt As PixelFormat = PixelFormat.Format32bppRgb Dim pixelFormatSize As Integer = Image.GetPixelFormatSize(fmt) Dim stride As Integer = imageWidth * pixelFormatSize Dim padding = 32 - (stride Mod 32) If padding < 32 Then stride += padding Dim pixels((stride \ 32) * imageHeight) As Integer Dim handle As GCHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned) Dim addr As IntPtr = Marshal.UnsafeAddrOfPinnedArrayElement(pixels, 0) Dim bitmap As New Bitmap(imageWidth, imageHeight, stride \ 8, fmt, addr)
你现在拥有的是一个简单的Integer数组和一个引用相同内存的位图。 对整数数组所做的任何更改都将直接影响位图。 让我们试试这个简单的亮度变换。
Public Sub Brightness(ByRef pixels() As Integer, ByVal scale As Single) Dim r, g, b As Integer Dim mult As Integer = CInt(1024.0f * scale) Dim pixel As Integer For i As Integer = 0 To pixels.Length - 1 pixel = pixels(i) r = pixel And 255 g = (pixel >> 8) And 255 b = (pixel >> 16) And 255 'brightness calculation 'shift right by 10 <=> divide by 1024 r = (r * mult) >> 10 g = (g * mult) >> 10 b = (b * mult) >> 10 'clamp to between 0 and 255 If r < 0 Then r = 0 If g < 0 Then g = 0 If b < 0 Then b = 0 r = (r And 255) g = (g And 255) b = (b And 255) pixels(i) = r Or (g << 8) Or (b << 16) Or &HFF000000 Next End Sub
您可能会注意到我已经使用了一些小技巧来避免在循环中进行浮点运算。 这相当提高了性能。 而当你完成你需要清理一点,当然…
addr = IntPtr.Zero If handle.IsAllocated Then handle.Free() handle = Nothing End If bitmap.Dispose() bitmap = Nothing pixels = Nothing
我在这里忽略了alpha组件,但是你也可以自由使用它。 我用这种方法把很多位图编辑工具扔在了一起。 它比Bitmap.LockBits()更快更可靠,最重要的是它需要零拷贝来开始编辑你的位图。
试试这个C#解决scheme。
创build一个winforms应用程序进行testing。
添加一个button和一个PictureBox,一个单击事件和一个表单closures事件。
为您的表单使用以下代码:
public partial class Form1 : Form { uint[] _Pixels { get; set; } Bitmap _Bitmap { get; set; } GCHandle _Handle { get; set; } IntPtr _Addr { get; set; } public Form1() { InitializeComponent(); int imageWidth = 100; //1920; int imageHeight = 100; // 1080; PixelFormat fmt = PixelFormat.Format32bppRgb; int pixelFormatSize = Image.GetPixelFormatSize(fmt); int stride = imageWidth * pixelFormatSize; int padding = 32 - (stride % 32); if (padding < 32) { stride += padding; } _Pixels = new uint[(stride / 32) * imageHeight + 1]; _Handle = GCHandle.Alloc(_Pixels, GCHandleType.Pinned); _Addr = Marshal.UnsafeAddrOfPinnedArrayElement(_Pixels, 0); _Bitmap = new Bitmap(imageWidth, imageHeight, stride / 8, fmt, _Addr); pictureBox1.Image = _Bitmap; } private void button1_Click(object sender, EventArgs e) { for (int i = 0; i < _Pixels.Length; i++) { _Pixels[i] = ((uint)(255 | (255 << 8) | (255 << 16) | 0xff000000)); } } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { _Addr = IntPtr.Zero; if (_Handle.IsAllocated) { _Handle.Free(); } _Bitmap.Dispose(); _Bitmap = null; _Pixels = null; } }
现在,对arrays进行的任何编辑都会自动更新位图。
您将需要调用picturebox上的刷新方法来查看这些更改。