@oguzhnatly/react-native-image-manipulator 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.gitattributes +1 -0
  2. package/Example/.babelrc +3 -0
  3. package/Example/.buckconfig +6 -0
  4. package/Example/.flowconfig +70 -0
  5. package/Example/.gitattributes +1 -0
  6. package/Example/.watchmanconfig +1 -0
  7. package/Example/App.js +49 -0
  8. package/Example/android/app/BUCK +65 -0
  9. package/Example/android/app/build.gradle +150 -0
  10. package/Example/android/app/proguard-rules.pro +17 -0
  11. package/Example/android/app/src/main/AndroidManifest.xml +26 -0
  12. package/Example/android/app/src/main/java/com/example/MainActivity.java +15 -0
  13. package/Example/android/app/src/main/java/com/example/MainApplication.java +45 -0
  14. package/Example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  15. package/Example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  16. package/Example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  17. package/Example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  18. package/Example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  19. package/Example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  20. package/Example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  21. package/Example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  22. package/Example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  23. package/Example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  24. package/Example/android/app/src/main/res/values/strings.xml +3 -0
  25. package/Example/android/app/src/main/res/values/styles.xml +8 -0
  26. package/Example/android/build.gradle +39 -0
  27. package/Example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  28. package/Example/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  29. package/Example/android/gradle.properties +18 -0
  30. package/Example/android/gradlew +172 -0
  31. package/Example/android/gradlew.bat +84 -0
  32. package/Example/android/keystores/BUCK +8 -0
  33. package/Example/android/keystores/debug.keystore.properties +4 -0
  34. package/Example/android/settings.gradle +3 -0
  35. package/Example/app.json +4 -0
  36. package/Example/index.js +7 -0
  37. package/Example/ios/Example/AppDelegate.h +14 -0
  38. package/Example/ios/Example/AppDelegate.m +35 -0
  39. package/Example/ios/Example/Base.lproj/LaunchScreen.xib +42 -0
  40. package/Example/ios/Example/Images.xcassets/AppIcon.appiconset/Contents.json +38 -0
  41. package/Example/ios/Example/Images.xcassets/Contents.json +6 -0
  42. package/Example/ios/Example/Info.plist +60 -0
  43. package/Example/ios/Example/main.m +16 -0
  44. package/Example/ios/Example-tvOS/Info.plist +54 -0
  45. package/Example/ios/Example-tvOSTests/Info.plist +24 -0
  46. package/Example/ios/Example.xcodeproj/project.pbxproj +1494 -0
  47. package/Example/ios/Example.xcodeproj/xcshareddata/xcschemes/Example-tvOS.xcscheme +129 -0
  48. package/Example/ios/Example.xcodeproj/xcshareddata/xcschemes/Example.xcscheme +129 -0
  49. package/Example/ios/ExampleTests/ExampleTests.m +68 -0
  50. package/Example/ios/ExampleTests/Info.plist +24 -0
  51. package/Example/package.json +17 -0
  52. package/Example/yarn.lock +4046 -0
  53. package/LICENSE +21 -0
  54. package/README.md +127 -0
  55. package/android/build.gradle +36 -0
  56. package/android/src/main/AndroidManifest.xml +5 -0
  57. package/android/src/main/java/com/reactnativeimagemanipulator/RNImageManipulatorModule.java +208 -0
  58. package/android/src/main/java/com/reactnativeimagemanipulator/RNImageManipulatorPackage.java +29 -0
  59. package/index.js +5 -0
  60. package/ios/ImageUtils.h +9 -0
  61. package/ios/ImageUtils.m +14 -0
  62. package/ios/RNImageManipulator.h +11 -0
  63. package/ios/RNImageManipulator.m +200 -0
  64. package/ios/RNImageManipulator.xcodeproj/project.pbxproj +265 -0
  65. package/ios/RNImageManipulator.xcworkspace/contents.xcworkspacedata +9 -0
  66. package/package.json +9 -0
  67. package/react-native-image-manipulator.podspec +19 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Oğuzhan Atalay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ ### `ImageManipulator.manipulate(uri, actions, saveOptions)`
