Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Converting a bitmap generated by a webcam to a jpg 1

Status
Not open for further replies.

tedsmith

Programmer
Nov 23, 2000
1,762
AU
I am trying to find ways to improve converting a bitmap generated by a webcam to a jpg so it can be sent faster via a network to another computer.

My problem is -

I tried GDIplus as per an old strongm examples with, instead of saving the jpg to file, I used:-
GdipCreateFromHDC Picture2.hDC, myGraphics
GdipDrawImage myGraphics, gdiImageTarget, 0, 0

This does quickly paint a jpg picture over a picturebox but this is wiped out by any object moving over it and it's .picture = 0 so there is nothing to send! It seems to be drawing only to the screen, not the picturebox.

Is there a way of using GDIplus to produce a data stream or fill a picturebox.picture like a loadpicture statement does so it can be sent over a network?

Currently I use AviCap32 feeding the webcam to the clipboard.
I then send the BMP in the clipboard over the network in a propertybag without any problem.

Using GDIplus I am able to capture the picture and convert it to the jpg picture'painted'box but I cant send this picture to the network.

GDIplus often freezes the app when you close it needing removal by task manager processes.

I haven't been able to get WIA to work on Win7 so I cant use this either unless someone has an answer to this separate problem!
 
>GDIplus often freezes

Well, sort of. I'm guessing that it is more likely that the version of my code that you are using doesn't contain all the GDIplus clean-up code (even more likely if you modified my code without realising how important it is to clean up GDIplus objects) - thus, while it may well be GDIplus causing the freeze, the root cause is most likely a programming error. The GDIplus examples I've produced here are normally not of production code quality, they have simply been to illustrate techniques

>This does quickly paint a jpg picture

No, it doesn't.

It paints a bitmap over the device context

Windows knows nothing about jpegs (as we've previously tried to tell you - all graphics are internally represented as bitmaps), apart from how to load and immediately convert them into bitmaps (and, with the right library, how to convert a bitmap to a jpeg for saving)

And GDI+ knows nothing (and cares nothing) about VB controls such a picture boxes. The example code (presumably from my post thread222-1335999 8 Mar 07 2:40, although it may have been from my earlier example that only showed how to save a jpeg) simply uses the VB picture box as the source of a valid Device Context.

>Is there a way of using GDIplus to produce a data stream
Yes, and I showed you exactly how to do that in the referenced example

>or fill a picturebox.picture like a loadpicture statement does so it can be sent over a network?

Well, yes (using the GDI Plus library's OleLoadPicture function which, unlike the API version that we've illustrated a number of times in this forum, can load direct from a stream) - but you need to understand that what ends up in the picture box is a bitmap, not a jpeg - so you'll have wasted a bunch of time doing the conversion to jpeg only to convert it straight back to a bitmap (and with a consequrent loss of quality compared to the origibnal bitmap) for transmission
 
Thanks for your quick reply
The problem back in 2007 was the questions I asked all degenerated into huge discussions as to methods of transmission and speed and whether I really needed this facility or not. Not wishing to stir up more trouble I gave up really trying to understand it all !
With current computers conversion and display speed is now not an issue but slow networks still are.
So the problem has resurfaced.

I used the code you refer to without alteration and still get the same crash on exit or when I stop in the IDE and restart. Disposeof statements are still there but maybe there should be more?

However I wrongly assumed that a "jpg" could be stored in the second picture box even though I read through all your previous explanations. Now I think about it I should have known better!

As GDI in your example has created a JPG stream, how does one retrieve this stream and feed it to a propertybag and hence a winsock?

This is my current transmission code that has proved error free in sending a large picture
TxPb.WriteProperty "WebCam", Clipboard.Getdata
Winsock1.SendData TxPb.Contents
Then at the receiving end (plus a 2 byte finish sendcomplete code to ensure a complete pic)
set Image1.picture=RxPb.ReadProperty ("WebCam",0)

If I try to write MyStream to the propertybag the error says it "doesn't support persistence"
It does this even when I convert a simple BMP graphic

So now how do I feed the GDI stream to the propertybag writeproperty ?

No doubt the answer is simple but I can't see it.
 
You need to get away from thinking that a propertybag is the only way of doing this. remember, the property bag is mostly useful for (peristable) ActiveX objects that VB understands (since it allows us to take some shortcuts).

And the way we use the propertybag is to serialise the ActiveX object so that we can send a buffer representing the object (propertybag.Contents) over winsock, which we then convert back in to propertybag at the other end, and from which we then extract the ActiveX object

So, with a stream we can extract the buffer representing that stream, send that buffer via winsock, then recreate the stream at the other end. Same process really.

And dilettante illustrated the basic technique for sending a stream (an ADODB stream rather than an IStream, but the principal is the same) in the same linked thread previously referenced. There now follows an illustrative example that works with an IStream (necessary for this to handle JPEGs). Note that you'll need the references mentioned in the previous thread to olelib and GDI Plus (and I've slightly improved GetEncoderCLSID routine, which was the actual cause of the instability you reported with the original examples).

You'll need two forms, the first with a picturebox and a command button, the second with a picturebox only. Both will require a winsock control. The example takes a bitmap displayed on the first form, converts it to a JPEG, sends the JPEG as a stream over winsock to the second form, where it is displayed:

Code:
[blue]
'Requires GDI Plus type library 1.3 and Edanmos OLE 1.8
Option Explicit

Public CannotClose As Boolean

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenW" (lpString As Any) As Long
'Private Declare Function IsBadStringPtrByLong Lib "kernel32" Alias "IsBadStringPtrW" (ByVal lpsz As Long, ByVal ucchMax As Long) As Long
 
Private Sub Command1_Click()
     Dim GpInput As GdiplusStartupInput
     Dim Token As Long
     Dim myCLSID As CLSID
     Dim myEncoderParameters As EncoderParameters
     Dim myEncoder(0) As EncoderParameter
     Dim gdiImage As Long
     Dim gdiImageTarget As Long
     Dim Quality As Long
     Dim myGraphics As Long

     Dim myStream As olelib.IStream ' Use olelib's IStream interface
     Dim b() As Byte 'Source of an HGlobal
     
     
     ReDim b(0) As Byte
     ' Get and check we have a CLSID for a jpeg codec
     If GetEncoderCLSID("image/jpeg", myCLSID) Then
         GpInput.GdiplusVersion = 1
         If GdiplusStartup(Token, GpInput) <> Ok Then 'Ok comes from gpStatus enumeration
             MsgBox "Error loading GDI+!", vbCritical
         Else
             myEncoderParameters.Count = 1
             myEncoder(0).Guid = GdiPlus.CLSIDFromString(EncoderQuality)
             myEncoder(0).Type = EncoderParameterValueTypeLong
             myEncoder(0).NumberOfValues = 1
             Quality = 25 ' 0 to 100%
             myEncoder(0).ValuePtr = VarPtr(Quality)
             myEncoderParameters.Parameter = myEncoder(0)
             GdipCreateBitmapFromHBITMAP Picture1.Picture.Handle, Picture1.Picture.hPal, gdiImage
             Set myStream = CreateStreamOnHGlobal(b(0), True)
             
             ' OK save image as a (possibly) compressed JPG into a stream
             GdipSaveImageToStream gdiImage, myStream, myCLSID, VarPtr(myEncoderParameters) ' one change from my previous example, to save to a stream instead of a file
             
          
             SendStream myStream ' send the stream via winsock
                                           
             ' OK we can dispose of stream
             Set myStream = Nothing
            GdipDisposeImage gdiImage

         End If
     End If
     If Token Then GdiplusShutdown Token
 End Sub
 
 ' helper function to get CLSID of output codec
 Private Function GetEncoderCLSID(ByVal strMimeType As String, outCLSID As CLSID) As Long
     Dim GpInput As GdiplusStartupInput
     Dim Token As Long
     Dim lEncoders As Long
     Dim lEncodersBuffer As Long
     Dim lp As Long
     Dim arrEncoders() As ImageCodecInfo
     GpInput.GdiplusVersion = 1
     If GdiplusStartup(Token, GpInput) <> Ok Then
         MsgBox "Error loading GDI+!", vbCritical
     Else
         GdipGetImageEncodersSize lEncoders, lEncodersBuffer
         ReDim arrEncoders(lEncoders * 3 - 1) As ImageCodecInfo 'make sure we have enough space

         GdipGetImageEncoders lEncoders, lEncodersBuffer, arrEncoders(0)
         GetEncoderCLSID = False
         For lp = LBound(arrEncoders) To UBound(arrEncoders)
             If StringFromPointer(arrEncoders(lp).MimeTypePtr) = strMimeType Then
                 outCLSID = arrEncoders(lp).CLSID
                 GetEncoderCLSID = True
                 Exit For
             End If
         Next
     End If
     If Token Then GdiplusShutdown Token
