RSS

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.png

icon.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에 구워 넣을 수는 있습니다. 뜯어보니 유리 느낌의 정체는 세 겹이었습니다.

  1. 몸통에 위가 밝고 아래가 어두운 그라데이션
  2. 왼쪽 위 모서리의 흰 테
  3. 아래로 떨어지는 부드러운 그림자

두 번째가 핵심인데, 만드는 방법이 간단합니다. 같은 도형을 오른쪽 아래로 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만 갈아끼웁니다.

다만 이 파이프라인이 대신해주지 못하는 게 하나 있습니다. 예뻐 보이는지 판단하는 일입니다. 오늘 열 번 갈아엎은 건 도구 문제가 아니었습니다. 지갑을 원색 클립아트로 그렸다가 촌스럽다는 말을 듣고, 방패로 갔다가, 다시 지갑으로 돌아왔다가, 사람으로 끝났습니다. 형태를 만드는 건 빨라졌지만 무엇을 만들지는 여전히 사람이 정해야 합니다.