기록

[Flutter] 이미지에서 선택 영역 투명화하기

avocado8 2024. 10. 11. 12:13

 

졸프 개발에 바쁜 요즘...

이번에 구현할 기능은 < 달리 2를 활용한 이미지 수정 > 기능이다.

달리2 모델을 돌리는 건 백엔드가 해줄 거고, 프론트에서 할 일은 사용자가 원본 사진에서 특정 영역을 선택하면 그 부분만 투명하게 만든 png 파일을 갖다주는 것이다.

 

1. 이미지에서 특정 영역 선택하기

이미지를 띄울 컨테이너를 만들고, 이미지와 버튼을 만들어준다.

이미지 위를 드래그하면 사각형으로 선택 영역을 표시해줘야 한다. 이미지 위에 사각형이 올라가는 구조이므로 Stack을 사용하고, 드래그 제스처를 구현해야 하므로 onPan과 관련된 메서드들을 사용해준다.

 

변수들은 Stateful 위젯 안에 이런 타입으로 선언해줬다.

 

onPanStart로 드래그를 시작했을 때 사각형의 시작점이 될 오프셋을 지정해주고, onPanUpdate로 마지막으로 터치하고 있는 지점을 사각형의 끝점으로 지정해준다. 두 점을 기준으로 Rect를 만들고, Rect가 생겼다면 Positioned로 오퍼시티 80의 파란 사각형으로 해당 부분을 표시해준다.

 

2. 선택 영역만 투명하게 만들기

'선택 완료' 버튼을 누르면 이미지에서 선택된 부분의 픽셀들의 투명화 값을 조절해 해당 부분만 투명하게 만들어줄 것이다.

image 패키지를 사용했다.

https://pub.dev/packages/image

 

image | Dart package

Dart Image Library provides server and web apps the ability to load, manipulate, and save images with various image file formats.

pub.dev

 

앗 디버깅 지우는거 깜빡...

이미지는 네트워크로 갖고올 것이므로 http를 사용해 Uint8List byte 형태로 가져오고, Image 변수에 디코딩한다.

중간에 scale하는 부분은 가져온 이미지의 실제 크기와 화면에 보여지는 크기가 달라서, 선택범위에 오차가 생기는 것을 방지하기 위해 넣어주었다.

선택된 사각형 범위 안에 있는 픽셀들만 a값을 0으로 바꿔 준다. rgb는 안 해도 되는데 테스트 용도로 그냥...

이런 식으로 선택된 Rect의 부분만 투명하게 된 이미지를 리턴받을 수 있다.

 

3. 투명화된 이미지 저장하기

투명화 이미지는 png로 저장되어야 하므로 encodePng를 사용해 이미지를 png의 uint8list로 만들어주고, 디렉토리 경로를 지정해 파일을 저장해준다.

 

🚀 트러블 슈팅

이렇게 하면 대략 구현은 끝난다. 잘 되는지 확인하려면 저장된 이미지를 확인해봐야 하는데, 그러기 위해 처음에는 위 코드에서 보이는 것처럼 image gallery saver 패키지를 이용해 내 휴대폰에 사진을 저장해서 확인하기로 했다.

망함

처음엔 아예 표시가 안 되더니, 검정색으로 바꾸니 되는가 싶다가도 아니었다. 저건 투명화가 아니라 그냥 검정 네모다. 즉 선택은 잘 되고 있는데 그냥 검은색으로 칠해지고 있었다.

 

rgb는 먹는데 a만 안 먹는 건 이상하니까 패키지 사용에는 문제가 없다고 생각했다.

그렇다면 생각할 수 있는 문제는 확장자였다. 투명화는 jpg같은 확장자에선 먹지 않고 png 확장자여야 하는데,

아니나다를까 jpg로 저장되고 있었다.

근데 난 분명 png로 인코딩을 했지 않나... 그렇다면 이건 ImageGallerySaver가 파일을 jpg로 저장하기 때문이겠구나!

그렇게 문서를 뒤졌으나 png로 저장하는 방법을 발견하지 못했다........

 

그래서 그냥 플러터 프로젝트 패키지 자체에 저장되는 파일을 까보기로 했다.

갤러리 세이브할때만의 문제라면 그건 상관없다. 어차피 이 투명화 마스크 파일이 사용자의 휴대폰에 저장될 일은 없다. 달리에 들어가기 위한 중간 처리작업일 뿐이니까.

 

저장된 파일 경로를 출력해보면, 나의 png 파일은

/data/data/0/com.example.qnart(패키지명)/app_flutter 안에 있다는 것을 확인할 수 있다.

 

그래서 저길 어떻게 들어가느냐 하면 일단 안드로이드 스튜디오를 켠다. (나는 vs코드를 사용하고 있어서 이 프로젝트가 안드스튜디오에 열려있지 않았다...) 아무 프로젝트나 실행하고 디바이스 매니저에서 연결된 내 기기를 선택하면,

Open in Device Explorer 을 눌러준다.

 

여기서 이제 출력된 경로를 따라 찾아주면 된다.

data\data\com.example.qnart(패키지명)\app)_flutter 안에 들어가면,

내가 저장한 파일이 있다.

오예 이제 열어봐야지

...

이거 그냥 껌정네모같은데

 

