Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Batch Image Resizer

0.00/5 (No votes)
9 Feb 2010 1  
A tool for resizing batches of images
screenshot

Introduction

This article describes a program designed to resize large numbers of images in a single batch process, and how that program was enhanced using some of the new Windows 7 features.

Update

I've made quite a few changes to the program to cater for the feature requests here on CodeProject. They were all good ideas so here they are below....

  • Added the ability to check subfolders. Thanks z33z and mhn217.
  • Added the ability to set a height/width for the output (automatically ignores images smaller than definitions and preserves aspect ratio). Thanks z33z.
  • Output images now preserve EXIF information. Thanks z33z.

Background

Almost everyone has a digital camera these days, and nearly as many people have some sort of social networking account.

People like to share those images, but more often than not, the various networking services restrict the file size, so users have to resize the images first before they can upload them. As a web developer, I have the luxury of doing this using Photoshop, but not everyone has access to that.

I looked around for alternatives, and stumbled across the Image Batch Resizer by Alberto Venditti. I liked what he had done, but I thought his software didn't quite cut it. I wanted to be able to resize transparent GIFs and indexed PNG files, so I started work on something of my own that I could then share with the public.

So here it is ...It's a mouthful... The Empirical Design Batch Image Resizer.

Using the Code

The premise is simple. Simply load up a folder full of images, choose a destination folder, choose your reduction ratio, and click Go.

There are a few more settings for painting matte background onto transparent GIFs and for using different diffusion techniques for reducing colours, but the default should work nicely.

The vast majority of the work is handled by a background worker with three simple routines to give the user a smoother interface experience.

#region "BackgroundWorker"

/// <summary>
/// Handles the DoWork event of the bwResize control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="T:System.ComponentModel.DoWorkEventArgs">
///       instance containing the event data.</param>
private void bwResize_DoWork(Object sender, DoWorkEventArgs e)
{
 ..........
}

/// <summary>
/// Handles the ProgressChanged event of the bwResize control.
/// Updates the progress bar.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see 
//    cref="T:System.ComponentModel.ProgressChangedEventArgs">
// instance containing the event data.</param>
private void bwResize_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
   this.prgBar.Value = e.ProgressPercentage;

   //Call the Windows 7 taskbar
   Windows7Tools.SetTaskbarProgressValue(e.ProgressPercentage);
}

/// <summary>
/// Handles the RunWorkerCompleted event of the bwResize control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see 
///   cref="T:System.ComponentModel.RunWorkerCompletedEventArgs">
/// instance containing the event data.</param>
private void bwResize_RunWorkerCompleted(object sender, 
                      RunWorkerCompletedEventArgs e)
{
   //Report success
   MessageBox.Show("All queued images processed", 
     "Batch Completed", MessageBoxButtons.OK, 
     MessageBoxIcon.Information);

 ..........

}

#endregion

Resizing the Images

Resizing images is fairly easy with GDI+, but sometimes the results leave a lot to be desired, especially when editing GIF files. To improve upon the results, I did a bit of Googling, and stumbled upon the Octree Quantizer. Now, all this is a bit high-brow and outside the scope of this article, but after following the instructions in the comments, I was able to convert the code into fully safe code, and added a few enhancements like Floyd Steinburg dithering.

As a bit of a challenge, I set myself the task of resizing the images in a different way. Instead of just simply halving the width and the height of the image if I want to reduce it by 50%, what about if I made it to take up half the area?

This was actually a bit of a mathematical puzzle, and I'd never seen imaging software do this, so Google was no help. It actually took me a few hours to figure this one out, so I'm quite proud of myself.

Here it is on paper:

maths

And in code:

/// <summary>
/// Performs a calculation to resolve the new height
/// and width for the resized image based upon
/// the calculated area of the new image (a percentage of the original image area).
/// </summary>
/// <param name="width">The original image width</param>
/// <param name="height">The original image height</param>
/// <returns>A generic list of Double</returns>
/// <remarks></remarks>
private List<double> CalculateDimensionsByArea(int width, 
                     int height, int reductionRatio)
{
    List<double> result = new List<double>();

    //Calculate the original area of the current image
    int Area = width * height;

    //Calculate the area for the new image 
    //(reduced by ratio selected on the trackbar)
    double newArea = (double)(((float)reductionRatio / 100) * (float)Area);

    //Calculate the new height and width based
    //on the new area and original width/height ratio
    //x = R * y                     x = width
    //R * y * y = A                 y = height
    //y^2 = A/R                     R = Ratio (width/height original image)
    //y = Sqrt(A/R)                 A = Area (from new image)
    //x = R * Sqrt(A/R)

    double Ratio = (double)(Math.Abs((float)width / (float)height));
    double newHeight = (double)(Math.Sqrt(newArea / Ratio));
    double newWidth = (double)((float)Ratio * (float)newHeight);

    //Add the results to the list
    result.Add(newWidth);
    result.Add(newHeight);

    return result;
}

