为什么 Bitmap 构造函数不保留框架信息?在下面的示例中,使用imagePath
3 页 tiff 图像作为路径,第一个测试可以很好地显示三页的不同大小。然而,第二个测试仅显示第一页的大小。
/* Returns the following...
Page:1 W/H: 7650/9900
Page:2 W/H: 3600/2025
Page:3 W/H: 3600/2025
Page:1 W/H: 7650/9900
(missing page 2 and 3)
*/
// Test opening using the Image.FromFile method
using (Image img = Image.FromFile(imagePath))
{
int p = img.GetFrameCount(FrameDimension.Page);
for (int i = 0; i < p; i++)
{
img.SelectActiveFrame(FrameDimension.Page, i);
Debug.WriteLine($"Page:{i + 1} W/H: {img.Width}/{img.Height}");
}
}
// Testing opening using the Bitmap constructor
using (Image orgImg = Image.FromFile(imagePath))
{
using (Image img = new Bitmap(orgImg))
{
int p = img.GetFrameCount(FrameDimension.Page);
for (int i = 0; i < p; i++)
{
img.SelectActiveFrame(FrameDimension.Page, i);
Debug.WriteLine($"Page:{i + 1} W/H: {img.Width}/{img.Height}");
}
}
}
作为后续问题,执行以下操作可以得到与第一个测试相同的结果。但是有没有更好的方法可以像第一个测试一样再次访问整个帧集合,而无需创建另一个 Bitmap/Image 对象?
/* Returns the following...
Page:1 W/H: 7650/9900
Page:2 W/H: 3600/2025
Page:3 W/H: 3600/2025
*/
// Testing opening by creating a new Image object
using (Image orgImg = Image.FromFile(imagePath))
{
using (Image img = (Image)orgImg.Clone())
{
int p = img.GetFrameCount(FrameDimension.Page);
for (int i = 0; i < p; i++)
{
img.SelectActiveFrame(FrameDimension.Page, i);
Debug.WriteLine($"Page:{i + 1} W/H: {img.Width}/{img.Height}");
}
}
}
作为前言:
System.Drawing
是一个令人惊讶的 GDI+ (又名gdiplus
) 的薄包装。System.Drawing.Image
是abstract class
(也是 的超类型Bitmap
);对应于GDI+ 的 C++ 类Image
。它只有两个子类:System.Drawing.Bitmap
它对应于GDI+ 的 C++ 类Bitmap
。System.Drawing.Imaging.Metafile
对应于GDI+ 的 C++ 类Metafile
- 这些是(可缩放的)非光栅图像,基本上是 GDI 绘图操作的线性序列(因此有点像 PostScript),但采用二进制格式,可能被认为是当今 SVG 的古老祖先。但是,元文件不在本答案的讨论范围内。Bitmap
具有令人惊讶的多功能性,因为它可以表示以下任何内容:.bmp
- 本质上只是带有额外文件头的序列化 DDB 位图。.ico
/.cur
文件格式也是多图像光栅图像文件,但 GDI+ 只会加载第一幅图像,而无法加载文件中包含的其他图像。为了解决这个问题,.NET 包含了和类,Icon
它们Cursor
提供了一些(基本的)支持,用于获取/文件中包含的图标目录结构中的其他图像,但这也超出了范围。.ico
.cur
当您使用
Image.FromFile(String filename)
加载单图像光栅文件(如.bmp
、.jpeg
或.png
)时,图像将作为新Bitmap
对象返回,并且任何支持的元数据(例如 JPEG 文件中的 EXIF 标签)都会加载到PropertyItems
集合中。当您使用
Image.FromFile(String fileName)
加载多图像光栅文件(如动画.gif
或多页 TIFF)时,它还将返回一个Bitmap
对象,该对象代表(并包含)文件中的第一个图像 - 而 GIF 或 TIFF 文件中的任何其他帧/页面/图像只能通过在该特定对象上使用该方法来“交换” - 该方法会显着改变对象,包括更新大多数人合理地认为一旦加载就固定且不可变的属性(例如,和,疯狂!)。SelectActiveFrame
Bitmap
SelectActiveFrame
Bitmap
.PixelFormat
.Width
.Height
使用时
new Bitmap(Image orignal)
请new Bitmap(Image original, Int32 width, Int32 height)
注意,这不会创建任何类型的状态副本或对象克隆。Image source
相反,它只是通过临时上下文将其绘制到新对象上Bitmap
Graphics
。Bitmap
有很多这些便利/辅助构造函数,包括许多在 GDI+ 中没有等效项的构造函数 - 有些人甚至可能称它为混乱- 我相信这是因为System.Drawing
它是 2001/2002 年 .NET Framework 1.x 的一部分,当时微软的 .NET 库 API 设计人员仍在提出关于 OOP 库应该是什么样子的不同想法/实验 - 因此为什么会有令人困惑的构造函数、静态工厂和克隆方法,它们似乎都做同样的事情(直到你意识到它们没有 - 正如你所发现的)。因此,如果您确实想要创建
Bitmap
从磁盘上的多图像文件加载的对象的正确克隆,那么您需要使用Image.Clone()
继承的Bitmap
,但其返回类型是Object
(因为这早于.NET对协变返回类型的支持),您需要自己添加转换(也许定义一个扩展方法来减少将来的麻烦?)如果您只是想迭代文件中包含的图像(并且您没有对图像数据进行任何更改:它们都是只读的),那么就不需要克隆任何东西,您应该能够重新迭代帧(即
0 < i < Image.FrameCount
)。如果您希望能够同时直接访问多个 GIF 帧(或 TIFF 页面)的光栅数据,那么不幸的是,您似乎必须对
.Clone()
要Bitmap
打开的每个可同时访问的光栅帧执行 - 或者在调用之间循环复制每个帧FrameCount
,因为如果您在没有先调用的情况下LockBits+UnlockBits
调用,GDI+ 会引发异常。.SelectActiveFrame
UnlockBits
...所以这不起作用:
相反,你必须这样做:
我花了一些时间深入研究
System.Drawing
GDI+ 的内部结构,看看是否有任何方法可以测试/检查Image
或是否Bitmap
是从文件加载的(如果可用,则包含所有其他帧)或是否是单帧副本(信息丢失)。不幸的是,我认为无法区分(例如)包含 1 张图像的正确加载的 TIFF 文件和Bitmap
从多图像 TIFF 文件错误地克隆对象(使其仅包含 1 张图像)的情况。