Trabajo rápido con mapas de bits en C #

Necesito acceder a cada píxel de un bitmap, trabajar con ellos y luego guardarlos en un bitmap.

Usando Bitmap.GetPixel() y Bitmap.SetPixel() , mi progtwig se ejecuta lentamente.

¿Cómo puedo convertir rápidamente Bitmap a byte[] y viceversa?

Necesito un byte[] con length = (4 * width * height) , que contiene datos RGBA de cada píxel.

Puedes hacerlo de dos maneras diferentes. Puede utilizar unsafe para obtener acceso directo a los datos, o puede usar referencias para copiar los datos hacia adelante y hacia atrás. El código inseguro es más rápido, pero la clasificación no requiere un código inseguro. Aquí hay una comparación de rendimiento que hice hace un tiempo.

Aquí hay una muestra completa usando 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; } 

Esto es lo mismo, pero con referencias:

 /*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; } 

Quieres LockBits . A continuación, puede extraer los bytes que desea del objeto BitmapData que le proporciona.

Puede usar el método Bitmap.LockBits. Además, si desea utilizar la ejecución de tareas paralelas, puede usar la clase Parallel en el espacio de nombres System.Threading.Tasks. Los siguientes enlaces tienen algunas muestras y explicaciones.

Basándome en la respuesta de @notJim (y con la ayuda de http://www.bobpowell.net/lockingbits.htm ), desarrollé lo siguiente que hace mi vida mucho más fácil ya que termino con una variedad de matrices que me permiten saltar a un píxel por sus coordenadas y . Por supuesto, la coordenada x necesita ser corregida por el número de bytes por píxel, pero esa es una extensión fácil.

 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 

Hay otra manera que es mucho más rápida y mucho más conveniente. Si echas un vistazo a los constructores de mapas de bits, encontrarás uno que toma e IntPtr como último parámetro. Ese IntPtr es para contener datos de píxeles. Entonces, ¿cómo se usa?

 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) 

Lo que tienes ahora es una matriz de enteros simple y un bitmap que hace referencia a la misma memoria. Cualquier cambio que realice en la matriz de enteros afectará directamente al bitmap. Permítanos probar esto con una simple transformación de brillo.

 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 

Puede notar que he usado un pequeño truco para evitar hacer matemáticas de punto flotante dentro del ciclo. Esto mejora el rendimiento bastante. Y cuando hayas terminado debes limpiar un poco, por supuesto ...

 addr = IntPtr.Zero If handle.IsAllocated Then handle.Free() handle = Nothing End If bitmap.Dispose() bitmap = Nothing pixels = Nothing 

He ignorado el componente alfa aquí, pero también puedes utilizarlo. He creado muchas herramientas de edición de mapas de bits de esta manera. Es mucho más rápido y más confiable que Bitmap.LockBits () y lo mejor de todo es que requiere cero copias de memoria para comenzar a editar su bitmap.

Prueba esta solución C #.

Crea una aplicación de winforms para probar.

Agregue un botón y un PictureBox, y un evento de clic y un evento de cierre de formulario.

Use el siguiente código para su formulario:

 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; } } 

Ahora, cualquier edición que realice en la matriz actualizará automáticamente el bitmap.

Tendrá que llamar al método de actualización en el cuadro de imagen para ver estos cambios.