Y'herd thisun? 

“Nothing is impossible on earth; one must only discover the means with which it can be carried out.”

from Ways of Spaceflight, 1928 (NASA F-622) by Hermann Oberth

Your own image control and App part 27

TaggedCoding, VB, Imaging

Originally published December 2002 on using VB7.0/2002. Updated for VB7.1 February 2005

Links for compiled demo versions, all required resources and source code are included at the end of this article.

Plus, get the complete eBook in Adobe Acrobat 7 format ... all here.


27) Passing save options between forms

We'll need to fill in the max, min and default values for the IntegerTextboxes with values specific to the images. Those values are available in the svDisplayImageDetail objects so we could add a Friend property to frmSaveJPEGOptions but a safer technique is to force the dev-user to pass the object in the ShowDialog method. Overriding isn't good enough because we don't want to give them a choice, so Shadowing is the way to go; as you remember, Shadowing intercepts calls to base interface members so the base method isn't used.

Before we create the new ShowDialog, we'll make a new class to pass the image details to the dialog and return the settings to the main form. Right click on the project node of the solution explorer and Add a new Class, name it "ImageSaveInfo.vb". The class will need to be aware of the svImageEditor types so the Smithvoice namespace should be imported and beyond that you just need a variable for the svDisplayImageDetail type and the userdefined final width/height and JPEG quality percentage.


Imports Smithvoice
Public Class ImageSaveInfo
#Region "Declarations"
Private m_Size As Size = New Size(0, 0)
Private m_JPEGQuality As Integer = 75
'hold the source image information
Private m_ImageDetail As svDisplayImageDetail
#End Region
#Region "Public methods"
Public Sub New(ByVal imgDetail As svDisplayImageDetail)
m_ImageDetail = imgDetail
End Sub
#End Region
#Region "Public Properties"
Public Property Size() As Size
Return m_Size
End Get
Set(ByVal Value As Size)
m_Size = Value
End Set
End Property
Public Property JPEGQuality() As Integer
Return m_JPEGQuality
End Get
Set(ByVal Value As Integer)
m_JPEGQuality = Value
End Set
End Property
Public ReadOnly Property SourceDetails() As svDisplayImageDetail
Return m_ImageDetail
End Get
End Property
#End Region
End Class


Even though the svImageEditor does conveniently provide an aspect correct resize method that takes a dimension and max value, this class uses a Size object for the user defined width and height. This will force the dev-user to work out the actual dimensions when the end user chooses that option but it keeps the class simpler than adding three booleans (for UsingAspectCorrect, UsingPercentage and MaxDimensionByWidth) and If...Else-ing through all of the possible combinations back in the main form.

Switch to codeview of frmSaveJPEGOPtions, add a module level variable for the ImageSaveInfo object and shadow the base ShowDialog function to accept that object:


Private m_SaveInfo As ImageSaveInfo
Public Shadows Function ShowDialog(ByVal saveInfo As ImageSaveInfo) _
As System.Windows.Forms.DialogResult
Me.DialogResult = DialogResult.None
m_SaveInfo = saveInfo
m_SaveInfo.Size = _
New Size(m_SaveInfo.SourceDetails.Width, m_SaveInfo.SourceDetails.Height)
Return Me.DialogResult
End Function

We want the form to be modal, waiting to return the final user defined values so after setting the variable we call the base's ShowDialog(), and before that we do some safety code making user that the m_SaveInfo Size property is initialized to the source image's size (in case the dev accidentally played with the Size property before passing the object) and se call on SetupDisplay to set the form's texts and IntegerTextboxes correctly:


