C#中基于GDI+(Graphics)图像处理系列之图片压缩优化

简介

笔者刚开始接触计算机时是在学校里,学校的网速你懂的,学校局域网里能有5MB/s,而访问学校以外的网站时能有256KB/s就相当满意了。那时候笔者在开发网站时,处理图片时就特别小心,能用gif的不用jpeg,而且反复的优化。
随着技术的进步、网络设备的不断更新,现在大家家用的光纤宽带基本上都是10MB/s以上了,但是图片优化的工作还是得继续做,因为压力转移到服务器一边,对于一个访问量超级大的Web系统而言,网络带宽是仍然是非常珍贵的,即使是某东、某宝,给用户看到的图片基本上都优化到了极致。然而,在实际业务中,系统后台操作人员并不都具备图片优化的技术能力,可能几MB的bmp或者png的原图就直接上传到后台了,笔者见过某个商品的详情页的图片累计有6MB的(因为只能限制单张图片大小,业务,业务人员上传了好几张图片),因此需要在后台直接实现图片的压缩优化。
本文将重点向大家介绍怎么使用GDI+(Graphics)对图片进行压缩优化。

图片压缩优化

对图片进行压缩优化过程中生成高质量图片的原理与缩略图一样,可以参考
C#中基于GDI+(Graphics)图像处理系列之高质量缩略图
相对缩略图,不同点是生成图片宽度和高度的算法不一样和当图片大小不变化时算一下是否需要进行压缩优化,代码中有详细的注释,这里不再赘述。

主要有两种情况

限制生成图片的最大宽度和最大高度

这种情况可能出现在新闻内容中的图片、商品详情中的图片。主要是在批量的压缩优化时限制生成图片的最大宽度或者最大高度,如同时限制了最大宽度和最大高度,进行计算对比,取小值;如果原图的宽和高都小于限定值,则计算平均每个像素所占文件大小,看是否需要进行优化。代码如下:

/// 
/// 对图片进行压缩优化(限制宽高),始终保持原宽高比
/// 
/// 目标保存路径
/// 源文件路径
/// 压缩后的图片宽度不大于这值,如果为0,表示不限制宽度
/// 压缩后的图片高度不大于这值,如果为0,表示不限制高度
/// 1~100整数,无效值则取默认值95
/// 如 image/jpeg
public bool GetCompressImage(string destPath, string srcPath, int maxWidth, int maxHeight, int quality, out string error,string mimeType = "image/jpeg")
{
     bool retVal = false;
     error = string.Empty;
     //宽高不能小于0
     if (maxWidth < 0 || maxHeight < 0)
     {
          error = "目标宽高不能小于0";
          return retVal;
     }            
     Image srcImage = null;
     Image destImage = null;
     Graphics graphics = null;
     try
     {
          //获取源图像
          srcImage = Image.FromFile(srcPath, false);
          FileInfo fileInfo = new FileInfo(srcPath);
          //目标宽度
          var destWidth = srcImage.Width;
          //目标高度
          var destHeight = srcImage.Height;
          //如果输入的最大宽为0,则不限制宽度
          //如果不为0,且原图宽度大于该值,则附值为最大宽度
          if (maxWidth != 0 && destWidth > maxWidth)
          {
               destWidth = maxWidth;
          }
          //如果输入的最大宽为0,则不限制宽度
          //如果不为0,且原图高度大于该值,则附值为最大高度
          if (maxHeight != 0 && destHeight > maxHeight)
          {
               destHeight = maxHeight;
          }
          float srcD = (float)srcImage.Height / srcImage.Width;
          float destD = (float)destHeight / destWidth;
          //目的高宽比 大于 原高宽比 即目的高偏大,因此按照原比例计算目的高度  
          if (destD > srcD)
          {
              destHeight = Convert.ToInt32(destWidth * srcD);
          }
          else if (destD < srcD) //目的高宽比 小于 原高宽比 即目的宽偏大,因此按照原比例计算目的宽度  
          {
               destWidth = Convert.ToInt32(destHeight / srcD);
          }
          //如果维持原宽高,则判断是否需要优化
          if (destWidth == srcImage.Width && destHeight == srcImage.Height && fileInfo.Length < destWidth * destHeight * sizePerPx)
          {
               error = "图片不需要压缩优化";
               return retVal;
          }
          //定义画布
          destImage = new Bitmap(destWidth, destHeight);
          //获取高清Graphics
          graphics = GetGraphics(destImage);
          //将源图像画到画布上,注意最后一个参数GraphicsUnit.Pixel
          graphics.DrawImage(srcImage, new Rectangle(0, 0, destWidth, destHeight), new Rectangle(0, 0, srcImage.Width, srcImage.Height), GraphicsUnit.Pixel);
          //如果是覆盖则先释放源资源
          if (destPath == srcPath)
          {
               srcImage.Dispose();
          }
          //保存到文件,同时进一步控制质量
          SaveImage2File(destPath, destImage, quality, mimeType);
          retVal = true;

    }
    catch (Exception ex)
    {
          error = ex.Message;
    }
    finally
    {
         if (srcImage != null)
              srcImage.Dispose();
         if (destImage != null)
              destImage.Dispose();
         if (graphics != null)
              graphics.Dispose();
    }
    return retVal;
}

限制生成图片长边的最大值

这种情况可能出现在对相片进行压缩优化,主要是在进行批量压缩优化时限制生成图片的最长边,有可能是宽也有可能是高(相片有的宽大于高,有的宽小于高);如果原图的宽和高都小于限定值,则计算平均每个像素所占文件大小,看是否需要进行优化。代码如下:

