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 config plugin이 반영되지 않는 이유: prebuild와 mod 순서생명시계 앱에 Face ID 잠금을 붙였습니다. expo-local-authentication 을 설치하고 app.json 에 플러그인과 권한 문구를 적었습니다.
- Expo SDK 56 iOS 빌드 오류와 Swift weak let 호환성저는 견적킷을 iPhone에서 실행하려다 xcodebuild 코드 65를 만났습니다. 빌드 화면에는 Reanimated와 Worklets 컴파일 로그가 길게 지나갔고, 마지막에는 오류 15개가 있다는 메시지만 남았습니다.
- React Native Debug·Release 구성 차이: No script URL provided 가 뜨는 이유앱 두 개를 아이폰에 올려놓고 하나를 열었더니 빨간 화면에 이 문구가 떴습니다.
- Expo 앱을 통째로 복사해 새 프로젝트 만들기: 그대로 가는 파일과 고쳐야 하는 파일성경 지도라는 앱을 만들면서 npx create-expo-app을 치지 않았습니다. 이미 두 스토어에 배포되고 있는 다른 앱의 폴더를 통째로 복사해서 시작했습니다.