Friday, March 6, 2009

Save an image in WebBrowser into a file

Save an image in WebBrowser into a file.

First of all, I am forced to share here because very hard to solve my problem and to find the solution. But the problem is simple, How to "Save an image or picture in WebBrowser into a file". It is hard for me because GDI+ is not my expertise.

About a month I searching this answer, googling around until I give up and try again, give up and try again a few time. What the most I frustrating of is after I found the solution, it is very simple. Hm...OK, at least I found it.

I am using WebBrowser to browse a site but it is not visible by user. I just want to show a picture in the page to user as a part of process like register. I don't want to use HttpWebRequest to get the image because in some cases, there is an image that may change randomly like advertisement. Also, it is out of a part of the process that need that image. In other words, I need the image from the WebBrowser not from URL itself.

To grab the image from WebBrowser IHTMLElementRender is used. It implement DrawToDC() that expose the WebBrowser to draw image to the other context. Now the image is ready to draw. It make me smile but after that I facing two big trouble.

Most of site I found, get the image from WebBrowser and show it to the PictureBox by using CreateGraphic.

Dim g As Graphics = Me.PictureBox1.CreateGraphics
Dim hdc As IntPtr = g.GetHdc
render.DrawToDC(hdc)
g.ReleaseHdc(hdc)
g.Dispose()

The first trouble is this will just a temporary image only. I can't save because DrawToDC() is just draw the image to the graphic that get from PictureBox.CreateGraphic. The image if on top of the PictureBox, not stored in the PictureBox.Image. The image can be missing if PictureBox refresh itself.

The second trouble, I wan to make my PictureBox and/or y Form hidden. After a few sample I found, I try to use New Bitmap to store the image. Then use BitBlt function to copy Graphic from PictureBox to the new bitmap. Now it is ok. I can save the image by using Bitmap.Save method. However when I try to hide or invisible the PictureBox, the saved image is an empty image. I take so many time here to trying the thing that I don't like to. The GDI+ and all its messy functions.

After a month I found this site. It tell how to capture overall web page image from IE to a file. I try to follow the algorithm on how to store the image without using CreateGraphic, BitBlt and so forth. Now I did it =)

Here is the final code:
Reference to Microsoft.mshtml needed. elem is the image element (img tag).

Private Sub StoreImage(ByVal elem As HtmlElement, ByVal FilePath As String)

If elem IsNot Nothing Then
Dim oelem As IHTMLElement = CType(elem.DomElement, IHTMLElement)
If oelem IsNot Nothing Then
Dim render As IHTMLElementRender = CType(oelem, IHTMLElementRender)
If render IsNot Nothing Then

Dim Temp As Bitmap = Nothing
Dim gTemp As Graphics = Nothing
Dim hdcTemp As IntPtr

Try
Temp = New Bitmap(elem.OffsetRectangle.Width, elem.OffsetRectangle.Height)
gTemp = Graphics.FromImage(Temp)
hdcTemp = gTemp.GetHdc()

render.DrawToDC(hdcTemp)

gTemp.ReleaseHdc(hdcTemp)
gTemp.Flush() ' please flush before save it
gTemp.Dispose()
gTemp = Nothing

Temp.Save(FilePath)

Catch ex As Exception
Dim a = ex.ToString()
Finally
If gTemp IsNot Nothing Then
gTemp.ReleaseHdc(hdcTemp)
gTemp.Dispose()
gTemp = Nothing
End If
If Temp IsNot Nothing Then
Temp.Dispose()
Temp = Nothing
End If
End Try

End If
End If
End If

End Sub

See the code inside the Try. Its very simple.

Please note that I have found this code before, create new image, create graphic from it, create the hdc from the graphic and call DrawToDC. However, because I want to proper dispose it, I use Try Finally block that handle it properly. This will make the image saved as a blank image. That is because I save it immediately after DrawToDC. I can't do that since the graphic does not update the bitmap. Then don't forget to call Flush().

Yeah, Finally Flush() is what I missing. Now I should take a rest.

2 comments:

  1. The line "render.DrawToDC(hdcTemp)"
    results in an error:
    "Value of type 'System.IntPtr' cannot
    be converted to 'MSHTML._RemotableHandle'."

    Any advice how to fix this?


    PS: I am using VB 2008 Express

    ReplyDelete