안드로이드를 만들며 10 — 스퀴클은 원이 아니에요 - 앱 아이콘을 세 번 틀리고 굽는 법
둥근 타일 원본을 적응형 아이콘에 넣는 단순한 일에서 세 번 틀렸습니다. 모서리가 두 겹이 되고, 검은 초승달이 남고, 아이콘이 제 칸의 절반도 못 채웠어요. 틀릴 때마다 배운 건 하나였습니다 — 짐작하지 말고 그림에게 물어라.
앱 아이콘 원본이 하나 도착합니다. 보라 그라데이션 바탕에 그림이 올라간, 모서리가 둥근 타일이 검정 위에 얹힌 PNG요. 이걸 안드로이드 앱에 넣으면 되는 일입니다. 단순해 보이죠.
여기서 세 번 틀렸습니다. 그 기록이에요.
배경 지식 — 적응형 아이콘은 ‘잘리는 것이 일’입니다
안드로이드 8부터 런처 아이콘은 적응형 아이콘(adaptive icon)입니다. 앱은 108dp짜리 앞면과 뒷면 두 겹을 내고, 모양은 런처가 정해요 — 원, 스퀴클, 둥근 네모, 기기마다 다른 마스크로 오려 냅니다.
중요한 숫자: 108dp 중 마스크가 남기는 건 가운데 72dp뿐입니다. 바깥 테두리는 패럴랙스 같은 효과를 위한 여유분이라, 언제든 잘려 나갈 수 있는 영역이에요. 즉 꽉 채워 그리면 사방이 잘립니다. 잘리는 게 버그가 아니라 사양입니다.
그런데 받은 원본은 이미 모서리가 둥글게 구워진 타일입니다. 그대로 넣으면 어떻게 될까요.
틀리기 1 — 잘라내면 그림이 커집니다
원본의 네 구석은 까맣습니다(둥근 타일 밖이니까요). 그대로 넣으면 런처가 제 모양대로 다시 둥글려서 모서리가 두 겹이 되고, 사이에 검은 초승달이 남아요.
첫 시도: 사방을 타일 반지름만큼 잘라내자. 검은 구석이 사라지겠지.
사라지긴 합니다. 대신 다른 게 생겨요 — 그림이 커집니다. 사방에서 6.5%를 잘라내면 남은 그림이 판을 채우도록 늘어나니까 15% 확대되는 셈이거든요. 안전 구역에 맞춰 그려진 그림이 그 밖으로 밀려 나갑니다. 잘림을 피하려다 잘림을 만든 거죠.
틀리기 2 — 스퀴클은 원이 아닙니다
두 번째 시도: 원본을 둥근 사각형으로 오려 내고, 그 밑에 배경 그라데이션을 깔자. 오려낸 곡선 밖은 밑색이 받쳐 주겠지.
이게 안 되는 이유가 이 글의 제목입니다.
원본의 구워진 모서리는 원호가 아니라 스퀴클(squircle) — 원보다 배가 부른 곡선입니다. 실제로 재 봤어요. 타일 맨 윗줄(y=0)의 검은 구간이 80px일 때, 원이라면 y=40에서 검은 구간이 11px로 줄어야 하는데 실제로는 17px이었습니다. 원으로 오려 내면 스퀴클과 원 사이의 초승달이 그대로 남아요.
덤으로 하나 더 — 밑에 깐 그라데이션도 배신합니다. 원본 배경이
순수한 세로 그라데이션이 아니라 구석이 더 어두웠거든요
(위 한가운데 #AE7CEA, 왼쪽 위 구석 #9968E7). 밑색이 구석에서
더 밝아서, 타일 테두리가 한 겹 윤곽선으로 떠올랐습니다.
합성할 색을 짐작하면 안 됩니다. 그라데이션이겠거니, 원호겠거니 — 둘 다 재 보면 아니었어요.
통한 방법 — 가장자리를 늘려 되메웁니다
정답은 오려 내는 것도, 밑색을 까는 것도 아니었습니다.
사방을 반지름+2px만큼 걷어내고, 걷어낸 만큼을 테두리 픽셀의 복제로
되메웁니다(이미지 도구의 extendWith: "copy"). 그림은 제 자리
제 크기에 남고, 채워 넣은 색은 타일의 가장자리 그 자체라
이음매가 원리적으로 생길 수 없어요. 스퀴클의 곡률도, 구석의 어두움도
알 필요가 없습니다 — 짐작할 색이 없으니까요.
틀리기 3 — 안 잘리는 것과 잘 보이는 것은 다릅니다
이제 크기입니다. 108dp 중 72dp만 보인다니 겁이 나서, 타일을 72dp로 줄여 앉혔습니다. 안 잘리긴 해요. 그런데 —
그림이 타일의 53%였으니 108 판에서 실제 그림은 38dp. 런처에 서면 제 칸의 절반도 못 채우는 작고 흐릿한 덩어리가 됐습니다. 옆 아이콘들 사이에서 혼자 왜소해요. ‘안 잘린다’는 조건이지 ‘잘 보인다’는 답이 아닙니다.
그럼 얼마가 맞을까요. 여기서 관점이 하나 바뀝니다 — 잘리는 경계는 사각형이 아니라 원입니다. 어떤 마스크가 와도 지름 66dp 원 안은 남아요(키라인 원). 그러니 ‘얼마로 줄일까’를 짐작하지 말고 그림에게 묻습니다: 밝은 화소의 최대 반지름을 재어, 그 지름이 66dp가 되는 타일 크기를 역산하는 거예요.
…라고 하고 66으로 맞췄더니 또 반쪽짜리였습니다. 그림이 마스크 가장자리에 그대로 닿아서 여백이 한 톨도 없었고, 런처에서 혼자 부푼 것처럼 보였어요. 66은 ‘안 잘리는 한계’지 ‘보기 좋은 목표’가 아닙니다. 한계와 목표는 다른 값이에요.
최종은 54dp — 판(108)의 절반입니다. 보이는 원(72dp) 안에서 사방 9dp가 숨 쉬고, 실패했던 38dp보다는 확실히 큽니다.
그리고, 이걸 두 번 손으로 하지 않습니다
이 삽질의 결론이 ‘올바른 값들’이었다면 반만 배운 겁니다. 진짜 결론은 이 일을 도구로 만들어 저장소에 두는 것이었어요.
node brand/app-icon.mjs
원본이 새로 오면 이거 하나를 돌립니다. 반지름은 박아 두지 않고 맨 윗줄의 검은 구간을 세어 원본마다 다시 잽니다. 뒷면 색도 브랜드색을 짐작해 넣지 않고 로고의 끝 색을 원본에서 재어 씁니다 — 앞면이 불투명하면 뒷면이 보이는 건 런처가 앞면을 움직일 때뿐이고, 그때 드러나는 색이 로고와 다를 이유가 없거든요.
사실 이 일을 처음 한 날은 스크립트를 스크래치패드에서 돌리고 잃어버렸습니다. 리소스 파일의 주석이 이미 없는 파일을 가리키고 있는 걸 나중에 발견했어요. 다음에도 돌릴 것은 저장소에 삽니다.
다시 같은 일을 한다면
- 적응형 아이콘은 잘리는 게 사양입니다. 108 중 보이는 건 72, 어떤 마스크에도 남는 건 지름 66dp 원 — 이 숫자들을 모르면 어느 방향으로든 틀립니다.
- 스퀴클은 원이 아니고, 그라데이션은 균일하지 않습니다. 합성할 값을 짐작하지 말고 재세요 — 제일 좋은 건 잴 필요가 없는 방법 (가장자리 복제)입니다.
- 한계와 목표는 다른 값입니다. ‘안 잘린다’(66)는 조건이고, ‘잘 보인다’(54)는 그 조건 안에서 따로 골라야 하는 답이에요.
- 한 번 한 삽질은 도구가 되어 저장소에 남아야 합니다. 스크래치에서 돌린 스크립트는 세션과 함께 사라집니다.