5

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

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

4 回答 4

1

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

于 2011-09-15T04:52:33.887 回答
0

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

于 2011-09-15T04:21:51.700 回答
0

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

您可以对一些 JPEG 压缩率进行采样,并通过实验构建一个表(我知道它不会完美,但足够接近),这将为您提供一个非常好的近似值。例如(取自维基百科):

Compression Ratio            Quality
     2.6:1                    100
      15:1                     50
      23:1                     25
      46:1                     10
于 2011-09-15T05:32:45.110 回答
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;
    }
}
}
于 2014-07-21T14:13:44.253 回答