Skip to content

Instantly share code, notes, and snippets.

@JoonDong2
Created May 18, 2021 04:02
import React, { Component } from "react"
import { Image, ImageLoadEventData, LayoutChangeEvent, NativeSyntheticEvent, View, ViewStyle } from "react-native"
import Cropper from "./Cropper";
import { OpenCVContext, OpenCVContextProps } from "../../../Contexts/OpenCVContext";
interface PerspectiveCropperProps {
source: {uri: string};
style?: ViewStyle;
}
// Cropper에서 좌표 가중치를 계산하기 위해 image~와 imageLayout~을 상태로 만들어 줄 필요가 있다.
interface State {
didLayout: boolean;
imageLoaded: boolean;
viewWidth: number;
viewHeight: number;
imageWidth: number;
imageHeight: number;
resizedImageWidth: number;
resizedImageHeight: number;
uri: string;
}
export interface Point {
x: number;
y: number
}
interface Points {
p1: Point,
p2: Point,
p3: Point,
p4: Point
}
export default class PersPectiveCropper extends Component<PerspectiveCropperProps, State> {
static contextType = OpenCVContext;
opencvContext: OpenCVContextProps | undefined = undefined;
private async loadImageToOpenCV(uri: string, imageWidth: number, imageHeight: number) {
if(!this.opencvContext) return;
const {imageLoad} = this.opencvContext;
imageLoad(uri, imageWidth, imageHeight);
}
points: Points = {
p1: {x: 0, y: 0},
p2: {x: 0, y: 0},
p3: {x: 0, y: 0},
p4: {x: 0, y: 0},
}
constructor(props: PerspectiveCropperProps, context: OpenCVContextProps) {
super(props);
this.opencvContext = context;
const state: State = {
didLayout: false,
imageLoaded: false,
viewWidth: 0,
viewHeight: 0,
imageWidth: 0,
imageHeight: 0,
resizedImageWidth: 0,
resizedImageHeight: 0,
uri: this.props.source.uri
}
this.state = state;
this.onImageLoad = this.onImageLoad.bind(this);
this.onLayout = this.onLayout.bind(this);
this.setP1 = this.setP1.bind(this);
this.setP2 = this.setP2.bind(this);
this.setP3 = this.setP3.bind(this);
this.setP4 = this.setP4.bind(this);
this.setPoints = [this.setP1, this.setP2, this.setP3, this.setP4];
}
// onLayout보다 먼저 발생했다 ... 언제나?
onImageLoad(event: NativeSyntheticEvent<ImageLoadEventData>) {
const {width: imageWidth, height: imageHeight} = event.nativeEvent.source; // 이미지의 실제 너비, 높이
this.loadImageToOpenCV(this.state.uri, imageWidth, imageHeight);
const {didLayout, viewWidth, viewHeight} = this.state;
// onLayout보다 먼저 발생한 경우, (일반적)
if(!didLayout) {
this.setState({
...this.state,
imageLoaded: true,
imageWidth,
imageHeight
});
return;
}
// onImageLoad가 더 늦게 발생한 경우, (외부에서 이미지를 변경한 경우)
const viewRatio = viewWidth / viewHeight; // FastImage의 비율
const imageRatio = imageWidth / imageHeight; // 이미지의 비율
const isWidthFull = imageRatio > viewRatio;
// FastImage 안의 이미지 높이, 너비 결정
const resizedImageWidth = isWidthFull ? viewWidth : (viewHeight * imageRatio);
const resizedImageHeight = isWidthFull ? (viewWidth / imageRatio) : viewHeight;
// console.log("[onImageLoad] 이미지 가로 풀? ", isWidthFull);
this.setState({
...this.state,
imageLoaded: true,
imageWidth,
imageHeight,
resizedImageWidth,
resizedImageHeight
});
}
onLayout(event: LayoutChangeEvent) {
// FastImage의 높이, 너비
const {width: viewWidth, height: viewHeight} = event.nativeEvent.layout;
const {imageWidth, imageHeight, imageLoaded} = this.state;
if(!imageLoaded) {
this.setState({
...this.state,
didLayout: true,
viewWidth,
viewHeight
})
return
}
// 이미지 로드 후 레이아웃 이벤트 발생 (일반적인 경우)
const viewRatio = viewWidth / viewHeight; // FastImage의 비율
const imageRatio = imageWidth / imageHeight; // 실제 이미지의 비율
const isWidthFull = imageRatio > viewRatio;
const resizedImageWidth = isWidthFull ? viewWidth : viewHeight * imageRatio;
const resizedImageHeight = isWidthFull ? viewWidth / imageRatio : viewHeight;
// console.log("[onLayout] 이미지 가로 풀? ", isWidthFull);
// 이미지는 이미 로드됐기 때문에, imageWidth와 imageHeight는 최신 상태이다.
this.setState({
...this.state,
didLayout: true,
viewWidth,
viewHeight,
resizedImageWidth,
resizedImageHeight
});
}
setP1(point: Point) { this.points.p1 = point; }
setP2(point: Point) { this.points.p2 = point; }
setP3(point: Point) { this.points.p3 = point; }
setP4(point: Point) { this.points.p4 = point; }
setPoints: ((point: Point) => void)[] = [];
// 크롭이 완료되고, 이미지가 리랜더링되기 전에 다시 크롭하면 오류가 발생한다. 외부에서 차단해야 한다.
crop() {
if(!this.opencvContext) return;
const {crop} = this.opencvContext;
crop(this.points);
}
componentDidUpdate(_: PerspectiveCropperProps, prevState: State){
const newUri = this.props.source.uri;
if(newUri !== prevState.uri) {
// uri 바뀌면 이미지 다시 로드되면서 onImageLoad 실행 => loadImageToOpenCV
this.setState({
...this.state,
uri: newUri
});
}
}
componentWillUnmount() {
if(!this.opencvContext) return;
const {imageUnload} = this.opencvContext;
imageUnload();
}
// 외부에서 입력되는 style이 hook인 경우, onLayout 재실행
render() {
return (
<View onLayout={this.onLayout} style={[this.props.style, {justifyContent: 'center', alignItems: 'center'}]}>
{this.state.didLayout && <Image width={this.state.viewWidth} height={this.state.viewHeight} resizeMode="contain" style={{position: 'absolute', width: '100%', height: '100%'}} onLoad={this.onImageLoad} source={{uri: this.state.uri}}/>}
{this.state.imageLoaded && <Cropper resizedImageWidth={this.state.resizedImageWidth} resizedImageHeight={this.state.resizedImageHeight} imageWidth={this.state.imageWidth} imageHeight={this.state.imageHeight} setPoints={this.setPoints}/>}
</View>
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment