需求

上周,领导给我分配了一个需求:服务器上的图片文件非常大,每天要用掉两个G的大小的空间,要做一个自动压缩图片的工具处理这些大图片。领导的思路是这样的:

1)打开一个图片,看它的属性里面象素是多少,大于1000就按比例缩小到1000。

2)再看它的品质属性,比如我们标准是50,如果大于这个值再修改品质。

压缩后的文件大小不能超过200k。

思路

因为服务器上的图片文件名是加密处理过的,和图片文件一起存在的还有其它附件,没有后缀名,用肉眼根本看不出来是否是图片文件。所以刚开始的时候,我的思路是先批量修改后缀名,再获取图片的像素,最后再进行压缩。后来在做的过程中,发现不用处理后缀名,直接获取图片信息就能识别文件是否是图片。

所以,最后的做法是:

1)遍历文件夹下的图片文件的时候,先根据图片信息,把图片文件提取到列表

2)然后再根据图片的像素大小进行处理。像素在1000以内的直接修改图片品质处理,像素大于1000的按尺寸大小压缩图片,然后再修改图片品质处理。

(像素大于1000这种情况之所以有两步是因为按尺寸大小进行压缩后,图片大小大于1M,不符合预期的要求,所以压缩图片后再修改图片品质。这一步为了避免混淆,我把按尺寸大小压缩图片放到另一个文件夹处理,这个文件夹在处理好图片后,会把压缩图片文件进行删除,所以这个文件夹永远是空的,不会占空间)

//做的时候一听到是自动压缩图片,批量处理文件,以为很难,很深奥,真正动手后其实是办法总比困难多。总有办法实现的,只是时间问题。

代码片段

1)因为文件的位置不固定,文件夹下面有图片,也有文件夹,里面还有图片。所以要遍历子目录。

        /// <summary>
/// 遍历文件
/// </summary>
/// <param name="di"></param>
public void ListFiles(DirectoryInfo di)
{
if (!di.Exists)
{
return;
} if (di == null)
{
return;
} //返回当前目录的文件列表
FileInfo[] files = di.GetFiles(); for (int i = 0; i < files.Length; i++)
{ try
{
//判断是否具有照片信息,报错即不是照片文件
GetMetaData.GetExifByMe(files[i].FullName); //把图片文件添加到列表视图
this.lvSourceFolderList.Items.Add(files[i].FullName); //把图片文件添加到图片列表
imageList.Add(files[i].FullName); }
catch (Exception)
{
//Logging.Error(System.IO.Path.GetFileName(files[i].FullName) + ",非图片文件," + ex.Message);
continue;
} }
this.lbInfomation.Text = "共" + this.lvSourceFolderList.Items.Count + "条数据";
//返回当前目录的子目录
DirectoryInfo[] dis = di.GetDirectories(); for (int j = 0; j < dis.Length; j++)
{
// Console.WriteLine("目录:" + dis[j].FullName);
ListFiles(dis[j]);//对于子目录,进行递归调用
} }

2)判断图片是否具有照片信息,我用的是MetadataExtractor,直接在nuget里面添加安装好,再添加一个GetExifByMe即可。这里在调用GetExifByMe的时候,不是图片文件会报错,报错的我直接忽略,继续continue。

  //判断是否具有照片信息,报错即不是照片文件
GetMetaData.GetExifByMe(files[i].FullName);
        #region   通过metadata-extractor获取照片参数

        //参考文献
