@sdcx/image-crop 0.2.0 → 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.
- package/LICENSE +1 -1
- package/README.md +27 -14
- package/RNImageCrop.podspec +3 -3
- package/android/build.gradle +48 -26
- package/android/src/main/java/com/reactnative/imagecrop/ImageCropPackage.java +1 -1
- package/android/src/main/java/com/reactnative/imagecrop/{RNImageCropView.java → ImageCropView.java} +12 -12
- package/android/src/main/java/com/reactnative/imagecrop/ImageCropViewManager.java +76 -0
- package/android/src/main/java/com/reactnative/imagecrop/OnCropEvent.java +34 -0
- package/dist/ImageCropNativeComponent.d.ts +22 -0
- package/dist/ImageCropNativeComponent.js +5 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +14 -0
- package/ios/ImageCrop/RNImageCropView.h +10 -0
- package/ios/ImageCrop/RNImageCropView.mm +186 -0
- package/package.json +50 -64
- package/src/ImageCropNativeComponent.ts +34 -0
- package/src/index.tsx +27 -0
- package/android/src/main/java/com/reactnative/imagecrop/RNImageCropViewManager.java +0 -77
- package/ios/ImageCrop/RNImageCrop.h +0 -30
- package/ios/ImageCrop/RNImageCrop.m +0 -129
- package/ios/ImageCrop/RNImageCropManager.h +0 -18
- package/ios/ImageCrop/RNImageCropManager.m +0 -37
- package/lib/ImageCropView.d.ts +0 -15
- package/lib/ImageCropView.js +0 -24
- package/lib/ImageCropViewRef.d.ts +0 -3
- package/lib/ImageCropViewRef.js +0 -1
- package/lib/index.d.ts +0 -5
- package/lib/index.js +0 -2
- package/lib/typings.d.ts +0 -6
- package/lib/typings.js +0 -1
- package/src/ImageCropView.tsx +0 -69
- package/src/ImageCropViewRef.ts +0 -3
- package/src/index.ts +0 -6
- package/src/typings.ts +0 -6
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -2,40 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
`ImageCropView` 是一个 React Native 原生 UI 组件,用于头像裁剪以及图片裁剪,同时支持图像主体识别后设置需要裁剪的主体区域。
|
|
4
4
|
|
|
5
|
+
## 版本兼容
|
|
6
|
+
|
|
7
|
+
| 版本 | RN 版本 | RN 架构 |
|
|
8
|
+
| ---- | ------- | ------- |
|
|
9
|
+
| 0.x | < 0.82 | 旧架构 |
|
|
10
|
+
| 1.x | >= 0.82 | 新架构 |
|
|
11
|
+
|
|
5
12
|
## Installation
|
|
6
13
|
|
|
7
14
|
```bash
|
|
8
15
|
yarn add @sdcx/image-crop
|
|
9
16
|
# &
|
|
10
|
-
pod install
|
|
17
|
+
cd ios && bundle exec pod install
|
|
11
18
|
```
|
|
12
19
|
|
|
13
20
|
## Usage
|
|
14
21
|
|
|
22
|
+
```tsx
|
|
23
|
+
<ImageCropView
|
|
24
|
+
ref={imageCropViewRef}
|
|
25
|
+
style={'your style'}
|
|
26
|
+
fileUri={'your file uri'}
|
|
27
|
+
cropStyle={'circular' | 'default'}
|
|
28
|
+
onCrop={(uri: string) => {}}
|
|
29
|
+
objectRect={objectRect}
|
|
30
|
+
/>
|
|
15
31
|
```
|
|
16
|
-
|
|
17
|
-
ref={imageCropViewRef}
|
|
18
|
-
style={'your style'}
|
|
19
|
-
fileUri={'your file uri'}
|
|
20
|
-
cropStyle={'circular' | 'default'}
|
|
21
|
-
onCropped={(uri: string) => {}}
|
|
22
|
-
objectRect={objectRect}
|
|
23
|
-
/>
|
|
24
|
-
```
|
|
32
|
+
|
|
25
33
|
#### cropStyle
|
|
26
|
-
|
|
34
|
+
|
|
35
|
+
默认 default 为裁剪矩形,若需要裁剪圆形头像,则设为 circular;
|
|
27
36
|
|
|
28
37
|
#### objectRect
|
|
29
|
-
objectRect可设置默认的图片裁剪区域,且当cropStyle为default时有效。
|
|
30
38
|
|
|
31
|
-
objectRect
|
|
39
|
+
objectRect 可设置默认的图片裁剪区域,且当 cropStyle 为 default 时有效。
|
|
40
|
+
|
|
41
|
+
objectRect 的四个属性分别是(单位都是像素 px):
|
|
42
|
+
|
|
32
43
|
1. left: 裁剪区域离图片左边的距离;
|
|
33
44
|
2. top: 裁剪区域离图片上边的距离;
|
|
34
45
|
3. width: 裁剪区域的宽度;
|
|
35
46
|
4. height: 裁剪区域的高度;
|
|
36
47
|
|
|
37
48
|
#### 裁剪
|
|
49
|
+
|
|
38
50
|
```
|
|
39
51
|
imageCropViewRef.crop()
|
|
40
52
|
```
|
|
41
|
-
|
|
53
|
+
|
|
54
|
+
然后裁剪成功后会在 onCropped 属性中回调裁剪后图片的 uri;
|
package/RNImageCrop.podspec
CHANGED
|
@@ -6,14 +6,14 @@ Pod::Spec.new do |s|
|
|
|
6
6
|
s.name = "RNImageCrop"
|
|
7
7
|
s.version = package["version"]
|
|
8
8
|
s.summary = package["description"]
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
s.homepage = package["homepage"]
|
|
11
11
|
s.license = package["license"]
|
|
12
12
|
s.authors = package["author"]
|
|
13
|
-
s.platforms = { :ios =>
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
14
|
s.source = { :git => "https://github.com/github-account/image-crop.git", :tag => "#{s.version}" }
|
|
15
15
|
|
|
16
16
|
s.source_files = "ios/ImageCrop/**/*.{h,m,mm}"
|
|
17
|
-
s.dependency "React-Core"
|
|
18
17
|
s.dependency "TOCropViewController"
|
|
18
|
+
install_modules_dependencies(s)
|
|
19
19
|
end
|
package/android/build.gradle
CHANGED
|
@@ -1,36 +1,58 @@
|
|
|
1
|
-
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
mavenCentral()
|
|
4
|
+
google()
|
|
5
|
+
}
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
9
|
+
}
|
|
5
10
|
}
|
|
6
11
|
|
|
7
12
|
apply plugin: 'com.android.library'
|
|
13
|
+
apply plugin: 'com.facebook.react'
|
|
14
|
+
|
|
15
|
+
def safeExtGet(prop, fallback) {
|
|
16
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
17
|
+
}
|
|
8
18
|
|
|
9
19
|
android {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
|
|
21
|
+
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
|
|
22
|
+
// Check AGP version for backward compatibility w/react-native versions still on gradle plugin 6
|
|
23
|
+
def major = agpVersion[0].toInteger()
|
|
24
|
+
def minor = agpVersion[1].toInteger()
|
|
25
|
+
if ((major == 7 && minor >= 3) || major >= 8) {
|
|
26
|
+
namespace "com.reactnative.imagecrop"
|
|
27
|
+
buildFeatures {
|
|
28
|
+
buildConfig true
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 35)
|
|
33
|
+
buildToolsVersion safeExtGet('buildToolsVersion', '35.0.0')
|
|
34
|
+
|
|
35
|
+
defaultConfig {
|
|
36
|
+
minSdkVersion safeExtGet('minSdkVersion', 24)
|
|
37
|
+
targetSdkVersion safeExtGet('targetSdkVersion', 35)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
sourceSets {
|
|
41
|
+
main {
|
|
42
|
+
java.srcDirs += [
|
|
43
|
+
"generated/java",
|
|
44
|
+
"generated/jni"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
repositories {
|
|
51
|
+
google()
|
|
52
|
+
mavenCentral()
|
|
30
53
|
}
|
|
31
54
|
|
|
32
55
|
dependencies {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
implementation 'com.github.yalantis:ucrop:2.2.6'
|
|
56
|
+
implementation 'com.github.yalantis:ucrop:2.2.6'
|
|
57
|
+
api 'com.facebook.react:react-native:+'
|
|
36
58
|
}
|
|
@@ -21,6 +21,6 @@ public class ImageCropPackage implements ReactPackage {
|
|
|
21
21
|
@NonNull
|
|
22
22
|
@Override
|
|
23
23
|
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
|
|
24
|
-
return Arrays.asList(new
|
|
24
|
+
return Arrays.asList(new ImageCropViewManager());
|
|
25
25
|
}
|
|
26
26
|
}
|
package/android/src/main/java/com/reactnative/imagecrop/{RNImageCropView.java → ImageCropView.java}
RENAMED
|
@@ -13,11 +13,10 @@ import android.widget.FrameLayout;
|
|
|
13
13
|
import androidx.annotation.NonNull;
|
|
14
14
|
|
|
15
15
|
import com.facebook.common.logging.FLog;
|
|
16
|
-
import com.facebook.react.bridge.Arguments;
|
|
17
16
|
import com.facebook.react.bridge.ReactContext;
|
|
18
17
|
import com.facebook.react.bridge.ReadableMap;
|
|
19
|
-
import com.facebook.react.
|
|
20
|
-
import com.facebook.react.uimanager.events.
|
|
18
|
+
import com.facebook.react.uimanager.UIManagerHelper;
|
|
19
|
+
import com.facebook.react.uimanager.events.EventDispatcher;
|
|
21
20
|
import com.yalantis.ucrop.callback.BitmapCropCallback;
|
|
22
21
|
import com.yalantis.ucrop.callback.OverlayViewChangeListener;
|
|
23
22
|
import com.yalantis.ucrop.view.GestureCropImageView;
|
|
@@ -29,7 +28,7 @@ import java.lang.reflect.Field;
|
|
|
29
28
|
import java.lang.reflect.Method;
|
|
30
29
|
import java.util.UUID;
|
|
31
30
|
|
|
32
|
-
public class
|
|
31
|
+
public class ImageCropView extends FrameLayout {
|
|
33
32
|
private static final int DEFAULT_COMPRESS_QUALITY = 90;
|
|
34
33
|
private static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.PNG;
|
|
35
34
|
|
|
@@ -148,7 +147,7 @@ public class RNImageCropView extends FrameLayout {
|
|
|
148
147
|
private GestureCropImageView mGestureCropImageView;
|
|
149
148
|
private OverlayView mOverlayView;
|
|
150
149
|
|
|
151
|
-
public
|
|
150
|
+
public ImageCropView(Context context) {
|
|
152
151
|
super(context);
|
|
153
152
|
init(context);
|
|
154
153
|
}
|
|
@@ -179,13 +178,14 @@ public class RNImageCropView extends FrameLayout {
|
|
|
179
178
|
}
|
|
180
179
|
|
|
181
180
|
private void onCropped(String uri) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
181
|
+
FLog.i(TAG, "onCropped:" +uri);
|
|
182
|
+
int surfaceId = UIManagerHelper.getSurfaceId(getContext());
|
|
183
|
+
int viewId = getId();
|
|
184
|
+
EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag((ReactContext) getContext(), viewId);
|
|
185
|
+
if (eventDispatcher != null) {
|
|
186
|
+
OnCropEvent event = new OnCropEvent(surfaceId, viewId, uri);
|
|
187
|
+
eventDispatcher.dispatchEvent(event);
|
|
188
|
+
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
private int getBitmapDegree(File file) {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package com.reactnative.imagecrop;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
|
|
6
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
7
|
+
import com.facebook.react.common.MapBuilder;
|
|
8
|
+
import com.facebook.react.uimanager.ThemedReactContext;
|
|
9
|
+
import com.facebook.react.uimanager.ViewGroupManager;
|
|
10
|
+
import com.facebook.react.uimanager.ViewManagerDelegate;
|
|
11
|
+
import com.facebook.react.viewmanagers.ImageCropViewManagerDelegate;
|
|
12
|
+
import com.facebook.react.viewmanagers.ImageCropViewManagerInterface;
|
|
13
|
+
|
|
14
|
+
import java.util.Map;
|
|
15
|
+
|
|
16
|
+
public class ImageCropViewManager extends ViewGroupManager<ImageCropView>
|
|
17
|
+
implements ImageCropViewManagerInterface<ImageCropView> {
|
|
18
|
+
private final ImageCropViewManagerDelegate<ImageCropView, ImageCropViewManager> mDelegagte = new ImageCropViewManagerDelegate<>(this);
|
|
19
|
+
private static final String REACT_CLASS = "ImageCropView";
|
|
20
|
+
|
|
21
|
+
@Override
|
|
22
|
+
protected ViewManagerDelegate<ImageCropView> getDelegate() {
|
|
23
|
+
return mDelegagte;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@NonNull
|
|
27
|
+
@Override
|
|
28
|
+
public String getName() {
|
|
29
|
+
return REACT_CLASS;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@NonNull
|
|
33
|
+
@Override
|
|
34
|
+
protected ImageCropView createViewInstance(@NonNull ThemedReactContext reactContext) {
|
|
35
|
+
return new ImageCropView(reactContext);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Override
|
|
39
|
+
public void setFileUri(ImageCropView view, @Nullable String uri) {
|
|
40
|
+
view.setFileUri(uri);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Override
|
|
44
|
+
public void setCropStyle(ImageCropView view, @Nullable String style) {
|
|
45
|
+
view.setCropStyle(style);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Override
|
|
49
|
+
public void setObjectRect(ImageCropView view, @Nullable ReadableMap value) {
|
|
50
|
+
view.setObjectRect(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Override
|
|
54
|
+
public void crop(ImageCropView view) {
|
|
55
|
+
view.crop();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Override
|
|
59
|
+
protected void onAfterUpdateTransaction(@NonNull ImageCropView view) {
|
|
60
|
+
super.onAfterUpdateTransaction(view);
|
|
61
|
+
view.initProperties();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Nullable
|
|
65
|
+
@Override
|
|
66
|
+
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
|
|
67
|
+
return MapBuilder.of(
|
|
68
|
+
OnCropEvent.Name, MapBuilder.of("registrationName", OnCropEvent.JSEventName)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Override
|
|
73
|
+
protected void addEventEmitters(@NonNull ThemedReactContext reactContext, @NonNull ImageCropView view) {
|
|
74
|
+
super.addEventEmitters(reactContext, view);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
package com.reactnative.imagecrop;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
|
|
6
|
+
import com.facebook.react.bridge.Arguments;
|
|
7
|
+
import com.facebook.react.bridge.WritableMap;
|
|
8
|
+
import com.facebook.react.uimanager.events.Event;
|
|
9
|
+
|
|
10
|
+
public class OnCropEvent extends Event<OnCropEvent> {
|
|
11
|
+
public static final String Name = "topCrop";
|
|
12
|
+
public static final String JSEventName = "onCrop";
|
|
13
|
+
|
|
14
|
+
private final String uri;
|
|
15
|
+
|
|
16
|
+
public OnCropEvent(int surfaceId, int viewTag, String uri) {
|
|
17
|
+
super(surfaceId, viewTag);
|
|
18
|
+
this.uri = uri;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@NonNull
|
|
22
|
+
@Override
|
|
23
|
+
public String getEventName() {
|
|
24
|
+
return Name;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Nullable
|
|
28
|
+
@Override
|
|
29
|
+
protected WritableMap getEventData() {
|
|
30
|
+
WritableMap event = Arguments.createMap();
|
|
31
|
+
event.putString("uri", uri);
|
|
32
|
+
return event;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CodegenTypes, HostComponent, ViewProps } from 'react-native';
|
|
2
|
+
export interface ObjectRect {
|
|
3
|
+
top: CodegenTypes.Float;
|
|
4
|
+
left: CodegenTypes.Float;
|
|
5
|
+
width: CodegenTypes.Float;
|
|
6
|
+
height: CodegenTypes.Float;
|
|
7
|
+
}
|
|
8
|
+
export type OnCropEventPayload = {
|
|
9
|
+
uri: string;
|
|
10
|
+
};
|
|
11
|
+
export interface NativeProps extends ViewProps {
|
|
12
|
+
fileUri?: string;
|
|
13
|
+
cropStyle?: CodegenTypes.WithDefault<'circular' | 'default', 'default'>;
|
|
14
|
+
objectRect?: ObjectRect;
|
|
15
|
+
onCrop?: CodegenTypes.DirectEventHandler<OnCropEventPayload>;
|
|
16
|
+
}
|
|
17
|
+
export interface NativeCommands {
|
|
18
|
+
crop: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare const Commands: NativeCommands;
|
|
21
|
+
declare const _default: HostComponent<NativeProps>;
|
|
22
|
+
export default _default;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ObjectRect, OnCropEventPayload, NativeProps } from './ImageCropNativeComponent';
|
|
3
|
+
import type { NativeSyntheticEvent } from 'react-native';
|
|
4
|
+
export type OnCropEvent = NativeSyntheticEvent<OnCropEventPayload>;
|
|
5
|
+
export type { ObjectRect };
|
|
6
|
+
export interface ImageCropViewInstance {
|
|
7
|
+
crop: () => void;
|
|
8
|
+
}
|
|
9
|
+
export type ImageCropViewProps = NativeProps;
|
|
10
|
+
declare const ImageCropView: React.ForwardRefExoticComponent<NativeProps & React.RefAttributes<ImageCropViewInstance>>;
|
|
11
|
+
export default ImageCropView;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React, { useImperativeHandle, useRef } from 'react';
|
|
2
|
+
import ImageCropNativeComponent, { Commands } from './ImageCropNativeComponent';
|
|
3
|
+
const ImageCropView = React.forwardRef((props, ref) => {
|
|
4
|
+
const viewRef = useRef(null);
|
|
5
|
+
useImperativeHandle(ref, () => ({
|
|
6
|
+
crop: () => {
|
|
7
|
+
if (viewRef.current) {
|
|
8
|
+
Commands.crop(viewRef.current);
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
return <ImageCropNativeComponent {...props} ref={viewRef}/>;
|
|
13
|
+
});
|
|
14
|
+
export default ImageCropView;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#import "RNImageCropView.h"
|
|
2
|
+
#import "UIImage+CropRotate.h"
|
|
3
|
+
#import "TOCropView.h"
|
|
4
|
+
|
|
5
|
+
#import <react/renderer/components/imagecrop/ComponentDescriptors.h>
|
|
6
|
+
#import <react/renderer/components/imagecrop/EventEmitters.h>
|
|
7
|
+
#import <react/renderer/components/imagecrop/Props.h>
|
|
8
|
+
#import <react/renderer/components/imagecrop/RCTComponentViewHelpers.h>
|
|
9
|
+
|
|
10
|
+
#import <React/RCTConversions.h>
|
|
11
|
+
#import <React/RCTLog.h>
|
|
12
|
+
|
|
13
|
+
using namespace facebook::react;
|
|
14
|
+
|
|
15
|
+
@interface RNImageCropView() <RCTImageCropViewViewProtocol>
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
RN传递进来的属性值:需要裁剪图片路径
|
|
19
|
+
*/
|
|
20
|
+
@property(nonatomic, copy, nonnull) NSString *fileUri;
|
|
21
|
+
|
|
22
|
+
@property(nonatomic, assign) ImageCropViewCropStyle cropStyle;
|
|
23
|
+
|
|
24
|
+
@property(nonatomic, assign) ImageCropViewObjectRectStruct objectRect;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
The original, uncropped image that was passed to this controller.
|
|
28
|
+
*/
|
|
29
|
+
@property (nonatomic, nonnull) UIImage *image;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
The cropping style of this particular crop view controller
|
|
33
|
+
*/
|
|
34
|
+
@property (nonatomic, assign) TOCropViewCroppingStyle croppingStyle;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
The crop view managed by this view controller.
|
|
38
|
+
*/
|
|
39
|
+
@property (nonatomic, strong, nonnull) TOCropView *cropView;
|
|
40
|
+
|
|
41
|
+
@property (nonatomic, assign) BOOL initialSetupPerformed;
|
|
42
|
+
|
|
43
|
+
@end
|
|
44
|
+
|
|
45
|
+
@implementation RNImageCropView
|
|
46
|
+
|
|
47
|
+
// Needed because of this: https://github.com/facebook/react-native/pull/37274
|
|
48
|
+
+ (void)load {
|
|
49
|
+
[super load];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
53
|
+
return concreteComponentDescriptorProvider<ImageCropViewComponentDescriptor>();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
+ (BOOL)shouldBeRecycled {
|
|
57
|
+
return NO;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
61
|
+
if ((self = [super initWithFrame:frame])) {
|
|
62
|
+
static const auto defaultProps = std::make_shared<const ImageCropViewProps>();
|
|
63
|
+
_props = defaultProps;
|
|
64
|
+
_cropStyle = ImageCropViewCropStyle::Default;
|
|
65
|
+
}
|
|
66
|
+
return self;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
- (void)updateProps:(const facebook::react::Props::Shared &)props oldProps:(const facebook::react::Props::Shared &)oldProps {
|
|
70
|
+
const auto &oldViewProps = static_cast<const ImageCropViewProps &>(*_props);
|
|
71
|
+
const auto &newViewProps = static_cast<const ImageCropViewProps &>(*props);
|
|
72
|
+
|
|
73
|
+
// `fileUri`
|
|
74
|
+
if (newViewProps.fileUri != oldViewProps.fileUri) {
|
|
75
|
+
self.fileUri = RCTNSStringFromStringNilIfEmpty(newViewProps.fileUri);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// `cropStyle`
|
|
79
|
+
if (newViewProps.cropStyle != oldViewProps.cropStyle) {
|
|
80
|
+
self.cropStyle = newViewProps.cropStyle;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// `objectRect`
|
|
84
|
+
if (newViewProps.objectRect.width != oldViewProps.objectRect.width || newViewProps.objectRect.height != oldViewProps.objectRect.height || newViewProps.objectRect.top != oldViewProps.objectRect.top || newViewProps.objectRect.left != oldViewProps.objectRect.left) {
|
|
85
|
+
self.objectRect = newViewProps.objectRect;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
[self addCropView:self.croppingStyle image:self.image];
|
|
89
|
+
|
|
90
|
+
[super updateProps:props oldProps:oldProps];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
- (void)setFileUri:(NSString *)fileUri {
|
|
94
|
+
_fileUri = fileUri;
|
|
95
|
+
_image = [UIImage imageWithContentsOfFile: [[NSURL alloc] initWithString:fileUri].path];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
- (const ImageCropViewEventEmitter &)eventEmitter {
|
|
99
|
+
return static_cast<const ImageCropViewEventEmitter &>(*_eventEmitter);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
|
|
103
|
+
RCTImageCropViewHandleCommand(self, commandName, args);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
- (void)crop {
|
|
107
|
+
CGRect cropFrame = self.cropView.imageCropFrame;
|
|
108
|
+
NSInteger angle = self.cropView.angle;
|
|
109
|
+
|
|
110
|
+
UIImage *image = nil;
|
|
111
|
+
if (angle == 0 && CGRectEqualToRect(cropFrame, (CGRect){CGPointZero, self.image.size})) {
|
|
112
|
+
image = self.image;
|
|
113
|
+
} else {
|
|
114
|
+
image = [self.image croppedImageWithFrame:cropFrame angle:angle circularClip:NO];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
[self saveImage:image];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
- (void)layoutSubviews {
|
|
121
|
+
[super layoutSubviews];
|
|
122
|
+
if (self.cropView && [self.subviews containsObject:self.cropView]) {
|
|
123
|
+
if (self.initialSetupPerformed) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
self.initialSetupPerformed = YES;
|
|
127
|
+
|
|
128
|
+
//设置图片主体检测参数
|
|
129
|
+
if (self.cropStyle != ImageCropViewCropStyle::Circular
|
|
130
|
+
&& self.objectRect.width > 0 && self.objectRect.height > 0) {
|
|
131
|
+
int top = static_cast<int>(self.objectRect.top);
|
|
132
|
+
int left = static_cast<int>(self.objectRect.left);
|
|
133
|
+
int width = static_cast<int>(self.objectRect.width);
|
|
134
|
+
int height = static_cast<int>(self.objectRect.height);
|
|
135
|
+
[self.cropView setImageCropFrame:CGRectMake(left, top, width, height)];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
//存在极小的概率,初始化时只在左上角展示一小块图片区域,目前没有找到较好的方案,尝试延迟一点时间初始化CropView可以解决问题
|
|
139
|
+
// [self.cropView performInitialSetup];
|
|
140
|
+
[self.cropView performSelector:@selector(performInitialSetup) withObject:nil afterDelay:0.05];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
- (void)addCropView:(TOCropViewCroppingStyle)style image:(UIImage *)image {
|
|
145
|
+
if (!_cropView) {
|
|
146
|
+
_cropView = [[TOCropView alloc] initWithCroppingStyle:style image:image];
|
|
147
|
+
_cropView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
148
|
+
[_cropView setBackgroundColor:[UIColor blackColor]];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (![self.subviews containsObject:self.cropView]) {
|
|
152
|
+
[self addSubview:_cropView];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
- (void)saveImage:(UIImage*)image {
|
|
157
|
+
NSString *fileName = [NSString stringWithFormat:@"%@.png", [self produceUUID]];
|
|
158
|
+
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
|
|
159
|
+
BOOL result =[UIImagePNGRepresentation(image)writeToFile:filePath atomically:YES];
|
|
160
|
+
|
|
161
|
+
if(result ==YES) {
|
|
162
|
+
NSLog(@"保存成功: %@", filePath);
|
|
163
|
+
NSString *uri = [[NSURL alloc] initFileURLWithPath:filePath].absoluteString;
|
|
164
|
+
[self eventEmitter].onCrop(ImageCropViewEventEmitter::OnCrop{
|
|
165
|
+
.uri = RCTStringFromNSString(uri)
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
- (TOCropViewCroppingStyle)croppingStyle {
|
|
171
|
+
if (self.cropStyle == ImageCropViewCropStyle::Circular) {
|
|
172
|
+
return TOCropViewCroppingStyleCircular;
|
|
173
|
+
}
|
|
174
|
+
return TOCropViewCroppingStyleDefault;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
- (NSString *)produceUUID {
|
|
178
|
+
CFUUIDRef uuid_ref = CFUUIDCreate(NULL);
|
|
179
|
+
CFStringRef uuid_string_ref= CFUUIDCreateString(NULL, uuid_ref);
|
|
180
|
+
NSString *uuid = [NSString stringWithString:(__bridge NSString *)uuid_string_ref];
|
|
181
|
+
CFRelease(uuid_ref);
|
|
182
|
+
CFRelease(uuid_string_ref);
|
|
183
|
+
return [uuid uppercaseString];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@end
|
package/package.json
CHANGED
|
@@ -1,66 +1,52 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"eslint": "^7.32.0",
|
|
53
|
-
"typescript": "^4.6.4"
|
|
54
|
-
},
|
|
55
|
-
"jest": {
|
|
56
|
-
"preset": "react-native",
|
|
57
|
-
"moduleFileExtensions": [
|
|
58
|
-
"ts",
|
|
59
|
-
"tsx",
|
|
60
|
-
"js",
|
|
61
|
-
"jsx",
|
|
62
|
-
"json",
|
|
63
|
-
"node"
|
|
64
|
-
]
|
|
65
|
-
}
|
|
2
|
+
"name": "@sdcx/image-crop",
|
|
3
|
+
"description": "A React Native ui component for image crop.",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"typings": "./dist/index.d.ts",
|
|
7
|
+
"react-native": "src/index",
|
|
8
|
+
"nativePackage": true,
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"dist",
|
|
12
|
+
"android",
|
|
13
|
+
"ios",
|
|
14
|
+
"RNImageCrop.podspec",
|
|
15
|
+
"!android/build",
|
|
16
|
+
"!ios/build",
|
|
17
|
+
"!**/__tests__"
|
|
18
|
+
],
|
|
19
|
+
"repository": "https://github.com/sdcxtech/react-native-troika",
|
|
20
|
+
"homepage": "https://github.com/sdcxtech/react-native-troika/tree/master/packages/image-crop#readme",
|
|
21
|
+
"author": "sdcx",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"keywords": [
|
|
24
|
+
"react-native",
|
|
25
|
+
"image-crop"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "rm -rf ./dist && tsc -p tsconfig.json",
|
|
29
|
+
"prepare": "npm run build",
|
|
30
|
+
"tsc": "tsc",
|
|
31
|
+
"test": "jest",
|
|
32
|
+
"lint": "eslint . --fix --ext .js,.jsx,.ts,.tsx"
|
|
33
|
+
},
|
|
34
|
+
"codegenConfig": {
|
|
35
|
+
"name": "imagecrop",
|
|
36
|
+
"type": "components",
|
|
37
|
+
"jsSrcsDir": "src",
|
|
38
|
+
"android": {
|
|
39
|
+
"javaPackageName": "com.reactnative.imagecrop"
|
|
40
|
+
},
|
|
41
|
+
"ios": {
|
|
42
|
+
"componentProvider": {
|
|
43
|
+
"ImageCropView": "RNImageCropView"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": ">=16.8",
|
|
49
|
+
"react-native": ">=0.60"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {}
|
|
66
52
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CodegenTypes, HostComponent, ViewProps } from 'react-native';
|
|
2
|
+
import { codegenNativeComponent, codegenNativeCommands } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface ObjectRect {
|
|
5
|
+
top: CodegenTypes.Float;
|
|
6
|
+
left: CodegenTypes.Float;
|
|
7
|
+
width: CodegenTypes.Float;
|
|
8
|
+
height: CodegenTypes.Float;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type OnCropEventPayload = {
|
|
12
|
+
uri: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export interface NativeProps extends ViewProps {
|
|
16
|
+
fileUri?: string;
|
|
17
|
+
cropStyle?: CodegenTypes.WithDefault<'circular' | 'default', 'default'>;
|
|
18
|
+
objectRect?: ObjectRect;
|
|
19
|
+
onCrop?: CodegenTypes.DirectEventHandler<OnCropEventPayload>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface NativeCommands {
|
|
23
|
+
// In TypeScript, the React.ElementRef is deprecated.
|
|
24
|
+
// The correct type to use is actually React.ComponentRef.
|
|
25
|
+
// However, due to a bug in Codegen, using ComponentRef will crash the app.
|
|
26
|
+
// We have the fix already, but we need to release a new version of React Native to apply it.
|
|
27
|
+
crop: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
|
|
31
|
+
supportedCommands: ['crop'],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export default codegenNativeComponent<NativeProps>('ImageCropView') as HostComponent<NativeProps>;
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React, { useImperativeHandle, useRef } from 'react';
|
|
2
|
+
import ImageCropNativeComponent, { Commands } from './ImageCropNativeComponent';
|
|
3
|
+
import type { ObjectRect, OnCropEventPayload, NativeProps } from './ImageCropNativeComponent';
|
|
4
|
+
import type { NativeSyntheticEvent } from 'react-native';
|
|
5
|
+
|
|
6
|
+
export type OnCropEvent = NativeSyntheticEvent<OnCropEventPayload>;
|
|
7
|
+
export type { ObjectRect };
|
|
8
|
+
|
|
9
|
+
export interface ImageCropViewInstance {
|
|
10
|
+
crop: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ImageCropViewProps = NativeProps;
|
|
14
|
+
|
|
15
|
+
const ImageCropView = React.forwardRef<ImageCropViewInstance, ImageCropViewProps>((props, ref) => {
|
|
16
|
+
const viewRef = useRef<React.ComponentRef<typeof ImageCropNativeComponent>>(null);
|
|
17
|
+
useImperativeHandle(ref, () => ({
|
|
18
|
+
crop: () => {
|
|
19
|
+
if (viewRef.current) {
|
|
20
|
+
Commands.crop(viewRef.current);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
return <ImageCropNativeComponent {...props} ref={viewRef} />;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export default ImageCropView;
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
package com.reactnative.imagecrop;
|
|
2
|
-
|
|
3
|
-
import androidx.annotation.NonNull;
|
|
4
|
-
import androidx.annotation.Nullable;
|
|
5
|
-
|
|
6
|
-
import com.facebook.react.bridge.ReadableArray;
|
|
7
|
-
import com.facebook.react.bridge.ReadableMap;
|
|
8
|
-
import com.facebook.react.common.MapBuilder;
|
|
9
|
-
import com.facebook.react.uimanager.ThemedReactContext;
|
|
10
|
-
import com.facebook.react.uimanager.ViewGroupManager;
|
|
11
|
-
import com.facebook.react.uimanager.annotations.ReactProp;
|
|
12
|
-
|
|
13
|
-
import java.util.Map;
|
|
14
|
-
|
|
15
|
-
public class RNImageCropViewManager extends ViewGroupManager<RNImageCropView> {
|
|
16
|
-
private static final String REACT_CLASS = "RNImageCrop";
|
|
17
|
-
|
|
18
|
-
private static final int COMMAND_CROP = 1;
|
|
19
|
-
|
|
20
|
-
@NonNull
|
|
21
|
-
@Override
|
|
22
|
-
public String getName() {
|
|
23
|
-
return REACT_CLASS;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
@NonNull
|
|
27
|
-
@Override
|
|
28
|
-
protected RNImageCropView createViewInstance(@NonNull ThemedReactContext reactContext) {
|
|
29
|
-
return new RNImageCropView(reactContext);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
@ReactProp(name = "fileUri")
|
|
33
|
-
public void setFileUri(RNImageCropView RNImageCropView, String fileUri) {
|
|
34
|
-
RNImageCropView.setFileUri(fileUri);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@ReactProp(name = "cropStyle")
|
|
38
|
-
public void setCropStyle(RNImageCropView RNImageCropView, String cropStyle) {
|
|
39
|
-
RNImageCropView.setCropStyle(cropStyle);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
@ReactProp(name = "objectRect")
|
|
43
|
-
public void setObjectRect(RNImageCropView RNImageCropView, ReadableMap objectRect) {
|
|
44
|
-
RNImageCropView.setObjectRect(objectRect);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
@Override
|
|
48
|
-
protected void onAfterUpdateTransaction(@NonNull RNImageCropView RNImageCropView) {
|
|
49
|
-
super.onAfterUpdateTransaction(RNImageCropView);
|
|
50
|
-
RNImageCropView.initProperties();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@Nullable
|
|
54
|
-
@Override
|
|
55
|
-
public Map<String, Integer> getCommandsMap() {
|
|
56
|
-
return MapBuilder.of("crop", COMMAND_CROP);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
@Override
|
|
60
|
-
public void receiveCommand(@NonNull RNImageCropView root, String commandId, @Nullable ReadableArray args) {
|
|
61
|
-
super.receiveCommand(root, commandId, args);
|
|
62
|
-
int commandIdInt = Integer.parseInt(commandId);
|
|
63
|
-
switch (commandIdInt) {
|
|
64
|
-
case COMMAND_CROP:
|
|
65
|
-
root.crop();
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
|
|
71
|
-
String name = "phasedRegistrationNames";
|
|
72
|
-
String bubbled = "bubbled";
|
|
73
|
-
return MapBuilder.<String, Object>builder()
|
|
74
|
-
.put("onCropped", MapBuilder.of(name, MapBuilder.of(bubbled, "onCropped")))
|
|
75
|
-
.build();
|
|
76
|
-
}
|
|
77
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#import <UIKit/UIKit.h>
|
|
2
|
-
#import <React/UIView+React.h>
|
|
3
|
-
|
|
4
|
-
#import "TOCropView.h"
|
|
5
|
-
|
|
6
|
-
@interface RNImageCrop : UIView
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
RN传递进来的属性值:需要裁剪图片路径
|
|
10
|
-
*/
|
|
11
|
-
@property(nonatomic, copy, nonnull) NSString *fileUri;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
RN传递进来的属性值:裁剪样式,default | circular
|
|
15
|
-
*/
|
|
16
|
-
@property(nonatomic, copy, nullable) NSString *cropStyle;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
RN传递进来的属性值:图像主体Rect,如{"width":208,"left":43,"top":111,"height":354}
|
|
20
|
-
*/
|
|
21
|
-
@property(nonatomic, copy, nullable) id objectRect;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
选定图片区域后,确认裁剪操作
|
|
25
|
-
*/
|
|
26
|
-
- (void)crop;
|
|
27
|
-
|
|
28
|
-
@property(nonatomic, copy, nullable) RCTBubblingEventBlock onCropped;
|
|
29
|
-
|
|
30
|
-
@end
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
#import "RNImageCrop.h"
|
|
2
|
-
#import "UIImage+CropRotate.h"
|
|
3
|
-
|
|
4
|
-
@interface RNImageCrop()
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
The original, uncropped image that was passed to this controller.
|
|
8
|
-
*/
|
|
9
|
-
@property (nonatomic, nonnull) UIImage *image;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
The cropping style of this particular crop view controller
|
|
13
|
-
*/
|
|
14
|
-
@property (nonatomic) TOCropViewCroppingStyle croppingStyle;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
The crop view managed by this view controller.
|
|
18
|
-
*/
|
|
19
|
-
@property (nonatomic, strong, nonnull) TOCropView *cropView;
|
|
20
|
-
|
|
21
|
-
@property (nonatomic, assign) BOOL initialSetupPerformed;
|
|
22
|
-
|
|
23
|
-
@end
|
|
24
|
-
|
|
25
|
-
@implementation RNImageCrop
|
|
26
|
-
|
|
27
|
-
- (instancetype)initWithFrame:(CGRect)frame {
|
|
28
|
-
if ((self = [super initWithFrame:frame])) {
|
|
29
|
-
}
|
|
30
|
-
return self;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
- (void)layoutSubviews {
|
|
34
|
-
[super layoutSubviews];
|
|
35
|
-
if (self.cropView && [self.subviews containsObject:self.cropView]) {
|
|
36
|
-
if (self.initialSetupPerformed) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
self.initialSetupPerformed = YES;
|
|
40
|
-
|
|
41
|
-
//设置图片主体检测参数
|
|
42
|
-
if (![_cropStyle isEqualToString:@"circular"]
|
|
43
|
-
&& _objectRect != nil
|
|
44
|
-
&& [_objectRect objectForKey:@"top"]
|
|
45
|
-
&& [_objectRect objectForKey:@"left"]
|
|
46
|
-
&& [_objectRect objectForKey:@"width"]
|
|
47
|
-
&& [_objectRect objectForKey:@"height"]) {
|
|
48
|
-
int top = [[_objectRect valueForKey:@"top"] intValue];
|
|
49
|
-
int left = [[_objectRect valueForKey:@"left"] intValue];
|
|
50
|
-
int width = [[_objectRect valueForKey:@"width"] intValue];
|
|
51
|
-
int height = [[_objectRect valueForKey:@"height"] intValue];
|
|
52
|
-
[self.cropView setImageCropFrame:CGRectMake(left, top, width, height)];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
//存在极小的概率,初始化时只在左上角展示一小块图片区域,目前没有找到较好的方案,尝试延迟一点时间初始化CropView可以解决问题
|
|
56
|
-
// [self.cropView performInitialSetup];
|
|
57
|
-
[self.cropView performSelector:@selector(performInitialSetup) withObject:nil afterDelay:0.05];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
- (void)setFileUri:(NSString *)fileUri {
|
|
62
|
-
_fileUri = fileUri;
|
|
63
|
-
_image = [UIImage imageWithContentsOfFile: [[NSURL alloc] initWithString:fileUri].path];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
- (void)didSetProps:(NSArray<NSString *> *)changedProps {
|
|
67
|
-
[self addCropView:self.croppingStyle image:self.image];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
- (void)addCropView:(TOCropViewCroppingStyle)style image:(UIImage *)image {
|
|
71
|
-
if (!_cropView) {
|
|
72
|
-
_cropView = [[TOCropView alloc] initWithCroppingStyle:style image:image];
|
|
73
|
-
_cropView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
74
|
-
[_cropView setBackgroundColor:[UIColor blackColor]];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (![self.subviews containsObject:self.cropView]) {
|
|
78
|
-
[self addSubview:_cropView];
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
- (void)crop {
|
|
83
|
-
CGRect cropFrame = self.cropView.imageCropFrame;
|
|
84
|
-
NSInteger angle = self.cropView.angle;
|
|
85
|
-
|
|
86
|
-
UIImage *image = nil;
|
|
87
|
-
if (angle == 0 && CGRectEqualToRect(cropFrame, (CGRect){CGPointZero, self.image.size})) {
|
|
88
|
-
image = self.image;
|
|
89
|
-
} else {
|
|
90
|
-
image = [self.image croppedImageWithFrame:cropFrame angle:angle circularClip:NO];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
[self saveImage:image];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
- (void)saveImage:(UIImage*)image {
|
|
97
|
-
NSString *fileName = [NSString stringWithFormat:@"%@.png", [self produceUUID]];
|
|
98
|
-
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
|
|
99
|
-
BOOL result =[UIImagePNGRepresentation(image)writeToFile:filePath atomically:YES];
|
|
100
|
-
|
|
101
|
-
if(result ==YES) {
|
|
102
|
-
NSLog(@"保存成功: %@", filePath);
|
|
103
|
-
|
|
104
|
-
if (self.onCropped) {
|
|
105
|
-
self.onCropped(@{
|
|
106
|
-
@"uri": [[NSURL alloc] initFileURLWithPath:filePath].absoluteString
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
- (TOCropViewCroppingStyle)croppingStyle {
|
|
113
|
-
if (_cropStyle && [_cropStyle isEqualToString:@"circular"]) {
|
|
114
|
-
return TOCropViewCroppingStyleCircular;
|
|
115
|
-
}
|
|
116
|
-
return TOCropViewCroppingStyleDefault;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
- (NSString *)produceUUID {
|
|
121
|
-
CFUUIDRef uuid_ref = CFUUIDCreate(NULL);
|
|
122
|
-
CFStringRef uuid_string_ref= CFUUIDCreateString(NULL, uuid_ref);
|
|
123
|
-
NSString *uuid = [NSString stringWithString:(__bridge NSString *)uuid_string_ref];
|
|
124
|
-
CFRelease(uuid_ref);
|
|
125
|
-
CFRelease(uuid_string_ref);
|
|
126
|
-
return [uuid uppercaseString];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
@end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// RNImageCropManager.h
|
|
3
|
-
// RNImageCrop
|
|
4
|
-
//
|
|
5
|
-
// Created by vibe on 2023/4/28.
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
#import <React/RCTViewManager.h>
|
|
9
|
-
#import <React/RCTBridgeModule.h>
|
|
10
|
-
#import <React/RCTUIManager.h>
|
|
11
|
-
|
|
12
|
-
NS_ASSUME_NONNULL_BEGIN
|
|
13
|
-
|
|
14
|
-
@interface RNImageCropManager : RCTViewManager
|
|
15
|
-
|
|
16
|
-
@end
|
|
17
|
-
|
|
18
|
-
NS_ASSUME_NONNULL_END
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// RNCropManager.m
|
|
3
|
-
// RNCrop
|
|
4
|
-
//
|
|
5
|
-
// Created by vibe on 2022/2/8.
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
#import "RNImageCropManager.h"
|
|
9
|
-
#import "RNImageCrop.h"
|
|
10
|
-
|
|
11
|
-
@implementation RNImageCropManager
|
|
12
|
-
|
|
13
|
-
RCT_EXPORT_MODULE(RNImageCrop)
|
|
14
|
-
RCT_EXPORT_VIEW_PROPERTY(fileUri, NSString)
|
|
15
|
-
RCT_EXPORT_VIEW_PROPERTY(cropStyle, NSString)
|
|
16
|
-
RCT_EXPORT_VIEW_PROPERTY(objectRect, id)
|
|
17
|
-
RCT_EXPORT_VIEW_PROPERTY(onCropped, RCTBubblingEventBlock)
|
|
18
|
-
RCT_EXPORT_METHOD(crop:(nonnull NSNumber *)reactTag) {
|
|
19
|
-
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
|
|
20
|
-
RNImageCrop *rnCrop = viewRegistry[reactTag];
|
|
21
|
-
if (![rnCrop isKindOfClass:[RNImageCrop class]]) {
|
|
22
|
-
RCTLogError(@"Invalid view returned from registry, expecting RNCrop, got: %@", rnCrop);
|
|
23
|
-
} else {
|
|
24
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
25
|
-
RNImageCrop *rnCrop = (RNImageCrop *)viewRegistry[reactTag];
|
|
26
|
-
[rnCrop crop];
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
}];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
- (UIView *)view
|
|
33
|
-
{
|
|
34
|
-
return [RNImageCrop new];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@end
|
package/lib/ImageCropView.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { ViewProps, ViewStyle } from 'react-native';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { ImageCropViewRef } from './ImageCropViewRef';
|
|
4
|
-
import { ObjectRect } from './typings';
|
|
5
|
-
interface SupperProps extends ViewProps {
|
|
6
|
-
fileUri: string;
|
|
7
|
-
objectRect?: ObjectRect;
|
|
8
|
-
cropStyle?: 'circular' | 'default';
|
|
9
|
-
}
|
|
10
|
-
interface CropViewProps extends SupperProps {
|
|
11
|
-
style?: ViewStyle;
|
|
12
|
-
onCropped: (uri: string) => void;
|
|
13
|
-
}
|
|
14
|
-
declare const ImageCropView: React.ForwardRefExoticComponent<CropViewProps & React.RefAttributes<ImageCropViewRef>>;
|
|
15
|
-
export default ImageCropView;
|
package/lib/ImageCropView.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { findNodeHandle, Platform, requireNativeComponent, UIManager } from 'react-native';
|
|
2
|
-
import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react';
|
|
3
|
-
const CropViewNativeComponent = requireNativeComponent('RNImageCrop');
|
|
4
|
-
const ImageCropView = forwardRef(({ fileUri, cropStyle, objectRect, style, onCropped }, ref) => {
|
|
5
|
-
const reactTag = useRef(null);
|
|
6
|
-
const crop = useCallback(() => {
|
|
7
|
-
UIManager.dispatchViewManagerCommand(reactTag.current, Platform.OS === 'ios'
|
|
8
|
-
? UIManager.getViewManagerConfig('RNImageCrop').Commands.crop
|
|
9
|
-
: UIManager.getViewManagerConfig('RNImageCrop').Commands.crop.toString(), Platform.OS === 'ios' ? [] : [reactTag.current]);
|
|
10
|
-
}, []);
|
|
11
|
-
const onNativeCropped = useCallback(({ nativeEvent }) => {
|
|
12
|
-
const uri = nativeEvent.uri;
|
|
13
|
-
if (onCropped) {
|
|
14
|
-
onCropped(uri);
|
|
15
|
-
}
|
|
16
|
-
}, [onCropped]);
|
|
17
|
-
useImperativeHandle(ref, () => ({
|
|
18
|
-
crop,
|
|
19
|
-
}), [crop]);
|
|
20
|
-
return (<CropViewNativeComponent fileUri={fileUri} cropStyle={cropStyle} objectRect={objectRect} onCropped={onNativeCropped} style={style} ref={mRef => {
|
|
21
|
-
reactTag.current = findNodeHandle(mRef);
|
|
22
|
-
}}/>);
|
|
23
|
-
});
|
|
24
|
-
export default ImageCropView;
|
package/lib/ImageCropViewRef.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/lib/index.d.ts
DELETED
package/lib/index.js
DELETED
package/lib/typings.d.ts
DELETED
package/lib/typings.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/src/ImageCropView.tsx
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { findNodeHandle, Platform, requireNativeComponent, UIManager, ViewProps, ViewStyle } from 'react-native'
|
|
2
|
-
import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'
|
|
3
|
-
import { ImageCropViewRef } from './ImageCropViewRef'
|
|
4
|
-
import { ObjectRect } from './typings'
|
|
5
|
-
|
|
6
|
-
const CropViewNativeComponent = requireNativeComponent<CropViewNativeComponentProps>('RNImageCrop')
|
|
7
|
-
|
|
8
|
-
interface SupperProps extends ViewProps {
|
|
9
|
-
fileUri: string
|
|
10
|
-
objectRect?: ObjectRect
|
|
11
|
-
cropStyle?: 'circular' | 'default'
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface CropViewNativeComponentProps extends SupperProps {
|
|
15
|
-
onCropped: (callback: any) => void
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface CropViewProps extends SupperProps {
|
|
19
|
-
style?: ViewStyle
|
|
20
|
-
onCropped: (uri: string) => void
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const ImageCropView = forwardRef<ImageCropViewRef, CropViewProps>(
|
|
24
|
-
({ fileUri, cropStyle, objectRect, style, onCropped }: CropViewProps, ref) => {
|
|
25
|
-
const reactTag = useRef<number | null>(null)
|
|
26
|
-
const crop = useCallback(() => {
|
|
27
|
-
UIManager.dispatchViewManagerCommand(
|
|
28
|
-
reactTag.current,
|
|
29
|
-
Platform.OS === 'ios'
|
|
30
|
-
? UIManager.getViewManagerConfig('RNImageCrop').Commands.crop
|
|
31
|
-
: UIManager.getViewManagerConfig('RNImageCrop').Commands.crop.toString(),
|
|
32
|
-
Platform.OS === 'ios' ? [] : [reactTag.current],
|
|
33
|
-
)
|
|
34
|
-
}, [])
|
|
35
|
-
|
|
36
|
-
const onNativeCropped = useCallback(
|
|
37
|
-
({ nativeEvent }: any) => {
|
|
38
|
-
const uri = nativeEvent.uri
|
|
39
|
-
if (onCropped) {
|
|
40
|
-
onCropped(uri)
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
[onCropped],
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
useImperativeHandle(
|
|
47
|
-
ref,
|
|
48
|
-
() => ({
|
|
49
|
-
crop,
|
|
50
|
-
}),
|
|
51
|
-
[crop],
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<CropViewNativeComponent
|
|
56
|
-
fileUri={fileUri}
|
|
57
|
-
cropStyle={cropStyle}
|
|
58
|
-
objectRect={objectRect}
|
|
59
|
-
onCropped={onNativeCropped}
|
|
60
|
-
style={style}
|
|
61
|
-
ref={mRef => {
|
|
62
|
-
reactTag.current = findNodeHandle(mRef)
|
|
63
|
-
}}
|
|
64
|
-
/>
|
|
65
|
-
)
|
|
66
|
-
},
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
export default ImageCropView
|
package/src/ImageCropViewRef.ts
DELETED
package/src/index.ts
DELETED