C#Image.FromStream():在Windows8/10中运行时丢失元数据

本文关键字:运行时 元数据 Windows8 FromStream C#Image | 更新日期: 2023-09-27 17:59:41

我有一个从web服务检索图像的应用程序。在发送到C#客户端之前,web服务会将一些元数据嵌入到图像中。

这是方法的一部分。它从Response对象中检索Stream,并从流中创建一个Image。请注意,我使用的是System.Drawing.Image,而不是System.Windows.Controls.Image——这意味着我不能使用任何ImageSource或BitmapSource。

System.Drawing.Image img = null;
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
    Stream stream = response.GetResponseStream();
    img = System.Drawing.Image.FromStream(stream);
    .......
}
return img;

图像看起来非常好,但里面嵌入了元数据。图像是PNG格式的,还有另一种方法可以从Image中提取信息。总共嵌入了六条元数据。这里描述了PNG格式(PNG块)。数据保存在"tEXt"区块下。

public static Hashtable GetData(Image image)
{
    Hashtable metadata = null;
    data = new Hashtable();
    byte[] imageBytes;
    using (MemoryStream stream = new MemoryStream())
    {
        image.Save(stream, image.RawFormat);
        imageBytes = new byte[stream.Length];
        imageBytes = stream.ToArray();
    }
    if (imageBytes.Length <= 8)
    {
        return null;
    }
    // Skipping 8 bytes of PNG header
    int pointer = 8;
    while (pointer < imageBytes.Length)
    {
        // read the next chunk
        uint chunkSize = GetChunkSize(imageBytes, pointer);
        pointer += 4;
        string chunkName = GetChunkName(imageBytes, pointer);
        pointer += 4;
        // chunk data -----
        if (chunkName.Equals("tEXt"))
        {
            byte[] data = new byte[chunkSize];
            Array.Copy(imageBytes, pointer, data, 0, chunkSize);
            StringBuilder stringBuilder = new StringBuilder();
            foreach (byte t in data)
            {
                stringBuilder.Append((char)t);
            }
            string[] pair = stringBuilder.ToString().Split(new char[] { ''0' });
            metadata[pair[0]] = pair[1];
        }
        pointer += (int)chunkSize + 4;
        if (pointer > imageBytes.Length)
            break;
    }
    return data;
}
private static uint GetChunkSize(byte[] bytes, int pos)
{
    byte[] quad = new byte[4];
    for (int i = 0; i < 4; i++)
    {
        quad[3 - i] = bytes[pos + i];
    }
    return BitConverter.ToUInt32(quad);
}
private static string GetChunkName(byte[] bytes, int pos)
{
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < 4; i++)
    {
        builder.Append((char)bytes[pos + i]);
    }
    return builder.ToString();
}

在Windows7中,所有六条元数据都被检测并提取出来。总之,在Windows7环境中,我设法得到了我需要的一切。

当我把它移到Windows 10终端(也尝试过Windows 8)时,情况会有所不同。我只能从Image中提取2条元数据。

因为我的GetData()方法将Image转换为byte[],所以我尝试直接从web服务流中提取数据。我将流转换为byte[],并使用相同的技术从byte[]中提取元数据。我使用这种方法成功地取回了所有6个元数据。

所以问题是:发生了什么变化它在Windows7中运行良好,但在Windows8和10中则不然。如果我不将流转换为Image,我仍然可以取回数据。在这个过程的某个地方,元数据丢失了。当我将流转换为Image时,或者当我将Image转换回byte[]时,它会丢失。附带说明一下,我已经尝试将byte[]转换为字符串。来自流的byte[]的字符串表示看起来与来自Imagebyte[]不同。使用正确的编码器,我可以看到后面的byte[]中缺少4个元数据。

C#Image.FromStream():在Windows8/10中运行时丢失元数据

元数据tEXt:在ISO/IEC 8859-1 中表示

在提出请求之前,请尝试添加以下内容:

 request.Headers.Add(HttpRequestHeader.AcceptCharset, "ISO-8859-1");

所以,修改你的代码:

System.Drawing.Image img = null;
 //accept Charset "ISO-8859-1"
 request.Headers.Add(HttpRequestHeader.AcceptCharset, "ISO-8859-1");
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
 Stream stream = response.GetResponseStream();
 img = System.Drawing.Image.FromStream(stream);
  .......
}
 return img;

仅供参考,你能在windows 7/8/10 中发布什么是windows EncodingName吗

使用powershell命令可以知道:

[System.Text.Encoding]::Default.EncodingName

编辑:

