|
-
Jul 16th, 2011, 04:06 AM
#1
Thread Starter
Fanatic Member
[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.
-
Jul 16th, 2011, 05:27 AM
#2
Fanatic Member
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.
-
Jul 16th, 2011, 11:43 AM
#3
Re: Fast transfer byte data to picturebox?
 Originally Posted by bergerkiller
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.
Last edited by boops boops; Jul 16th, 2011 at 12:16 PM.
-
Jul 16th, 2011, 02:20 PM
#4
Thread Starter
Fanatic Member
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!
-
Jul 17th, 2011, 01:57 AM
#5
Re: Fast transfer byte data to picturebox?
 Originally Posted by paulg4ije
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
-
Jul 17th, 2011, 02:28 AM
#6
Thread Starter
Fanatic Member
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.
-
Jul 17th, 2011, 05:29 AM
#7
Fanatic Member
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.
Last edited by bergerkiller; Jul 17th, 2011 at 05:49 AM.
-
Jul 17th, 2011, 08:11 AM
#8
Re: Fast transfer byte data to picturebox?
 Originally Posted by bergerkiller
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
Last edited by boops boops; Jul 17th, 2011 at 08:15 AM.
-
Jul 18th, 2011, 05:11 AM
#9
Thread Starter
Fanatic Member
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|