/// 
/// 对图片进行压缩优化,始终保持原宽高比,限制长边长度,常用场景:相片
/// 
/// 目标保存路径
/// 源文件路径
/// 压缩后的图片边(宽或者高)长变不大于这值,为0表示不限制  
/// 1~100整数,无效值,则取默认值95
/// 如 image/jpeg
public bool GetCompressImage(string destPath, string srcPath, int maxLength, int quality, out string error, string mimeType = "image/jpeg")
{
     bool retVal = false;
     error = string.Empty;
     //最大边长不能小于0
     if (maxLength < 0)
     {
          error = "最大边长不能小于0";
          return retVal;
     }            
     Image srcImage = null;
     Image destImage = null;
     Graphics graphics = null;
     try
     {
          //获取源图像
          srcImage = Image.FromFile(srcPath, false);
          FileInfo fileInfo = new FileInfo(srcPath);
          //目标宽度
          var destWidth = srcImage.Width;
          //目标高度
          var destHeight = srcImage.Height;
          //如果限制
          if (maxLength > 0)
          {
               //原高宽比
               float srcD = (float)srcImage.Height / srcImage.Width;
               //如果宽>高,且大于 限制
               if (destWidth > destHeight && destWidth > maxLength)
               {
                    destWidth = maxLength;
                    destHeight = Convert.ToInt32(destWidth * srcD);
               }
               else
               {
                    if (destHeight > maxLength)
                    {
                        destHeight = maxLength;
                        destWidth = Convert.ToInt32(destHeight / srcD);
                    }
               }
         }
         //如果维持原宽高,则判断是否需要优化
         if (destWidth == srcImage.Width && destHeight == srcImage.Height && fileInfo.Length < destWidth * destHeight * sizePerPx)
         {
              error = "图片不需要压缩优化";
              return retVal;
         }
         //定义画布
         destImage = new Bitmap(destWidth, destHeight);
         //获取高清Graphics
         graphics = GetGraphics(destImage);
         //将源图像画到画布上,注意最后一个参数GraphicsUnit.Pixel
         graphics.DrawImage(srcImage, new Rectangle(0, 0, destWidth, destHeight), new Rectangle(0, 0, srcImage.Width, srcImage.Height), GraphicsUnit.Pixel);
         //如果是覆盖则先释放源资源
         if (destPath == srcPath)
         {
              srcImage.Dispose();
         }
         //保存到文件,同时进一步控制质量
         SaveImage2File(destPath, destImage, quality, mimeType);
         retVal = true;
   }
   catch (Exception ex)
   {
        error = ex.Message;
   }
   finally
   {
         if (srcImage != null)
               srcImage.Dispose();
         if (destImage != null)
               destImage.Dispose();
         if (graphics != null)
               graphics.Dispose();
    }
    return retVal;
}

其他需要的代码

下面的代码在本系列文章中反复使用,建议去《C#中基于GDI+(Graphics)图像处理系列之前言》获取整个图像处理工具类的源码

//优化良好的图片每个像素平均占用文件大小,经验值,可根据需要修改
private static readonly double sizePerPx = 0.18;
/// 
 /// 获取高清的Graphics
 /// 
 /// 
 /// 
 public Graphics GetGraphics(Image img)
 {
      var g = Graphics.FromImage(img);
      //设置质量
      g.SmoothingMode = SmoothingMode.HighQuality;
      g.CompositingQuality = CompositingQuality.HighQuality;
      //InterpolationMode不能使用High或者HighQualityBicubic,如果是灰色或者部分浅色的图像是会在边缘处出一白色透明的线
      //用HighQualityBilinear却会使图片比其他两种模式模糊(需要肉眼仔细对比才可以看出)
      g.InterpolationMode = InterpolationMode.Default;
      g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
      return g;
}
/// 
/// 将Image实例保存到文件,注意此方法不执行 img.Dispose()
/// 图片保存时本可以直接使用destImage.Save(path, ImageFormat.Jpeg),但是这种方法无法进行进一步控制图片质量
/// 
/// 
/// 
/// 1~100整数,无效值,则取默认值95
/// 
public void SaveImage2File(string path, Image destImage, int quality, string mimeType = "image/jpeg")
{
     if (quality <= 0 || quality > 100) quality = 95;
     //创建保存的文件夹
     FileInfo fileInfo = new FileInfo(path);
     if (!Directory.Exists(fileInfo.DirectoryName))
     {
          Directory.CreateDirectory(fileInfo.DirectoryName);
     }
     //设置保存参数,保存参数里进一步控制质量
     EncoderParameters encoderParams = new EncoderParameters();
     long[] qua = new long[] { quality };           
     EncoderParameter encoderParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
     encoderParams.Param[0] = encoderParam;
     //获取指定mimeType的mimeType的ImageCodecInfo
     var codecInfo = ImageCodecInfo.GetImageEncoders().FirstOrDefault(ici => ici.MimeType == mimeType);
     destImage.Save(path, codecInfo, encoderParams);            
}

完整示例程序源码

http://download.csdn.net/detail/lhtzbj12/9730116

示例程序截图

C#中基于GDI+(Graphics)图像处理系列之图片压缩优化_第1张图片
如果想查阅本系列其他文章,请移步《C#中基于GDI+(Graphics)图像处理系列之前言》

你可能感兴趣的:(C#,.Net)