Friday, November 21, 2008
Aspect correct resizing

You ever have one of those functions that you've created a hundred times in the past and every time you think you'll need it on a new project you say to yourself "I've done that a hundred times, no problem" then when you get around to typing it you sit there and scratch you head and say 'Now ... how do I do that again?" This is ours.

Use this function to resize an image to a specific width or height and keep the aspect ratio correct.

To cut down the "dots", the module or class or form holding the function needs the typical Drawing/Imaging namespaces...

Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Drawing.Drawing2D

 

And here's the routine ...

Public Function AspectResizeImage(ByVal sourceImage As Image, _
        ByVal maxSize As Integer, _
        Optional ByVal ByWidth As Boolean = True) _
        As Image

        Dim ratio As Single = 1
        Dim newWidth, newHeight As Integer
        Dim bmpSource As Bitmap
        Dim bmpDest As Bitmap
        Dim gfx As Graphics
  
        Try
            'curb against out of memory issues
            'by just keeping things in a realistic range
            If maxSize < 1 Or maxSize > 5000 Then
                Throw New ArgumentOutOfRangeException("MaxSize", _
                    MaxSize, _
                    "Maximum size must be between 1 and 5000 pixels")
            End If

            bmpSource = New Bitmap(sourceImage)
            ratio = CSng(bmpSource.Width / bmpSource.Height)
            
            If ByWidth Then
                newWidth = maxSize
                newHeight = CInt(maxSize / ratio)

            Else 'by height
                newWidth = CInt(maxSize * ratio)
                newHeight = maxSize

            End If

            'make bitmap for result
            bmpDest = New Bitmap(newWidth, newHeight)

            'create Graphics object for final bitmap
            gfx = Graphics.FromImage(bmpDest)
            gfx.InterpolationMode = InterpolationMode.HighQualityBicubic

            'blast the source image into the dest
            gfx.DrawImage(bmpSource, 0, 0, bmpDest.Width, bmpDest.Height)

            Return DirectCast(bmpDest.Clone, Image)

        Catch ex As OutOfMemoryException
            Throw ex
        Catch ex As ArgumentOutOfRangeException
            Throw ex
        Catch ex As Exception
            'ToDo: remove after complete testing
            Throw ex
        Finally

            If Not (sourceImage Is Nothing) Then
                sourceImage.Dispose() ' get rid of the old one if it exists.
            End If

            If Not (bmpSource Is Nothing) Then
                bmpSource.Dispose() ' get rid of the old one if it exists.
            End If

            If Not (bmpDest Is Nothing) Then
                bmpDest.Dispose() ' get rid of the old one if it exists.
            End If

            If Not gfx Is Nothing Then
                gfx.Dispose()
            End If

        End Try

    End Function

Just pass in the original image and give a MAX value and specify whether that value is for the width or the height (Width is obviosuly the default) and out pops the aspect correct copy.

Generally it's just figuring the relationship between the original width and height then applying that to the correct dimension of the DrawImage resizing.

We use InterpolationMode.HighQualityBicubic because the rule of thumb is that it's the highest quality of all stock mode options, but it's also the most processor intensive. We've never seen it being a big deal on most systems but if you hit a performance drag, try dropping it down to HighQualityLinear. (Some people think Linear is crisper, pick your favorite or even add an InterpolationMode argument and let your users choose)

In our experience it's best to limit the MAX to a realistic range to avoid memory problems, hence the ArgumentOutOfRangeException. Make sure you set traps for exceptions. To reduce overhead, it'd be a good idea to test it out hard under your real needs to discover all of the exact exceptions that your situation could throw and add them to the Catch list then take out the generic Catch-All.

To see it in action, add a picturebox to a form, name it pbDemo. Add a textbox named txtAmount with a default value of 1, and a checkbox named chkUseWidth. Now add three buttons named butGetFile, butReset and butResize. Stick the following code in the button_click events:

Private Sub butGetFile_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butGetFile.Click
    
   Dim f As New OpenFileDialog

   Try
      With f
         .Filter = "Image Files|*.bmp;*.gif;*.jpg;*.png"
      
         If .ShowDialog = DialogResult.OK Then
            pbDemo.Image = Image.FromFile(.FileName)
       
            'stick a distinct image object into the tag as a backup
            pbDemo.Tag = Image.FromFile(.FileName)

            InitAmountDisplay()

         End If

      End With

   Catch ex as Exception
      Msgbox(Ex.Tostring)
   
   Finally
      If Not f Is Nothing Then
         f.Dispose()
         f = Nothing
      End If

   End Try

End sub

Private Sub butReset_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butReset.Click
  
   If Not pbDemo.Tag is Nothing Then 
   
      Dim img As Image = DirectCast(pbDemo.Tag, Image)
      img = DirectCast(img.Clone, Image)
      pbDemo.Image = img

      InitAmountDisplay()
  
   End If

End sub

Private Sub butResize_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles butResize.Click

   If Not pbDemo.Tag is Nothing Then 

      Dim img As Image = DirectCast(pbDemo.Tag, Image)
      img = DirectCast(img.Clone, Image)

      If IsNumeric(txtAmount.Text) Then
         Try
            pbDemo.Image = AspectResizeImage(img, _
              Integer.Parse(txtAmount.Text), _
              chkUseWidth.Checked)
         
         Catch ex As Exception
            MsgBox(ex.ToString)
         End Try
   
      End If
 
   End If  
   
End Sub

'oh, and you'll want this too...
Private Sub InitAmountDisplay()

   With pbDemo.Image

      If chkUseWidth.Checked Then
          'width
           txtAmount.Text = .Width.ToString
      Else
           txtAmount.Text = .Height.ToString
      End If

   End With

End Sub

For this example, we stick a distinct copy of the image into the picturebox tag so you can keep playing with the code over and over based on the original. If you want to base tests on previous settings to see how long it takes to degrade the image just cut the tag support and in/out using picturebox's image object.

There, that's going to save us re-inventing that stupid wheel for the hundred and first time. And if you get use out of it, so much the better. :)

Robert Smith
Kirkland, WA

added to smithvoice january 2005


Print  

pagecomment
  Add Comment



Submit Comment
  View Ratings
50.00%0
40.00%0
30.00%0
20.00%0
10.00%0

Number of Comments 0 , Average of Ratings
  View Comments
No comment.


Privacy Statement  |  Terms Of Use
Copyright 2008 by Robert C. Smith