2011-09-15 48 views
5

Đây là mã chức năng để giảm hình ảnh xuống một kích thước nhỏ hơn được chỉ định. Nhưng nó có một vài điều mà không phải là tốt:Thay đổi kích thước hình ảnh jpeg thành kích thước được chỉ định

  • nó chậm
  • nó có thể làm một vài lần lặp trước khi nhận được hình ảnh thu nhỏ
  • mỗi lần nó phải xác định kích thước nó có để tải toàn bộ hình ảnh vào a memoryStream

Tôi muốn cải thiện nó. Có thể có một số cách để có được ước tính ban đầu tốt hơn để loại trừ nhiều lần lặp lại không? Tôi đang đi về điều này tất cả sai? Lý do của tôi để tạo ra nó là chấp nhận bất kỳ hình ảnh nào có kích thước không xác định và chia tỷ lệ nó thành một kích thước nhất định. Điều này sẽ cho phép lập kế hoạch tốt hơn cho nhu cầu lưu trữ. Khi bạn mở rộng đến chiều cao/chiều rộng nhất định, kích thước hình ảnh có thể thay đổi quá nhiều cho nhu cầu của chúng tôi.

Bạn sẽ cần một tham chiếu đến 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; 
    } 

Calling này sẽ tạo ra một hình ảnh thứ 2 đó là gần với kích thước đến giá trị đề nghị:

 var image = Image.FromFile(@"C:\Temp\test.jpg"); 
     var scaled = ScaleDownToKb(image, 250, 80); 
     SaveJpeg(scaled, @"C:\Temp\test_REDUCED.jpg", 80); 

Ví dụ cụ thể này:

  • gốc kích thước: 628 kB
  • kích thước tệp được yêu cầu: 250 kB
  • kích thước tệp được chia tỷ lệ: 238 kB

Trả lời

0

Thay vì thực hiện một bộ lặp chậm cho mỗi ảnh, thực hiện kiểm tra với một số ảnh đại diện và có độ phân giải sẽ cho bạn kích thước tệp mong muốn trên mức trung bình. Sau đó sử dụng độ phân giải đó mọi lúc.

+0

Tôi đã làm điều gì đó tương tự như đề xuất của bạn. Đó là loại tác phẩm, nhưng sau đó có những lúc, ví dụ, kích thước mục tiêu là 250 kB và hình ảnh được chia tỷ lệ là 440 kB (kích thước orig 628 kB) vì có nhiều chi tiết hơn trong hình ảnh hoặc thứ gì đó. Đó là quá nhiều lỗi. Cám ơn vì sự gợi ý. – jbobbins

1

Tôi nghĩ bạn có thể giả định tăng trưởng tuyến tính (và giảm) kích thước tệp tùy thuộc vào số lượng pixel tăng trưởng. Có nghĩa là, ví dụ: nếu bạn có hình ảnh 500x500 200 kb và bạn cần hình ảnh 50 kb, bạn nên giảm kích thước hình ảnh xuống 250x250 (4 lần ít pixel hơn). Tôi tin rằng điều này sẽ giúp bạn có được một hình ảnh mong muốn với một lần lặp lại hầu hết thời gian. Nhưng bạn có thể tinh chỉnh điều này hơn nữa, bằng cách giới thiệu một số phần trăm rủi ro (như 10%) để giảm tỷ lệ hoặc điều gì đó tương tự.

0

@jbobbins: Tôi đồng ý với @xpda, nếu lần đầu tiên thay đổi kích thước hình ảnh thành kích thước mục tiêu quá xa ngưỡng, bạn có thể lặp lại bước một lần nữa hoặc đơn giản quay trở lại thuật toán inneficient trước đó của bạn . Nó sẽ hội tụ nhanh hơn rất nhiều so với thực hiện hiện tại của bạn. Toàn bộ điều nên được thực hiện trong O (1) thay vì O (log n), như bạn đang làm bây giờ.

Bạn có thể lấy mẫu một số tỷ lệ nén JPEG và sắp xếp một bảng từ thử nghiệm (tôi biết nó sẽ không hoàn hảo, nhưng đủ gần) sẽ cho bạn một phép tính xấp xỉ rất tốt. Ví dụ: (taken from Wikipedia):

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

nhờ Icarus. Tôi sẽ thử cái này. – jbobbins

+0

Với kỹ năng toán học kém, tôi không chắc nên bắt đầu từ đâu. Tôi sẽ đánh giá cao bất kỳ sự giúp đỡ nào bạn có thể đưa ra ở đó về việc làm việc này bằng toán học. Cảm ơn! – jbobbins

+0

vì vậy có 2 việc cần làm, phải không? 1) có được tỷ lệ nén cho Q80 (giá trị chất lượng tôi đang sử dụng trong ví dụ của tôi). Tôi không chắc bạn làm toán học gì để ngoại suy điều đó. 2) (by looping? Math?) Dựa trên giá trị nén nhận được W và H sẽ xuất ra kích thước byte đích cho bitmap 24 bpp – jbobbins

0

Giải pháp của tôi cho vấn đề này là giảm chất lượng cho đến khi đạt được kích thước mong muốn. Dưới đây là giải pháp của tôi cho hậu thế.

NB: Điều này có thể được cải thiện bằng cách thực hiện một số loại phỏng đoán.

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; 
    } 
} 
} 
Các vấn đề liên quan