since 1997 a place for my stuff, and if it helps you then so much the better

 
...
...

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


...
...

"In theory, theory and practice are the same. In practice, they are not." -Albert Einstein