Private Sub SetupDisplay()
rbSaveCurrent.Text = "Save current size " & _
m_SaveInfo.SourceDetails.Width.ToString & " width, " & _
m_SaveInfo.SourceDetails.Height.ToString & " height"
rbSaveSpecific.Text = "Exact size " & _
"(max: " & Format(m_SaveInfo.SourceDetails.MaxWidth, "#,###") & " width, " & _
Format(m_SaveInfo.SourceDetails.MaxHeight, "#,###") & " height)"
With txtSpecificWidth
.MinValue = 1
.MaxValue = m_SaveInfo.SourceDetails.MaxWidth
.DefaultValue = m_SaveInfo.SourceDetails.Width
.Text = m_SaveInfo.SourceDetails.Width.ToString
End With
With txtSpecificHeight
.MinValue = 1
.MaxValue = m_SaveInfo.SourceDetails.MaxHeight
.DefaultValue = m_SaveInfo.SourceDetails.Height
.Text = m_SaveInfo.SourceDetails.Height.ToString
End With
rbSaveAspectPercent.Text = "Aspect correct percent " & _
"(min: 1, max: " & Format(CInt(m_SaveInfo.SourceDetails.MaxZoom), "#,###") & "%)"
With txtAspectPercent
.MinValue = 1
.MaxValue = m_SaveInfo.SourceDetails.MaxZoom
.DefaultValue = 100
.Text = "100"
End With
rbSaveAspectExact.Text = "Aspect correct size " & _
"(max: " & Format(m_SaveInfo.SourceDetails.MaxWidth, "#,###") & " width, " & _
Format(m_SaveInfo.SourceDetails.MaxHeight, "#,###") & " height)"
If chkAspectExactWidth.Checked Then
txtAspectExact.MaxValue = m_SaveInfo.SourceDetails.MaxWidth
txtAspectExact.Text = m_SaveInfo.SourceDetails.Width.ToString
txtAspectExact.MaxValue = m_SaveInfo.SourceDetails.MaxHeight
txtAspectExact.Text = m_SaveInfo.SourceDetails.Height.ToString
End If
lblJpegQuality.Text = "jpeg quality: " & trackQuality.Value.ToString
End Sub

As each radiobutton is clicked it's related controls should be enabled, simple enough with a traditional switchboard routine (if each options detail controls were in a groupbox or panel then just the container would need to be enabled/disabled, cutting down the lines. Doing that is up to you.):


