UNPKG

expo-image-cropper-gl-react

Version:

Crop image in Expo using pan to move, pinch to zoom -> capture to file or base64

253 lines (233 loc) 7.96 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { View, Image, PixelRatio, PanResponder } from 'react-native'; import { Surface } from 'gl-react-expo'; import GLImage from './Image'; const imageDimensionsAfterZoom = (viewport, dimensions, zoom) => { const ImageRatio = dimensions.width / dimensions.height; const ViewportRatio = viewport.width / viewport.height; if (ImageRatio > ViewportRatio) { return { height: Math.floor(viewport.height / zoom), width: Math.floor((viewport.height * ImageRatio) / zoom), }; } else return { height: Math.floor(viewport.width / ImageRatio / zoom), width: Math.floor(viewport.width / zoom), }; }; const movementFromZoom = (gestureState, viewport, dimensions, offsets, zoom) => { let newPosX, newPosY; // X-axis let widthOffset = dimensions.width - viewport.width; let pxVsMovX = 1 / dimensions.width; let moveX = gestureState.dx * pxVsMovX * zoom; newPosX = parseFloat(offsets.x) - parseFloat(moveX); // Y-axis let heightOffset = dimensions.height - viewport.height; let pxVsMovY = 1 / dimensions.height; let moveY = gestureState.dy * pxVsMovY * zoom; newPosY = parseFloat(offsets.y) - parseFloat(moveY); return { x: newPosX, y: newPosY, }; }; class ImageCrop extends Component { constructor(props) { super(props); this.state = { zoom: 1, //pan settings centerX: 0.5, centerY: 0.5, //Image sizes imageHeight: 300, imageWidth: 300, imageDimHeight: 0, imageDimWidth: 0, currentCapture: '', }; this.surfaceCont = React.createRef(); } async captureSurface() { const pictureSave = await this.surfaceCont.current.glView.capture(); return pictureSave; } componentWillMount() { Image.getSize(this.props.image, (width, height) => { //update state this.setState({ imageHeight: height, imageWidth: width, }); }); // //get dimensions after crop // this._dimensionAfterZoom = imageDimensionsAfterZoom( { height: this.props.cropHeight, width: this.props.cropWidth }, { height: this.state.imageHeight, width: this.state.imageWidth }, this.state.zoom, ); this.setState({ imageDimHeight: this._dimensionAfterZoom.height, imageDimWidth: this._dimensionAfterZoom.width, }); this._panResponder = PanResponder.create({ onStartShouldSetPanResponder: (evt, gestureState) => true, onStartShouldSetPanResponderCapture: (evt, gestureState) => true, onMoveShouldSetPanResponder: (evt, gestureState) => true, onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, onPanResponderTerminationRequest: (evt, gestureState) => false, onShouldBlockNativeResponder: (evt, gestureState) => true, onPanResponderGrant: (evt, gestureState) => { //move variables this.offsetX = this.state.centerX; this.offsetY = this.state.centerY; //zoom variables this.zoomLastDistance = 0; this.zoomCurrentDistance = 0; }, onPanResponderMove: (evt, gestureState) => { //We are moving the image if (evt.nativeEvent.changedTouches.length <= 1) { var trackX = (gestureState.dx / this.props.cropWidth) * this.state.zoom; var trackY = (gestureState.dy / this.props.cropHeight) * this.state.zoom; var newPosX = Number(this.offsetX) - Number(trackX); var newPosY = Number(this.offsetY) - Number(trackY); if (newPosX > 1) newPosX = Number(1); if (newPosY > 1) newPosY = Number(1); if (newPosX < 0) newPosX = Number(0); if (newPosY < 0) newPosY = Number(0); var movement = movementFromZoom( gestureState, { width: this.props.cropWidth, height: this.props.cropHeight }, { width: this.state.imageDimWidth, height: this.state.imageDimHeight, }, { x: this.offsetX, y: this.offsetY }, this.state.zoom, ); this.setState({ centerX: movement.x }); this.setState({ centerY: movement.y }); } else { //We are zooming the image if (this.zoomLastDistance == 0) { let a = evt.nativeEvent.changedTouches[0].locationX - evt.nativeEvent.changedTouches[1].locationX; let b = evt.nativeEvent.changedTouches[0].locationY - evt.nativeEvent.changedTouches[1].locationY; let c = Math.sqrt(a * a + b * b); this.zoomLastDistance = c.toFixed(1); } else { let a = evt.nativeEvent.changedTouches[0].locationX - evt.nativeEvent.changedTouches[1].locationX; let b = evt.nativeEvent.changedTouches[0].locationY - evt.nativeEvent.changedTouches[1].locationY; let c = Math.sqrt(a * a + b * b); this.zoomCurrentDistance = c.toFixed(1); //what is the zoom level var screenDiagonal = Math.sqrt( this.state.imageHeight * this.state.imageHeight + this.state.imageWidth * this.state.imageWidth, ); var distance = (this.zoomCurrentDistance - this.zoomLastDistance) / 400; var zoom = this.state.zoom - distance; if (zoom < 0) zoom = 0.0000001; if (zoom > 1) zoom = 1; this.setState({ zoom: zoom, }); //Set last distance.. this.zoomLastDistance = this.zoomCurrentDistance; } } }, }); } componentWillReceiveProps(nextProps) { if (this.props.zoom != nextProps.zoom) { var zoom = (100 - nextProps.zoom) / 100; this.setState({ zoom: zoom }); } // //get dimensions after crop // this._dimensionAfterZoom = imageDimensionsAfterZoom( { height: this.props.cropHeight, width: this.props.cropWidth }, { height: this.state.imageHeight, width: this.state.imageWidth }, this.state.zoom, ); this.setState({ imageDimHeight: this._dimensionAfterZoom.height, imageDimWidth: this._dimensionAfterZoom.width, }); } render() { return ( <View {...this._panResponder.panHandlers}> <Surface style={{ width: this.props.cropWidth, height: this.props.cropHeight }} pixelRatio={this.props.pixelRatio} backgroundColor="transparent" ref={this.surfaceCont} > <GLImage height={this.props.cropHeight} width={this.props.cropWidth} source={{ uri: this.props.image }} imageSize={{ height: this.state.imageHeight, width: this.state.imageWidth, }} resizeMode="cover" zoom={this.state.zoom} center={[this.state.centerX, this.state.centerY]} /> </Surface> </View> ); } crop() { return this.surfaceCont.current.glView.capture({ quality: this.props.quality, type: this.props.type, format: this.props.format, filePath: this.props.filePath, }); } } ImageCrop.defaultProps = { image: '', cropWidth: 300, cropHeight: 300, zoomFactor: 0, minZoom: 0, maxZoom: 100, quality: 1, pixelRatio: PixelRatio.get(), type: 'jpg', format: 'base64', filePath: '', }; ImageCrop.propTypes = { image: PropTypes.string.isRequired, cropWidth: PropTypes.number.isRequired, cropHeight: PropTypes.number.isRequired, zoomFactor: PropTypes.number, maxZoom: PropTypes.number, minZoom: PropTypes.number, quality: PropTypes.number, pixelRatio: PropTypes.number, type: PropTypes.string, format: PropTypes.string, filePath: PropTypes.string, }; export default ImageCrop;