2011-09-15 45 views
5

这是功能性代码,可将图像缩小至指定的较小尺寸。但它也有几件事情是不擅长:将jpeg图像调整为指定大小

  • 它的速度慢
  • 可以得到缩放后的图像每次必须确定它有整个图像加载到大小时
  • 前做几次迭代一个memoryStream

我想改进它。有没有办法得到一个更好的初步估计,以排除如此多的迭代?我是否全部错了?我创建它的原因是接受任何未知尺寸的图像并将其缩放到一定的尺寸。这将允许更好地规划存储需求。当您缩放到某个高度/宽度时,图像尺寸可能会因我们的需要而变化太多。

您将需要System.Drawing的引用。

//Scale down the image till it fits the given file size. 
    public static Image ScaleDownToKb(Image img, long targetKilobytes, long quality) 
    { 
     //DateTime start = DateTime.Now; 
     //DateTime end; 

     float h, w; 
     float halfFactor = 100; // halves itself each iteration 
     float testPerc = 100; 
     var direction = -1; 
     long lastSize = 0; 
     var iteration = 0; 
     var origH = img.Height; 
     var origW = img.Width; 

     // if already below target, just return the image 
     var size = GetImageFileSizeBytes(img, 250000, quality); 
     if (size < targetKilobytes * 1024) 
     { 
      //end = DateTime.Now; 
      //Console.WriteLine("================ DONE. ITERATIONS: " + iteration + " " + end.Subtract(start)); 
      return img; 
     } 

     while (true) 
     { 
      iteration++; 

      halfFactor /= 2; 
      testPerc += halfFactor * direction; 

      h = origH * testPerc/100; 
      w = origW * testPerc/100; 

      var test = ScaleImage(img, (int)w, (int)h); 
      size = GetImageFileSizeBytes(test, 50000, quality); 

      var byteTarg = targetKilobytes * 1024; 
      //Console.WriteLine(iteration + ": " + halfFactor + "% (" + testPerc + ") " + size + " " + byteTarg); 

      if ((Math.Abs(byteTarg - size)/(double)byteTarg) < .1 || size == lastSize || iteration > 15 /* safety measure */) 
      { 
       //end = DateTime.Now; 
       //Console.WriteLine("================ DONE. ITERATIONS: " + iteration + " " + end.Subtract(start)); 
       return test; 
      } 

      if (size > targetKilobytes * 1024) 
      { 
       direction = -1; 
      } 
      else 
      { 
       direction = 1; 
      } 

      lastSize = size; 
     } 
    } 

    public static long GetImageFileSizeBytes(Image image, int estimatedSize, long quality) 
    { 
     long jpegByteSize; 
     using (var ms = new MemoryStream(estimatedSize)) 
     { 
      SaveJpeg(image, ms, quality); 
      jpegByteSize = ms.Length; 
     } 
     return jpegByteSize; 
    } 

    public static void SaveJpeg(Image image, MemoryStream ms, long quality) 
    { 
     ((Bitmap)image).Save(ms, FindEncoder(ImageFormat.Jpeg), GetEncoderParams(quality)); 
    } 

    public static void SaveJpeg(Image image, string filename, long quality) 
    { 
     ((Bitmap)image).Save(filename, FindEncoder(ImageFormat.Jpeg), GetEncoderParams(quality)); 
    } 

    public static ImageCodecInfo FindEncoder(ImageFormat format) 
    { 

     if (format == null) 
      throw new ArgumentNullException("format"); 

     foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders()) 
     { 
      if (codec.FormatID.Equals(format.Guid)) 
      { 
       return codec; 
      } 
     } 

     return null; 
    } 

    public static EncoderParameters GetEncoderParams(long quality) 
    { 
     System.Drawing.Imaging.Encoder encoder = System.Drawing.Imaging.Encoder.Quality; 
     //Encoder encoder = new Encoder(ImageFormat.Jpeg.Guid); 
     EncoderParameters eparams = new EncoderParameters(1); 
     EncoderParameter eparam = new EncoderParameter(encoder, quality); 
     eparams.Param[0] = eparam; 
     return eparams; 
    } 

    //Scale an image to a given width and height. 
    public static Image ScaleImage(Image img, int outW, int outH) 
    { 
     Bitmap outImg = new Bitmap(outW, outH, img.PixelFormat); 
     outImg.SetResolution(img.HorizontalResolution, img.VerticalResolution); 
     Graphics graphics = Graphics.FromImage(outImg); 
     graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; 
     graphics.DrawImage(img, new Rectangle(0, 0, outW, outH), new Rectangle(0, 0, img.Width, img.Height), GraphicsUnit.Pixel); 
     graphics.Dispose(); 

     return outImg; 
    } 

调用这将创建一个第二图象是接近大小要求值:

 var image = Image.FromFile(@"C:\Temp\test.jpg"); 
     var scaled = ScaleDownToKb(image, 250, 80); 
     SaveJpeg(scaled, @"C:\Temp\test_REDUCED.jpg", 80); 

