虽然我不是 JPEG 文件格式的专家,但我对该主题进行了一些研究,我发现以下内容可以帮助您解决问题。
请注意,由于缺少示例文件来检查并说明它与 .Net/GDI+ JPEG/JFIF 解码器所期望的有什么不同,因此此答案将假设而不是具体查明问题的根源。
JPEG/JFIF 格式
首先,您可能希望对 JPEG/JFIF 格式本身有所了解。毕竟,您刚刚遇到了 .Net/GDI+ 无法加载/解析的文件。由于我没有您遇到问题的文件,我建议您在选择的十六进制编辑器中加载它......它能够基于模板/代码/解析器突出显示文件。
我使用了 Sweetscape 在线模板库中的 010 Editor 和 JPEG Template。
010 Editor 提供 30 天免费试用。
您要特别寻找的是 SOFn 标识符和 bad JPEG 中的数据。
在 SOFn 数据中,我可以看到我的图像是 Y (154) 像素高和 X (640) 像素宽,每个组件的精度为 8 位使用 3 个组件,使其每像素 24 位。
JPEG/JFIF 格式是许多不同实现/格式的巨大组合。显然,在 odd JPEG 格式出现之前很久很久以前就已经存在的任何库中,您不会找到该格式的所有变体。 GDI+ 库有哪些。
在您的情况下,我怀疑您在 JPEG 文件中遇到了 commonly asked about CMYK 颜色配置文件。
.Net 实现
您说您使用了 System.Drawing.Graphics.FromImage,所以我假设您的代码如下所示:
Graphics.FromImage(Image.FromFile("nope.jpg"));
Graphics.FromImage(Image.FromFile("nope.jpg", true));
Graphics.FromImage(Image.FromStream(nopeJpegStream));
从这些调用中,当本机 gdiplus.dll 调用时,您可能会收到 OutOfMemoryException...
- GdipGetImageGraphicsContext
- GdipLoadImageFromFile
- GdipLoadImageFromFileICM(或它们各自的 *Stream 变体)或
- GdipImageForceValidation
... 返回代码 3 或 5(分别为内存不足或缓冲区不足)
我从 referencesource.microsoft.com 那里收集到的 .Net 资源。
无论如何,这很可能不是 .Net 的问题,而是 Microsoft 未提供源代码的 GDI+ (gdiplus.dll) 的问题。这也意味着无法使用 .Net 包装器控制图像的加载方式,也无法检查它为什么会失败。 (虽然我仍然怀疑你的 JPEG 是用 CMYK 保存的)
不幸的是,当您在 GDI+ 领域前进时,您会发现更多这些奇怪的异常/错误。由于该库几乎已被弃用,取而代之的是 Windows Presentation Framework (WPF) 和 Windows Imaging 组件。 (WIC)
我自己的测试
由于您从未提供有关该主题的图片或任何其他详细信息,因此我试图重现您的问题。这本身就是一项任务,Image.FromFile (GdipLoadImageFromFile) 在许多不同的文件格式上都会失败。至少它不关心文件扩展名是什么,幸好 Photoshop 做到了。
因此,根据您的信息,我终于设法复制了一个 .jpg 文件,该文件在 Photoshop 中加载良好,显示 DPI 为 96,位深为 32。当然,如果我对 JPEG 格式有更多了解,我可能会得到马上解决。
在 010 编辑器中显示此文件(我必须在 Photoshop 中设置为 CMYK 颜色空间)给了我以下 SOFn 数据:Y (154) 像素高和 X (640) 像素宽,使用 4 个组件,每个组件的精度为 8 位,因此每个像素为 32 位。
我怀疑您会在“坏”文件中看到相同的内容。
是的,Image.FromFile 现在会抛出 OutOfMemoryException!
可能的解决方案
- 使用外部库加载图像文件。 (我留给你一个练习,但 ImageMagick A.K.A Magick.NET 似乎是一个不错的选择)
- 使用可以将图像从一种格式转换为另一种格式的命令行工具(当您遇到此异常时调用)。或者从 JPEG 到 JPEG,因为它可能在这种情况下。 (再一次,ImageMagick 的“转换”命令行工具似乎是一个不错的选择)
-
使用 Windows Presentation Framework 程序集...
public static Image ImageFromFileWpf(string filename) {
/* Load the image into an encoder using the Presentation Framework.
* This is done by adding a frame (which in laymans terms is a layer) to a class derived BitmapEncoder.
* Only TIFF, Gif and JPEG XR supports multiple frames.
* Since we are going to convert our image to a GDI+ resource we won't support this as GDI+ doesn't (really) support it either.
* If you want/need support for layers/animated Gif files, create a similar method to this one that takes a BitmapFrame as an argument and then...
* 1. Instanciate the appropriate BitmapDecoder.
* 2. Iterate over the BitmapDecoders frames, feeding them to the new method.
* 3. Store the returned images in a collection of images.
*
* Finally, i opted to use a PngBitmapEncoder here which supports image transparency.
*/
var bitmapEncoder = new PngBitmapEncoder();
bitmapEncoder.Frames.Add(BitmapFrame.Create(new Uri(filename)));
// Use a memorystream as a handover from one file format to another.
using (var memoryStream = new MemoryStream()) {
bitmapEncoder.Save(memoryStream);
/* We MUST create a copy of our image from stream, MSDN specifically states that the stream must remain
* open throughout the lifetime of the image.
* We cannot instanciate the Image class, so we instanciate a Bitmap from our temporary image instead.
* Bitmaps are derived from Image anyways, so this is perfectly fine.
*/
var tempImage = Image.FromStream(memoryStream);
return new Bitmap(tempImage);
}
}
基于this answer...
...我会说这是一个不错的选择,因为它可以让您保持在 .Net 框架内。
请记住,当方法返回时,您确实会返回一个 PNG 图像。如果您在其上调用Image.Save(string),您将保存一个PNG文件,无论您将其保存为什么扩展名。
有一个重载Image.Save(string, ImageFormat) 将使用预期的文件格式保存文件。但是,将这种重载与 ImageFormat.Jpeg 一起使用会导致生成的文件在不止一个级别上损失质量。
这可以通过使用第三个重载来解决:
foreach (var encoder in ImageCodecInfo.GetImageEncoders()) {
if (encoder.MimeType == "image/jpeg")
image.Save(filename, encoder, new EncoderParameters { Param = new [] { new EncoderParameter(Encoder.Quality, 100L) }});
}
这至少可以保存“几乎”没有压缩的 JPEG。 GDI+ 在这方面仍然做得不好。
然而,无论你如何扭动它。 GDI+ 不如适当的图像库好,后者很可能又是 ImageMagick。您离 GDI+ 越远,您的生活就会越好。
结论 / TL:DR 和其他说明。
问:我可以在 .Net 中加载这些文件吗?
答:是的,只是有点摆弄,并且不使用 GDI+ 来初始加载作为 GDI+ 的文件不支持 JPEG 文件中的 CMYK 颜色空间。
即便如此,GDI+ 缺乏对很多东西的支持,这就是为什么我会推荐一个外部图像库而不是 GDI+。
问: Windows 和
答: 文件的 DPI 和位深度不匹配,这只是证明 Windows JPEG 加载不同从其他应用程序 JPEG 加载例程。只有使用 GDI 或 GDI+ 的应用程序才能看到与 Windows 在显示图像详细信息时相同的信息。
如果您使用的是 Windows 7+,那么它不会使用 GDI+ 来显示信息或图像。它正在使用 WPF 或 WIC 来做到这一点,这在某种程度上是最新的。
问:如果我使用图形程序打开 jpg 文件并重新保存而不更改任何内容,Windows 资源管理器中的文件属性现在匹配/读取正确(72 dpi 和 24 位深度)
答:如果您使用的是 Adobe Photoshop 并使用“保存为网络”,则 JPEG 图像将不会以 CMYK 格式保存。改用“另存为...”,您会发现色彩空间(和位深度)保持不变。
但是,在 Photoshop 中加载我的文件时,我无法重现您在 DPI 和位深度方面的差异。它们在 Windows 和 Photoshop 中报告为相同。