카테고리 없음

웹 어셈블리 이미지 처리 연습 기록 (1)

hyuckkim 2022. 4. 3. 23:10

https://github.com/hyuckkim/Online-ModernArt-Maker 

 

GitHub - hyuckkim/Online-ModernArt-Maker

Contribute to hyuckkim/Online-ModernArt-Maker development by creating an account on GitHub.

github.com

사실 온라인도 아니고 모던아트도 아니고 메이커도 아니지만 이름을 뭘로 해야 할지 모르겠어서 그냥 이렇게 적음.

사이트는 대충 로컬 이미지를 imagequant로 팔레트 png 이미지로 바꾸는 기능 / 각 팔레트 바꾸는 기능 이렇게 2개.

libimagequant (LIQ) — Image Quantization Library (pngquant.org)

 

libimagequant (LIQ) — Image Quantization Library

LIQ_OWN_PIXELS makes pixel array owned by the object. The pixels will be freed automatically at any point when it's no longer needed. If you set this flag you must not free the pixel array yourself. If the image has been created with liq_image_create_rgba_

pngquant.org

동기

 2학년 때 정보과제연구를 진행했었는데, 1학기 때 만든 게 '안녕' 이고 2학기 때 팔레트 이미지 분석을 했었다. 최종적으로 이미지의 팔레트를 바꾸는 사이트를 만들자고 생각했는데, 그 당시에는 blob과 웹 어셈블리의 존재를 몰라서 이미지를 서버로 보내서 처리하고 받아오고 해야 하는 줄 알았다.

 유튜브에서 squoosh! 라는 사이트를 우연히 보게 됐다. 그 사이트에서 이미지를 팔레트 이미지로 바꾸는 기능을 보고 (png 이미지였다. png에 팔레트 기능이 있었던가?) 어케 했지 하고 찾아보니까 구글 오픈소스였다. 그렇게 imagequant랑 webassembly랑 사용 예시 전부 다 갖춰졌다. 그래서 이제 만드는 일만 남았다.

https://squoosh.app 

 

Squoosh

Simple Open your image, inspect the differences, then save instantly. Feeling adventurous? Adjust the settings for even smaller files.

squoosh.app

시작

  mdn에서 rust-bindgen을 사용해봤다. 내용은 좋았으나 여기서 구버전 충돌 나고 typescript 오류나고 express 문제 생기고 했다. 이 때까지는 webassembly에 대한 개념이 일종의 서버사이드 프레임워크 정도로 생각했다.

 하여튼 그래서 그냥 static 웹서버에서 webassembly를 쓰는 거에 대해 긴가민가 했다. 내가 쓰는 Edge 브라우저에서는 보안 설정을 켜면 webassembly가 아예 실행이 안 되는 문제가 있어서 더더욱. 오죽하면 github page에서 쓸 수 있냐는 이슈까지 (아주 잠깐이지만) 열었었다. 지금 생각하면 너무 창피하다.

 rust-bindgen이 생성한 파일을 import 할 때도 문제가 있었다.

import * as rust from "";

이렇게 임포트를 하고서 꼭

rust.default();

default 함수를 실행해야 한다. 난 또 export default라길래 import 하면 자동으로 실행되는 줄 알았지.

 하여튼 여기까지 해서 깃허브에 '10을 반환하는 웹어셈블리 함수'가 올라갔다.

라이브러리

 구글에서 쓴 방식 거의 그대로 따라했다. GPL 위반인가? 저건 C++이고 이건 러스트니까 아니길 빈다.

squoosh/imagequant.cpp at dev · GoogleChromeLabs/squoosh (github.com)

 

GitHub - GoogleChromeLabs/squoosh: Make images smaller using best-in-class codecs, right in the browser.

Make images smaller using best-in-class codecs, right in the browser. - GitHub - GoogleChromeLabs/squoosh: Make images smaller using best-in-class codecs, right in the browser.

github.com