Windows 7

Adding those shiny little extras to the app was surprisingly easy. The first thing was to get the Windows 7 Training Kit for Developers.

From there, I got a copy of Microsoft.WindowsAPICodePack.dll and Microsoft.WindowsAPICodePack.Shell.dll, and added them as references to my project. Creating a class to house my code in (Windows7Tools), I was able to access the functionality of the taskbar by simply adding these two lines:

using Microsoft.WindowsAPICodePack.Taskbar;
using Microsoft.WindowsAPICodePack.Shell;

Setting the value of the taskbar was as easy as this:

/// <summary>
/// Sets the value of the Windows 7 TaskBarProgressBar
/// to match the current progress of the application.
/// </summary>
/// <param name="currentValue">The value to set the
/// TaskBarProgressBar to </param>
internal void SetTaskbarProgressValue(int currentValue)
{
   if (TaskbarManager.IsPlatformSupported)
    {
       TaskbarProgressBarState state = TaskbarProgressBarState.Normal;
       _windows7Taskbar.SetProgressState(state);
       _windows7Taskbar.SetProgressValue(currentValue, 100);

    }
}

And resetting it again:

/// <summary>
/// Resets the Windows 7 TaskBarProgressBar to no progress state.
/// </summary>
internal void ResetTaskbarProgressValue()
{
   if (TaskbarManager.IsPlatformSupported)
    {
       TaskbarProgressBarState state = TaskbarProgressBarState.NoProgress;
       _windows7Taskbar.SetProgressState(state);
    }
}

Obviously, at all points, it was important to check if the installed Operating System is Windows 7, to avoid any errors.

TaskbarManager.IsPlatformSupported

...simply calls a function in the API like this:

/// <summary>
/// Whether the software is running on Windows 7.
/// </summary>
/// <value></value>
/// <returns>true if the software is running
///    on Windows7, else false.</returns>
bool IsPlatformSupported
{
   get
    {
       return (Environment.OSVersion.Version.Major == 6 && 
          Environment.OSVersion.Version.Minor >= 1) ||  
          (Environment.OSVersion.Version.Major > 6);
    }
}

Jumplists were a little bit tougher....

Whilst the Windows 7 API does allow you to add or remove static files or tasks to a jumplist, the good people at Microsoft seem to have forgotten about folders.

By default, only Explorer can display lists of recently opened folders, and only registered file types can be associated with your program. I was able to mimic some of the appropriate behaviour by recreating the jumplist with an array of recently opened files stored in my app-settings, but I had no way of honoring when a user deleted a jumplist link, as the Windows 7 Taskbar only returns a string array of paths, not the arguments to your program.

Update...... It turns out that the jumplist class was really easy to edit. I simply added a public nested class called RecentLink and edited two functions to return this class.

private RemovedLink RemoveCustomCategoryItem(IShellItem item)
    {
       RemovedLink rl = new RemovedLink();
       rl.RemovedArgs = "";

       if (customCategoriesCollection != null)
        {
           IntPtr pszString = IntPtr.Zero;
           HRESULT hr = item.GetDisplayName
		(ShellNativeMethods.SIGDN.SIGDN_FILESYSPATH, out pszString);
           if (hr == HRESULT.S_OK && pszString != IntPtr.Zero)
            {
               rl.RemovedPath = Marshal.PtrToStringAuto(pszString);
               // Free the string
               Marshal.FreeCoTaskMem(pszString);
            }

           // Remove this item from each category
           foreach (JumpListCustomCategory category in customCategoriesCollection)
               category.RemoveJumpListItem(rl.RemovedPath);
        }

       return rl;
    }