Private Sub rbSaveCurrent_CheckedChanged(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles rbSaveCurrent.CheckedChanged, rbSaveSpecific.CheckedChanged, _
rbSaveAspectExact.CheckedChanged, rbSaveAspectPercent.CheckedChanged
If CType(sender, RadioButton).Checked Then
If sender Is rbSaveCurrent Then
txtSpecificWidth.Enabled = False
txtSpecificHeight.Enabled = False
txtAspectExact.Enabled = False
chkAspectExactWidth.Enabled = False
txtAspectPercent.Enabled = False
ElseIf sender Is rbSaveSpecific Then
txtSpecificWidth.Enabled = True
txtSpecificHeight.Enabled = True
txtAspectExact.Enabled = False
chkAspectExactWidth.Enabled = False
txtAspectPercent.Enabled = False
ElseIf sender Is rbSaveAspectExact Then
txtSpecificWidth.Enabled = False
txtSpecificHeight.Enabled = False
txtAspectExact.Enabled = True
chkAspectExactWidth.Enabled = True
txtAspectPercent.Enabled = False
ElseIf sender Is rbSaveAspectPercent Then
txtSpecificWidth.Enabled = False
txtSpecificHeight.Enabled = False
txtAspectExact.Enabled = False
chkAspectExactWidth.Enabled = False
txtAspectPercent.Enabled = True
End If
End If
End Sub

A couple of quickies take care of the JPEG quality label and updating the display for aspect correct being switched between width and height:


Private Sub trackQuality_Scroll(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles trackQuality.Scroll
lblJpegQuality.Text = "jpeg quality: " & trackQuality.Value.ToString
End Sub
Private Sub chkAspectExactWidth_CheckedChanged(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles chkAspectExactWidth.CheckedChanged
'safety net
If m_SaveInfo Is Nothing Then Exit Sub
If chkAspectExactWidth.Checked Then
txtAspectExact.MaxValue = m_SaveInfo.SourceDetails.MaxWidth
txtAspectExact.MaxValue = m_SaveInfo.SourceDetails.MaxHeight
End If
End Sub

The real grunt work of the dialog happens when the user presses the Saved button. At that point the Size values have to be calculated and put into the m_SaveInfo ImageSaveInfo object. Here's the code for both the buttons and the private method:


Private Sub butSave_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles butSave.Click
End Sub
Private Sub UpdateSaveInfo()
Dim newWidth As Integer
Dim newHeight As Integer
If rbSaveCurrent.Checked Then
m_SaveInfo.Size = New Size(m_SaveInfo.SourceDetails.Width, _
ElseIf rbSaveSpecific.Checked Then
m_SaveInfo.Size = _
New Size(CInt(txtSpecificWidth.Text), CInt(txtSpecificHeight.Text))
ElseIf rbSaveAspectExact.Checked Then
If chkAspectExactWidth.Checked Then
'calc new height from width
newWidth = CInt(txtAspectExact.Text)
newHeight = _
 CInt(m_SaveInfo.SourceDetails.Height * (newWidth / m_SaveInfo.SourceDetails.Width))
m_SaveInfo.Size = New Size(newWidth, newHeight)
'calc new width from height
newHeight = CInt(txtAspectExact.Text)
newWidth = _
 CInt(m_SaveInfo.SourceDetails.Width * (newHeight / m_SaveInfo.SourceDetails.Height))
m_SaveInfo.Size = New Size(newWidth, newHeight)
End If
ElseIf rbSaveAspectPercent.Checked Then
newWidth = CInt(txtAspectPercent.Text)
newHeight = CInt(txtAspectPercent.Text)
newWidth = CInt(m_SaveInfo.SourceDetails.Width * (newWidth / 100))
newHeight = CInt(m_SaveInfo.SourceDetails.Height * (newHeight / 100))
m_SaveInfo.Size = New Size(newWidth, newHeight)
End If
m_SaveInfo.JPEGQuality = trackQuality.Value
End Sub


For easier maintenance, arrange the form routines into regions before moving on.

Rebuild and save, then switch to codeview on the main form. We'll be saving from both the soruce and result editors and the save functions don't make sense as form methods so create a new region named "Save Methods" and add a sub to show the frmSaveJPEGOptions as a dialog:


#Region "Save methods"
Private Sub ShowSaveDialog(ByVal editor As svImageEditor)
Dim f As New frmSaveJPEGOptions
Dim saveInfo As New ImageSaveInfo(editor.CurrentImageDetail)
f.Owner = Me
f.StartPosition = FormStartPosition.CenterParent
If f.ShowDialog(saveInfo) = DialogResult.OK Then
MsgBox("Save size set to: " & saveInfo.Size.ToString & vbCrLf & _
 "Jpeg quality set to: " & saveInfo.JPEGQuality.ToString)
End If
f = Nothing
End Sub

To test your dialog, add calls to the new sub in the toolbar buttonclick events, using the correct editor in the method's argument:


... 'Source:
Case "butSave"
Case "butSave"

Rebuild and save. F5 and load some images to verify that your dialog is showing the correct size limits, that the textboxes are restricting you from exceeding the limits and that the values are being returned to the main form correctly.

Back in the Save Methods region, add the code to do the work. Though we'll only be saving to JPEGs right now, keep the door open for other file types by using the standard helper function GetEncoderInfo to get back the encoder object from a passed mimetype. (GetEncoderInfo is used just about every time you want to do a file save. Since it's so common I'm not sure why MS didn't just make a baked-in method to save us from the silly loop, but they didn't so get used to typing it and seeing it in so many places that its name has become a defacto standard for the process.)


Private Sub SaveImage(ByVal editor As svImageEditor, _
ByVal saveInfo As ImageSaveInfo, ByVal fileName As String)
Dim imgTemp As Image
Dim codecInfo As ImageCodecInfo = GetEncoderInfo("image/jpeg")
Dim enc As Encoder = Encoder.Quality
Dim encParam As EncoderParameter = _
New EncoderParameter(enc, saveInfo.JPEGQuality)
Dim encParams As EncoderParameters = New EncoderParameters(1)
encParams.Param(0) = encParam
If editor.CurrentDisplayImage.Width = saveInfo.Size.Width And _
editor.CurrentDisplayImage.Height = saveInfo.Size.Height Then
'no resize
editor.CurrentDisplayImage.Save(fileName, codecInfo, encParams)
imgTemp = svImageResult.ResizedDisplayImageSpecific(saveInfo.Size.Width, _
saveInfo.Size.Height, svInterpolationMode.HighQualityBicubic)
imgTemp.Save(fileName, codecInfo, encParams)
End If
Catch ex As Exception
'Todo: remove after testing
If Not imgTemp Is Nothing Then
imgTemp = Nothing
End If
encParams = Nothing
encParam = Nothing
enc = Nothing
codecInfo = Nothing
End Try
End Sub
Private Function GetEncoderInfo(ByVal mimeType As String) As ImageCodecInfo
Dim codecs As ImageCodecInfo() = ImageCodecInfo.GetImageEncoders()
For Each codec As ImageCodecInfo In codecs
 If codec.MimeType = mimeType Then
 Return codec
 End If
End Function

Back in ShowSaveDialog, replace the msgbox with a call to the new routine:


If f.ShowDialog(saveInfo) = DialogResult.OK Then
Dim fs As New SaveFileDialog
With fs
.Filter = "JPEG|*.jpg"
.FilterIndex = 0
If .ShowDialog = DialogResult.OK Then
SaveImage(editor, saveInfo, .FileName)
End If
End With
End If

Now if the dialog returns an OK from a press of the Save button the user is prompted for a file location and if that dialog returns OK then the image is saved. Hopefully.

Rebuild, Save. Test out your new app. If you get no errors, try to go right up to the limits of resizing. XCOPY your exe and dll to another machine or two and try the same values again. After saving a large image, try to load it into the app.

I can almost bet that at least once you hit an OutofMemoryException and if you didn't, trust me, its just waiting to hit your users. What's more, there's not much of anything you can do about it.

Next: Dealing with reality: The last exception

Robert Smith
Kirkland, WA

added to smithvoice march 2005

jump to:

  • 1) The spec
  • 2) Setting up the workspace
  • 3) Feature 1: Loading an image
  • 4) Custom Exceptions
  • 5) "Fax images" and Multipage TIFFs
  • 6) Custom events
  • 7) Selecting specific fax pages
  • 8) Feature 2: Rotating image displays
  • 9) The most useful tool in GDI+: DrawImage
  • 10) Feature 3: Zooming
  • 11) Handling the unhandleable exception
  • 12) Fixing the squish
  • 13) Zooming to fit the control
  • 14) You're already beating the Pros
  • 15) Feature 4: Cropping
  • 16) Bonus Feature: StickyMouse
  • 17a) Final Cleanup
  • 17b) Passing the current display image
  • 18) Making the application
  • 19) Source and result viewports
  • 20) A better toolbar
  • 21) Hooking the toolbar to the project
  • 22) Adding ImageEditors
  • 23) The toolbar ZoomCombo
  • 24) The final solution
  • 25) Saving to image files
  • 26) An integer-only textbox
  • 27) Passing save options between forms
  • 28) Dealing with that last exception
  • 29) Offer more options with menus
  • 30 The downloads and ebook

  • home     who is smith    contact smith     rss feed π
    Since 1997 a place for my stuff, and it if helps you too then all the better