为日记应用 Lanternly 的 1.1 版本,我需要一张图片放进 App Store Connect 的两个新位置:产品页页眉和搜索结果。我没有为它准备新设计,也不想重新拍摄应用。但我手上有已经发布的英文和俄文 iPhone 截图。我就是用它们做出了一张 5244 × 2950 的文件,两个位置都能用。
Apple 的尺寸、格式和规则,我已经在关于 Creative Assets 的文章里讲过,这里不再重复。这篇讲的是制作过程,以及我在上传之后才在自己的封面图里发现的一个错误。
为什么用截图#
截图已经通过审核,准确展示了应用里实际有的东西。从中取出界面,封面图就不会做出多余的承诺。而且不需要为每种语言单独绘制:界面已经本地化,需要翻译的只剩标题和下面的一行字。
构图很简单:标题和副标题在顶部居中,下面是三部手机(中间是“今天”,两侧是与 Luna 的聊天和日历),中间那部手机上方是吉祥物 Luna。画面里除了 iPhone 没有别的设备,这一点是原则问题。
这背后有段经历。同一个脚本的第一版还会生成 iPhone Duo 的画面:拿 iPad 截图裁成 Duo 的尺寸。这样做不行。设备的位置里必须放这台设备上拍的截图,否则截图展示的就不是用户实际会得到的东西。我把这些截图组从 App Store Connect 删掉了。Duo 的位置暂时空着,等到能在 Duo 模拟器里截图再说。
文件是怎么生成的#
Python 脚本分三步完成。
从截图中抠出手机。 在 App Store 的画板上,手机位于浅色背景上的深色边框里。脚本寻找第一行深色像素明显较多的行:那就是边框的顶部。再根据这一行找到左右边缘,把手机一直裁到画板底部。圆角用半径为宽度 12.5% 的蒙版遮住,让圆角外面是透明背景,而不是旧画板的一块。
生成 HTML 页面。 背景、一块柔和的光斑、三部带阴影并旋转 ±6° 的手机、Lora 字体的标题和 Inter 字体的一行文字——和应用里用的是同样的字体。Luna 是取自贴纸源文件的矢量 SVG。
在 headless Chrome 中渲染。 页面按 2622 × 1475 个 CSS 像素排版,以 2 倍系数截图:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--headless=new --hide-scrollbars --allow-file-access-from-files \
--virtual-time-budget=3000 --force-device-scale-factor=2 \
--window-size=2622,1475 --screenshot=header-en.png \
file:///path/to/_header-en.html输出正好是 5244 × 2950。--virtual-time-budget 让字体和 SVG 在截图前加载完,否则图上会是备用字体。
一个重要的细节:PNG 必须不带 alpha 通道,Apple 不接受透明度。页面背景不透明时,Chrome 的截图本身不含 alpha,但只要做过任何后期处理,我都会把文件重新存成 RGB。
上传后才发现的错误#
关于裁切,Apple 只写了笼统的一句:
Be sure your focal point artwork is within the center of your composition to prevent any unwanted clipping.
没有精确的几何参数。如果假设 16:9 的通用素材按中心裁切,结果是这样的:
| 位置 | 比例 | 5244 × 2950 中保留的部分 | 被裁掉的部分 |
|---|---|---|---|
| 页眉 | 21:9 | 全部宽度,高 2247 px | 上下各 351 px |
| 搜索 | 3:2 | 全部高度,宽 4425 px | 左右各 410 px |
我把这两个框叠到自己的封面图上,发现标题从顶部第 240 个像素开始。而在 21:9 的页眉里,上边界在第 351 个像素。如果是中心裁切,标题 “A journal that stays yours” 会被拦腰切断。
第一版在我检查之前就已经上传到 App Store Connect 了。它没有进入审核:1.1 版本还没提交。
现在我在上传之前就做检查。下面这个脚本会在封面图上画出两个框,并保存一张缩小的预览图:
#!/usr/bin/env python3
"""在 16:9 素材上画出 21:9 和 3:2 裁切框: python3 crop_check.py header.png preview.png"""
import sys
from PIL import Image, ImageDraw
src, preview = sys.argv[1], sys.argv[2]
im = Image.open(src).convert("RGB")
W, H = im.size
d = ImageDraw.Draw(im)
h21 = round(W * 9 / 21) # 页眉:全部宽度
top = (H - h21) // 2
d.rectangle((0, top, W - 1, top + h21), outline=(255, 0, 0), width=14)
w32 = round(H * 3 / 2) # 搜索:全部高度
left = (W - w32) // 2
d.rectangle((left, 0, left + w32, H - 1), outline=(0, 0, 255), width=14)
im.resize((W // 4, H // 4)).save(preview)
print(f"21:9 y {top}..{top + h21}, 3:2 x {left}..{left + w32}")红框是页眉,蓝框是搜索。所有需要看清的内容,都必须落在两个框的交集里。
不重新制作怎么修复#
这时我已经删掉了原始画板,没有素材可以重新生成封面图。好在构图的上部是空白背景。我把整张图向下移动了 260 像素,再用同一背景原本的前 260 行填满顶部空出来的条带:
from PIL import Image
im = Image.open("header-en.png").convert("RGB")
W, H = im.size
D = 260
out = Image.new("RGB", (W, H))
out.paste(im.crop((0, 0, W, D)), (0, 0)) # 顶部的背景条带
out.paste(im.crop((0, 0, W, H - D)), (0, D)) # 整个构图下移 D
out.save("header-en-v2.png", optimize=True)现在标题从第 500 个像素开始,位于两个框之内。底部损失了手机下端的 260 个像素,但屏幕本来就延伸到画面边缘之外,所以没有影响。第 260 行的接缝看不出来:那里是没有细节的柔和渐变。
这个技巧只有在你移动的那一侧边缘是均匀背景时才行得通。如果顶部是图案或照片,就只能重新制作了。
我不知道的事#
- Apple 究竟怎么裁切素材。 中心裁切是我的推测,依据是那句 “center of your composition”。真实情况只有 App Store Connect 里的“预览”能告诉你,而且每种语言都要检查一遍:不同语言的标题长度不同。
- 素材在 iPad、Mac 和 Vision 上是什么样。 预览只显示 iPhone 和 iPad。在其他设备上我还没见过。
- 封面图在搜索结果里会不会比截图效果更好。 这只有版本上线后的数据才能说明。
如果你在做类似的封面图,请在第一次上传前叠上 21:9 和 3:2 的框。这只需要一分钟,却能让我少走一轮弯路。商店页面其余字段我是怎么翻译成 39 种语言的,写在另一篇文章里。



