213 lines
6.4 KiB
Python
213 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Created: 2026-06-02
|
|
Updated: 2026-06-02
|
|
Name: replace_platform_icons.py
|
|
Desc: Replace app icons for iOS, macOS, Android, OHOS platforms
|
|
using processed icons from assets/templates/resized folder.
|
|
Original icons are backed up before replacement.
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
from PIL import Image
|
|
|
|
BASE = '/Users/wushu/Documents/trae_projects/project/xianyan'
|
|
SOURCE_DIR = os.path.join(BASE, 'assets/templates/resized')
|
|
BACKUP_ROOT = os.path.join(BASE, 'assets/templates/backup_platform_icons')
|
|
|
|
|
|
def get_source_icon(size):
|
|
"""Get source icon path for a given pixel size."""
|
|
filename = f'icon_{size}x{size}.png'
|
|
path = os.path.join(SOURCE_DIR, filename)
|
|
if os.path.exists(path):
|
|
return path
|
|
print(f" WARNING: Source icon {filename} not found!")
|
|
return None
|
|
|
|
|
|
def backup_and_replace(target_path, source_path):
|
|
"""Backup original icon and replace with new one."""
|
|
if not os.path.exists(target_path):
|
|
print(f" SKIP: Target not found: {target_path}")
|
|
return False
|
|
|
|
# Create backup path maintaining directory structure
|
|
rel_path = os.path.relpath(target_path, BASE)
|
|
backup_path = os.path.join(BACKUP_ROOT, rel_path)
|
|
backup_dir = os.path.dirname(backup_path)
|
|
os.makedirs(backup_dir, exist_ok=True)
|
|
|
|
# Backup if not already backed up
|
|
if not os.path.exists(backup_path):
|
|
shutil.copy2(target_path, backup_path)
|
|
|
|
# Copy source to target
|
|
shutil.copy2(source_path, target_path)
|
|
|
|
# Verify
|
|
new_img = Image.open(target_path)
|
|
src_img = Image.open(source_path)
|
|
if new_img.size == src_img.size:
|
|
return True
|
|
else:
|
|
print(f" ERROR: Size mismatch after copy! {new_img.size} vs {src_img.size}")
|
|
return False
|
|
|
|
|
|
def replace_ios_runner():
|
|
"""Replace iOS Runner app icons."""
|
|
print("\n=== iOS Runner AppIcon ===")
|
|
ios_dir = os.path.join(BASE, 'ios/Runner/Assets.xcassets/AppIcon.appiconset')
|
|
|
|
# Mapping: target_filename -> source_pixel_size
|
|
mapping = {
|
|
'Icon-App-1024x1024@1x.png': 1024,
|
|
'Icon-App-20x20@1x.png': 20,
|
|
'Icon-App-20x20@2x.png': 40,
|
|
'Icon-App-20x20@3x.png': 60,
|
|
'Icon-App-29x29@1x.png': 29,
|
|
'Icon-App-29x29@2x.png': 58,
|
|
'Icon-App-29x29@3x.png': 87,
|
|
'Icon-App-40x40@1x.png': 40,
|
|
'Icon-App-40x40@2x.png': 80,
|
|
'Icon-App-40x40@3x.png': 120,
|
|
'Icon-App-60x60@2x.png': 120,
|
|
'Icon-App-60x60@3x.png': 180,
|
|
'Icon-App-76x76@1x.png': 76,
|
|
'Icon-App-76x76@2x.png': 152,
|
|
'Icon-App-83.5x83.5@2x.png': 167,
|
|
}
|
|
|
|
count = 0
|
|
for target_name, size in mapping.items():
|
|
source = get_source_icon(size)
|
|
if source:
|
|
target = os.path.join(ios_dir, target_name)
|
|
if backup_and_replace(target, source):
|
|
print(f" OK: {target_name} <- icon_{size}x{size}.png")
|
|
count += 1
|
|
print(f" Replaced: {count}/{len(mapping)}")
|
|
return count
|
|
|
|
|
|
def replace_ios_widget():
|
|
"""Replace iOS Widget app icon."""
|
|
print("\n=== iOS Widget AppIcon ===")
|
|
widget_dir = os.path.join(BASE, 'ios/XianyanWidget/Assets.xcassets/AppIcon.appiconset')
|
|
|
|
mapping = {
|
|
'Icon-App-1024x1024.png': 1024,
|
|
}
|
|
|
|
count = 0
|
|
for target_name, size in mapping.items():
|
|
source = get_source_icon(size)
|
|
if source:
|
|
target = os.path.join(widget_dir, target_name)
|
|
if backup_and_replace(target, source):
|
|
print(f" OK: {target_name} <- icon_{size}x{size}.png")
|
|
count += 1
|
|
print(f" Replaced: {count}/{len(mapping)}")
|
|
return count
|
|
|
|
|
|
def replace_macos():
|
|
"""Replace macOS app icons."""
|
|
print("\n=== macOS AppIcon ===")
|
|
macos_dir = os.path.join(BASE, 'macos/Runner/Assets.xcassets/AppIcon.appiconset')
|
|
|
|
mapping = {
|
|
'app_icon_16.png': 16,
|
|
'app_icon_32.png': 32,
|
|
'app_icon_64.png': 64,
|
|
'app_icon_128.png': 128,
|
|
'app_icon_256.png': 256,
|
|
'app_icon_512.png': 512,
|
|
'app_icon_1024.png': 1024,
|
|
}
|
|
|
|
count = 0
|
|
for target_name, size in mapping.items():
|
|
source = get_source_icon(size)
|
|
if source:
|
|
target = os.path.join(macos_dir, target_name)
|
|
if backup_and_replace(target, source):
|
|
print(f" OK: {target_name} <- icon_{size}x{size}.png")
|
|
count += 1
|
|
print(f" Replaced: {count}/{len(mapping)}")
|
|
return count
|
|
|
|
|
|
def replace_android():
|
|
"""Replace Android launcher icons."""
|
|
print("\n=== Android mipmap ===")
|
|
res_dir = os.path.join(BASE, 'android/app/src/main/res')
|
|
|
|
# Android density -> pixel size
|
|
mapping = {
|
|
'mipmap-mdpi': 48,
|
|
'mipmap-hdpi': 72,
|
|
'mipmap-xhdpi': 96,
|
|
'mipmap-xxhdpi': 144,
|
|
'mipmap-xxxhdpi': 192,
|
|
}
|
|
|
|
count = 0
|
|
for density, size in mapping.items():
|
|
source = get_source_icon(size)
|
|
if source:
|
|
target = os.path.join(res_dir, density, 'ic_launcher.png')
|
|
if backup_and_replace(target, source):
|
|
print(f" OK: {density}/ic_launcher.png <- icon_{size}x{size}.png")
|
|
count += 1
|
|
print(f" Replaced: {count}/{len(mapping)}")
|
|
return count
|
|
|
|
|
|
def replace_ohos():
|
|
"""Replace OHOS (HarmonyOS) app icons."""
|
|
print("\n=== OHOS icons ===")
|
|
|
|
# All OHOS icons are 1024x1024
|
|
paths = [
|
|
'ohos/AppScope/resources/base/media/app_icon.png',
|
|
'ohos/AppScope/resources/base/media/foreground_icon.png',
|
|
'ohos/entry/src/main/resources/base/media/icon.png',
|
|
'ohos/entry/src/main/resources/base/media/foreground_icon.png',
|
|
]
|
|
|
|
source = get_source_icon(1024)
|
|
count = 0
|
|
if source:
|
|
for rel_path in paths:
|
|
target = os.path.join(BASE, rel_path)
|
|
if backup_and_replace(target, source):
|
|
print(f" OK: {rel_path} <- icon_1024x1024.png")
|
|
count += 1
|
|
print(f" Replaced: {count}/{len(paths)}")
|
|
return count
|
|
|
|
|
|
def main():
|
|
os.makedirs(BACKUP_ROOT, exist_ok=True)
|
|
print(f"Source: {SOURCE_DIR}")
|
|
print(f"Backup: {BACKUP_ROOT}")
|
|
|
|
total = 0
|
|
total += replace_ios_runner()
|
|
total += replace_ios_widget()
|
|
total += replace_macos()
|
|
total += replace_android()
|
|
total += replace_ohos()
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"Total icons replaced: {total}")
|
|
print(f"Backup location: {BACKUP_ROOT}")
|
|
print("Done!")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|