Last active
October 26, 2021 06:26
-
-
Save giannisos/dc46db340a3135c88f4feb72ef7021e5 to your computer and use it in GitHub Desktop.
Create launcher images 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 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) |
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 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." | |
} |
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 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." | |
} |
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 ##### | |
# 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