-
-
Notifications
You must be signed in to change notification settings - Fork 12
/
gen-std-icon.py
executable file
·232 lines (181 loc) · 7.63 KB
/
gen-std-icon.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
228
229
230
231
232
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4 nu
""" Generate an Android ic_launcher friendly icon from a PNG logo
Requirements:
- imagemagick
- python3
Generated icon is a 512x512 piels wide 24b transparent PNG.
It contains a transparent background canvas (which can be
customized by changing its template in templates/)
It then adds a resized version of the provided logo in the center
Then adds two markers:
An offline marker indicating it's OFFLINE (bared WiFi icon)
A lang marker using a bubbled flag (only added if version code supplied)
Script can be called with either a local PNG file or an URL to PNG.
The supported languages are based on the template flag-bubbles icons. """
from __future__ import (unicode_literals, absolute_import,
division, print_function)
import logging
import os
import re
import shutil
import struct
import sys
import tempfile
from io import StringIO
from subprocess import call
SIZE = 512 # final icon size
WHITE_CANVAS_SIZE = 464
MARKER_SIZE = 85 # square size of the offline and lang markers
LANG_POSITION = (SIZE / 2 - MARKER_SIZE - 10, 386) # X, Y of language code marker
OFFLINE_POSITION = (SIZE / 2 + 10,
LANG_POSITION[1])
INNER_LOGO_SIZE = 415 # maximum logo square size
CURRENT_PATH = os.path.dirname(os.path.abspath(__file__))
SUPPORTED_LANGS = [re.search(r'launcher\-flag\-([a-z]{2})\.png', fname)
.group(1)
for fname in os.listdir(os.path.join(CURRENT_PATH,
'templates'))
if 'launcher-flag' in fname]
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
# external dependencies (make sure we're all set up!)
try:
import requests
except ImportError:
logger.error("Missing dependency: Unable to import requests.\n"
"Please install requests with "
"`pip install requests "
"either on your machine or in a virtualenv.")
sys.exit(1)
def get_image_info(data):
w, h = struct.unpack(b'>LL', bytes(data[16:24], encoding="latin-1"))
width = int(w)
height = int(h)
return width, height
def syscall(args, shell=False, with_print=True):
''' execute an external command. Use shell=True if using bash specifics '''
args = args.split()
if with_print:
print("-----------\n" + u" ".join(args) + "\n-----------")
if shell:
args = ' '.join(args)
call(args, shell=shell)
def get_remote_content(url):
''' file descriptor from remote file using GET '''
req = requests.get(url)
try:
req.raise_for_status()
except Exception as e:
logger.error("Failed to load data at `{}`".format(url))
logger.exception(e)
sys.exit(1)
return StringIO.StringIO(req.text)
def get_local_content(path):
''' file descriptor from local file '''
if not os.path.exists(path) or not os.path.isfile(path):
logger.error("Unable to find JSON file `{}`".format(path))
sys.exit(1)
try:
fd = open(path, 'r')
except Exception as e:
logger.error("Unable to open file `{}`".format(path))
logger.exception(e)
sys.exit(1)
return fd
def is_remote_path(path):
return re.match(r'^https?\:', path)
def get_local_remote_fd(path):
''' file descriptor for a path (either local or remote) '''
if is_remote_path(path):
return get_remote_content(path)
else:
return get_local_content(path)
def copy_to(src, dst):
''' copy source content (local or remote) to local file '''
local = None
if is_remote_path(src):
local = tempfile.NamedTemporaryFile(delete=False)
download_remote_file(src, local.name)
src = local.name
shutil.copy(src, dst)
if local is not None:
os.remove(local.name)
def download_remote_file(url, path):
''' download url to path '''
syscall('wget -c -O {path} {url}'.format(path=path, url=url))
def resize(path, width, height, new_path=None):
if new_path is None:
new_path = path
syscall('convert {inf} -resize {width}x{height} {outf}'
.format(inf=path, width=width, height=height, outf=new_path))
def sq_resize(path, size, new_path=None):
return resize(path, size, size, new_path)
def main(logo_path, lang_code=None):
if lang_code is not None:
if lang_code not in SUPPORTED_LANGS:
logger.error("No image template for language code `{}`.\n"
"Please download a square PNG bubble flag for that lang "
"and store it in templates/ with proper name."
.format(lang_code))
sys.exit(1)
# create a temp directory to store our stalls
tmpd = tempfile.mkdtemp()
logger.info("Creating android launcher icon for {}/{} using {}"
.format(logo_path, lang_code, tmpd))
# download/copy layer1 (logo)
layer1 = os.path.join(tmpd, 'layer1.png')
copy_to(logo_path, layer1)
if not os.path.exists(layer1) or not os.path.isfile(layer1):
logger.error("Unable to find logo file at `{}`".format(layer1))
sys.exit(1)
logo_w, logo_h = get_image_info(open(layer1, 'r', encoding="latin-1").read())
if not logo_w == logo_h:
logger.warning("Your logo image is not square ({}x{}). "
"Result might be ugly..."
.format(logo_w, logo_h))
else:
logger.debug("PNG file is {}x{}".format(logo_w, logo_h))
# resize logo so it fits in both image and white canvas
if logo_w > INNER_LOGO_SIZE or logo_h > INNER_LOGO_SIZE:
logger.debug("resizing logo to fit in {0}x{0}".format(INNER_LOGO_SIZE))
sq_resize(layer1, INNER_LOGO_SIZE, layer1)
# multiply white background and logo
layer0 = os.path.join(CURRENT_PATH, 'templates',
'launcher-background-white.png')
layer0p1 = os.path.join(tmpd, 'layer0_layer1.png')
syscall('composite -gravity center {l1} {l0} {l0p1}'
.format(l1=layer1, l0=layer0, l0p1=layer0p1))
# prepare layer2 (offline marker)
offline_mk = os.path.join(CURRENT_PATH, 'templates',
'launcher-marker-offline.png')
layer2 = os.path.join(tmpd, 'layer2.png')
sq_resize(offline_mk, MARKER_SIZE, layer2)
# prepare layer3 (lang marker)
lang_mk = os.path.join(CURRENT_PATH, 'templates',
'launcher-flag-{}.png'.format(lang_code))
layer3 = os.path.join(tmpd, 'layer3.png')
sq_resize(lang_mk, MARKER_SIZE, layer3)
# multiply layer0p1 (white + logo) with offline marker (layer2)
layer1p2 = os.path.join(tmpd, 'layer1_layer2.png')
syscall('composite -geometry +{x}+{y} {l2} {l0p1} {l1p2}'
.format(l2=layer2, l0p1=layer0p1, l1p2=layer1p2,
x=OFFLINE_POSITION[0], y=OFFLINE_POSITION[1]))
# multiply layer1p2 (white + logo + offline) with lang marker (layer3)
if lang_code is not None:
layer2p3 = os.path.join(tmpd, 'layer2_layer3.png')
syscall('composite -geometry +{x}+{y} {l3} {l1p2} {l2p3}'
.format(l3=layer3, l1p2=layer1p2, l2p3=layer2p3,
x=LANG_POSITION[0], y=LANG_POSITION[1]))
# copy final result to current directory
icon_path = os.path.join(CURRENT_PATH,
'ic_launcher_512_{}.png'.format(lang_code))
if lang_code is None:
shutil.copy(layer1p2, icon_path)
else:
shutil.copy(layer2p3, icon_path)
# remove temp directory
shutil.rmtree(tmpd)
if __name__ == '__main__':
main(*sys.argv[1:])