【问题标题】:extract a stored png image from a binary file the right way以正确的方式从二进制文件中提取存储的 png 图像
【发布时间】:2012-07-14 07:44:47
【问题描述】:

我有一个二进制文件,其中一次包含一个 PNG 文件(二进制文件不是 DLL,不是 EXE,没有什么常见的,只是一个包含不同文本信息的文件,一个 PNG 文件和其他一些东西。我不知道该文件的格式。PNG 文件可通过执行此类文件的程序显示)。我没有执行这些文件的程序的来源。 我现在的任务是从二进制文件中提取这个 PNG 文件,以便显示或保存为 PNG。我写了一个代码,它适用于其中一些文件(假设大约 50% 的文件),但在其他文件上则不行。在不工作的文件上,创建此文件的程序仍然可以显示包含图像,因此图像在每个文件中肯定是有效的 - 但无论如何我的代码不适用于某些文件。

有些图像似乎有另一种格式,可能是编码类型(我已经尝试了所有不同的编码类型,但没有成功)。这是我的代码(我希望有人能告诉我要更改什么以使图像始终可读)。

我的代码是做什么的:它找到已知的 PNG 图像“‰PNG”的起始字符串和已知的结束字符串“IEND®B`‚”。这个字符串在我的任何包含 PNG 的二进制文件中都是相同的。 然后我的代码获取开始和结束之间的字符串 + 开始和结束序列并保存它 到带有 Encoding.Default 的文件。大多数通过这种方式提取的 PNG 文件可以使用图像查看器显示,但大约 50% 是无效的。如果我用编辑器打开它并将字符与工作图像进行比较,图像看起来还不错。到目前为止,我不知道哪个符号是错误图像格式的原因。

如果需要我会提供更多信息,这里是我的代码:

private void button2_Click(object sender, EventArgs e)
    {
        string ReadFile1 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "file.dat");
        string WriteFile1 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "test.png");
        string TMP = File.ReadAllText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), ReadFile1), Encoding.Default); //System.Text.Encoding.GetEncoding(1251)
        int start1 = TMP.IndexOf("PNG", 0 ,StringComparison.Ordinal);
        if (start1 == 0) { return; }
        int end1 = TMP.IndexOf("IEND", StringComparison.Ordinal);
        string PNG = TMP.Substring(start1 - 1, (end1 + 9) - start1);
        File.WriteAllText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "test.png"), PNG, Encoding.Default);
    }

我还想到了首先用二进制方法获取 PNG 并使用了这段代码, 但是我只读取字符串就得到了完全相同的结果。这里是我之前的 代码。我使用要比较的字符串在字节数组中寻找位置。 我对二进制代码没有运气......

 byte[] by;
        // 1.
        // Open file with a BinaryReader.
        using (BinaryReader b = new BinaryReader(File.Open(ReadFile1, FileMode.Open), Encoding.Default))
        {
            // 2.
            // Variables for our position.
            int pos = start1 - 1;           //I determine the right positions before doing this
            int required = (end1 + 9) - start1; 

            // 3.
            // Seek to our required position.
            b.BaseStream.Seek(pos, SeekOrigin.Begin);

            // 4.
            // Read the next 2000 bytes.
            by = b.ReadBytes(required);
            b.Close();
        }

        FileStream writeStream;
        writeStream = new FileStream(WriteFile1, FileMode.Create);
        BinaryWriter writeBinay = new BinaryWriter(writeStream, Encoding.Default);
        writeBinay.Write(by);
        writeBinay.Close(); */

【问题讨论】:

    标签: c# binary png binaryfiles binary-data


    【解决方案1】:

    PNG 文件是二进制文件。如果您使用某种编码读取它们,您将丢失信息并且程序的输出不再是有效的 PNG 文件。更多解释和代码示例请参考Using Chunks in a PNG

    另请阅读PNG Specifiaction: File structure 了解详细信息。

    【讨论】:

    • @feedwall 问题是您使用编码方案将二进制数据作为文本读取。如果这对您来说还不够解释,您必须搜索并阅读“字符编码”。
    • 这就是重点:编码在这里无关紧要,但你可以使用它:File.ReadAllText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), ReadFile1), Encoding.Default) 最后一个参数定义你不想使用默认编码将文件内容读入文本 - 不管那是在那个特定时刻在您的应用程序中。 PNG-24 不是文本文件,它们是二进制文件,应该这样处理。从 Monroe Thomas 的回答开始,仔细阅读 PNG 规范。如果您的文件之前已作为文本文件处理 - 也可以更改。
    【解决方案2】:

    您不应该将文件作为文本文件读取;内容可能会发生转换。您应该尝试使用File.ReadAllBytes,然后搜索PNG文件开头和结尾的字节序列,然后写出该字节区域。

    要在字节数组中查找字节序列,可以使用如下代码:

    private static int IndexOf(byte[] array, byte[] sequence, int startIndex)
    {
        if (sequence.Length == 0)
            return -1;
    
        int found = 0;
        for (int i = startIndex; i < array.Length; i++)
        {
            if (array[i] == sequence[found])
            {
                if (++found == sequence.Length)
                {
                    return i - found + 1;
                }
            }
            else
            {
                found = 0;
            }
        }
    
        return -1;
    }
    
    private void button2_Click(object sender, EventArgs e) 
    { 
        string ReadFile1 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "file.dat"); 
        string WriteFile1 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "test.png"); 
    
        byte[] TMP = File.ReadAllBytes(ReadFile1);
    
        byte[] pngStartSequence = new byte[] { 0x89, 0x50, 0x4E, 0x47 };
        byte[] pngEndSequence = new byte[] { 0x49, 0x46, 0x4E, 0x44 };
    
        int start1 = IndexOf(TMP, pngStartSequence, 0);
        if (start1 == -1)
        {
           // no PNG present
           MessageBox.Show("Could not find PNG header");
           return;
        }
    
        int end1 = IndexOf(TMP, pngEndSequence, start1 + pngStartSequence.Length);
        if (end1 == -1)
        {
           // no IEND present
           MessageBox.Show("Could not find PNG footer");
           return;
        }
    
        int pngLength = end1 - start1 + 8;
        byte[] PNG = new byte[pngLength];
    
        Array.Copy(TMP, start1, PNG, 0, pngLength);
    
        File.WriteAllBytes(WriteFile1, PNG); 
    } 
    

    【讨论】:

    • @feedwall 您需要将上面第一个代码块中的函数 IndexOf 添加到您的文件中。我做了一些小改动,再试一次。
    • @feedwall 感谢您的关注;我已经更新了答案。可能是程序在存储 PNG 文件之前对其进行了某种编码,并在读取文件时反转了该编码。不过,这很难进行逆向工程。
    • @feedwall 我认为如果字符串 PNG 在文件中出现多次,那么您可能会得到错误的信息。因为我们现在比较的是二进制而不是文本,所以我们应该准确地比较所有的页眉和页脚字节。我已经更新了我的答案以包括开始和结束序列的字节表示。
    • @feedwall 由于我们在搜索中包含了 PNG 之前的特殊字符,因此我们不再在 Array.Copy 中使用 start1 - 1
    • @feedwall ...长度计算略有不同。
    【解决方案3】:

    使用File.ReadAllBytesFile.WriteAllBytes。作为文本的读写可能会受到编码的影响。

    您可以使用Jb Evain algorithmByte Array 中查找模式,如下所示:

    static void Main()
    {
        // PNG file signature
        var startPattern = new byte[] { 137, 80, 78, 71, 13, 10, 26, 105 };
        var data = File.ReadAllBytes("png file");
    
        var start = data.Locate(startPattern);
        // and end like this
    }    
    
    public static int[] Locate(this byte[] self, byte[] candidate)
    {
        if (IsEmptyLocate(self, candidate))
            return Empty;
    
        var list = new List<int>();
    
        for (int i = 0; i < self.Length; i++)
        {
            if (!IsMatch(self, i, candidate))
                continue;
    
            list.Add(i);
        }
    
        return list.Count == 0 ? Empty : list.ToArray();
    }
    
    static bool IsMatch(byte[] array, int position, byte[] candidate)
    {
        if (candidate.Length > (array.Length - position))
            return false;
    
        for (int i = 0; i < candidate.Length; i++)
            if (array[position + i] != candidate[i])
                return false;
    
        return true;
    }
    
    static readonly int[] Empty = new int[0];
    
    static bool IsEmptyLocate(byte[] array, byte[] candidate)
    {
        return array == null
                || candidate == null
                || array.Length == 0
                || candidate.Length == 0
                || candidate.Length > array.Length;
    }
    

    【讨论】:

    • 这将是一种查找字节序列的慢速方法。您可以在不分配新列表的情况下执行此操作。
    猜你喜欢
    • 2011-01-02
    • 2013-04-03
    • 2021-06-23
    • 1970-01-01
    • 2023-03-18
    • 2017-06-25
    • 2023-03-15
    相关资源
    最近更新 更多