본문 바로가기

리액트 네이티브

RN Perspective Crop - 네이티브편

RN Perspective Crop - OpenCV편에서 만든 WebView는 이미지 처리 연산만 담당할 뿐 사용자에게 보이지 않습니다.

하지만 내부의 캔버스는 크기를 가지고 있습니다. 윈도우에서 브라우저 크기를 조절해도 스크롤바만 생기고 내부 컨텐츠 크기는 그대로인 것과 동일한 개념입니다.

 

이제 아래와 같이 사용자가 크롭할 영역을 선택할 수 있는 컴포넌트인 Perspective Cropper를 만들어보겠습니다.

 

Perspective Crop

주요 재료

react-native-gesture-handler

크기가 없고, 자식 컴포넌트를 터치하고 드래그했을 때, 움직인 거리 정보를 포함한 이벤트를 발생시키는 PanGestureHandler 컴포넌트를 제공해 줍니다.

 

react-native-reanimated

리액트 네이티브에서 기본적으로 제공되는 애니메이션 라이브러리인 Animated는 느립니다.

 

한 두개 컴포넌트를 Animated로 만드는 것은 차이가 없을지 모르겠지만, 위의 gif처럼 만들기 위해서 9개의 컴포넌트가 필요하고, 사용자의 터치 동작에 의해 최소 4개의 컴포넌트가 새로운 위치를 계산하고 그곳으로 이동해야 합니다. 

 

이 라이브러리는 리액트 네이티브 자바스크립트 엔진 외부에 별도의 자바스크립트 엔진을 새로 만들고, 여기서 애니메이션 동작을 UI 스레드와 동기적으로 처리한다고 합니다.

 

그리고 만들어질 때부터 react-native-gesture-handler와 연계하는 것을 가정했기 때문에, react-native-gesture-handler에 포함된 컴포넌트와 연동하기도 쉽습니다.

 

react-native-svg

사각형을 그리고, 선택되지 않은 영역을 어둡게 처리하기 위해 사용됩니다.

react-native-reanimated의 Animated.createAnimatedComponent를 사용해서 Animated 컴포넌트로 래핑해서 사용합니다.

 

클래스 컴포넌트? 함수 컴포넌트?

저는 컴포넌트를 만들 때 99%는 함수 컴포넌트를 사용합니다. Hook이 너무 편하기 때문인데요. 이번엔 좀 달랐습니다.

 

해당 컴포넌트를 외부에서 참조 객체를 사용하여 해당 컴포넌트의 crop 함수를 실행시키고 싶었기 때문입니다.

 

React.forwordRef 함수가 있길래 검색해 봤더니 함수 컴포넌트로 참조 객체를 받아서 내부의 클래스 컴포넌트와 연결시켜 주는 래핑 함수였고, 커스텀 객체의 커스텀 메서드를 외부에서 실행하려면 클래스형 컴포넌트로 만들어야 했습니다.

설계

주요 기능만 다이어그램으로 그리면 아래와 같습니다.

 

클릭하여 확대

 

Perspective Cropper

직접 만든 컴포넌트인 Cropper와 기본 컴포넌트인 Image 두 개가 겹쳐 있는 상태입니다.

 

Image는 가로, 세로 100%이고, resizedMode가 contain이기 때문에, 이미지는 원본 비율로 Image 컴포넌트 안에서 꽉 차게 됩니다.

 

그리고 onImageLoad에서 얻은 이미지의 가로, 세로(state.imageWidth/imageHeight) 비율과 컨테이너의 가로, 세로(state.viewWidth/viewHeight) 비율을 비교해서 화면에 보이는 이미지의 가로, 세로 크기(state.imageWidth/imageHeight)를 구합니다. 이것을 Cropper에 전달합니다.

 

Cropper

좌상단, 우상단, 좌하단, 우하단 순서로 p1, p2, p3, p4입니다. 캔버스 상의 포인트들과 순서가 같습니다.

 

단, Cropper에서는 absolute bottom을 사용해서 각 포인트의 위치를 조절하기 때문에, 포인트가 움직일 때마다, absolute top 기준으로 변환해 줍니다.

 

그리고 Perspective Cropper로 부터 화면에 표시되는 이미지의 크기와 실제 이미지의 크기가 둘 다 넘어오기 때문에, 이것을 이용하여 가중치를 구하고, absolute top 기준으로 변환된 값에 곱해서 실제 이미지 상의 좌표를 구해줍니다. 

p12는 p1과 p2 사이의 View이고, p12, p13, p23, p34의 left와 bottom 값은 onGestureEvent에서 관련된 포인트의 left와 bottom이 변할 때마다 자동으로 바뀝니다.

 

pCenter는 가운데 일정 영역을 차지하는 View입니다.

 

적지 않은 애니메이션인데, react-native-reanimated 덕분에 무리없이 동작합니다.

 

여기에 더하여 각 포인트마다 움직임을 제한하느라 Cropper 만드는게 제일 힘들었던 것 같습니다.