End Function

 ' Works for strings that are nullchar terminated
 Public Function StringFromPointer(lpString As Long) As String
    'Dim sRet As String
    'Dim lret As Long
    Dim lMaxLength As Long
    'Dim lCodec
    
    If lpString = 0 Then
        StringFromPointer = ""
    Else
        lMaxLength = lstrlen(ByVal lpString)
        StringFromPointer = Space$(lMaxLength)
        CopyMemory ByVal StrPtr(StringFromPointer), ByVal lpString, lMaxLength& * 2
    End If
    
End Function

Private Sub SendStream(myStream As olelib.IStream)
    Dim fred As olelib.STATSTG
    Dim buffer() As Byte
    
    myStream.Stat fred, STATFLAG_NONAME
    
    
    
    ReDim buffer(fred.cbSize * 10000 - 1) As Byte
    
    myStream.Seek 0, STREAM_SEEK_SET
    myStream.Read buffer(0), fred.cbSize * 10000 ' stick stream into buffer
    Winsock1.SendData CLng(fred.cbSize * 10000) ' Send actual size of stream first ...
    Winsock1.SendData buffer ' ... and then send buffer
    
End Sub

Private Sub Form_Load()
    'Form1.Show
    Form2.Show
    CannotClose = True 'ensures we cannot close form2 by mistake if form1 is still open
    
    ' Non-robust technique
    Winsock1.Connect
    Do Until Winsock1.State = sckConnected
       DoEvents
    Loop
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
    CannotClose = False
    Unload Form2
    Winsock1.Close
End Sub

Private Sub Winsock1_Close()
    Winsock1.Close
End Sub
[/blue]

Code:
[blue]
Option Explicit

Private myStream As olelib.IStream

Private Sub Form_Load()
    Winsock1.Listen
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
     If Form1.CannotClose Then Cancel = True
End Sub



Private Sub Winsock1_ConnectionRequest(ByVal requestID As Long)
    
    If Winsock1.State <> sckClosed Then Winsock1.Close
    Winsock1.Accept requestID
    ResetStream

End Sub

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)

    Dim StreamStats As olelib.STATSTG
    
    Dim gdiImageTarget As Long
    Dim GpInput As GdiplusStartupInput
    Dim Token As Long
    Dim myGraphics As Long
    Dim buffer() As Byte
    
    Static SentStreamSize As Long
    
    If SentStreamSize = 0 Then ' Have we got size of data yet?
        Winsock1.GetData SentStreamSize, vbLong
        'Debug.Print "Expecting: " & SentStreamSize & " bytes in stream"
    Else ' Ok, start accumulating data into the stream
        ReDim buffer(0) As Byte ' reset input buffer
        Winsock1.GetData buffer, vbArray Or vbByte ' read the byte array
        myStream.Seek 0, STREAM_SEEK_END
        myStream.Write buffer(0), bytesTotal ' append to a stream
        myStream.Stat StreamStats, STATFLAG_NONAME
        If SentStreamSize <= StreamStats.cbSize * 10000 Then 'we've accumulated all the data
            'Debug.Print "StreamStats thinks: " & StreamStats.cbSize * 10000
            GpInput.GdiplusVersion = 1
            If GdiplusStartup(Token, GpInput) <> Ok Then
                MsgBox "Error loading GDI+!", vbCritical
            Else
                GdipLoadImageFromStream myStream, gdiImageTarget 'load GDI+ image from stream (which in this case contains a JPEG)
                
                ' At this stage we have loaded a jpg into GDI+ from a stream
                ' so we can reset that stream for reuse
                ResetStream
                
                ' Now display, letting GDI+ do all the hard work
                GdipCreateFromHDC Picture1.hDC, myGraphics
                GdipDrawImage myGraphics, gdiImageTarget, 0, 0
        
                GdipDisposeImage gdiImageTarget
                GdipDeleteGraphics myGraphics
             End If
             SentStreamSize = 0
        End If
    End If
    
    If Token Then GdiplusShutdown Token

