Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Create launcher icons for apps - A python plugin for Gimp
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)
{
"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."
}
{
"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."
}
#!/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