none
Trying to convert bitmap to unsigned integer array . . . quickly RRS feed

  • Question

  • Hi

    Some years ago, John Wein provided the GCHandle method to quickly convert an unsigned integer array into a bitmap. I am now trying to do the opposite - - convert a bitmap into an unsigned integer array. I was hoping there was a method similar to GCHandle to achieve this. I understand you can use lock bits to quickly convert a bitmap into a byte array. But I want a direct bitmap to unsigned integer if possible.

    This is my unsuccessful attempt : -

    Imports System.Runtime.InteropServices
    Public Class Form4
        Private bmp As New Bitmap(16, 16)
        Private gcH As GCHandle
        Private ntgr() As UInteger
    
        Private Sub Form7_Load(sender As Object, e As EventArgs) Handles Me.Load
            ClientSize = New Size(1000, 500)
            CenterToScreen()
        End Sub
    
        Private Sub Form4_Click(sender As Object, e As EventArgs) Handles Me.Click
            BitmapToUintExample()
            'UIntToBitmapExample()
            Invalidate()
        End Sub
    
        Private Sub BitmapToUintExample()
            bmp = New Bitmap("e:\LTemp2017\GPU_gone.png") ' << Change this to your chosen picture file
            Dim I As Integer = bmp.Width * bmp.Height - 1
            'gcH = GCHandle.Alloc(bmp, GCHandleType.Pinned)
            'ReDim ntgr(I)
            'Marshal.Copy(gcH.AddrOfPinnedObject, ntgr, 0, I)
            'gcH.Free()
        End Sub
    
        Private Sub UIntToBitmapExample()
            Dim I As Integer
            ReDim ntgr(404999) ' = W * H - 1    for a 900 X 450 bitmap 32bpp
            For I = 0 To 404999
                ntgr(I) = 4294967040 ' yellow fill
            Next
            For I = 5400 To 7199 ' draw black line
                ntgr(I) = 4278190080
            Next
            gcH = GCHandle.Alloc(ntgr, GCHandleType.Pinned)
            bmp = New Bitmap(900, 450, 3600, Imaging.PixelFormat.Format32bppPArgb, gcH.AddrOfPinnedObject)
            gcH.Free()
        End Sub
    
        Private Sub Form4_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
            e.Graphics.DrawImage(bmp, 0, 0)
        End Sub
    End Class


    Pride is the most destructive force in the universe


    • Edited by LeonCS Wednesday, September 27, 2017 9:15 AM
    Wednesday, September 27, 2017 9:12 AM

Answers

  • Hi LeonCS,

    I don't find the way to convert bitmap to unsigned integer array, but I find the way to convert bitmap to int array, use Marshal.Copy Method, but  the method Marshal.Copy doesn't take uint[]

    https://stackoverflow.com/questions/1998995/bitmap-to-int-using-marshal-copy

    Best Regards,

    Cherry


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    • Marked as answer by LeonCS Sunday, October 1, 2017 12:01 AM
    Thursday, September 28, 2017 6:22 AM
    Moderator