End Sub

Private Sub ResetStream()
    Dim b(0) As Byte ' declaration to get an HGlobal
    Set myStream = Nothing
    Set myStream = CreateStreamOnHGlobal(b(0), True)
End Sub
[/blue]

 
Thank you for your detailed reply and the time you must have taken to do it.
It works beautifully between 2 computers however it does not answer my original question and I still can't achieve what I want.

The method of transmission is not the issue.

The received "jpg picture" using this method, is no different to what the transmitter GDI BMP to JPG conversion produces before it is sent to the winsock so that I still can't handle the received picture in any way.

I don't want to show the JPG in a picturebox at the receiving end and if I make a dummy invisible picturebox, I can't draw to it or extract it's picture for another purpose or send it on later to another computer as a jpg. As you pointed out it would be a bitmap again anyway.
At the receiver, I have to show 16 clients pictures on the one screen in reduced imageboxes and magnify the one that is clicked on for examination.

If I could just convert the GDI stream to a bunch of bits that an imagebox.picture will understand before or after sending them, then my problem would be solved.

Eg in Dataarrival instead of "letting GDI+ do all the hard work", something like perhaps:-
dim MyPicture as Picture
set MyPicture = GDISomeOtherProcess MyGraphics,gdiImageTarget
Image1.Pictue=MyPicture (locally - not a picturebox)
or
Image1.Picture=MagicalConversionProcess MyStream.read(?,?)

Is there a more detailed understandable description on the internet of the functions in GDI than Vb gives when you press f2? I couldn't find one.

 
>The method of transmission is not the issue.

Well then you shouldn't have said

>I cant send this picture to the network

or

>Is there a way of using GDIplus to produce a data stream ... so it can be sent over a network?

(I've ignored the 'or fill a picturebox.picture like a loadpicture statement does' because what you'd end up with there is a bitmap being converted to a jpg, then being converted to a bitmap before being sent, which is completely pointless and a waste of cycles)


Here, however, is an alternative version of the code for form 2, which includes a function that will turn a GDI+ image into a StdPicture (i.e a Picture) object:
Code:
[blue]
Option Explicit

Private mystream As olelib.IStream

Private Sub Form_Load()
    Winsock1.Listen
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
     If Form1.CannotClose Then Cancel = True
End Sub



Private Sub Winsock1_ConnectionRequest(ByVal requestID As Long)
    
    If Winsock1.State <> sckClosed Then Winsock1.Close
    Winsock1.Accept requestID
    ResetStream

End Sub

Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)

    Dim StreamStats As olelib.STATSTG
    
    Dim gdiImageTarget As Long
    Dim GpInput As GdiplusStartupInput
    Dim Token As Long
    Dim myGraphics As Long
    Dim buffer() As Byte
    
    Static SentStreamSize As Long
    
    If SentStreamSize = 0 Then ' Have we got size of data yet?
        Winsock1.GetData SentStreamSize, vbLong
        'Debug.Print "Expecting: " & SentStreamSize & " bytes in stream"
    Else ' Ok, start accumulating data into the stream
        ReDim buffer(0) As Byte ' reset input buffer
        Winsock1.GetData buffer, vbArray Or vbByte ' read the byte array
        mystream.Seek 0, STREAM_SEEK_END
        mystream.Write buffer(0), bytesTotal ' append to a stream
        mystream.Stat StreamStats, STATFLAG_NONAME
        If SentStreamSize <= StreamStats.cbSize * 10000 Then 'we've accumulated all the data
            'Debug.Print "StreamStats thinks: " & StreamStats.cbSize * 10000
            GpInput.GdiplusVersion = 1
            If GdiplusStartup(Token, GpInput) <> Ok Then
                MsgBox "Error loading GDI+!", vbCritical
            Else
                GdipLoadImageFromStream mystream, gdiImageTarget 'load GDI+ image from stream (which in this case contains a JPEG)
                
                
                ' At this stage we have loaded a jpg into GDI+ from a stream
                ' so we can reset that stream for reuse
                ResetStream
                
                ' Now display, letting GDI+ do all the hard work
                
                'GdipCreateFromHDC Picture1.hDC, myGraphics
                'GdipDrawImage myGraphics, gdiImageTarget, 0, 0
                
                ' New version, working with a StdPicture object created from the GDI+ image
                Set Picture1.Picture = CreatePictureFromGDIPlusImage(gdiImageTarget)
        
                GdipDisposeImage gdiImageTarget
                GdipDeleteGraphics myGraphics
             End If
             SentStreamSize = 0
        End If
    End If
    
    If Token Then GdiplusShutdown Token

