7 comments

@Shaamil Ahmed 2020-01-09 06:55:59

If you're on .NET Core or .NET Standard I'll suggest to use the new Span<T> for higher efficiency.

Here's a rough implementation

public unsafe class UnmanagedBitmap : IDisposable
{
    private Bitmap bmp;
    private Rectangle rect;
    private int bytesPerPixel;
    private IntPtr bufferPtr;
    private byte* bufferBytes;
    private int size;
    private BitmapData bData;
    private ImageLockMode lockMode;
    private byte* scan0;

    public UnmanagedBitmap(Bitmap bmp, ImageLockMode lockMode)
    {
        this.bmp = bmp;
        this.lockMode = lockMode;

        bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
        size = bmp.Width * bmp.Height * bytesPerPixel;
        bufferPtr = Marshal.AllocHGlobal(size);
        bufferBytes = (byte*)bufferPtr;

        rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        bData = bmp.LockBits(rect, lockMode, bmp.PixelFormat);
        scan0 = (byte*)bData.Scan0.ToPointer();
    }

    public void Dispose()
    {
        bmp.UnlockBits(bData);
        Marshal.FreeHGlobal(bufferPtr);
    }

    public Span<byte> GetPixel(int x, int y)
    {
        var pixel = scan0 + y * bData.Stride * x * bytesPerPixel;
        return new Span<byte>(pixel, bytesPerPixel);
    }
}

You can also modify the code to return a byte array instead if you're on .NET framework

@davidtbernal 2009-10-13 21:59:37

You can do it a couple of different ways. You can use unsafe to get direct access to the data, or you can use marshaling to copy the data back and forth. The unsafe code is faster, but marshaling doesn't require unsafe code. Here's a performance comparison I did a while back.

Here's a complete sample using 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;
}

Here's the same thing, but with 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;
}

@MusiGenesis 2009-10-13 22:10:27

Thanks, and I didn't even have to goad anybody. :)

@Steve Niles 2012-12-28 22:33:57

You should be aware when doing this, that the order of the color channels may be different depending on what PixelFormat you're using. For example, with 24bit Bitmaps, the first byte of the pixel is the blue channel, followed by green, then red, as opposed to the commonly expected Red-Green-Blue order.

@freakinpenguin 2014-07-01 08:22:02

Thanks for you effort!

@GorillaApe 2014-09-30 11:50:09

bData.Height seems a bit expensive. I got some performance boost skipping it

@danodonovan 2015-02-19 14:55:55

@Parhs sure, skipping bData.Height will speed things up, because you're only taking the first row of your data. Try size = 1 that'll make things even faster!

@wondra 2015-06-26 23:04:07

You should also be aware that this may not get expected byte data because generally bData.Stride does not have to be equal to b.Width * bytePerPix.

@Md Sifatul Islam 2017-02-15 08:50:02

whats the purpose of using the variable magnitude?

@Nyerguds 2017-04-24 07:27:11

For >=8bpp images I usually get around the stride difference by copying per line, increasing the source offset by the stride with each line but copying to a new array with the actual width (well, width * BPP / 8 of course) * height as length. For images with <8bpp though, stuff gets... interesting. A good rule is to always include the stride with your image processing functions, preferably as ref parameter in case it gets changed for your output.

@Pipe 2017-11-08 19:00:18

Link is ded, can anyone summarize what it said?

@davidtbernal 2017-11-24 05:24:18

@Pipe fixed the link

@ja72 2018-01-30 01:06:31

What is GetBitsPerPixel() here?

@rotgers 2018-03-20 11:25:18

@ja72 System.Drawing.Bitmap.GetPixelFormatSize(...);

@royalTS 2018-09-07 09:19:01

@Jargon Isn´t the order of the color channels depending on the endianness rather than the pixel format?

@WonderWorker 2016-09-08 10:11:36

Try this C# solution.

Create a winforms app for testing.

Add a Button and a PictureBox, and a click event and a form closing event.

Use the following code for your form:

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;

    }

}

Now, any edits you make to the array will automatically update the Bitmap.

You will need to call the refresh method on the picturebox to see these changes.

@turgay 2014-09-03 11:39:49

You can use Bitmap.LockBits method. Also if you want to use parallel task execution, you can use the Parallel class in System.Threading.Tasks namespace. Following links have some samples and explanations.

@Daniklad 2014-05-13 17:51:48

There is another way that is way faster and much more convenient. If you have a look at the Bitmap constructors you will find one that takes and IntPtr as the last parameter. That IntPtr is for holding pixel data. So how do you use it?

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)

What you have now is a simple Integer array and a Bitmap referencing the same memory. Any changes you make to the Integer array will be directly affecting the Bitmap. Let us try this with a simple brightness transform.

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

You may notice that I have used a little trick to avoid doing floating point math within the loop. This improves performance quite a bit. And when you are done you need to clean up a little of course...

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

I have ignored the alpha component here but you are free to use that as well. I have thrown together a lot of bitmap editing tools this way. It is much faster and more reliable than Bitmap.LockBits() and best of all, it requires zero memory copying to start editing your bitmap.

@cjbarth 2013-02-28 22:54:45

Building on @notJim answer (and with help from http://www.bobpowell.net/lockingbits.htm), I developed the following that makes my life a lot easier in that I end up with an array of arrays that allows me to jump to a pixel by it's x and y coordinates. Of course, the x coordinate needs to be corrected for by the number of bytes per pixel, but that is an easy extension.

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

@David Seiler 2009-10-13 21:39:04

You want LockBits. You can then extract the bytes you want from the BitmapData object it gives you.

@MusiGenesis 2009-10-13 21:46:56

I think it's faster if you use the BitmapData object returned from LockBits inside an unsafe block with a pointer cast (NOTE: I actually have no idea if this is faster or not, but I'm trying to goad someone else into benchmarking it).

@David Seiler 2009-10-13 21:52:29

It'd save you the copies into and out of the BitmapData, which is nice. Not sure how much savings that would grant in practice, though; memcpy() is really fast these days.

Related Questions

Sponsored Content

61 Answered Questions

[SOLVED] How do I calculate someone's age in C#?

  • 2008-07-31 23:40:59
  • Jeff Atwood
  • 585665 View
  • 1795 Score
  • 61 Answer
  • Tags:   c# .net datetime

44 Answered Questions

[SOLVED] How do I create an Excel (.XLS and .XLSX) file in C# without installing Microsoft Office?

  • 2008-09-29 22:30:28
  • mistrmark
  • 1061244 View
  • 1829 Score
  • 44 Answer
  • Tags:   c# .net excel file-io

66 Answered Questions

[SOLVED] What is the difference between String and string in C#?

10 Answered Questions

[SOLVED] What are the correct version numbers for C#?

39 Answered Questions

18 Answered Questions

[SOLVED] Is there a way to check if a file is in use?

18 Answered Questions

[SOLVED] How to convert a Drawable to a Bitmap?

43 Answered Questions

18 Answered Questions

[SOLVED] Save bitmap to location

  • 2009-03-16 03:26:06
  • Chrispix
  • 472693 View
  • 460 Score
  • 18 Answer
  • Tags:   android bitmap save

Sponsored Content