Introduction
Every week I take dozens of pictures during the soccer games of Kevin, my 7 year old son. Most of these pictures are placed on a web site created for his team. And every week I find myself performing the ever recurring task of resizing the images (mostly 3072 x 2048) to something more friendly (e.g.: 768 x 512). Why? Just for those less fortunate that have no ADSL or so.
Of course, every repeating task is prone to be automated, and a first application was born to resize the pictures according to my wishes. To cope with different source and target paths, as well as different ways of resizing, the application needed a user interface. Working on the application and its (simple, yet effective) user interface, generalizing the use of the application is evident. The next step then is to share it with all of you and hope you find the application or some code snippet useful.
Using Image Converter
Just download the zipped executable, unzip it, and open it. After you have opened ImageConverter, you can open the image files that you want to convert by pressing the Open... button. The target path for the converted images is automatically set to the source path, if not set already. By pressing the Browse... button, you can select an alternative path for the converted images.
Before you start the conversion process, you can define the image format after conversion, you can define how the images should be named, and you can define the required image size.
For naming the target images, you can use the original name, and the corresponding extension for the target image format is added. If a file with the resulting name already exists, it will be overwritten. Another option is to use a prefix defined by you. Each target image file will get a unique name based on this prefix and a sequence number.
The target size can be a specified size, which means that the original image will be stretched. If you are just interested in obtaining images for which the largest of width or height is less than or equal to the specified maximum, the image will be scaled if either width or height is larger than the specified maximum. The third option is to scale each image by the specified percentage.
Using the code
The main code for the application is in FormImageConverter.cs whilst the code for the Thumbnail
control I use is in Thumbnail.cs.
Selecting Images
The images to be converted are selected by invoking the ShowDialog
method of a standard OpenFileDialog
instance. The selected image files are processed by the DisplayThumbnails
method, which disposes off any Thumbnail
control still present. Then for each image file, a Thumbnail
control is created and added to panelThumbnails.Controls
. Note that if the file to be loaded is not a (valid) image file, an OutOfMemoryException
is thrown and the Thumbnail
control will not be created.
Conversion Process Thread
Because I want some progress information displayed during the conversion process, the conversion process takes place in another thread. The thread is created and started from the event handler for the Convert button.
private void buttonConvert_Click(object sender, System.EventArgs e)
{
this.Cursor = Cursors.WaitCursor;
_convertThread = new Thread(new ThreadStart(ConvertImageFiles));
_convertThread.Start();
}
The ConvertImageFiles
method takes care of determining the extension and image format for the target image, as well as the name for the image file. Using this information and knowing the size for the target image, the original image as represented by the Thumbnail
control can be converted.
private void ConvertImageFiles()
{
this.Cursor = Cursors.WaitCursor;
_converting = true;
this.EnableControls();
try
{
...
System.Drawing.Size targetSize = this.DetermineTargetSize(thumbnail.Image);
System.Drawing.Image targetImage = null;
try
{
targetImage = new Bitmap(thumbnail.Image, targetSize);
targetImage.Save(targetFileName, targetImageFormat);
this.Invoke(_imageInfoDelegate,
new object[] { thumbnail, targetFileName, targetSize });
targetImage.Dispose();
}
catch (System.Threading.ThreadAbortException e)
{
throw e;
}
catch {}
...
}
catch {}
finally
{
this.Cursor = Cursors.Default;
_converting = false;
this.EnableControls();
}
}
To prevent the user from changing the settings during the conversion process, the controls on the main form are disabled at the start, and the Close button becomes a Cancel button. Pressing the Cancel button causes the thread to be aborted by calling _convertThread.Abort
method, which causes a System.Threading.ThreadAbortException
to be thrown. The exception is caught and re-thrown until the outer try
-catch
is reached, and the controls can be enabled again.
To update the progress information during the conversion process, a delegate is executed using the Invoke
method on the main form. The progress information uses the thumbnail, the target file name, and the target size. These objects are put in an object array, which holds the arguments for the DisplayImageInfo
method to be called.
private delegate void ImageInfoDelegate(Thumbnail thumbnail,
string targetFile, System.Drawing.Size toSize);
private ImageInfoDelegate _imageInfoDelegate = null;
_imageInfoDelegate = new ImageInfoDelegate(DisplayImageInfo);
this.Invoke(_imageInfoDelegate, new object[] { thumbnail,
targetFileName, targetSize });
Enabling/Disabling Controls
As noted, the various controls on the main form are disabled during the conversion process and the Close button changed into a Cancel button. Also, if no images have been selected or no target path defined, the Convert button is disabled. This is implemented in the EnableControls
method, which has to be called every time the conditions queried in the method have changed.
private void EnableControls()
{
this.buttonConvert.Enabled =
!_converting &&
this.panelThumbnails.Controls.Count > 0 &&
this.labelTargetPathValue.Text.Trim() != "";
this.buttonBrowseTargetPath.Enabled = !_converting;
this.buttonOpen.Enabled = !_converting;
this.groupBoxOnConvert.Enabled = !_converting;
this.groupBoxTargetSize.Enabled = !_converting;
this.comboBoxTargetFormat.Enabled = !_converting;
this.buttonClose.Text = _converting ? "Cancel" : "Close";
}
Thumbnail Control
The Thumbnail
control is a UserControl
on which a Panel
is placed that contains a PictureBox
and two Label
s. The PictureBox
holds the thumbnail image, the file name Label
, and the original image size Label
. The border around the control is obtained by setting the control's DockPadding
to 2 for all sides. The thin line between the PictureBox
and the file name Label
is the BackColor
of the underlying Panel
'shining' through. This is accomplished by setting both Label
s to dock on the bottom and by giving the PictureBox
the size of the remaining space with the height set to just one pixel less.
The main task of the Thumbnail
control is to show a thumbnail for each loaded image file and to present easy access to the corresponding image and its file path. The file path is supplied to the constructor, and the file is loaded using the FromFile
method.
public Thumbnail(string filePath)
{
InitializeComponent();
_filePath = filePath;
try
{
_image = System.Drawing.Image.FromFile(_filePath);
}
catch (System.OutOfMemoryException e)
{
throw e;
}
...
Loading a file that does not represent a (recognizable) image causes an OutOfMemoryException
, so the exception is caught and thrown again to inform the code constructing the thumbnail.
If it is loadable, the size for the thumbnail image is calculated and the visual feedback is set.
...
int max = Math.Min(this.pictureBox.Width, this.pictureBox.Height);
int width = _image.Width;
int height = _image.Height;
if (_image.Width > max || _image.Height > max)
{
if (_image.Width > _image.Height)
{
width = max;
height = (int) (_image.Height * max / _image.Width);
}
else
{
width = (int) (_image.Width * max / _image.Height);
height = max;
}
}
this.pictureBox.Image = new Bitmap(_image, width, height);
this.labelFileName.Text = Path.GetFileName(_filePath);
this.labelImageSize.Text = string.Format("{0} x {1}",
_image.Size.Width, _image.Size.Height);
}
Conclusion
I found working on the ImageConverter application quite satisfying. Of course, it saves me a lot of time resizing pictures before displaying them on the soccer team's web site. But this small application also shows various aspects that can be found in developing a general C# application. Such aspects include the creation of user controls to get that specific user interface feeling you want, using threads for progress information, and utilizing the power of certain built-in .NET framework methods.
History
First version on September 8th, 2004.