private RemovedLink RemoveCustomCategoryLink(IShellLinkW link)
    {
       RemovedLink rl = new RemovedLink();

       if (customCategoriesCollection != null)
        {
           //Get the path.
           StringBuilder sbPath = new StringBuilder(256);
           link.GetPath(sbPath, sbPath.Capacity, IntPtr.Zero, 2);
           rl.RemovedPath = sbPath.ToString();

           //Get the arguments.
           StringBuilder sbArgs = new StringBuilder(256);
           link.GetArguments(sbArgs, sbArgs.Capacity);
           rl.RemovedArgs = sbArgs.ToString();

           // Remove this item from each category
           foreach (JumpListCustomCategory category in customCategoriesCollection)
               category.RemoveJumpListItem(rl.RemovedPath);
        }

       return rl;
    }

/// <summary>
/// Added class to allow for passing of args to deleted list.
/// </summary>
public class RemovedLink
    {
       public string RemovedPath
        {
           get;
           set;
        }

       public string RemovedArgs
        {
           get;
           set;
        }
    }

I could now check my list of recent folders with an event handler.

/// <summary>
/// The event handler for JumpListItemsRemoved
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref=
	"T:Microsoft.WindowsAPICodePack.Taskbar.UserRemovedJumpListItemsEventArgs"/> 
	instance containing the event data.</param>
private void _jumpList_JumpListItemsRemoved(object sender, 
	UserRemovedJumpListItemsEventArgs e)
    {
       foreach (JumpList.RemovedLink jl in e.RemovedItems)
        {
           RecentFoldersList.ForEach(rt =>

Creating a jumplist is easy though.

/// <summary>
/// Creates a Windows 7 jumplist containing a list of recently added folders.
/// </summary>
/// <param name="recentFoldersList">The list of recently added folders.</param>
internal void CreateJumplist(List<recentfolder> recentFoldersList)
    {
       if (TaskbarManager.IsPlatformSupported)
        {
           //Create the jumplist.
           _jumpList = JumpList.CreateJumpList();

           //Define our event handler.
           _jumpList.JumpListItemsRemoved += new EventHandler
				(_jumpList_JumpListItemsRemoved);

           //Define visible categories.
           _jumpList.KnownCategoryToDisplay = JumpListKnownCategoryType.Neither;

           //Define our new custom category.
           _customRecentCategory = new JumpListCustomCategory("Recent Folders");

           //Add the custom category.
           _jumpList.AddCustomCategories(_customRecentCategory);

           //Refresh the jumplist so the event handler is triggered.
           _jumpList.Refresh();

           //Create the list of recent folders.
           CreateCustomRecentCategory(recentFoldersList);
           _jumpList.Refresh();
        }
    }

/// <summary>
/// Creates a custom "Recent Folders" category for our jumplist
/// </summary>
internal void CreateCustomRecentCategory(List<recentfolder> recentFoldersList)
{
        //Check how many slots are available.
       int maxSlots = (int)_jumpList.MaxSlotsInList;
       //Reorder and trim our list if necessary.
       recentFoldersList = recentFoldersList.OrderByDescending
       		(rcnt => rcnt.RecentDate).Take(maxSlots).ToList();
            
       //Rebuild the main menu on our form.
       _resizerForm.RebuildMenu(this.RecentFoldersList);

       //The system folder for our current user.
       string systemFolder = 
		Environment.GetFolderPath(Environment.SpecialFolder.System);

       recentFoldersList.ForEach(rcnt =>
        {
           //Check the path for illegal characters.
           if (CheckDirectoryName(rcnt.RecentPath))
            {
               //Set the arguments.
               string args = string.Format("\" {0}\"", rcnt.RecentPath);
               //Set the title.
               string title = GetDirectoryTitle(rcnt.RecentPath);
               //Set the icon path.
               string iconPath = Path.Combine(systemFolder, "Shell32.dll");
               //Set the icon.
               IconReference iconReference = new IconReference(iconPath, 4);

               using (JumpListLink jli = new JumpListLink(_myApplicationPath, title) 
               	{ Arguments = args, IconReference = iconReference })
                {
                   _customRecentCategory.AddJumpListItems(jli);
                }
            }
        });
}

Points of Interest

This was the first bit of serious programming I've ever done with C#. I started coding with VBA, and have always been a bit lazy to move over from Visual Basic, but I've since fallen in love with the syntax, especially lambda expressions. I would heartily recommend anyone who hasn't braved the change-over to give it a try.

History

  • 02/01/2010 Initial release
  • 02/02/2010 Uploaded fixed source and demo files
  • 08/02/2010 Added new jumplist functionality, about page, subfolders, EXIF info and resize function

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here