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"
private void bwResize_DoWork(Object sender, DoWorkEventArgs e)
{
..........
}
private void bwResize_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.prgBar.Value = e.ProgressPercentage;
Windows7Tools.SetTaskbarProgressValue(e.ProgressPercentage);
}
private void bwResize_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
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:
And in code:
private List<double> CalculateDimensionsByArea(int width,
int height, int reductionRatio)
{
List<double> result = new List<double>();
int Area = width * height;
double newArea = (double)(((float)reductionRatio / 100) * (float)Area);
double Ratio = (double)(Math.Abs((float)width / (float)height));
double newHeight = (double)(Math.Sqrt(newArea / Ratio));
double newWidth = (double)((float)Ratio * (float)newHeight);
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:
internal void SetTaskbarProgressValue(int currentValue)
{
if (TaskbarManager.IsPlatformSupported)
{
TaskbarProgressBarState state = TaskbarProgressBarState.Normal;
_windows7Taskbar.SetProgressState(state);
_windows7Taskbar.SetProgressValue(currentValue, 100);
}
}
And resetting it again:
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:
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);
Marshal.FreeCoTaskMem(pszString);
}
foreach (JumpListCustomCategory category in customCategoriesCollection)
category.RemoveJumpListItem(rl.RemovedPath);
}
return rl;
}
private RemovedLink RemoveCustomCategoryLink(IShellLinkW link)
{
RemovedLink rl = new RemovedLink();
if (customCategoriesCollection != null)
{
StringBuilder sbPath = new StringBuilder(256);
link.GetPath(sbPath, sbPath.Capacity, IntPtr.Zero, 2);
rl.RemovedPath = sbPath.ToString();
StringBuilder sbArgs = new StringBuilder(256);
link.GetArguments(sbArgs, sbArgs.Capacity);
rl.RemovedArgs = sbArgs.ToString();
foreach (JumpListCustomCategory category in customCategoriesCollection)
category.RemoveJumpListItem(rl.RemovedPath);
}
return rl;
}
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.
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.
internal void CreateJumplist(List<recentfolder> recentFoldersList)
{
if (TaskbarManager.IsPlatformSupported)
{
_jumpList = JumpList.CreateJumpList();
_jumpList.JumpListItemsRemoved += new EventHandler
(_jumpList_JumpListItemsRemoved);
_jumpList.KnownCategoryToDisplay = JumpListKnownCategoryType.Neither;
_customRecentCategory = new JumpListCustomCategory("Recent Folders");
_jumpList.AddCustomCategories(_customRecentCategory);
_jumpList.Refresh();
CreateCustomRecentCategory(recentFoldersList);
_jumpList.Refresh();
}
}
internal void CreateCustomRecentCategory(List<recentfolder> recentFoldersList)
{
int maxSlots = (int)_jumpList.MaxSlotsInList;
recentFoldersList = recentFoldersList.OrderByDescending
(rcnt => rcnt.RecentDate).Take(maxSlots).ToList();
_resizerForm.RebuildMenu(this.RecentFoldersList);
string systemFolder =
Environment.GetFolderPath(Environment.SpecialFolder.System);
recentFoldersList.ForEach(rcnt =>
{
if (CheckDirectoryName(rcnt.RecentPath))
{
string args = string.Format("\" {0}\"", rcnt.RecentPath);
string title = GetDirectoryTitle(rcnt.RecentPath);
string iconPath = Path.Combine(systemFolder, "Shell32.dll");
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