-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbootstrap_sprite_generator.py
228 lines (179 loc) · 8.76 KB
/
bootstrap_sprite_generator.py
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
#!/usr/bin/python
# -*- coding: utf-8 -*-
from argparse import ArgumentParser
import os
import math
import Image
#from bsg.iconfilter import GenericIconFilter, GenericIconFilter
from iconfilter import GenericIconFilter, GlyphIconFilter
from collections import OrderedDict
class SpriteGenerator(object):
sprite_less_header = """
#// This file automatically generated by bootstrap sprite generator
#// https://github.com/plar/bootstrap-sprite-generator
#
#[class^="icon-"],
#[class*=" icon-"] {
# display: inline-block;
# width: %(max_width)spx;
# height: %(max_height)spx;
# line-height: %(max_height)spx;
# vertical-align: text-top;
# background-image: url("@{iconSpriteProPath}");
# background-position: %(max_width)spx %(max_height)spx;
# background-repeat: no-repeat;
#
# .ie7-restore-right-whitespace();
#}
#.icon-white {
# background-image: url("@{iconWhiteSpriteProPath}");
#}
#
"""
sprite_less_item = """
#.icon-%(name)-26s { background-position: %(ox)spx %(oy)spx; }
#
"""
align_hor_to_int = {'left': -1, 'center': 0, 'right': 1}
align_ver_to_int = {'top': -1, 'center': 0, 'bottom': 1}
def __init__(self, options, filter, filter_type):
super(SpriteGenerator, self).__init__()
self._options = options
self._icon_filter = filter
self._icon_type = filter_type
self._icon_dir = options.icon_dir if options.icon_dir else filter.default_icon_dir
self._output_dir = options.output_dir if options.output_dir else filter.default_output_dir
self._resize = options.resize
self._align_hor = options.align_hor
self._align_ver = options.align_ver
def run(self):
# load icons of specific _options.type from _options.icon_dir
(icons, max_width, max_height) = self._load_icons()
# generate sprite map and sprites-pro.less file
self._generate_sprite_image(icons, max_width, max_height)
self._generate_sprite_less(icons, max_width, max_height)
# print icons
def _load_icons(self):
print "Load icons(%s) from %s directory..." % (self._icon_type, self._icon_dir)
icons = OrderedDict()
max_width = max_height = -1
give_advice = True
for file in os.listdir(self._icon_dir):
icon_name = self._icon_filter.icon_name(self._icon_type, file)
if not icon_name:
continue
# collect image info and dimension
fimg = open("%s/%s" % (self._icon_dir, file), "rb")
img = Image.open(fimg)
img.load() # force load image
(w, h) = img.size
if w > max_width:
max_width = w
if h > max_height:
max_height = h
# check for duplicated icon names
if icon_name in icons:
if give_advice:
print "Advice: You can use '-m' parameter to redefine icon name for specific file"
give_advice = None
print "Warning: icon '%s' already exists, file '%s', previous file '%s'" % (icon_name, file, icons[icon_name]['file_name'])
icons[icon_name] = dict(icon_name=icon_name, file_name=file, image=img, width=w, height=h)
fimg.close()
print "Total icons: %d" % (len(icons))
print "Max icon size: %dpx x %spx" % (max_width, max_height)
if self._resize:
max_width = max_height = self._resize
for (unused, icon) in icons.items():
img = icon['image']
(w, h) = img.size
img = img.copy()
img.thumbnail((self._resize, self._resize), Image.ANTIALIAS)
(w, h) = img.size
icon['image'] = img
icon['width'] = w
icon['height'] = h
print "Resized tile size: %dpx x %spx" % (max_width, max_height)
else:
print "Tile size: %dpx x %spx" % (max_width, max_height)
return (icons, max_width, max_height)
def _align(self, icon_size, max_size, align_type):
if align_type == -1: # left
return 0
if align_type == 0: # center
return (max_size - icon_size) >> 1
elif align_type == 1: # right
return max_size - icon_size
def _generate_sprite_image(self, icons, max_width, max_height):
s = math.sqrt(len(icons))
sprite_cols = int(math.ceil(s))
sprite_rows = int(math.ceil(s))
print "Sprite image size in tiles: %dx%d" % (sprite_cols, sprite_rows)
sprite_width = sprite_cols * max_width
sprite_height = sprite_rows * max_height
print "Sprite image size in pixels: %dx%d" % (sprite_width, sprite_height)
sprite_file_name = "%s/%s" % (self._output_dir, "sprites-pro.png")
print "Creating sprite image %s..." % sprite_file_name
sprite = Image.new(mode='RGBA', size=(sprite_width, sprite_height), color=(0, 0, 0, 0)) # transparent
current_icon = 0
for (unused, icon) in icons.items():
ix = (current_icon % sprite_cols) * max_width
iy = (current_icon / sprite_cols) * max_height
icon['location'] = (ix, iy) # we need it for _less generation
# adjust icon
cx = self._align(icon['width'], max_width, self.align_hor_to_int[self._align_hor])
cy = self._align(icon['height'], max_height, self.align_ver_to_int[self._align_ver])
location = (ix + cx, iy + cy)
sprite.paste(icon['image'], location)
current_icon += 1
# save sprite
try:
os.makedirs(self._output_dir)
except:
pass
sprite.save(sprite_file_name)
print "Done"
return
def _generate_sprite_less(self, icons, max_width, max_height):
css_file_name = "%s/%s" % (self._output_dir, "sprites-pro.less")
print "Creating sprite css %s..." % css_file_name
sprite_file = open(css_file_name, "w")
# write css definition
sprite_file.write(self._get_as_text(self.sprite_less_header) % (dict(max_width=max_width, max_height=max_height)))
# write each icon
icon_line = self._get_as_text(self.sprite_less_item)
for (icon_name, icon) in icons.items():
(ox, oy) = icon['location']
sprite_file.write(icon_line % (dict(name=icon_name, ox=-ox, oy=-oy)))
sprite_file.close()
print "Done"
def _get_as_text(self, block):
lines = []
for line in block.split("\n"):
parts = line.split("#")
if len(parts) == 2:
lines.append(parts[1])
return "\n".join(lines)
def main():
parser = ArgumentParser(usage="%(prog)s [options]", description="Generate bootstrap sprite files for different icon libraries.", epilog="Supported icon libraries: GlyphIcon, FigueIcons and Generic Folders :)")
parser.add_argument('-d', dest='icon_dir', help="icon files directory")
parser.add_argument('-o', dest='output_dir', help="result files directory. if directory does not exist, it will be created automatically.")
parser.add_argument('-r', dest='resize', help="resize original library icons to specific size(pixels)", default=None, type=int)
parser.add_argument('-ah', dest='align_hor', help="align horizontally inside tile", choices=("left", "center", "right"), default="center")
parser.add_argument('-av', dest='align_ver', help="align vertically inside tile", choices=("top", "center", "bottom"), default="center")
parser.add_argument('-m', action='append', dest='adjust_map', help="adjust icon name for specific file. It option can be used multiply times. ie: -m glyphicons_079_signal.png:signal-strength")
# prepare type argument
supported_types = []
filters = [GlyphIconFilter(), GenericIconFilter()]
prefix2filter = dict()
for filter in filters:
prefix2filter[filter.prefix] = filter
for type in filter.types:
supported_types.append("%s:%s" % (filter.prefix, type))
parser.add_argument('-t', dest='type', help="sprite generator type", choices=supported_types, required=True)
args = parser.parse_args()
# run selected generator
(filter, filter_type) = args.type.split(":")
generator = SpriteGenerator(args, prefix2filter[filter], filter_type)
generator.run()
if __name__ == "__main__":
main()