@react-native-ohos/react-native-image-crop-picker 0.40.4 → 0.40.5-rc.11
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/CHANGELOG.md +17 -0
- package/COMMITTERS.md +1 -5
- package/harmony/image_crop_picker/index.ets +3 -1
- package/harmony/image_crop_picker/oh-package.json5 +1 -1
- package/harmony/image_crop_picker/src/main/cpp/generated/RNOH/generated/BaseReactNativeImageCropPickerPackage.h +0 -1
- package/harmony/image_crop_picker/src/main/ets/{ImageCropPickerPackage.ts → ImageCropPickerPackage.ets} +2 -1
- package/harmony/image_crop_picker/src/main/ets/ImageCropPickerTurboModule.ts +45 -30
- package/harmony/image_crop_picker/src/main/ets/generated/components/ts.ts +1 -1
- package/harmony/image_crop_picker/src/main/ets/generated/turboModules/ImageCropPicker.ts +2 -0
- package/harmony/image_crop_picker/src/main/ets/pages/CircleImageInfo.ets +795 -0
- package/harmony/image_crop_picker/src/main/ets/pages/ImageEditInfo.ets +1073 -172
- package/harmony/image_crop_picker/src/main/ets/utils/CircleImageProcessor.ets +125 -0
- package/harmony/image_crop_picker/src/main/ets/utils/Constants.ets +4 -0
- package/harmony/image_crop_picker/src/main/ets/utils/EncodeUtil.ets +21 -4
- package/harmony/image_crop_picker/src/main/resources/base/profile/main_pages.json +2 -1
- package/harmony/image_crop_picker.har +0 -0
- package/package.json +9 -3
- /package/harmony/image_crop_picker/{ts.ts → ts.ets} +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { image } from '@kit.ImageKit';
|
|
2
|
+
import util from '@ohos.util';
|
|
3
|
+
|
|
4
|
+
export class CircleImageProcessor {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 将正方形图片转换为真正的圆形图片(带透明背景)
|
|
8
|
+
*/
|
|
9
|
+
static async createTrueCircleImage(squarePm: PixelMap): Promise<PixelMap> {
|
|
10
|
+
const info = await squarePm.getImageInfo();
|
|
11
|
+
const size = Math.min(info.size.width, info.size.height);
|
|
12
|
+
|
|
13
|
+
console.info(`Creating true circle image, size: ${size}x${size}`);
|
|
14
|
+
|
|
15
|
+
// 1. 读取原始像素(BGRA格式)
|
|
16
|
+
const originalBuffer = new ArrayBuffer(squarePm.getPixelBytesNumber());
|
|
17
|
+
await squarePm.readPixelsToBuffer(originalBuffer);
|
|
18
|
+
const originalPixels = new Uint8ClampedArray(originalBuffer);
|
|
19
|
+
|
|
20
|
+
// 2. 创建新的RGBA像素数组(支持透明度)
|
|
21
|
+
const pixelBuffer = new Uint8ClampedArray(size * size * 4);
|
|
22
|
+
|
|
23
|
+
const radius = size / 2;
|
|
24
|
+
const centerX = radius;
|
|
25
|
+
const centerY = radius;
|
|
26
|
+
const radiusSq = radius * radius;
|
|
27
|
+
|
|
28
|
+
// 3. 遍历每个像素,创建圆形遮罩
|
|
29
|
+
for (let y = 0; y < size; y++) {
|
|
30
|
+
for (let x = 0; x < size; x++) {
|
|
31
|
+
const dx = x - centerX;
|
|
32
|
+
const dy = y - centerY;
|
|
33
|
+
const distSq = dx * dx + dy * dy;
|
|
34
|
+
|
|
35
|
+
const srcIdx = (y * size + x) * 4;
|
|
36
|
+
const dstIdx = (y * size + x) * 4;
|
|
37
|
+
|
|
38
|
+
if (distSq <= radiusSq) {
|
|
39
|
+
// 圆形区域内:复制像素并转换 BGRA -> RGBA
|
|
40
|
+
// 同时进行抗锯齿处理(边缘半透明)
|
|
41
|
+
const distance = Math.sqrt(distSq);
|
|
42
|
+
let alpha = 255; // 完全不透明
|
|
43
|
+
|
|
44
|
+
// 边缘抗锯齿:距离半径3像素内的区域逐渐透明
|
|
45
|
+
if (distance > radius - 3) {
|
|
46
|
+
const edgeDistance = radius - distance;
|
|
47
|
+
alpha = Math.max(0, Math.min(255, Math.floor((edgeDistance / 3) * 255)));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pixelBuffer[dstIdx] = originalPixels[srcIdx + 2]; // R
|
|
51
|
+
pixelBuffer[dstIdx + 1] = originalPixels[srcIdx + 1]; // G
|
|
52
|
+
pixelBuffer[dstIdx + 2] = originalPixels[srcIdx]; // B
|
|
53
|
+
pixelBuffer[dstIdx + 3] = alpha; // A(带抗锯齿)
|
|
54
|
+
} else {
|
|
55
|
+
// 圆形区域外:完全透明
|
|
56
|
+
pixelBuffer[dstIdx] = 0; // R
|
|
57
|
+
pixelBuffer[dstIdx + 1] = 0; // G
|
|
58
|
+
pixelBuffer[dstIdx + 2] = 0; // B
|
|
59
|
+
pixelBuffer[dstIdx + 3] = 0; // A(完全透明)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 4. 创建新的PixelMap(RGBA_8888支持透明度)
|
|
65
|
+
const opts: image.InitializationOptions = {
|
|
66
|
+
editable: true,
|
|
67
|
+
pixelFormat: image.PixelMapFormat.RGBA_8888,
|
|
68
|
+
size: { height: size, width: size },
|
|
69
|
+
alphaType: image.AlphaType.PREMUL // 预乘alpha,透明度效果更好
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
console.info('Circle image created successfully');
|
|
73
|
+
return await image.createPixelMap(pixelBuffer.buffer, opts);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 从矩形图片创建圆形图片
|
|
78
|
+
*/
|
|
79
|
+
static async cropAndCreateCircle(sourcePm: PixelMap, cropRegion: image.Region): Promise<PixelMap> {
|
|
80
|
+
// 1. 先裁剪为正方形
|
|
81
|
+
const squarePm = await CircleImageProcessor.cropToSquare(sourcePm, cropRegion);
|
|
82
|
+
|
|
83
|
+
// 2. 转换为圆形
|
|
84
|
+
const circlePm = await CircleImageProcessor.createTrueCircleImage(squarePm);
|
|
85
|
+
|
|
86
|
+
// 3. 清理临时资源
|
|
87
|
+
squarePm.release();
|
|
88
|
+
|
|
89
|
+
return circlePm;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 裁剪为正方形
|
|
94
|
+
*/
|
|
95
|
+
private static async cropToSquare(sourcePm: PixelMap, region: image.Region): Promise<PixelMap> {
|
|
96
|
+
// 确保是正方形区域
|
|
97
|
+
const squareSize = Math.min(region.size.width, region.size.height);
|
|
98
|
+
const squareRegion: image.Region = {
|
|
99
|
+
x: region.x + (region.size.width - squareSize) / 2,
|
|
100
|
+
y: region.y + (region.size.height - squareSize) / 2,
|
|
101
|
+
size: { width: squareSize, height: squareSize }
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// 深拷贝PixelMap然后裁剪
|
|
105
|
+
const copyPm = await CircleImageProcessor.copyPixelMap(sourcePm);
|
|
106
|
+
copyPm.cropSync(squareRegion);
|
|
107
|
+
return copyPm;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 深拷贝PixelMap
|
|
112
|
+
*/
|
|
113
|
+
private static async copyPixelMap(pm: PixelMap): Promise<PixelMap> {
|
|
114
|
+
const imageInfo: image.ImageInfo = await pm.getImageInfo();
|
|
115
|
+
const buffer: ArrayBuffer = new ArrayBuffer(pm.getPixelBytesNumber());
|
|
116
|
+
await pm.readPixelsToBuffer(buffer);
|
|
117
|
+
|
|
118
|
+
const opts: image.InitializationOptions = {
|
|
119
|
+
editable: true,
|
|
120
|
+
pixelFormat: image.PixelMapFormat.BGRA_8888,
|
|
121
|
+
size: { height: imageInfo.size.height, width: imageInfo.size.width }
|
|
122
|
+
};
|
|
123
|
+
return image.createPixelMap(buffer, opts);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -11,4 +11,8 @@ export class Constants {
|
|
|
11
11
|
static readonly IMAGE_FORMAT: string = '.jpg'
|
|
12
12
|
static readonly IMAGE_PREFIX: string = 'image';
|
|
13
13
|
static readonly ENCODE_FILE_PERMISSION: string = 'rw'
|
|
14
|
+
|
|
15
|
+
// 添加png支持
|
|
16
|
+
static readonly ENCODE_FORMAT_PNG: string = 'image/png'
|
|
17
|
+
static readonly IMAGE_FORMAT_PNG: string = '.png'
|
|
14
18
|
}
|
|
@@ -10,7 +10,6 @@ import util from '@ohos.util';
|
|
|
10
10
|
|
|
11
11
|
const TAG: string = 'imageEdit_Encode';
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
export async function encode(component: Object, pixelMap: ESObject) : Promise<string> {
|
|
15
14
|
let imgPath: string = '';
|
|
16
15
|
const newPixelMap: ESObject = pixelMap;
|
|
@@ -20,14 +19,32 @@ export async function encode(component: Object, pixelMap: ESObject) : Promise<st
|
|
|
20
19
|
quality: Constants.ENCODE_QUALITY
|
|
21
20
|
}
|
|
22
21
|
let packerData = await imagePackerApi.packing(newPixelMap, packOptions);
|
|
23
|
-
Logger.info(TAG, 'into compressPictures data: ' + JSON.stringify(packerData));
|
|
24
22
|
const context = getContext(component);
|
|
25
23
|
imgPath = context.tempDir + '/rn_image_crop_picker_lib_temp_' + util.generateRandomUUID(true) + Constants.IMAGE_FORMAT;
|
|
26
24
|
let newFile = fs.openSync(imgPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
|
|
27
|
-
Logger.info(TAG, 'into compressPictures newFile id: ' + newFile.fd);
|
|
28
25
|
const number = fs.writeSync(newFile.fd, packerData);
|
|
29
|
-
Logger.info(TAG, 'into compressPictures write data to file succeed size: ' + number);
|
|
30
26
|
fs.closeSync(newFile.fd);
|
|
31
27
|
imagePackerApi.release();
|
|
32
28
|
return imgPath;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// PNG编码函数
|
|
32
|
+
export async function encodeToPng(component: Object, pixelMap: ESObject): Promise<string> {
|
|
33
|
+
let imgPath: string = ''
|
|
34
|
+
const newPixelMap: ESObject = pixelMap
|
|
35
|
+
const imagePackerApi = image.createImagePacker()
|
|
36
|
+
const packOptions: image.PackingOption = {
|
|
37
|
+
format: Constants.ENCODE_FORMAT_PNG, // 使用PNG格式
|
|
38
|
+
quality: Constants.ENCODE_QUALITY // PNG质量参数可能无效,但保留
|
|
39
|
+
}
|
|
40
|
+
let packerData = await imagePackerApi.packing(newPixelMap, packOptions)
|
|
41
|
+
Logger.info(TAG, 'into PNG encode data size: ' + packerData.byteLength)
|
|
42
|
+
const context = getContext(component)
|
|
43
|
+
imgPath = context.tempDir + '/circle_image_' + util.generateRandomUUID(true) + Constants.IMAGE_FORMAT_PNG
|
|
44
|
+
let newFile = fs.openSync(imgPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE)
|
|
45
|
+
const number = fs.writeSync(newFile.fd, packerData)
|
|
46
|
+
Logger.info(TAG, 'PNG file saved: ' + imgPath + ', size: ' + number)
|
|
47
|
+
fs.closeSync(newFile.fd)
|
|
48
|
+
imagePackerApi.release()
|
|
49
|
+
return imgPath
|
|
33
50
|
}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-ohos/react-native-image-crop-picker",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.5-rc.11",
|
|
4
4
|
"description": "Select single or multiple images, with cropping option",
|
|
5
5
|
"main": "js/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -40,10 +40,16 @@
|
|
|
40
40
|
"react-native": ">=0.40.0"
|
|
41
41
|
},
|
|
42
42
|
"harmony": {
|
|
43
|
-
"alias": "react-native-image-crop-picker"
|
|
43
|
+
"alias": "react-native-image-crop-picker",
|
|
44
|
+
"autolinking": {
|
|
45
|
+
"etsPackageClassName": "ImageCropPickerPackage",
|
|
46
|
+
"cppPackageClassName": "ImageCropPickerPackage",
|
|
47
|
+
"cmakeLibraryTargetName": "rnoh_image_crop_picker",
|
|
48
|
+
"ohPackageName": "@react-native-ohos/react-native-image-crop-picker"
|
|
49
|
+
}
|
|
44
50
|
},
|
|
45
51
|
"publishConfig": {
|
|
46
52
|
"registry": "https://registry.npmjs.org/",
|
|
47
53
|
"access": "public"
|
|
48
54
|
}
|
|
49
|
-
}
|
|
55
|
+
}
|
|
File without changes
|