Skip to content

Instantly share code, notes, and snippets.

@giannisos
Last active October 26, 2021 06:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save giannisos/dc46db340a3135c88f4feb72ef7021e5 to your computer and use it in GitHub Desktop.
Save giannisos/dc46db340a3135c88f4feb72ef7021e5 to your computer and use it in GitHub Desktop.
Create launcher images for apps - A python plugin for Gimp
This plugin uses the current image loaded in Gimp to create the launcher images for iOS a/o Android apps.
The source image must be larger or equal to 2732x2732 pixel.
When putting the contents in your image, consider that:
- in order to generate all the various requested sizes, the image will be cropped and scaled depending on the initial settings;
- nevertheless, a 1125x1125 pixel central part of the image will never be cropped (that's a safe zone for important contents!).
See this source image example indicating the cropping areas depending on the orientation:
https://blog.sosidee.com/create_app_launcher_images_template.png
All images will be generated in png format.
Files:
- create-app-launcher-images.txt (this file)
- create_app_launcher_images.py (main script)
- create_app_launcher_images.en (english translation)
- create_app_launcher_images.it (italian translation)
{
"reg.menu": "Create launcher images for apps..."
,"reg.info": "App launcher images creation [by SOSidee.com] "
,"reg.desc": "Plugin to create launcher images 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"
,"reg.orientation.label": "Orientation(s):"
,"reg.orientation.opt.portrait": "Portrait"
,"reg.orientation.opt.landscape": "Landscape"
,"reg.orientation.opt.all": "Both"
,"msg.source.size.invalid": "The image must be larger or equal to {0}x{1} pixel."
,"msg.source.size.portrait": "Portrait orientation: the image must be larger or equal to {0}x{1} px."
,"msg.source.size.landscape": "Landscape orientation: the image must be larger or equal to {0}x{1} px."
}
{
"reg.menu": "Crea immagini di lancio per le app..."
,"reg.info": "Creazione immagini di lancio per le app [by SOSidee.com] "
,"reg.desc": "Plugin per creare immagini 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"
,"reg.orientation.label": "Orientamento:"
,"reg.orientation.opt.portrait": "Verticale (portrait)"
,"reg.orientation.opt.landscape": "Orizzontale (landscape)"
,"reg.orientation.opt.all": "Entrambi"
,"msg.source.size.invalid": "L'immagine deve essere maggiore o uguale a {0}x{1} pixel."
,"msg.source.size.portrait": "Orientamento verticale: l'immagine deve essere maggiore o uguale a {0}x{1} px."
,"msg.source.size.landscape": "Orientamento orizzontale: l'immagine deve essere maggiore o uguale a {0}x{1} px."
}
#!/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 #####
# source image: 2732x2732
# safe zone: 1125x1125
# safe zone portrait: 1125x2272
# safe zone landscape: 2272x1125
# 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 ---
class IMAGE_SOURCE:
PORTRAIT_WIDTH = 1125;
PORTRAIT_HEIGHT = 2732;
LANDSCAPE_WIDTH = 2732;
LANDSCAPE_HEIGHT = 1125;
# end class
class IMAGE_OUTPUT:
FILE_NAME = "splash";
FOLDER_IOS = "iOS";
FOLDER_ANDROID = "Android";
# end class
def get_sequences():
rets = [];
# list of actions sequence for platforms and orientations (Save default: iOS and portrait)
# portrait
rets.append( Sequence( Crop(2048, 2732), Save() ));
rets.append( Sequence( Crop(1700, 2720), Scale(200, 320), Save(PLATFORM.ANDROID) ));
rets.append( Sequence( Crop(1632, 2720), Scale(960, 1600), Save(PLATFORM.ANDROID), Scale(480, 800), Save(PLATFORM.ANDROID) ));
rets.append( Sequence( Crop(1792, 2688), Scale(1280, 1920), Save(PLATFORM.ANDROID), Scale(640, 960), Save(), Scale(320, 480), Save(PLATFORM.BOTH) ));
rets.append( Sequence( Crop(1500, 2668), Scale(750, 1334), Save() ));
rets.append( Sequence( Crop(1920, 2560), Scale(1668, 2224), Save(), Scale(1536, 2048), Save(), Scale(768, 1024), Save() ));
rets.append( Sequence( Crop(1440, 2560), Scale(1242, 2208), Save(), Scale(720, 1280), Save(PLATFORM.ANDROID) ));
rets.append( Sequence( Crop(1125, 2436), Save() ));
rets.append( Sequence( Crop(1280, 2272), Scale(640, 1136), Save() ));
rets.append( Sequence( Crop(1242, 2688), Save(), Scale(828, 1792), Save() ));
#landscape
rets.append( Sequence( Crop(2732, 2048), Save(PLATFORM.IOS, ORIENTATION.LANDSCAPE) ));
rets.append( Sequence( Crop(2720, 1700), Scale(320, 200), Save(PLATFORM.ANDROID, ORIENTATION.LANDSCAPE) ));
rets.append( Sequence( Crop(2720, 1632), Scale(1600, 960), Save(PLATFORM.ANDROID, ORIENTATION.LANDSCAPE), Scale(800, 480), Save(PLATFORM.ANDROID, ORIENTATION.LANDSCAPE) ));
rets.append( Sequence( Crop(2688, 1792), Scale(1920, 1280), Save(PLATFORM.ANDROID, ORIENTATION.LANDSCAPE), Scale(960, 640), Save(ORIENTATION.LANDSCAPE), Scale(480, 320), Save(PLATFORM.BOTH, ORIENTATION.LANDSCAPE) ));
rets.append( Sequence( Crop(2668, 1500), Scale(1334, 750), Save(PLATFORM.IOS, ORIENTATION.LANDSCAPE) ));
rets.append( Sequence( Crop(2560, 1920), Scale(2224, 1668), Save(PLATFORM.IOS, ORIENTATION.LANDSCAPE), Scale(2048, 1536), Save(PLATFORM.IOS, ORIENTATION.LANDSCAPE), Scale(1024, 768), Save(PLATFORM.IOS, ORIENTATION.LANDSCAPE) ));
rets.append( Sequence( Crop(2560, 1440), Scale(2208, 1242), Save(PLATFORM.IOS, ORIENTATION.LANDSCAPE), Scale(1280, 720), Save(PLATFORM.ANDROID, ORIENTATION.LANDSCAPE) ));
rets.append( Sequence( Crop(2436, 1125), Save(PLATFORM.IOS, ORIENTATION.LANDSCAPE) ));
rets.append( Sequence( Crop(2272, 1280), Scale(1136, 640), Save(PLATFORM.IOS, ORIENTATION.LANDSCAPE) ));
rets.append( Sequence( Crop(2688, 1242), Save(PLATFORM.IOS, ORIENTATION.LANDSCAPE), Scale(1792, 828), Save(PLATFORM.IOS, ORIENTATION.LANDSCAPE) ));
return rets;
# end def
class Save(SaveFile):
def __init__(self, platform = PLATFORM.IOS, orientation = ORIENTATION.PORTRAIT):
super(self.__class__, self).__init__(platform);
self.orientation = orientation;
# end def
# end class
def save_image(image, folder, orientation):
ret = False;
try:
if not os.path.exists(folder):
os.makedirs(folder);
# end if
filename = IMAGE_OUTPUT.FILE_NAME + "-" + orientation.upper() + "_" + str(image.width) + "x" + str(image.height) + ".png";
filepath = os.path.join(folder, filename);
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 Exception as e:
box_msg("Unexpected error({0}): {1}".format(e.errno, e.strerror));
# end try
return ret;
# end def
def create_image(source, sequence, folder, platform, orientation, separated):
ret = True;
image = source.duplicate();
image.merge_visible_layers(0);
for action in sequence.actions:
if isinstance(action, Crop):
size = action.size;
pdb.gimp_image_crop(image, size.width, size.height, (image.width - size.width) / 2, (image.height - size.height) / 2 );
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) and fits(action.orientation, orientation):
if separated:
if fits(action.platform, PLATFORM.IOS):
path = os.path.join(folder, IMAGE_OUTPUT.FOLDER_IOS);
save_image(image, path, action.orientation);
# end if
if fits(action.platform, PLATFORM.ANDROID):
path = os.path.join(folder, IMAGE_OUTPUT.FOLDER_ANDROID);
save_image(image, path, action.orientation);
# end if
else:
ret = save_image(image, folder, action.orientation);
# 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_images(image, layer, b_useimagefolder, s_outputfolder, i_platform, i_orientation, b_separatefolders = True):
orientation = ORIENTATIONS[i_orientation];
if orientation == ORIENTATION.PORTRAIT:
if (image.width < IMAGE_SOURCE.PORTRAIT_WIDTH) or (image.height < IMAGE_SOURCE.PORTRAIT_HEIGHT):
msg = LANG.get("msg.source.size.portrait");
box_msg( msg.format(IMAGE_SOURCE.PORTRAIT_WIDTH, IMAGE_SOURCE.PORTRAIT_HEIGHT) );
return;
# end if
elif orientation == ORIENTATION.LANDSCAPE:
if (image.width < IMAGE_SOURCE.LANDSCAPE_WIDTH) or (image.height < IMAGE_SOURCE.LANDSCAPE_HEIGHT):
msg = LANG.get("msg.source.size.landscape");
box_msg( msg.format(IMAGE_SOURCE.LANDSCAPE_WIDTH, IMAGE_SOURCE.LANDSCAPE_HEIGHT) );
return;
# end if
else:
if (image.width < IMAGE_SOURCE.LANDSCAPE_WIDTH) or (image.height < IMAGE_SOURCE.PORTRAIT_HEIGHT):
msg = LANG.get("msg.source.size.invalid");
box_msg( msg.format(IMAGE_SOURCE.LANDSCAPE_WIDTH, IMAGE_SOURCE.PORTRAIT_HEIGHT) );
return;
# end if
# 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) and fits(seq.orientation, orientation):
res = create_image(image, seq, folder, platform, orientation, b_separatefolders);
# end if
if res is False:
break;
# end if
# end for
# end def
register(
"python_fu_sos_create_app_launcher_images",
LANG.get("reg.info"),
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")])
,(PF_OPTION, "orientation", LANG.get("reg.orientation.label"), 2, [LANG.get("reg.orientation.opt.ios"), LANG.get("reg.orientation.opt.android"), LANG.get("reg.orientation.opt.all")])
],
[],
plugin_create_app_launcher_images
)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment