Liquid Glass 아이콘 직접 만들기: .icon 파일 구조와 librsvg 함정
앱 아이콘 하나 만드는 데 하루를 썼습니다. 열 번 넘게 갈아엎었고 빌드를 네 번 중간에 끊었습니다. 그 과정에서 알아낸 것 중에 검색해도 잘 안 나오는 게 두 개 있어서 적어둡니다.
librsvg를 깔아도 ImageMagick은 여전히 SVG를 못 그립니다
아침에 쓴 글에서 저는 이렇게 결론 내렸습니다. "ImageMagick이 SVG의 path를 조용히 버린다." 반은 맞고 반은 틀렸습니다.
brew install librsvg로 렌더러를 깔고 다시 해봤습니다.
$ rsvg-convert --version
rsvg-convert version 2.62.3
$ magick -list delegate | grep svg
svg => "rsvg-convert' --dpi-x %x --dpi-y %y -o '%o' '%i"델리게이트가 rsvg-convert를 가리키고 있고 그 프로그램도 이제 있습니다. 그런데 결과가 여전히 이상했습니다. 그라데이션을 채운 방패를 그렸는데 통째로 검게 나왔습니다.
같은 파일을 두 경로로 렌더링해 픽셀을 비교했습니다.
rsvg-convert 직접 → #1734C4 (파랑, 정상)
magick 경유 → #000000 (전부 검정)ImageMagick이 델리게이트를 안 씁니다. 내부 MSVG 렌더러로 처리하는데, 그게 path는 그리면서 fill="url(#gradient)" 참조를 무시합니다. 참조가 해결되지 않으니 기본값인 검정으로 칠합니다. 에러는 없습니다.
그러니까 아침에 겪은 "배경만 나오는" 현상의 진짜 원인은 path가 아니라 그라데이션 참조였습니다. 배경 사각형은 단색이라 살아남았고 그라데이션을 쓴 도형만 사라졌던 겁니다. 원인을 잘못 짚고 넘어갔었습니다.
해법은 순서를 바꾸는 것입니다.
rsvg-convert -w 1024 -h 1024 in.svg -o out.png # 렌더링은 rsvg 가
magick out.png -resize 240x240 small.png # 후처리만 ImageMagick 이이렇게 나누고 나니 SVG의 모든 기능을 쓸 수 있게 됐습니다. 다단 그라데이션, clipPath, 필터 체인까지 다 됩니다. 그 전까지는 -draw 프리미티브로 좌표를 손으로 계산하고 있었습니다.
.icon은 그냥 JSON 폴더입니다
iOS 26의 Liquid Glass 아이콘은 PNG로 만들 수 없습니다. 유리 질감과 빛 반사를 시스템이 실시간으로 렌더링하기 때문입니다. 개발자는 완성된 그림이 아니라 레이어를 넘깁니다.
그 형식이 .icon이고, 만드는 도구가 Xcode 26에 딸려 오는 Icon Composer입니다. GUI 앱이라 스크립트로는 못 만들겠구나 싶었는데, 시스템을 뒤져보니 실제 .icon 번들이 하나 있었습니다. 열어봤습니다.
AppIcon.icon/
icon.json
Assets/logomark_light.png
Assets/background.pngicon.json은 평범한 JSON입니다.
{
"fill": { "automatic-gradient": "extended-srgb:0.00000,0.53333,1.00000,1.00000" },
"groups": [
{
"layers": [
{
"fill": "automatic",
"glass": true,
"image-name": "logomark_light.png",
"position": { "scale": 1.85, "translation-in-points": [0, 0] }
}
],
"shadow": { "kind": "neutral", "opacity": 0.5 },
"translucency": { "enabled": true, "value": 0.5 }
}
],
"supported-platforms": { "circles": ["watchOS"], "squares": "shared" }
}세 항목이 핵심입니다.
"glass": true 가 Liquid Glass를 켜는 스위치입니다.
"fill": "automatic" 은 레이어 색을 시스템에 맡깁니다. 사용자가 홈 화면을 다크나 틴티드로 바꾸면 알아서 따라갑니다. PNG 방식이면 네 벌을 따로 만들어야 하는 부분입니다.
"automatic-gradient" 는 배경입니다. 색 하나만 주면 그라데이션을 시스템이 만듭니다.
그래서 손으로 썼습니다. 별 모양 SVG를 PNG로 뽑아 Assets/에 넣고 JSON을 작성했습니다. Icon Composer로 열어보니 정상적으로 읽혔습니다. Icon Composer가 하는 일도 결국 이 JSON을 쓰는 것뿐입니다.
이게 왜 중요하냐면, 아이콘을 스크립트로 재생성할 수 있게 되기 때문입니다. 색만 바꿔 앱마다 다른 아이콘을 만드는 게 명령 한 줄이 됩니다. 실제로 오늘 만든 스크립트 하나가 안드로이드용 PNG 일곱 장과 .icon 레이어를 동시에 뽑습니다.
Expo는 이미 .icon을 지원합니다
SDK 56의 prebuild 설정을 뒤져보니 이런 코드가 있었습니다.
if (icon && typeof icon === 'string' && path.extname(icon) === '.icon' && projectName) {경고 문구도 있습니다. "Liquid glass icons (.icon) should be provided as a string to the ios.icon property, not as an object."
그래서 app.json을 이렇게 두면 됩니다.
{
"icon": "./assets/icon.png",
"ios": { "icon": "./design/saleskit.icon" }
}최상위 icon은 안드로이드와 웹이 쓰고, ios.icon만 .icon을 가리킵니다. prebuild가 번들을 Xcode 프로젝트로 복사하고 ASSETCATALOG_COMPILER_APPICON_NAME까지 잡아줍니다.
안드로이드에는 유리가 없습니다
Liquid Glass는 iOS 전용입니다. 안드로이드는 배경과 전경 두 레이어를 런처가 잘라 합칠 뿐이고 재질을 렌더링하는 기능이 없습니다.
대신 효과를 PNG에 구워 넣을 수는 있습니다. 뜯어보니 유리 느낌의 정체는 세 겹이었습니다.
- 몸통에 위가 밝고 아래가 어두운 그라데이션
- 왼쪽 위 모서리의 흰 테
- 아래로 떨어지는 부드러운 그림자
두 번째가 핵심인데, 만드는 방법이 간단합니다. 같은 도형을 오른쪽 아래로 11px 밀어서 덮으면 왼쪽 위만 흰 선으로 남습니다. 별도의 테두리를 그릴 필요가 없습니다.
정지 화면으로는 iOS와 구분이 잘 안 됩니다. 차이는 움직임입니다. iOS는 폰을 기울이면 반사가 따라 움직이고 안드로이드는 고정입니다.
모노크롬에서 두 번 당했습니다
안드로이드 13의 테마 아이콘은 이미지의 알파 채널만 읽어 단색으로 칠합니다. 여기서 두 가지를 놓쳤습니다.
그림자를 넣으면 안 됩니다. 반투명 그림자가 알파를 어중간하게 깎아서 형태 가장자리가 뭉개집니다. 모노크롬 레이어는 그림자 없는 순수 실루엣이어야 합니다.
도형이 여럿이면 붙지 않게 해야 합니다. 지갑 아이콘을 만들 때 카드 세 장을 나란히 뒀는데 색이 다르니 컬러에서는 구분됐지만 단색으로 칠하니 한 덩어리가 됐습니다. 십자가가 지도 위에 겹친 아이콘도 같은 문제였습니다.
해결은 획이 아니라 틈입니다. 겹치는 도형 둘 사이에 선을 긋는 게 아니라, 위 도형을 조금 키운 모양으로 아래 도형을 파냅니다. 선은 단색화되면 사라지지만 구멍은 남습니다.
구멍은 fill="none"으로 뚫리지 않습니다
이건 하루에 세 번 당했습니다. -draw든 SVG든 fill: none은 "칠하지 않음"이지 투명이 아닙니다. 그 자리에 이미 뭔가 그려져 있으면 그대로 남습니다.
진짜로 뚫으려면 밝기를 알파로 옮겨야 합니다.
magick -size 1024x1024 xc:black -fill white -draw "..." mask.png
magick mask.png \( +clone \) -alpha off -compose CopyOpacity -composite out.png검정이 투명, 흰색이 불투명이 됩니다. 그리고 마스크를 그릴 때 -fill white를 빠뜨리면 안 됩니다. -draw의 기본 fill이 검정이라 도형까지 검게 그려지고, 결과가 통째로 투명해집니다.
정리하면
지금 쓰는 파이프라인은 이렇습니다.
SVG 작성 → rsvg-convert 로 PNG → ImageMagick 으로 후처리
├─ 안드로이드 PNG 7종
└─ .icon 레이어 (iOS Liquid Glass)스크립트 하나에 색상값만 상단 변수로 빼두면 앱마다 색을 바꿔 재생성할 수 있습니다. 형태를 바꿀 때는 SVG path만 갈아끼웁니다.
다만 이 파이프라인이 대신해주지 못하는 게 하나 있습니다. 예뻐 보이는지 판단하는 일입니다. 오늘 열 번 갈아엎은 건 도구 문제가 아니었습니다. 지갑을 원색 클립아트로 그렸다가 촌스럽다는 말을 듣고, 방패로 갔다가, 다시 지갑으로 돌아왔다가, 사람으로 끝났습니다. 형태를 만드는 건 빨라졌지만 무엇을 만들지는 여전히 사람이 정해야 합니다.
함께 읽기
- ImageMagick으로 앱 아이콘 만들기: SVG가 안 그려지고 마스크가 투명해진 이유앱 하나를 두 스토어에 올리면서 이미지 21개를 만들어야 했습니다. 아이콘, 적응형 아이콘 레이어들, 스플래시, 파비콘, 그리고 꾸민 스토어 스크린샷 12장. 디자인 도구를 열지 않고 전부 명령줄에서 만들었습니다.
- Expo 앱을 통째로 복사해 새 프로젝트 만들기: 그대로 가는 파일과 고쳐야 하는 파일성경 지도라는 앱을 만들면서 npx create-expo-app을 치지 않았습니다. 이미 두 스토어에 배포되고 있는 다른 앱의 폴더를 통째로 복사해서 시작했습니다.
- 스토어 이미지 규격 정리: 스크린샷·아이콘·그래픽에 실제로 필요한 것앱 하나를 App Store와 Google Play에 동시에 올리면서 이미지를 몇 개나 만들었는지 세어봤습니다. 스크린샷 12장, 스토어 전용 그래픽 2개, 앱 번들에 들어가는 아이콘 자산 7개. 합쳐서 21개입니다. 코드를 다 짜놓고 시작한 마무리 작업인데 결국 여기에 시간을 더 썼습니다.
- App Store Connect API 키: Team Key로 할 수 있는 것과 없는 것새 앱을 App Store Connect에 등록하면서, 화면을 계속 왔다 갔다 하기 귀찮아서 API로 자동화할 수 있는 부분은 자동화해보려고 했습니다. 결론부터 적으면 절반만 성공했습니다. 조회·수정은 되는데, 앱 생성 자체는 API로 안 됩니다.
- RN 앱 두 번째 배포: 재사용되는 인증서, 새로 해야 하는 등록몇 주 전에 만든 고객 포털 앱을 반쯤 복사해서 새 RN 앱을 하나 더 만들었습니다. 성경 속 여정을 지도에 그리는 화면 하나를 웹에서 옮겨온 것뿐인데, 옮기고 나니 "인증서까지 그대로 가져다 쓸 수 있나"가 궁금해졌습니다. 결론부터 적으면 절반만 맞았습니다.