ICO文件格式
存放在github.com上的源代码链接
Go语言处理Windows系统的图标ICO文件(上)
Go语言处理Windows系统的图标ICO文件(下)
提取ICO文件中的所有图标图像
在上一篇文章中,我们了解了ico文件的结构,在这一篇文章中,我们首先来看看如何将多icon资源的ico文件中的图标图像提取出来。
从我选中的部分,我们已经知道了,该ico文件有25个ico图像(png和bmp),从开头的22个字节后,每16字节为一个ico文件的header,直到第一个header中的偏移量为止(19-22字节所描述的偏移量)。
那么将我们的理解转换为代码如下:
1、通过22个字节的header
中,最后4个字节,我们获取icon图标头结构偏移量0x960100
,即:‘406bytes offset’。
2、我们获取的0-406的内容就是我们第一个ico文件中所有icon图标的structure header
。
3、根据icon数量来循环处理22个字节后的其他icon文件的header
声明与定义一个读取ico文件的函数:
func LoadIconFile(rd io.Reader) (icon *WinIcon, err error)
如果我们完成了读取,则返回一个*WinIcon
对象的指针,如果失败则返回错误对象。
那么我们的错误应该有哪些呢?
1、非法的文件(非法的ico文件)
a、文件的长度连22个字节都没(说明根本就不是一个ico文件,只是扩展名是.ico)
b、文件的长度的确超过了22字节,但是这22字节的内容却和ico的header结构不匹配
c、可能传入的读取像不是一个*os.File
,亦或则不是一个文件呢…可能需要做些基本判断
d、在读取数据中,会不会出现越界的情况呢?
先暂时想到这里。
这里我定义了一些错误信息对象:
// 定义变量
var (
// 错误信息
ErrIcoInvalid = errors.New("ico: Invalid icon file") // 无效的ico文件
ErrIcoReaders = errors.New("ico: Reader type is not os.File pointer") // LoadIconFile的io.Reader参数不是文件指针
ErrIcoFileType = errors.New("ico: Reader is directory, not file") // io.Reader的文件指针是目录,不是文件
ErrIconsIndex = errors.New("ico: Slice out of bounds") // 读取ico文件时,可能出现的切片越界错误
)
定义两个常量:
// 定义常量
const (
fileHeaderSize = 6 // 文件头的大小
headerSize = 16 // icon图标的头结构大小
)
所以我们还需要一个函数来判断是否是ico文件:
func getIconHeader(b []byte) (wih *winIconHeader, err error)
在这个函数中,我们判断文件合法性,如果非法,则返回err
,如果合法,则提取icon的结构。
如果是单icon图标的ico文件,那么结构中的文件数量则为1,如果是多icon图标的ico文件,则文件数量为n,我们可以根据这个n来循环。或则为1则直接读取icon图标数据。
文件头只有6个字节,只是做一个简单判断,即3个部分,保留字段、是否是ico的marker,icon图标的数量
getIconHeader
的代码实现:
if len(b) != fileHeaderSize {
return nil, ErrIcoInvalid
}
reserved := binary.LittleEndian.Uint16(b[0:2])
filetype := binary.LittleEndian.Uint16(b[2:4])
imagecount := binary.LittleEndian.Uint16(b[4:6])
if reserved != 0 || filetype != 1 || imagecount == 0 {
return nil, ErrIcoInvalid
}
header := &winIconFileHeader{
ReservedA: reserved,
FileType: filetype,
ImageCount: imagecount,
}
return header, nil
我们获取到了文件的头结构后,也就获得了icon图标的数量,根据数量我们可以控制循环的次数。
这里是部分LoadIconFile
函数的代码:
// 创建一个 winIconStruct 数组切片
icos := make([]winIconStruct, int(icoHeader.ImageCount))
// 根据文件头中表示的icon图标文件的数量进行循环
structOffset := fileHeaderSize
// 这里的icoHeader就是文件头,ImageCount就是我们获取的ico图标数量
for i := 0; i < int(icoHeader.ImageCount); i++ {
// data 是怎个文件的[]byte数据
wis := getIconStruct(data, structOffset, headerSize)
structOffset += headerSize
icos[i] = *wis
}
// 这个循环的意义在于,我们将ico图标的头结构全部拿到,拿到这个就可以
// 根据ico图标的头结构信息来获取偏移量,从而获取图标图像的数据
// 创建 WinIcon 对象
ico = &WinIcon{
fileHeader: icoHeader,
icos: icos,
data: data,
}
return ico, nil
获取所有数据:
func getFileAll(rd *bufio.Reader, size int64) (fb []byte, err error)
实现:
data := make([]byte, size)
// 丢弃1-6字节内容(文件头)
// if _, err := rd.Discard(fileHeaderSize); err != nil {
// return nil, err
// }
// size = size - fileHeaderSize
for i := int64(0); i < size; i++ {
b, err := rd.ReadByte()
if err != nil {
return nil, err
}
data[i] = b
}
return data, nil
当我们有了文件的所有数据,以及icon图标的所有头结构后,我们就可以获取图标数据了:
func (wi *WinIcon) GetImageData(index int) (d []byte, err error)
func (wi *WinIcon) getImageData(data []byte, offset, datasize int) []byte
getImageData
是包内的private:
成员函数,GetImageData
是包外public:
成员函数
从参数上看GetImageData
仅需要传递索引即可,getImageData
需要传递的内容是所有数据data,以及ico文件在所有数据中的偏移量,以及数据大小(length长度),最后返回ico的图像数据:[]byte类型。
getImageData
代码实现:
var d = make([]byte, datasize) // 不建议 data[offset:length] 采用复制数据会相对安全些
for i, j := offset, 0; i < datasize+offset; i++ {
d[j] = data[i]
j++
}
return d
GetImageData
代码实现:
if index >= wi.getIconsHeaderCount() || index < 0 {
return nil, ErrIconsIndex
}
wis := wi.icos[index]
db := wi.getImageData(wi.data, int(wis.ImageOffset), int(wis.ImageDataSize))
return db, nil
wi.getIconsHeaderCount
获取我们已经的得到的icon图标头结构的数量。
基本上而言,以上代码就是读取数据的主要内容。完整代码请见页面中的github.com的链接。
好了,当我们实现了ico文件的读取/解析功能后,我们就可以实现提取数据的功能了。
函数签名:
func (wis winIconStruct) generateFileNameFormat(prefix string, width, height, bit int) string
func (wis winIconStruct) IconToFile(path string, data []byte) error
func (wi *WinIcon) ExtractIconToFile(filePrefix, filePath string) error
func (wi *WinIcon) IconToFile(filePath string, index int) error
这里顺便提一下,关于WinIcon
、winIconStruct
,在之前的结构体定义的时候,WinIcon
是首字母大写的,意思就是它是包级的,包外可以访问,而winIconStruct
是包内的,包外是隐藏,我们包内实现,包内使用。
所以generateFileNameFormat
、IconToFile
并不是提供给调用ico包的角色使用的。
这里就贴一下(wi *WinIcon) ExtractIconToFile
,我们可以使用该函数将ico文件中所有的icon图标全部提取出来,并保存到磁盘,而(wis winIconStruct) generateFileNameFormat
就是自动生成文件名前缀的函数。生成后效果如下:
favicon_all.ico 这个文件就是多icon图标结构的ico文件,使用了30天试用版的某IconWorkshop软件生成(我不是美术,所以花钱买,貌似辱没了Gopher呀,而且买了,我就没必要写这个教程了?,我的win10三套都是正版,office正版,Xmind正版…,基本上我很少用盗版,嗯…不好意思,跑题了??),2-4个ico文件,分辨率为0x0,是因为图标的width和height数据段使用1byte,前面说了,1byte的最大正整数为255,而256这个数字则会被储存为0x00,高位丢失。所以在ico文件中如果icon头结构中的w,h字段值为0,那么如果没有出错的话,分辨率则为256x256pixel。
func (wi *WinIcon) ExtractIconToFile(filePrefix, filePath string) error
ExtractIconToFile
的实现:
for _, v := range wi.icos {
fileName := v.generateFileNameFormat(filePrefix,
int(v.Width),
int(v.Height),
int(v.BitsPerPixel))
fp := filepath.Join(filePath, fileName)
d := wi.getImageData(wi.data, int(v.ImageOffset), int(v.ImageDataSize))
if err :=