2015-05-05 15 views
10

Tôi đang cố gắng để có được kích thước từ một bức tranh trực tiếp từ trang web sử dụng mã này:Nhận kích thước hình ảnh trực tiếp từ URL trong C#

string image = @"http://www.hephaestusproject.com/blog/wp-content/uploads/2014/01/csharp3.png"; 
byte[] imageData = new WebClient().DownloadData(image); 
MemoryStream imgStream = new MemoryStream(imageData); 
Image img = Image.FromStream(imgStream); 

int wSize = img.Width; 
int hSize = img.Height; 

Nó hoạt động nhưng hiệu suất là khủng khiếp vì tôi cần phải tải nhiều hình ảnh chỉ để có được kích thước của chúng. Có cách nào tốt hơn để làm điều tương tự không?

+2

không. không thực sự. bạn cần hình ảnh để biết kích thước của nó. bạn MIGHT có thể mở PART của luồng, tùy thuộc vào loại tệp và phân tích cú pháp dữ liệu meta trực tiếp .... nhưng tôi nghi ngờ điều đó sẽ phù hợp với những gì bạn cần; và nói chung, khi yêu cầu web được gửi đi, bạn sẽ nhận được toàn bộ hình ảnh trong một lần. – hometoast

Trả lời

6

Trong một từ, không.

Trong một vài từ nữa, bạn sẽ phải dựa vào đó là tài nguyên chứa chi tiết về kích thước hình ảnh trên một máy chủ nhất định. Trong 99,99% trường hợp sẽ không tồn tại.

4

Trong trường hợp điều này hữu ích cho những người đến sau này, có vẻ như điều này thực sự có thể xảy ra. Một đánh giá ngắn gọn về định dạng hình ảnh JPG, PNG và GIF cho thấy rằng tất cả chúng thường có một tiêu đề ở đầu tệp chứa kích thước hình ảnh.

Reddit sử dụng thuật toán để tải xuống các khối liên tiếp 1024 byte để xác định kích thước hình ảnh mà không cần tải xuống toàn bộ hình ảnh. Mã này bằng Python nhưng nó nằm trong phương thức _fetch_image_size tại đây: https://github.com/reddit/reddit/blob/35c82a0a0b24441986bdb4ad02f3c8bb0a05de57/r2/r2/lib/media.py#L634

Nó sử dụng một trình phân tích cú pháp riêng trong lớp ImageFile của họ và cố gắng phân tích hình ảnh liên tiếp và lấy kích thước khi tải nhiều byte hơn. Tôi đã dịch một cách lỏng lẻo này sang C# trong mã bên dưới, tận dụng rất nhiều mã phân tích hình ảnh tại số https://stackoverflow.com/a/112711/3838199.

Có thể có một số trường hợp truy xuất toàn bộ tệp là cần thiết nhưng tôi nghi ngờ áp dụng cho một tập hợp nhỏ các ảnh JPEG (có lẽ là hình ảnh tiến bộ). Trong thử nghiệm bình thường của tôi có vẻ như hầu hết các kích thước hình ảnh được truy xuất thông qua truy xuất 1024 byte đầu tiên; thực sự kích thước đoạn này có thể nhỏ hơn.

using System; 
using System.Collections.Generic; 
using System.Drawing; // note: add reference to System.Drawing assembly 
using System.IO; 
using System.Linq; 
using System.Net.Http; 
using System.Net.Http.Headers; 

