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