将jpeg图像的大小调整为指定大小

时间:2011-09-15 00:11:40

标签: c# image file jpeg image-size

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

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

4 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:0)

不是对每个图像进行慢速迭代,而是使用数字代表性图像进行测试,并获得一个分辨率,以便为您提供平均所需的文件大小。然后一直使用该分辨率。

答案 2 :(得分:0)

@jbobbins:我同意@xpda,如果第一次尝试将图像调整到目标大小的距离太远,则可以再次重复该步骤,或者简单地回到之前的inneficient算法。它会比你当前的实现快得多。正如你现在所做的那样,整个事情应该在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

答案 3 :(得分: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;
    }
}
}