Move gen-std-icon.py to kiwix-android-custom git repo
236
gen-std-icon.py
@ -1,236 +0,0 @@
|
||||
#!/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
|
||||
|
||||
Generated icon is a 512x512 piels wide 24b transparent PNG.
|
||||
It contains a white rounded-square 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
|
||||
|
||||
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 sys
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import tempfile
|
||||
import shutil
|
||||
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 = (42, 386) # X, Y of language code marker
|
||||
OFFLINE_POSITION = (LANG_POSITION[0] + MARKER_SIZE + SIZE * 0.05,
|
||||
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):
|
||||
if is_png(data):
|
||||
w, h = struct.unpack(b'>LL', data[16:24])
|
||||
width = int(w)
|
||||
height = int(h)
|
||||
else:
|
||||
raise Exception('not a png image')
|
||||
return width, height
|
||||
|
||||
|
||||
def is_png(data):
|
||||
return (data[:8] == b'\211PNG\r\n\032\n'and (data[12:16] == 'IHDR'))
|
||||
|
||||
|
||||
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):
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
logo_w, logo_h = get_image_info(open(layer1, 'r').read())
|
||||
except:
|
||||
logger.error("Unable to get logo width and height. Is it a PNG file?")
|
||||
sys.exit(1)
|
||||
|
||||
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)
|
||||
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))
|
||||
shutil.copy(layer2p3, icon_path)
|
||||
|
||||
# remove temp directory
|
||||
shutil.rmtree(tmpd)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 3:
|
||||
print('Usage:\t{} <logo_path> <lang-code>'.format(sys.argv[0]))
|
||||
sys.exit(1)
|
||||
main(*sys.argv[1:])
|
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 27 KiB |