RSS

ImageMagick으로 앱 아이콘 만들기: SVG가 안 그려지고 마스크가 투명해진 이유

앱 하나를 두 스토어에 올리면서 이미지 21개를 만들어야 했습니다. 아이콘, 적응형 아이콘 레이어들, 스플래시, 파비콘, 그리고 꾸민 스토어 스크린샷 12장. 디자인 도구를 열지 않고 전부 명령줄에서 만들었습니다.

쓴 도구는 ImageMagick입니다. 이미 깔려 있었습니다.

Version: ImageMagick 7.1.2-3 Q16-HDRI aarch64

간단히 말하면 이미지 편집을 명령어로 하는 도구입니다. 크기 조절, 자르기, 도형 그리기, 글자 얹기, 합성을 셸에서 합니다. 사람이 한 장 만드는 데는 GUI가 빠르지만, 같은 작업을 12장에 반복하거나 문구를 고쳐 전부 다시 뽑아야 할 때는 이쪽이 압도적으로 빠릅니다. 실제로 스크린샷 문구를 한 번 잘못 잡아 12장을 통째로 다시 만들었는데, 스크립트라 명령 한 줄로 끝났습니다.

버전 7부터 명령어가 magick 하나로 통일됐습니다. 인터넷에 돌아다니는 예제의 convert, composite, identify는 6 시절 이름이고 지금은 magick, magick composite, magick identify로 씁니다.

세 번 막혔고, 셋 다 에러 메시지가 친절하지 않았습니다.

SVG를 줬는데 배경만 그려졌습니다

아이콘을 SVG로 그려서 PNG로 뽑을 생각이었습니다. 십자가와 접힌 지도를 <path>로 정의하고 변환했더니 배경 사각형만 남고 나머지가 사라졌습니다. 에러는 없었습니다. 종료 코드도 0이었습니다.

델리게이트 목록을 보고 이유를 알았습니다.

$ magick -list delegate | grep svg
svg => "rsvg-convert' --dpi-x %x --dpi-y %y -o '%o' '%i"

$ command -v rsvg-convert
(없음)

ImageMagick은 SVG를 직접 렌더링하지 않습니다. rsvg-convert라는 외부 프로그램에 넘깁니다. 그게 없으니 내장된 최소 파서로 떨어졌고, 그 파서는 단순한 사각형만 이해하고 path는 조용히 버립니다. 실패를 실패라고 말하지 않는 게 가장 나쁜 부분이었습니다.

brew install librsvg로 해결할 수 있었지만, 도형 몇 개짜리 아이콘 하나 때문에 의존성을 늘리고 싶지 않아서 SVG를 버리고 -draw로 직접 그렸습니다.

-draw는 별도의 언어입니다

접힌 지도 3면과 라틴 십자가를 좌표로 정의했습니다. 1024 캔버스 기준입니다.

P1="path 'M 192 504 L 396 436 L 396 824 L 192 892 Z'"
P2="path 'M 416 440 L 608 504 L 608 892 L 416 828 Z'"
P3="path 'M 628 504 L 832 436 L 832 824 L 628 892 Z'"
CV="rectangle 472,136 552,672"
CH="rectangle 352,264 672,344"

magick -size 1024x1024 "xc:#0F172A" \
  -stroke none -fill '#F6E7C8' -draw "$P1" -draw "$P2" -draw "$P3" \
  -fill '#E9B44C' -draw "$CV" -draw "$CH" \
  icon.png

여기서 두 번째로 막혔습니다. 선 끝을 둥글게 하려고 -linecap round를 붙였더니 인식하지 못한다고 나왔습니다. -strokelinecap도 마찬가지였습니다.

-draw 안쪽은 CLI 옵션과 다른 별개의 미니 언어입니다. 그리기 관련 설정은 옵션이 아니라 그리기 문자열 안에 써야 합니다.

-draw "stroke-linecap round bezier 10,90 40,10 60,90 90,10"

fill, stroke, stroke-width, stroke-linecap, translate, rotate 전부 이 문자열 안에서 씁니다. SVG 속성 이름과 거의 같아서 익숙해지면 오히려 편합니다.

