在 C# 中验证来自文件的图像

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/210650/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 18:15:22  来源:igfitidea点击:

Validate image from file in C#

c#.netimagefile-io

提问by SemiColon

I'm loading an image from a file, and I want to know how to validate the image before it is fully read from the file.

我正在从文件加载图像,我想知道如何在从文件中完全读取图像之前对其进行验证。

string filePath = "image.jpg";
Image newImage = Image.FromFile(filePath);

The problem occurs when image.jpg isn't really a jpg. For example, if I create an empty text file and rename it to image.jpg, an OutOfMemory Exception will be thrown when image.jpg is loaded.

当 image.jpg 不是真正的 jpg 时会出现问题。例如,如果我创建一个空文本文件并将其重命名为 image.jpg,则在加载 image.jpg 时将抛出 OutOfMemory 异常。

I'm looking for a function that will validate an image given a stream or a file path of the image.

我正在寻找一个函数,可以在给定图像的流或文件路径的情况下验证图像。

Example function prototype

示例函数原型

bool IsValidImage(string fileName);
bool IsValidImage(Stream imageStream);

采纳答案by FlySwat

JPEG's don't have a formal header definition, but they do have a small amount of metadata you can use.

JPEG 没有正式的标头定义,但它们确实有少量可以使用的元数据。

  • Offset 0 (Two Bytes): JPEG SOI marker (FFD8 hex)
  • Offset 2 (Two Bytes): Image width in pixels
  • Offset 4 (Two Bytes): Image height in pixels
  • Offset 6 (Byte): Number of components (1 = grayscale, 3 = RGB)
  • 偏移 0(两个字节):JPEG SOI 标记(FFD8 十六进制)
  • 偏移量 2(两个字节):以像素为单位的图像宽度
  • 偏移量 4(两个字节):图像高度(以像素为单位)
  • 偏移量 6(字节):分量数(1 = 灰度,3 = RGB)

There are a couple other things after that, but those aren't important.

之后还有其他一些事情,但这些都不重要。

You can open the file using a binary stream, and read this initial data, and make sure that OffSet 0 is 0, and OffSet 6 is either 1,2 or 3.

您可以使用二进制流打开文件,并读取此初始数据,并确保 OffSet 0 为 0,OffSet 6 为 1,2 或 3。

That would at least give you slightly more precision.

这至少会让你更精确。

Or you can just trap the exception and move on, but I thought you wanted a challenge :)

或者你可以捕获异常并继续前进,但我认为你想要一个挑战:)

回答by Enrico Murru

I would create a method like:

我会创建一个方法,如:

Image openImage(string filename);

in which I handle the exception. If the returned value is Null, there is an invalid file name / type.

我在其中处理异常。如果返回值为 Null,则存在无效的文件名/类型。

回答by Quantenmechaniker

You could read the first few bytes of the Stream and compare them to the magic header bytes for JPEG.

您可以读取 Stream 的前几个字节,并将它们与 JPEG 的魔术头字节进行比较。

回答by MusiGenesis

Using Windows Forms:

使用 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;
}

Otherwise if you're using WPFyou can do the following:

否则,如果您使用 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;
}

You must release the image created. Otherwise when you call this function large number of times, this would throw OutOfMemoryExceptionbecause the system ran out of resources, and not because the image is corrupt yielding an incorrect result, and if you delete images after this step, you'd potentially be deleting good ones.

您必须释放创建的映像。否则,当您多次调用此函数时,这将抛出OutOfMemoryException,因为系统资源不足,而不是因为图像已损坏而产生不正确的结果,如果在此步骤之后删除图像,则可能会删除好的。

回答by Troy Howard

You can do a rough typing by sniffing the header.

您可以通过嗅探标题进行粗略的打字。

This means that each file format you implement will need to have a identifiable header...

这意味着您实现的每种文件格式都需要有一个可识别的标题...

JPEG: First 4 bytes are FF D8 FF E0 (actually just the first two bytes would do it for non jfif jpeg, more info here).

JPEG:前 4 个字节是 FF D8 FF E0(实际上只有前两个字节可以用于非 jfif jpeg,更多信息在这里)。

GIF: First 6 bytes are either "GIF87a" or "GIF89a" (more info here)

GIF:前 6 个字节是“GIF87a”或“GIF89a”(更多信息在这里

PNG: First 8 bytes are: 89 50 4E 47 0D 0A 1A 0A (more info here)

PNG:前 8 个字节是:89 50 4E 47 0D 0A 1A 0A(更多信息在这里

TIFF: First 4 bytes are: II42 or MM42 (more info here)

TIFF:前 4 个字节是:II42 或 MM42(更多信息在这里

etc... you can find header/format information for just about any graphics format you care about and add to the things it handles as needed. What this won't do, is tell you if the file is a valid version of that type, but it will give you a hint about "image not image?". It could still be a corrupt or incomplete image, and thus crash when opening, so a try catch around the .FromFile call is still needed.

等等...您可以找到几乎任何您关心的图形格式的标题/格式信息,并根据需要添加到它处理的内容中。这不会做的是告诉您该文件是否是该类型的有效版本,但它会给您提示“图像不是图像?”。它可能仍然是损坏或不完整的图像,因此在打开时会崩溃,因此仍然需要尝试捕获 .FromFile 调用。

回答by SemiColon

Well, I went ahead and coded a set of functions to solve the problem. It checks the header first, then attempts to load the image in a try/catch block. It only checks for GIF, BMP, JPG, and PNG files. You can easily add more types by adding a header to imageHeaders.

好吧,我继续编写了一组函数来解决问题。它首先检查标题,然后尝试在 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;
}

回答by lorddarq

in case yo need that data read for other operations and/or for other filetypes (PSD for example), later on, then using the Image.FromStreamfunction is not necessarily a good ideea.

如果您稍后需要为其他操作和/或其他文件类型(例如 PSD)读取数据,那么使用该Image.FromStream功能不一定是一个好主意。

回答by Paulo

A method that supports Tiff and Jpeg also

一种也支持 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();
        }
    }
}

回答by David Boike

This should do the trick - you don't have to read raw bytes out of the header:

这应该可以解决问题 - 您不必从标头中读取原始字节:

using(Image test = Image.FromFile(filePath))
{
    bool isJpeg = (test.RawFormat.Equals(ImageFormat.Jpeg));
}

Of course, you should trap the OutOfMemoryException too, which will save you if the file isn't an image at all.

当然,您也应该捕获 OutOfMemoryException ,如果文件根本不是图像,它会拯救您。

And, ImageFormat has pre-set items for all the other major image types that GDI+ supports.

而且,ImageFormat 具有 GDI+ 支持的所有其他主要图像类型的预设项。

Note, you must use .Equals() and not == on ImageFormat objects (it is not an enumeration) because the operator == isn't overloaded to call the Equals method.

请注意,您必须在 ImageFormat 对象上使用 .Equals() 而不是 ==(它不是枚举),因为运算符 == 未重载以调用 Equals 方法。

回答by ray

I took Semicolon's answer and converted to VB:

我接受了分号的答案并转换为 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