Sunday, September 19, 2010

Automatically Resize Images in .Net WebBrowser Control

A few years back before multi-tabbed browsers I wrote a WinForms app in VB.Net which contained a tabbed interface with each tab containing a custom control that used a WebBrowser control and looked just like a real, well, web browser! (What else did you expect!?)

Anyway, one thing that I could never work out was how to automatically resize downloaded images to fit within the WebBrowser control if they were bigger - something IE does on the fly but that the WebBrowser control simply won't do without specific coding.

I'd tried downloading the image directly to an Image object, resizing it based on the WebBrowser width & height, saving it locally then navigating to the image (or creating custom HTML to show the image) but it never seemed to work very well, it was slow and it caused utter chaos with the backward and forward buttons on the browser control - to name but a few obstacles.

Well, recently I revisited the project as quite by accident I stumbled upon a way I'd never thought of - to use the GetAttribute and SetAttribute methods of the WebBrowser.Document object. (It seems so darned obvious now - grrrrr!)

So I thought since I'd seen lots of people (including me) asking about this on various forums I'd share some very simple code and post an equally simple VB.Net app illustrating this to my web site for downloading.

You can get the app here. (PS: It's a VS2008 project as I don't use VS2005 any more... sorry!). The app is exceedingly simple so even a .Net newbie should be able to grasp what's happening fairly quickly.

For those that don't want to download the app here's an explanation and some example code of how I got automatic image resizing to work. I'm going to assume here you know enough about the WebBrowser control to understand how to get one working in a .Net WinForms app... if not you can use Visual Studio help or look it up here.

So, given an example URL of an image that is 1400 wide x 1050 high and a WebBrowser control which is, say, 800 wide by 600 high we will see below how to get the image resized automatically so that it fits in to the browser control whilst retaining it's correct width/height ratio.

(BTW, sorry about the code formatting - haven't worked out how to do this properly yet in Blogger as I'm still very new to the world of blogging!)

First we simply navigate to the image file by calling the Navigate method:

WebBrowser1.Navigate("http://www.41south.com/blog/test1400x1050.jpg")

Before the image (or web page) has been retrieved the Navigating event fires and all we do here is simply work out whether we're downloading an image and set a global string variable to either "IMAGE" or "PAGE". (Yes, I could have used a Boolean but just trying to make it really obvious!)

Private Sub WebBrowser1_Navigating(_
ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.WebBrowserNavigatingEventArgs) _
Handles WebBrowser1.Navigating

'Check if we're loading an image
If e.Url.ToString.ToLower.EndsWith(".jpg") OrElse _
e.Url.ToString.ToLower.EndsWith(".png") OrElse _
e.Url.ToString.ToLower.EndsWith(".bmp") OrElse _
e.Url.ToString.ToLower.EndsWith(".gif") Then

strURLType = "IMAGE"

Else

strURLType = "PAGE"

End If

End Sub

Once the image has been downloaded, the DocumentCompleted event fires and here's where the fun starts. If our flag set above is "IMAGE" then we retrieve the image from the WebBrowser control by getting the first image from the Images collection in the Document's HTML. (There should only ever be one - remember, we're loading an individual image here.) Once that's done we check if the image width and height is larger than the dimensions of the WebBrowser control (I subtract 10 from it's width and height as I like to have a 5 pixel margin around the image) and if they are then I call the CalculateDisplaySize function to work out the ideal size for the image so it fits into the control but retains it's correct width/height ratio. I also hide the scroll bars and add the aforementioned 5 pixel margin - both of which can be removed or altered according to your needs. Finally, if the image was re-sized I add a custom alt tag to it to show it's current and original size.

In the app I also have a CheckBox (called chkResizeImage) which indicates whether to do the resizing or not - unchecking this just bypasses the resizing even if the image is larger than the WebBrowser control.

There are lots of other fancy things you can do with the WebBrowser control by setting the attributes but that's a topic for another blog entry!

So without further ado, here's the rest of the code - hope this helps a few people solve this problem. Feel free to ask any questions or to suggest improvements!

Mike


Private Sub WebBrowser1_DocumentCompleted(_
ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs) _
Handles WebBrowser1.DocumentCompleted

If strURLType = "IMAGE" Then

Dim browserWidth As Integer = WebBrowser1.Width - 10
Dim browserHeight As Integer = WebBrowser1.Height - 10
Dim width, height As Integer
Dim newWidth, newHeight As Integer
Dim ImageSize() As Integer

'Get the first (and only) image in the Document's Image collection.
Dim el As HtmlElement = WebBrowser1.Document.Images(0)

'Save the width and height of the existing image.
width = CInt(el.GetAttribute("WIDTH"))
height = CInt(el.GetAttribute("HEIGHT"))

'Calculate the new image size (if the downloaded image
'width or height > the browser control's width/height).
If (height > browserHeight OrElse width > browserWidth) _
And chkResizeImage.Checked Then

'Now calculate our new imagesize and set the image attributes.
ImageSize = CalculateDisplaySize(browserWidth, browserHeight, width, height)
newWidth = ImageSize(0)
newHeight = ImageSize(1)

'We have our image so set <BODY> attributes to hide scrollbars and
'set a 5 pixel margin around the image.
WebBrowser1.Document.Body.SetAttribute("SCROLL", "NO")
WebBrowser1.Document.Body.SetAttribute("LEFTMARGIN", "5")
WebBrowser1.Document.Body.SetAttribute("RIGHTMARGIN", "5")
WebBrowser1.Document.Body.SetAttribute("TOPMARGIN", "5")
WebBrowser1.Document.Body.SetAttribute("BOTTOMMARGIN", "5")

'Set the new width and height of the image and set an alt tag
'showing the old and new sizes.
el.SetAttribute("WIDTH", newWidth)
el.SetAttribute("HEIGHT", newHeight)
el.SetAttribute("ALT", String.Format("Original size {0} x {1}, resized to {2} x {3}", width, height, newWidth, newHeight))

End If

End If
End Sub

Private Function CalculateDisplaySize(ByVal browserWidth As Integer, _
ByVal browserHeight As Integer, _
ByVal w As Integer, _
ByVal h As Integer) As Integer()

'Default our return parameters to the image size.
Dim ImageSize() As Integer = {w, h}

Dim nh As Integer = h
Dim nw As Integer = w
Dim r As Double = 0

Try
If w >= h Then
'Width >= height so first re-size image based on image wdith...
nw = browserWidth
r = CDbl(h / w)
nh = CInt(Math.Floor(CDbl(browserWidth * r)))

'If the height of the image is still larger than the browser height
'then re-calculate image size based on the browser height.
If nh > browserHeight Then
nh = browserHeight
nw = CInt(Math.Floor(CDbl(browserHeight / r)))
End If
Else
'Height > width so first re-size image based on image width...
nh = browserHeight
r = CDbl(w / h)
nw = CInt(Math.Floor(CDbl(browserHeight * r)))

'If the width of the image is still larger than the browser width
'then re-calculate image size based on the browser width.
If nw > browserWidth Then
nw = browserWidth
nh = CInt(Math.Floor(CDbl(browserWidth / r)))
End If
End If

'Save the new height & width as the resized dimensions.
ImageSize(0) = nw
ImageSize(1) = nh

Catch ex As Exception
MsgBox("Error in CalculateDisplaySize: " & ex.ToString)
End Try

Return ImageSize

End Function

No comments:

Post a Comment