End Sub

Private Sub ResetStream()
    Dim b(0) As Byte ' declaration to get an HGlobal
    Set mystream = Nothing
    Set mystream = CreateStreamOnHGlobal(b(0), True)
End Sub


Private Function CreatePictureFromGDIPlusImage(gdiImage As Long) As StdPicture
    Dim lpPictDesc As olelib.PICTDESC
    Dim myUUID As olelib.UUID
    Dim hBitmap As Long
    Dim background As Long
    
    olelib.CLSIDFromString IPictureGUID, myUUID
    GdipCreateHBITMAPFromBitmap gdiImage, hBitmap, background
    
    With lpPictDesc
        .cbSizeofstruct = Len(lpPictDesc)
        .hBitmap = hBitmap
        .hpal_or_xExt = 0&
        .PICTYPE = PICTYPE_BITMAP
    End With

   Set CreatePictureFromGDIPlusImage = olelib.OleCreatePictureIndirect(lpPictDesc, myUUID, False)
    
End Function[/blue]
 
Sorry, the bit about the network was only to give people an overview of what I was attempting.
If I had been able to "send this picture to the network" in the same format as a picture from a picturebox then I wouldn't have had the current problem.

I tried your latest example but it doesn't recognise CreatePictureFromGDIPlusImage (sub or function not found)

I can't see anything like it in GDIPlus or OLE in the object browser either.

References I am using:-
Edanmo's OLE interfaces & functions v1.81 olelib.tlb 1/2/2004
GDI+ Type Library 1.21(dseaman@ig.com.br) Gdiplus.tlb 11/9/2003
There are a number of other different versions of GDIPlus.dll in my computer with dates:-
6/2/2003, 7/2/2003, 4/08/2004, 27/02/2007, 13/8/2009, 14/4/2008
Are any of these the same as yours?
Most of them say "cant add a reference" when you try to use them.
 
>If I had been able to "send this picture to the network" in the same format as a picture from a picturebox then I wouldn't have had the current problem.

Quite so. But solving converting the GDI+ JPEG image into a picture object at the sending end, i.e into a bitmap, completely defeats the purpse of what you were trying to achieve, as you'd have:

1) Convert Picture (containing a bitmap) into JPEG
2) Convert JPEG back into a Picture (containing a bitmap)
3) Stick Picture into Propertybag
4) Send contents of Propertybag via winsock
5) Load transmitted contents into a new propertybag at destination
6) extract contents of propertybag as Picture
7) Convert Picture (containing a bitmap) into JPEG
8) Convert JPEG to back into a Picture (containing a bitmap)
9) Display Picture (containing bitmap)

Now at some point you'd presumably have figured out that since you were actually transmitting a Picture then steps 7 and 8 would be completeley superfluous. And then you'd realise that in that case steps 1 and 2 would not be necessary

And then you'd realise that transmitting a Picture completely defeats the purpose of what you are doing, so you need to figure out how to send a JPEG over Winsock - and THAT'S what I focussed on because that is what it looked like you were really asking (because simply providing you with code that would convert a JPEG back into a Picture object would NOT solve your problem)

>I tried your latest example but it doesn't recognise CreatePictureFromGDIPlusImage (sub or function not found)

>I can't see anything like it in GDIPlus or OLE in the object browser either

Er, its a function I wrote, and is the last few lines of the example. Are you SURE you are using my code?
 
At last success!
It converts and transmits a 1 meg BMP converted to JPG ten times faster then it takes to send the BMP between 2 computers on a 100mb network.

When I cut and pasted your latest code I missed the function at the bottom!

Regarding my "methods"
I never intended such a convoluted solution as you suggest. I was asking various questions about various stages of the exercise that may have given this impression in an attempt to get to the bottom of it.

What I meant from the very first when I said JPG picture was, was the bunch of bits that represents an encoded RGB picture in JPG format.
I admit this is not the same as a Microsoft definition of a "Picture" object but I assumed you knew what I meant. Sorry for my bad use of definitions.
To me a JPG is still a picture but I will refrain from using this in future.
Microsoft have hijacked the word 'picture' which to all normal people is something you can see or file away.

So the very initial request was how do I
1) Convert Picture (containing a bitmap) into JPEG code
3) Stick JPG code into Propertybag
4) Send contents of Propertybag via winsock
6) extract contents of propertybag to a variable
7) feed this variable containing the JPG Code to an Imagebox and save it to feed to other computers when called on later.

Step 7 was the last remaining hurdle but now it is solved thanks to strongm again!
I will look at using your code for the transmission instead of a propertybag if I can make this coexist with sending text as well.

Is there any understandable published material on how to use GDIplus functions? I can't find one on the internet.
 
>At last success!

Hurrah!

>I never intended such a convoluted solution as you suggest

I know. That method was essentially simply a thought experiment. I've lost the will to explain why I used it, though.

>What I meant from the very first when I said JPG picture was, was the bunch of bits that represents an encoded RGB picture in JPG format

I'm aware of that.

>any understandable published material on how to use GDIplus functions

Understandable from a VB perspective? Not really, not that I'm aware of. GDI+ was not really written with VB programmers in mind (indeed it is really only thanks to the efforts of Dana Seaman and his library that we get tou use large chunks of it at all). Most of the documentation is therefore written with C++ programmers in mind. And we know from past experience (remember all the stuff about BITMAP documentation) that you find it hard to get your mind around such documentation.
 
I don't want to drag your thread off in the weeds, but I tried playing around with a minimal approach and found a number of woes. Are you seeing the same issues?

1.) Cameras usually have to use the WDM-VFW bridge (adapter) driver now. This means if you try to enumerate the VFW devices (capGetDriverDescription) you find only Device 0, and if you try to use it (via WM_CAP_DRIVER_CONNECT) a selection dialog appears if two or more actual cameras are present. Also I've noticed that on Win7 (32- or 64-bit) the CONNECT fails at this point.

2.) 64-bit Windows (Vista, Win7) often results in a hard program crash when using the AVICap32 API.

BTW:

As far as WIA 2.0 goes... Yes it no longers supports video devices as of Vista (and Win7, Win8). This was related to some kind of DRM issue and not a technical issue. But it still works great for things like compressing BMP to JPEG, converting a JPEG image file contained in a Byte array to a StdPicture object, etc.
 
Sorry strongm I should have realised it but after testing, I found that your CreatePictureFromGDIPlusImage function create a Bitmat sized "picture" from the steam three times larger than the original bitmap.
I measured the size by putting into a propertybay and Ubound(Contents). I presume this is valid?

This is OK if this is at the receiving end but I was hoping to use at the transmitting end otherwise I have to rewrite the software in all the other receiving devices if I send the GDI stream.

I was hoping for a routine in the 'transmitter" that produced a reduced bunch of bits of similar same smaller size as a a saved jpg file that could be distributed and fed directly to an image like Loadpicture does (as per the steps in my last post ). This is always much smaller than the original and the imagebox does the bitmap picture reconstruction.

Is this possible?

dilettante
I have observed this even in XP when using 2 cameras
I have been using AVICap32 in Windows 7 64 with no crashes only capturing a picture every second (not continuous)
You can get a complete computer freeze for 30 seconds or so if you close the app or unplug the camera with the software still "connected" but not if you "disconnect" in software first. This freeze clears itself after about 30 seconds. Otherwise no problems.

