-
Notifications
You must be signed in to change notification settings - Fork 11
/
CreateIcon.gd
274 lines (226 loc) · 8.14 KB
/
CreateIcon.gd
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
273
274
class_name CreateIcon
extends SceneTree
var error_callable: Callable
func _init() -> void:
var arguments = OS.get_cmdline_args()
if arguments.size() != 4 and arguments.size() != 9:
print(
"Usage:\n godot -s CreateIcon.gd name <file>...\n",
"\n",
"Creates uncompressed windows ico file.\n",
"Add --headless to hide Godot console.\n",
"\n",
"Arguments:\n",
" godot path to Godot 4 beta2+ executable\n",
" name path to created icon\n",
" <file> provide one or six files. If one provided it will be scaled for all\n",
" icon resolutions. Multiple files should be 16x16, 32x32, 48x48, 64x64,\n",
" 128x128\n and 256x256 pixels big."
)
quit()
return
var images := []
if arguments.size() == 9:
var names := [arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8]]
var check_names := {}
for name in names:
if check_names.has(name):
printerr("File ", name, " was added more than once")
return
check_names[name] = true
images = load_images(names)
else:
images = prepare_images(arguments[3])
if not images.is_empty():
save_icon(arguments[2], images)
quit()
func load_images(paths: PackedStringArray) -> Array:
var images := []
for path in paths:
var image := Image.new()
var error = image.load(path)
if error:
print_error(str("Could not load image: ", path))
return []
image.convert(Image.FORMAT_RGBA8)
images.append(image)
images.sort_custom(sort_images_by_size)
var index := 0
for size in [16, 32, 48, 64, 128, 256]:
var image: Image = images[index]
if image.get_width() != size:
print_error(str("Image has incorrect width: ", image.get_width(), " expected: ", size))
return []
if image.get_height() != size:
print_error(str("Image has incorrect height: ", image.get_height(), " expected: ", size))
return []
index += 1
return images
func prepare_images(path: String) -> Array:
var images := []
for size in [16, 32, 48, 64, 128, 256]:
var image := Image.new()
var error = image.load(path)
if error:
print_error(str("Could not load image: ", path))
return []
image.convert(Image.FORMAT_RGBA8)
image.resize(size, size)
images.append(image)
return images
func save_icon(destination_path: String, images: Array) -> void:
var file = FileAccess.open(destination_path, FileAccess.WRITE)
if not file:
print_error(str("Could not open file for writing!\n", FileAccess.get_open_error()))
return
var icon_creator := IconCreator.new()
file.store_buffer(icon_creator.generate_icon(images))
func print_error(error_message: String) -> void:
printerr(error_message)
if error_callable:
error_callable.call(error_message)
static func sort_images_by_size(a: Image, b: Image) -> bool:
return a.get_width() < b.get_width()
class IconCreator:
const ADLER_MOD := 65521
const ZLIB_BLOCK_SIZE := 16384
const CRC_TABLE_SIZE := 256
const ICON_ENTRY_SIZE := 16
var PNG_SIGNATURE := PackedByteArray([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0a])
var IHDR_SIGNATURE := PackedByteArray([0x49, 0x48, 0x44, 0x52])
var IDAT_SIGNATURE := PackedByteArray([0x49, 0x44, 0x41, 0x54])
var IEND_SIGNATURE := PackedByteArray([0x49, 0x45, 0x4e, 0x44])
var crc_table: Array
func _init() -> void:
crc_table = generate_crc_table()
func generate_icon(images: Array) -> PackedByteArray:
var result := PackedByteArray()
result.append_array(generate_icon_header(images.size()))
var offset := result.size() + images.size() * ICON_ENTRY_SIZE
var pngs := []
for image in images:
assert(image.get_format() == Image.FORMAT_RGBA8)
var png := generate_png(image)
pngs.append(png)
var icon_entry := generate_icon_entry(image, png.size(), offset)
result.append_array(icon_entry)
offset += png.size()
for png in pngs:
result.append_array(png)
return result
func generate_icon_header(size: int) -> PackedByteArray:
var result := PackedByteArray()
result.append_array(lsb_first(0x0, 2)) # reserved
result.append_array(lsb_first(0x1, 2)) # icon type
result.append_array(lsb_first(size, 2)) # image count
return result
func generate_icon_entry(image: Image, size: int, offset: int) -> PackedByteArray:
var result := PackedByteArray()
result.append(image.get_width()) # width
result.append(image.get_height()) # height
result.append(0x0) # size of color palette
result.append(0x0) # reserved
result.append_array(lsb_first(0, 2)) # no color planes
result.append_array(lsb_first(32, 2)) # bits per pixel
result.append_array(lsb_first(size)) # size of embedded png
result.append_array(lsb_first(offset))
return result
func generate_png(image: Image) -> PackedByteArray:
var result := PackedByteArray()
var header_chunk := generate_header_chunk(image.get_width(), image.get_height())
var data_chunk := generate_data_chunk(image)
var end_chunk := generate_end_chunk()
result.append_array(PNG_SIGNATURE)
result.append_array(generate_chunk(header_chunk))
result.append_array(generate_chunk(data_chunk))
result.append_array(generate_chunk(end_chunk))
return result
func generate_chunk(chunk: PackedByteArray) -> PackedByteArray:
var result := PackedByteArray()
result.append_array(msb_first(chunk.size() - 4))
result.append_array(chunk)
result.append_array(msb_first(crc(chunk)))
return result
func generate_header_chunk(width: int, height: int) -> PackedByteArray:
var result = PackedByteArray()
result.append_array(IHDR_SIGNATURE)
result.append_array(msb_first(width))
result.append_array(msb_first(height))
result.append(0x8) # bit depth
result.append(0x6) # color type 32bit RGBA
result.append(0x0) # compression method
result.append(0x0) # filter method
result.append(0x0) # interlace method
return result
func generate_data_chunk(image: Image) -> PackedByteArray:
@warning_ignore("shadowed_variable")
var filtered_pixels := filtered_pixels(image.get_width(), image.get_height(), image.get_data())
@warning_ignore("integer_division")
var zlib_block_count := filtered_pixels.size() / ZLIB_BLOCK_SIZE + (1 if filtered_pixels.size() % ZLIB_BLOCK_SIZE else 0)
var result := PackedByteArray()
result.append_array(IDAT_SIGNATURE)
result.append(0x78) # CMF
result.append(0x1) # FLG
for i in range(zlib_block_count):
var last_block := i == zlib_block_count - 1
result.append(0x1 if last_block else 0x0)
@warning_ignore("shadowed_variable")
var block_size := filtered_pixels.size() % ZLIB_BLOCK_SIZE if last_block else ZLIB_BLOCK_SIZE
result.append_array(block_size(block_size))
for b in range(block_size):
result.append(filtered_pixels[i * ZLIB_BLOCK_SIZE + b])
result.append_array(msb_first(adler(filtered_pixels)))
return result
func generate_end_chunk() -> PackedByteArray:
return IEND_SIGNATURE
func filtered_pixels(width: int, height: int, pixels: PackedByteArray) -> PackedByteArray:
var result = PackedByteArray()
for row in range(height):
result.append(0x0)
for column in range(width * 4):
result.append(pixels[row * width * 4 + column])
return result
func generate_crc_table() -> Array:
var result = []
var c: int
for n in range(CRC_TABLE_SIZE):
c = n
for _i in range(8):
if (c & 1) != 0:
c = 0xedb88320 ^ (c >> 1)
else:
c = c >> 1
result.append(c)
return result
func crc(bytes: PackedByteArray) -> int:
var c := 0xffffffff
for i in range(bytes.size()):
c = crc_table[(c ^ bytes[i]) & 0xff] ^ (c >> 8)
return c ^ 0xffffffff
func adler(bytes: PackedByteArray) -> int:
var a := 1
var b := 0
for byte in bytes:
a = (a + byte) % ADLER_MOD
b = (a + b) % ADLER_MOD
return b << 16 | a
func msb_first(i: int) -> PackedByteArray:
var result := PackedByteArray()
result.append((i >> 24) & 0xff)
result.append((i >> 16) & 0xff)
result.append((i >> 8) & 0xff)
result.append(i & 0xff)
return result
func lsb_first(i: int, size = 4) -> PackedByteArray:
var result := PackedByteArray()
for _s in range(size):
result.append(i & 0xff)
i = i >> 8
return result
func block_size(i: int) -> PackedByteArray:
var result := PackedByteArray()
result.append(i & 0xff)
result.append((i >> 8) & 0xff)
result.append((i & 0xff) ^ 0xff)
result.append(((i >> 8) & 0xff) ^ 0xff)
return result