解读图标文件
告诉您:图标是怎样支持透明特性的,及各种格式图标的建立
图标文件是一种特殊的位图,它的大小固定(16*16 或32*32像素大小),且支持透明的特性。图标的格式也有24位、8位和4位几种不同的格式,它与位图最大的区别是:位图无法支持透明而图标可以。那图标是怎样做到这一点的呢?原来,图标采用对图像扫描两遍的方式进行记录位图信息的,第一遍扫描每个像素上的颜色数据完成后,重新以1位位图的方式对图像进行第二次扫描,并以辅助信息的方式追加在文件的后面,这样,图标文件的结构就比位图文件多了辅助信息这部分(见图)。
图标文件头 位图信息头 [系统调色盘] 图片信息 辅助信息 icofileheader bmpinfoheader rgbquad rgbdata fby (14bytes) (40bytes) |
为了使图标的某些部分变得透明,在第一遍扫描时,把这部分的颜色值改为黑色,在第二遍扫描时,这部分的颜色为白色,不透明的部分为黑色(0为黑色,1为白色)。这样,图标的背景就变透明了。
我们以32*32像素大小的图标为例来计算一下图标文件的大小:
种类 | 文件头 | 信息头 | 调色盘 | 图片信息 | 辅助信息 | 文件大小 |
24bit | 22 | 40 | 0 | 32*32*3 | 128 | 3262 |
8bit | 22 | 40 | 4*256 | 32*32 | 128 | 2238 |
4bit | 22 | 40 | 4*16 | 32*32/2 | 128 | 766 |
编程举例:(保存为24位透明图标)
|
|
用VB6新建一个工程,命名为WriteIcoFile.vbp。添加一个窗体WriteIcoFile.frm ,caption=” WriteIcoFile”。添加图片框picture1,scalemode=3-pixel;autoredraw=true;autosize=true;borderstyle=0-none。添加一个按钮command1的caption=”BmpToIco”,添加两个标签Label1,label2。粘贴代码:
Option Explicit
Private Type ICONFILEHEADER '22bytes
idReserved As Integer ’为0
idType As Integer ’为1
idCount As Integer ’文件中图标个数,为1
bWidth As Byte ’宽,为16或32
bHeight As Byte ’ 高,为16或32
bColorCount As Byte ’调色盘颜色数量:16或255 或0
bReserved As Byte ’为0
wPlanes As Integer ’为1
wBitCount As Integer ’每个像素占的位数
dwBytesInRes As Long ’图标文件后四项总字节数
dwImageOffset As Long ’图标文件头长度,为22
End Type
Private Type BITMAPINFOHEADER '40 bytes
biSize As Long ’信息头的长度,为40
biWidth As Long ’宽
biHeight As Long ’扫描两遍,值为实实际高度的2 倍
biPlanes As Integer ’为1
biBitCount As Integer ’每个像素占的位数
biCompression As Long ’为0
biSizeImage As Long ’图标文件后三项总字节数
biXPelsPerMeter As Long ’为0
biYPelsPerMeter As Long ’为0
biClrUsed As Long ’为0
biClrImportant As Long ’为0
End Type
Private Type RGBQUAD '4bytes调色盘中颜色
rgbBlue As Byte
rgbGreen As Byte
rgbRed As Byte
rgbReserved As Byte
End Type
Private Type RGBDATA '24bitcolor图像信息中每一个像素包含的RGB
b As Byte
g As Byte
r As Byte
End Type
Private Sub Command1_Click() ’保存为24位透明图标
If Picture1.Height = 32 And Picture1.Width = 32 Then
Dim ifh As ICONFILEHEADER 'ICOfileheader
Dim bih As BITMAPINFOHEADER
Dim rgbd() As RGBDATA
Dim rgbl() As Long
Dim fby() As Byte
Open App.Path & "/temp.ico" For Binary As #1
’设置图标文件头的值
ifh.idReserved = 0
ifh.idCount = 1
ifh.idType = 1
ifh.bHeight = 32
ifh.bWidth = 32
ifh.bColorCount = 0
ifh.bReserved = 0
ifh.wPlanes = 1
ifh.wBitCount = 24
ifh.dwBytesInRes = 3240
ifh.dwImageOffset = Len(ifh)
Put #1, , ifh
’设置图片信息头的值
bih.biSize = Len(bih)
bih.biWidth = 32
bih.biHeight = 64 '!!!
bih.biPlanes = 1
bih.biBitCount = 24
bih.biCompression = 0&
bih.biSizeImage = 3200
bih.biXPelsPerMeter = 0&
bih.biYPelsPerMeter = 0&
bih.biClrUsed = 0&
bih.biClrImportant = 0&
Put #1, , bih
Dim i, j, k As Integer
Dim by, byy As Long
by = 16777215
ReDim rgbd(1023) ’申请1024个像素的RGB空间
’从左下角逐行扫描
For i = 0 To 31
For j = 0 To 31
byy = Picture1.Point(j, 31 - i) ’获得图像上某一点的颜色Long值
If byy = by Then ’如果该点为白色(16777215=RGB(255,255,255))
rgbd(k).r = 0
rgbd(k).g = 0
rgbd(k).b = 0
Else
rgbd(k).b = Int(byy / 65536)
byy = byy - rgbd(k).b * 65536
rgbd(k).g = Int(byy / 256)
rgbd(k).r = byy Mod 256
End If
k = k + 1
Next
Next
Put #1, , rgbd
’计算图标的辅助信息,白色背景部分对应的位的值为1
ReDim fby(127)
k = 0
For i = 0 To 31
For j = 0 To 3 ’每个字节包含8个像素的1位信息
byy = 0
If Picture1.Point(j * 8 + 7, 31 - i) = by Then byy = 1
If Picture1.Point(j * 8 + 6, 31 - i) = by Then byy = byy + 2
If Picture1.Point(j * 8 + 5, 31 - i) = by Then byy = byy + 4
If Picture1.Point(j * 8 + 4, 31 - i) = by Then byy = byy + 8
If Picture1.Point(j * 8 + 3, 31 - i) = by Then byy = byy + 16
If Picture1.Point(j * 8 + 2, 31 - i) = by Then byy = byy + 32
If Picture1.Point(j * 8 + 1, 31 - i) = by Then byy = byy + 64
If Picture1.Point(j * 8, 31 - i) = by Then byy = byy + 128
fby(k) = byy
k = k + 1
Next
Next
Put #1, , fby
Close #1
Label1.Caption = "成功创建" & App.Path & "/temp.ico"
Else
Label1.Caption = "图片大小应为32*32"
End If
End Sub
Private Sub Form_Load()
Label2.Caption = "本例程序展示了图标创建的巅峰水平(能写出24位的透明的标准图标)" & vbCrLf & "为了简化程序,程序中只展示了将32*32带白色背景的的24位位图转化成图标文件的代码。" & vbCrLf & "您如果想创建自已的图标,可以用自己做的图片代替同目录下的temp.bmp" & vbCrLf & "即可做出属于自己的高品质图标。"
Picture1.Picture = LoadPicture(App.Path & "/temp.bmp")
End Sub
编程原理:
窗体导入时,导入同目录下的24位位图“temp.bmp”,command1按下时,判断图片大小是否为32*32,为图标文件头和位图信息头赋值,利用Picture1的Point(x,y) as long方法,从左下角逐行扫描,获得图像的像素颜色的long值,然后经过计算,转换成rgbd(),来获得图像的24位图像信息,需注意的是,把白色背景的值,改为黑色的值(r=0;g=0,b=0),接着,再重新扫描,把白色背景的值设为1,不透明部分值设为0 ,经过计算获得图像的1位图像信息,最后写入同目录下的“temp.ico”。
Long 与(R,G,B)之间的转换:
我说明一下二进制和十进制的转换问题,我们在利用图片框Picture1的PSet (x,y), col和Point(x,y) 来描绘图像和获得图像颜色值时,使用的是Long类型的数据,而24位位图文件中的颜色信息是以(R,G,B)Bytes类型的数据存储的,在(R,G,B)与Long互换的计算中,需要用到二进制与十进制的转换。
R=255 G=255 B=255 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 128 64 32 16 8 4 2 1 这样每8位上的数加起来就有255 RGB(b,g,r)就是把3个8位二进制连成1 个24位的数 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 8388608…………………..65536…………………………512 256………………… 8 4 2 1 这样24位上的数加起来就有16777215 即RGB(255,255,255)=16777215
R=1 G=1 B=1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 这样每8位上的数加起来就有1 RGB(b,g,r)把3个8位二进制连成1 个24位的数 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 65536 0 0 0 0 0 0 0 256 0 0 0 0 0 0 0 1 这样24位上的数加起来就有65739 即RGB(1,1,1)=65793
|
把(R,G,B)转换成Long用RGB(b,g,r)就可以完成。(用算式:b *65536 + g *256 + r 来计算也可以)
那怎样把Long转换成(R,G,B)呢?我们可以用逆运算的计算方法来完成,用上面举的例了来说,
如果一个颜色的Long值为65793
B=Int(Long/65536) =Int(1.00392) =1
G=Int((Long – B * 65536) / 256) =Int(257 / 256) = 1
R=(Long – R * 65536) Mod 256 = 257 Mod 256 =1
经过计算,Long值为65793,可转换成(1,1,1)。
编程举例2:(保存为8位256色不透明图标)
在上面的窗体中,添加一个按钮command2 ,caption=”Save256ColorIco”,编写代码:
Private Sub Command2_Click() ’保存为8位256色不透明图标
If Picture1.Height = 32 And Picture1.Width = 32 Then
Dim ifh As ICONFILEHEADER 'ICOfileheader
Dim bih As BITMAPINFOHEADER
Dim rgbq(255) As RGBQUAD
Dim rgbby(1023) As Byte
Dim fby(127) As Byte
Open App.Path & "/temp2.ico" For Binary As #1
ifh.idReserved = 0
ifh.idCount = 1
ifh.idType = 1
ifh.bHeight = 32
ifh.bWidth = 32
ifh.bColorCount = 255
ifh.bReserved = 0
ifh.wPlanes = 1
ifh.wBitCount = 8
ifh.dwBytesInRes = 2216
ifh.dwImageOffset = Len(ifh)
Put #1, , ifh
bih.biSize = Len(bih)
bih.biWidth = 32
bih.biHeight = 64 '!!!
bih.biPlanes = 1
bih.biBitCount = 8
bih.biCompression = 0&
bih.biSizeImage = 2176
bih.biXPelsPerMeter = 0&
bih.biYPelsPerMeter = 0&
bih.biClrUsed = 0&
bih.biClrImportant = 0&
Put #1, , bih
Dim i, j, k As Integer
Dim r, g, b, ra, ga, ba, lIndex As Integer
Dim by, byy As Long
by = 16777215
’创建调色盘中的颜色(创建216个,其余为初始值0,0,0,0)
For b = 0 To 255 Step 51
For g = 0 To 255 Step 51
For r = 0 To 255 Step 51
With rgbq(lIndex)
.rgbRed = r: .rgbGreen = g: .rgbBlue = b
End With
lIndex = lIndex + 1
Next r
Next g
Next b
Put #1, , rgbq
For i = 0 To 31
For j = 0 To 31
byy = Picture1.Point(j, 31 - i)
If byy = by Then
rgbby(k) = 0
Else
’算出某点上的R,G,B的值
b = Int(byy / 65536)
byy = byy -b * 65536
g = Int(byy / 256)
r = byy Mod 256
’算出该点上的颜色在调色盘中的ID号,并存入像素流rgbby() 中
ra = Cint(r/51)
ga = Cint(g/51)
ba = Cint(b/51)
rgbby(k) = ba * 36 + ga * 6 + ra
End If
k = k + 1
Next
Next
Put #1, , rgbby
’不对fby() 赋值,那么fby() 中的每个数值都为初始值0 ,图标没有透明部分
Put #1, , fby
Close #1
Label1.Caption = "成功创建" & App.Path & "/temp2.ico"
Else
Label1.Caption = "图片大小应为32*32"
End If
End Sub
用计算的方法获得像素流:
调色盘中的颜色可以自由创建,我们把每一种颜色分成5档共6个不同的值(0,52,102,153,204,255),然后按一定的排列顺序,组成0~215 共216种不同的颜色。
接着我们在扫描图片时,获得每个像素上的颜色Long ,用计算的方法算出(R,G,B)的值,再根据上面创建调色盘的方法,计算出与该点颜色相近的调色盘中的某一个颜色,在调色盘中排列的序号ID,把这个ID值存入Byte 类型的rgbby() 数组中,就形成了像像素流。
必须注意的是,由于位图和图标的像素流是倒置的,所以,除了要从左下角开始扫描外,还要在保存的过程中把RGB 倒置成BGR。
童跃 福建省华安县际头小学
2007 年 5 月 1 日