//官网: https://drewnoakes.com/code/exif/
//nuget 官网:https://www.nuget.org/
//nuget 使用: http://www.cnblogs.com/chsword/archive/2011/09/14/NuGet_Install_OperatePackage.html
//nuget MetadataExtractor: https://www.nuget.org/packages/MetadataExtractor/ /// <summary>通过MetadataExtractor获取照片参数
/// </summary>
/// <param name="imgPath">照片绝对路径</param>
/// <returns></returns>
public static Dictionary<string, string> GetExifByMe(string imgPath)
{
var rmd = ImageMetadataReader.ReadMetadata(imgPath); var rt = new Dictionary<string, string>();
foreach (var rd in rmd)
{
foreach (var tag in rd.Tags)
{
var temp = EngToChs(tag.Name);
if (temp == "其他")
{
continue;
}
if (!rt.ContainsKey(temp))
{
rt.Add(temp, tag.Description);
} }
}
return rt;
} /// <summary>筛选参数并将其名称转换为中文
/// </summary>
/// <param name="str">参数名称</param>
/// <returns>参数中文名</returns>
private static string EngToChs(string str)
{
var rt = "其他";
switch (str)
{
case "Exif Version":
rt = "Exif版本";
break;
case "Model":
rt = "相机型号";
break;
case "Lens Model":
rt = "镜头类型";
break;
case "File Name":
rt = "文件名";
break;
case "File Size":
rt = "文件大小";
break;
case "Date/Time":
rt = "拍摄时间";
break;
case "File Modified Date":
rt = "修改时间";
break;
case "Image Height":
rt = "照片高度";
break;
case "Image Width":
rt = "照片宽度";
break;
case "X Resolution":
rt = "水平分辨率";
break;
case "Y Resolution":
rt = "垂直分辨率";
break;
case "Color Space":
rt = "色彩空间";
break; case "Shutter Speed Value":
rt = "快门速度";
break;
case "F-Number":
rt = "光圈";//Aperture Value也表示光圈
break;
case "ISO Speed Ratings":
rt = "ISO";
break;
case "Exposure Bias Value":
rt = "曝光补偿";
break;
case "Focal Length":
rt = "焦距";
break; case "Exposure Program":
rt = "曝光程序";
break;
case "Metering Mode":
rt = "测光模式";
break;
case "Flash Mode":
rt = "闪光灯";
break;
case "White Balance Mode":
rt = "白平衡";
break;
case "Exposure Mode":
rt = "曝光模式";
break;
case "Continuous Drive Mode":
rt = "驱动模式";
break;
case "Focus Mode":
rt = "对焦模式";
break;
}
return rt;
} #endregion

文件浏览完毕后的截图:

3)文件全部浏览完毕后,就开始进行压缩。

因为文件数量大,原来的简单压缩版本总是容易卡死,这里的新版本用了线程,就没有卡死的问题了。

这里的压缩核心代码直接参考了

用C#开发一个WinForm版的批量图片压缩工具

      Thread workThread = new Thread(new ThreadStart(CompressAll));
workThread.IsBackground = true;
workThread.Start();

我添加了i标识处理成功的文件数量,压缩失败的时候i-=1。


if (CompressPicture(item, fileName))
{
if (this.InvokeRequired)
{
this.Invoke(new DelegateWriteResult(WriteResult), new object[] { item, true });
}
else
{
this.WriteResult(item, true);
}
}
else
{
i -= 1; if (this.InvokeRequired)
{
this.Invoke(new DelegateWriteResult(WriteResult), new object[] { item, false });
}
else
{
this.WriteResult(item, false);
}
}

改变图片质量这里就是第二步思路,分两步走:

像素在1000以内的直接修改图片品质处理;

像素大于1000的按尺寸大小压缩图片,然后再修改图片品质处理。

        /// <summary>
/// 改变图片质量
/// </summary>
/// <param name="imgPath">文件路径</param>
/// <param name="imgName">文件名</param>
private static bool VaryQualityLevel(string imgPath, string imgName)
{ bool result = false; Bitmap bmp1 = new Bitmap(imgPath); //获取照片信息
// GetExifByMe(imgPath);
//先获取图片的像素
var imgPixl = RGB2Gray(bmp1); //像素超出,先压缩图片
if (imgPixl.Width > 1000 && imgPixl.Height > 1000)
{
double width = 0;
double height = 0;
if (imgPixl.Width > 2000 && imgPixl.Height > 2000)
{
width = System.Math.Ceiling(Convert.ToDouble(imgPixl.Width / 4));
height = System.Math.Ceiling(Convert.ToDouble(imgPixl.Height / 4));
}
else if (imgPixl.Width > 1000 && imgPixl.Height > 1000)
{
width = System.Math.Ceiling(Convert.ToDouble(imgPixl.Width / 2));
height = System.Math.Ceiling(Convert.ToDouble(imgPixl.Height / 2));
}
//cutimg先创建好
//检查是否存在文件夹
string subPath = @"d:/cutimg/";
if (false == System.IO.Directory.Exists(subPath))
{
//创建pic文件夹
System.IO.Directory.CreateDirectory(subPath);
}
result = FixSize(imgPath, Convert.ToInt32(width), Convert.ToInt32(height), subPath + imgName, imgName);
}
else
{ result = SetImgQuality(imgPath, imgPath, imgName); } return result; }

