Create launcher icons for apps - A python plugin for Gimp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This plugin uses the current image loaded in Gimp to create the launcher icons for iOS a/o Android apps. | |
The source image must be larger or equal to 1024x1024 pixel. | |
All icons will be generated in png format. | |
Files: | |
- create-app-launcher-icons.txt (this file) | |
- create_app_launcher_icons.py (main script) | |
- create_app_launcher_icons.en (english translation) | |
- create_app_launcher_icons.it (italian translation) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"reg.menu": "Create launcher icons for apps..." | |
,"reg.info": "App launcher icons creation [by SOSidee.com] " | |
,"reg.desc": "Plugin to create launcher icons for mobile devices applications" | |
,"reg.imgfolder.label": "Output folder:" | |
,"reg.imgfolder.opt.source": "use the source image folder" | |
,"reg.imgfolder.opt.select": "use the folder selected below:" | |
,"reg.selfolder.label": "Selected folder for the output:" | |
,"reg.platform.label": "Platform(s):" | |
,"reg.platform.opt.ios": "iOS" | |
,"reg.platform.opt.android": "Android" | |
,"reg.platform.opt.all": "Both" | |
,"msg.source.size.invalid": "The image must be larger or equal to {0}x{1} pixel." | |
,"msg.source.size.different": "The image width and height must be equal." | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"reg.menu": "Crea icone di lancio per le app..." | |
,"reg.info": "Creazione icone di lancio per le app [by SOSidee.com] " | |
,"reg.desc": "Plugin per creare icone di lancio per applicazioni per dispositivi mobili" | |
,"reg.imgfolder.label": "Cartella di output:" | |
,"reg.imgfolder.opt.source": "usa la cartella dell'immagine sorgente" | |
,"reg.imgfolder.opt.select": "usa la cartella selezionata sotto:" | |
,"reg.selfolder.label": "Cartella selezionata per l'output:" | |
,"reg.platform.label": "Piattaforma:" | |
,"reg.platform.opt.ios": "iOS" | |
,"reg.platform.opt.android": "Android" | |
,"reg.platform.opt.all": "Entrambe" | |
,"msg.source.size.invalid": "L'immagine deve essere maggiore o uguale a {0}x{1} pixel." | |
,"msg.source.size.different": "Larghezza ed altezza dell'immagine devono essere uguali." | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# ##### BEGIN GPL LICENSE BLOCK ##### | |
# | |
# This program is free software; you can redistribute it and/or | |
# modify it under the terms of the GNU General Public License | |
# as published by the Free Software Foundation; either version 3 | |
# of the License, or (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program; if not, write to the Free Software Foundation, | |
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
# | |
# ##### END GPL LICENSE BLOCK ##### | |
# LIBRARY --- START --- | |
import sys; | |
import os; | |
from gimpfu import *; | |
import json; | |
class PLATFORM: | |
IOS = "i"; | |
ANDROID = "a"; | |
BOTH = "ia"; | |
# end class | |
class ORIENTATION: | |
PORTRAIT = "P" | |
LANDSCAPE = "L"; | |
BOTH = "PL"; | |
# end class | |
PLATFORMS = [PLATFORM.IOS, PLATFORM.ANDROID, PLATFORM.BOTH]; | |
ORIENTATIONS = [ORIENTATION.PORTRAIT, ORIENTATION.LANDSCAPE, ORIENTATION.BOTH]; | |
class Size(object): | |
width = None; | |
height = None; | |
def __init__(self, width, height): | |
self.width = width; | |
self.height = height; | |
# end def | |
# end class | |
class Resize(object): | |
mode = None; # "crop"|"scale" | |
size = None; | |
def __init__(self, width, height): | |
self.size = Size(width, height); | |
self.mode = ""; | |
# end def | |
# end class | |
class Crop(Resize): | |
def __init__(self, width, height = 0): | |
if height == 0: | |
height = width; | |
# end if | |
super(self.__class__, self).__init__(width, height); | |
self.mode = "crop"; | |
# end def | |
# end class | |
class Scale(Resize): | |
def __init__(self, width, height = 0): | |
if height == 0: | |
height = width; | |
# end if | |
super(self.__class__, self).__init__(width, height); | |
self.mode = "scale"; | |
# end def | |
# end class | |
class SaveFile(object): | |
platform = None; | |
orientation = None; | |
filename = None; | |
def __init__(self, platform): | |
self.platform = platform; | |
# end def | |
# end class | |
class Sequence: | |
platform = None; | |
orientation = None; | |
actions = None; | |
def __init__(self, *args): | |
self.actions = []; | |
for a in args: | |
self.actions.append(a); | |
# end for | |
self._check(); | |
# end def | |
def _check(self): | |
self.platform = ""; | |
self.orientation = ""; | |
for action in self.actions: | |
if isinstance(action, SaveFile): | |
if action.platform == PLATFORM.BOTH and self.platform != PLATFORM.BOTH: | |
self.platform = PLATFORM.BOTH; | |
# end if | |
if action.platform == PLATFORM.IOS and self.platform.find(PLATFORM.IOS) == -1: | |
self.platform = self.platform + PLATFORM.IOS; | |
# end if | |
if action.platform == PLATFORM.ANDROID and self.platform.find(PLATFORM.ANDROID) == -1: | |
self.platform = self.platform + PLATFORM.ANDROID; | |
# end if | |
if action.orientation == ORIENTATION.BOTH and self.orientation != ORIENTATION.BOTH: | |
self.orientation = ORIENTATION.BOTH; | |
# end if | |
if action.orientation == ORIENTATION.PORTRAIT and self.orientation.find(ORIENTATION.PORTRAIT) == -1: | |
self.orientation = self.orientation + ORIENTATION.PORTRAIT; | |
# end if | |
if action.orientation == ORIENTATION.LANDSCAPE and self.orientation.find(ORIENTATION.LANDSCAPE) == -1: | |
self.orientation = self.orientation + ORIENTATION.LANDSCAPE; | |
# end if | |
# end if | |
# end for | |
# end def | |
# end class | |
def handle_msg(msg, handler): | |
currenth = pdb.gimp_message_get_handler(); | |
pdb.gimp_message_set_handler(handler); | |
if not msg is str: | |
msg = str(msg); | |
# end if | |
gimp.message(msg.decode('utf-8','replace')); | |
pdb.gimp_message_set_handler(currenth); | |
# end def | |
def box_msg(msg): | |
handle_msg(msg, 0); | |
# end def | |
def console_msg(msg): | |
handle_msg(msg, 2); | |
# end def | |
def get_python(): | |
return "current python version: " + str(sys.version_info[0]) + "." + str(sys.version_info[1]); | |
# end def | |
def fits(container, content): | |
ret = False; | |
chars = list(content); | |
for c in chars: | |
if container.find(c) > -1: | |
ret = True; | |
break; | |
# end if | |
# end for | |
return ret; | |
# end def | |
class LANGUAGE(object): | |
items = None; | |
def __init__(self): | |
filename = os.path.realpath(sys.argv[0]) if sys.argv[0] else ""; | |
if filename != "": | |
filename = os.path.splitext(filename)[0]; | |
language = os.environ['LANGUAGE']; | |
code = language.split("_")[0]; | |
if os.path.exists(filename + "." + code): | |
filename = filename + "." + code; | |
elif os.path.exists(filename + ".en"): | |
filename = filename + ".en"; | |
else: | |
filename = ""; | |
# end if | |
self.items = {}; | |
if filename != "": | |
self._load(filename); | |
# end if | |
# end if | |
# end def | |
def _load(self, filename): | |
with open(filename) as data: | |
self.items = json.load(data); | |
# end with | |
# end def | |
def get(self, key): | |
ret = key; | |
if key in self.items: | |
ret = self.items[key]; | |
# end with | |
return ret; | |
# end def | |
# end class | |
LANG = LANGUAGE(); | |
# LIBRARY --- END --- | |
# if _android_folders is True each Android's icon will be named 'icon.png' and generated in a different folder (e.g. 'Android/drawable-xhdpi/') | |
# if _android_folders is False each Android's icon will have a different name (e.g. 'xhdpi.png') and generated in the same folder (Android/) | |
_android_folders = False; | |
class IMAGE_SOURCE: | |
WIDTH = 1024; | |
HEIGHT = 1024; | |
# end class | |
class IMAGE_OUTPUT: | |
FOLDER_IOS = "iOS"; | |
FOLDER_ANDROID = "Android"; | |
# end class | |
def get_sequences(): | |
rets = []; | |
# list of actions sequence for platforms (Save default: iOS) | |
rets.append( Sequence( Scale(20), Save("Icon-Notification") )); | |
rets.append( Sequence( Scale(29), Save("Icon-Small") )); | |
rets.append( Sequence( Scale(40), Save("Icon-Small-40") )); | |
rets.append( Sequence( Scale(50), Save("Icon-Small-50") )); | |
rets.append( Sequence( Scale(57), Save("Icon") )); | |
rets.append( Sequence( Scale(58), Save("Icon-Small@2x") )); | |
rets.append( Sequence( Scale(60), Save("Icon-Notification@3x") )); | |
rets.append( Sequence( Scale(72), Save("Icon-72") )); | |
rets.append( Sequence( Scale(76), Save("Icon-76") )); | |
rets.append( Sequence( Scale(80), Save("Icon-Small-40@2x") )); | |
rets.append( Sequence( Scale(87), Save("Icon-Small@3x") )); | |
rets.append( Sequence( Scale(100), Save("Icon-Small-50@2x") )); | |
rets.append( Sequence( Scale(114), Save("Icon@2x") )); | |
rets.append( Sequence( Scale(120), Save("Icon-60@2x") )); | |
rets.append( Sequence( Scale(144), Save("Icon-72@2x") )); | |
rets.append( Sequence( Scale(152), Save("Icon-76@2x") )); | |
rets.append( Sequence( Scale(167), Save("Icon-83.5@2x") )); | |
rets.append( Sequence( Scale(180), Save("Icon-60@3x") )); | |
rets.append( Sequence( Scale(512), Save("iTunesArtwork") )); | |
rets.append( Sequence( Scale(1024), Save("iTunesArtwork@2x") )); | |
rets.append( Sequence( Flatten(), Scale(1024), Save("Icon-Marketing") )); | |
rets.append( Sequence( Scale(512), Save("GooglePlayStore", PLATFORM.ANDROID) )); | |
names = []; | |
if _android_folders is True: | |
names.append("drawable-xxxhdpi/icon"); | |
names.append("drawable-xxhdpi/icon"); | |
names.append("drawable-xhdpi/icon"); | |
names.append("drawable/icon"); | |
names.append("drawable-hdpi/icon"); | |
names.append("drawable-mdpi/icon"); | |
names.append("drawable-ldpi/icon"); | |
else: | |
names.append("xxxhdpi"); | |
names.append("xxhdpi"); | |
names.append("xhdpi"); | |
names.append("icon"); | |
names.append("hdpi"); | |
names.append("mdpi"); | |
names.append("ldpi"); | |
# end if | |
rets.append( Sequence( Scale(192), Save(names[0], PLATFORM.ANDROID) )); | |
rets.append( Sequence( Scale(144), Save(names[1], PLATFORM.ANDROID) )); | |
rets.append( Sequence( Scale(96), Save(names[2], PLATFORM.ANDROID) )); | |
rets.append( Sequence( Scale(72), Save(names[3], PLATFORM.ANDROID) )); | |
rets.append( Sequence( Scale(72), Save(names[4], PLATFORM.ANDROID) )); | |
rets.append( Sequence( Scale(48), Save(names[5], PLATFORM.ANDROID) )); | |
rets.append( Sequence( Scale(36), Save(names[6], PLATFORM.ANDROID) )); | |
return rets; | |
# end def | |
class Flatten(object): | |
pass; | |
# end class | |
class Save(SaveFile): | |
def __init__(self, filename, platform = PLATFORM.IOS): | |
super(self.__class__, self).__init__(platform); | |
self.filename = filename; | |
# end def | |
# end class | |
def save_icon(image, folder, filename): | |
ret = False; | |
try: | |
if filename.find("/") > -1: | |
paths = filename.split("/"); | |
for p in paths[:-1]: | |
folder = os.path.join(folder, p); | |
# end for | |
filename = paths[-1]; | |
# end if | |
file = filename + ".png"; | |
if filename.find("iTunesArtwork") > -1: | |
file = filename; | |
# end if | |
if not os.path.exists(folder): | |
os.makedirs(folder); | |
# end if | |
filepath = os.path.join(folder, file); | |
gimp.pdb.file_png_save(image, image.layers[0], filepath, "raw_filename", 0, 9, 0, 0, 0, 0, 0); | |
ret = True; | |
except IOError as e: | |
box_msg("I/O error({0}): {1}".format(e.errno, e.strerror)); | |
except: | |
box_msg("Unexpected error: {0}".format(sys.exc_info()[0])); | |
# end try | |
return ret; | |
# end def | |
def create_icon(source, sequence, folder, platform): | |
ret = True; | |
image = source.duplicate(); | |
image.merge_visible_layers(0); | |
for action in sequence.actions: | |
if isinstance(action, Flatten): | |
layer = image.layers[0]; | |
if layer.has_alpha: | |
pdb.gimp_layer_flatten(layer); | |
# end if | |
elif isinstance(action, Scale): | |
size = action.size; | |
pdb.gimp_image_scale(image, size.width, size.height); | |
elif isinstance(action, Save): | |
if fits(action.platform, platform): | |
if fits(action.platform, PLATFORM.IOS): | |
path = os.path.join(folder, IMAGE_OUTPUT.FOLDER_IOS); | |
save_icon(image, path, action.filename); | |
# end if | |
if fits(action.platform, PLATFORM.ANDROID): | |
path = os.path.join(folder, IMAGE_OUTPUT.FOLDER_ANDROID); | |
save_icon(image, path, action.filename); | |
# end if | |
# end if | |
# end if | |
if ret is False: | |
break; | |
# end if | |
# end for | |
pdb.gimp_image_delete(image); | |
return ret; | |
# end def | |
def plugin_create_app_launcher_icons(image, layer, b_useimagefolder, s_outputfolder, i_platform): | |
if (image.width < IMAGE_SOURCE.WIDTH) or (image.height < IMAGE_SOURCE.HEIGHT): | |
msg = LANG.get("msg.source.size.invalid"); | |
box_msg( msg.format(IMAGE_SOURCE.WIDTH, IMAGE_SOURCE.HEIGHT) ); | |
return; | |
# end if | |
if (image.width != image.height): | |
box_msg(LANG.get("msg.source.size.different")); | |
return; | |
# end if | |
folder = s_outputfolder; | |
if (b_useimagefolder is True) or (folder is None) or (folder == os.sep): | |
folder = os.path.dirname(image.filename); | |
# end if | |
platform = PLATFORMS[i_platform]; | |
res = True; | |
for seq in get_sequences(): | |
if fits(seq.platform, platform): | |
res = create_icon(image, seq, folder, platform); | |
# end if | |
if res is False: | |
break; | |
# end if | |
# end for | |
# end def | |
register( | |
"python_fu_sos_create_app_launcher_icons", | |
LANG.get("reg.info"), # + "\n\n[" + get_python() + "]", | |
LANG.get("reg.desc"), | |
"SOSidee.com", | |
'GPL v3', # copyright | |
"2018", | |
"<Image>/Image/" + LANG.get("reg.menu"), | |
"RGB*, GRAY*", | |
[ | |
(PF_RADIO, "useimagefolder", LANG.get("reg.imgfolder.label"), 1, ((LANG.get("reg.imgfolder.opt.source"), 1), (LANG.get("reg.imgfolder.opt.select"), 0))), | |
(PF_DIRNAME, "outputfolder", LANG.get("reg.selfolder.label"), os.sep ), | |
(PF_OPTION, "platform", LANG.get("reg.platform.label"), 2, [LANG.get("reg.platform.opt.ios"), LANG.get("reg.platform.opt.android"), LANG.get("reg.platform.opt.all")]), | |
], | |
[], | |
plugin_create_app_launcher_icons | |
) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment