65.9K
CodeProject 正在变化。 阅读更多。
Home

批量图像尺寸调整器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (10投票s)

2010年2月1日

CPOL

4分钟阅读

viewsIcon

56400

downloadIcon

3600

一个用于调整图像批次的工具

screenshot

引言

本文介绍了一个程序,该程序旨在一次性批量处理大量图像的缩放,以及该程序如何利用一些新的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也帮不上忙。实际上花了我几个小时才弄清楚,所以我为此感到非常自豪。

这是纸上的计算结果

maths

以及代码实现

/// <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.dllMicrosoft.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任务栏只返回一个路径字符串数组,而不是您程序的参数

更新……事实证明,跳转列表类非常容易编辑。我只是添加了一个名为RecentLinkpublic嵌套类,并编辑了两个函数来返回此类。

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信息和缩放功能
© . All rights reserved.