调用按图片尺寸压缩方法,先存储压缩后的图片,图片大小往往还超过1M。再设置图片的质量,二次处理,压缩后的图片大小小于200K。

        /// <summary> 按图片尺寸大小压缩图片</summary>
/// <param name="sourceFile">原始图片文件</param>
/// <param name="xWidth">图片width</param>
/// <param name="yWidth">图片height</param>
/// <param name="outputFile">输出文件名</param>
/// <param name="imgName">文件名</param>
/// <returns>成功返回true,失败则返回false</returns>
public static bool FixSize(string sourceFile, int xWidth, int yWidth, string outputFile, string imgName)
{
try
{
Bitmap sourceImage = new Bitmap(sourceFile); ImageCodecInfo myImageCodecInfo = GetEncoderInfo("image/jpeg"); Bitmap newImage = new Bitmap((int)(xWidth), (int)(yWidth)); Graphics g = Graphics.FromImage(newImage); g.DrawImage(sourceImage, 0, 0, xWidth, yWidth); sourceImage.Dispose(); g.Dispose(); newImage.Save(outputFile); //设置图片质量
SetImgQuality(sourceFile, outputFile, imgName); newImage.Dispose(); //删除该图片文件
File.Delete(outputFile); return true;
}
catch (Exception ex)
{
Logging.Error("FixSize:" + imgName + " 压缩出错:" + ex.Message);
return false;
}
}

调用按图片尺寸压缩的时候,这里发生“GDI+发生一般性错误”这个提示,原因是因为调用了SetImgQuality这个方法,文件还没有释放出来,在最后加上bmp1.Dispose();就解决了。

 //设置图片质量
SetImgQuality(sourceFile, outputFile, imgName);

在文件压缩出错的时候,我把出错的文件写入文本:


for (int j = 0; j < this.lvSourceFolderList.Items.Count; j++)
{
if (fileName == this.lvSourceFolderList.Items[j].Text)
{
//压缩失败的文件写入文本
using (StreamWriter my_writer = new StreamWriter(@"d:\CompressFailFile.txt", true, System.Text.Encoding.Default))
{
string txtstr = "压缩失败:" + fileName + "\r\n";
my_writer.Write(txtstr);
my_writer.Flush();
} this.lvSourceFolderList.Items[j].BackColor = SystemColors.ControlDark;
}
}

在这里出现“文件正由另一进程使用,该进程无法访问该文件”的错误提示,当时在本地上跑没有任何问题,放在服务器上跑就报错。后来把服务器上面的文件拿到本地测试,发现是这里出错了。换了using后完美解决。

压缩出错的文件除了在文本记录外,我还做了高亮显示。选中高亮数据的时候,因为无法复制,添加了SelectedIndexChanged事件以及文本框显示。

        /// <summary>
