这是该方法的一部分.它从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个元数据.
解决方法
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. )
编辑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的实例,并对图像进行处理.
您可以在以下位置找到源代码的完整演示: