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