All replies

  • I understand you can use lock bits to quickly convert a bitmap into a byte array. But I want a direct bitmap to unsigned integer if possible.

    An integer array and a byte array are the same thing with a different type mapping (EQUIVALENCE In FORTRAN).  What was the problem that you ran into in trying to convert that code to use an integer array instead of a byte array?

    Wednesday, September 27, 2017 12:09 PM
  • Hi LeonCS,

    I don't find the way to convert bitmap to unsigned integer array, but I find the way to convert bitmap to int array, use Marshal.Copy Method, but  the method Marshal.Copy doesn't take uint[]

    https://stackoverflow.com/questions/1998995/bitmap-to-int-using-marshal-copy

    Best Regards,

    Cherry


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    • Marked as answer by LeonCS Sunday, October 1, 2017 12:01 AM
    Thursday, September 28, 2017 6:22 AM
    Moderator
  • Thanks Acamar

    I have not yet altered lock bits code. I just stole the marshal copy line and tried to bung it into GCHandle context. gchandle alloc will not pin bitmap type and then without pinned, the marshal copy line wont have its pointer

    Appears I can use lock bit to convert it into a signed integer array as Cherry says.

    Give me time to joggle things around. Will publish when I get organized.

    Cheers 


    Pride is the most destructive force in the universe


    • Edited by LeonCS Thursday, September 28, 2017 10:20 AM
    Thursday, September 28, 2017 10:15 AM
  • Appears I can use lock bit to convert it into a signed integer array as Cherry says.

    Signed / Unsigned is also just a Type mapping - no change is required in the data.  Unless you are expecting the unsigned integers to be meaningful in some predefined context.

    Thursday, September 28, 2017 10:33 AM
  •  I agree that unless there is some special reason that they need to be UIntegers,  then you can read the data as a Byte or Integer type array much easier and probably quicker.  However,  if you really need them to be unsigned integers then you can read the data as a Byte array,  then loop through the bytes four at a time using the BitConverter.ToUInt32 method to convert each 4 bytes to a UInteger placing them into the array.

     With a few quick tests i came up with the averages in milliseconds for 3 different size images (seen in code below).

    Imports System.Drawing.Imaging
    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Private sw As New Stopwatch
        Private SourceBitmap As New Bitmap("C:\TestFolder\angrybird.png") ' 2048x1536=176ms | 1280x959=68ms | 128x128=1ms
        Private uiArray() As UInteger = {}
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            sw.Start()
    
            Dim bmd As BitmapData = SourceBitmap.LockBits(New Rectangle(Point.Empty, SourceBitmap.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)
            Dim bts((bmd.Stride * bmd.Height) - 1) As Byte
            Marshal.Copy(bmd.Scan0, bts, 0, bts.Length)
            SourceBitmap.UnlockBits(bmd)
    
            ReDim uiArray((bts.Length \ 4) - 1)
    
            For i As Integer = 0 To uiArray.Length - 1
                uiArray(i) = BitConverter.ToUInt32(bts, i * 4)
            Next
    
            sw.Stop()
            Me.Text = sw.ElapsedMilliseconds.ToString
            sw.Reset()
        End Sub
    End Class
    

     


    If you say it can`t be done then i`ll try it

    Thursday, September 28, 2017 5:23 PM
  • Signed / Unsigned is also just a Type mapping - no change is required in the data.  Unless you are expecting the unsigned integers to be meaningful in some predefined context.

    To be able to quickly scan all the existing colors in a bitmap, when compiling palettes for example - - . Devon's thread 

    https://social.msdn.microsoft.com/Forums/en-US/e3e92657-87d9-492f-92a7-030c23dc89df/help-with-lockbits-needed?forum=vbgeneral 

    triggered my thought's on how I would do this. Using signed instead of unsigned is no problem really.

    The following code seems to work OK - but I have not carefully tested it - so may contain errors

    Imports System.Runtime.InteropServices Public Class Form4 Private bmp As New Bitmap(16, 16) Private gcH As GCHandle

    Private Sub Form7_Load(sender As Object, e As EventArgs) Handles Me.Load ClientSize = New Size(1000, 1100) CenterToScreen() End Sub Private Sub Form4_Click(sender As Object, e As EventArgs) Handles Me.Click LockBitsBmpToIntExample() Invalidate() End Sub Private Sub LockBitsBmpToIntExample() ' Create a new bitmap. 'bmp = New Bitmap("e:\LTemp2017\GPU_gone.png") ' << this is a 24bit bitmap so it wont work in the following code bmp = New Bitmap("e:\LTemp2017\WLH01.png") ' << Change this to your chosen picture file - must load as a 32bit bitmap ' This code is specific to a bitmap with 32 bits per pixels. ' Lock the bitmap's bits. Dim rect As New Rectangle(0, 0, bmp.Width, bmp.Height) Dim bmpData As Imaging.BitmapData = bmp.LockBits(rect, Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat) ' Get the address of the first line. Dim ptr As IntPtr = bmpData.Scan0 ' Declare an array to hold the data of the bitmap. Dim I As Integer = CInt(bmpData.Stride * bmp.Height / 4) Dim intArray(I - 1) As Integer ' Copy the RGB values into the array. Marshal.Copy(ptr, intArray, 0, I) 'A 32bpp image will have a [B, G, R, A][B, G, R, A]. . . . format ' Unlock the bits. bmp.UnlockBits(bmpData) End Sub Private Sub Form4_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint e.Graphics.DrawImage(bmp, 0, 0) End Sub End Class



    Pride is the most destructive force in the universe


    • Edited by LeonCS Saturday, September 30, 2017 11:51 PM
    Saturday, September 30, 2017 11:48 PM
  • Thanks Iron

    I think using BitConverter will slow things down. This should be a lot faster.??

    Dim J as integer

    For i As Integer = 0 To uiArray.Length - 1
                uiArray(i) = bts(j) + bts(j+1)*256 + bts(j+2)*65536 * bts(j+3)*16777216

    j+=4

            Next


    Pride is the most destructive force in the universe

    Sunday, October 1, 2017 12:00 AM
  •  Here is a small test i threw together the other day to convert a bitmap to an Integer array and then back to a bitmap. It has the average time in milliseconds for several sized images.  I tried it by copying the color data to a Byte array and an Integer array but,  it did not seem to make any difference in the average times though.

    Imports System.Drawing.Imaging
    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Private sw As New Stopwatch
        Private SourceBitmap As New Bitmap("C:\TestFolder\MyCat.jpg")
        Private ints() As Integer = {}
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            '---------------------------------------
            '| Image Size  |  Average Milliseconds |
            '|-------------------------------------|
            '| 4726x2960   |  211                  |
            '| 2048x1536   |  48                   |
            '| 1280x959    |  22                   |
            '| 640x480     |  5                    |
            '| 128x128     |  0                    |
            '---------------------------------------
    
            sw.Start()
    
            Dim bmd As BitmapData = SourceBitmap.LockBits(New Rectangle(Point.Empty, SourceBitmap.Size), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)
            ReDim ints(((bmd.Stride * bmd.Height) \ 4) - 1)
            Marshal.Copy(bmd.Scan0, ints, 0, ints.Length)
            SourceBitmap.UnlockBits(bmd)
    
            sw.Stop()
            Label1.Text = "Convert To Array: " & sw.ElapsedMilliseconds.ToString & "ms"
            sw.Reset()
        End Sub
    
        Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
            sw.Start()
    
            Dim gcH As GCHandle = GCHandle.Alloc(ints, GCHandleType.Pinned)
            Dim bm As New Bitmap(SourceBitmap.Width, SourceBitmap.Height, (SourceBitmap.Width * 4), PixelFormat.Format32bppArgb, gcH.AddrOfPinnedObject)
            gcH.Free()
    
            sw.Stop()
            Label2.Text = "Convert To Bitmap: " & sw.ElapsedMilliseconds.ToString & "ms"
            sw.Reset()
    
            If PictureBox1.Image IsNot Nothing Then PictureBox1.Image.Dispose()
            PictureBox1.Image = bm
        End Sub
    End Class
     

     EDIT: Missed your last post but,  yes it does slow it down having to go back through and convert the bytes to UIntegers.  8)


    If you say it can`t be done then i`ll try it


    • Edited by IronRazerz Sunday, October 1, 2017 12:59 AM
    Sunday, October 1, 2017 12:48 AM
  • Iron

    Thanks for providing those times. I still think there SHOULD be a direct memory sharing method to do this instead of having to do a COPY process. Take for instance the integer to bitmap gchandle method takes microseconds - not 20 odd milliseconds - - because the new bitmap is simply addressed to the existing integer array memory. No copy procedure occurs. Both objects own the same memory, and both can manipulate the memory whilst unmanaged.

    It is a pity the reverse cannot be done (or so it seems ? ) Why cannot a new integer array be also addressed to an existing bitmap INT PTR. This should be allowable/possible just by basic reasoning.


    Pride is the most destructive force in the universe

    Sunday, October 1, 2017 1:24 AM
  • I found that a bitmapData object CAN be pinned with GCHandle.Alloc

    So I am half way there - - I now just need to know how to assign a new int array to a specific INT PTR

    see - -

        Private Sub BitmapToUintExample()
            bmp = New Bitmap("e:\LTemp2017\WLH01.png") ' << Change this to your chosen picture file
            Dim rect As New Rectangle(0, 0, bmp.Width, bmp.Height)
            Dim bmpData As Imaging.BitmapData = bmp.LockBits(rect,
            Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat)
            Dim I As Integer = bmp.Width * bmp.Height - 1
            gcH = GCHandle.Alloc(bmpData, GCHandleType.Pinned)
            'ReDim Preserve ntgr(I, gcH.AddrOfPinnedObject)
            'how to declare a new array upon gcH.AddrOfPinnedObject ? ? ? <<<
            gcH.Free()
        End Sub


    Pride is the most destructive force in the universe

    Sunday, October 1, 2017 1:52 AM
  •  Well,  yes you can do this with the BitmapData class but,  that does not contain the color data that you want,  it only contains the handle (BitmapData.Scan0) to the memory address where the first scan line starts.  This is why the Marshal.Copy method is needed,  to copy X amount of bytes starting at the Scan0 address.

      Not to be a negative-nellie but,  maybe digging around in the unmanaged win32 api's you could get a little faster response but,  i don`t see it happening using .net methods.  8)


    If you say it can`t be done then i`ll try it

    Sunday, October 1, 2017 1:00 PM