namespace Utilities 
{ 
    // largely credited to https://stackoverflow.com/a/112711/3838199 for the image-specific code 
    public static class ImageUtilities 
    { 
     private const string ErrorMessage = "Could not read image data"; 
     private const int ChunkSize = 1024; 
     private static readonly Dictionary<byte[], Func<BinaryReader, Size>> ImageFormatDecoders = new Dictionary<byte[], Func<BinaryReader, Size>>() 
     { 
      { new byte[]{ 0x42, 0x4D }, DecodeBitmap}, 
      { new byte[]{ 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif }, 
      { new byte[]{ 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif }, 
      { new byte[]{ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, DecodePng }, 
      { new byte[]{ 0xff, 0xd8 }, DecodeJfif }, 
     }; 

     /// <summary> 
     /// Retrieve the dimensions of an online image, downloading as little as possible 
     /// </summary> 
     public static Size GetWebDimensions(Uri uri) 
     { 
      var moreBytes = true; 
      var currentStart = 0; 
      byte[] allBytes = { }; 

      while (moreBytes) 
      { 
       try 
       { 
        var newBytes = GetSomeBytes(uri, currentStart, currentStart + ChunkSize - 1); 
        if (newBytes.Length < ChunkSize) moreBytes = false; 
        allBytes = Combine(allBytes, newBytes); 
        return GetDimensions(new BinaryReader(new MemoryStream(allBytes))); 
       } 
       catch 
       { 
        currentStart += ChunkSize; 
       } 
      } 

      return new Size(0, 0); 
     } 

     private static byte[] GetSomeBytes(Uri uri, int startRange, int endRange) 
     { 
      using (var client = new HttpClient()) 
      { 
       var request = new HttpRequestMessage { RequestUri = uri }; 
       request.Headers.Range = new RangeHeaderValue(startRange, endRange); 
       try 
       { 
        var response = client.SendAsync(request).Result; 
        return response.Content.ReadAsByteArrayAsync().Result; 
       } 
       catch { } 
      } 
      return new byte[] { }; 
     } 

     /// <summary> 
     /// Gets the dimensions of an image. 
     /// </summary> 
     /// <returns>The dimensions of the specified image.</returns> 
     /// <exception cref="ArgumentException">The image was of an unrecognized format.</exception>  
     public static Size GetDimensions(BinaryReader binaryReader) 
     { 
      int maxMagicBytesLength = ImageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length; 

      byte[] magicBytes = new byte[maxMagicBytesLength]; 

      for (int i = 0; i < maxMagicBytesLength; i += 1) 
      { 
       magicBytes[i] = binaryReader.ReadByte(); 

       foreach (var kvPair in ImageFormatDecoders) 
       { 
        if (magicBytes.StartsWith(kvPair.Key)) 
        { 
         return kvPair.Value(binaryReader); 
        } 
       } 
      } 

      throw new ArgumentException(ErrorMessage, nameof(binaryReader)); 
     } 

     // from https://stackoverflow.com/a/415839/3838199 
     private static byte[] Combine(byte[] first, byte[] second) 
     { 
      byte[] ret = new byte[first.Length + second.Length]; 
      Buffer.BlockCopy(first, 0, ret, 0, first.Length); 
      Buffer.BlockCopy(second, 0, ret, first.Length, second.Length); 
      return ret; 
     } 

     private static bool StartsWith(this byte[] thisBytes, byte[] thatBytes) 
     { 
      for (int i = 0; i < thatBytes.Length; i += 1) 
      { 
       if (thisBytes[i] != thatBytes[i]) 
       { 
        return false; 
       } 
      } 
      return true; 
     } 

     private static short ReadLittleEndianInt16(this BinaryReader binaryReader) 
     { 
      byte[] bytes = new byte[sizeof(short)]; 
      for (int i = 0; i < sizeof(short); i += 1) 
      { 
       bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte(); 
      } 
      return BitConverter.ToInt16(bytes, 0); 
     } 

     private static int ReadLittleEndianInt32(this BinaryReader binaryReader) 
     { 
      byte[] bytes = new byte[sizeof(int)]; 
      for (int i = 0; i < sizeof(int); i += 1) 
      { 
       bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte(); 
      } 
      return BitConverter.ToInt32(bytes, 0); 
     } 

     private static Size DecodeBitmap(BinaryReader binaryReader) 
     { 
      binaryReader.ReadBytes(16); 
      int width = binaryReader.ReadInt32(); 
      int height = binaryReader.ReadInt32(); 
      return new Size(width, height); 
     } 

     private static Size DecodeGif(BinaryReader binaryReader) 
     { 
      int width = binaryReader.ReadInt16(); 
      int height = binaryReader.ReadInt16(); 
      return new Size(width, height); 
     } 

     private static Size DecodePng(BinaryReader binaryReader) 
     { 
      binaryReader.ReadBytes(8); 
      int width = binaryReader.ReadLittleEndianInt32(); 
      int height = binaryReader.ReadLittleEndianInt32(); 
      return new Size(width, height); 
     } 

     private static Size DecodeJfif(BinaryReader binaryReader) 
     { 
      while (binaryReader.ReadByte() == 0xff) 
      { 
       byte marker = binaryReader.ReadByte(); 
       short chunkLength = binaryReader.ReadLittleEndianInt16(); 

       if (marker == 0xc0 || marker == 0xc1 || marker == 0xc2) 
       { 
        binaryReader.ReadByte(); 

        int height = binaryReader.ReadLittleEndianInt16(); 
        int width = binaryReader.ReadLittleEndianInt16(); 
        return new Size(width, height); 
       } 

       binaryReader.ReadBytes(chunkLength - 2); 
      } 

      throw new ArgumentException(ErrorMessage); 
     } 
    } 
} 

Các xét nghiệm:

using System; 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using Utilities; 
using System.Drawing; 

namespace Utilities.Tests 
{ 
    [TestClass] 
    public class ImageUtilitiesTests 
    { 
     [TestMethod] 
     public void GetPngDimensionsTest() 
     { 
      string url = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"; 
      Uri uri = new Uri(url); 
      var actual = ImageUtilities.GetWebDimensions(uri); 
      Assert.AreEqual(new Size(272, 92), actual); 
     } 

     [TestMethod] 
     public void GetJpgDimensionsTest() 
     { 
      string url = "https://upload.wikimedia.org/wikipedia/commons/e/e0/JPEG_example_JPG_RIP_050.jpg"; 
      Uri uri = new Uri(url); 
      var actual = ImageUtilities.GetWebDimensions(uri); 
      Assert.AreEqual(new Size(313, 234), actual); 
     } 

     [TestMethod] 
     public void GetGifDimensionsTest() 
     { 
      string url = "https://upload.wikimedia.org/wikipedia/commons/a/a0/Sunflower_as_gif_websafe.gif"; 
      Uri uri = new Uri(url); 
      var actual = ImageUtilities.GetWebDimensions(uri); 
      Assert.AreEqual(new Size(250, 297), actual); 
     } 
    } 
} 
+0

Cảm ơn rất nhiều vì đoạn mã trên. Nó rất hữu ích. Tuy nhiên, trong các thử nghiệm của tôi, đoạn mã trên dường như trả về 0,0 dưới dạng chiều rộng và chiều cao cho tất cả hình ảnh png & GIF. Có thể có thêm các dấu hiệu cần được đưa vào? Nếu bạn cập nhật mã này hoặc biết về các điểm đánh dấu bổ sung cho png và GIF, bạn có thể đăng những mã này không? Cảm ơn –

+0

Như tôi đã đề cập, tôi đang tận dụng mã phân tích hình ảnh từ http://stackoverflow.com/a/112711/3838199 và có lẽ nó không hoạt động trong mọi trường hợp. Tuy nhiên tôi đã thêm các bài kiểm tra đơn vị của mình và mặc dù chúng được thừa nhận một cách triệt để nhưng chúng hiển thị các hình ảnh cụ thể trong đó thường trình này hoạt động hoàn hảo. Cũng lưu ý rằng mã này hiện đang được sử dụng trong một ứng dụng sản xuất lớn có kích thước lớn của hình ảnh trên hơn 800 triệu trang web. – karfus

Các vấn đề liên quan