Подтвердить изображение из файла в С#
Я загружаю изображение из файла, и я хочу знать, как проверить изображение до его полного чтения из файла.
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);
Ответы
Ответ 1
JPEG не имеет формального определения заголовка, но у них есть небольшое количество метаданных, которые вы можете использовать.
- Смещение 0 (два байта): JPEG SOI-маркер (FFD8 hex)
- Смещение 2 (два байта): ширина изображения в пикселях
- Смещение 4 (два байта): высота изображения в пикселях
- Смещение 6 (байт): количество компонентов (1 = оттенки серого, 3 = RGB)
После этого есть еще несколько вещей, но это не важно.
Вы можете открыть файл с использованием двоичного потока и прочитать эти исходные данные и убедиться, что значение OffSet 0 равно 0, а OffSet 6 - 1,2 или 3.
Это, по крайней мере, даст вам немного большую точность.
Или вы можете просто поймать исключение и двигаться дальше, но я думал, что вам нужна задача:)
Ответ 2
вот моя проверка изображения. Я не могу полагаться на расширения файлов и должен сам проверять формат.
Я загружаю BitmapImages в WPF из байт-массивов и не знаю формат заранее. 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;
}
Ответ 3
Использование форм 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, потому что в системе закончились ресурсы, а не потому, что изображение повреждено, что приводит к некорректному результату, и если вы удаляете изображения после этого шага, вы потенциально можете удалить хорошие.
Ответ 4
Ну, я пошел вперед и закодировал набор функций для решения проблемы. Сначала он проверяет заголовок, затем пытается загрузить изображение в блок 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;
}
Ответ 5
Вы можете сделать грубую типизацию, обнюхивая заголовок.
Это означает, что каждый формат файла, который вы реализуете, должен иметь идентификационный заголовок...
JPEG: первые 4 байта - это FF D8 FF E0 (фактически только первые два байта сделают это для не jfif jpeg, больше информации здесь).
GIF: первые 6 байтов являются либо "GIF87a", либо "GIF89a" (подробнее здесь)
PNG: первые 8 байтов: 89 50 4E 47 0D 0A 1A 0A (подробнее здесь)
TIFF: первые 4 байта: II42 или MM42 (подробнее здесь)
и т.д.... вы можете найти информацию о заголовке/формате практически для любого графического формата, о котором вы заботитесь, и добавить к тому, что он обрабатывает по мере необходимости. Что это не сделает, скажет вам, является ли файл допустимой версией этого типа, но он даст вам подсказку о "изображении не изображение?". Он все равно может быть поврежденным или неполным изображением и, таким образом, сбой при открытии, поэтому по-прежнему нужно попытаться поймать вызов .FromFile.
Ответ 6
Это должно сделать трюк - вам не нужно читать исходные байты из заголовка:
using(Image test = Image.FromFile(filePath))
{
bool isJpeg = (test.RawFormat.Equals(ImageFormat.Jpeg));
}
Конечно, вы также должны замаскировать исключение OutOfMemoryException, которое спасет вас, если файл вообще не является изображением.
И, ImageFormat имеет предварительно установленные элементы для всех других основных типов изображений, поддерживаемых GDI +.
Примечание. Вы должны использовать .Equals() и not == для объектов ImageFormat (это не перечисление), потому что оператор == не перегружен, чтобы вызвать метод Equals.
Ответ 7
Метод, поддерживающий 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();
}
}
}
Ответ 8
Я бы создал такой метод, как:
Image openImage(string filename);
в котором я обрабатываю исключение. Если возвращаемое значение равно Null, существует недопустимое имя файла/тип.
Ответ 9
Я взял Semicolon ответ и преобразован в 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
Ответ 10
Вы можете прочитать первые несколько байтов Stream и сравнить их с байтами заголовка для JPEG.
Ответ 11
в случае, если вам нужны данные, прочитанные для других операций и/или для других типов файлов (например, PSD), затем использование функции Image.FromStream
не обязательно является хорошей идеей.
Ответ 12
Заметила пару проблем со всеми вышеперечисленными функциями.
Прежде всего - 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 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