[英]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[]
的字符串表示看起来与Image
的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.
)
编辑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的实例,并对图像进行处理。 您可以在以下位置找到包含源代码的完整演示:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.