Necesito para acceder a cada uno de los píxeles de un mapa de bits, trabajar con ellos, a continuación, guardarlos en un mapa de bits.

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

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

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

6 Comentarios

  1. 76

    Usted puede hacer un par de maneras diferentes. Usted puede utilizar unsafe para obtener acceso directo a los datos, o puede utilizar el cálculo de referencias para copiar los datos de ida y vuelta. El código inseguro es más rápido, pero el cálculo de referencias no requiere de código inseguro. He aquí un comparación de rendimiento que hice hace un tiempo.

    He aquí un ejemplo completo de uso de 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
                //data[0] = blueComponent;
                //data[1] = greenComponent;
                //data[2] = redComponent;
            }
        }
    
        b.UnlockBits(bData);
    
        return b;
    }

    Aquí 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;
    }
    • Gracias, y yo ni siquiera han torear a nadie. 🙂
    • Usted debe ser consciente de cuando se hace esto, que el orden de los canales de color puede ser diferente dependiendo de lo que PixelFormat que está utilizando. Por ejemplo, con mapas de bits de 24 bits, el primer byte del píxel es el canal azul, seguido por el verde, luego rojo, en contraposición a lo que comúnmente se espera de Rojo-Verde-Azul de la orden.
    • Gracias por tu esfuerzo!
    • bData.Altura parece un poco caro. Tengo algo de impulso en el rendimiento de saltar
    • seguro, omitiendo bData.Height acelerar las cosas, ya que solo se están tomando la primera fila de los datos. Trate de size = 1 que voy a hacer las cosas aún más rápido!
    • También debe ser consciente de que esto no puede conseguir espera de bytes de datos, ya que generalmente bData.Stride no tiene que ser igual a b.Width * bytePerPix.
    • ¿cuál es el objetivo de la utilización de la variable magnitud?
    • Para >=8bpp imágenes me suele salir alrededor de la zancada diferencia copiando por línea, aumentando la fuente compensado por la zancada con cada línea pero copiar a una nueva matriz con el ancho real (bueno, width * BPP / 8 de curso) * altura como en longitud. Para las imágenes con <8bpp, aunque, lo que se… interesante. Una buena regla es la de incluir siempre la calma, con sus funciones de procesamiento de imagen, preferiblemente como parámetro de referencia en caso de que se cambió para su salida.
    • El enlace es ded, ¿alguien puede hacer un resumen de lo que dijo?
    • corregido el enlace
    • ¿Qué es GetBitsPerPixel() aquí?
    • @ja72 System.Drawing.Bitmap.GetPixelFormatSize(...);
    • No es el orden de los canales de color dependiendo el peso, más que el formato de píxel?

  2. 4

    Puede utilizar de mapa de bits.LockBits método. También si desea utilizar paralelo de la ejecución de la tarea, puede utilizar el Paralelo de la clase en el Sistema.Las operaciones de roscado.Tareas de espacio de nombres. Siguientes enlaces tienes algunos ejemplos y explicaciones.

  3. 3

    Desea LockBits. Usted puede, a continuación, extraer los bytes que desea desde el objeto BitmapData que le da.

    • Creo que es más rápido si utiliza el BitmapData objeto devuelto de LockBits dentro de un unsafe bloque con un puntero de conversión (NOTA: no tengo idea de si es más rápido o no, pero estoy tratando de torear a alguien más en el benchmarking es).
    • Desea guardar la copia en y fuera de la BitmapData, lo cual es bueno. No estoy seguro cuánto ahorro que podría garantizar en la práctica, sin embargo; memcpy() es muy rápido en estos días.
  4. 2

    Edificio en @notJim respuesta (y con la ayuda de http://www.bobpowell.net/lockingbits.htm), he desarrollado el siguiente que hace mi vida mucho más fácil en que termino con una matriz de matrices que me permita ir a un píxel es x y y coordenadas. Por supuesto, la x coordinar necesita ser corregido por el número de bytes por píxel, sino que 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
  5. 2

    Hay otra manera que es mucho más rápido y mucho más conveniente. Si usted tiene un vistazo al mapa de bits constructores encontrarás una que se lleva y IntPtr como el último parámetro. Que IntPtr es para la celebración de los datos de los píxeles. Entonces, ¿cómo usarlo?

    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 tenemos ahora es una simple matriz de Enteros y un mapa de bits hace referencia a la misma memoria. Los cambios que realice a la matriz de Enteros será lo que afecta directamente al mapa de bits. Vamos a tratar esto con un simple brillo de transformación.

    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 observar que he utilizado un pequeño truco para evitar hacer matemáticas de punto flotante en el interior del bucle. Esto mejora el rendimiento un poco.
    Y cuando haya terminado usted necesita para limpiar un poco de curso…

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

    Me han hecho caso omiso de la componente alfa aquí, pero usted es libre de usarlo así. Me han juntado un montón de mapa de bits, herramientas de edición de esta manera. Es mucho más rápido y más fiable que la de mapa de bits.LockBits() y lo mejor de todo, es necesario cero de la memoria de copia para empezar a editar el mapa de bits.

  6. 0

    Pruebe esta solución de C#.

    Crear una aplicación winforms para la prueba.

    Agregar un Botón y un Cuadro de imagen, y haga clic en un evento y una forma de cierre del evento.

    Utilice 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, los cambios que efectúe en la matriz, se actualizará automáticamente el mapa de bits.

    Usted tendrá que llamar al método refresh en el picturebox para ver estos cambios.

Dejar respuesta

Please enter your comment!
Please enter your name here