""" Icon and LaunchImage generator for iOS ====================================== .. author:: Mathieu Virbel """ __all__ = ["launchimage"] import sh import json from PIL import Image from os.path import join, exists from os import makedirs appicon_json = { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x", "filename": "Icon40.png" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x", "filename": "Icon60.png" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon29.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon58.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon87.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon80.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon120.png", "scale" : "3x" }, { "size" : "57x57", "idiom" : "iphone", "filename" : "Icon57.png", "scale" : "1x" }, { "size" : "57x57", "idiom" : "iphone", "filename" : "Icon114.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon120.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon180.png", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "filename" : "Icon20.png", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "filename" : "Icon40.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon29.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon58.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon40.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon80.png", "scale" : "2x" }, { "size" : "50x50", "idiom" : "ipad", "filename" : "Icon50.png", "scale" : "1x" }, { "size" : "50x50", "idiom" : "ipad", "filename" : "Icon100.png", "scale" : "2x" }, { "size" : "72x72", "idiom" : "ipad", "filename" : "Icon72.png", "scale" : "1x" }, { "size" : "72x72", "idiom" : "ipad", "filename" : "Icon144.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon76.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon152.png", "scale" : "2x" }, # If activated, we got a submission error: # "Error ITMS-9000: Invalid Image Path - No image found at the path # referenced under key 'CFBundleIcons': 'AppIcon120x120'" # { # "size" : "120x120", # "idiom" : "car", # "filename" : "Icon120.png", # "scale" : "1x" # }, { "size" : "24x24", "idiom" : "watch", "scale" : "2x", "filename" : "Icon48.png", "role" : "notificationCenter", "subtype" : "38mm" }, { "size" : "27.5x27.5", "idiom" : "watch", "scale" : "2x", "filename" : "Icon55.png", "role" : "notificationCenter", "subtype" : "42mm" }, { "size" : "29x29", "idiom" : "watch", "filename" : "Icon58.png", "role" : "companionSettings", "scale" : "2x" }, { "size" : "29x29", "idiom" : "watch", "filename" : "Icon87.png", "role" : "companionSettings", "scale" : "3x" }, { "size" : "40x40", "idiom" : "watch", "scale" : "2x", "filename" : "Icon80.png", "role" : "appLauncher", "subtype" : "38mm" }, { "size" : "44x44", "idiom" : "watch", "scale" : "2x", "filename" : "Icon88.png", "role" : "longLook", "subtype" : "42mm" }, { "size" : "86x86", "idiom" : "watch", "scale" : "2x", "filename" : "Icon172.png", "role" : "quickLook", "subtype" : "38mm" }, { "size" : "44x44", "idiom" : "watch", "scale" : "2x", "filename" : "Icon88.png", "role" : "appLauncher", "subtype" : "40mm" }, { "size" : "50x50", "idiom" : "watch", "scale" : "2x", "filename" : "Icon100.png", "role" : "appLauncher", "subtype" : "44mm" }, { "size" : "98x98", "idiom" : "watch", "scale" : "2x", "filename" : "Icon196.png", "role" : "quickLook", "subtype" : "42mm" }, { "size" : "108x108", "idiom" : "watch", "scale" : "2x", "filename" : "Icon216.png", "role" : "quickLook", "subtype" : "44mm" }, { "size" : "16x16", "idiom" : "mac", "filename" : "Icon16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "Icon32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "Icon32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "Icon64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "Icon128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "Icon256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "Icon256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "Icon512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "Icon512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "Icon1024.png", "scale" : "2x" }, { "size": "83.5x83.5", "idiom": "ipad", "filename": "Icon167.png", "scale": "2x" }, { "idiom" : "ios-marketing", "size" : "1024x1024", "scale" : "1x", "filename": "Icon1024.png" }, { "idiom" : "watch-marketing", "size" : "1024x1024", "scale" : "1x", "filename": "Icon1024.png" }, ], "info" : { "version" : 1, "author" : "xcode" }, # "properties" : { # "pre-rendered" : True # } } launchimage_json = { "images" : [ { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "736h", "filename" : "Default1242x2208.png", "minimum-system-version" : "8.0", "orientation" : "portrait", "scale" : "3x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "736h", "filename" : "Default2208x1242.png", "minimum-system-version" : "8.0", "orientation" : "landscape", "scale" : "3x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "667h", "filename" : "Default750x1334.png", "minimum-system-version" : "8.0", "orientation" : "portrait", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "minimum-system-version" : "7.0", "filename" : "Default640x960.png", "scale" : "2x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "retina4", "filename" : "Default640x1136.png", "minimum-system-version" : "7.0", "orientation" : "portrait", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "filename" : "Default768x1024.png", "scale" : "1x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "filename" : "Default1024x768.png", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "filename" : "Default1536x2048.png", "scale" : "2x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "minimum-system-version" : "7.0", "filename" : "Default2048x1536.png", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "filename" : "Default320x480.png", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "filename" : "Default640x960.png", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "filename" : "Default640x1136.png", "subtype" : "retina4", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "filename" : "Default768x1024.png", "scale" : "1x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "filename" : "Default1024x768.png", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "ipad", "extent" : "full-screen", "filename" : "Default1536x2048.png", "scale" : "2x" }, { "orientation" : "landscape", "idiom" : "ipad", "extent" : "full-screen", "filename" : "Default2048x1536.png", "scale" : "2x" }, ], "info" : { "version" : 1, "author" : "xcode" } } def icon(image_xcassets, image_fn): """Generate all the possible Icon from a single image_fn """ appicon_dir = join(image_xcassets, "AppIcon.appiconset") if not exists(appicon_dir): makedirs(appicon_dir) with open(join(appicon_dir, "Contents.json"), "w") as fd: json.dump(appicon_json, fd) options = ( # iPhone # Spotlight - iOS 5,6 # Settings - iOS 5-8 # 29pt - 1x,2x,3x ("87", None, "Icon87.png"), ("58", None, "Icon58.png"), ("29", "Icon58.png", "Icon29.png"), # iPhone notification # 20pt - 2x,3x # ("40", None, "Icon40.png"), ("60", None, "Icon60.png"), # iPhone # Spotlight - iOS 7-8 # 40pt 2x,3x ("120", None, "Icon120.png"), ("80", None, "Icon80.png"), # iPhone # App - iOS 5,6 # 57pt 1x,2x ("114", None, "Icon114.png"), ("57", "Icon114.png", "Icon57.png"), # iPhone # App - iOS 7,8 # 60pt 2x,3x ("180", None, "Icon180.png"), #("120", None, "Icon120.png # duplicate"), # iPad # Notifications # 20pt 1x,2x ("20", "Icon80.png", "Icon20.png"), ("40", "Icon80.png", "Icon40.png"), # iPad # Settings iOS 5-8 #("58", None, "Icon58.png # duplicate"), #("29", "Icon58.png", "Icon29.png # duplicate"), # iPad # Spotlight iOS 7,8 # 40pt 1x,2x #("80", None, "Icon80.png # duplicate"), ("40", "Icon80.png", "Icon40.png"), # iPad # Spotlight iOS 5,6 # 50pt 1x,2x ("100", None, "Icon100.png"), ("50", "Icon100.png", "Icon50.png"), # iPad # App iOS 5,6 # 72pt 1x,2x ("144", None, "Icon144.png"), ("72", "Icon144.png", "Icon72.png"), # iPad # App iOS 7,8 # 76pt 1x,2x ("152", None, "Icon152.png"), ("76", "Icon152.png", "Icon76.png"), # iPad # App iOS 9 # 83.5pt 2x ("167", None, "Icon167.png"), # CarPlay # App iOS 8 # 120pt 1x #("120", None, "Icon120.png # duplicate"), # Apple Watch # Notification Center # 38mm, 42mm ("48", None, "Icon48.png"), ("55", None, "Icon55.png"), # Apple Watch # Companion Settings # 29pt 2x,3x #("58", None, "Icon58.png # duplicate"), #("87", None, "Icon87.png # duplicate"), # Apple Watch # Home Screen (All) # Long Look (38mm) #("80", None, "Icon80.png # duplicate"), # Apple Watch # Long Look (42mm) ("88", None, "Icon88.png"), # Apple Watch # Short Look # 38mm, 42mm, 44mm ("172", None, "Icon172.png"), ("196", None, "Icon196.png"), ("216", None, "Icon216.png"), # OS X # 512pt 1x,2x ("1024", None, "Icon1024.png"), ("512", "Icon1024.png", "Icon512.png"), # OS X # 256pt 1x,2x #("512", "Icon1024.png", "Icon512.png # duplicate"), ("256", "Icon512.png", "Icon256.png"), # OS X # 128pt 1x,2x #("256", "Icon512.png", "Icon256.png # duplicate"), ("128", "Icon256.png", "Icon128.png"), # OS X # 32pt 1x,2x ("64", "Icon128.png", "Icon64.png"), ("32", "Icon64.png", "Icon32.png"), # OS X # 16pt 1x,2x #("32", "Icon64.png", "Icon32.png # duplicate"), ("16", "Icon32.png", "Icon16.png")) _generate("AppIcon.appiconset", image_xcassets, image_fn, options, icon=True) def launchimage(image_xcassets, image_fn): """Generate all the possible Launch Images from a single image_fn """ launchimage_dir = join(image_xcassets, "LaunchImage.launchimage") if not exists(launchimage_dir): makedirs(launchimage_dir) with open(join(launchimage_dir, "Contents.json"), "w") as fd: json.dump(launchimage_json, fd) options = ( # size, input, output # iPhone 3.5" @2x ("640 960", None, "Default640x960.png"), # iPhone 3.5" @1x ("320 480", None, "Default320x480.png"), # iPhone 4.0" @2x ("640 1136", None, "Default640x1136.png"), # iPhone 5.5" @3x - landscape ("2208 1242", None, "Default2208x1242.png"), # iPhone 5.5" @3x - portrait ("1242 2208", None, "Default1242x2208.png"), # iPhone 4.7" @2x ("750 1334", None, "Default750x1334.png"), # iPad @2x - landscape ("2048 1536", None, "Default2048x1536.png"), # iPad @2x - portrait ("1536 2048", None, "Default1536x2048.png"), # iPad @1x - landscape ("1024 768", None, "Default1024x768.png"), # iPad @1x - portrait ("768 1024", None, "Default768x1024.png"), ) _generate("LaunchImage.launchimage", image_xcassets, image_fn, options) def _buildimage(in_fn, out_fn, size, padcolor=None): im = Image.open(in_fn) # read the first left/bottom pixel bgcolor = im.getpixel((0, 0)) # ensure the image fit in the destination size if im.size[0] > size[0] or im.size[1] > size[1]: f = max(im.size[0] / size[0], im.size[1] / size[1]) newsize = int(im.size[0] / f), int(im.size[1] / f) im = im.resize(newsize) # create final image outim = Image.new("RGB", size, bgcolor[:3]) x = (size[0] - im.size[0]) // 2 y = (size[1] - im.size[1]) // 2 outim.paste(im, (x, y)) # save the image outim.save(out_fn) def _generate(d, image_xcassets, image_fn, options, icon=False): for c, in_fn, out_fn in options: args = [] if in_fn is not None: filename = join(image_xcassets, d, in_fn) else: filename = image_fn if icon: args += ["-Z", c] args += [ "--out", join(image_xcassets, d, out_fn) ] print("sips", " ".join(args)) sh.sips(*args) else: size = [int(x) for x in c.split()] _buildimage(filename, join(image_xcassets, d, out_fn), size)