Have you any suggestions on examples of using WIA to convert only?
 
Good to hear that Win7 64-bit is working ok for you. Perhaps the issues I ran into on 64-bit Windows (I know I tried Vista 64-bit at least if not Win7) were related to cameras and drivers.

I've been playing with an example I put together yesterday and have the nastiest bugs out of it now (I think).

This one is using WIA 2.0 to convert frames to JPEG and received JPEG "image file" streams to StdPicture objects for display. There are tweakable constants and they are set for 5 fps and 60% JPEG compression. Capturing is at 320x240 pixels, which could also be changed easily enough though I didn't create Consts for those (duh, next time for sure).

I've only done some LAN testing and I haven't tried to measure the bandwidth required, but it seems to work reasonably well. over a WAN it might be another story though.


I've posted this at another site, as AVICap32 Cam Streaming w/o Clipboard where you should be able to download it without being a member. There is a small server program and a small "multi-client" program that can connect to 4 server instances. Of course with the WDM/VFW driver issues right now every "server instance" must be on a separate machine even if you have multiple cameras.

Perhaps the code there will give you some ideas. If nothing else using WIA sure saves on lines of code to write and debug! However performance might be better using lower level APIs, perhaps even enough to be worth the effort.
 
>I was hoping for a routine in the 'transmitter" that produced a reduced bunch of bits of similar same smaller size as a a saved jpg file that could be distributed and fed directly to an image

I have on repeated occassions explained that you cannot do this at the transmitting end (yes, from a technical point of view you can do the conversion, but it is pointless). I'm not going to explain yet again why this is the case.

Let's use some traditional terms: transmitter= server, receiver = client

The code I've produced for you does exactly what you require:

It converts a bitmap into a jpeg on server
Server sends the jpeg representation to the client
Client loads the jpeg into a Picture object (which it has to do by converting it back into a bitmap)

I appreciate that you want to try and avoid rewriting any code on the clients, but that just isn't going to be possible, I'm afraid.

