Results 1 to 9 of 9

Thread: [RESOLVED] Fast transfer byte data to picturebox?

  1. #1

    Thread Starter
    Fanatic Member
    Join Date
    Aug 2004
    Location
    Essex, UK
    Posts
    653

    Resolved [RESOLVED] Fast transfer byte data to picturebox?

    I need to transfer a block of pixel values as quickly as possible to a picturebox control. The data will be in a byte array representing 512 pixels by 4 lines. The data is greyscale values from 0 to 255 but I could easily store each value three times to represent R, G and B if that made it easier/quicker to write the values to the picturebox.

    I've used fastpix (by Vic Joseph "Boops Boops") in the past but this seems more geared towards fast plotting of individual pixels and as I have a pre-formatted block of pixel data I was wondering about alternatives. I've done a bit of research and BitBlt seems interesting, but can this be used with a byte array as the "source"? All the examples I've seen are for transferring/merging between bitmaps/pictureboxes. How about Direct Memory Access? I just need a simple "brute force" method of getting my pre-formatted data into the picturebox.

    Thanks for your help.

    Oh, when I say "fast" I mean I'd like to plot my 512 x 4 pixels in less than 10mS.
    Last edited by paulg4ije; Jul 16th, 2011 at 04:35 AM.

  2. #2
    Fanatic Member
    Join Date
    Jul 2009
    Posts
    629

    Re: Fast transfer byte data to picturebox?

    BitBlt works with device context operations. What this means is, that a "2D surface", the "context", sits in memory and that is transferred over.

    Since all you have is a (multi dimensional) array of bytes, you have no formatting yet. You need a raw bitmap format first, and since a bitmap is not in grey scales (or not always), there is no easy way of "memory copying" to achieve this. Perhaps using some API functions that handle byte array copying, or using "CopyMemory", but I am not sure about that.

    SetPixel should be pretty fast, though. You do the exact same memory copying as you would in an array copy. The only slow-making factor would be the creation of a new Bitmap object in memory.

    Note: If you would draw every pixel using a Graphics object you would have somewhat the same effect.

  3. #3
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,125

    Re: Fast transfer byte data to picturebox?

    Quote Originally Posted by bergerkiller View Post
    SetPixel should be pretty fast, though. You do the exact same memory copying as you would in an array copy. The only slow-making factor would be the creation of a new Bitmap object in memory.
    I don't know were you get that from, Bergerkiller. Creating an empty bitmap in memory is practically instantaneous, but SetPixel is usually too slow for practical image processing. Still, making a bitmap of 512*4 pixels shouldn't be much of a challenge however you do it.

    @Paulg4ije: thank's for the mention! If I've understood your requirement, a LockBits method such as FastPix provides a way of solving it. To test it, I create a 512*4 pixel array and fill it with Random bytes, then convert that to a PictureBox image:

    Code:
    	Dim testArray(511, 3) As Byte
    	Dim rnd As New Random
    
    	Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    		'fill the array with random values
    		For i As Integer = 0 To testArray.GetUpperBound(0)
    			For j As Integer = 0 To testArray.GetUpperBound(1)
    				testArray(i, j) = CByte(rnd.Next(256))
    			Next
    		Next
    
    		Dim sw As Stopwatch = Stopwatch.StartNew
    		Dim bmp As New Bitmap(512, 4)
    		Using fp As New Fastpix2(bmp)
    			Dim pixels As Integer() = fp.PixelArray
    			For i As Integer = 0 To testArray.GetUpperBound(0)
    				For j As Integer = 0 To testArray.GetUpperBound(1)
    					Dim gray As Byte = testArray(i, j)
    					Dim color As Integer = &HFF000000 + gray * &H10101
    					pixels(j * 512 + i) = color
    				Next
    			Next
    		End Using
    		PictureBox1.Image = bmp
    
    		MessageBox.Show((sw.ElapsedTicks * 1000 / Stopwatch.Frequency).ToString("0.00"))
    	End Sub
    Explanation: multiplying the gray byte by &H10101 fills the red, green and blue bytes of the integer color with the gray value. Adding &HFF000000 sets the alpha byte to 255. On pressing the button I got a time time of just over 1 millisecond - and that's on a fairly average twin core under WinXPh. Repeated presses give a processing time of about 0.1 milliseconds each time (presumably that's .Net optimization at work). That's well within you limits, isn't it?

    BB

    EDIT: code changed to create a new random array each time the button is pressed. I think that provides a more realistic evaluation, even though generating the array isn't in the timed section.

  4. #4

    Thread Starter
    Fanatic Member
    Join Date
    Aug 2004
    Location
    Essex, UK
    Posts
    653

    Re: Fast transfer byte data to picturebox?

    Thanks for the replies - sorry a bit late responding; just got in from a long day at a Christening.

    Thanks BB. Fastpix might well do what I want. I probably should spend more time working with it. Because I need to call the plotting routine quite frequently (at around 100mS intervals) I had tried to set it up with module-level scope so that I didn't have to keep creating the Fastpix reference each time, and I seemed to have trouble doing that. That is the main reason I started looking for alternatives but as it's so fast to create the Fastpix "object" each time I may as well use that.

    Thanks for your reponse and thanks for Fastpix. I already use it in other areas of my current project, such a "zooming" an image to x4 magnification and converting an RGB image to greyscale. It comes in very handy indeed!

  5. #5
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,125

    Re: Fast transfer byte data to picturebox?

    Quote Originally Posted by paulg4ije View Post
    Thanks for the replies - sorry a bit late responding; just got in from a long day at a Christening.

    Thanks BB. Fastpix might well do what I want. I probably should spend more time working with it. Because I need to call the plotting routine quite frequently (at around 100mS intervals) I had tried to set it up with module-level scope so that I didn't have to keep creating the Fastpix reference each time, and I seemed to have trouble doing that. That is the main reason I started looking for alternatives but as it's so fast to create the Fastpix "object" each time I may as well use that.

    Thanks for your reponse and thanks for Fastpix. I already use it in other areas of my current project, such a "zooming" an image to x4 magnification and converting an RGB image to greyscale. It comes in very handy indeed!
    Hi Paulg4ije, it sounds like what you need is a Pinned Array. That is an array which can contain the pixel data of a bitmap. I first heard about it in the CodeBank and you can get a fuller technical explanation here. When I was making FastPix, I considered Pinned Arrays as an alternative to Lockbits but at the time I found them consistently slower to instantiate (maybe there was a measuring error). But the great advantage of a Pinned Array compared to Lockbits is that you only have to create it once for a bitmap of a given size. Then you can update it with new pixel data as often as you like.

    Here's a VB.Net function for making a Pinned Array from a bitmap:

    Code:
    Imports System.Runtime.InteropServices 'Above class declaration
    '........
    	Public Function CreatePinnedArray(ByRef bmp As Bitmap) As Integer()
    		Dim pixelData(bmp.Width * bmp.Height - 1) As Integer
    		GCHandle.Alloc(pixelData, GCHandleType.Pinned)
    		bmp = New Bitmap(bmp.Width, bmp.Height, bmp.Width * 4, _
    		   Imaging.PixelFormat.Format32bppArgb, _
    		   Marshal.UnsafeAddrOfPinnedArrayElement(pixelData, 0))
    		Using g As Graphics = Graphics.FromImage(bmp)
    			g.DrawImageUnscaled(bmp, New Rectangle(0, 0, bmp.Width, bmp.Height))
    		End Using
    		Return pixelData
    	End Function
    Here's an example of using a Pinned Array and measuring its performance as in my previous post:
    Code:
    	Private _pinnedArray As Integer()
    	Dim _bmp As Bitmap
    
    	Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
    		Dim width As Integer = testArray.GetUpperBound(0) + 1
    		Dim height As Integer = testArray.GetUpperBound(1) + 1
    
    		'fill the test array with random values
    		For i As Integer = 0 To width - 1
    			For j As Integer = 0 To height - 1
    				testArray(i, j) = CByte(rnd.Next(256))
    			Next
    		Next
    
    		Dim sw As Stopwatch = Stopwatch.StartNew
    
    		'create the pinned array
    		If _pinnedArray Is Nothing Then
    			_bmp = New Bitmap(width, height)
    			_pinnedArray = CreatePinnedArray(_bmp)
    		End If
    
    		'update the pixels in the bitmap
    		For w As Integer = 0 To width - 1
    			For h As Integer = 0 To height - 1
    				Dim gray As Byte = testArray(w, h)
    				Dim color As Integer = &HFF000000 + gray * &H10101
    				_pinnedArray(h * width + w) = color
    			Next
    		Next
    		PictureBox1.Image = _bmp
    
    		MessageBox.Show((sw.ElapsedTicks * 1000 / Stopwatch.Frequency).ToString("0.00"))
    
    	End Sub
    As you can see, the Pinned Array is created only the first time you press the button. With a bitmap of 512 * 4 pixels, I am getting a time of 0.8 milliseconds to create the Pinned Array - so now even that is faster than instantiating FastPix/LockBits! The time to update the bitmap after that is considerably faster -- about 2x as fast as the FastPix method at around 0.05 milliseconds each time. The differences between the two methods even out as the bitmaps get larger.

    Since the array is protected from Garbage Collection, I think you would have to take care to set it to Nothing when it is no longer needed.

    BB

  6. #6

    Thread Starter
    Fanatic Member
    Join Date
    Aug 2004
    Location
    Essex, UK
    Posts
    653

    Re: Fast transfer byte data to picturebox?

    Thanks BB. The pinned arrary sounds very interesting. I should have made one thing clearer. I actually have a picturebox/bitmap that is 512 x 512 pixels and I need to add blocks of 512 x 4 to it at intervals of around 100mS. Obviously I need to specify the location (within in the main picturebox) of the new block that is added each time, although it will always be four lines down from the last block. I'm not sure if that changes your recommendations for a fast pixel plotter.

    I really appreciate your help with this.

  7. #7
    Fanatic Member
    Join Date
    Jul 2009
    Posts
    629

    Re: Fast transfer byte data to picturebox?

    With SetPixel I meant the SetPixel API.

    What it does is create a Device Context out of a bitmap, on which it performs SetPixel a lot quicker. Not sure what they did to the standard SetPixel function of the Bitmap, but it is indeed terribly slow. Perhaps they created and destroyed the DC every time you call the function, not sure.

    And yes, creating a new bitmap in memory IS slow. I did it once as some sort of buffer for a drawing program. Eventually it was faster to draw everything in one go than to draw the stored buffer. (buffer was also drawn over).

    I can remember testing this; generating a 800x600 bitmap in memory took me 5 ms.

    If anyone would like to test the API vs. the lockbits function, feel free. I haven't tested it completely. Here my custom DC class: (API.vb)
    Code:
        Public Class DC
            Implements System.IDisposable
    
            Private Declare Function BitBlt Lib "gdi32" (ByVal hdc As IntPtr, ByVal nXDest As Integer, ByVal nYDest As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hdcSrc As IntPtr, ByVal nXSrc As Integer, ByVal nYSrc As Integer, ByVal dwRop As CopyPixelOperation) As Boolean
            Private Declare Function GetPixel Lib "gdi32" Alias "GetPixel" (ByVal hdc As IntPtr, ByVal X As Int32, ByVal Y As Int32) As Int32
            Private Declare Function SetPixel Lib "gdi32" Alias "SetPixel" (ByVal hdc As IntPtr, ByVal X As Int32, ByVal Y As Int32, ByVal crColor As UInt32) As UInt32
            Private Declare Function ReleaseDC Lib "user32" (ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As Boolean
            Private Declare Function GetClipBox Lib "gdi32" (ByVal hdc As IntPtr, ByRef lprc As WRECT) As Integer
            Private Declare Function GetClipRgn Lib "gdi32" (ByVal hdc As IntPtr, ByRef hrgn As Region) As Integer
            Private Declare Function GetRandomRgn Lib "gdi32" (ByVal hdc As IntPtr, ByRef hrgn As Region, ByVal inum As Integer) As Integer
    
            Private managedtype As DCSource
            Private managedobject As Object
            Private managedgraphics As Graphics
            Private hdc As IntPtr
            Private _disposed As Boolean
            Public Enum DCSource
                None
                Handle
                Graphics
            End Enum
            Sub New(ByVal g As Graphics)
                Me.managedobject = g
                Me.managedtype = DCSource.Graphics
                Me.hdc = g.GetHdc
            End Sub
            Sub New(ByVal hwnd As IntPtr, ByVal hdc As IntPtr)
                Me.managedobject = hwnd
                Me.managedtype = DCSource.Handle
                Me.hdc = hdc
            End Sub
            Sub New()
                Me.managedobject = Nothing
                Me.managedtype = DCSource.None
            End Sub
            Public ReadOnly Property Source() As DCSource
                Get
                    Return Me.managedtype
                End Get
            End Property
            Public Sub SetPixel(ByVal X As Integer, ByVal Y As Integer, ByVal C As Color)
                SetPixel(Me.hdc, X, Y, C.ToArgb)
            End Sub
            Public Sub SetPixel(ByVal p As Point, ByVal C As Color)
                SetPixel(p.X, p.Y, C)
            End Sub
            Public Function GetPixel(ByVal X As Integer, ByVal Y As Integer) As Color
                Return ColorTranslator.FromOle(GetPixel(Me.hdc, X, Y))
            End Function
            Public Function GetPixel(ByVal p As Point) As Color
                Return GetPixel(p.X, p.Y)
            End Function
            Public Property Pixel(ByVal X As Integer, ByVal Y As Integer) As Color
                Get
                    Return GetPixel(X, Y)
                End Get
                Set(ByVal value As Color)
                    SetPixel(X, Y, value)
                End Set
            End Property
    
            Public ReadOnly Property ClipBox() As Rectangle
                Get
                    Dim w As WRECT
                    GetClipBox(Me.hdc, w)
                    Return w.Value
                End Get
            End Property
            Public ReadOnly Property ClipSize() As Size
                Get
                    Return Me.ClipBox.Size
                End Get
            End Property
    
            Public Property Handle() As IntPtr
                Get
                    Return Me.hdc
                End Get
                Set(ByVal value As IntPtr)
                    Me.hdc = value
                End Set
            End Property
    
            Public ReadOnly Property Disposed() As Boolean
                Get
                    Return Me._disposed
                End Get
            End Property
    
            Public Sub Draw(ByVal source As Graphics, ByVal destrect As Rectangle, ByVal sourceoffset As Point, ByVal operation As CopyPixelOperation)
                Dim hdc As IntPtr = source.GetHdc
                Draw(hdc, destrect, sourceoffset, operation)
                source.ReleaseHdc(hdc)
            End Sub
            Public Sub Draw(ByVal source As DC, ByVal destrect As Rectangle, ByVal sourceoffset As Point, ByVal operation As CopyPixelOperation)
                Draw(source.hdc, destrect, sourceoffset, operation)
            End Sub
            Public Sub Draw(ByVal sourcehdc As IntPtr, ByVal destrect As Rectangle, ByVal sourceoffset As Point, ByVal operation As CopyPixelOperation)
                BitBlt(Me.hdc, destrect.X, destrect.Y, destrect.Width, destrect.Height, sourcehdc, sourceoffset.X, sourceoffset.Y, operation)
            End Sub
    
            Public Sub CopyTo(ByVal destination As Graphics, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
                CopyTo(destination, Me.ClipBox, operation)
            End Sub
            Public Sub CopyTo(ByVal destination As Graphics, ByVal destrect As Rectangle, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
                CopyTo(destination, destrect, New Point(0, 0), operation)
            End Sub
            Public Sub CopyTo(ByVal destination As Graphics, ByVal destrect As Rectangle, ByVal sourceoffset As Point, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
                Dim hdc As IntPtr = destination.GetHdc
                CopyTo(hdc, destrect, sourceoffset, operation)
                destination.ReleaseHdc(hdc)
            End Sub
            Public Sub CopyTo(ByVal destination As DC, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
                CopyTo(destination.hdc, Me.ClipBox, New Point(0, 0), operation)
            End Sub
            Public Sub CopyTo(ByVal destination As DC, ByVal destrect As Rectangle, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
                CopyTo(destination.hdc, destrect, New Point(0, 0), operation)
            End Sub
            Public Sub CopyTo(ByVal destination As DC, ByVal destrect As Rectangle, ByVal sourceoffset As Point, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
                CopyTo(destination.hdc, destrect, sourceoffset, operation)
            End Sub
            Public Sub CopyTo(ByVal desthdc As IntPtr, ByVal destrect As Rectangle, ByVal sourceoffset As Point, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt)
                BitBlt(desthdc, destrect.X, destrect.Y, destrect.Width, destrect.Height, Me.hdc, sourceoffset.X, sourceoffset.Y, operation)
            End Sub
    
            Public Function ToBitmap(Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt) As Bitmap
                Return ToBitmap(Me.ClipSize, operation)
            End Function
            Public Function ToBitmap(ByVal Resolution As Size, Optional ByVal operation As CopyPixelOperation = CopyPixelOperation.SourceCopy Or CopyPixelOperation.CaptureBlt) As Bitmap
                ToBitmap = New Bitmap(Resolution.Width, Resolution.Height)
                Dim g As Graphics = Graphics.FromImage(ToBitmap)
                Dim ghdc As IntPtr = g.GetHdc
                BitBlt(ghdc, 0, 0, Resolution.Width, Resolution.Height, Me.hdc, 0, 0, operation)
                g.ReleaseHdc(ghdc)
                g.Dispose()
            End Function
    
            Public ReadOnly Property Graphics() As Graphics
                Get
                    If Me.managedgraphics Is Nothing Then Me.managedgraphics = Graphics.FromHdc(Me.hdc)
                    Return Me.managedgraphics
                End Get
            End Property
            Public Shared Function FromHandle(ByVal handle As IntPtr) As DC
                FromHandle = New DC()
                FromHandle.hdc = handle
            End Function
    
            Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean)
                If Me._disposed = False Then
                    Select Case managedtype
                        Case DCSource.Handle
                            ReleaseDC(CType(Me.managedobject, IntPtr), Me.hdc)
                        Case DCSource.Graphics
                            CType(Me.managedobject, Graphics).ReleaseHdc(Me.hdc)
                    End Select
                    If managedgraphics IsNot Nothing Then managedgraphics.Dispose()
                    Me.managedgraphics = Nothing
                    Me.managedobject = Nothing
                    Me.managedtype = Nothing
                    Me.hdc = Nothing
                    Me._disposed = True
                End If
            End Sub
            Public Overloads Sub Dispose() Implements IDisposable.Dispose
                Dispose(True)
                GC.SuppressFinalize(Me)
            End Sub
        End Class
    You can construct it from a graphics object or a window (handle)
    To construct it from a bitmap, first create a new graphics object for this bitmap and then perform the operation on the graphics object.

    Code:
    Dim b As Bitmap = Image.FromFile("test.png")
    Dim g As Graphics = Graphics.FromImage(b)
    Dim dc As New DC(g)
    For x As Integer = 0 To b.Width - 1
        For y As Integer = 0 To b.Height - 1
            Dim c As Color = dc.Pixel(x, y)
            c = Color.FromArgb(255 - c.R, 255 - c.G, 255 - c.B)
            dc.Pixel(x, y) = c
        Next
    Next
    dc.Dispose()
    g.Dispose()
    EDIT

    WRECT is the standard Windows rectangle API structure:
    Code:
        Public Structure WRECT
            Public Left, Top, Right, Bottom As Integer
            Sub New(ByVal r As Rectangle)
                Me.Value = r
            End Sub
            Public Property Value() As Rectangle
                Get
                    Return New Rectangle(Left, Top, Right - Left, Bottom - Top)
                End Get
                Set(ByVal value As Rectangle)
                    Me.Left = value.Left
                    Me.Right = value.Right
                    Me.Top = value.Top
                    Me.Bottom = value.Bottom
                End Set
            End Property
        End Structure
    EDIT2

    Ignore me please, it was slow after all. Not sure why it was so fast during my previous test.
    I guess handling the raw bitmap data using (un)lockbits is a lot faster, but only if you want to perform lots of pixel operations on it.

  8. #8
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,125

    Re: Fast transfer byte data to picturebox?

    Quote Originally Posted by bergerkiller View Post
    With SetPixel I meant the SetPixel API.
    What it does is create a Device Context out of a bitmap, on which it performs SetPixel a lot quicker. Not sure what they did to the standard SetPixel function of the Bitmap, but it is indeed terribly slow. Perhaps they created and destroyed the DC every time you call the function, not sure.

    And yes, creating a new bitmap in memory IS slow. I did it once as some sort of buffer for a drawing program. Eventually it was faster to draw everything in one go than to draw the stored buffer. (buffer was also drawn over).

    I can remember testing this; generating a 800x600 bitmap in memory took me 5 ms.
    It doesn't surprise me that the API Setpixel is faster. But the place to look for the difference in performance compared to Lockbits might be explained not by one or the other method, but by the processing loop. Resolving references to methods or properties outside the class or even outside the sub is costly -- much more so than arithmetic and logic operations. That's why I always declare a local copy of the FastPix.PixelArray. A simple rule is to avoid "anything with a dot in it" in the inner loop. Your references to the DC.Pixel property, to Color.FromArgb and to c.R etc. are probably the culprits. Compare that to the inner loop in Post #3: not a dot in sight.

    About the time to make a bitmap. I think we need to be clear about what we are timing. If I time the creation of an empty 800 * 600 bitmap like this:
    Code:
    		Dim sw As Stopwatch = Stopwatch.StartNew
    		Dim bmp As New Bitmap(800, 600)
    		MessageBox.Show((sw.ElapsedTicks * 1000 / Stopwatch.Frequency).ToString("0.00"))
    I get a time of about 1.2 milliseconds. But if I also force updating of the pixels on the screen by inserting this in the timing loop:
    Code:
    	PictureBox1.Image = bmp
    	PictureBox1.Invalidate(New Rectangle(0, 0, _W, _H))
    	PictureBox1.Update()
    the time rises to over 8 milliseconds. Maybe that's equivalent to what you were timing but on a slower machine. The time is proprtional to the number of pixels drawn, so filling a full-screen picturebox (1680*1000 approx) on my machine takes over 30 milliseconds. That's where accelerated graphics techniques such as DC.BitBlt (or DirectX, XNA, WPF etc.) are useful.

    BB

  9. #9

    Thread Starter
    Fanatic Member
    Join Date
    Aug 2004
    Location
    Essex, UK
    Posts
    653

    Re: Fast transfer byte data to picturebox?

    Many thanks for all the input guys. I think I have enough to work with now.

    Paul.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width