前言
为 iOS 应用提供 icon 时,我们需要裁剪各种各样的尺寸,目前 Xcode 的 AppIcon 供裁剪的尺寸总共就有 18 个之多。分别是:
iPhone Notification 20 pt 的 2x 和 3x
iPhone Settings 29 pt 的 2x 和 3x
iPhone Spotlight 40 pt 的 2x 和 3x
iPhone App 60 pt 的 2x 和 3x
iPad Notification 20 pt 的 2x 和 3x
iPad Settings 29 pt 的 2x 和 3x
iPad Spotlight 40 pt 的 2x 和 3x
iPad App 60 pt 的 2x 和 3x
iPad Pro App 83.5 pt 的 2x
App Store 的 1024 x 1024
一般来说,只要提供一张 1024 x 1024 的最大尺寸图,然后对该图裁剪成多份尺寸,依次放入 Xcode 里即可。但是这么做很费劲,而且容易出错,像这种工作,交给机器干是再适合不过了。
这里提供一个用 python 脚本自动生成所有 Xcode AppIcon 的方法,不但要求生成所有所需尺寸的图片,并且自动生成对应的配置文件,开发者在运行完脚本后只要直接将指定输出文件拷贝到项目对应目录下即可完成任务。
XCode 中的 Assets 目录及其结构
在 XCode 项目中,有个 Assets 选项,其所在文件夹是一个叫 Assets.xcassets 的目录,该目录下的 AppIcon.appiconset 文件夹是存放应用内 icon 相关信息的地方。
Assets.xcassets 目录内有一个 Contents.json 文件,这个文件用来保存所有 icon 的尺寸和位置信息。它的 json 内容结构示例如下:
{
"images": [{
"idiom": "iphone",
"scale": "2x",
"size": "20x20",
"filename": "iphone2x@20x20.png"
}, ...more ],
"info": {
"author": "xcode",
"version": 1
}
}
json 字典是由两个键值对组成。
第一个键值对是一个是数组 images,images 的元素中包含着 Xcode 所需的 icon 文件的信息。
键名 | 用途 | 示例 |
---|---|---|
idiom | icon 类型 | iphone, ipad, ios-markiting |
scale | 分辨率倍数 | 2x, 3x |
size | 图片尺寸 | 20x20 |
filename | 文件名 | iphone2x@20x20.png |
第二个键值对是额外的字典信息 info,提供了该文件的一些信息(通常不那么重要),可以根据自己需要填写。
键名 | 含义 |
---|---|
author | 作者 |
version | 版本号 |
脚本 icon_generator.py 介绍
icon_generator.py 脚本的作用就是根据用户给定的 icon 文件,生成整个 AppIcon.appiconset 文件夹,并且能保证文件夹内的所有文件符合 xcode 所要求的规范。
安装的软件环境只需要两个:
- 编程语言运行环境 Python 3
- Python 3 的图形处理库 PIL(也称 Pillow)
脚本名叫 icon_generator.py,通过命令行执行,要求用户提供一个足够大的 png 正方形 icon 图片,这样就 python 脚本负责将这个图片进行处理,生成所需的所有 xcode 所需的 icon 文件,最终生成一个 AppIcon.appiconset 文件夹。命令行示例如下:
python icon_generator.py my_icon.png ./dir
其中,my_icon.png 就是指定的 icon 文件,./dir 是指定一个用于生成结果的目录。
执行完毕后,./dir 目录内将会看到 AppIcon.appiconset 文件夹,我们只要直接将该文件夹拷贝到项目的 Assets.xcassets 即可。
脚本代码:
# using utf-8
import os
import sys
import json
from PIL import Image
JSON_STRING = '''
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
'''
ICON_DIR_NAME = "AppIcon.appiconset"
JSON_FILE_NAME = "Contents.json"
if len(sys.argv) < 3 :
print("argument error.\nexpamle: python icon_generator.py my_icon.png ./dir")
sys.exit(-1)
input_file = sys.argv[1]
output_dir = sys.argv[2]
// icon_generator.py
dest_dir = os.path.join(output_dir, ICON_DIR_NAME)
json_file_path = os.path.join(dest_dir, JSON_FILE_NAME)
if not os.path.exists(dest_dir):
os.mkdir(dest_dir)
input_image = Image.open(input_file)
info_map = json.loads(JSON_STRING)
icon_array = info_map["images"]
output_map = info_map
output_map["images"] = []
for icon in icon_array:
output_icon = icon
idiom = icon["idiom"]
scale = icon["scale"]
size = icon["size"]
output_width = int(float(size[:size.find("x")]) * float(scale[:scale.find("x")]))
output_height = output_width
output_image = input_image.resize((output_width, output_height))
output_name = idiom + scale + "@" + size + ".png"
output_path = os.path.join(dest_dir, output_name)
output_image.save(output_path, "png")
output_image.close()
output_icon["filename"] = output_name
output_map["images"].append(output_icon)
json_file = open(json_file_path, "w")
json_file.write(json.dumps(output_map))
json_file.close()
input_image.close()