그리고 역시나 껌정네모였다. ㅋㅋ

왜안먹지. 왜 rgb는 먹는데 a가 안먹냐고. 그렇다는건 확장자 문제밖에 생각이 안 나는데 머냐고.

챗지피티를 털고 플러터 그림판이니 뭐시기니 열심히 검색하다가 순간 떠오른 시나리오가 있었다.

아...이거...... 혹시 내가 넣은 원본 그림이 png가 아니었던 거 아냐......?

응 jpg야 ㅋㅋ

 

사실 넣는 원본 자체는 상관없다고 생각했었다. 내가 저장을 png로 하면 되는 거 아닌가? 싶어서...

아무튼 문제 후보를 찾았으니 바로 png 그림 링크로 바꿔서 다시 테스트해봤다.

 

아...

 

 

완전 투명...

 

원본 이미지도 png여야 했다. ㅎㅎ; 데이터베이스에 있는 사진들을 싹 png로 교체할 필요가 생겼다.

아무튼 해결!

🤗

 

+10.13

...

한 줄 알았는데

백엔드와 연결 테스트 도중 마스킹이 작동하지 않는 문제가 다시 발생했다...

 

데이터베이스에 저장된 이미지는 확실히 png이므로 확장자 문제는 아니다

그럼 이미지를 불러올 때 혹시 뭔가 문제가 생기나?

불러온 이미지 파일의 타입을 확인해보면

맞는데....

png 맞다. 그럼 확장자 문제는 일단 확실히 아니다.

 

아니 근데 그냥 인터넷에서 긁어온 png 파일 링크는 잘만 마스킹이 되는데 왜 똑같은 조건의 우리 파일 링크는 안 되는가...

아니 위는 되고 아래는 안되는 이유가 뭔가 둘다 png인데.

코드나 로직상의 문제는 아닌 게 확실하다. 왜냐면 위에 것들은 잘만 되니까

그렇다면 결국 위와 아래의 차이가 무엇이냐인데, 문제는 "png에서 알파 채널의 존재 유무" 였다.

 

png는 rgb에 a, 즉 투명도 채널을 추가로 사용한다. 그런데 웬걸.. png 그림이더라도 저 아래 고흐 그림처럼 그림 자체에 투명한 부분이 존재하지 않을 경우 a 채널을 자동으로 사용하지 않게 된다고 한다. 위에 있는 그림들은 애초에 투명한 부분들이 존재했기에 a 채널이 인식되었던 것이다.

 

원인을 찾았으면 해결하면 된다 이제!

a 채널이 없어진 게 문제라면 a 채널을 강제로 추가해주면 되는 일이다. 지피티와 공식문서의 힘을 빌렸다. 간단한 문제들은 지피티만으로도 대부분 해결되나, 지피티가 제공하는 정보는 패키지의 예전 버전을 참조하고 있을 확률이 높아 개발 시점에서는 쓸 수 없을 수도 있다. 그럴 때 공식문서를 열심히 뒤져서 업데이트해주면 된다. 

 

image 패키지 공식 문서 >>

https://github.com/brendan-duncan/image/blob/main/doc/README.md

 

image/doc/README.md at main · brendan-duncan/image

Dart Image Library for opening, manipulating, and saving various different image file formats. - brendan-duncan/image

github.com

 

image 패키지 최신 버전에 따르면 hasAlpha로 알파채널 여부를 확인할 수 있다.

알파채널이 없다면, convert 메서드를 사용해 채널을 rgba 4개로 설정해주고, 그림은 불투명해야 하니 알파채널에 255를 할당해준다. 그러면 이제 마스킹이 다시 잘 되는 것을 확인할 수 있다.

 

+ 추가로, 달리2의 image edit 기능을 사용하려면 "이미지 크기가 4MB 이하" 여야 한다는 조건이 있다.

근데 마스킹 이미지 크기가 막 12MB 이난리가 나고 (알파채널 때문일 듯하다) 있어서 파일을 잘 저장해도 결과가 제대로 오지 않았다.

다행히 이미지 패키지에는 이미지를 리사이징하는 메서드가 구현되어 있다.

https://github.com/brendan-duncan/image/blob/main/doc/transform.md

 

image/doc/transform.md at main · brendan-duncan/image

Dart Image Library for opening, manipulating, and saving various different image file formats. - brendan-duncan/image

github.com

 

리사이징할 이미지와 최대 사이즈를 받아서 리사이징하는 함수를 작성해주었다.

가로/세로를 0.7씩 줄여가며 용량이 최대 사이즈보다 작아질 때까지 리사이징을 반복해준다.

 

선택영역 투명화 처리가 끝난 뒤 위 함수를 적용해 이미지 크기를 줄여준다.

그럼 이제 저장되는 마스킹 이미지의 용량이 4MB를 넘지 않도록 해줄 수 있다.

 

특정 영역 선택 -> 선택 완료 버튼 -> (마스킹 이미지 제작) -> 바꿀 내용 입력 -> (백엔드에서 달리2 결과 반환) -> 최종 결과 출력!

선택한 부분에 크리스마스 트리가 생긴 것을 확인할 수 있다.

 

진짜 해결.

문제가 또 생기지 말았으면... ㅎㅎ