Trabalho rápido com bitmaps em c #

Eu preciso acessar cada pixel de um bitmap, trabalhar com eles e salvá-los em um bitmap.

Usando Bitmap.GetPixel() e Bitmap.SetPixel() , meu programa é executado lentamente.

Como posso converter rapidamente o Bitmap em byte[] e voltar?

Eu preciso de um byte[] com length = (4 * width * height) , contendo dados RGBA de cada pixel.

    Você pode fazer isso de várias maneiras diferentes. Você pode usar o unsafe para obter access direto aos dados ou pode usar o empacotamento para copiar os dados de um lado para outro. O código não seguro é mais rápido, mas o empacotamento não requer código inseguro. Aqui está uma comparação de desempenho que eu fiz um tempo atrás.

    Aqui está uma amostra 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; } 

    Aqui está a mesma coisa, mas com marshaling:

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

    Você quer LockBits . Você pode extrair os bytes desejados do object BitmapData que ele fornece.

    Você pode usar o método Bitmap.LockBits. Além disso, se você quiser usar a execução de tarefas paralelas, poderá usar a class Parallel no namespace System.Threading.Tasks. Os links a seguir contêm algumas amostras e explicações.

    Com base na resposta do @notJim (e com a ajuda do http://www.bobpowell.net/lockingbits.htm ), desenvolvi o seguinte que torna minha vida muito mais fácil, pois acabo com uma matriz de matrizes que me permite salte para um pixel pelas coordenadas x e y . Naturalmente, a coordenada x precisa ser corrigida pelo número de bytes por pixel, mas essa é uma extensão 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 

    Existe uma outra maneira que é muito mais rápida e muito mais conveniente. Se você der uma olhada nos construtores Bitmap, você encontrará um que toma e IntPtr como o último parâmetro. Esse IntPtr é para armazenar dados de pixel. Então, como você 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) 

    O que você tem agora é uma matriz Integer simples e um Bitmap que faz referência à mesma memory. Quaisquer alterações feitas na matriz Integer afetarão diretamente o Bitmap. Vamos tentar isso com uma simples transformação de brilho.

     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 

    Você pode notar que eu usei um pequeno truque para evitar fazer matemática de ponto flutuante dentro do loop. Isso melhora bastante o desempenho. E quando você terminar, você precisa limpar um pouco, claro ...

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

    Eu ignorei o componente alpha aqui, mas você está livre para usá-lo também. Eu montei muitas ferramentas de edição de bitmap dessa maneira. É muito mais rápido e confiável do que o Bitmap.LockBits () e, o melhor de tudo, requer zero cópias de memory para iniciar a edição do seu bitmap.

    Experimente esta solução C #.

    Crie um aplicativo do winforms para testes.

    Adicione um botão e um PictureBox e um evento de clique e um evento de fechamento de formulário.

    Use o seguinte código para o seu formulário:

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

    Agora, qualquer edição que você fizer na matriz atualizará automaticamente o Bitmap.

    Você precisará chamar o método de atualização no picturebox para ver essas alterações.