精华内容
下载资源
问答
  • Go语言处理Windows系统的图标ICO文件(上) Go语言处理Windows系统的图标ICO文件(下)… 编写中 提取ICO文件中的所有图标图像 在上一篇文章中,我们了解了ico文件的结构,在这一篇文章中,我们首先来看看如何将多icon...

    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 := v.IconToFile(fp, d); err != nil {
    		return err
    	}
    }
    return nil
    

    这里我贴出package的测试ico_test.go

    package ico
    
    import (
    	"log"
    	"os"
    	"path/filepath"
    	"testing"
    )
    
    func TestLoadIconFile(t *testing.T) {
    	var fs *os.File
    	defer func() {
    		if fs != nil {
    			fs.Close()
    		}
    		if err := recover(); err != nil {
    			log.SetFlags(log.Ldate | log.Ltime | log.Llongfile)
    			log.Printf("LoadIconFile() = Error:%v\r\n", err)
    			os.Exit(1)
    		}
    	}()
    	path := "../testico/"
    	file := "favicon_all.ico"
    	filePath := filepath.Join(path, file)
    	fs, err := os.Open(filePath)
    	if err != nil {
    		panic(err)
    	}
    	wi, err := LoadIconFile(fs)
    	if err != nil {
    		panic(err)
    	}
    	// if err := wi.ExtractIconToFile("test", "../testdata/", 0); err != nil {
    	// 	panic(err)
    	// }
    	// for _, v := range wi.icos {
    	// 	fmt.Printf("image width:%v, height:%v\r\n", v.Width, v.Height)
    	// 	fmt.Printf("image offset:%v, datesize:%v\r\n", v.ImageOffset, v.ImageDataSize)
    	// 	d := wi.getImageData(wi.data, int(v.ImageOffset), int(v.ImageDataSize))
    	// 	fn := fmt.Sprintf("../testico/%vx%v@%v.ico", v.Width, v.Height, v.BitsPerPixel)
    	// 	err := ioutil.WriteFile(fn, d, 0)
    	// 	if err != nil {
    	// 		fmt.Println(err)
    	// 	}
    	// }
    	if err := wi.ExtractIconToFile("test", "../testico/"); err != nil {
    		panic(err)
    	}
    }
    

    我们使用go语言封装的时候,没必要写main package和main函数来跑代码,golang给我们自带了UT,善加利用,即可提高效率和质量。

    提取后的png文件头部:
    png图像文件的开头部分内容
    提取后的dib(bitmap)文件头部:
    bmp图像文件的dib头

    我们需要实现对png或bmp的数据检测函数。
    以下是具有像素格式RGB24的2×2像素,24位位图(Windows DIB标题BITMAPINFOHEADER)的示例:
    放大显示的效果
    bmp的文件结构
    以下是具有像素格式ARGB32的alpha通道(Windows DIB标题BITMAPV4HEADER)中具有不透明度值的4×2像素,32位位图的示例:
    放大显示的效果
    ARGB32位图的结构
    PNG文件头结构:
    PNG File Headericon全部提取与单个提取
    关于提取的*.ico文件数据,实际上还需要进一步处理,bitmap需要写一个bmp头,因为ico文件中的bitmap icon图标是没有BMP header,只有DIB header的。而png没有这个问题,我们需要识别一下后,bmp的保存为 *.bmp,png的保存为*.png。有了提取,我们还需要实现根据命令行参数将多个bmp或png文件打包为ico文件结构并输出ico文件的功能。而这些内容,我们将在下一集教程中讲解。

    下面是读取ico文件,实现了全部提取及单独提取及单独提取并保存为单icon图标的ico文件的功能:
    打包功能还没有写,下一章教程实现。

    /*
       _____       __   __             _  __
      ╱ ____|     |  ╲/   |           | |/ /
     | |  __  ___ |  ╲ /  | __  _ _ __| ' /
     | | |_ |/ _ ╲| |╲ /| |/ _`  | '__|  <
     | |__| |  __/| |   | (  _|  | |  | . ╲
      ╲_____|╲___ |_|   |_|╲__,_ |_|  |_|╲_╲
     可爱飞行猪❤: golang83@outlook.com  ???
     Author Name: GeMarK.VK.Chow奥迪哥  ???
     Creaet Time: 2019/05/25 - 07:51:34
     ProgramFile: ico.go
     Description:
    			  Windows系统的ico文件工具包
    */
    
    package ico
    
    import (
    	"bufio"
    	"bytes"
    	"encoding/binary"
    	"errors"
    	"fmt"
    	"io"
    	"io/ioutil"
    	"os"
    	"path/filepath"
    	"runtime"
    )
    
    // 定义常量
    const (
    	typeUKN          = iota // unknow type
    	typeBMP                 // bmp ico
    	typePNG                 // png ico
    	fileHeaderSize   = 6    // 文件头的大小
    	headerSize       = 16   // icon图标的头结构大小
    	bitmapHeaderSize = 14   // 位图文件头
    	dibHeaderSize    = 40   // dib结构头
    )
    
    // 定义变量
    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文件时,可能出现的切片越界错误
    )
    
    // 类型定义
    // 定义icon图标数据的存放
    type (
    	ICONTYPE      int
    	WinIconData   [][]byte
    	WinIconStruct []winIconStruct
    )
    
    // 定义 Windows 系统的 Ico 文件结构
    type WinIcon struct {
    	fileHeader *winIconFileHeader // 文件头
    	icos       WinIconStruct      // icon 头结构
    	icod       WinIconData        // 单独的icon图标的数据
    	data       []byte             // 所有ico文件数据
    }
    
    type winIconFileHeader struct {
    	ReservedA  uint16 // 保留字段,始终为 '0x0000'
    	FileType   uint16 // 图像类型:'0x0100' 为 ico,'0x0200' 为 cur
    	ImageCount uint16 // 图像数量:至少为 '0x0100' 即 1个图标
    }
    
    type winIconStruct struct {
    	Width         uint8  // 图像宽度
    	Height        uint8  // 图像高度
    	Palette       uint8  // 调色板颜色数,不使用调色版为 '0x00'
    	ReservedB     uint8  // 保留字段,始终为 '0x00'
    	ColorPlanes   uint16 // 在ico中,指定颜色平面,'0x0000' 或则 '0x0100'
    	BitsPerPixel  uint16 // 在ico中,指定每像素的位数,如:'0x2000' 32bit
    	ImageDataSize uint32 // 图像数据的大小,单位字节
    	ImageOffset   uint32 // 图像数据的偏移量
    }
    
    type dibHeader struct {
    	dibSize        uint32 // 4bytes
    	bitmapWidth    uint32 // 4bytes
    	bitmapHeight   uint32 // 4bytes
    	colorPlanes    uint16 // 2bytes
    	BitsPerPixel   uint16 // 2bytes
    	markerBI_RGB   uint32 // 4bytes
    	originalSize   uint32 // 4bytes
    	printResH      uint32 // 4bytes
    	printResV      uint32 // 4bytes
    	Palette        uint32 // 4bytes
    	importantColor uint32 // 4bytes
    }
    
    type bitmapHeader struct {
    	bitmapID         uint16
    	fileSize         uint32
    	unusedA          uint16
    	unusedB          uint16
    	bitmapDataOffset uint32
    }
    
    // createBitmapHeader 创建位图文件头结构
    func createBitmapHeader(datasize int) *bitmapHeader {
    	return &bitmapHeader{
    		bitmapID:         binary.LittleEndian.Uint16([]byte{0x42, 0x4d}),
    		fileSize:         uint32(datasize + bitmapHeaderSize),
    		unusedA:          0,
    		unusedB:          0,
    		bitmapDataOffset: uint32(bitmapHeaderSize + dibHeaderSize),
    	}
    }
    
    // GetIconType 获取icon的数据类型
    func GetIconType(d []byte) ICONTYPE {
    	if checkDIBHeader(d) {
    		return typeBMP
    	}
    	if checkPNGHeader(d) {
    		return typePNG
    	} else {
    		return typeUKN
    	}
    }
    
    // checkPNGHeader 检测是否是png ico数据
    func checkPNGHeader(d []byte) bool {
    	if len(d) < 8 {
    		return false
    	}
    	if bytes.Compare(d[0:8], []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}) != 0 {
    		return false
    	}
    	return true
    }
    
    // headerToBytes 将bitmapHeader位图头结构转换为字节切片
    func (bmh *bitmapHeader) headerToBytes() []byte {
    	d := make([]byte, bitmapHeaderSize)
    	binary.LittleEndian.PutUint16(d[0:2], bmh.bitmapID)
    	binary.LittleEndian.PutUint32(d[2:6], bmh.fileSize)
    	binary.LittleEndian.PutUint16(d[6:8], bmh.unusedA)
    	binary.LittleEndian.PutUint16(d[8:10], bmh.unusedB)
    	binary.LittleEndian.PutUint32(d[10:14], bmh.bitmapDataOffset)
    	return d
    }
    
    // JoinHeader 将bitmapfileheader链接到含有dib头的位图数据前
    func (bmh *bitmapHeader) JoinHeader(d []byte) []byte {
    	h := bmh.headerToBytes()
    	j := [][]byte{h, d}
    	return bytes.Join(j, nil)
    }
    
    // checkDIBHeader 检测是否是bmp ico数据
    func checkDIBHeader(d []byte) bool {
    	if len(d) < 40 {
    		return false
    	}
    	a := d[0:4]
    	b := []byte{0x28, 0, 0, 0}
    	if bytes.Compare(a, b) != 0 {
    		return false
    	}
    	return true
    }
    
    // 将ico文件的数据载入到内存
    func LoadIconFile(rd io.Reader) (icon *WinIcon, err error) {
    	// 类型断言
    	v, t := rd.(*os.File)
    	if !t {
    		return nil, ErrIcoReaders
    	}
    	// 声明与定义变量
    	var (
    		fileSize int64
    		ico      *WinIcon
    	)
    
    	// 获取文件信息及判断是否是文件,而不是目录
    	fi, err := v.Stat()
    	if err != nil {
    		return nil, err
    	}
    	if fi.IsDir() {
    		return nil, ErrIcoFileType
    	}
    	fileSize = fi.Size()
    
    	// 创建缓冲IO的Reader对象窥视6个字节的文件头
    	reader := bufio.NewReader(rd)
    	p, err := reader.Peek(fileHeaderSize)
    	if err != nil {
    		return nil, err
    	}
    
    	// 检测文件头及获取头结构
    	icoHeader, err := getIconFileHeader(p)
    	if err != nil {
    		return nil, err
    	}
    
    	// 获取ico文件的所有数据
    	data, err := getFileAll(reader, fileSize)
    	if err != nil {
    		return nil, err
    	}
    
    	// 创建一个 winIconStruct 数组切片
    	icos := make(WinIconStruct, int(icoHeader.ImageCount))
    	icod := make(WinIconData, int(icoHeader.ImageCount))
    	// 根据文件头中表示的icon图标文件的数量进行循环
    	structOffset := fileHeaderSize
    	for i := 0; i < int(icoHeader.ImageCount); i++ {
    		wis := getIconStruct(data, structOffset, headerSize)
    		icodata := wis.getImageData(data, wis.getIconOffset(), wis.getIconLength())
    		structOffset += headerSize
    		icos[i] = *wis
    		icod[i] = icodata
    	}
    
    	// 创建 WinIcon 对象
    	ico = &WinIcon{
    		fileHeader: icoHeader,
    		icos:       icos,
    		icod:       icod,
    		data:       data,
    	}
    	return ico, nil
    }
    
    // getFileAll 获取ico文件所有数据(不包括文件头的6个字节)
    // rd *bufio.Reader: 对象
    // size int64: 文件大小(我们需要读取的总数量)
    // fb []byte: 文件的所有数据,如果成功读取的话
    // err error: 如果读取出现错误,返回错误
    func getFileAll(rd *bufio.Reader, size int64) (fb []byte, err error) {
    	data := make([]byte, size)
    	for i := int64(0); i < size; i++ {
    		b, err := rd.ReadByte()
    		if err != nil {
    			return nil, err
    		}
    		data[i] = b
    	}
    	return data, nil
    }
    
    // getIconFileHeader 获取文件头结构
    // b []byte: 读取的数据来自这个字节切片
    // wih *winIconFileHeader: 如果获取成功返回 winIconFileHeader对象指针
    // err error: 如果读取发生错误,则返回错误信息
    func getIconFileHeader(b []byte) (wih *winIconFileHeader, err error) {
    	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
    }
    
    // getIconStruct 根据 offset, length 来获取icon图标结构
    // b []byte: 文件数据的字节切片
    // offset int: 偏移量
    // length int: 数据长度
    func getIconStruct(b []byte, offset, length int) (wis *winIconStruct) {
    	var std []byte
    	//std = b[offset:length]
    	std = make([]byte, headerSize)
    	j := 0
    	for i := offset; i < length+offset; i++ {
    		std[j] = b[i]
    		j++
    	}
    	is := &winIconStruct{
    		Width:         std[0],
    		Height:        std[1],
    		Palette:       std[2],
    		ReservedB:     std[3],
    		ColorPlanes:   binary.LittleEndian.Uint16(std[4:6]),
    		BitsPerPixel:  binary.LittleEndian.Uint16(std[6:8]),
    		ImageDataSize: binary.LittleEndian.Uint32(std[8:12]),
    		ImageOffset:   binary.LittleEndian.Uint32(std[12:]),
    	}
    	return is
    }
    
    // getImageData 根据 offset, length 参数获取图标图像数据
    // data []byte: 图像数据的字节切片
    // offset int: 图像数据的偏移量
    // length int: 图像数据的长度
    // return []byte: 返回获取的数据字节切片
    func (wi *WinIcon) getImageData(data []byte, offset, datasize int) []byte {
    	var d = make([]byte, datasize) //data[offset:length]
    	for i, j := offset, 0; i < datasize+offset; i++ {
    		d[j] = data[i]
    		j++
    	}
    	return d
    }
    
    func (wis winIconStruct) getImageData(data []byte, offset, ds int) []byte {
    	var d = make([]byte, ds)
    	for i, j := offset, 0; i < ds+offset; i++ {
    		d[j] = data[i]
    		j++
    	}
    	return d
    }
    
    // ExtractIconToFile 提取 ico 数据到文件
    // filePrefix string: 为前缀,如果传如空字符串,则没有前缀,使用数字和分辨率作为文件名
    // filePath string: 提取的数据写入的路径,空字符串则将文件保存到当前目录
    // 舍弃:--count int: 提取文件的数量,0: 为所有,> 0 则根据已保存的map对象来提取对应数量内容,指定数量超出实际数量则全部提取--
    // 该函数不检测路径的有效性,使用者自己把控,如果路径有问题,会返回error对象
    func (wi *WinIcon) ExtractIconToFile(filePrefix, filePath string) error {
    	var ext string
    	for i, v := range wi.icos {
    		w := v.getIconWidth()
    		h := v.getIconHeight()
    		b := v.getIconBitsPerPixel()
    		d, _ := wi.GetImageData(i)
    		if GetIconType(d) == typeBMP {
    			ext = "bmp"
    		} else {
    			ext = "png"
    			if w == 0 && h == 0 {
    				w = 256
    				h = 256
    			}
    		}
    		fn := v.generateFileNameFormat(filePrefix, ext, w, h, b)
    		if err := wi.IconToFile(filePath, fn, i); err != nil {
    			return err
    		}
    	}
    	return nil
    }
    
    // GetImageData 获取ico图标的图像数据
    // index int: 下标索引,0序
    // 如果越界或读取数据错误,返回 error 对象
    func (wi *WinIcon) GetImageData(index int) (d []byte, err error) {
    	if index >= wi.getIconsHeaderCount() || index < 0 {
    		return nil, ErrIconsIndex
    	}
    	wis := wi.icos[index]
    	offset := wis.getIconOffset()
    	datasize := wis.getIconLength()
    	data := wi.getImageData(wi.data, offset, datasize)
    	return data, nil
    }
    
    // IconToFile 将图标写入文件
    // path string: 文件写入的路径
    // name string: 文件名
    // error 如果写入发生错误,则返回错误信息
    // IconToFile 并不会检测路径是否有效
    func (wi *WinIcon) IconToFile(path, name string, index int) error {
    	if index >= wi.getIconsHeaderCount() || index < 0 {
    		return ErrIconsIndex
    	}
    	wis := wi.icos[index]
    	p := filepath.Join(path, name)
    	d, e := wi.GetImageData(index)
    	if e != nil {
    		return e
    	}
    	// 处理bitmap头结构
    	if GetIconType(d) == typeBMP {
    		w := wis.getIconWidth()
    		h := wis.getIconHeight()
    		b := wis.getIconBitsPerPixel()
    		s := len(d) - dibHeaderSize
    		dib := createDIBHeader(w, h, b, s, 0, 0)
    		err := dib.EditDIBHeader(d)
    		if err != nil {
    			return err
    		}
    		bmh := createBitmapHeader(len(d))
    		d = bmh.JoinHeader(d)
    	}
    	if e := wis.IconToFile(p, d); e != nil {
    		return e
    	} else {
    		return nil
    	}
    }
    
    // IconToIcoFile 将ico文件中的指定icon图标数据写入ico文件
    // path string: 路径(不检查合法性)
    // index int: icon图标的索引
    // error: 如果发生错误返回error对象
    func (wi *WinIcon) IconToIcoFile(path string, index int) error {
    	if index < 0 || index >= len(wi.icos) {
    		return ErrIconsIndex
    	}
    	d := wi.icod[index]
    	wis := wi.icos[index]
    	wis.ImageOffset = fileHeaderSize + headerSize
    	wis.ImageDataSize = uint32(len(d))
    	d = wis.joinHeader(d)
    	if e := ioutil.WriteFile(path, d, getPerm()); e != nil {
    		return e
    	}
    	return nil
    }
    
    // getIconsHeaderCount 获取 icons 图标的结构数量-可能和头结构的ico数量不一致,只是可能
    // 返回值为数量,类型 int
    func (wi *WinIcon) getIconsHeaderCount() int {
    	return len(wi.icos)
    }
    
    // generateFileNameFormat 产生文件名
    func (wis winIconStruct) generateFileNameFormat(prefix, ext string, width, height, bit int) string {
    	return fmt.Sprintf("%s_icon%dx%d@%dbit.%s", prefix, width, height, bit, ext)
    }
    
    // iconToFile 将ico图像数据写入磁盘文件
    func (wis winIconStruct) IconToFile(path string, data []byte) error {
    	if err := ioutil.WriteFile(path, data, getPerm()); err != nil {
    		return err
    	}
    	return nil
    }
    
    func (wis winIconStruct) headerToBytes() []byte {
    	d := make([]byte, fileHeaderSize+headerSize)
    	binary.LittleEndian.PutUint16(d[0:2], 0)
    	binary.LittleEndian.PutUint16(d[2:4], 1)
    	binary.LittleEndian.PutUint16(d[4:6], 1)
    	d[6] = uint8(wis.getIconWidth())
    	d[7] = uint8(wis.getIconHeight())
    	d[8] = wis.Palette
    	d[9] = wis.ReservedB
    	binary.LittleEndian.PutUint16(d[10:12], wis.ColorPlanes)
    	binary.LittleEndian.PutUint16(d[12:14], wis.BitsPerPixel)
    	binary.LittleEndian.PutUint32(d[14:18], wis.ImageDataSize)
    	binary.LittleEndian.PutUint32(d[18:22], wis.ImageOffset)
    	return d
    }
    
    func (wis winIconStruct) joinHeader(d []byte) []byte {
    	h := wis.headerToBytes()
    	j := [][]byte{h, d}
    	return bytes.Join(j, nil)
    }
    
    // getIconOffset 获取icon图像数据的偏移量
    // 返回偏移量数据
    func (wis winIconStruct) getIconOffset() int {
    	return int(wis.ImageOffset)
    }
    
    // getIconLength 获取icon图像数据的长度
    // 返回长度数据
    func (wis winIconStruct) getIconLength() int {
    	return int(wis.ImageDataSize)
    }
    
    // getIconWidth 获取icon图像数据的宽度
    func (wis winIconStruct) getIconWidth() int {
    	return int(wis.Width)
    }
    
    // getIconHeight 获取icon图像数据的高度
    func (wis winIconStruct) getIconHeight() int {
    	return int(wis.Height)
    }
    
    // getIconBitsPerPixel 获取icon图像数据的颜色位数
    func (wis winIconStruct) getIconBitsPerPixel() int {
    	return int(wis.BitsPerPixel)
    }
    
    // createDIBHeader创建DIB头结构
    func createDIBHeader(width, height, bit, size, p, i int) *dibHeader {
    	return &dibHeader{
    		dibSize:        40,             // uint32
    		bitmapWidth:    uint32(width),  // uint32
    		bitmapHeight:   uint32(height), // uint32
    		colorPlanes:    1,              // uint16
    		BitsPerPixel:   uint16(bit),    // uint16
    		markerBI_RGB:   0,              // uint32
    		originalSize:   uint32(size),   // uint32
    		printResH:      2835,           // uint32
    		printResV:      2835,           // uint32
    		Palette:        uint32(p),      // uint32
    		importantColor: uint32(i),      // uint32
    	}
    }
    
    // HeaderToBytes 将DIB的头结构转换为字节切片
    func (dh *dibHeader) HeaderToBytes() []byte {
    	d := make([]byte, dibHeaderSize)
    	binary.LittleEndian.PutUint32(d[0:4], dh.dibSize)
    	binary.LittleEndian.PutUint32(d[4:8], dh.bitmapWidth)
    	binary.LittleEndian.PutUint32(d[8:12], dh.bitmapHeight)
    	binary.LittleEndian.PutUint16(d[12:14], dh.colorPlanes)
    	binary.LittleEndian.PutUint16(d[14:16], dh.BitsPerPixel)
    	binary.LittleEndian.PutUint32(d[16:20], dh.markerBI_RGB)
    	binary.LittleEndian.PutUint32(d[20:24], dh.originalSize)
    	binary.LittleEndian.PutUint32(d[24:28], dh.printResH)
    	binary.LittleEndian.PutUint32(d[28:32], dh.printResV)
    	binary.LittleEndian.PutUint32(d[32:36], dh.Palette)
    	binary.LittleEndian.PutUint32(d[36:40], dh.importantColor)
    	return d
    }
    
    // EditDIBHeader 修改DIB头
    func (dh *dibHeader) EditDIBHeader(b []byte) error {
    	if len(b) <= 40 {
    		return ErrIconsIndex
    	}
    	h := dh.HeaderToBytes()
    	for i := 0; i < 40; i++ {
    		b[i] = h[i]
    	}
    	return nil
    }
    
    func CreateWinIcon(b [][]byte) (*WinIcon, error) {
    
    	return nil, nil
    }
    
    func getPerm() os.FileMode {
    	if runtime.GOOS == "windows" {
    		return 0
    	} else {
    		return 0666
    	}
    }
    
    
    展开全文
  • Go语言处理Windows系统的图标ICO文件(中)… 编写中 Go语言处理Windows系统的图标ICO文件(下)… 编写中 概述 我们在编写一个程序后,如果是windows系统中的程序,我们希望它有一个图标,这个时候,我们可以使用一些...

    ICO 文件格式

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


    概述

    我们在编写一个程序后,如果是windows系统中的程序,我们希望它有一个图标,这个时候,我们可以使用一些软件来完成图标,嗯…但是好的Icon制作软件要收费,例如Axialis IconWorkshop,可以制作Windows以及MacOS等系统的图标。网络上基于Web页面的在线转转换工具也是有的,但是我看了下,貌似只是单Image(jpg or png or bmp)转单icon格式,而一个Windows应用程序实际上用到的ico文件,应该是多icon图标的,即一个ico文件内,包含了多个icon图标图像数据。例如下图:
    通过PE软件查看exe中的icon
    我们可以通过PE Explorer 看到一个Windows的应用程序中,包含图标有多个不同分辨率及不同颜色数量的icon,从Windows Vista开始,windows可以支持的图标分辨率可以达到256x256@32bit的图标资源,既然我们的口号是I am a Gopher!,那么我们无论是从学习角度,还是从Go Code角度,我们完全可以自己实现一个我们自己的Icon工具。


    单个icon文件分析

    单个icon文件的意思是:我们要分析的ico文件只包含了一个图标,实际上ico文件是一种目录结构的文件,即文件内的数据格式结构是可以存放多文件(多个ico图标),为了将理解流程简化,所以我们先观察只在文件结构中存放了一张图标的ico文件。

    我这里首先来分析一个16x16pixel@24bit 的ico文件:
    一个16x16像素的单icon文件
    资源管理器中显示的2KB大小是一个大概的值,我们鼠标右键菜单,点击属性查看实际大小:
    ico文件实际大小
    我们看到是1.12KB,实际大小为1150字节。(1150 / 1024 = 1.123046875 round 1.12KB)

    我们再查看文件的HEX(Binary二进制文件,通常我们用16进制查看器来阅读)
    ico文件16进制显示数据
    我框选的部分有22个字节,这22个字节就是我们这个ico文件的header部分,我们通常称之为文件头(用于描述文件结构的概念)
    然后我们通过下面这张图来充分理解,每个字节的含义:
    ico的文件头
    首先根据Windows系统下ico文件格式的标准,存储的数据采用的是little-endian即“小端序”。
    什么是小端序,我们可以理解为:数据的低位在前,高位在后,即个十百千-> 1024 -> 4201
    在16进制查看器中,例如:

    var data uint32 = 255
    

    那么小端序存储在内存或磁盘的方式为:

    FF 00 00 00
     |-  255的16进制
    
    var data uint32 = 256
    

    那么一个字节能够表示的最大正整数为255,这里256,超出了1位:

    00 01 00 00
     |----|- 进位了,256的16进制为100
    

    关于小端序的数据转换,这里先说一下代码:

    var data = []byte{0x00, 0x01, 0x00, 0x00}
    Int32Data := binary.LittleEndian.Uint32(data[0:])
    t.Logf("%v", Int32Data)
    

    输出结果:

    === RUN   Test_LittleEndian
    --- PASS: Test_LittleEndian (0.00s)
        main_test.go:144: 256 // 这里是结果
    PASS
    ok      ICOFormat 0.187s
    

    现在我们将概念的理解转换为代码:

    type winIcon struct {
    	reservedA     uint16 // 保留字段,始终为 '0x0000'
    	fileType      uint16 // 图像类型:'0x0100' 为 ico,'0x0200' 为 cur
    	imageCount    uint16 // 图像数量:至少为 '0x0100' 即 1个图标
    	width         uint8  // 图像宽度
    	height        uint8  // 图像高度
    	palette       uint8  // 调色板颜色数,不使用调色版为 '0x00'
    	reservedB     uint8  // 保留字段,始终为 '0x00'
    	colorPlanes   uint16 // 在ico中,指定颜色平面,'0x0000' 或则 '0x0100'
    	bitsPerPixel  uint16 // 在ico中,指定每像素的位数,如:'0x2000' 32bit
    	imageDataSize uint16 // 图像数据的大小,单位字节
    	imageOffset   uint32 // 图像数据的偏移量
    }
    

    等一等,这似乎只是单图标文件的结构描述,我们前面说了,ico文件内部应该是以目录结构描述的。
    而且图像的数据可以是bmp(Bitmap位图),也可以是png(Portable Network Graphics便携式网络图形)格式。


    多个ico图标结构分析

    所以我们再来看一个多icon图标文件的ico文件结构:
    多icon图标的ico文件结构
    上图中橘黄色的框中为BMP或PNG的数据偏移量,0x960100 -> 小端转换后为:406字节
    那么后面的数据段应该是啥样的呢?我们看看从头偏移406字节的内容:
    多icon图标的ico文件头结构
    从第一个字节开始偏移406字节的内容选中后,即便我不用不同颜色的框标明一下,都会觉得感觉每一行的结构都好特么的像。再用不同颜色的框一画,聪明的同学肯定感觉一切尽在掌握。哈哈哈。刚好25个…
    换句话说,那个BMP或PNG的偏移量表示的就是偏移过后,就是图像数据。而偏移量前的就是我们的ico文件中的头结构。

    那么我们可以这样理解ico文件的头结构:
    1-6字节:ico文件头,表明ico文件中包含多少个icon图标
    7-22字节:ico文件结构头,主要说明图像数据的偏移量
    而19至22字节的偏移量则是我们整个单或多ico图标ico文件的头结构。

    那么我们来将理解转换代码:

    type WinIcon struct {
    	fileHeader *winIconFileHeader // 文件头
    	icos       []winIconStruct    // icon 头结构
    	data       []byte             // 所有ico文件数据
    }
    
    type winIconHeader struct {
    	ReservedA  uint16 // 保留字段,始终为 '0x0000'
    	FileType   uint16 // 图像类型:'0x0100' 为 ico,'0x0200' 为 cur
    	ImageCount uint16 // 图像数量:至少为 '0x0100' 即 1个图标
    }
    
    type winIconStruct struct {
    	Width         uint8  // 图像宽度
    	Height        uint8  // 图像高度
    	Palette       uint8  // 调色板颜色数,不使用调色版为 '0x00'
    	ReservedB     uint8  // 保留字段,始终为 '0x00'
    	ColorPlanes   uint16 // 在ico中,指定颜色平面,'0x0000' 或则 '0x0100'
    	BitsPerPixel  uint16 // 在ico中,指定每像素的位数,如:'0x2000' 32bit
    	ImageDataSize uint16 // 图像数据的大小,单位字节
    	ImageOffset   uint32 // 图像数据的偏移量
    }
    

    下一节文章会讲到如何读取ico文件

    展开全文
  • Go语言处理Windows系统的图标ICO文件(上) Go语言处理Windows系统的图标ICO文件(中) 将PNG或BMP转换为ICO文件 在前面的两章中,我们初探了ico文件的结构,并且完成了将ico文件中的bmp和png数据提取出来的功能,在本...

    ICO文件格式

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

    将PNG或BMP转换为ICO文件

    在前面的两章中,我们初探了ico文件的结构,并且完成了将ico文件中的bmp和png数据提取出来的功能,在本章中,我们来看看如何将bmp或png转换成png图片。

    这里看看运行后的效果:
    运行效果

    在下面这个URL链接中可以获取本教程内容的ico图标文件资源:
    <ico文件下载>

    先修改之前的定义:

    // 定义常量
    // Constant definition
    const (
    	typeUKN           = iota // unknow type
    	typeBMP                  // bmp file
    	typePNG                  // png file
    	fileHeaderSize    = 6    // 文件头的大小
    	pngFileHeaderSize = 8    // png文件头大小
    	headerSize        = 16   // icon图标的头结构大小
    	bitmapHeaderSize  = 14   // 位图文件头
    	dibHeaderSize     = 40   // dib结构头
    )
    
    // 定义变量
    // Variable definitions
    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文件时,可能出现的切片越界错误
    	PNGHEADER      = []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A} // PNG 文件头
    	DIBHEADER      = []byte{0x28, 0, 0, 0}                                  // DIB 头
    	BMPHEADERID    = []byte{0x42, 0x4d}
    )
    
    // 类型定义 type definition
    // 定义icon图标数据的类型
    // Define the type of icon data
    type (
    	ICONTYPE      int
    	WinIconData   []byte
    	WinIconStruct []winIconStruct
    )
    
    // 定义 Windows 系统的 Ico 文件结构
    // Defining the Ico file structure of Windows system
    type WinIcon struct {
    	fileHeader *winIconFileHeader // 文件头
    	icos       WinIconStruct      // icon 头结构
    }
    
    // ico文件头结构
    // 参考维基百科:
    // https://en.wikipedia.org/wiki/ICO_(file_format)
    type winIconFileHeader struct {
    	ReservedA  uint16 // 保留字段,始终为 '0x0000'
    	FileType   uint16 // 图像类型:'0x0100' 为 ico,'0x0200' 为 cur
    	ImageCount uint16 // 图像数量:至少为 '0x0100' 即 1个图标
    }
    
    // icon图标头结构
    // 参考维基百科:
    // https://en.wikipedia.org/wiki/ICO_(file_format)
    type winIconStruct struct {
    	Width         uint8       // 图像宽度
    	Height        uint8       // 图像高度
    	Palette       uint8       // 调色板颜色数,不使用调色版为 '0x00'
    	ReservedB     uint8       // 保留字段,始终为 '0x00'
    	ColorPlanes   uint16      // 在ico中,指定颜色平面,'0x0000' 或则 '0x0100'
    	BitsPerPixel  uint16      // 在ico中,指定每像素的位数,如:'0x2000' 32bit
    	ImageDataSize uint32      // 图像数据的大小,单位字节
    	ImageOffset   uint32      // 图像数据的偏移量
    	data          WinIconData // 该图标的图像数据
    }
    
    // bitmap 的 DIB 头结构
    // DIB header (bitmap information header)
    // 参考维基百科:
    // https://en.wikipedia.org/wiki/BMP_file_format
    type dibHeader struct {
    	dibSize        uint32 // 0x28 00 00 00 -> 40bytes (DIB Header Size)
    	bitmapWidth    uint32 // Width of the bitmap in pixels -> left to right order
    	bitmapHeight   uint32 // Heigth of the bitmap in pixels -> bottom to top order
    	colorPlanes    uint16 // Number of color planes being used
    	BitsPerPixel   uint16 // Number of bits per pixel
    	markerBI_RGB   uint32 // BI_BITFIELDS, no pixel array compression used
    	originalSize   uint32 // Size of the raw bitmap data (including padding)
    	printResH      uint32 // 0x13 0b 00 00 or 0x12 0b 00 00-> 72 DPI x 39.3701
    	printResV      uint32 // 0x13 0b 00 00 or 0x12 0b 00 00-> 72 DPI x 39.3701
    	Palette        uint32 // 0 or 255 -> 0x00 00 00 00 or 0x00 01 00 00
    	importantColor uint32 // 0 or 255 -> 0x00 00 00 00 or 0x00 01 00 00
    }
    
    // bitmap 的 BITMAP 头结构
    // BITMAPFILEHEADER(14bytes)
    // 参考维基百科:
    // https://en.wikipedia.org/wiki/BMP_file_format
    type bitmapHeader struct {
    	bitmapID         uint16 // 0x42 0x4d "BM"
    	fileSize         uint32 // BMP头与DIB的大小
    	unusedA          uint16 // 0x00 00
    	unusedB          uint16 // 0x00 00
    	bitmapDataOffset uint32 // Bitmap Data 偏移量
    }
    

    首先我在这里先定义一个载入路径并创建ico文件的函数:

    func CreateWinIcon(filePath []string) (*WinIcon, error)
    func (wi *WinIcon) WriteIcoFile(filePath, fileName string)
    

    并且对应的在ico_test.go创建一个UT函数并实现:

    func TestCreateWinIcon(t *testing.T) {
    	type args struct {
    		filePath []string
    	}
    	tests := []struct {
    		name    string
    		args    args
    		want    *WinIcon
    		wantErr bool
    	}{
    		{
    			"Test Create Win Icon",
    			args{[]string{
    				// 所有的bmp均为32bit含有alpha通道的ARGB格式。
    				"D:\\go_project\\src\\WinIconTools\\testico\\vkico16x16@32bit.bmp",
    				"D:\\go_project\\src\\WinIconTools\\testico\\vkico20x20@32bit.bmp",
    				"D:\\go_project\\src\\WinIconTools\\testico\\vkico24x24@32bit.bmp",
    				"D:\\go_project\\src\\WinIconTools\\testico\\vkico32x32@32bit.bmp",
    				"D:\\go_project\\src\\WinIconTools\\testico\\vkico40x40@32bit.bmp",
    				"D:\\go_project\\src\\WinIconTools\\testico\\vkico64x64@32bit.bmp",
    				"D:\\go_project\\src\\WinIconTools\\testico\\vkico256x256@32bit.bmp",
    			}},
    			nil,
    			false,
    		},
    	}
    	for _, tt := range tests {
    		t.Run(tt.name, func(t *testing.T) {
    			got, err := CreateWinIcon(tt.args.filePath)
    			if err != nil {
    				t.Errorf("CreateWinIcon() = %v", err)
    			}
    			for i, _ := range got.icos {
    				// 将创建的ico中的各个icon图标的宽、高等信息打印
    				t.Logf("width:%v, heigth:%v, bits:%v, offset:%v, length:%v\r\n",
    					got.icos[i].getIconWidth(),
    					got.icos[i].getIconHeight(),
    					got.icos[i].getIconBitsPerPixel(),
    					got.icos[i].getIconOffset(),
    					got.icos[i].getIconLength(),
    				)
    			}
    			// 以年月日时分秒作为文件名
    			Time := func() string {
    				tb, _ := time.Now().MarshalText()
    				return fmt.Sprintf("%s%s",
    					strings.Join(strings.Split(string(tb[0:10]), "-"), ""),
    					strings.Join(strings.Split(string(tb[11:19]), ":"), ""),
    				)
    			}()
    			// 将winIconStruct对象及icon图像数据转换为[]byte切片后写入磁盘
    			got.WriteIcoFile("../testico", Time+".ico")
    		})
    	}
    }
    

    以上函数就是用来测试CreateWinIconWriteIcoFile两个函数的TestCreateWinIcon测试函数内容。
    思路就是将我们需要打包的bmp图片们的路径以一个[]string的形式参数传递给TestCreateWinIcon将这些图片解析为我们之前定义好的winIconStruct对象,然后我们实现一个WriteIcoFile函数,其作用是将WinIconwinIconStruct对象写成ico文件。

    这里实现的是图片到winIconStruct对象的转换:

    var (
    	fs  []*os.File
    	err error
    )
    defer func() { // 可能遇到错误,出错的时候把文件指针关闭,毕竟多张图片,失败前也会有成功打开的
    	if fs != nil {
    		for _, v := range fs {
    			if v != nil {
    				v.Close()
    			}
    		}
    	}
    }()
    fs = make([]*os.File, len(filePath))
    for i, v := range filePath {
    	fs[i], err = os.OpenFile(v, os.O_RDONLY, getPerm())
    	if err != nil {
    		return nil, err
    	}
    }
    icos := make(WinIconStruct, len(fs))
    for i, v := range fs {
    	d, t, e := loadImageData(v)
    	if e != nil && e != io.EOF {
    		return nil, e
    	}
    	switch t {
    	case typeBMP:
    		icos[i] = bmpToIcon(d)
    		icos[i].data = d[bitmapHeaderSize:]
    	case typePNG:
    		d := pngToIconPNG(d)
    		if d != nil {
    			icos[i] = pngToIcon(d)
    			icos[i].data = d
    		} else {
    			return nil, ErrIcoInvalid
    		}
    	default:
    		return nil, ErrIcoInvalid
    	}
    	// 根据icon图标的width排个序(升序)
    	// Ascending of sort with icon image width
    	// 对Less、Len、Swap方法实现后,就可以对自定义的结构排序
    	sort.Sort(icos)
    	wi := &WinIcon{
    		icos: icos,
    	}
    	wi.generateOffset()
    	return wi, nil
    

    写入磁盘文件的实现:

    var (
    	fs  *os.File
    	err error
    )
    fp := filepath.Join(filePath, fileName)
    fs, err = os.OpenFile(fp, os.O_CREATE|os.O_RDWR, getPerm())
    if err != nil {
    	panic(err)
    }
    defer fs.Close()
    ih := make([]byte, fileHeaderSize)
    binary.LittleEndian.PutUint16(ih[0:2], 0)
    binary.LittleEndian.PutUint16(ih[2:4], 1)
    binary.LittleEndian.PutUint16(ih[4:6], uint16(wi.getIconsHeaderCount()))
    if _, err := fs.Write(ih); err != nil {
    	panic(err)
    }
    // 写ico文件的fileHeader和icons的structHeader
    for _, v := range wi.icos {
    	b := v.headerToBytes(false)
    	if _, err := fs.Write(b); err != nil {
    		panic(err)
    	}
    }
    // 写icons的图像数据
    for _, v := range wi.icos {
    	// 这里将icon的宽度做一个修改
    	w := binary.LittleEndian.Uint32(v.data[8:12])
    	binary.LittleEndian.PutUint32(v.data[8:12], w*2)
    	if _, err := fs.Write(v.data); err != nil {
    		panic(err)
    	}
    }
    

    为何不是用go语言的golang.org/x/image/bmp包提供的实现?
    首先golang.org/x/image/bmp的实现对与32bit带alpha通道的bmp识别有问题!
    而且其并不是语言的标准包,所以依赖管理上,还需要下载,而我大天朝的网络…
    实现中,使用了image/png对png文件的数据进行了压缩,在pngToIconPNG(d)函数中:

    rd := bytes.NewReader(b)
    i, _ := png.Decode(rd)
    buf := new(bytes.Buffer)
    enc := new(png.Encoder)
    enc.CompressionLevel = png.BestCompression
    e := enc.Encode(buf, i)
    if e != nil {
    	return nil
    } else {
    	return buf.Bytes()
    }
    

    所有的源代码在https://github.com/gemark/WinIconTools中可以看到。

    展开全文
  • windows图标(ico文件)

    2009-06-25 22:29:54
    一些漂亮的windows图标,可以用于程序中,
  • windows安装包 默认图标 打包 软件 图标 安装包图标.ico格式文件

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 604
精华内容 241
关键字:

windows文件图标ico