다만 웹어셈블리에서 imagequant를 쓸 때 주의점이 있다. feature를 꺼야 한다. 기본 상태로는 멀티스레딩을 하는데 이게 웹어셈블리랑 상성이 안 좋은가 보다.

 

 문제가 하나 있었다. 내가 원하는 건 팔레트로 저용량이 된 이미지지 색 수만 줄어든 24비트 이미지가 아니었다. 구글이 만든 대로는 cavas data를 주고 또다른 canvas data를 받는다. 나는 웹어셈블리에서 png 파일을 받길 원한다.

 png 라이브러리를 찾아 봤으나 맘에 드는 게 없었다. 어차피 편하게 가는 게 목적이 아니라 공부 하려는게 목적이니까 한 번 직접 구현해보기로 했다.

png

 png가 무엇인가. 인터넷 표준의 왕. 무압축의 신. 사진 보낼 때 제발 jpg 말고 이걸로 주세요. 심지어 자체 조사 결과에 따르면 로드 속도도 딱히 밀리지 않는다. 중요한 건 png에 팔레트 기능이 있다는 것이다. png 파일의 구조 뜯어보기로 했다.

https://www.w3.org/TR/PNG/

 

Portable Network Graphics (PNG) Specification (Second Edition)

 

www.w3.org

 png는 앞 8바이트에 png라면 무조건 가지는 정보를 써 두고 그 다음부터 반복이 시작된다. 

내용 길이 (4) - 이름 (4) - 내용 (*) - crc(4)

내용은 deflate로 압축해야 하고, crc는 adler32를 사용한다. 헤더 몇 개만 있으면 구현 끝이다. 별로 안 어렵네? 

코딩

 헤더를 구조체로 만들었다. 길이랑 crc는 데이터 구할 때 한 번만 쓰니까 없애고, 이름과 내용만 받았다. 이름은 4바이트니까 문자열로 생성자 두고 [u8; 4], 4바이트 배열로 만들었다. 내용은 바이트 배열로 만들었다. 그리고 내용에 1바이트 / 4바이트 / 바이트 배열을 추가하는 함수를 만들었다. 마지막으로 deflate 압축은 라이브러리가 있다. 라이브러리로 쓱쓱싹싹 하면 된다.

 다 만들어갈 때쯤에야 알았는데 팔레트에서 bitdepth가 무시된다는 게 팔레트쪽 PLTE를 말한다는 것을 깨달았다. bitvec도 처음 써보고 unsafe도 처음 써봤다.

bitvec (myrrlyn.net)

 

bitvec

$ ls ~ -r-- Home -r-- About dr-x Blog dr-x Crates -r-- Hermaeus dr-x Oeuvre -r-- Portfolio -r-- Résumé -r-- WebRing -r-- Workbench I recently rebuilt this site. I would appreciate bug reports, in presentation, content, or formerly-working links, at the G

myrrlyn.net

 픽셀 데이터는 가로줄마다 끊는다. 각 줄의 앞에 한 바이트가 더 들어가는데 이 바이트의 정보로 앞 픽셀과 뒤 픽셀의 관계를 정할 수 있다. 아무래도 데이터에 0이 많을수록 압축이 쉬우니까 같은 색은 이걸로 때우고 0 칠하라고 있는 것.

 crc는 라이브러리 쓰는 것보다 그냥 자체 구현이 쉽다. 러스트로 옮겨도 아무 문제 없었으니까 그냥 직접 명세 보고 만들어 쓰자. crc 안맞으니까 로컬에선 괜찮은데 웹기반에서는 안 열렸다.

 그리고 이 데이터들을 반환하면 웹 어셈블리 부분 끝이다.

타입스크립트

 화면 상태가 3개다. 처음 / 이미지 받고 / 이미지 팔레트화 되고. 처음에는 개체들을 다 생성했는데 너무 장황해져서 그냥 html 문자열 부어서 생성하는 게 나았다. 이러나저러나 'as'를 꽤 많이 쓰게 됐다.

  여기서는 별 문제 없었던 듯. 여기까지 만들고 잠깐 쉬었다.