Go语言处理Windows系统的图标ICO文件(中)

本文介绍如何使用Go语言解析ICO文件并提取其中的图标图像。通过理解ICO文件格式,提取多图标资源的ICO文件中的每个图标,包括读取文件头结构、判断文件合法性以及保存为PNG或BMP格式。
摘要由CSDN通过智能技术生成

ICO文件格式

存放在github.com上的源代码链接
Go语言处理Windows系统的图标ICO文件(上)
Go语言处理Windows系统的图标ICO文件(下)


提取ICO文件中的所有图标图像

在上一篇文章中,我们了解了ico文件的结构,在这一篇文章中,我们首先来看看如何将多icon资源的ico文件中的图标图像提取出来。
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

这里顺便提一下,关于WinIconwinIconStruct,在之前的结构体定义的时候,WinIcon 是首字母大写的,意思就是它是包级的,包外可以访问,而winIconStruct是包内的,包外是隐藏,我们包内实现,包内使用。
所以generateFileNameFormatIconToFile 并不是提供给调用ico包的角色使用的。

这里就贴一下(wi *WinIcon) ExtractIconToFile ,我们可以使用该函数将ico文件中所有的icon图标全部提取出来,并保存到磁盘,而(wis winIconStruct) generateFileNameFormat 就是自动生成文件名前缀的函数。生成后效果如下:
ico文件中的所有icon图标提取后的效果
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 :=
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值