1import { Ionicons } from '@expo/vector-icons'; 2import { Asset } from 'expo-asset'; 3import * as ImageManipulator from 'expo-image-manipulator'; 4import * as ImagePicker from 'expo-image-picker'; 5import React from 'react'; 6import { 7 Image, 8 ScrollView, 9 StyleSheet, 10 Text, 11 TouchableOpacity, 12 TouchableOpacityProps, 13 View, 14} from 'react-native'; 15 16import Colors from '../constants/Colors'; 17 18interface State { 19 ready: boolean; 20 image?: Asset | ImageManipulator.ImageResult; 21 original?: Asset; 22} 23 24// See: https://github.com/expo/expo/pull/10229#discussion_r490961694 25// eslint-disable-next-line @typescript-eslint/ban-types 26export default class ImageManipulatorScreen extends React.Component<{}, State> { 27 static navigationOptions = { 28 title: 'ImageManipulator', 29 }; 30 31 readonly state: State = { 32 ready: false, 33 }; 34 35 componentDidMount() { 36 const image = Asset.fromModule(require('../../assets/images/example2.jpg')); 37 image.downloadAsync().then(() => { 38 this.setState({ 39 ready: true, 40 image, 41 original: image, 42 }); 43 }); 44 } 45 46 render() { 47 return ( 48 <ScrollView style={styles.container}> 49 <View style={{ padding: 10 }}> 50 <View style={styles.actionsButtons}> 51 <Button style={styles.button} onPress={() => this._rotate(90)}> 52 <Ionicons name="ios-refresh" size={16} color="#ffffff" /> 90 53 </Button> 54 <Button style={styles.button} onPress={() => this._rotate(45)}> 55 45 56 </Button> 57 <Button style={styles.button} onPress={() => this._rotate(-90)}> 58 -90 59 </Button> 60 <Button 61 style={styles.button} 62 onPress={() => this._flip(ImageManipulator.FlipType.Horizontal)}> 63 Flip horizontal 64 </Button> 65 <Button 66 style={styles.button} 67 onPress={() => this._flip(ImageManipulator.FlipType.Vertical)}> 68 Flip vertical 69 </Button> 70 <Button style={styles.button} onPress={() => this._resize({ width: 250 })}> 71 Resize width 72 </Button> 73 <Button style={styles.button} onPress={() => this._resize({ width: 300, height: 300 })}> 74 Resize both to square 75 </Button> 76 <Button style={styles.button} onPress={() => this._compress(0.1)}> 77 90% compression 78 </Button> 79 <Button style={styles.button} onPress={this._crop}> 80 Crop - half image 81 </Button> 82 <Button style={styles.button} onPress={this._combo}> 83 Cccombo 84 </Button> 85 </View> 86 87 {this.state.ready && this._renderImage()} 88 <View style={styles.footerButtons}> 89 <Button style={styles.button} onPress={this._pickPhoto}> 90 Pick a photo 91 </Button> 92 <Button style={styles.button} onPress={this._reset}> 93 Reset photo 94 </Button> 95 </View> 96 </View> 97 </ScrollView> 98 ); 99 } 100 101 _renderImage = () => { 102 return ( 103 <View style={styles.imageContainer}> 104 <Image 105 source={{ uri: (this.state.image! as Asset).localUri || this.state.image!.uri }} 106 style={styles.image} 107 /> 108 </View> 109 ); 110 }; 111 112 _pickPhoto = async () => { 113 const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync(); 114 if (status !== 'granted') { 115 alert('Permission to MEDIA_LIBRARY not granted!'); 116 return; 117 } 118 const result = await ImagePicker.launchImageLibraryAsync({ 119 allowsEditing: false, 120 }); 121 if (result.cancelled) { 122 alert('No image selected!'); 123 return; 124 } 125 this.setState({ image: result }); 126 }; 127 128 _rotate = async (deg: number) => { 129 await this._manipulate([{ rotate: deg }], { 130 format: ImageManipulator.SaveFormat.PNG, 131 }); 132 }; 133 134 _resize = async (size: { width?: number; height?: number }) => { 135 await this._manipulate([{ resize: size }]); 136 }; 137 138 _flip = async (flip: ImageManipulator.FlipType) => { 139 await this._manipulate([{ flip }]); 140 }; 141 142 _compress = async (compress: number) => { 143 await this._manipulate([], { compress }); 144 }; 145 146 _crop = async () => { 147 await this._manipulate([ 148 { 149 crop: { 150 originX: 0, 151 originY: 0, 152 width: this.state.image!.width! / 2, 153 height: this.state.image!.height!, 154 }, 155 }, 156 ]); 157 }; 158 159 _combo = async () => { 160 await this._manipulate([ 161 { rotate: 180 }, 162 { flip: ImageManipulator.FlipType.Vertical }, 163 { 164 crop: { 165 originX: this.state.image!.width! / 4, 166 originY: this.state.image!.height! / 4, 167 width: this.state.image!.width! / 2, 168 height: this.state.image!.width! / 2, 169 }, 170 }, 171 ]); 172 }; 173 174 _reset = () => { 175 this.setState(state => ({ image: state.original })); 176 }; 177 178 _manipulate = async ( 179 actions: ImageManipulator.Action[], 180 saveOptions?: ImageManipulator.SaveOptions 181 ) => { 182 const { image } = this.state; 183 const manipResult = await ImageManipulator.manipulateAsync( 184 (image! as Asset).localUri || image!.uri, 185 actions, 186 saveOptions 187 ); 188 this.setState({ image: manipResult }); 189 }; 190} 191 192const Button: React.FunctionComponent<TouchableOpacityProps> = ({ onPress, style, children }) => ( 193 <TouchableOpacity onPress={onPress} style={[styles.button, style]}> 194 <Text style={styles.buttonText}>{children}</Text> 195 </TouchableOpacity> 196); 197 198const styles = StyleSheet.create({ 199 container: { 200 flex: 1, 201 }, 202 imageContainer: { 203 marginVertical: 10, 204 alignItems: 'center', 205 justifyContent: 'center', 206 }, 207 image: { 208 width: 300, 209 height: 300, 210 resizeMode: 'contain', 211 }, 212 button: { 213 padding: 8, 214 borderRadius: 3, 215 backgroundColor: Colors.tintColor, 216 marginRight: 10, 217 marginBottom: 10, 218 }, 219 actionsButtons: { 220 flexDirection: 'row', 221 flexWrap: 'wrap', 222 }, 223 footerButtons: { 224 flexDirection: 'row', 225 flexWrap: 'wrap', 226 }, 227 buttonText: { 228 color: '#fff', 229 fontSize: 12, 230 }, 231}); 232