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[]
的字符串表示看起来与来自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.
)
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