2015-05-05 65 views
10

我試圖從圖片直接從網頁上使用此代碼獲取尺寸:直接從URL獲取圖像尺寸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; 

它的工作原理,但性能是可怕的,因爲我需要下載很多圖片只是爲了獲得他們的尺寸。有沒有更好的方法來做同樣的事情?

+2

沒有。不是真的。你需要圖像來知道它的大小。你可能會打開流的一部分,取決於文件類型,並直接解析元數據....但我懷疑這將適合你所需要的;一般來說,一旦網絡請求被髮送,你就會一次獲得整個圖像。 – hometoast

回答

6

總之,沒有。

還有一句話,您將不得不依賴於存在包含給定服務器上圖像尺寸細節的資源。 99.99%的案例根本不存在。

4

如果這對後來出現的人有幫助,看起來這確實是可能的。對JPG,PNG和GIF圖像格式的簡要回顧顯示,它們通常在包含圖像尺寸的文件開頭處都有一個標題。

Reddit使用一種算法來下載連續的1024字節塊來確定圖像尺寸而無需下載整個圖像。代碼使用Python,但在_fetch_image_size方法中:https://github.com/reddit/reddit/blob/35c82a0a0b24441986bdb4ad02f3c8bb0a05de57/r2/r2/lib/media.py#L634

它在其ImageFile類中使用單獨的解析器,並隨着嘗試解析圖像並檢索更多字節下載時的大小。我已經在下面的代碼中鬆散地將其轉換爲C#,並且大大利用了圖像解析代碼https://stackoverflow.com/a/112711/3838199

可能有些情況下需要檢索整個文件,但我懷疑這適用於相對較小的JPEG圖像子集(可能是逐行圖像)。在我的隨意測試中,似乎大多數圖像大小都是通過第一個1024字節的檢索來檢索的;事實上,這塊大小可能會更小。

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

測試:

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

非常感謝上面這段代碼。這非常有用。但是,在我的測試中,上面的代碼似乎將所有png&GIF圖像的寬度和高度都返回爲0,0。也許還有其他標記需要包含?如果您更新了此代碼或瞭解png和GIF的其他標記,您可以發佈這些代碼嗎?謝謝 –

+0

正如我所提到的,我利用http://stackoverflow.com/a/112711/3838199中的圖像解析代碼,也許它在所有情況下都不起作用。 但是我已經添加了我的單元測試,雖然他們顯然沒有詳盡無遺,但他們在這個例程完美工作的地方展示了特定的圖像。 另請注意,此代碼目前正在大型生產應用程序中使用,這些應用程序在超過8億個網站上刮取圖像大小。 – karfus