我有一个从 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 block )here .数据保存在“tEXt” block 下。
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[]
,所以我尝试直接从网络服务流中提取数据.我将流转换为 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:
我用文本数据对 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 block 。
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 安装库。 安装包元数据提取器
编辑 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 的实例并对图像进行任何处理。 您可以在以下位置找到带有源代码的完整演示:
关于C# 图像.FromStream() : Lost metadata when running in Windows 8/10,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38693292/