批量图像尺寸调整器






4.67/5 (10投票s)
一个用于调整图像批次的工具

引言
本文介绍了一个程序,该程序旨在一次性批量处理大量图像的缩放,以及该程序如何利用一些新的Windows 7功能进行增强。
更新
我对此程序进行了不少修改,以满足CodeProject上大家的功能请求。这些都是很好的想法,所以下面将它们一一列出……
- 增加了检查子文件夹的功能。感谢z33z和mhn217。
- 增加了设置输出高/宽度的功能(自动忽略小于设定的尺寸的图像并保留宽高比)。感谢z33z。
- 输出的图像现在保留EXIF信息。感谢z33z。
背景
如今,几乎每个人都拥有数码相机,而且近乎相同的人拥有某种社交网络账户。
人们喜欢分享这些图片,但通常情况下,各种社交网络服务会限制文件大小,因此用户在上传图片之前必须先缩放图片。作为一名网页开发者,我可以用Photoshop来完成这件事,但并非人人都能获得Photoshop。
我四处寻找替代方案,偶然发现了Alberto Venditti的Image Batch Resizer。我喜欢他的作品,但我认为他的软件还不够完善。我希望能够缩放透明GIF和索引PNG文件,所以我开始着手开发我自己的工具,然后可以与公众分享。
所以,这是我的作品……它名字很长……Empirical Design Batch Image Resizer。
Using the Code
原理很简单。只需加载一个包含图片的文件夹,选择一个目标文件夹,选择缩放比例,然后点击“开始”。
还有一些设置,用于在透明GIF上绘制哑光背景,以及使用不同的扩散技术来减少颜色数量,但默认设置应该已经很好了。
绝大多数工作由后台工作进程处理,它包含三个简单的例程,以提供更流畅的用户界面体验。
#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
缩放图像
使用GDI+缩放图像相当容易,但有时结果会不尽人意,尤其是在编辑GIF文件时。为了改善结果,我搜索了一下,偶然发现了Octree Quantizer。现在,这些都比较高深,超出了本文的范围,但在遵循注释中的说明后,我成功地将代码转换成了完全安全的代码,并添加了一些增强功能,例如Floyd Steinburg抖动。
作为一项挑战,我给自己设定了一个不同的缩放图像的方法。如果我想将图像缩小50%,而不是简单地将其宽度和高度减半,如果我让它的面积变为一半呢?
这实际上是一个数学谜题,我从未见过图像软件这样做过,所以Google也帮不上忙。实际上花了我几个小时才弄清楚,所以我为此感到非常自豪。
这是纸上的计算结果

以及代码实现
/// <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
为应用程序添加那些闪亮的小功能比预想的要容易得多。首先是获取Microsoft Windows 7开发者培训工具包。
然后,我获取了Microsoft.WindowsAPICodePack.dll和Microsoft.WindowsAPICodePack.Shell.dll的副本,并将它们作为引用添加到我的项目中。创建了一个类来存放我的代码(Windows7Tools
),我可以通过添加这两行代码来访问任务栏的功能:
using Microsoft.WindowsAPICodePack.Taskbar;
using Microsoft.WindowsAPICodePack.Shell;
设置任务栏进度条的值很简单,就像这样:
/// <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);
}
}
以及重置它:
/// <summary>
/// Resets the Windows 7 TaskBarProgressBar to no progress state.
/// </summary>
internal void ResetTaskbarProgressValue()
{
if (TaskbarManager.IsPlatformSupported)
{
TaskbarProgressBarState state = TaskbarProgressBarState.NoProgress;
_windows7Taskbar.SetProgressState(state);
}
}
显然,在所有操作点,检查安装的操作系统是否为Windows 7至关重要,以避免任何错误。
TaskbarManager.IsPlatformSupported
……这只是简单地像这样调用API中的一个函数:
/// <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)则稍微难一些……
虽然Windows 7 API允许您向跳转列表中添加或删除静态文件或任务,但微软的好人们似乎忘记了文件夹。
默认情况下,只有Explorer可以显示最近打开的文件夹列表,并且只有注册的文件类型可以与您的程序关联。我通过重新创建跳转列表,并将最近打开的文件数组存储在我的应用程序设置中,来模仿一些适当的行为,但我无法处理用户删除跳转列表链接的情况,因为Windows 7任务栏只返回一个路径字符串数组,而不是您程序的参数。
更新……事实证明,跳转列表类非常容易编辑。我只是添加了一个名为RecentLink
的public
嵌套类,并编辑了两个函数来返回此类。
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;
}
}
现在,我可以通过一个事件处理程序来检查我的最近文件夹列表。
/// <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 =>
{
if (jl.RemovedArgs.Contains(rt.RecentPath))
{
RecentFoldersList.Remove(rt);
}
});
}
}
创建跳转列表很容易。
/// <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);
}
}
});
}
关注点
这是我第一次用C#进行严肃的编程。我最初是用VBA开始编码的,并且一直不愿意从Visual Basic迁移过来,但我现在已经爱上了它的语法,尤其是lambda表达式。我强烈建议任何还没有勇于尝试改变的人都试一试。
历史
- 2010年1月2日 初始发布
- 2010年2月2日 上传修复的源代码和演示文件
- 2010年2月8日 添加了新的跳转列表功能、关于页面、子文件夹、EXIF信息和缩放功能