2011-09-15 17 views
5

यह छवि को एक निर्दिष्ट छोटे आकार में कम करने के लिए कार्यात्मक कोड है।निर्दिष्ट आकार में जेपीईजी छवि का आकार बदलें

  • यह धीमी है
  • यह आकार बदली गई चित्र
  • हर बार आकार में पूरी छवि लोड करने के लिए है निर्धारित करने के लिए है से पहले कई पुनरावृत्तियों कर सकते हैं: लेकिन यह कई चीजें हैं जो अच्छा नहीं कर रहे हैं एक मेमोरीस्ट्रीम

मैं इसे बेहतर बनाना चाहता हूं। क्या इतने सारे पुनरावृत्तियों को रोकने के लिए एक बेहतर प्रारंभिक अनुमान प्राप्त करने का कोई तरीका हो सकता है? क्या मैं इस सब गलत के बारे में जा रहा हूँ? इसे बनाने के मेरे कारण अज्ञात आकार की किसी भी छवि को स्वीकार करना और इसे एक निश्चित आकार में स्केल करना है। यह भंडारण आवश्यकताओं के लिए बेहतर योजना की अनुमति देगा। जब आप किसी निश्चित ऊंचाई/चौड़ाई तक स्केल करते हैं, तो छवि आकार हमारी आवश्यकताओं के लिए बहुत अधिक भिन्न हो सकता है।

आपको सिस्टम के लिए एक रेफरी की आवश्यकता होगी। ड्रॉइंग।

//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 केबी
  • स्केल किए गए फ़ाइल का आकार: 238 केबी

उत्तर

0
इसके बजाय प्रत्येक छवि के लिए पुनरावृत्तियों की एक धीमी गति से सेट करने का

, एक नंबर प्रतिनिधि छवियों के साथ एक परीक्षण करते हैं और एक प्रस्ताव है कि आप पर औसत वांछित फ़ाइल आकार दे देंगे मिलता है। फिर हर समय उस संकल्प का उपयोग करें।

+0

मैंने आपके सुझाव के समान कुछ किया। यह काम करता है, लेकिन फिर ऐसे समय होते हैं, उदाहरण के लिए, लक्ष्य आकार 250 केबी था और स्केल की गई छवि 440 केबी (मूल आकार 628 केबी) थी क्योंकि छवि या कुछ में अधिक जानकारी थी। यह बहुत अधिक त्रुटि है। सलाह के लिये धन्यवाद। – jbobbins

1

मुझे लगता है कि आप पिक्सेल गिनती वृद्धि के आधार पर फ़ाइल आकार के रैखिक विकास (और घटाने) को ग्रहण कर सकते हैं। मतलब, यदि, उदाहरण के लिए, आपके पास 500x500 200 केबी छवि है और आपको 50 केबी छवि की आवश्यकता है, तो आपको छवि आयामों को 250x250 (4 गुना कम पिक्सेल) में कम करना चाहिए। मुझे विश्वास है कि यह आपको वांछित छवि को अधिकतर समय में एक पुनरावृत्ति के साथ प्राप्त कर लेना चाहिए। लेकिन आप अनुपात को कम करने या उस तरह कुछ करने के लिए कुछ जोखिम प्रतिशत (जैसे 10%) पेश करके इसे और भी आगे बढ़ा सकते हैं।

0

@jbobbins: मैं @xpda से सहमत हूं, यदि लक्ष्य आकार में छवि का आकार बदलने का पहला प्रयास थ्रेसहोल्ड से बहुत दूर है, तो आप चरण एक बार फिर से दोहरा सकते हैं या बस अपने पिछले अनावश्यक एल्गोरिदम पर वापस आ सकते हैं । यह आपके वर्तमान कार्यान्वयन से बहुत तेज़ होगा। पूरी चीज ओ (लॉग एन) के बजाय ओ (1) में निष्पादित की जानी चाहिए, जैसा कि आप अभी कर रहे हैं।

आप कुछ जेपीईजी संपीड़न अनुपात का नमूना दे सकते हैं और प्रयोग से एक टेबल का निर्माण कर सकते हैं (मुझे पता है कि यह सही नहीं होगा, लेकिन पर्याप्त बंद होगा) जो आपको बहुत अच्छा अनुमान देगा। उदाहरण (taken from Wikipedia) के लिए:

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

धन्यवाद Icarus। मैं कोशिश करूँगा – jbobbins

+0

मेरे गरीब गणित कौशल के साथ मुझे यकीन नहीं है कि कहां से शुरू करना है। मैं गणित के बाहर काम करने पर आपको जो भी मदद दे सकता हूं उसकी सराहना करता हूं। धन्यवाद! – jbobbins

+0

तो 2 चीजें करने के लिए सही हैं, है ना? 1) Q80 के लिए संपीड़न अनुपात प्राप्त करें (गुणवत्ता मान मैं अपने उदाहरण में उपयोग कर रहा हूँ)। मुझे यकीन नहीं है कि आप इसे निकालने के लिए क्या गणित करते हैं। 2) (लूपिंग द्वारा? गणित?) संपीड़न मूल्य के आधार पर डब्ल्यू और एच प्राप्त करता है जो 24 बीपीपी बिटमैप – 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; 
    } 
} 
} 
संबंधित मुद्दे