ファイルから画像をロードしていますが、ファイルから完全に読み取られる前に画像を検証する方法を知りたいです。
string filePath = "image.jpg";
Image newImage = Image.FromFile(filePath);
この問題は、image.jpgが実際にjpgではない場合に発生します。たとえば、空のテキストファイルを作成し、その名前をimage.jpgに変更すると、image.jpgが読み込まれたときにOutOfMemory例外がスローされます。
画像のストリームまたはファイルパスを指定して、画像を検証する関数を探しています。
関数プロトタイプの例
bool IsValidImage(string fileName);
bool IsValidImage(Stream imageStream);
JPEGには正式なヘッダー定義はありませんが、使用できる少量のメタデータがあります。
その後、いくつかのことがありますが、それらは重要ではありません。
バイナリストリームを使用してファイルを開き、この初期データを読み取り、OffSet 0が0で、OffSet 6が1,2または3であることを確認できます。
これにより、少なくともわずかに精度が上がります。
または、例外をトラップして先に進むこともできますが、私はあなたが挑戦したいと思っていました:)
これが私の画像チェックです。ファイル拡張子に頼ることはできず、自分でフォーマットを確認する必要があります。バイト配列からWPFのBitmapImagesをロードしていますが、フォーマットが事前にわかりません。 WPFは形式を正常に検出しますが、BitmapImageオブジェクトの画像形式を通知しません(少なくとも、このプロパティを認識していません)。そして、フォーマットを検出するためだけにSystem.Drawingで画像を再度ロードしたくありません。このソリューションは高速で、私にとってはうまく機能します。
public enum ImageFormat
{
bmp,
jpeg,
gif,
tiff,
png,
unknown
}
public static ImageFormat GetImageFormat(byte[] bytes)
{
// see http://www.mikekunz.com/image_file_header.html
var bmp = Encoding.ASCII.GetBytes("BM"); // BMP
var gif = Encoding.ASCII.GetBytes("GIF"); // GIF
var png = new byte[] { 137, 80, 78, 71 }; // PNG
var tiff = new byte[] { 73, 73, 42 }; // TIFF
var tiff2 = new byte[] { 77, 77, 42 }; // TIFF
var jpeg = new byte[] { 255, 216, 255, 224 }; // jpeg
var jpeg2 = new byte[] { 255, 216, 255, 225 }; // jpeg Canon
if (bmp.SequenceEqual(bytes.Take(bmp.Length)))
return ImageFormat.bmp;
if (gif.SequenceEqual(bytes.Take(gif.Length)))
return ImageFormat.gif;
if (png.SequenceEqual(bytes.Take(png.Length)))
return ImageFormat.png;
if (tiff.SequenceEqual(bytes.Take(tiff.Length)))
return ImageFormat.tiff;
if (tiff2.SequenceEqual(bytes.Take(tiff2.Length)))
return ImageFormat.tiff;
if (jpeg.SequenceEqual(bytes.Take(jpeg.Length)))
return ImageFormat.jpeg;
if (jpeg2.SequenceEqual(bytes.Take(jpeg2.Length)))
return ImageFormat.jpeg;
return ImageFormat.unknown;
}
Windowsフォームの使用:
bool IsValidImage(string filename)
{
try
{
using(Image newImage = Image.FromFile(filename))
{}
}
catch (OutOfMemoryException ex)
{
//The file does not have a valid image format.
//-or- GDI+ does not support the pixel format of the file
return false;
}
return true;
}
それ以外の場合WPFを使用次のことができます:
bool IsValidImage(string filename)
{
try
{
using(BitmapImage newImage = new BitmapImage(filename))
{}
}
catch(NotSupportedException)
{
// System.NotSupportedException:
// No imaging component suitable to complete this operation was found.
return false;
}
return true;
}
作成したイメージをリリースする必要があります。それ以外の場合、この関数を何度も呼び出すと、システムがリソースを使い果たしたためにOutOfMemoryExceptionがスローされます。イメージが破損して誤った結果が得られたためではなく、このステップ後にイメージを削除した場合、良いものを削除する可能性があります。
さて、私は先に進み、問題を解決するために一連の関数をコーディングしました。最初にヘッダーをチェックしてから、イメージをtry/catchブロックにロードしようとします。 GIF、BMP、JPG、およびPNGファイルのみをチェックします。 imageHeadersにヘッダーを追加することで、さらにタイプを簡単に追加できます。
static bool IsValidImage(string filePath)
{
return File.Exists(filePath) && IsValidImage(new FileStream(filePath, FileMode.Open, FileAccess.Read));
}
static bool IsValidImage(Stream imageStream)
{
if(imageStream.Length > 0)
{
byte[] header = new byte[4]; // Change size if needed.
string[] imageHeaders = new[]{
"\xFF\xD8", // JPEG
"BM", // BMP
"GIF", // GIF
Encoding.ASCII.GetString(new byte[]{137, 80, 78, 71})}; // PNG
imageStream.Read(header, 0, header.Length);
bool isImageHeader = imageHeaders.Count(str => Encoding.ASCII.GetString(header).StartsWith(str)) > 0;
if (isImageHeader == true)
{
try
{
Image.FromStream(imageStream).Dispose();
imageStream.Close();
return true;
}
catch
{
}
}
}
imageStream.Close();
return false;
}
ヘッダーをスニッフィングすることで大まかなタイピングを行うことができます。
つまり、実装する各ファイル形式には、識別可能なヘッダーが必要です...
JPEG:最初の4バイトはFF D8 FF E0です(実際には、最初の2バイトだけが非jfif jpegに対して行われます。詳細は here )。
GIF:最初の6バイトは、「GIF87a」または「GIF89a」のいずれかです(詳細 here )
PNG:最初の8バイトは次のとおりです:89 50 4E 47 0D 0A 1A 0A(詳細 こちら )
TIFF:最初の4バイト:II42またはMM42(詳細 ここ )
など...必要なグラフィック形式に関するヘッダー/形式情報を検索し、必要に応じて処理するものに追加できます。これができないことは、ファイルがそのタイプの有効なバージョンであるかどうかを教えてくれますが、「画像ではなく画像?」についてのヒントを提供します。それはまだ壊れているか不完全な画像である可能性があり、したがって開くときにクラッシュする可能性があります。
これでうまくいくはずです-ヘッダーから生のバイトを読み取る必要はありません:
using(Image test = Image.FromFile(filePath))
{
bool isJpeg = (test.RawFormat.Equals(ImageFormat.Jpeg));
}
もちろん、OutOfMemoryExceptionもトラップする必要があります。これは、ファイルがまったくイメージでない場合に保存されます。
また、ImageFormatには、GDI +がサポートする他のすべての主要な画像タイプのプリセット項目があります。
演算子==はEqualsメソッドを呼び出すためにオーバーロードされないため、ImageFormatオブジェクトでは==ではなく.Equals()を使用する必要があります(列挙ではありません)。
TiffとJpegもサポートするメソッド
private bool IsValidImage(string filename)
{
Stream imageStream = null;
try
{
imageStream = new FileStream(filename, FileMode.Open);
if (imageStream.Length > 0)
{
byte[] header = new byte[30]; // Change size if needed.
string[] imageHeaders = new[]
{
"BM", // BMP
"GIF", // GIF
Encoding.ASCII.GetString(new byte[]{137, 80, 78, 71}),// PNG
"MM\x00\x2a", // TIFF
"II\x2a\x00" // TIFF
};
imageStream.Read(header, 0, header.Length);
bool isImageHeader = imageHeaders.Count(str => Encoding.ASCII.GetString(header).StartsWith(str)) > 0;
if (imageStream != null)
{
imageStream.Close();
imageStream.Dispose();
imageStream = null;
}
if (isImageHeader == false)
{
//Verify if is jpeg
using (BinaryReader br = new BinaryReader(File.Open(filename, FileMode.Open)))
{
UInt16 soi = br.ReadUInt16(); // Start of Image (SOI) marker (FFD8)
UInt16 jfif = br.ReadUInt16(); // JFIF marker
return soi == 0xd8ff && (jfif == 0xe0ff || jfif == 57855);
}
}
return isImageHeader;
}
return false;
}
catch { return false; }
finally
{
if (imageStream != null)
{
imageStream.Close();
imageStream.Dispose();
}
}
}
上記のすべての機能に関するいくつかの問題に注目してください。まず第一に-Image.FromFileは与えられた画像を開き、その後何らかの理由で与えられた画像ファイルを開きたい人は誰でもファイルを開くエラーを引き起こします。アプリケーション自体ですら-Image.FromStreamを使用して切り替えました。
Apiを切り替えた後、例外のタイプがOutOfMemoryExceptionからArgumentExceptionに変わります。 (おそらく.netフレームワークのバグ?)
また、.netが現在の機能でチェックするよりも多くの画像ファイル形式のサポートを追加する場合、最初に失敗した場合にのみ画像をロードしようとします-その後のみエラーを報告します。
したがって、私のコードは次のようになります。
try {
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
Image im = Image.FromStream(stream);
// Do something with image if needed.
}
}
catch (ArgumentException)
{
if( !IsValidImageFormat(path) )
return SetLastError("File '" + fileName + "' is not a valid image");
throw;
}
どこで:
/// <summary>
/// Check if we have valid Image file format.
/// </summary>
/// <param name="path"></param>
/// <returns>true if it's image file</returns>
public static bool IsValidImageFormat( String path )
{
using ( FileStream fs = File.OpenRead(path) )
{
byte[] header = new byte[10];
fs.Read(header, 0, 10);
foreach ( var pattern in new byte[][] {
Encoding.ASCII.GetBytes("BM"),
Encoding.ASCII.GetBytes("GIF"),
new byte[] { 137, 80, 78, 71 }, // PNG
new byte[] { 73, 73, 42 }, // TIFF
new byte[] { 77, 77, 42 }, // TIFF
new byte[] { 255, 216, 255, 224 }, // jpeg
new byte[] { 255, 216, 255, 225 } // jpeg Canon
} )
{
if (pattern.SequenceEqual(header.Take(pattern.Length)))
return true;
}
}
return false;
} //IsValidImageFormat
私はセミコロンの答えを取り、VBに変換しました:
Private Function IsValidImage(imageStream As System.IO.Stream) As Boolean
If (imageStream.Length = 0) Then
isvalidimage = False
Exit Function
End If
Dim pngByte() As Byte = New Byte() {137, 80, 78, 71}
Dim pngHeader As String = System.Text.Encoding.ASCII.GetString(pngByte)
Dim jpgByte() As Byte = New Byte() {255, 216}
Dim jpgHeader As String = System.Text.Encoding.ASCII.GetString(jpgByte)
Dim bmpHeader As String = "BM"
Dim gifHeader As String = "GIF"
Dim header(3) As Byte
Dim imageHeaders As String() = New String() {jpgHeader, bmpHeader, gifHeader, pngHeader}
imageStream.Read(header, 0, header.Length)
Dim isImageHeader As Boolean = imageHeaders.Count(Function(str) System.Text.Encoding.ASCII.GetString(header).StartsWith(str)) > 0
If (isImageHeader) Then
Try
System.Drawing.Image.FromStream(imageStream).Dispose()
imageStream.Close()
IsValidImage = True
Exit Function
Catch ex As Exception
System.Diagnostics.Debug.WriteLine("Not an image")
End Try
Else
System.Diagnostics.Debug.WriteLine("Not an image")
End If
imageStream.Close()
IsValidImage = False
End Function
後で他の操作や他のファイルタイプ(PSDなど)のデータを読み込む必要がある場合は、Image.FromStream
関数は必ずしも良いアイデアではありません。
私は次のようなメソッドを作成します:
Image openImage(string filename);
例外を処理します。戻り値がNullの場合、無効なファイル名/タイプがあります。
ストリームの最初の数バイトを読み取って、JPEGのマジックヘッダーバイトと比較できます。