/** * Copyright (c) 2015-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format * @flow */ 'use strict'; var React = require('react'); var ReactNative = require('react-native'); var { CameraRoll, Image, ImageEditor, Platform, ScrollView, StyleSheet, Text, TouchableHighlight, View, } = ReactNative; var PAGE_SIZE = 20; type ImageOffset = {| x: number, y: number, |}; type ImageSize = {| width: number, height: number, |}; type ImageCropData = {| offset: ImageOffset, size: ImageSize, displaySize?: ?ImageSize, resizeMode?: ?any, |}; class SquareImageCropper extends React.Component< $FlowFixMeProps, $FlowFixMeState, > { state: any; _isMounted: boolean; _transformData: ImageCropData; constructor(props) { super(props); this._isMounted = true; this.state = { randomPhoto: null, measuredSize: null, croppedImageURI: null, cropError: null, }; this._fetchRandomPhoto(); } async _fetchRandomPhoto() { try { const data = await CameraRoll.getPhotos({first: PAGE_SIZE}); if (!this._isMounted) { return; } var edges = data.edges; var edge = edges[Math.floor(Math.random() * edges.length)]; var randomPhoto = edge && edge.node && edge.node.image; if (randomPhoto) { this.setState({randomPhoto}); } } catch (error) { console.warn("Can't get a photo from camera roll", error); } } componentWillUnmount() { this._isMounted = false; } render() { if (!this.state.measuredSize) { return ( { var measuredWidth = event.nativeEvent.layout.width; if (!measuredWidth) { return; } this.setState({ measuredSize: {width: measuredWidth, height: measuredWidth}, }); }} /> ); } if (!this.state.croppedImageURI) { return this._renderImageCropper(); } return this._renderCroppedImage(); } _renderImageCropper() { if (!this.state.randomPhoto) { return ; } var error = null; if (this.state.cropError) { error = {this.state.cropError.message}; } return ( Drag the image within the square to crop: (this._transformData = data)} /> Crop {error} ); } _renderCroppedImage() { return ( Here is the cropped image: Try again ); } _crop() { ImageEditor.cropImage( this.state.randomPhoto.uri, this._transformData, croppedImageURI => this.setState({croppedImageURI}), cropError => this.setState({cropError}), ); } _reset() { this.setState({ randomPhoto: null, croppedImageURI: null, cropError: null, }); this._fetchRandomPhoto(); } } class ImageCropper extends React.Component<$FlowFixMeProps, $FlowFixMeState> { _contentOffset: ImageOffset; _maximumZoomScale: number; _minimumZoomScale: number; _scaledImageSize: ImageSize; _horizontal: boolean; componentWillMount() { // Scale an image to the minimum size that is large enough to completely // fill the crop box. var widthRatio = this.props.image.width / this.props.size.width; var heightRatio = this.props.image.height / this.props.size.height; this._horizontal = widthRatio > heightRatio; if (this._horizontal) { this._scaledImageSize = { width: this.props.image.width / heightRatio, height: this.props.size.height, }; } else { this._scaledImageSize = { width: this.props.size.width, height: this.props.image.height / widthRatio, }; if (Platform.OS === 'android') { // hack to work around Android ScrollView a) not supporting zoom, and // b) not supporting vertical scrolling when nested inside another // vertical ScrollView (which it is, when displayed inside UIExplorer) this._scaledImageSize.width *= 2; this._scaledImageSize.height *= 2; this._horizontal = true; } } this._contentOffset = { x: (this._scaledImageSize.width - this.props.size.width) / 2, y: (this._scaledImageSize.height - this.props.size.height) / 2, }; this._maximumZoomScale = Math.min( this.props.image.width / this._scaledImageSize.width, this.props.image.height / this._scaledImageSize.height, ); this._minimumZoomScale = Math.max( this.props.size.width / this._scaledImageSize.width, this.props.size.height / this._scaledImageSize.height, ); this._updateTransformData( this._contentOffset, this._scaledImageSize, this.props.size, ); } _onScroll(event) { this._updateTransformData( event.nativeEvent.contentOffset, event.nativeEvent.contentSize, event.nativeEvent.layoutMeasurement, ); } _updateTransformData(offset, scaledImageSize, croppedImageSize) { var offsetRatioX = offset.x / scaledImageSize.width; var offsetRatioY = offset.y / scaledImageSize.height; var sizeRatioX = croppedImageSize.width / scaledImageSize.width; var sizeRatioY = croppedImageSize.height / scaledImageSize.height; var cropData: ImageCropData = { offset: { x: this.props.image.width * offsetRatioX, y: this.props.image.height * offsetRatioY, }, size: { width: this.props.image.width * sizeRatioX, height: this.props.image.height * sizeRatioY, }, }; this.props.onTransformDataChange && this.props.onTransformDataChange(cropData); } render() { return ( ); } } exports.framework = 'React'; exports.title = 'ImageEditor'; exports.description = 'Cropping and scaling with ImageEditor'; exports.examples = [ { title: 'Image Cropping', render() { return ; }, }, ]; var styles = StyleSheet.create({ container: { flex: 1, alignSelf: 'stretch', }, imageCropper: { alignSelf: 'center', marginTop: 12, }, cropButtonTouchable: { alignSelf: 'center', marginTop: 12, }, cropButton: { padding: 12, backgroundColor: 'blue', borderRadius: 4, }, cropButtonLabel: { color: 'white', fontSize: 16, fontWeight: '500', }, });