Introduction
Image Batch Converter ("IBC" from here on out) provides a quick and easy way to resize, reformat, rename, add text and apply effects to a group of images. Originally it was nothing more than a quick resizer for images imported from my cell phone, but when I decided to submit it to Code Project I added the additional features. At present it only supports a few formats (JPG, PNG, TIF, BMP AND GIF) but I'm studying up on several 3rd party libraries to add more formats.
Using the Code
There are 3 windows used in the conversion process. The main window, a text-editing window, and a results window that's displayed after the conversion. There is also a help file that explains each step involved in converting images. The code is uncomplicated - just plain vanilla GDI+.
Here's the Load event. When IBC launches, it does the following:
- Sets the
SelectedIndex
property for the ComboBoxes to their value when the previous session closed (stored in My.Settings
)
- Disables the Convert button until all parameters are set
- Checks
My.Settings
to see if a destination directory was saved from the last session. IBC will fetch it if it exists.
- Updates the UI - mainly enabling/disabling controls
Private Sub main_Load(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles MyBase.Load
Dim invP As Char() = Path.GetInvalidPathChars()
Dim invC As Char() = Path.GetInvalidFileNameChars()
For Each ch As Char In invP
invChars.Add(ch)
Next
For Each ch As Char In invC
invChars.Add(ch)
Next
cmb_Format.SelectedIndex = My.Settings.selFmt
cmb_ROT.SelectedIndex = 0
chk_TXT.Checked = My.Settings.Txt_Enabled
tb_Convert.Enabled = False
If Directory.Exists(My.Settings.txt_DestDir) Then
txt_DestDir.Text = My.Settings.txt_DestDir
Else
txt_DestDir.Text = String.Empty
End If
UpdateUI()
End Sub
When IBC closes, it checks to be sure the current destination directory exists. If it does, it's saved to My.Settings
.
Private Sub main_FormClosing(ByVal sender As Object,
ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
If Directory.Exists(txt_DestDir.Text) Then
My.Settings.txt_DestDir = txt_DestDir.Text
End If
My.Settings.selFmt = cmb_Format.SelectedIndex
My.Settings.Txt_Enabled = chk_TXT.Checked
End Sub
The Conversion List
Adding images
To add images to the list, click the "+" sign in the tool bar. IBC doesn't create a collection of images. It just stores the path strings in the listView control. Selecting an image from the list displays a thumbnail in the picturebox on the right. Here's the code for the button's Click
event.
Private Sub tb_Add_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles tb_Add.Click
res = dialog_Open.ShowDialog
If res = Windows.Forms.DialogResult.OK Then
Try
Dim tmp() As String = dialog_Open.FileNames
For l = 0 To tmp.Length - 1
Dim tf As Boolean = False
For zz As Integer = 0 To lvFiles.Items.Count - 1
If lvFiles.Items.Item(zz).Text = tmp(l) Then
tf = True Exit For End If
Next
If tf = False Then
Dim lvi As New ListViewItem(Path.GetFullPath(tmp(l)))
lvFiles.Items.Add(lvi)
End If
Next
lvFiles.AutoResizeColumn(0, ColumnHeaderAutoResizeStyle.ColumnContent)
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Exclamation, title)
End Try
End If
Try
lvFiles.Items.Item(lvFiles.Items.Count - 1).Selected = True
Catch ex As Exception
End Try
UpdateUI()
End Sub
Removing images from the list
If you change your mind about converting a particular image or group of images, click the red "X" in the tool bar. This opens a drop-down menu for removal options. You can either clear the entire list ("Clear All"), or use the checkboxes in the listview along with the "Clear Selected" option.
Here's the code for clearing selected images from the list...
Private Sub tb_ClearSel_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles tb_ClearSel.Click
Try
If lvFiles.CheckedItems.Count > 0 Then For l = lvFiles.CheckedIndices.Count - 1 To 0 Step -1 lvFiles.Items.RemoveAt(lvFiles.CheckedIndices(l)) Next
End If
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Exclamation, title)
End Try
UpdateUI() End Sub
And to clear all images...
Private Sub tb_ClearAll_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles tb_ClearAll.Click
Try
lvFiles.Items.Clear()
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Exclamation, title)
End Try
UpdateUI()
End Sub
Destination Directory and Format
Once you've made your image selections, it's time to decide where the converted images will be written to disk, and what format if any they'll be converted to. This is done in the "Destination" tab.
Choosing a Destination Directory (required)
The first text box at the top contains the directory that will hold your converted images. Click the folder button right of the text box to open a Windows Folder dialog. The dialog's "New Folder" option is enabled so you can create a new directory if needed. The chosen directory will be displayed in the textbox. Here's the code for the folder button's Click
event.
Private Sub btn_DestDir_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles btn_DestDir.Click
Try
res = dialog_Folder.ShowDialog
If res = Windows.Forms.DialogResult.OK Then
myStr = dialog_Folder.SelectedPath
If Not myStr.EndsWith("\") Then
myStr &= "\"
End If
txt_DestDir.Text = myStr
End If
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Exclamation, title)
End Try
UpdateUI()
End Sub
Choosing a Base File Name (optional)
In the second text box, you can type a base file name that will be applied to all converted images with a counter. For example, if you type "MyConvertedImages" with .jpg as the selected format, your file names will be:
- "MyConvertedImages_1.jpg"
- "MyConvertedImages_2.jpg"
... and so on. To retain the original file names, just leave this text box empty. If ANY legal text is in this text box, it'll automatically use it for a base file name. It attempts to block characters that Windows doesn't recognize as legal path characters. I say "attempts" here because there is a caveat in that regard in the VB.net documentation regarding the method I used (Path.GetInvalidPathChars) to build the collection of characters to watch for. My guess is it's very close to 100% accuracy.
Overwrite Rules (required)
IBC provides three "Overwrite Rules" to protect existing files. They are:
- Overwrite Automatically
- IBC overwrites any file of the same name and format without input from the user.
- Ask Before Overwrite
- A Windows message box will pop up and ask if you're sure you wish to overwrite an existing file.
- Do Not Overwrite
- All existing files are skipped (not overwritten) with no input from the user.
Select the appropriate rule by clicking one of the RadioButtons in the "Overwrite Rules" Groupbox.
Selecting a Destination Format (optional)
If you wish to save all selected images to a single format (.jpg, .png, etc), first make sure the "Use Selected Format" checkbox is checked. This enables the format controls. In the Destination Format GroupBox you can choose which image format your converted images will be saved in. Select one from the drop down list. The default is .jpg. If .jpg is selected, the nearby track bar allows you to set the quality / compression of the images. If you leave the format checkbox unchecked, each image will be saved in its original format.
Resizing and Image Effects
Resizing (optional)
IBC provides three methods of resizing images, and the resize tools are located in a small tab control. For resizing to occur, you must check the "Resize Images" CheckBox just above the tab control. To select a resize method, click the appropriate tab. The resizing methods are:
- Single Dimension (1st tab)
- Allows user to enter a single numeric value and designate whether it represents width or height via RadioButtons. The other dimension is scaled automatically to maintain aspect ratio.
- Percentage (2nd tab)
- Provides a trackbar with a range of 10 to 300 percent for upsizing and downsizing images with aspect ratio maintained.
- Absolute Dimension (3rd tab)
- User enters both a width and height. All images are sized to these dimensions without regard to aspect ratio.
Image Transformations (optional)
IBC doesn't support complex transformations, but it does allow you to flip, rotate and convert images to grayscale. Just select the appropriate check boxes for vertical and/or horizontal flip, grayscale, and choose rotation degree from the drop-down list.
NOTE: If you add text to a grayscale image, the text color is retained. So, you can have a grayscale image with chatreuse text if you so desire. Also, text alignment is not affected by flip/rotate because it's drawn after everything else happens.
Using the Text Tool
This feature, located in the Resizing and Effects tab, allows you to add a single line of text to all converted images. You can choose any font, style and color your computer supports. Note that you won't be able to add text until all other options are either set or disabled. This is because IBC provides a post-conversion preview of all the images while the text tool is open, so you can see how the text will look on each image. This is also a good way to preview your images even if you don't intend to add any text.
First, be sure the "Use Text" CheckBox in the "Add Text" GroupBox is selected. If the "Edit Text" button just below it is enabled, you can open the text tool. Click the button to open the tool. The text tool is a Windows dialog. It'll size itself to fill your screen when it opens to make it easier to view larger images. You can resize it if you want to.
Explanation of the Tool Bar Buttons
From left to right, the tool bar buttons are:
- Save and Return to Main Window
- Saves the text and formatting and closes the text tool
- You MUST click the Save button for the text to appear in your images.
- If you make changes to existing text but click the Cancel button (below), you will lose your changes and the previous text, if there is any, will be drawn instead.
- Cancel and Return to Main Window
- Discards changes to text and closes the text tool
- Set Font
- Set the font (all the text is in one font and style)
- Set Text Color
- Sets the color. The text input area is always black on white. The color changes only in the preview.
- Text Alignment Menu
- Opens two drop-down menus (horizontal & vertical) to set how the text is aligned on the image.
- Horizontal options are: Left, Right, and Center
- Vertical options are: Top, Middle and Bottom
- For example, choosing Left and Bottom from the menus places the text in the lower left corner.
- Navigation Buttons for Image List (First, Previous, Next, Last) and an "x of x" display
- Works similar to a slide show. This allows you to page through all the images to see how your text will appear. Be sure that your text isn't too wide for any of the images.
- TrackBar for Setting Opacity / Transparency of the Text
- Slide right to increase opacity, left to decrease it.
- One use for this is the creation of simplistic watermarks for your images.
Entering Text
Type in the TextBox just below the tool bar. The currently selected image (previewed below the TextBox) updates as you type. There is no need to click a "Preview Button" or open another window to see how the text will look. Same goes for changing the font, color and opacity. Click back and forth via the tool bar arrows to view multiple images.
The StatusBar at the bottom of the window shows the path and file name of the currently selected image.
Here's the text tool's Load
event handler. It creates a collection of type Image and loads the images. Then it converts them, all except the text. The Back and Next buttons in the tool bar allow you to page through the images. No images are saved to disk at this point.
Private Sub dialog_Text_Load(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles MyBase.Load
Dim wr As Rectangle = Screen.PrimaryScreen.WorkingArea
Me.Size = New System.Drawing.Size(wr.Width - 10, wr.Height - 10)
Me.Location = New System.Drawing.Point(5, 5)
Me.Cursor = Cursors.WaitCursor
imgList.Clear()
For Each lvi As ListViewItem In main.lvFiles.Items
Try
Dim newBMP As Bitmap = PrepPreviewImage(Image.FromFile(lvi.Text))
imgList.Add(newBMP)
Catch ex As Exception
MsgBox(ex.ToString, MsgBoxStyle.Exclamation, main.title)
End Try
Next
SourceImage = imgList(0) index = 0 : imgCnt = main.lvFiles.Items.Count txt_Text.Font = My.Settings.TxtFont
sldr_Opc.Value = My.Settings.Opacity
lbl_Opc.Text = sldr_Opc.Value.ToString
BrushColor = My.Settings.txt_Color TextBrush = New SolidBrush(My.Settings.txt_Color) txt_Text.Text = My.Settings.TxtString
For Each tsmi As ToolStripMenuItem In tb_HAlign.DropDownItems
tsmi.Checked = False
Next
For Each tsmi As ToolStripMenuItem In tb_VAlign.DropDownItems
tsmi.Checked = False
Next
Select Case My.Settings.alignH
Case 1
alignh_Left.Checked = True
HTextAlign = TextAlignH.Left
Case 2
alignh_Center.Checked = True
HTextAlign = TextAlignH.Center
Case 3
alignh_Right.Checked = True
HTextAlign = TextAlignH.Right
End Select
Select Case My.Settings.alignV
Case 1
alignV_Top.Checked = True
VTextAlign = TextAlignV.Top
Case 2
alignv_Middle.Checked = True
VTextAlign = TextAlignV.Middle
Case 3
alignv_Bottom.Checked = True
VTextAlign = TextAlignV.Bottom
End Select
UpdateImage()
UpdateUI()
Me.Cursor = Cursors.Default
End Sub
Any time you take an action that changes the preview image (typing, changing font or color, or alignment), the image of course must be updated. So, the textbox's TextChanged
event, and the Click
events for the Font, Color and Alignment menu items call the UpdateImage
Sub.
Private Sub UpdateImage()
Try
If TextBrush Is Nothing Then
BrushColor = Color.FromArgb(sldr_Opc.Value, 240, 240, 240)
TextBrush = New SolidBrush(BrushColor)
End If
img = New Bitmap(SourceImage.Width, SourceImage.Height)
Dim g As Graphics = Graphics.FromImage(img)
g.DrawImage(SourceImage, 0, 0)
If txt_Text.TextLength > 0 Then
rectSize = g.MeasureString(TextForImages, txt_Text.Font, New PointF(0, 0),
StringFormat.GenericTypographic)
Select Case HTextAlign
Case TextAlignH.Center
x = (img.Width / 2) - (rectSize.Width / 2)
Case TextAlignH.Left
x = 2
Case TextAlignH.Right
x = (img.Width - rectSize.Width) - 2
End Select
Select Case VTextAlign
Case TextAlignV.Bottom
y = (img.Height - rectSize.Height) - 2
Case TextAlignV.Middle
y = (img.Height / 2) - (rectSize.Height / 2)
Case TextAlignV.Top
y = 2
End Select
Dim dPoint As New Point(x, y)
g.TextRenderingHint = TextRenderingHint.AntiAlias
g.DrawString(TextForImages, txt_Text.Font, TextBrush, dPoint,
StringFormat.GenericTypographic)
End If
picbox.Image = img
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub
Converting the Images
The Convert button will remain disabled until all necessary options are set (destination directory, etc). Once you've set all the required options, click the Convert ("Gear") button in the tool bar to start the process. Once the process is complete a dialog will open that details the results.
Possible results are:
- SUCCESS, if the image converted
- FAILED if it did not convert
- FILE NOT FOUND (lets face it, sometimes files disappear)
- SKIPPED if prevented by an overwrite rule.
When you close the results dialog, the destination directory will open so you can see the images.
NOTE: If an image file is corrupt and somehow still made it into the list, in all likelihood you'll get a FILE NOT FOUND or FAILED message in the results dialog.
Here's the code for the Convert button's click event:
Private Sub tb_Convert_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles tb_Convert.Click
dialog_Results.lb_Results.Items.Clear()
nameCnt = 0
For l = 0 To lvFiles.Items.Count - 1
Try
fileNameStr = CreateFileName()
If File.Exists(fileNameStr) Then
If rad_OvrNO.Checked Then
dialog_Results.lb_Results.Items.Add(fileNameStr & " : SKIPPED - No Overwrite")
dialog_Results.lb_Results.Items.Add("")
Exit Try
ElseIf rad_OvrPrompt.Checked Then
msg = "The image," & Chr(10) _
& fileNameStr & Chr(10) _
& "already exists. Overwrite?"
res = MsgBox(msg, MsgBoxStyle.YesNo, title)
If res = Windows.Forms.DialogResult.No Then
dialog_Results.lb_Results.Items.Add(
fileNameStr & " : SKIPPED - No Overwrite")
dialog_Results.lb_Results.Items.Add("")
Exit Try
End If
ElseIf rad_OvrAuto.Checked Then
End If End If
tmpBMP = System.Drawing.Image.FromFile(lvFiles.Items.Item(l).Text)
Dim destExt As String = cmb_Format.SelectedItem.ToString
If chk_Reformat.Checked Then
imgFormat = SetFormat(destExt)
Else
imgFormat = SetFormat(Path.GetExtension(lvFiles.Items.Item(l).Text))
End If
Dim imgSize As Size = ResizeImage(tmpBMP.Size)
Dim newBMP As New Bitmap(imgSize.Width, imgSize.Height)
Dim g As Graphics = Graphics.FromImage(newBMP)
g.DrawImage(tmpBMP, New Rectangle(0, 0, imgSize.Width, imgSize.Height))
tmpBMP.Dispose()
If chk_FlipH.Checked Then
newBMP.RotateFlip(RotateFlipType.RotateNoneFlipX)
End If
If chk_FlipV.Checked Then
newBMP.RotateFlip(RotateFlipType.RotateNoneFlipY)
End If
If cmb_ROT.SelectedIndex > 0 Then
Select Case cmb_ROT.SelectedIndex
Case 1
newBMP.RotateFlip(RotateFlipType.Rotate90FlipNone)
Case 2
newBMP.RotateFlip(RotateFlipType.Rotate180FlipNone)
Case 3
newBMP.RotateFlip(RotateFlipType.Rotate270FlipNone)
End Select
End If
If chk_Gray.Checked Then
newBMP = GrayScale(newBMP)
End If
If chk_TXT.Checked AndAlso My.Settings.TxtString.Length > 0 Then
Dim sb As New SolidBrush(My.Settings.txt_Color)
Select Case My.Settings.alignH
Case 1
x = 2
Case 2
x = (newBMP.Width / 2) - (My.Settings.txt_Rsize.Width / 2)
Case 3
x = (newBMP.Width - My.Settings.txt_Rsize.Width) - 2
End Select
Select Case My.Settings.alignV
Case 1
y = 2
Case 2
y = (newBMP.Height / 2) - (My.Settings.txt_Rsize.Height / 2)
Case 3
y = (newBMP.Height - My.Settings.txt_Rsize.Height) - 2
End Select
Dim tgr As Graphics = Graphics.FromImage(newBMP)
tgr.DrawString(My.Settings.TxtString, My.Settings.TxtFont, sb, x, y,
StringFormat.GenericTypographic)
End If
If imgFormat.Equals(ImageFormat.Jpeg) Then
Dim jgpEncoder As ImageCodecInfo = GetEncoder(ImageFormat.Jpeg)
Dim myEncoder As Encoder = Encoder.Quality
Dim myEncoderParameters As New EncoderParameters(1)
Dim myEncoderParameter As New EncoderParameter(myEncoder, sldr_Quality.Value)
myEncoderParameters.Param(0) = myEncoderParameter
newBMP.Save(fileNameStr, jgpEncoder, myEncoderParameters)
Else
newBMP.Save(fileNameStr, imgFormat)
End If
dialog_Results.lb_Results.Items.Add(fileNameStr & " : SUCCESS!")
dialog_Results.lb_Results.Items.Add("")
Catch ex As Exception
MsgBox(ex.ToString)
If Not File.Exists(fileNameStr) Then
dialog_Results.lb_Results.Items.Add(fileNameStr & " : FILE NOT FOUND")
dialog_Results.lb_Results.Items.Add("")
Else
dialog_Results.lb_Results.Items.Add(fileNameStr & " : ERROR")
dialog_Results.lb_Results.Items.Add("")
End If
End Try
Next l
dialog_Results.ShowDialog()
Try
System.Diagnostics.Process.Start(txt_DestDir.Text)
Catch ex As Exception
MsgBox("Could not open destination directory.", MsgBoxStyle.Exclamation, title)
End Try
End Sub
The Convert sub calls several functions to complete the operation.
If you've selected a resize option, this function takes the size of each original (ByVal s As Size
) and applies that option. It then returns the new size. If you have chosen not to resize, it returns the original size back to the Convert sub.
Friend Function ResizeImage(ByVal s As Size)
If Not chk_Resize.Checked Then
Return s
End If
Try
Select Case tab_Resize.SelectedIndex
Case 0 If rad_Width.Checked Then
pctW = Val(txt_Dim.Text) / s.Width
newW = Val(txt_Dim.Text)
newH = s.Height * pctW
ElseIf rad_Height.Checked Then
pctH = Val(txt_Dim.Text) / s.Height
newW = s.Width * pctH
newH = Val(txt_Dim.Text)
End If
Case 1 pctSldr = sldr_Size.Value / 100
newW = s.Width * pctSldr
newH = s.Height * pctSldr
Case 2 newW = Val(txt_AbsWidth.Text)
newH = Val(txt_AbsHeight.Text)
End Select
Catch ex As Exception
Return New Size(0, 0)
End Try
Return New Size(newW, newH)
End Function
If you've selected a common format for all converted images...
Private Function SetFormat(ByVal ext As String) As ImageFormat
Select Case ext
Case ".jpg"
Return ImageFormat.Jpeg
Case ".gif"
Return ImageFormat.Gif
Case ".bmp"
Return ImageFormat.Bmp
Case ".png"
Return ImageFormat.Png
Case ".tif"
Return ImageFormat.Tiff
Case Else
MsgBox(msg, MsgBoxStyle.Information, "Error")
Return Nothing
End Select
End Function
This creates a new path and file name for each image...
Private Function CreateFileName()
Dim fStr As String = String.Empty
fStr = txt_DestDir.Text
If Not fStr.EndsWith("\") Then
fStr &= "\"
End If
If txt_FileName.TextLength > 0 Then
nameCnt += 1
fStr &= txt_FileName.Text & "_" & nameCnt.ToString
Else
fStr &= Path.GetFileNameWithoutExtension(lvFiles.Items.Item(l).Text)
End If
If chk_Reformat.Checked Then
fStr &= cmb_Format.SelectedItem.ToString
Else
fStr &= Path.GetExtension(lvFiles.Items.Item(l).Text)
End If
Return fStr
End Function
If you're converting to JPG, this returns the encoder so you can set compression
Private Function GetEncoder(ByVal format As ImageFormat) As ImageCodecInfo
Dim codecs As ImageCodecInfo() = ImageCodecInfo.GetImageDecoders()
For Each codec As ImageCodecInfo In codecs
If codec.MimeType.Equals("image/jpeg") Then
Return codec
End If
Next codec
Return Nothing
End Function
And if you're converting to grayscale...
Friend Function GrayScale(ByVal img As Image)
Try
Dim bm As Bitmap = New Bitmap(img.Width, img.Height)
Dim g As Graphics = Graphics.FromImage(bm)
Dim cm As ColorMatrix = New ColorMatrix(New Single()() _
{New Single() {0.3, 0.3, 0.3, 0, 0}, _
New Single() {0.59, 0.59, 0.59, 0, 0}, _
New Single() {0.11, 0.11, 0.11, 0, 0}, _
New Single() {0, 0, 0, 1, 0}, _
New Single() {0, 0, 0, 0, 1}})
Dim ia As ImageAttributes = New ImageAttributes()
ia.SetColorMatrix(cm)
g.DrawImage(img, New Rectangle(0, 0, img.Width, img.Height), 0, 0, img.Width,
img.Height, GraphicsUnit.Pixel, ia)
g.Dispose()
Return bm
Catch ex As Exception
MsgBox(ex.ToString, MsgBoxStyle.Exclamation, title)
Return Nothing
End Try
End Function
That's about it. The conversion process runs pretty quickly even with a large number of images. If you find any bugs, please do post a message here and I'll crush it as soon as time allows. I hope you find IBC useful and enjoyable.
Points of Interest
I spent some time stressing over how best to create a help file to be included in the project. I ended up creating simple HTML docs and storing them in My.Resources
. The help app consists of a SplitContainer with a WebBrowser control in the right panel to display the text. The left panel contains a TableLayoutPanel
with LinkLabels representing each topic in the help file. Click the link and the corresponding HTML file is written to the browser's DocumentText
property. Can't call it a perfect solution, but it has the virtue of being easy on the eyes. I'd enjoy knowing what others think of it.
History
Initial release uploaded 09.24.2010