【问题标题】:Resize jpeg image to specified size将 jpeg 图像调整为指定大小
【发布时间】:2011-11-17 11:27:56
【问题描述】:

这是将图像缩小到指定较小尺寸的功能代码。但它有几个不好的地方:

  • 很慢
  • 它可以在获得缩放图像之前进行多次迭代
  • 每次它必须确定将整个图像加载到 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

【问题讨论】:

    标签: c# image file jpeg image-size


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      不要对每个图像进行一组缓慢的迭代,而是使用一些具有代表性的图像进行测试,并获得一个能够平均为您提供所需文件大小的分辨率。然后一直使用该分辨率。

      【讨论】:

      • 我做了类似于你的建议的事情。它有点工作,但有时,例如,目标大小为 250 kB,缩放后的图像为 440 kB(原始大小为 628 kB),因为图像中有更多细节或其他东西。那错误太多了。感谢您的建议。
      【解决方案3】:

      @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
      

      【讨论】:

      • 由于我的数学能力很差,我不知道从哪里开始。我将不胜感激您在数学上提供的任何帮助。谢谢!
      • 所以有两件事要做,对吧? 1)获取 Q80 的压缩比(我在示例中使用的质量值)。我不确定你做了什么数学来推断这一点。 2) (通过循环?数学?)基于压缩值获取 W 和 H,这将输出 24 bpp 位图的目标字节大小
      • @Jbobbins:正确。 Q80的压缩比是通过外推计算的;没有确切的公式,因为 JPEG 压缩会因图片亮度等因素而异;尽管如此,您应该能够运行多个测试并计算出一个非常合理的平均值。
      • 嗯。这是我陷入困境的外推部分。当绘制图表时(您的维基百科数据或我使用的一些测试数据),它显示出指数曲线,所以不知道如何在曲线上找到该点。
      【解决方案4】:

      我对这个问题的解决方案是降低质量,直到达到所需的大小。以下是我的后代解决方案。

      注意:这可以通过做一些猜测来改善。

      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;
          }
      }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-05-24
        • 2016-02-11
        • 1970-01-01
        • 2013-08-12
        • 2012-07-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多