만들게 된 계기
회사에서 안드로이드 앱을 만들때 Lottie 애니메이션을 많이 쓰면서 개발하고 있다. 벡터기반의 Lottie 애니메이션이라면 용량걱정은 없겠지만, 3D 렌더링된 이미지를 프레임별로 재생하는 형식의 Lottie 애니메이션이기 때문에 용량 부담이 컸다. 애니메이션을 줄이고 줄여서 14mb가 되었는데, 이건 좀 아니다 싶어서 결국 시작시에 인터넷 여건이 좋으면 다운로드 받아두는 걸로 구현을 해두긴 했다.
애니메이션이 나에게 처음오면 zip으로 압축했을때 1~2MB 정도의 사이즈다. 보통 이게 3개정도 넣어달라고 우르르 몰려오는데...순식간에 6MB가 앱 용량에 추가되는거다. 디자이너분들도 PINGoo같은 툴을 써서 압축해서 주시지만, TinyPNG만큼은 압축률이 좋지않다.
PINGoo가 평균 30% 압축을하면, TinyPNG는 70%를 압축해준다. 그렇기 때문에 TinyPNG로 왠만하면 해달라고하거나, 내가 했지만 유료이기 때문에 대략 30개정도 압축하면 기다렸다가 나중에 다시해야한다. 그런 귀찮은 일을 디자이너분들께 시킬때마다 미안한 마음이 들었다. 결제하면되지않냐고..? 그건 개발자의 해결방식이 아니라고 생각했다...(가오)ㅋㅋㅋㅋ
그래서 디자이너들이 쓸 수 있으면서도 TinyPNG 만큼 귀찮지 않으면서! TinyPNG 정도의 압축률을 보여주는 프로그램을 만들어보자! 두둥-이된것이다.
70% 압축, 진짜될까?
가능성을 검토하는게 중요했다.
데스크톱 프로그램 개발은 C#으로 학부때 계산기 만드는게 다였긴하지만, 어떻게든 해내겠지?! 라는 생각. 하지만 PINGoo도 못하는 70% 압축이 내가 되겠냐는...가능성 검토였다.
TinyPNG가 부리는 마법에 대해서 다른 사람들도 궁금했나본데, pngquant라는 라이브러리로 비슷하게 압축을할 수 있다는 카더라를 봤다. (StackOverFlow) pngquant는 PNG 이미지에대한 손실 압축 라이브러리이고, 공식 사이트에 의하면 PNG의 투명도를 유지하면서도 일반적으로는 70%가량까지 압축이 된다고한다. PINGoo역시 pngquant로 개발되었다는 사실에 약간의 꺼림칙한 느낌이 있었지만, 최적화 설정이 안되었다는 점에서 내가 최적화를 해보자! 하고 자신있게 pngquant를 다운받았다.
pngquant는 cli 툴이기때문에 옵션값 바꿔서 이미지를 만들어 내는 건 쉬웠다. 하지만...그 결과를 비교하는 것은 온전히 내 눈으로 해야했다. 회사 앱에서 많이 쓰이는 3D 렌더링 스타일의 이미지를 하나 잡고 2시간동안 옵션만 주구장창 바꿔가며 미세검사를 했다. 이럴땐 눈썰미가 좋은것이 불만족을 야기해 더 오래한것같지만....아래 2가지 사항이 주요 논점이었다.
- TinyPNG 결과물과의 화질, 용량 비교
- 각 옵션값들이 이미지에 주는 영향
TinyDoge의 영업비밀
힘들게 최상의 옵션을 찾아서 정말 좋아했는데 3D 렌더링 스타일이 아닌 "인물"이나, "풍경"들에 대해서는 결과물이 달랐다. 내쪽이 좀더 용량은 아꼈지만 그만큼 퀄리티도 좋지않았다. 애초에 퀄리티 옵션을 0~100중에 50밖에 안줬는데 인물 입술색 날라가는건 당연한 결과였겠지....공룡 요시는 입술따위없었으니....
퀄리티를 높이고, floyd 값을 낮추거나, posterize 옵션을 주거나 하는등 여러 대안이있었는데 쉽게 하나의 정책으로 결정하지 못하겠다고 생각했다. 이미지 스타일마다 최상의 결과가 달랐기 때문이다. 여러개 옵션을주고 그 이미지들 중에 젤 용량이 작은 걸 찾는 내 모습에서 발견했는데, 프로그램도 그렇게 구현하면 되겠다는 생각이들었다.
그래서 TinyDoge의 압축방법은 3가지 옵션을 주고, 그중에서 가장 용량이 적은걸(sz > 0) 찾자! 가되었다.
그런데말입니다, 관문하나 더
- 동생이 이전에 학교과제로 만든 Python GUI 프로그램을 봤어서 Python으로 데스크톱 프로그램을 만들 수 있는지는 알고있다.
- python pngquant 모듈 있나? 찾아봤는데 대충 흘겨보니 존재했다.
- pngquant 옵션은 마스터했다.
코딩을 시작하기전 나의 준비상태다. 얼핏보면 준비가 다된것같은데, python pngquant 모듈이 막상 사용하려고 보니 문제였다. 실제 내가 2시간동안 삽질하면서 설정한 옵션값을 python pngquant 모듈에서는 설정할 수 없었던 것이다. 아래는 python pngquant의 이미지 압축설정,
def config(self, quant_file=None, min_quality=None, max_quality=None, ndeep=None, ndigits=None, tmp_file=None)
def quant_image(self, image=None, dst=None, ndeep=None, ndigits=None, override=True, delete=True)
이것은 pngquant cli의 옵션들이다.
options:
--force overwrite existing output files (synonym: -f)
--skip-if-larger only save converted files if they're smaller than original
--output file destination file path to use instead of --ext (synonym: -o)
--ext new.png set custom suffix/extension for output filenames
--quality min-max don't save below min, use fewer colors below max (0-100)
--speed N speed/quality trade-off. 1=slow, 4=default, 11=fast & rough
--nofs disable Floyd-Steinberg dithering
--posterize N output lower-precision color (e.g. for ARGB4444 output)
--strip remove optional metadata (default on Mac)
--verbose print status messages (synonym: -v)
역시 안되는 건가...생각했을때 def config쪽을보고 희망을 찾았다. 바로 quant_file을 넘겨주는 부분이다. 어처피 python pngquant 역시 pngquant 바이너리를 필요로하니 바이너리를 내 프로그램에 embed해서 명령어를 내부적으로 쓰면 되겠다고 생각했다.
디자이너들도 Windows를 쓰는사람도 있고 Mac을 쓰는 사람도 있으니 mac용, windows용 바이너리를 다운받아 포함시켰다. 결과는 성공적이었다! 지금은 집에 Windows 컴퓨터 뿐이라 Mac은 테스트 못했다는게 아쉽다.
PyQt5로 감성있는 소프트웨어 만들기
Python으로 데스크톱 프로그램을 만들면 Mac에서도 사용할 수 있다고 해서 PyQt5를 난생...처음해보는데 일단 찬물에 뛰어들었다! Widget과 Layout 개념만 잡아놓으니 뭔가를...할만은 했다. 디자이너를 위해 만들고 있는 프로그램이니까 열심히 UI를 스타일링했다. ㅋㅋㅋㅋ 안이쁜건 용서안되는 사람들이니까... 하지만 막 Riot 클라이언트처럼 만드는 방법을 모르기때문에 최대한 쉬우면서도 촌스럽지않게 가려면 어떻게 할까 하다 싸이버 펑크로 컨셉잡았다. CyberDoge~!
PyQt5 개발하면서 있었던 문제점은, 오류가 났을떄 알수없는 exit 코드만 던지고 꺼져버린다는 거다.
def excepthook(exc_type, exc_value, exc_tb):
tb = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
print("error catched!:")
print("error message:\n", tb)
QtWidgets.QApplication.quit()
# or QtWidgets.QApplication.exit(0)
if __name__ == '__main__':
sys.excepthook = excepthook
app = QApplication(sys.argv)
ex = TinyRaccoon()
sys.exit(app.exec_())
그래서 exceptionhook을 달아 두는 방법으로 디버깅을 한다고 한다. *초기 이름은 TinyRaccoon이었다는 것이...여기 남아있네
PyInstaller 너까지 왜그래...
pyinstaller ModuleNotFoundError: No module named 'PyQt5'
PyQt5로 프로그램을 다 만들었다면 PyInstaller로 실행파일(exe)로 최종적으로 빌드해야한다. PyInstaller도 많이 깨지고 부셔지면서 배웠다...ㅋㅋㅋ PyInstaller를 이용할땐 사용한 외부 라이브러리는 같이 import 해야하므로 hidden-imports 옵션에 명시해줘야한다. 하지만, 명시해줘도 import가 안되는 문제에 직면했다. 인터넷에 나오는 해결방법도 모두 내가한대로 하면 되었다.
Python 3.10과 최신 PyInstaller와 PyQ5와의 최악의 궁합으로 인해 hidden imports도 되지않아 Python 버전을 3.8로 다운그레이드 했다. 혹시나 미래에 나와 같은 문제에 부딫히고 있을 사람을 위해 해결방법을 기술하겠다. 나도 여러번 바꾸느라 정확히는 어디부분이 잘못되었는지 모르겠지만 내 환경은 이렇다.
- Python 3.8
- PyInstaller 버전은 5.3
- PyInstaller는 venv안에서 실행해야 됨
pyinstaller --clean --noconsole
--icon=icon.ico --add-data="icon.ico;."
--add-data="icon.png;."
--add-data="pngquant;pngquant"
--hidden-import=PyQt5
--hidden-import=python3-yaml
-y ./main.py -n TinyDoge
pngquant 바이너리를 포함시켜야했기때문에 data에 추가를 해줬고, PyQt5.sip을 설치하라는 것도 있던데, PyQt5로 된다. --noconsole 옵션을 주면 만들어진 실행파일을 실행할때 뒤에 콘솔이 안뜨게 된다. 물론 디버깅할땐 콘솔뜨는게 낫다.
PyInstaller는 윈도우에서 빌드하면 윈도우 실행파일이, 맥에서 빌드하면 맥 실행파일이 생성된다고하니 월요일날 출근하면 맥북을 빌려서 마저 빌드해야겠다.
그래서 성능은 좋나요?
랜덤한 이미지로 TinyPNG와 대결한 결과표다. 결과 이미지는 일반 사람들이 눈치채지 못할 정도의 차이이고, 2라운드만 승리했지만, 자세히보면 압축률이 거의 비슷한 수준이라는 것을 알 수 있다!
파일명 | 사이즈 | TinyDoge | TinyPNG |
dice.png | 83.9kb | 19.3kb (-76%) | 20.1kb (-76%) |
justice.png | 196.2kb | 49.2kb (-74%) | 46.6kb (-76%) |
mountain.png | 50.7kb | 49.5kb (-5%) | 48.3kb (-5%) |
mushroom.png | 43.4kb | 10.5kb (-75%) | 18.0kb (-60%) |
pixel.png | 992b | 992b (0%) | 947b (-5%) |
yoshi.png | 93.4kb | 20.6kb (-77%) | 20.1 kb (-79%) |
하지만 지금은 압축중에 듀얼모니터로 창을 이동시 강제종료되는 문제는 있다. 정말로 디자이너들이 유용하게 써준다면 버전 업을 할 수도 있지 않을까? 9월 3일 토요일...내 모든 토요일을 여기에 바쳤다. ㅋㅋㅋㅋ 안끝내면 안풀리는 성격이라...또 오랜만에 이렇게 하루를 꼬박 개발에만 쏟아본거같아서 재밌었다. 요즘은 게임보다 개발이 더 낫다고 생각할때가 많다. 게임이 재밌는게 나와야말이지!!!
TinyDoge는 Github에서 설치할 수 있다. 소스코드는 Doge판이니 보지말아주라굿~!
https://github.com/lx5475/TinyDoge
'프로그래밍 > 회고' 카테고리의 다른 글
DASHI - 뜨거운 순간 업데이트 (2) | 2024.12.13 |
---|---|
DASHI - 챕터와 속도 개선 업데이트 (2) | 2024.03.31 |
DASHI - 트위치 다시보기 부활 (29) | 2023.11.19 |
[Python] 국내 주요 음원차트 API (0) | 2023.04.10 |
김래일의 애니캐 테스트 - 첫 React 프로젝트 회고 (1) | 2022.09.11 |