ICO文件多版本图像提取技术

在图形用户界面(GUI)编程中,图标文件(.ico)是一种常见的资源文件格式,它允许存储一个或多个图像,通常用于应用程序的图标。尽管GDI+提供了丰富的图形处理功能,但在处理ICO文件时,它缺少一些关键功能,比如查看单个ICO文件中包含的不同版本的图标。本文将展示如何访问并利用这些不同版本的图像。

由于这个话题非常受欢迎,决定使用C#重新编写代码。C#版本与VB版本基本相同,但它提供了更多关于图标文件内部结构的细节。

图标文件(ICO)

通常,图标文件包含同一图像的多个版本。这些文件通常包含图像的较小和较大版本,这样就不必调整图像大小,从而避免了分辨率的损失。

"new Icon"的局限性

Visual Studio中的新图标构造函数对多个图像版本的支持并不理想。虽然可以指定请求的图标的高度和宽度,但无法知道应该请求什么(如果不知道文件中有什么)。为了解决这个限制,必须直接查看ICO文件,并提取想要利用的位和字节。创建了一个图表来说明图标文件的内部布局。

由于空间原因,只在上述示例中放置了两个图标,但实际上可以有任意数量的图标。

图标头

图标头是访问整个文件的关键。这个6字节的块告诉正在访问的文件中有多少个图标,以及正在访问的文件的类型(0代表位图,1代表图标)。

图标条目

读取图标头之后,将知道这个图标文件中有多少个图标。然后可以安全地读取那么多的图标条目块。图标条目块不包含图像信息,它们包含图像字节的偏移量和长度。

打开图标文件

最简单的方法是创建一个FileStream,并将文件转储到一个字节数组中。然后可以将字节数组加载到Stream对象中。以下代码将演示如何操作。

Private Function readIcoFile(ByVal filename As String) As MemoryStream ' 打开文件 Dim icoBinaryFile As New FileStream(filename, FileMode.Open, FileAccess.Read) ' 创建字节数组并读取 Dim byteArray(icoBinaryFile.Length) As Byte icoBinaryFile.Read(byteArray, 0, icoBinaryFile.Length) icoBinaryFile.Close() ' 用字节数组加载流 icoStream = New MemoryStream(byteArray) icoStream.Seek(0, SeekOrigin.Begin) ' 调试,这些值应该是相同的! Console.WriteLine("Number of bytes: " & byteArray.Length) Console.WriteLine("Length of Stream: " & icoStream.Length) End Function

读取数据

发现最好的方法是创建类。第一个类是图标头类,它只包含它读取的元素的定义以及一个新的过程,该过程将从流中读取信息。

Private Class iconHeader Public Reserved As Short ' 总是0 Public Type As Short ' 0=Bitmap, 1=Icon Public Count As Short ' 图标数量 ' Sub: New ' 目的:从原始ICO文件中读取6字节的头部 ' 注意:Short或Int16是2个字节 Public Sub New() Dim icoFile As New BinaryReader(icoStream) Reserved = icoFile.ReadInt16 Type = icoFile.ReadInt16 Count = icoFile.ReadInt16 End Sub End Class

下一步几乎是一样的,但是处理的是图标条目数据。

Private Class iconEntry Public Width As Byte ' 图像的宽度(像素) Public Height As Byte ' 图像的高度(像素) Public ColorCount As Byte ' 图像中的颜色数量(如果>=8bpp则为0) Public Reserved As Byte ' 保留(必须为0) Public Planes As Short ' 颜色平面 Public BitCount As Short ' 每像素位数 Public BytesInRes As Integer ' 这个资源有多少字节? Public ImageOffset As Integer ' 文件中的这个图像在哪里? ' Sub: New ' 目的:从原始ICO文件中读取16字节的头部 ' 注意:Byte是1个字节 ' Short或Int16是2个字节 ' Integer或Int32是4个字节 Public Sub New(ByVal Index As Integer) Dim icoFile As New BinaryReader(icoStream) Width = icoFile.ReadByte Height = icoFile.ReadByte ColorCount = icoFile.ReadByte Reserved = icoFile.ReadByte Planes = icoFile.ReadInt16 BitCount = icoFile.ReadInt16 BytesInRes = icoFile.ReadInt32 ImageOffset = icoFile.ReadInt32 End Sub End Class

现在有了所有的偏移量、大小、颜色信息和图像的长度。

构建图像

在这里实际上是使用从头部和条目中提取的信息来构建一个图标。通过创建另一个流并使用BinaryWriter来填充数据来实现这一点。下面是一个例子:

Private Function buildIcon(ByVal index As Integer) As Icon Dim thisIcon As iconEntry = icons(index) ' 分配图标字节数组的空间 Dim icoByteArray(thisIcon.BytesInRes) As Byte ' 创建流 Dim newIcon As New MemoryStream Dim writer As New BinaryWriter(newIcon) ' 这个文件中只有一个图标 Dim newCount As Short = 1 ' 6字节+16字节是新的偏移量 Dim newOffset As Integer = 22 Console.WriteLine("Icon Index: " & index & ", Offset: " & thisIcon.ImageOffset) ' 写文件 With writer .Write(icoHeader.Reserved) .Write(icoHeader.Type) .Write(newCount) Console.WriteLine("Header written: " & newIcon.Position) .Write(thisIcon.Width) .Write(thisIcon.Height) .Write(thisIcon.ColorCount) .Write(thisIcon.Reserved) .Write(thisIcon.Planes) .Write(thisIcon.BitCount) .Write(thisIcon.BytesInRes) .Write(newOffset) Console.WriteLine("Image Header written: " & newIcon.Position) ' 从流中读取图标 icoStream.Seek(thisIcon.ImageOffset, SeekOrigin.Begin) icoStream.Read(icoByteArray, 0, thisIcon.BytesInRes) ' 写出去 .Write(icoByteArray) .Flush() Console.WriteLine("Image written: " & newIcon.Position) End With ' 移动到开始 newIcon.Seek(0, SeekOrigin.Begin) Dim thisImage As Icon = New Icon(newIcon, thisIcon.Width, thisIcon.Height) writer.Close() Return thisImage End Function

注意,几乎只是写回了读取的数据的副本(做了一些改动)。所做的改动是:

' 这个文件中只有一个图标 Dim newCount As Short = 1 ' 正在移动图像偏移量,使其紧接在头部之后 Dim newOffset As Integer = 22
  • count - ICO文件中的图标数量
  • sizes - 包含ICO中图标大小的Size数组
  • image - 返回特定图标的图像
  • findIcon - 返回图像,但可以指定是否想要最大或最小的图像
Public thisIcon As multiIcon(filename) ' 这将创建类的一个新的实例。 PictureBox1.Image = thisIcon.image(ComboBox1.SelectedIndex).ToBitmap ' 这将加载一个图标到图片框中。 PictureBox1.Image = thisIcon.findIcon(multiIcon.iconCriteria.Largest).ToBitmap() PictureBox1.Image = thisIcon.findIcon(multiIcon.iconCriteria.Smallest).ToBitmap() ' 这将加载特定版本的图标到图片框中。 Dim size As Size For Each size In thisIcon.sizes ComboBox1.Items.Add(size.ToString) Next ' 这将列出当前可用的所有图标的大小。
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485