对于这个具体的例子:

  • 原始文件大小:628 KB
  • 请求文件大小:250 kB
  • 缩放后的文件大小:238 kB

回答

0

而不是做一组慢迭代每个图像的,做了一些有代表性的图像测试,并获得一项决议,给你想要的文件的大小平均。然后始终使用该分辨率。

+0

我做了类似的建议什么的。这样的工作,但后来有次在那里,例如,目标规模为250 KB和缩放图像为440 KB(原稿尺寸628 KB)因为有图像或什么的更多细节。这太错误了。感谢您的建议。 – jbobbins

1

我认为你可以假设文件大小的线性增长(和减少),这取决于像素数量的增长。这意味着,例如,如果您拥有500x500 200 kb的图像,并且您需要50 kb的图像,则应该将图像尺寸减小到250x250(像素数减少4倍)。我相信这应该会在大多数时间通过一次迭代获得想要的图像。但是,通过引入一些风险百分比(如10%)来降低比率或者类似的东西,您可以进一步调整它。

0

@jbobbins:我同意@xpda,如果第一次尝试将图像调整到目标大小与阈值距离太远,则可以再次重复该步骤,或者简单地回退到先前的有效算法。它将比当前的实施更快地收敛。正如你现在所做的那样,整个事情应该在O(1)而不是O(log n)中执行。

你可以采样一些JPEG压缩比率和排序从实验(我知道它不会是完美的,但足够接近)构建一个表,这将给你一个非常好的近似值。例如(taken from Wikipedia):

Compression Ratio   Quality 
    2.6:1     100 
     15:1      50 
     23:1      25 
     46:1      10 
+0

谢谢伊卡洛斯。我会试试这个。 – jbobbins

+0

随着我可怜的数学技能我不知道从哪里开始。我希望你能在数学上提供任何帮助。谢谢! – jbobbins

+0

所以有2件事要做,对吧? 1)获得Q80的压缩比率(我在本例中使用的质量值)。我不确定你做了什么数学推断。 ?2)(通过循环基于压缩值数学)得到的W和H将输出目标字节大小为24 BPP位图 – jbobbins

0

我的解决这个问题是降低质量,直至期望的大小为止。以下是我后代的解决方案。

注:这可以通过做某种猜测来改善。

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.IO; 
using System.Drawing; 
using System.Drawing.Imaging; 
using System.Drawing.Drawing2D; 

namespace PhotoShrinker 
{ 
    class Program 
    { 
    /// <summary> 
    /// Max photo size in bytes 
    /// </summary> 
    const long MAX_PHOTO_SIZE = 409600; 

    static void Main(string[] args) 
    { 
     var photos = Directory.EnumerateFiles(Directory.GetCurrentDirectory(), "*.jpg"); 

     foreach (var photo in photos) 
     { 
      var photoName = Path.GetFileNameWithoutExtension(photo); 

      var fi = new FileInfo(photo); 
      Console.WriteLine("Photo: " + photo); 
      Console.WriteLine(fi.Length); 

      if (fi.Length > MAX_PHOTO_SIZE) 
      { 
       using (var stream = DownscaleImage(Image.FromFile(photo))) 
       { 
        using (var file = File.Create(photoName + "-smaller.jpg")) 
        { 
         stream.CopyTo(file); 
        } 
       } 
       Console.WriteLine("Done."); 
      } 
      Console.ReadLine(); 
     } 

    } 

    private static MemoryStream DownscaleImage(Image photo) 
    { 
     MemoryStream resizedPhotoStream = new MemoryStream(); 

     long resizedSize = 0; 
     var quality = 93; 
     //long lastSizeDifference = 0; 
     do 
     { 
      resizedPhotoStream.SetLength(0); 

      EncoderParameters eps = new EncoderParameters(1); 
      eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)quality); 
      ImageCodecInfo ici = GetEncoderInfo("image/jpeg"); 

      photo.Save(resizedPhotoStream, ici, eps); 
      resizedSize = resizedPhotoStream.Length; 

      //long sizeDifference = resizedSize - MAX_PHOTO_SIZE; 
      //Console.WriteLine(resizedSize + "(" + sizeDifference + " " + (lastSizeDifference - sizeDifference) + ")"); 
      //lastSizeDifference = sizeDifference; 
      quality--; 

     } while (resizedSize > MAX_PHOTO_SIZE); 

     resizedPhotoStream.Seek(0, SeekOrigin.Begin); 

     return resizedPhotoStream; 
    } 

    private static ImageCodecInfo GetEncoderInfo(String mimeType) 
    { 
     int j; 
     ImageCodecInfo[] encoders; 
     encoders = ImageCodecInfo.GetImageEncoders(); 
     for (j = 0; j < encoders.Length; ++j) 
     { 
      if (encoders[j].MimeType == mimeType) 
       return encoders[j]; 
     } 
     return null; 
    } 
} 
}