2009-10-13 15 views
37

Necesito acceder a cada píxel de un mapa de bits, trabajar con ellos y luego guardarlos en un mapa de bits.Trabajo rápido con mapas de bits en C#

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

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

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

+1

El siguiente enlace contiene métodos de acceso de píxeles de mapa de bits comparativamente. http://csharpexamples.com/fast-image-processing-c/ – turgay

Respuesta

65

Puede hacerlo de diferentes maneras. Puede usar 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 un performance comparison que hice hace un tiempo.

He aquí 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; 
} 

Ésta es la misma cosa, pero con el cálculo de 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; 
} 
+1

Gracias, y ni siquiera tuve que molestar a nadie. :) – MusiGenesis

+8

Al hacer esto, debe tener en cuenta que el orden de los canales de color puede ser diferente dependiendo de qué PixelFormat esté usando. Por ejemplo, con los mapas de bits de 24 bits, el primer byte del píxel es el canal azul, seguido del verde y luego del rojo, a diferencia del orden comúnmente esperado de color rojo-verde-azul. – Jargon

+0

¡Gracias por su esfuerzo! –

3

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

+0

Creo que es más rápido si usas el objeto 'BitmapData' devuelto por LockBits dentro de un bloque' inseguro' con un molde de puntero (NOTA: en realidad no tengo ni idea si esto es más rápido o no, pero estoy tratando de incitar a otra persona a que lo comparta). – MusiGenesis

+1

Te ahorraría las copias dentro y fuera de BitmapData, lo cual es bueno. Sin embargo, no estoy seguro de cuánto ahorro se otorgaría en la práctica; memcpy() es realmente rápido en estos días. –

1

Basándose en respuesta @notJim (y con la ayuda de http://www.bobpowell.net/lockingbits.htm), he desarrollado la Seguir eso hace que mi vida sea mucho más fácil ya que termino con una matriz de matrices que me permite saltar a un píxel por sus coordenadas x y y. Por supuesto, la coordenada x debe corregirse 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 
0

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 tiene ahora es una matriz de enteros simple y un mapa de bits que hace referencia a la misma memoria. Cualquier cambio que realice en la matriz de enteros afectará directamente al mapa de bits. 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 coma flotante en el ciclo. Esto mejora el rendimiento bastante. Y cuando haya terminado lo que necesita para limpiar un poco claro ...

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

me han ignorado el componente alfa aquí, pero usted es libre de utilizar eso también. 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.

0

Pruebe 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.

utilizar el siguiente código para el 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, ninguna de las modificaciones que realice en la matriz actualizará automáticamente el mapa de bits.

Deberá llamar al método de actualización en la ventana de imagen para ver estos cambios.

Cuestiones relacionadas