/// 选择行
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void lvSourceFolderList_SelectedIndexChanged(object sender, EventArgs e)
{ ListView.SelectedIndexCollection indexes = lvSourceFolderList.SelectedIndices;// string pr = ""; foreach (int index in indexes)
{
pr = lvSourceFolderList.Items[index].Text;
} this.lblChoose.Visible = true;
this.txtContent.Visible = true;
this.txtContent.Text = pr;// 显示选择的行的内容 }

最后,贴上我的源码。因为自己在做的过程中参考、借鉴了很多前辈的分享,我也把自己完整的代码分享出来。

源码 Github地址

参考资源

在做的过程中,我走了很多弯路,幸好在这个互联网发达的时代,在知识共享的时代,我有幸参考了各路前辈分享的资料,才得以完成这个任务。非常感谢以下前辈的分享,还有一个分享当时没有保存到链接,找不着了。无论如何,我心永存感激。

C#保存图片设置图片质量的方法

.net c#通过Exif获取图片信息(参数)

用C#开发一个WinForm版的批量图片压缩工具

C# Winform版批量压缩图片程序的更多相关文章

  1. 10 行 Python 代码,批量压缩图片 500 张,简直太强大了

    本文原创并首发于公众号[Python猫],未经授权,请勿转载. 原文地址:https://mp.weixin.qq.com/s/5hpFDgjCpfb0O1Jg-ycACw 熟悉 "Pyth ...

  2. 使用Python轻松批量压缩图片

    在互联网,图片的大小对一个网站的响应速度有着明显的影响,因此在提供用户预览的时候,图片往往是使用压缩后的.如果一个网站图片较多,一张张压缩显然很浪费时间.那么接下来,我就跟大家分享一个批量压缩图片的方 ...

  3. tinypng的python批量压缩图片功能

    tinypng网站提供的图片压缩功能很不错,但是直接在网站上压缩有限制,大量压缩图片时比较麻烦,还好官方提供了很多脚本的自动化压缩接口.下面简单说下python批量压缩步骤. 1.申请api key ...

  4. winform采集网站美女图片程序---多线程篇

    设定思路: 采集目标: http://www.8kmm.com,   已知网址列表(List保存),  应用多线程(Thread)读取该列表, 获取url时不能重复(加锁Lock). 允许无序采集! ...

  5. 用Photoshop软件实现批量压缩照片

    前提:手头有 "大" 照片,出于某种原因想把它变成 "小" 照片:电脑刚好安装有PS软件. 需知:如果您的压缩需求仅限于降低图片品质,降低图片像素,那么建议您采 ...

  6. java上传图片并压缩图片大小

    Thumbnailator 是一个优秀的图片处理的Google开源Java类库.处理效果远比Java API的好.从API提供现有的图像文件和图像对象的类中简化了处理过程,两三行代码就能够从现有图片生 ...

  7. 用C#开发一个WinForm版的批量图片压缩工具

    我们在实际项目开发过程中,曾经遇到过一个需求,就是要开发一个对大量图片进行整理(删除掉一些不符合要求的图片).归类(根据格式进行分类,比如jpg格式.bmp格式等).压缩(因为有的图片很大很占空间,看 ...

  8. Winform文件夹图片批量压缩整理修改

    效果图: 窗体设计器生成的代码: namespace ImageCompact { partial class MainForm { /// <summary> /// 必需的设计器变量. ...

  9. shell 批量压缩指定文件夹及子文件夹内图片

    shell 批量压缩指定文件夹及子文件夹内图片 用户上传的图片,一般都没有经过压缩,造成空间浪费.因此须要编写一个程序,查找文件夹及子文件夹的图片文件(jpg,gif,png),将大于某值的图片进行压 ...

随机推荐

  1. 资源-DotNet-站点:DotNet 站点列表

    ylbtech-资源-DotNet-站点:DotNet 站点列表 1.ASP.NET Web返回顶部 1.1.问卷星 https://www.wjx.cn/sample/service.aspx 1. ...

  2. 出席分布式事务Seata 1.0.0 GA典礼

    前言 图中那个红衣服的就是本人 什么是分布式事务 分布式事务就是指事务的参与者.支持事务的服务器.资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上. 简单的说,就是一次大的操作由不同的小 ...

  3. Oralce-PL/SQL编程-游标

    PL/SQL(Procedural Language/SQL)是Oracle在数据库中引入的一种过程化编程语言. PL/SQL块结构 声明部分 执行部分(必须的) 异常处理部分 [declare] - ...

  4. ubuntu 设置固定IP

    vim  /etc/network/interface address   要固定的IP地址 netmask  子网掩码  A类地址 默认255.0.0.0   B类地址默 255.255.0.0  ...

  5. chromedriver安装报错

     解决方法:   可以使用 npm init -f命令生成package.json,package.json中缺少的字段可以参照模板 package.json进行填充,package.json中的字段 ...

  6. django简单实现注册登录模块

    源码下载:https://files.cnblogs.com/files/hardykay/login.zip 新建项目(我使用pycharm开发,也可以使用如下命令建立项目 ) cmd命令行,前提需 ...

  7. urllib库爬取实例

    from urllib import request import random def spider(url): user_agent_list = [ "Mozilla/5.0 (Win ...

  8. unity碰撞检测(耗费性能)

    using System.Collections; using System.Collections.Generic; using UnityEngine; public class PengZhua ...

  9. js 对象 window,parent,top,opener,document

    Js 对象 window top parentWindow 当前html 页面Parent 当前html 页面的父页面Top 当前html页面的祖页面Window ==parent = top 当前页 ...

  10. 想实现网页滚动一定距离底部弹出div

    <script type="text/javascript"> $(window).scroll(function () { if ($(this).scrollTop ...