2
+
3
+ Manipulate the image provided via `uri`. Available modifications are rotating, flipping (mirroring), resizing and cropping. Each invocation results in a new file. With one invocation you can provide a set of actions to perform over the image. Overwriting the source file would not have an effect in displaying the result as images are cached.
4
+
5
+ #### Arguments
6
+
7
+ - **uri (_string_)** -- URI of the file to manipulate. Should be in the app's scope.
8
+ - **actions (_array_)** --
9
+
10
+ An array of objects representing manipulation options. Each object should have one of the following keys:
11
+
12
+ - **resize (_object_)** -- An object of shape `{ width, height }`. Values correspond to the result image dimensions. If you specify only one value, the other will be set automatically to preserve image ratio.
13
+ - **rotate (_number_)** -- Degrees to rotate the image. Rotation is clockwise when the value is positive and counter-clockwise when negative.
14
+ - **flip (_object_)** -- An object of shape `{ vertical, horizontal }`. Having a field set to true, flips the image in specified axis.
15
+ - **crop (_object_)** -- An object of shape `{ originX, originY, width, height }`. Fields specify top-left corner and dimensions of a crop rectangle.
16
+
17
+ - **saveOptions (_object_)** -- A map defining how modified image should be saved:
18
+ - **compress (_number_)** -- A value in range 0 - 1 specifying compression level of the result image. 1 means no compression and 0 the highest compression.
19
+ - **format (_string_)** -- Either `'jpeg'` or `'png'`. Specifies what type of compression should be used and what is the result file extension. PNG compression is lossless but slower, JPEG is faster but the image has visible artifacts. Defaults to `'jpeg'`.
20
+ - **base64 (_boolean_)** -- Whether to also include the image data in Base64 format.
21
+
22
+ #### Returns
23
+
24
+ Returns `{ uri, width, height }` where `uri` is a URI to the modified image (useable as the source for an `Image`/`Video` element), `width, height` specify the dimensions of the image. It can contain also `base64` - it is included if the `base64` saveOption was truthy, and is a string containing the JPEG/PNG (depending on `format`) data of the image in Base64--prepend that with `'data:image/xxx;base64,'` to get a data URI, which you can use as the source for an `Image` element for example (where `xxx` is 'jpeg' or 'png').
25
+
26
+ ### Basic Example
27
+
28
+ This will first rotate the image 90 degrees clockwise, then flip the rotated image vertically and save it as a PNG.
29
+
30
+ ```javascript
31
+ import React from "react";
32
+ import { Button, TouchableOpacity, Text, View, Image } from "react-native";
33
+ import ImageManipulator from "react-native-image-manipulator";
34
+
35
+ import Colors from "../constants/Colors";
36
+
37
+ export default class ImageManipulatorSample extends React.Component {
38
+ state = {
39
+ ready: false,
40
+ image: null
41
+ };
42
+
43
+ componentWillMount() {
44
+ (async () => {
45
+ const image = Asset.fromModule(require("../path/to/image.jpg"));
46
+ await image.downloadAsync();
47
+ this.setState({
48
+ ready: true,
49
+ image
50
+ });
51
+ })();
52
+ }
53
+
54
+ render() {
55
+ return (
56
+ <View style={{ flex: 1 }}>
57
+ <View style={{ padding: 10 }}>
58
+ <Button onPress={this._rotate90andFlip} />
59
+ {this.state.ready && this._renderImage()}
60
+ </View>
61
+ </View>
62
+ );
63
+ }
64
+
65
+ _rotate90andFlip = async () => {
66
+ const manipResult = await ImageManipulator.manipulate(
67
+ this.state.image.localUri || this.state.image.uri,
68
+ [{ rotate: 90 }, { flip: { vertical: true } }],
69
+ { format: "png" }
70
+ );
71
+ this.setState({ image: manipResult });
72
+ };
73
+
74
+ _renderImage = () => {
75
+ return (
76
+ <View
77
+ style={{
78
+ marginVertical: 10,
79
+ alignItems: "center",
80
+ justifyContent: "center"
81
+ }}
82
+ >
83
+ <Image
84
+ source={{ uri: this.state.image.localUri || this.state.image.uri }}
85
+ style={{ width: 300, height: 300, resizeMode: "contain" }}
86
+ />
87
+ </View>
88
+ );
89
+ };
90
+ }
91
+ ```
92
+
93
+ ## Getting started
94
+
95
+ `$ npm install @oguzhnatly/react-native-image-manipulator --save`
96
+ ### OR
97
+ `$ yarn add @oguzhnatly/react-native-image-manipulator`
98
+
99
+ ### Mostly automatic installation
100
+
101
+ `$ react-native link react-native-image-manipulator`
102
+
103
+ ### Manual installation
104
+
105
+ #### iOS
106
+
107
+ 1. In XCode, in the project navigator, right click `Libraries` ➜ `Add Files to [your project's name]`
108
+ 2. Go to `node_modules` ➜ `react-native-image-manipulator` and add `RNImageManipulator.xcodeproj`
109
+ 3. In XCode, in the project navigator, select your project. Add `libRNImageManipulator.a` to your project's `Build Phases` ➜ `Link Binary With Libraries`
110
+ 4. Run your project (`Cmd+R`)<
111
+
112
+ #### Android
113
+
114
+ 1. Open up `android/app/src/main/java/[...]/MainActivity.java`
115
+
116
+ - Add `import com.reactnativeimagemanipulator.RNImageManipulatorPackage;` to the imports at the top of the file
117
+ - Add `new RNImageManipulatorPackage()` to the list returned by the `getPackages()` method
118
+
119
+ 2. Append the following lines to `android/settings.gradle`:
120
+ ```
121
+ include ':react-native-image-manipulator'
122
+ project(':react-native-image-manipulator').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-image-manipulator/android')
123
+ ```
124
+ 3. Insert the following lines inside the dependencies block in `android/app/build.gradle`:
125
+ ```
126
+ compile project(':react-native-image-manipulator')
127
+ ```
@@ -0,0 +1,36 @@
1
+
2
+ buildscript {
3
+ repositories {
4
+ google()
5
+ jcenter()
6
+ }
7
+
8
+ dependencies {
9
+ classpath 'com.android.tools.build:gradle:3.3.1'
10
+ }
11
+ }
12
+
13
+ apply plugin: 'com.android.library'
14
+
15
+ android {
16
+ compileSdkVersion 28
17
+ buildToolsVersion "28.0.3"
18
+
19
+ defaultConfig {
20
+ minSdkVersion 16
21
+ targetSdkVersion 28
22
+ versionCode 1
23
+ versionName "1.0"
24
+ }
25
+ lintOptions {
26
+ abortOnError false
27
+ }
28
+ }
29
+
30
+ repositories {
31
+ mavenCentral()
32
+ }
33
+
34
+ dependencies {
35
+ implementation 'com.facebook.react:react-native:+'
36
+ }
@@ -0,0 +1,5 @@
1
+
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
+ package="com.reactnativeimagemanipulator">
4
+
5
+ </manifest>
@@ -0,0 +1,208 @@
1
+ package com.reactnativeimagemanipulator;
2
+
3
+ import android.graphics.Bitmap;
4
+ import android.graphics.Matrix;
5
+ import android.net.Uri;
6
+ import android.util.Base64;
7
+
8
+ import com.facebook.common.executors.CallerThreadExecutor;
9
+ import com.facebook.common.executors.UiThreadImmediateExecutorService;
10
+ import com.facebook.common.references.CloseableReference;
11
+ import com.facebook.datasource.DataSource;
12
+ import com.facebook.drawee.backends.pipeline.Fresco;
13
+ import com.facebook.imagepipeline.common.RotationOptions;
14
+ import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
15
+ import com.facebook.imagepipeline.image.CloseableImage;
16
+ import com.facebook.imagepipeline.request.ImageRequest;
17
+ import com.facebook.imagepipeline.request.ImageRequestBuilder;
18
+ import com.facebook.react.bridge.Arguments;
19
+ import com.facebook.react.bridge.Promise;
20
+ import com.facebook.react.bridge.ReactApplicationContext;
21
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
22
+ import com.facebook.react.bridge.ReactMethod;
23
+ import com.facebook.react.bridge.ReadableArray;
24
+ import com.facebook.react.bridge.ReadableMap;
25
+ import com.facebook.react.bridge.WritableMap;
26
+
27
+ import java.io.ByteArrayOutputStream;
28
+ import java.io.File;
29
+ import java.io.FileOutputStream;
30
+ import java.io.IOException;
31
+ import java.util.UUID;
32
+
33
+ public class RNImageManipulatorModule extends ReactContextBaseJavaModule {
34
+ private static final String DECODE_ERROR_TAG = "E_DECODE_ERR";
35
+ private static final String ARGS_ERROR_TAG = "E_ARGS_ERR";
36
+
37
+ public RNImageManipulatorModule(ReactApplicationContext reactContext) {
38
+ super(reactContext);
39
+ }
40
+
41
+ @Override
42
+ public String getName() {
43
+ return "RNImageManipulator";
44
+ }
45
+
46
+ @ReactMethod
47
+ public void manipulate(final String uriString, final ReadableArray actions, final ReadableMap saveOptions, final Promise promise) {
48
+ if (uriString == null || uriString.length() == 0) {
49
+ promise.reject(ARGS_ERROR_TAG, "Uri passed to ImageManipulator cannot be empty!");
50
+ return;
51
+ }
52
+ ImageRequest imageRequest =
53
+ ImageRequestBuilder
54
+ .newBuilderWithSource(Uri.parse(uriString))
55
+ .setRotationOptions(RotationOptions.autoRotate())
56
+ .build();
57
+ final DataSource<CloseableReference<CloseableImage>> dataSource
58
+ = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, getReactApplicationContext());
59
+ dataSource.subscribe(new BaseBitmapDataSubscriber() {
60
+ @Override
61
+ public void onNewResultImpl(Bitmap bitmap) {
62
+ if (bitmap != null) {
63
+ processBitmapWithActions(bitmap, actions, saveOptions, promise);
64
+ } else {
65
+ onFailureImpl(dataSource);
66
+ }
67
+ }
68
+
69
+ @Override
70
+ public void onFailureImpl(DataSource dataSource) {
71
+ // No cleanup required here.
72
+ String basicMessage = "Could not get decoded bitmap of " + uriString;
73
+ if (dataSource.getFailureCause() != null) {
74
+ promise.reject(DECODE_ERROR_TAG,
75
+ basicMessage + ": " + dataSource.getFailureCause().toString(), dataSource.getFailureCause());
76
+ } else {
77
+ promise.reject(DECODE_ERROR_TAG, basicMessage + ".");
78
+ }
79
+ }
80
+ },
81
+ CallerThreadExecutor.getInstance()
82
+ );
83
+ }
84
+
85
+ private void processBitmapWithActions(Bitmap bmp, ReadableArray actions, ReadableMap saveOptions, Promise promise) {
86
+ int imageWidth, imageHeight;
87
+
88
+ for (int idx = 0; idx < actions.size(); idx ++) {
89
+ ReadableMap options = actions.getMap(idx);
90
+
91
+ imageWidth = bmp.getWidth();
92
+ imageHeight = bmp.getHeight();
93
+
94
+ if (options.hasKey("resize")) {
95
+ ReadableMap resize = options.getMap("resize");
96
+ int requestedWidth = 0;
97
+ int requestedHeight = 0;
98
+ float imageRatio = 1.0f * imageWidth / imageHeight;
99
+
100
+ if (resize.hasKey("width")) {
101
+ requestedWidth = (int) resize.getDouble("width");
102
+ requestedHeight = (int) (requestedWidth / imageRatio);
103
+ }
104
+ if (resize.hasKey("height")) {
105
+ requestedHeight = (int) resize.getDouble("height");
106
+ requestedWidth = requestedWidth == 0 ? (int) (imageRatio * requestedHeight) : requestedWidth;
107
+ }
108
+
109
+ bmp = Bitmap.createScaledBitmap(bmp, requestedWidth, requestedHeight, true);
110
+ } else if (options.hasKey("rotate")) {
111
+ int requestedRotation = options.getInt("rotate");
112
+ Matrix rotationMatrix = new Matrix();
113
+ rotationMatrix.postRotate(requestedRotation);
114
+ bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), rotationMatrix, true);
115
+ } else if (options.hasKey("flip")) {
116
+ Matrix rotationMatrix = new Matrix();
117
+ ReadableMap flip = options.getMap("flip");
118
+ if (flip.hasKey("horizontal") && flip.getBoolean("horizontal")) {
119
+ rotationMatrix.postScale(-1, 1);
120
+ }
121
+ if (flip.hasKey("vertical") && flip.getBoolean("vertical")) {
122
+ rotationMatrix.postScale(1, -1);
123
+ }
124
+ bmp = Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(), bmp.getHeight(), rotationMatrix, true);
125
+ } else if (options.hasKey("crop")) {
126
+ ReadableMap crop = options.getMap("crop");
127
+ if (!crop.hasKey("originX") || !crop.hasKey("originY") || !crop.hasKey("width") || !crop.hasKey("height")) {
128
+ promise.reject("E_INVALID_CROP_DATA", "Invalid crop options has been passed. Please make sure the object contains originX, originY, width and height.");
129
+ return;
130
+ }
131
+ int originX, originY, requestedWidth, requestedHeight;
132
+ originX = (int) crop.getDouble("originX");
133
+ originY = (int) crop.getDouble("originY");
134
+ requestedWidth = (int) crop.getDouble("width");
135
+ requestedHeight = (int) crop.getDouble("height");
136
+ if (originX > imageWidth || originY > imageHeight || requestedWidth > bmp.getWidth() || requestedHeight > bmp.getHeight()) {
137
+ promise.reject("E_INVALID_CROP_DATA", "Invalid crop options has been passed. Please make sure the requested crop rectangle is inside source image.");
138
+ return;
139
+ }
140
+ bmp = Bitmap.createBitmap(bmp, originX, originY, requestedWidth, requestedHeight);
141
+ }
142
+ }
143
+
144
+ int compressionQuality = 100;
145
+ if (saveOptions.hasKey("compress")) {
146
+ compressionQuality = (int) (100 * saveOptions.getDouble("compress"));
147
+ }
148
+ String format, extension;
149
+ Bitmap.CompressFormat compressFormat;
150
+
151
+ if (saveOptions.hasKey("format")) {
152
+ format = saveOptions.getString("format");
153
+ } else {
154
+ format = "jpeg";
155
+ }
156
+
157
+ if (format.equals("png")) {
158
+ compressFormat = Bitmap.CompressFormat.PNG;
159
+ extension = ".png";
160
+ } else if (format.equals("jpeg")) {
161
+ compressFormat = Bitmap.CompressFormat.JPEG;
162
+ extension = ".jpg";
163
+ } else {
164
+ compressFormat = Bitmap.CompressFormat.JPEG;
165
+ extension = ".jpg";
166
+ }
167
+
168
+ boolean base64 = saveOptions.hasKey("base64") && saveOptions.getBoolean("base64");
169
+
170
+ FileOutputStream out = null;
171
+ ByteArrayOutputStream byteOut = null;
172
+ String path = null;
173
+ String base64String = null;
174
+ try {
175
+ path = this.getReactApplicationContext().getFilesDir() + "/" + UUID.randomUUID() + extension;
176
+ out = new FileOutputStream(path);
177
+ bmp.compress(compressFormat, compressionQuality, out);
178
+
179
+ if (base64) {
180
+ byteOut = new ByteArrayOutputStream();
181
+ bmp.compress(compressFormat, compressionQuality, byteOut);
182
+ base64String = Base64.encodeToString(byteOut.toByteArray(), Base64.DEFAULT);
183
+ }
184
+ } catch (Exception e) {
185
+ e.printStackTrace();
186
+ } finally {
187
+ try {
188
+ if (out != null) {
189
+ out.close();
190
+ }
191
+ if (byteOut != null) {
192
+ byteOut.close();
193
+ }
194
+ } catch (IOException e) {
195
+ e.printStackTrace();
196
+ }
197
+ }
198
+
199
+ WritableMap response = Arguments.createMap();
200
+ response.putString("uri", Uri.fromFile(new File(path)).toString());
201
+ response.putInt("width", bmp.getWidth());
202
+ response.putInt("height", bmp.getHeight());
203
+ if (base64) {
204
+ response.putString("base64", base64String);
205
+ }
206
+ promise.resolve(response);
207
+ }
208
+ }
@@ -0,0 +1,29 @@
1
+
2
+ package com.reactnativeimagemanipulator;
3
+
4
+ import java.util.Arrays;
5
+ import java.util.Collections;
6
+ import java.util.List;
7
+
8
+ import com.facebook.react.ReactPackage;
9
+ import com.facebook.react.bridge.NativeModule;
10
+ import com.facebook.react.bridge.ReactApplicationContext;
11
+ import com.facebook.react.uimanager.ViewManager;
12
+ import com.facebook.react.bridge.JavaScriptModule;
13
+
14
+ public class RNImageManipulatorPackage implements ReactPackage {
15
+ @Override
16
+ public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
17
+ return Arrays.<NativeModule>asList(new RNImageManipulatorModule(reactContext));
18
+ }
19
+
20
+ // Deprecated from RN 0.47
21
+ public List<Class<? extends JavaScriptModule>> createJSModules() {
22
+ return Collections.emptyList();
23
+ }
24
+
25
+ @Override
26
+ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
27
+ return Collections.emptyList();
28
+ }
29
+ }
package/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { NativeModules } from "react-native";
2
+
3
+ const { RNImageManipulator } = NativeModules;
4
+
5
+ export default RNImageManipulator;
@@ -0,0 +1,9 @@
1
+ #import <UIKit/UIKit.h>
2
+ #import <CoreMedia/CoreMedia.h>
3
+ #import <Foundation/Foundation.h>
4
+
5
+ @interface ImageUtils : NSObject
6
+
7
+ + (UIImage *)cropImage:(UIImage *)image toRect:(CGRect)rect;
8
+
9
+ @end
@@ -0,0 +1,14 @@
1
+ #import "ImageUtils.h"
2
+
3
+ @implementation ImageUtils
4
+
5
+ + (UIImage *)cropImage:(UIImage *)image toRect:(CGRect)rect
6
+ {
7
+ CGImageRef takenCGImage = image.CGImage;
8
+ CGImageRef cropCGImage = CGImageCreateWithImageInRect(takenCGImage, rect);
9
+ image = [UIImage imageWithCGImage:cropCGImage scale:image.scale orientation:image.imageOrientation];
10
+ CGImageRelease(cropCGImage);
11
+ return image;
12
+ }
13
+
14
+ @end
@@ -0,0 +1,11 @@
1
+
2
+ #if __has_include("RCTBridgeModule.h")
3
+ #import "RCTBridgeModule.h"
4
+ #else
5
+ #import <React/RCTBridgeModule.h>
6
+ #endif
7
+
8
+ @interface RNImageManipulator : NSObject <RCTBridgeModule>
9
+
10
+ @end
11
+
@@ -0,0 +1,200 @@
1
+ #import "RNImageManipulator.h"
2
+ #import "ImageUtils.h"
3
+ #import <React/RCTLog.h>
4
+ #import <Photos/Photos.h>
5
+
6
+ @implementation RNImageManipulator
7
+
8
+ RCT_EXPORT_MODULE(RNImageManipulator);
9
+
10
+ @synthesize bridge = _bridge;
11
+
12
+ - (void)setBridge:(RCTBridge *)bridge
13
+ {
14
+ _bridge = bridge;
15
+ }
16
+
17
+ - (dispatch_queue_t)methodQueue
18
+ {
19
+ return dispatch_get_main_queue();
20
+ }
21
+
22
+ RCT_EXPORT_METHOD(manipulate:(NSString *)uri
23
+ actions:(NSArray *)actions
24
+ saveOptions:(NSDictionary *)saveOptions
25
+ resolver:(RCTPromiseResolveBlock)resolve
26
+ rejecter:(RCTPromiseRejectBlock)reject)
27
+ {
28
+ NSURL *url = [NSURL URLWithString:uri];
29
+ NSString *path = [url.path stringByStandardizingPath];
30
+
31
+ if ([[url scheme] isEqualToString:@"assets-library"]) {
32
+ PHFetchResult<PHAsset *> *fetchResult = [PHAsset fetchAssetsWithALAssetURLs:@[url] options:nil];
33
+ if (fetchResult.count > 0) {
34
+ PHAsset *asset = fetchResult[0];
35
+ CGSize size = CGSizeMake([asset pixelWidth], [asset pixelHeight]);
36
+ PHImageRequestOptions *options = [PHImageRequestOptions new];
37
+ [options setResizeMode:PHImageRequestOptionsResizeModeExact];
38
+ [options setNetworkAccessAllowed:YES];
39
+ [options setSynchronous:NO];
40
+ [options setDeliveryMode:PHImageRequestOptionsDeliveryModeHighQualityFormat];
41
+
42
+ [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:size contentMode:PHImageContentModeAspectFit options:options resultHandler:^(UIImage * _Nullable image, NSDictionary * _Nullable info) {
43
+ if (!image) {
44
+ reject(@"E_IMAGE_MANIPULATION_FAILED", [NSString stringWithFormat:@"The file isn't convertable to image. Given path: `%@`.", path], nil);
45
+ return;
46
+ }
47
+ [self manipulateImage:image actions:actions saveOptions:saveOptions resolver:resolve rejecter:reject];
48
+ }];
49
+ return;
50
+ } else {
51
+ reject(@"E_IMAGE_MANIPULATION_FAILED", [NSString stringWithFormat:@"The file does not exist. Given path: `%@`.", path], nil);
52
+ return;
53
+ }
54
+ } else {
55
+ if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
56
+ reject(@"E_IMAGE_MANIPULATION_FAILED", [NSString stringWithFormat:@"The file does not exist. Given path: `%@`.", path], nil);
57
+ return;
58
+ }
59
+
60
+ UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
61
+ if (image == nil) {
62
+ reject(@"E_CANNOT_OPEN", @"Could not open provided image", nil);
63
+ return;
64
+ }
65
+
66
+ [self manipulateImage:image actions:actions saveOptions:saveOptions resolver:resolve rejecter:reject];
67
+ return;
68
+ }
69
+ }
70
+
71
+ -(void)manipulateImage:(UIImage *)image
72
+ actions:(NSArray *)actions
73
+ saveOptions:(NSDictionary *)saveOptions
74
+ resolver:(RCTPromiseResolveBlock)resolve
75
+ rejecter:(RCTPromiseRejectBlock)reject
76
+ {
77
+ for (NSDictionary *options in actions) {
78
+ if (options[@"resize"]) {
79
+ float imageWidth = image.size.width;
80
+ float imageHeight = image.size.height;
81
+ float imageRatio = imageWidth / imageHeight;
82
+
83
+ NSInteger requestedWidth = 0;
84
+ NSInteger requestedHeight = 0;
85
+ NSDictionary *resize = options[@"resize"];
86
+ if (resize[@"width"]) {
87
+ requestedWidth = [(NSNumber *)resize[@"width"] integerValue];
88
+ requestedHeight = requestedWidth/imageRatio;
89
+ }
90
+ if (resize[@"height"]) {
91
+ requestedHeight = [(NSNumber *)resize[@"height"] integerValue];
92
+ requestedWidth = requestedWidth == 0 ? imageRatio * requestedHeight : requestedWidth;
93
+ }
94
+
95
+ CGSize requestedSize = CGSizeMake(requestedWidth, requestedHeight);
96
+ UIGraphicsBeginImageContextWithOptions(requestedSize, NO, 1.0);
97
+ [image drawInRect:CGRectMake(0, 0, requestedWidth, requestedHeight)];
98
+ image = UIGraphicsGetImageFromCurrentImageContext();
99
+ UIGraphicsEndImageContext();
100
+ } else if (options[@"rotate"]) {
101
+ float rads = [(NSNumber *)options[@"rotate"] integerValue] * M_PI/180;
102
+ CGSize size = image.size;
103
+ UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,size.width, size.height)];
104
+ CGAffineTransform t = CGAffineTransformMakeRotation(rads);
105
+ rotatedViewBox.transform = t;
106
+ CGSize rotatedSize = rotatedViewBox.frame.size;
107
+
108
+ UIGraphicsBeginImageContext(rotatedSize);
109
+ CGContextRef bitmap = UIGraphicsGetCurrentContext();
110
+ CGContextTranslateCTM(bitmap, rotatedSize.width/2, rotatedSize.height/2);
111
+ CGContextRotateCTM(bitmap, rads);
112
+ CGContextScaleCTM(bitmap, 1.0, -1.0);
113
+ CGContextDrawImage(bitmap, CGRectMake(-size.width / 2, -size.height / 2, size.width, size.height), image.CGImage);
114
+
115
+ image = UIGraphicsGetImageFromCurrentImageContext();
116
+ UIGraphicsEndImageContext();
117
+ } else if (options[@"flip"]) {
118
+ NSDictionary *flip = options[@"flip"];
119
+ UIImageView *tempImageView = [[UIImageView alloc] initWithImage:image];
120
+
121
+ UIGraphicsBeginImageContext(tempImageView.frame.size);
122
+ CGContextRef context = UIGraphicsGetCurrentContext();
123
+ CGAffineTransform transform;
124
+ if (flip[@"vertical"]) {
125
+ transform = CGAffineTransformMake(1, 0, 0, -1, 0, tempImageView.frame.size.height);
126
+ CGContextConcatCTM(context, transform);
127
+ } else if (flip[@"horizontal"]) {
128
+ transform = CGAffineTransformMake(-1, 0, 0, 1, tempImageView.frame.size.width, 0);
129
+ CGContextConcatCTM(context, transform);
130
+ }
131
+
132
+ [tempImageView.layer renderInContext:context];
133
+ image = UIGraphicsGetImageFromCurrentImageContext();
134
+ UIGraphicsEndImageContext();
135
+ } else if (options[@"crop"]) {
136
+ NSDictionary *cropData = options[@"crop"];
137
+ if (cropData[@"originX"] == nil || cropData[@"originY"] == nil || cropData[@"width"] == nil || cropData[@"height"]== nil) {
138
+ reject(@"E_INVALID_CROP_DATA", @"Invalid crop options has been passed. Please make sure the object contains originX, originY, width and height.", nil);
139
+ return;
140
+ }
141
+
142
+ float originX = [(NSNumber *)cropData[@"originX"] floatValue];
143
+ float originY = [(NSNumber *)cropData[@"originY"] floatValue];
144
+ float requestedWidth = [(NSNumber *)cropData[@"width"] floatValue];
145
+ float requestedHeight = [(NSNumber *)cropData[@"height"] floatValue];
146
+
147
+ if (originX > image.size.width || originY > image.size.height || requestedWidth > image.size.width || requestedHeight > image.size.height) {
148
+ reject(@"E_INVALID_CROP_DATA", @"Invalid crop options has been passed. Please make sure the requested crop rectangle is inside source image.", nil);
149
+ return;
150
+ }
151
+ CGRect cropDimensions = CGRectMake(originX, originY, requestedWidth, requestedHeight);
152
+ image = [ImageUtils cropImage:image toRect:cropDimensions];
153
+ }
154
+ }
155
+
156
+ float compressionValue = 1.0;
157
+ if (saveOptions[@"compress"] != nil) {
158
+ compressionValue = [(NSNumber *)saveOptions[@"compress"] floatValue];
159
+ }
160
+
161
+ NSData *imageData = nil;
162
+ NSString *format = saveOptions[@"format"];
163
+ NSString *extension;
164
+ if (format == nil) {
165
+ format = @"jpeg";
166
+ }
167
+ if ([format isEqualToString:@"jpeg"]) {
168
+ imageData = UIImageJPEGRepresentation(image, compressionValue);
169
+ extension = @".jpg";
170
+ } else if ([format isEqualToString:@"png"]) {
171
+ imageData = UIImagePNGRepresentation(image);
172
+ extension = @".png";
173
+ } else {
174
+ RCTLogWarn(@"Unsupported format: %@, using JPEG instead.", format);
175
+ imageData = UIImageJPEGRepresentation(image, compressionValue);
176
+ extension = @".jpg";
177
+ }
178
+
179
+
180
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
181
+ NSString *directory = directory = [paths firstObject];
182
+ NSString *fileName = [[[NSUUID UUID] UUIDString] stringByAppendingString:extension];
183
+ NSString *newPath = [directory stringByAppendingPathComponent:fileName];
184
+
185
+ [imageData writeToFile:newPath atomically:YES];
186
+ NSURL *fileURL = [NSURL fileURLWithPath:newPath];
187
+ NSString *filePath = [fileURL absoluteString];
188
+ NSMutableDictionary *response = [[NSMutableDictionary alloc] init];
189
+ response[@"uri"] = filePath;
190
+ response[@"filename"] = fileName;
191
+ response[@"width"] = @(CGImageGetWidth(image.CGImage));
192
+ response[@"height"] = @(CGImageGetHeight(image.CGImage));
193
+ if (saveOptions[@"base64"] && [saveOptions[@"base64"] boolValue]) {
194
+ response[@"base64"] = [imageData base64EncodedStringWithOptions:0];
195
+ }
196
+
197
+ resolve(response);
198
+ }
199
+
200
+ @end