微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

我有一个从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格式,还有一种方法可以从图像中提取信息.共有六块元数据嵌入. PNG格式(PNG块)描述为here.数据保存在“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,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)时,事情变得不同.我只能从图像中提取2个元数据.

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

所以问题是:有什么变化?它在Windows 7中工作得很好,但在Windows 8和10中不是这样.我仍然可以收回数据,前提是我不将流转换成图像.在此过程中,元数据丢失.当将流转换为图像时,或将图像转换为byte []时,丢失.作为附注,我已经尝试将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 Method

编辑2:

我用TEXT数据对PNG图像文件进行了实验:

我开发了一个函数来测量图像的大小(以字节为单位),这是通过函数FromStream()读取的,我在win7 / win10上执行.

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

The file size: 502,888 byte (real size on disk).     

 win 7         win10        function used
 569674        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,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的实例,并对图像进行处理.
您可以在以下位置找到源代码的完整演示:

Reading Metadata from PNG loaded from Web Stream -TryIt

原文地址:https://www.jb51.cc/c/112515.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