我查看了DOTNet System.Drawing.Image.FromStream的源代码并发现该语句:

  // [Obsolete("Use Image.FromStream(stream, useEmbeddedColorManagement)")]
    public static Image FromStream(Stream stream) { 
        return Image.FromStream(stream, false);
    }

尝试使用:

  Image.FromStream(stream, true); 
  or
 Image.FromStream(stream, true,true);

有关参数的详细信息:

  public static Image FromStream(
  Stream stream,
  bool useEmbeddedColorManagement,////true to use color management  information embedded in the data stream; otherwise, false. 
  bool validateImageData //true to validate the image data; otherwise, false.
  )

Image.FromStream方法

编辑2:

我用tEXT数据在PNG图像文件上做了一个实验:

我开发了一个以字节为单位测量图像大小的函数,该函数由FromStream()读取,我在win7/win 10上执行。

下表表示两种环境下图像的实际大小(以字节为单位):

 The file size: 502,888 byte (real size on disk).     
 win 7         win10        function used
 569674        597298      Image.FromStream(stream, true,true)
 597343        597298      Image.FromStream(stream, true)
 597343        597298      Image.FromStream(stream, false)

您发现两种环境中的大小不同磁盘中的实际大小。

因此,您预计元数据的位置会发生变化(但不会丢失,只是重新分配)

我使用十六进制编辑器工具来查看tTEXT块。

tEXT位于文件开头的66位(十进制),在两个环境中都是一样的!!!

我使用了我自己的元数据读取器功能,结果是相同的,对windows 7或windows 10都有效(没有数据丢失)。

PNG格式的官方网站是:https://www.w3.org/TR/PNG/

结论

函数Image.FromStream不适合读取元数据,图像文件应该以原始字节格式读取,而不是以图像格式读取,因为函数FromStream重新分配了原始数据,以保持图像及其数据不失真(这是dotnet中函数的内部)。

要按照PNG规范所述读取元数据,您应该按照规范所述从文件开头读取RAW BYTES中的流。

我建议您使用类库MetadataExtractor来读取元数据,其结果在Windows7和Windows10中都非常准确

您可以从nuget安装库。安装包元数据提取器

编辑3:建议的解决方案

现在问题解决了,下面的类对赢7、赢8 都有效

主要更改是将图像文件读取为原始字节

class MetaReader 
{
    public static Hashtable GetData(string fname)
    {
        using (FileStream image = new FileStream(fname, FileMode.Open, FileAccess.Read))
        {
            Hashtable metadata = new Hashtable();
            byte[] imageBytes;
            using (var memoryStream = new MemoryStream())
            {
                image.CopyTo(memoryStream);
                imageBytes = memoryStream.ToArray();
                Console.WriteLine(imageBytes.Length);
            }
            if (imageBytes.Length <= 8)
            {
                return null;
            }
            // Skipping 8 bytes of PNG header
            int pointer = 8;
            while (pointer < imageBytes.Length)
            {
                // read the next chunk
                uint chunkSize = GetChunkSize(imageBytes, pointer);
                pointer += 4;
                string chunkName = GetChunkName(imageBytes, pointer);
                pointer += 4;
                // chunk data -----
                if (chunkName.Equals("tEXt"))
                {
                    byte[] data = new byte[chunkSize];
                    Array.Copy(imageBytes, pointer, data, 0, chunkSize);
                    StringBuilder stringBuilder = new StringBuilder();
                    foreach (byte t in data)
                    {
                        stringBuilder.Append((char)t);
                    }
                    string[] pair = stringBuilder.ToString().Split(new char[] { ''0' });
                    metadata[pair[0]] = pair[1];
                    Console.WriteLine(metadata[pair[0]]);
                }
                pointer += (int)chunkSize + 4;
                if (pointer > imageBytes.Length)
                    break;
            }
            return metadata;
        }
    }
    private static uint GetChunkSize(byte[] bytes, int pos)
    {
        byte[] quad = new byte[4];
        for (int i = 0; i < 4; i++) { quad[3 - i] = bytes[pos + i]; }
        return BitConverter.ToUInt32(quad, 0);
    }
    private static string GetChunkName(byte[] bytes, int pos)
    {
        StringBuilder builder = new StringBuilder(); for (int i = 0; i < 4; i++) { builder.Append((char)bytes[pos + i]); }
        return builder.ToString();
    }
}

从Web服务读取元数据:

您可以从url加载图像文件作为流,并实时读取元数据。此外,您还可以创建System.Drawing.Image的实例,并对图像进行任何处理。您可以在以下位置找到完整的演示源代码:

从Web流加载的PNG读取元数据-TryIt