1import Ionicons from '@expo/vector-icons/build/Ionicons'; 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 | ImagePicker.ImagePickerAsset; 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 const height = 103 this.state.image?.height && this.state.image?.height < 300 ? this.state.image?.height : 300; 104 const width = 105 this.state.image?.width && this.state.image?.width < 300 ? this.state.image?.width : 300; 106 107 return ( 108 <View style={styles.imageContainer}> 109 <Image 110 source={{ uri: (this.state.image! as Asset).localUri || this.state.image!.uri }} 111 style={[styles.image, { height, width }]} 112 /> 113 </View> 114 ); 115 }; 116 117 _pickPhoto = async () => { 118 const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync(); 119 if (status !== 'granted') { 120 alert('Permission to MEDIA_LIBRARY not granted!'); 121 return; 122 } 123 const result = await ImagePicker.launchImageLibraryAsync({ 124 allowsEditing: false, 125 }); 126 if (result.canceled) { 127 alert('No image selected!'); 128 return; 129 } 130 this.setState({ image: result.assets[0] }); 131 }; 132 133 _rotate = async (deg: number) => { 134 await this._manipulate([{ rotate: deg }], { 135 format: ImageManipulator.SaveFormat.PNG, 136 }); 137 }; 138 139 _resize = async (size: { width?: number; height?: number }) => { 140 await this._manipulate([{ resize: size }]); 141 }; 142 143 _flip = async (flip: ImageManipulator.FlipType) => { 144 await this._manipulate([{ flip }]); 145 }; 146 147 _compress = async (compress: number) => { 148 await this._manipulate([], { compress }); 149 }; 150 151 _crop = async () => { 152 await this._manipulate([ 153 { 154 crop: { 155 originX: 0, 156 originY: 0, 157 width: this.state.image!.width! / 2, 158 height: this.state.image!.height!, 159 }, 160 }, 161 ]); 162 }; 163 164 _combo = async () => { 165 await this._manipulate([ 166 { rotate: 180 }, 167 { flip: ImageManipulator.FlipType.Vertical }, 168 { 169 crop: { 170 originX: this.state.image!.width! / 4, 171 originY: this.state.image!.height! / 4, 172 width: this.state.image!.width! / 2, 173 height: this.state.image!.width! / 2, 174 }, 175 }, 176 ]); 177 }; 178 179 _reset = () => { 180 this.setState((state) => ({ image: state.original })); 181 }; 182 183 _manipulate = async ( 184 actions: ImageManipulator.Action[], 185 saveOptions?: ImageManipulator.SaveOptions 186 ) => { 187 const { image } = this.state; 188 const manipResult = await ImageManipulator.manipulateAsync( 189 (image! as Asset).localUri || image!.uri, 190 actions, 191 saveOptions 192 ); 193 this.setState({ image: manipResult }); 194 }; 195} 196 197const Button: React.FunctionComponent<TouchableOpacityProps> = ({ onPress, style, children }) => ( 198 <TouchableOpacity onPress={onPress} style={[styles.button, style]}> 199 <Text style={styles.buttonText}>{children}</Text> 200 </TouchableOpacity> 201); 202 203const styles = StyleSheet.create({ 204 container: { 205 flex: 1, 206 }, 207 imageContainer: { 208 marginVertical: 10, 209 alignItems: 'center', 210 justifyContent: 'center', 211 }, 212 image: { 213 resizeMode: 'contain', 214 }, 215 button: { 216 padding: 8, 217 borderRadius: 3, 218 backgroundColor: Colors.tintColor, 219 marginRight: 10, 220 marginBottom: 10, 221 }, 222 actionsButtons: { 223 flexDirection: 'row', 224 flexWrap: 'wrap', 225 }, 226 footerButtons: { 227 flexDirection: 'row', 228 flexWrap: 'wrap', 229 }, 230 buttonText: { 231 color: '#fff', 232 fontSize: 12, 233 }, 234}); 235