Let's try a simpler allegory. Let's say that you are just trying to send plain ASCII text. And you write the simplest method of sending and receiving that over winsock (doesn't matter what that method is, the point is it only knows how to deal with plain text). Now you discover that some of the blocks of text are too big, so you decide that you want to compress them for transmission, and you choose a simpe algorith such as RLE. That's great in that it reduces the size of the text being transmitted, but has the problem that the client cannot display the text in it's original form. You have to have a converter on the client to reconstitute the original text. There's no way around this.

Same thing with bitmaps - jpeg's are just a compression routine, and we need to decompress them on the client before they can be displayed

>like Loadpicture does

That's exactly what the CreatePictureFromGDIPlusImage function does. It takes a GDI+ image and returns a Picture object (LoadPicture takes a filename and returns a Picture object).
 
Thanks. I see your point more clearly now.

I am interested in what is the nature of the GDIplus stream in your example in layman's terms?

If, for illustration only, you open an existing jpg file for binary as #1 and feed it into a bytearray = input (Lof(1),#1) and transmit all these bytes in turn, is the GDI+ image in your example of similar nature to the received data or is it something entirely different again?
 
>the GDI+ image

The GDI+ image is it's own format (and I have no idea what it uses internally), but it includes a fair amount of additional state information above and beyond the raw image itself. However, when we save it to a stream it is just the same as saving it to a file. So the stream in this case should just contain the bytes representing the jpeg.
 
I have been watching this thread, and other related threads silently for sometime. Main reason being that I have practically no idea how GDI+ stuff works. Had no plans to intervene until strongm's last post rang a bell.

>when we save it to a stream it is just the same as saving it to a file. So the stream in this case should just contain the bytes representing the jpeg.

I just checked it. And yes, the bytes in the stream simply represent the raw contents of the jpeg file, as if it were read directly into the array. I wrote the bytes representing the jpeg to a file with .jpg extension, and resulting file behaved like a normal jpeg file. I could open it in an image viewer and do other stuff. That really helped.

Having established this, I believe we can make the code on the client side much simpler.

Currently, strongm is doing following steps to reconstruct the Picture object on client side.

1. Receive the byte array (buffer) using Winsock.
2. Create a ole steam object (mystream) and write the byte array, buffer into it.
3. Create a GDI+ image object (gdiImageTarget) from mystream using GdipLoadImageFromStream function.
4. Create a vb Picture object (StdPicture) from gdiImageTarget using CreatePictureFromGDIPlusImage function.

I remembered posting PictureFromRawBits function in thread222-1541026 which loads a picture, like the LoadPicture function, with only difference that it reads the data from memory, instead of a file.
So if we pass the byte array, buffer, from step 1 to this function, it will create the Picture object directly, without going into the trouble of all GDI+ stuff on the client side. Of course, GDI+ will be needed on server side to create the jpeg stream.
I just checked using PictureFromRawBits and it worked perfectly.

Using PictureFromRawBits on client side has another advantage. It supports all image file formats which are supported by LoadPicture function. These include BMP, JPG, GIF, icons/cursors and even metafiles. So, if some day, you decide to send the image, in a different format, say GIF, or BMP again, you only would need to modify your server code, and the client will display the new content seamlessly. On the other hand, if you use GDI+ decoder, you would need to modify your client code as well, depending on the type of image being sent.

PictureFromRawBits also retains the "originality" of the Picture object, in contrast to CreatePictureFromGDIPlusImage function. So if you put the resulting picture into a PropertyBag and measure its size using Ubound(Contents), it will return the same size as the original jpeg in compressed format.

Hope it helps.

Watch out for the CreateStreamOnHGlobal API function used in above referenced thread. It might clash with CreateStreamOnHGlobal function used in Edanmo's OLE library, if you have referenced it in your project. You might need to use an Alias for the API function to avoid name confusion.
 
>I believe we can make the code on the client side much simpler

I took the approach that you outline originally, but couldn't get it working properly for some reason (oddly seemed to claim the stream was an 'invalid picture', and I didn't have time to investigate), and so reverted to the method presented.

I may revisit
 
Ah! I was using OleLoadPicture from Edanmo's library - and it would appear to have a bug! If I use the straight API call (aliased) it works fine, so we can have a function like:

Code:
[blue]Private Declare Function vbOleLoadPicture Lib "olepro32" Alias "OleLoadPicture" (pStream As Any, ByVal lSize As Long, ByVal fRunmode As Long, riid As Any, ppvObj As Any) As Long

Private Function LoadPictureFromStream(jpgStream As IStream) As StdPicture
    Dim myUUID As olelib.UUID
    
    jpgStream.Seek 0, STREAM_SEEK_SET ' make sure we are at beginning of stream
    olelib.CLSIDFromString IPictureGUID, myUUID
    vbOleLoadPicture ByVal ObjPtr(myStream), 0&, 0&, myUUID, LoadPictureFromStream
End Function[/blue]


However, whilst revisiting, I recalled that StdPicture has a hidden iPersistStream interface - so we ought to be able to load it directly from the stream ... and indeed we can, giving us an alternative LoadPictureFromStream:

Code:
[blue]Private Function LoadPictureFromStream2(myStream As IStream) As StdPicture
    Dim oPersist As IPersistStream
    
    Set LoadPictureFromStream2 = New StdPicture
    myStream.Seek 0, STREAM_SEEK_SET
    Set oPersist = LoadPictureFromStream2 [green]' Get the hidden IPersistStream interface of the StdPicture object[/green]
    oPersist.Load myStream
End Function[/blue]
 
Great!
One quandary is what is be best way to transmit other types of data on the same port.
I need to reply that a picture has been received, and to send a text report back to the server.
I tried a rather cludgy method using SentStreamSize as an indicator of what was to follow with the following concept -
Client side:
'Acknowledge
Senddata clng(1)
Senddata "OK"

'Report
Senddata clng(2)
Senddata txtMessage

'Picture
as previous code that works

'Server side
Select case SentStreamSize
Case 1
'Acknowledge

case 2
'text received

case else
'picture received previous code that works

end select

Any better ideas?
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top