-
Notifications
You must be signed in to change notification settings - Fork 6
/
go-png2ico.go
272 lines (226 loc) · 6.85 KB
/
go-png2ico.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/*
The MIT License
Copyright (c) 2023 John Siu
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package main
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"os"
"github.com/J-Siu/go-helper"
)
// ICO structire
type ICO struct {
file string
fh *os.File
}
// PNG structure
type PNG struct {
file string // filename
fh *os.File
height uint8
width uint8
depth uint16 // bit/pixel
size uint32
offset uint32
isPNG bool
buf []byte
}
// Open : open PNG file
func (png *PNG) Open(file string) error {
helper.DebugLog("PNG:Open:", file)
var e error
var n int
png.file = file
png.isPNG = false
png.fh, e = os.Open(file)
if e != nil {
return e
}
/*
25byte PNG header - BigEndian
00: 89 50 4e 47 0d 0a 1a 0a // 8byte - magic number
IHDR chunk
08: xx xx xx xx // 4byte - chunk length
12: 49 48 44 52 // 4byte - chunk type(IHDR)
16: xx xx xx xx // 4byte - width
20: xx xx xx xx // 4byte - height
24: xx // 1byte - bit depth (bit/pixel)
*/
headerLen := 25
header := make([]byte, headerLen)
n, e = png.fh.Read(header)
if e != nil {
return e
}
helper.DebugLog("PNG:Open:Header:", hex.EncodeToString(header), "(", n, ")")
// 8byte header[0:8] - magic number
magic := []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}
if bytes.Equal(magic[:], header[:8]) {
helper.DebugLog("PNG:Open: Found PNG magic")
} else {
return errors.New("Not PNG")
}
// 4byte header[8:12] - chunk length - skipped
// 4byte header[12:16] - chunk type IHDR
if bytes.Equal([]byte("IHDR"), header[12:16]) {
helper.DebugLog("PNG:Open: Found IHDR chunk")
} else {
return errors.New("PNG no IHDR chunk")
}
// It is PNG
png.isPNG = true
// 4byte header[16:20] - width
width := binary.BigEndian.Uint32(header[16:20])
helper.DebugLog("PNG:Open:width:", width)
// 4byte header[20:24] - height
height := binary.BigEndian.Uint32(header[20:24])
helper.DebugLog("PNG:Open:height:", height)
if width <= 256 && height <= 256 {
// ICO format use 0 for 256px or larger
if width >= 256 {
width = 0
}
if height >= 256 {
height = 0
}
}
png.width = uint8(width)
png.height = uint8(height)
// 1byte header[25] - color depth
png.depth = uint16(uint8(header[24]))
helper.DebugLog("PNG:Open:depth:", png.depth)
stat, _ := os.Stat(file)
png.size = uint32(stat.Size())
helper.DebugLog("PNG:Open:size:", png.size)
// Pass all check, create PNG struct
helper.DebugLog("PNG:Open:png:", *png)
return nil
}
// Read : read PNG file
func (png *PNG) Read() error {
helper.DebugLog("PNG:Read:", png.file)
var e error
var n int
var n64 int64
n64, e = png.fh.Seek(0, 0)
helper.ErrCheck(e)
helper.DebugLog("PNG:Read:Seek:", n64)
png.buf = make([]byte, png.size)
n, e = png.fh.Read(png.buf)
helper.DebugLog("PNG:Read:byte:", n)
return e
}
// Open : open ICO filehandle
func (ico *ICO) Open(file string) error {
var e error
helper.DebugLog("ICO:Open:", file)
ico.fh, e = os.Create(file)
return (e)
}
// Write : write ICO
func (ico *ICO) Write(b *[]byte) error {
var e error
var n int
n, e = ico.fh.Write(*b)
helper.DebugLog("ICO:Write:byte:", n)
return (e)
}
// ICONDIR - return ICONDIR byte array
func (ico *ICO) ICONDIR(num uint16) *[]byte {
/*
6byte ICONDIR - LittleEndian
00: 00 00 // 2byte, must be 0
02: 01 00 // 2byte, 1 for ICO
04: xx xx // 2byte, img number
*/
b := []byte{0, 0, 1, 0, 0, 0}
binary.LittleEndian.PutUint16(b[4:6], num)
helper.DebugLog("ICO:ICONDIR:", hex.EncodeToString(b))
return &b
}
// ICONDIRENTRY - return ICONDIRENTRY byte array
func (png *PNG) ICONDIRENTRY() *[]byte {
helper.DebugLog("PNG:ICONDIRENTRY:png:", *png)
/*
16byte ICONDIRENTRY - LittleEndian
00: xx // 1byte, width
01: xx // 1byte, height
02: 00 // 1byte, color palette number, 0 for PNG
03: 00 // 1byte, reserved, always 0
04: 00 00 // 2byte, color planes, 0 for PNG
06: xx xx // 2byte, color depth
08: xx xx xx xx // 4byte, image size
12: xx xx xx xx // 4byte, image offset
*/
b := make([]byte, 16)
copy(b[0:6], []byte{png.width, png.height, 0, 0, 0, 0})
binary.LittleEndian.PutUint16(b[6:8], png.depth)
binary.LittleEndian.PutUint32(b[8:12], png.size)
binary.LittleEndian.PutUint32(b[12:16], png.offset)
helper.DebugLog("PNG:ICONDIRENTRY:", hex.EncodeToString(b))
return &b
}
func usage() {
fmt.Println("go-png2ico version 1.0.7")
fmt.Println("License : MIT License Copyright (c) 2023 John Siu")
fmt.Println("Support : https://github.com/J-Siu/go-png2ico/issues")
fmt.Println("Debug : export _DEBUG=true")
fmt.Println("Usage : go-png2ico <PNG file> <PNG file> ... <ICO file>")
}
func main() {
//Debug
helper.DebugEnv()
// ARGs
args := os.Args[1:]
argc := len(args)
switch argc {
case 0:
usage()
os.Exit(0)
case 1:
helper.ErrCheck(errors.New("Input/Output file missing"))
}
fileout := args[argc-1]
// Make sure destination file is *not* PNG
png := new(PNG)
if png.Open(fileout) == nil || png.isPNG {
helper.ErrCheck(errors.New("Output file (" + png.file + ") is a PNG file."))
} else {
helper.DebugLog("main:", png.file, "not PNG")
}
// Get and calculate all PNGs info
pngs := []*PNG{}
pngc := argc - 1
var pngTotalSize uint32 = 0
var LenICONDIR uint32 = 6
var LenICONDIRENTRY uint32 = 16
var LenAllICONDIRENTRY uint32 = LenICONDIRENTRY * uint32(pngc)
for i := 0; i < pngc; i++ {
png := new(PNG)
helper.ErrCheck(png.Open(args[i]))
// offset = len(ICONDIR) + len(all ICONDIRENTRY) + len(all PNG before current one)
png.offset = LenICONDIR + LenAllICONDIRENTRY + pngTotalSize
pngs = append(pngs, png)
pngTotalSize += png.size
}
// Open ICON
ico := new(ICO)
helper.ErrCheck(ico.Open(fileout))
helper.ErrCheck(ico.Write(ico.ICONDIR(uint16(pngc))))
// Write ICONDIRENTRY
for i := 0; i < pngc; i++ {
helper.ErrCheck(ico.Write(pngs[i].ICONDIRENTRY()))
}
// Copy PNG
for i := 0; i < pngc; i++ {
helper.ErrCheck(pngs[i].Read())
helper.ErrCheck(ico.Write(&pngs[i].buf))
}
}