繁体   English   中英

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

[英]C# Image.FromStream(): Lost metadata when running in Windows 8 / 10

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

这是该方法的一部分。 它从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();
}

在Windows 7中,检测并提取出所有六个元数据。 简而言之,在Windows 7环境中,我设法获得了我需要的一切。

当我将它移动到Windows 10终端(也尝试过Windows 8)时,事情变得不同了。 我只能从Image提取2个元数据。

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

所以问题是: 改变了什么? 它在Windows 7中完全正常,但在Windows 8和10中却不是这样。如果我不将流转换为Image ,我仍然可以取回数据。 在此过程的某处,元数据将丢失。 当我将流转换为Image ,或者当我将Image转换回byte[]时,它会丢失。 作为旁注,我尝试将byte[]转换为字符串。 来自流的byte[]的字符串表示看起来与Imagebyte[]不同。 使用正确的编码器,我可以看到后面的byte[]缺少4个元数据。

元数据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来读取元数据,其结果在windows 7和windows 10中都非常准确

您可以从nuget安装库。 install-Package MetadataExtractor

编辑3:提议的解决方案

现在问题得到解决,以下课程对win 7,win 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 Stream -TryIt加载的PNG中读取元数据

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM