画像形式の判定
北本です。
投稿の間隔が大きく空いてしまい申し訳ありません。
前回の内容は、WebClientでのサーバーから終わりのわからない連番のJPGファイルをダウンロードする際、404エラーで終わりを判定しようとすると、ファイルの非存在時にエラーページのHTMLにリダイレクトされる仕様になっているような場合にうまくいかないという話でした。今回はこの対策を考えます。
ここでは、ファイルの中身から画像ファイルであるかどうかを判定することにします。画像ファイルの先頭にはその形式を示すヘッダ情報が含まれていますので、その数バイト分を確認することで画像形式を判断することができます。例えば、JPEGの場合であれば先頭の2バイトが必ず「0xFF, 0xD8」になっています。
他の画像形式の例も挙げておきます。
形式 | 先頭 |
JPEG | 0xFF, 0xD8 |
PNG | 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A |
BMP | 0x42, 0x4D |
GIF | 0x47, 0x49, 0x46 |
では、以上を踏まえて前回のC#のコードを修正してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | using System.Collections.Generic; using System.IO; using System.Net; namespace ImageDownloader { class Program { static void Main(string[] args) { // 実行ファイルのディレクトリ配下のdownloadフォルダを保存先とする string saveFolder = @"./download"; if (!Directory.Exists(saveFolder)) { // フォルダが存在しなければ作成 Directory.CreateDirectory(saveFolder); } using (WebClient wc = new WebClient()) { string urlFormat = @"http://localhost:8080/test/test.php?image_name={0}_{1}.jpg"; int i = 1; // 親番 int j = 1; // 枝番 while (true) { while (true) { string url = string.Format(urlFormat, i, j); string fileName = string.Format(@"saved_image_{0}_{1}.jpg", i, j); string saveFilePath = Path.Combine(saveFolder, fileName); byte[] data; try { // ファイルのバイト列を取得 data = wc.DownloadData(url); } catch { // ダウンロードに失敗 break; } if (IsJpg(data)) { using (var fs = new FileStream(saveFilePath, FileMode.Create, FileAccess.Write)) { using (var bw = new BinaryWriter(fs)) { // ファイルを保存 bw.Write(data); } } } else { // JPG画像ではない break; } // 枝番を1つ進める j++; } if (j == 1) { // 枝番「1」が存在しなかった場合、親番の最後の数を超えたとみなし終了 break; } // 親番を1つ進め、枝番を1に戻す i++; j = 1; } } } /// <summary> /// 対象ファイルのバイト列の先頭がheaderに指定したバイト列に一致するか /// </summary> /// <param name="data">対象ファイルのバイト列</param> /// <param name="header">比較対象のヘッダのバイト列</param> /// <returns>一致すればtrue、そうでなければfalse</returns> static bool IsMatchFileHeader(byte[] data, List<byte> header) { if (data.Length < header.Count) { return false; } for (int i = 0; i < header.Count; i++) { if (data[i] != header[i]) { return false; } } return true; } /// <summary> /// JPGファイルであるか /// </summary> /// <param name="data">対象ファイルのバイト列</param> /// <returns>JPGであればtrue、そうでなければfalse</returns> static bool IsJpg(byte[] data) { List<byte> header = new List<byte> { 0xFF, 0xD8 }; return IsMatchFileHeader(data, header); } } } |
IsMatchFileHeader、IsJpgというメソッドを追加しています。IsJpgがファイルのバイト列を受け取ってJPG形式であるかを判定するメソッドです。IsMatchFileHeaderはIsJpgから呼び出しているメソッドで、引数dataに指定したバイト列の先頭が、引数headerに指定したバイト列に一致しているかを判定しています。
もし、PNG、BMP、GIFの判定をしたければ、以下のようなメソッドを作成すればOKです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | static bool IsPng (byte[] data) { List<byte> header = new List<byte> { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; return IsMatchFileHeader(data, header); } static bool IsGif(byte[] data) { List<byte> header = new List<byte> { 0x47, 0x49, 0x46 }; return IsMatchFileHeader(data, header); } static bool IsBmp(byte[] data) { List<byte> header = new List<byte> { 0x42, 0x4D }; return IsMatchFileHeader(data, header); } |
また、前回はWebClient.DownloadFileでURLから直接ファイルを保存していましたが、WebClient.DownloadDataでバイト列として受け取り、それを前述のIsJpgメソッドでJPGファイルであるかを判定し、そうである場合のみファイルを保存するように変更しています。
以上、今回は先頭バイトから画像形式を判定する方法について取り上げました。
WebClientでの画像ダウンロードに関する話題はこれで終わりとします。