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

これを呼び出すと、要求された値に近いサイズの2番目の画像が作成されます。

        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 分の 1 ピクセル) に縮小する必要があります。ほとんどの場合、これにより、1回の反復で目的の画像が得られるはずです。しかし、これをさらに微調整することができます。これには、リスク パーセント (10% など) を削減率またはそのようなものに導入することによって可能です。

于 2011-09-15T04:52:33.887 に答える
0

@jbobbins: @xpda に同意します。画像をターゲット サイズにサイズ変更する最初の試行がしきい値から離れすぎている場合は、もう一度手順を繰り返すか、単純に以前の非効率なアルゴリズムに戻ることができます。現在の実装よりもはるかに高速に収束します。現在行っているように、すべてを O(log n) ではなく O(1) で実行する必要があります。

いくつかの 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 に答える
0

画像ごとにゆっくりとした一連の反復を行う代わりに、多数の代表的な画像でテストを行い、平均して目的のファイル サイズが得られる解像度を取得します。次に、その解像度を常に使用します。

于 2011-09-15T04:21:51.700 に答える