접힌 선을 그릴 때는 획을 긋는 대신 면 사이에 간격을 뒀습니다. 위 좌표에서 396416처럼 20px 벌어져 있는 부분입니다. 획으로 그으면 Android 13 테마 아이콘처럼 전체가 단색으로 칠해질 때 선이 사라져 형태를 알아볼 수 없게 됩니다. 구멍은 단색화돼도 남습니다.

마스크가 통째로 투명해졌습니다

제일 오래 붙잡은 문제입니다.

스토어 스크린샷을 꾸미면서 안쪽 캡처의 위 모서리만 둥글게 깎으려고 마스크를 만들었습니다.

magick -size "${SW}x${SH}" xc:black \
  -draw "roundrectangle 0,0 $((SW-1)),$((SH+CORNER)) $CORNER,$CORNER" \
  mask.png
magick shot.png mask.png -alpha off -compose CopyOpacity -composite rounded.png

결과물의 스크린샷 자리가 텅 비었습니다. 배경과 헤드라인은 멀쩡한데 그 아래가 아무것도 없었습니다. 좌표를 의심해서 한참 헤맸는데 좌표는 맞았습니다.

CopyOpacity는 마스크의 밝기를 알파 값으로 읽습니다. 흰 부분은 불투명, 검은 부분은 투명입니다. 그런데 -draw의 기본 fill은 검정입니다. 검정 캔버스에 검정 둥근 사각형을 그린 셈이라 전부 밝기 0, 즉 전부 투명이었습니다.

한 마디를 넣어 고쳤습니다.

magick -size "${SW}x${SH}" xc:black -fill white \
  -draw "roundrectangle ..." mask.png

마스크를 만들 때는 -fill white를 반드시 적는다고 외우는 게 낫습니다. 이것도 에러가 나지 않습니다. 그냥 조용히 빈 이미지가 나옵니다.

알아두니 쓸모 있던 것들

한쪽 치수만 지정하기. -resize "780x"는 폭만 780으로 맞추고 높이는 비율대로 따라옵니다. x780이면 반대입니다.

그림자. 이 조합을 통째로 외워두면 됩니다.

\( image.png \( +clone -background black -shadow 34x28+0+10 \) +swap -background none -layers merge +repage \)

+clone으로 복제하고, 그 복제본을 그림자로 흐리고, +swap으로 앞뒤를 바꿔 원본이 위로 오게 한 다음 합칩니다. 순서를 바꾸면 그림자가 이미지를 덮습니다.

글자는 두 가지 방법이 있습니다. label:"텍스트"는 글자 크기에 딱 맞는 새 이미지를 만들고, -annotate는 기존 이미지 위에 얹습니다. 뱃지 칩처럼 글자 폭을 재서 배경 알약을 그려야 할 때는 label:로 만든 뒤 magick identify -format '%w'로 폭을 읽는 방식이 편했습니다.

한글 폰트. macOS에 기본으로 있는 /System/Library/Fonts/AppleSDGothicNeo.ttc를 지정하면 됩니다. 따로 설치할 게 없습니다.

중간 확인. magick identify -format '%f %wx%h %[size]\n' *.png 한 줄이면 만들어진 파일 목록과 크기를 한눈에 봅니다. 스토어 규격에 맞는지 확인할 때 매번 썼습니다.

다시 한다면

SVG로 시작하려면 magick -list delegate | grep svg로 렌더러가 실제로 있는지부터 확인하겠습니다. 없다면 librsvg를 깔거나 처음부터 -draw로 가는 게 낫습니다. 어중간하게 시작했다가 조용한 실패를 디버깅하는 게 제일 손해였습니다.

그리고 조용한 실패가 이 도구의 성격입니다. 세 번 막혔는데 셋 다 에러 메시지가 없었고, 결과 이미지를 눈으로 보고서야 알았습니다. 스크립트를 짤 때 단계별 중간 결과를 파일로 남겨두면 어디서 틀어졌는지 찾기가 훨씬 쉽습니다. 저도 마스크 문제를 잡을 때 mask.png를 열어보고 나서야 원인을 알았습니다.