@react-native-ohos/react-native-image-crop-picker 0.50.2-rc.3 → 0.50.2-rc.5

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 CHANGED
@@ -1,7 +1,129 @@
1
1
  # Changelog
2
- ## 0.50.2-rc.3
3
- pre-release version 0.50.2-rc.3
4
2
 
5
- ## v0.50.1
6
- ## 更新内容
7
- * feat: add OpenHarmony support for react-native-image-crop-picker
3
+ ## 鸿蒙化Log
4
+
5
+ ### v0.50.2-rc.2
6
+
7
+ - Fix the issue where the file size after compression is larger than the original file.
8
+
9
+ ### v0.50.1
10
+
11
+ - react-native-image-crop-picker仓库迁移 ([e09dff16cc4c51bfdcec842921d3c30d49d39a19](https://gitee.com/openharmony-sig/rntpc_react-native-image-crop-picker/pulls/1))
12
+ - chore: add COMMITTERS.md ([6e71f31b17e5aba5a60215b58ddc6f656def74f9](https://gitee.com/openharmony-sig/rntpc_react-native-image-crop-picker/pulls/2))
13
+ - pre-release: @react-native-ohos/react-native-image-crop-picker@0.50.2-rc.2([db47a7fb5b3960c0e05a93f83edb13a5113eade2](https://gitee.com/openharmony-sig/rntpc_react-native-image-crop-picker/pulls/4))
14
+
15
+ ## ReleaseLog
16
+
17
+ ### v0.50.0
18
+
19
+ #### What's Changed
20
+
21
+ - fix: Resolve OutOfMemoryError in image/video processing by [@GeeksEra](https://github.com/GeeksEra) in [#2137](https://github.com/ivpusic/react-native-image-crop-picker/pull/2137)
22
+ - Update ImageCropPicker.m by [@uranashel44](https://github.com/uranashel44) in [#2129](https://github.com/ivpusic/react-native-image-crop-picker/pull/2129)
23
+ - [BugFix - ios] Keep selection order by [@sean5940](https://github.com/sean5940) in [#2099](https://github.com/ivpusic/react-native-image-crop-picker/pull/2099)
24
+ - Fix the issure in Android where the photo picker does not support a maxFiles selection limit. by [@xgtz421](https://github.com/xgtz421) in [#2147](https://github.com/ivpusic/react-native-image-crop-picker/pull/2147)
25
+ - If the "maxFiles" keyword is missing, assign a default value of 5 to align with the documentation. by [@xgtz421](https://github.com/xgtz421) in [#2148](https://github.com/ivpusic/react-native-image-crop-picker/pull/2148)
26
+ - New Architecture support by [@ivpusic](https://github.com/ivpusic) in [#2171](https://github.com/ivpusic/react-native-image-crop-picker/pull/2171)
27
+
28
+ #### New Contributors
29
+
30
+ - [@GeeksEra](https://github.com/GeeksEra) made their first contribution in [#2137](https://github.com/ivpusic/react-native-image-crop-picker/pull/2137)
31
+ - [@uranashel44](https://github.com/uranashel44) made their first contribution in [#2129](https://github.com/ivpusic/react-native-image-crop-picker/pull/2129)
32
+ - [@xgtz421](https://github.com/xgtz421) made their first contribution in [#2147](https://github.com/ivpusic/react-native-image-crop-picker/pull/2147)
33
+
34
+ ### v0.42.0
35
+
36
+ #### What's Changed
37
+
38
+ - Implement the new official Android Photo Picker by [@Pauligrinder](https://github.com/Pauligrinder) in [#2093](https://github.com/ivpusic/react-native-image-crop-picker/pull/2093)
39
+
40
+ #### New Contributors
41
+
42
+ - [@Pauligrinder](https://github.com/Pauligrinder) made their first contribution in [#2093](https://github.com/ivpusic/react-native-image-crop-picker/pull/2093)
43
+
44
+ ### v0.41.6
45
+
46
+ #### What's Changed
47
+
48
+ - Enhancement:: Add: Filename in response object for Android by [@AmeyShrivastava](https://github.com/AmeyShrivastava) in [#1945](https://github.com/ivpusic/react-native-image-crop-picker/pull/1945)
49
+ - Fix openCamera reported dimensions for portrait photos on Android by [@gaearon](https://github.com/gaearon) in [#2110](https://github.com/ivpusic/react-native-image-crop-picker/pull/2110)
50
+
51
+ #### New Contributors
52
+
53
+ - [@AmeyShrivastava](https://github.com/AmeyShrivastava) made their first contribution in [#1945](https://github.com/ivpusic/react-native-image-crop-picker/pull/1945)
54
+ - [@gaearon](https://github.com/gaearon) made their first contribution in [#2110](https://github.com/ivpusic/react-native-image-crop-picker/pull/2110)
55
+
56
+ ### v0.41.5
57
+
58
+ #### What's Changed
59
+
60
+ - fix: [iOS] Image is being resized up instead of down & Error "User did not grant library permission." in Android 10 by [@xhirazi](https://github.com/xhirazi) in [#2103](https://github.com/ivpusic/react-native-image-crop-picker/pull/2103)
61
+
62
+ #### New Contributors
63
+
64
+ - [@xhirazi](https://github.com/xhirazi) made their first contribution in [#2103](https://github.com/ivpusic/react-native-image-crop-picker/pull/2103)
65
+
66
+ ### v0.41.4
67
+
68
+ #### What's Changed
69
+
70
+ - Update example project by [@sean5940](https://github.com/sean5940) in [#2097](https://github.com/ivpusic/react-native-image-crop-picker/pull/2097)
71
+
72
+ #### New Contributors
73
+
74
+ - [@sean5940](https://github.com/sean5940) made their first contribution in [#2097](https://github.com/ivpusic/react-native-image-crop-picker/pull/2097)
75
+
76
+ ### v0.41.3
77
+
78
+ #### What's Changed
79
+
80
+ - feat(android):RN-0.73 and AGP 8.0 Compatibility by [@dishantwalia](https://github.com/dishantwalia) in [#2018](https://github.com/ivpusic/react-native-image-crop-picker/pull/2018)
81
+
82
+ #### New Contributors
83
+
84
+ - [@dishantwalia](https://github.com/dishantwalia) made their first contribution in [#2018](https://github.com/ivpusic/react-native-image-crop-picker/pull/2018)
85
+
86
+ ### v0.41.2
87
+
88
+ #### What's Changed
89
+
90
+ - Fixes an issue that would cause the compiler to crash in Xcode 16 beta1 by [@isnine](https://github.com/isnine) in [#2068](https://github.com/ivpusic/react-native-image-crop-picker/pull/2068)
91
+
92
+ #### New Contributors
93
+
94
+ - [@isnine](https://github.com/isnine) made their first contribution in [#2068](https://github.com/ivpusic/react-native-image-crop-picker/pull/2068)
95
+
96
+ ### v0.41.1
97
+
98
+ #### What's Changed
99
+
100
+ - URGENT: Updated RNImageCropPicker.podspec by [@ShivamKJJW](https://github.com/ShivamKJJW) in [#2056](https://github.com/ivpusic/react-native-image-crop-picker/pull/2056)
101
+ - https://github.com/ivpusic/react-native-image-crop-picker/pull/2068)
102
+
103
+ #### New Contributors
104
+
105
+ - [@ShivamKJJW](https://github.com/ShivamKJJW) made their first contribution in [#2056](https://github.com/ivpusic/react-native-image-crop-picker/pull/2056)
106
+
107
+ ### v0.41.0
108
+
109
+ #### What's Changed
110
+
111
+ - fix(ios): replace `UIGraphicsBeginImageContext` in Compression.m by [@remarkablemark](https://github.com/remarkablemark) in [#2055](https://github.com/ivpusic/react-native-image-crop-picker/pull/2055)
112
+ - Add Privacy manifest by [@ujeon](https://github.com/ujeon) in [#2045](https://github.com/ivpusic/react-native-image-crop-picker/pull/2045)
113
+ - add Dutch (nl) as a translation for iOS by [@HostenMarijn](https://github.com/HostenMarijn) in [#1918](https://github.com/ivpusic/react-native-image-crop-picker/pull/1918)
114
+
115
+ #### New Contributors
116
+
117
+ - [@remarkablemark](https://github.com/remarkablemark) made their first contribution in [#2055](https://github.com/ivpusic/react-native-image-crop-picker/pull/2055)
118
+ - [@ujeon](https://github.com/ujeon) made their first contribution in [#2045](https://github.com/ivpusic/react-native-image-crop-picker/pull/2045)
119
+ - [@HostenMarijn](https://github.com/HostenMarijn) made their first contribution in [#1918](https://github.com/ivpusic/react-native-image-crop-picker/pull/1918)
120
+
121
+ ### v0.40.3
122
+
123
+ #### What's Changed
124
+
125
+ - Make list items accessible by [@v-miibr](https://github.com/v-miibr) in [#1439](https://github.com/ivpusic/react-native-image-crop-picker/pull/1439)
126
+
127
+ #### New Contributors
128
+
129
+ - [@v-miibr](https://github.com/v-miibr) made their first contribution in [#1439](https://github.com/ivpusic/react-native-image-crop-picker/pull/1439)
@@ -5,4 +5,5 @@
5
5
  */
6
6
 
7
7
  export { ImageEditInfo } from "./src/main/ets/pages/ImageEditInfo"
8
+ export { CircleImageInfo } from "./src/main/ets/pages/CircleImageInfo"
8
9
  export * from "./ts";
@@ -3,7 +3,7 @@
3
3
  "description": "Please describe the basic information.",
4
4
  "main": "index.ets",
5
5
  "type": "module",
6
- "version": "0.50.2-rc.3",
6
+ "version": "0.50.2-rc.5",
7
7
  "dependencies": {
8
8
  "@rnoh/react-native-openharmony": "file:../../node_modules/@react-native-oh/react-native-harmony/harmony/react_native_openharmony.har"
9
9
  },
@@ -443,8 +443,8 @@ export class ImageCropPickerTurboModule extends TurboModule implements ImageCrop
443
443
  results.size = length;
444
444
  results.creationDate = stat.ctime + '';
445
445
  results.modificationDate = stat.mtime + '';
446
- results.path = this.isNullOrUndefined(tempFilePaths) ? null : filePrefix + value;
447
446
  if (this.isImage(value)) {
447
+ results.path = this.isNullOrUndefined(tempFilePaths) ? null : filePrefix + value;
448
448
  results.data = includeBase64 ? this.imageToBase64(value) : null;
449
449
  results.mime = 'image/' + imageType;
450
450
  Logger.info(`${TAG} into openPickerResult value : ${value}`);
@@ -463,6 +463,12 @@ export class ImageCropPickerTurboModule extends TurboModule implements ImageCrop
463
463
  results.duration = null;
464
464
  } else {
465
465
  Logger.info(`${TAG} into getPickerResult video start`);
466
+ let qualityNumber = this.isNullOrUndefined(options.compressImageQuality) ? ImageQuality : options.compressImageQuality;
467
+ if(qualityNumber !== 1){
468
+ results.path = this.isNullOrUndefined(tempFilePaths) ? null : value;
469
+ } else {
470
+ results.path = this.isNullOrUndefined(tempFilePaths) ? null : filePrefix + value;
471
+ }
466
472
  results.data = null;
467
473
  results.mime = 'video/' + imageType;
468
474
  let url = 'fd://' + file.fd;
@@ -919,6 +925,7 @@ export class ImageCropPickerTurboModule extends TurboModule implements ImageCrop
919
925
  const showCropGuidelines: boolean = this.isNullOrUndefined(options?.showCropGuidelines) ? true : options?.showCropGuidelines;
920
926
  const showCropFrame: boolean = this.isNullOrUndefined(options?.showCropFrame) ? true : options?.showCropFrame;
921
927
  const freeStyleCropEnabled: boolean = this.isNullOrUndefined(options?.freeStyleCropEnabled) ? false : options?.freeStyleCropEnabled;
928
+ const cropperCircleOverlay: boolean = this.isNullOrUndefined(options?.cropperCircleOverlay) ? false : options?.cropperCircleOverlay;
922
929
  const cropperRotate: string = options?.cropperRotateButtonsHidden + '';
923
930
  AppStorage.setOrCreate('initWidth', initWidth);
924
931
  AppStorage.setOrCreate('initHeight', initHeight);
@@ -932,6 +939,7 @@ export class ImageCropPickerTurboModule extends TurboModule implements ImageCrop
932
939
  AppStorage.setOrCreate('showCropGuidelines', showCropGuidelines);
933
940
  AppStorage.setOrCreate('showCropFrame', showCropFrame);
934
941
  AppStorage.setOrCreate('freeStyleCropEnabled', freeStyleCropEnabled);
942
+ AppStorage.setOrCreate('cropperCircleOverlay', cropperCircleOverlay);
935
943
 
936
944
  try {
937
945
  let want: Want = {
@@ -0,0 +1,789 @@
1
+ import { image } from '@kit.ImageKit';
2
+ import Matrix4 from '@ohos.matrix4'
3
+ import fs from '@ohos.file.fs';
4
+ import router from '@ohos.router';
5
+ import display from '@ohos.display';
6
+ import { Constants } from '../utils/Constants';
7
+ import { getPixelMap } from '../utils/DecodeAndEncodeUtil';
8
+ import { RotateType } from '../viewmodel/viewAndModel';
9
+ import { encodeToPng } from '../utils/EncodeUtil';
10
+ import Logger from '../Logger';
11
+ import { CircleImageProcessor } from '../utils/CircleImageProcessor';
12
+
13
+ const TAG: string = 'CircleImageEditInfo';
14
+
15
+ @Extend(Row)
16
+ function bottomIconStyle() {
17
+ .size({ width: '20%', height: '100%' })
18
+ .justifyContent(FlexAlign.Center)
19
+ }
20
+
21
+ @Extend(Text)
22
+ function textStyle() {
23
+ .size({ width: '20%', height: '100%' })
24
+ .textAlign(TextAlign.Center)
25
+ }
26
+
27
+ @Extend(Image)
28
+ function iconStyle() {
29
+ .size({ width: 24, height: 24 })
30
+ .fillColor('#FFFFFF')
31
+ .opacity(0.9)
32
+ }
33
+
34
+ @Component
35
+ export struct CropView {
36
+ @StorageProp('filePath') filePath: string = '';
37
+ @StorageProp('cropperRotate') cropperRotate: string = '';
38
+ @StorageProp('initWidth') initWidth: number = 0;
39
+ @StorageProp('initHeight') initHeight: number = 0;
40
+ @StorageProp('textTitle') title: string = '';
41
+ @StorageProp('cancelText') cancel: string = '';
42
+ @StorageProp('cancelTextColor') cancelTextColor: string = '';
43
+ @StorageProp('chooseText') choose: string = '';
44
+ @StorageProp('chooseTextColor') chooseTextColor: string = '';
45
+ @StorageProp('freeStyleCropEnabled') freeStyleCropEnabled: boolean = false;
46
+ @StorageProp('cropperCircleOverlay') cropperCircleOverlay: boolean = false;
47
+ @StorageProp('enableRotationGesture') enableRotationGesture: boolean = false;
48
+ @State private model: CropModel = new CropModel();
49
+ private settings: RenderingContextSettings = new RenderingContextSettings(true);
50
+ private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
51
+ @State pm: PixelMap | undefined = undefined;
52
+ @State private matrix: object = Matrix4.identity()
53
+ .translate({ x: 0, y: 0 })
54
+ .scale({ x: this.model.scale, y: this.model.scale });
55
+ @State screenWidth: number = 0;
56
+ @State screenHeight: number = 0;
57
+ @State imageWidth: number = 0;
58
+ @State imageHeight: number = 0;
59
+ @State angle: number = 0;
60
+ @State gestureAngle: number = 0
61
+ private tempScale = 1;
62
+ private startOffsetX: number = 0;
63
+ private startOffsetY: number = 0;
64
+ private initialAngle: number = 0;
65
+ private initialScale: number = 1;
66
+ private initialOffsetX: number = 0;
67
+ private initialOffsetY: number = 0;
68
+ private initialImageWidth: number = 0;
69
+ private initialImageHeight: number = 0;
70
+ private isImageAnimation: boolean = true
71
+ private isStackAnimation: boolean = true
72
+ @StorageProp('bottomRectHeight')
73
+ bottomRectHeight: number = 0;
74
+ @StorageProp('topRectHeight')
75
+ topRectHeight: number = 0;
76
+
77
+ aboutToAppear(): void {
78
+ this.screenHeight = this.getUIContext().px2vp(display.getDefaultDisplaySync().height);
79
+ this.filePath = AppStorage.Get('filePath') || ''
80
+ this.model.setImage(this.filePath)
81
+ getPixelMap(this.filePath)
82
+ .then((pixelMap: image.PixelMap) => {
83
+ if (pixelMap) {
84
+ this.model.src = pixelMap;
85
+ pixelMap.getImageInfo().then((imageInfo) => {
86
+ this.imageHeight = imageInfo.size == undefined ? 0 : imageInfo.size?.height;
87
+ this.imageWidth = imageInfo.size == undefined ? 0 : imageInfo.size?.width;
88
+ this.initialImageWidth = this.imageWidth;
89
+ this.initialImageHeight = this.imageHeight;
90
+ })
91
+ }
92
+ })
93
+ }
94
+
95
+ @Builder
96
+ CropTitleBar() {
97
+ Text(this.title)
98
+ .fontColor(Color.White)
99
+ .fontSize(20)
100
+ .textAlign(TextAlign.Center)
101
+ .width('100%')
102
+ .height(46)
103
+ .position({ x: 0, y: 20 })
104
+ .backgroundColor(Color.Black)
105
+ }
106
+
107
+ @Builder
108
+ BottomToolbar() {
109
+ Row() {
110
+ Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center }) {
111
+ Text(this.cancel)
112
+ .textStyle()
113
+ .fontColor(this.cancelTextColor)
114
+ .onClick(async () => {
115
+ router.back()
116
+ });
117
+ Row() {
118
+ Image($r('app.media.ic_anti_clockwise'))
119
+ .iconStyle()
120
+ .visibility(this.cropperRotate === 'true' ? Visibility.Hidden : Visibility.Visible)
121
+ .onClick(async () => {
122
+ this.rotateImage(RotateType.ANTI_CLOCK);
123
+ });
124
+ }
125
+ .bottomIconStyle();
126
+
127
+ Row() {
128
+ Image($r('app.media.ic_reset'))
129
+ .iconStyle()
130
+ .onClick(() => {
131
+ this.resetAllTransformations();
132
+ });
133
+ }
134
+ .bottomIconStyle();
135
+
136
+ Row() {
137
+ Image($r('app.media.ic_clockwise'))
138
+ .iconStyle()
139
+ .visibility(this.cropperRotate === 'true' ? Visibility.Hidden : Visibility.Visible)
140
+ .onClick(() => {
141
+ this.rotateImage(RotateType.CLOCKWISE);
142
+ });
143
+ }
144
+ .bottomIconStyle();
145
+
146
+ Text(this.choose)
147
+ .textStyle()
148
+ .fontColor(this.chooseTextColor)
149
+ .onClick(async () => {
150
+ if (this.model.src) {
151
+ try {
152
+ // 进行普通的正方形裁剪
153
+ const squarePm = await this.model.crop(image.PixelMapFormat.RGBA_8888);
154
+
155
+ // 对裁剪后的图片应用旋转
156
+ const normalizedAngle = ((this.angle % 360) + 360) % 360;
157
+ if (normalizedAngle !== 0) {
158
+ await squarePm.rotate(normalizedAngle);
159
+ }
160
+
161
+ // 转换为真正的圆形图片
162
+ const circlePm = await CircleImageProcessor.createTrueCircleImage(squarePm);
163
+
164
+ // 保存为PNG格式(支持透明度)
165
+ let imgPath = await encodeToPng(this, circlePm);
166
+
167
+ // 清理资源
168
+ squarePm.release();
169
+ circlePm.release();
170
+
171
+ // 保存路径
172
+ AppStorage.setOrCreate('cropImagePath', imgPath);
173
+ AppStorage.setOrCreate('isCircleImage', true);
174
+ router.back()
175
+ } catch (error) {
176
+ Logger.error(TAG, "Circle crop failed:" + JSON.stringify(error));
177
+ }
178
+ }
179
+ })
180
+ }
181
+ .width('100%')
182
+ .height(56)
183
+ .backgroundColor(Color.Black)
184
+ .margin({ bottom: 30 })
185
+ }
186
+ .width('100%')
187
+ .height(56)
188
+ .position({ x: 0, y: (this.screenHeight - 66) })
189
+ .backgroundColor(Color.Black)
190
+ }
191
+
192
+ build() {
193
+ Stack() {
194
+ Stack() {
195
+ Image(this.model.path)
196
+ .width('100%')
197
+ .height('100%')
198
+ .alt(this.model.previewSource)
199
+ .objectFit(ImageFit.Contain)
200
+ .transform(this.matrix)
201
+ .animation({ duration: this.isImageAnimation ? 300 : 0 })
202
+ .onComplete((msg) => {
203
+ if (msg) {
204
+ // 图片加载成功
205
+ this.model.imageWidth = msg.width;
206
+ this.model.imageHeight = msg.height;
207
+ this.model.componentWidth = msg.componentWidth;
208
+ this.model.componentHeight = msg.componentHeight;
209
+ this.saveInitialState();
210
+ this.checkImageAdapt();
211
+ if (this.model.imageLoadEventListener != null && msg.loadingStatus == 1) {
212
+ this.model.imageLoadEventListener.onImageLoaded(msg);
213
+ }
214
+ }
215
+ })
216
+ .onError((error) => {
217
+ if (this.model.imageLoadEventListener != null) {
218
+ this.model.imageLoadEventListener.onImageLoadError(error);
219
+ }
220
+ })
221
+ }
222
+ .rotate({ angle: this.angle })
223
+ .width('100%')
224
+ .height('100%')
225
+ .animation({ duration: this.isStackAnimation ? 300 : 0 })
226
+ .priorityGesture(
227
+ TapGesture({ count: 2, fingers: 1 })
228
+ .onAction((event: GestureEvent) => {
229
+ if (!event) {
230
+ return
231
+ }
232
+ if (this.model.zoomEnabled) {
233
+ if (this.model.scale != 1) {
234
+ this.model.scale = 1;
235
+ this.updateMatrix();
236
+ } else {
237
+ this.zoomTo(2);
238
+ }
239
+ }
240
+
241
+ this.checkImageAdapt();
242
+ })
243
+ )
244
+ .gesture(
245
+ GestureGroup(GestureMode.Parallel,
246
+ // 拖动手势
247
+ PanGesture({})
248
+ .onActionStart(() => {
249
+ Logger.info(TAG, "CropView Pan gesture start");
250
+ this.startOffsetX = this.model.offsetX;
251
+ this.startOffsetY = this.model.offsetY;
252
+ this.isImageAnimation = false
253
+ })
254
+ .onActionUpdate((event: GestureEvent) => {
255
+ Logger.info(TAG, "CropView Pan gesture update" + JSON.stringify(event));
256
+ if (event) {
257
+ if (this.model.panEnabled) {
258
+ let distanceX: number = this.startOffsetX + vp2px(event.offsetX) / this.model.scale;
259
+ let distanceY: number = this.startOffsetY + vp2px(event.offsetY) / this.model.scale;
260
+ this.model.offsetX = distanceX;
261
+ this.model.offsetY = distanceY;
262
+ this.updateMatrix()
263
+ }
264
+ }
265
+ })
266
+ .onActionEnd(() => {
267
+ Logger.info(TAG, "CropView Pan gesture end");
268
+ this.checkImageAdapt();
269
+ this.isImageAnimation = true
270
+ }),
271
+ // 缩放手势
272
+ PinchGesture({ fingers: 2 })
273
+ .onActionStart(() => {
274
+ this.isStackAnimation = false
275
+ this.tempScale = this.model.scale
276
+ })
277
+ .onActionUpdate((event) => {
278
+ if (event) {
279
+ if (!this.model.zoomEnabled) {
280
+ return;
281
+ }
282
+ this.zoomTo(Math.min(5.0, Math.max(0.1, (this.tempScale * event.scale))))
283
+ }
284
+ })
285
+ .onActionEnd((event) => {
286
+ this.checkImageAdapt()
287
+ this.updateMatrix()
288
+ this.isStackAnimation = true
289
+ }),
290
+ //旋转手势
291
+ RotationGesture({})
292
+ .onActionStart(() => {
293
+ if (this.enableRotationGesture) {
294
+ // 记录手势起始
295
+ this.gestureAngle = this.angle
296
+ // 关闭动画避免旋转错误
297
+ this.isStackAnimation = false
298
+ }
299
+ })
300
+ .onActionUpdate((event: GestureEvent) => {
301
+ if (this.enableRotationGesture) {
302
+ let normalizedAngle = event.angle
303
+ if (normalizedAngle < 0) {
304
+ normalizedAngle += 360
305
+ }
306
+ this.angle = this.gestureAngle + normalizedAngle
307
+ }
308
+ })
309
+ .onActionEnd(() => {
310
+ if (this.enableRotationGesture) {
311
+ this.isStackAnimation = false
312
+ const tmp = this.angle % 360
313
+ this.angle = tmp
314
+ let time = setTimeout(() => {
315
+ this.isStackAnimation = true
316
+ this.angle = closestAngle
317
+ this.updateMatrix()
318
+ this.checkImageAdapt()
319
+ clearTimeout(time)
320
+ }, 50)
321
+
322
+ // 计算最近的吸附角度
323
+ const snapAngles = [0, 90, 180, 270, 360]
324
+ let closestAngle = snapAngles[0]
325
+ let minDiff = Infinity
326
+
327
+ for (const snapAngle of snapAngles) {
328
+ const diff = Math.abs(tmp - snapAngle)
329
+ if (diff < minDiff) {
330
+ minDiff = diff
331
+ closestAngle = snapAngle
332
+ }
333
+ }
334
+ }
335
+ })
336
+ )
337
+ )
338
+
339
+ Canvas(this.context)
340
+ .width('100%')
341
+ .height('100%')
342
+ .backgroundColor(Color.Transparent)
343
+ .onReady(() => {
344
+ if (this.context == null) {
345
+ return
346
+ }
347
+ let height = this.context.height
348
+ let width = this.context.width
349
+ this.context.fillStyle = this.model.maskColor;
350
+ this.context.fillRect(0, 0, width, height)
351
+ let centerX = width / 2;
352
+ let centerY = height / 2;
353
+ this.context.globalCompositeOperation = 'destination-out'
354
+ this.context.fillStyle = 'white'
355
+ let frameWidthInVp = px2vp(this.model.frameWidth);
356
+ let frameHeightInVp = px2vp(this.model.getFrameHeight());
357
+ this.context.beginPath();
358
+ this.context.arc(centerX, centerY, px2vp(this.model.frameWidth / 2), 0, 2 * Math.PI);
359
+ this.context.fill();
360
+ this.context.globalCompositeOperation = 'source-over';
361
+ this.context.strokeStyle = this.model.strokeColor;
362
+ let radius = Math.min(frameWidthInVp, frameHeightInVp) / 2;
363
+ this.context.beginPath();
364
+ this.context.arc(centerX, centerY, radius, 0, 2 * Math.PI);
365
+ this.context.closePath();
366
+ this.context.lineWidth = 1;
367
+ this.context.stroke();
368
+ })
369
+ .enabled(false)
370
+
371
+ this.CropTitleBar()
372
+
373
+ this.BottomToolbar()
374
+ }
375
+ .backgroundColor(Color.Black)
376
+ }
377
+
378
+ /**
379
+ * 检查手势操作后,图片是否填满取景框,没填满则进行调整
380
+ */
381
+ private checkImageAdapt() {
382
+ let offsetX = this.model.offsetX;
383
+ let offsetY = this.model.offsetY;
384
+ let scale = this.model.scale;
385
+ Logger.info(TAG, "CropView offsetX: " + offsetX + ", offsetY: " + offsetY + ", scale: " + scale);
386
+
387
+ // 图片适配控件的时候也进行了缩放,计算出这个缩放比例
388
+ let widthScale = this.model.componentWidth / this.model.imageWidth;
389
+ let heightScale = this.model.componentHeight / this.model.imageHeight;
390
+ let adaptScale = Math.min(widthScale, heightScale);
391
+ Logger.info(TAG,
392
+ "CropView Image scale: " + adaptScale + "while attaching the component[" + this.model.componentWidth + ", " +
393
+ this.model.componentHeight);
394
+
395
+ // 经过两次缩放(适配控件、手势)后,图片的实际显示大小
396
+ let showWidth = this.model.imageWidth * adaptScale * this.model.scale;
397
+ let showHeight = this.model.imageHeight * adaptScale * this.model.scale;
398
+ let imageX = (this.model.componentWidth - showWidth) / 2;
399
+ let imageY = (this.model.componentHeight - showHeight) / 2;
400
+ Logger.info(TAG, "CropView Image left top is: (" + imageX + ", " + imageY + ")");
401
+
402
+ // 取景框的左上角坐标
403
+ let frameX = (this.model.componentWidth - this.model.frameWidth) / 2;
404
+ let frameY = (this.model.componentHeight - this.model.getFrameHeight()) / 2;
405
+
406
+ // 图片左上角坐标
407
+ let showX = imageX + offsetX * scale;
408
+ let showY = imageY + offsetY * scale;
409
+ Logger.info(TAG, "CropView Image show at (" + showX + ", " + showY + ")");
410
+
411
+ if (this.model.frameWidth > showWidth || this.model.getFrameHeight() > showHeight) { // 图片缩放后,大小不足以填满取景框
412
+ let xScale = this.model.frameWidth / showWidth;
413
+ let yScale = this.model.getFrameHeight() / showHeight;
414
+ let newScale = Math.max(xScale, yScale);
415
+ this.model.scale = this.model.scale * newScale;
416
+ showX *= newScale;
417
+ showY *= newScale;
418
+ }
419
+
420
+ // 调整x轴方向位置,使图像填满取景框
421
+ if (showX > frameX) {
422
+ showX = frameX;
423
+ } else if (showX + showWidth < frameX + this.model.frameWidth) {
424
+ showX = frameX + this.model.frameWidth - showWidth;
425
+ }
426
+ // 调整y轴方向位置,使图像填满取景框
427
+ if (showY > frameY) {
428
+ showY = frameY;
429
+ } else if (showY + showHeight < frameY + this.model.getFrameHeight()) {
430
+ showY = frameY + this.model.getFrameHeight() - showHeight;
431
+ }
432
+ this.model.offsetX = (showX - imageX) / scale;
433
+ this.model.offsetY = (showY - imageY) / scale;
434
+ this.updateMatrix();
435
+ }
436
+
437
+ public zoomTo(scale: number): void {
438
+ this.model.scale = scale;
439
+ this.updateMatrix();
440
+ }
441
+
442
+ public updateMatrix(): void {
443
+ this.matrix = Matrix4.identity()
444
+ .translate({ x: this.model.offsetX, y: this.model.offsetY })
445
+ .scale({ x: this.model.scale, y: this.model.scale })
446
+ }
447
+
448
+ public rotateImage(rotateType: RotateType) {
449
+ Logger.info(TAG, "into rotateImage rotateType : " + rotateType)
450
+ if (rotateType === RotateType.CLOCKWISE) {
451
+ if (!this.model.src) {
452
+ Logger.info(TAG, "into rotateImage return")
453
+ return;
454
+ }
455
+ try {
456
+ this.model.src.rotate(Constants.CLOCK_WISE)
457
+ .then(() => {
458
+ this.angle = this.angle + Constants.CLOCK_WISE;
459
+ Logger.info(TAG, `into rotateImage Constants.CLOCK_WISE return ${this.angle}`,)
460
+ })
461
+ } catch (error) {
462
+ }
463
+ }
464
+ if (rotateType === RotateType.ANTI_CLOCK) {
465
+ if (!this.model.src) {
466
+ Logger.info(TAG, "into rotateImage return")
467
+ return;
468
+ }
469
+ try {
470
+ this.model.src.rotate(Constants.ANTI_CLOCK)
471
+ .then(() => {
472
+ this.angle = this.angle + Constants.ANTI_CLOCK;
473
+ Logger.info(TAG, `into rotateImage Constants.ANTI_CLOCK return ${this.angle}`,)
474
+ })
475
+ } catch (error) {
476
+ }
477
+ }
478
+ }
479
+
480
+ // 保存初始状态方法
481
+ private saveInitialState(): void {
482
+ this.initialAngle = this.angle;
483
+ this.initialScale = this.model.scale;
484
+ this.initialOffsetX = this.model.offsetX;
485
+ this.initialOffsetY = this.model.offsetY;
486
+ Logger.info(TAG, "Initial state saved: " +
487
+ `angle=${this.initialAngle}, scale=${this.initialScale}, offsetX=${this.initialOffsetX}, offsetY=${this.initialOffsetY}`);
488
+ }
489
+
490
+ // 重置所有变换的方法
491
+ private resetAllTransformations(): void {
492
+ Logger.info(TAG, "Resetting all transformations...");
493
+
494
+ // 重置位置和缩放
495
+ this.model.scale = this.initialScale;
496
+ this.model.offsetX = this.initialOffsetX;
497
+ this.model.offsetY = this.initialOffsetY;
498
+
499
+ // 重置旋转角度
500
+ if (this.angle !== this.initialAngle) {
501
+ let angleDifference = this.initialAngle - this.angle;
502
+
503
+ // 如果有PixelMap且需要旋转
504
+ if (this.model.src && angleDifference !== 0) {
505
+ angleDifference = angleDifference % 360
506
+ // 异步旋转图片
507
+ this.model.src.rotate(angleDifference)
508
+ .then(() => {
509
+ this.angle = this.initialAngle;
510
+ Logger.info(TAG, `Image rotated back by ${angleDifference} degrees`);
511
+ this.updateMatrix();
512
+ this.checkImageAdapt();
513
+ })
514
+ .catch((error: Error) => {
515
+ Logger.error(TAG, "Failed to reset rotation: " + JSON.stringify(error));
516
+ // 即使旋转失败,也更新UI状态
517
+ this.angle = this.initialAngle;
518
+ this.updateMatrix();
519
+ this.checkImageAdapt();
520
+ });
521
+ } else {
522
+ // 无需旋转或没有PixelMap,直接更新
523
+ this.angle = this.initialAngle;
524
+ this.updateMatrix();
525
+ this.checkImageAdapt();
526
+ }
527
+ } else {
528
+ // 角度相同,直接更新
529
+ this.updateMatrix();
530
+ this.checkImageAdapt();
531
+ }
532
+
533
+ // 重置图片宽高到初始值
534
+ if (this.imageWidth !== this.initialImageWidth || this.imageHeight !== this.initialImageHeight) {
535
+ this.imageWidth = this.initialImageWidth;
536
+ this.imageHeight = this.initialImageHeight;
537
+ }
538
+
539
+ Logger.info(TAG, "All transformations have been reset: " +
540
+ `angle=${this.angle}, scale=${this.model.scale}, offsetX=${this.model.offsetX}, offsetY=${this.model.offsetY}`);
541
+ }
542
+ }
543
+
544
+ interface ImageLoadedEvent {
545
+ width: number;
546
+ height: number;
547
+ componentWidth: number;
548
+ componentHeight: number;
549
+ loadingStatus: number;
550
+ contentWidth: number;
551
+ contentHeight: number;
552
+ contentOffsetX: number;
553
+ contentOffsetY: number;
554
+ }
555
+
556
+ export interface ImageLoadEventListener {
557
+
558
+ onImageLoaded(msg: ImageLoadedEvent): void;
559
+
560
+ onImageLoadError(error: ImageError): void;
561
+ }
562
+
563
+ export class CropModel {
564
+ /**
565
+ * PixelMap图片对象
566
+ *
567
+ */
568
+ src?: image.PixelMap = undefined;
569
+ /**
570
+ * 图片地址
571
+ */
572
+ path: string = '';
573
+ /**
574
+ * 图片预览
575
+ */
576
+ previewSource: string | Resource = '';
577
+ /**
578
+ * 是否可以拖动
579
+ */
580
+ panEnabled: boolean = true;
581
+ /**
582
+ * 是否可以缩放
583
+ */
584
+ zoomEnabled: boolean = true;
585
+ /**
586
+ * 取景框宽度
587
+ */
588
+ frameWidth = 1000;
589
+ /**
590
+ * 取景框宽高比
591
+ */
592
+ frameRatio = 1;
593
+ /**
594
+ * 遮罩颜色
595
+ */
596
+ maskColor: string = '#AA000000';
597
+ /**
598
+ * 取景框边框颜色
599
+ */
600
+ strokeColor: string = '#FFFFFF';
601
+ /**
602
+ * 图片加载监听
603
+ */
604
+ imageLoadEventListener: ImageLoadEventListener | null = null;
605
+ /**
606
+ * 图片宽度
607
+ */
608
+ imageWidth: number = 0;
609
+ /**
610
+ * 图片高度
611
+ */
612
+ imageHeight: number = 0;
613
+ /**
614
+ * 控件宽度
615
+ */
616
+ componentWidth: number = 0;
617
+ /**
618
+ * 控件高度
619
+ */
620
+ componentHeight: number = 0;
621
+ /**
622
+ * 手势缩放比例
623
+ */
624
+ scale: number = 1;
625
+ /**
626
+ * x轴方向偏移量
627
+ */
628
+ offsetX: number = 0;
629
+ /**
630
+ * y轴方向偏移量
631
+ */
632
+ offsetY: number = 0;
633
+
634
+ public setImage(path: string, previewSource?: string | Resource): CropModel {
635
+ this.path = path;
636
+ if (!!previewSource) {
637
+ this.previewSource = previewSource;
638
+ }
639
+ return this;
640
+ }
641
+
642
+ public setScale(scale: number): CropModel {
643
+ this.scale = scale;
644
+ return this;
645
+ }
646
+
647
+ public isPanEnabled(): boolean {
648
+ return this.panEnabled;
649
+ }
650
+
651
+ public setPanEnabled(panEnabled: boolean): CropModel {
652
+ this.panEnabled = panEnabled;
653
+ return this;
654
+ }
655
+
656
+ public setZoomEnabled(zoomEnabled: boolean): CropModel {
657
+ this.zoomEnabled = zoomEnabled;
658
+ return this;
659
+ }
660
+
661
+ public setFrameWidth(frameWidth: number): CropModel {
662
+ this.frameWidth = frameWidth;
663
+ return this;
664
+ }
665
+
666
+ public setFrameRatio(frameRatio: number): CropModel {
667
+ this.frameRatio = frameRatio;
668
+ return this;
669
+ }
670
+
671
+ public setMaskColor(color: string): CropModel {
672
+ this.maskColor = color;
673
+ return this;
674
+ }
675
+
676
+ public setStrokeColor(color: string): CropModel {
677
+ this.strokeColor = color;
678
+ return this;
679
+ }
680
+
681
+ public setImageLoadEventListener(listener: ImageLoadEventListener): CropModel {
682
+ this.imageLoadEventListener = listener;
683
+ return this;
684
+ }
685
+
686
+ public getScale(): number {
687
+ return this.scale;
688
+ }
689
+
690
+ public isZoomEnabled(): boolean {
691
+ return this.zoomEnabled;
692
+ }
693
+
694
+ public getImageWidth(): number {
695
+ return this.imageWidth;
696
+ }
697
+
698
+ public getImageHeight(): number {
699
+ return this.imageHeight;
700
+ }
701
+
702
+ public getFrameHeight() {
703
+ return this.frameWidth / this.frameRatio;
704
+ }
705
+
706
+ public reset(): void {
707
+ this.scale = 1;
708
+ this.offsetX = 0;
709
+ this.offsetY = 0;
710
+ }
711
+
712
+ public async crop(format: image.PixelMapFormat): Promise<image.PixelMap> {
713
+
714
+ if (!this.path || this.path == '') {
715
+ throw new Error('Please set path first');
716
+ }
717
+ if (this.imageWidth == 0 || this.imageHeight == 0) {
718
+ throw new Error('The image is not loaded');
719
+ }
720
+
721
+ // 图片适配控件的时候也进行了缩放,计算出这个缩放比例
722
+ let widthScale = this.componentWidth / this.imageWidth;
723
+ let heightScale = this.componentHeight / this.imageHeight;
724
+ let adaptScale = Math.min(widthScale, heightScale);
725
+
726
+ // 经过两次缩放(适配控件、手势)后,图片的实际显示大小
727
+ let totalScale = adaptScale * this.scale;
728
+ let showWidth = this.imageWidth * totalScale;
729
+ let showHeight = this.imageHeight * totalScale;
730
+ let imageX = (this.componentWidth - showWidth) / 2;
731
+ let imageY = (this.componentHeight - showHeight) / 2;
732
+
733
+ // 取景框的左上角坐标
734
+ let frameX = (this.componentWidth - this.frameWidth) / 2;
735
+ let frameY = (this.componentHeight - this.getFrameHeight()) / 2;
736
+
737
+ // 图片左上角坐标
738
+ let showX = imageX + this.offsetX * this.scale;
739
+ let showY = imageY + this.offsetY * this.scale;
740
+
741
+ let x = (frameX - showX) / totalScale;
742
+ let y = (frameY - showY) / totalScale;
743
+ let file = fs.openSync(this.path, fs.OpenMode.READ_ONLY)
744
+ let imageSource: image.ImageSource = image.createImageSource(file.fd);
745
+ let decodingOptions: image.DecodingOptions = {
746
+ editable: true,
747
+ desiredPixelFormat: image.PixelMapFormat.BGRA_8888,
748
+ }
749
+
750
+ // 创建pixelMap
751
+ let pm = await imageSource.createPixelMap(decodingOptions);
752
+ let cp = await this.copyPixelMap(pm);
753
+ pm.release();
754
+ let region: image.Region =
755
+ { x: x, y: y, size: { width: this.frameWidth / totalScale, height: this.getFrameHeight() / totalScale } };
756
+ cp.cropSync(region);
757
+ return cp;
758
+ }
759
+
760
+ async copyPixelMap(pm: PixelMap): Promise<PixelMap> {
761
+ const imageInfo: image.ImageInfo = await pm.getImageInfo();
762
+ const buffer: ArrayBuffer = new ArrayBuffer(pm.getPixelBytesNumber());
763
+ await pm.readPixelsToBuffer(buffer);
764
+ const opts: image.InitializationOptions = {
765
+ editable: true,
766
+ pixelFormat: image.PixelMapFormat.RGBA_8888,
767
+ size: { height: imageInfo.size.height, width: imageInfo.size.width }
768
+ };
769
+ return image.createPixelMap(buffer, opts);
770
+ }
771
+ }
772
+
773
+ @Entry
774
+ @Component
775
+ export struct CircleImageInfo {
776
+ @State private model: CropModel = new CropModel();
777
+
778
+ build() {
779
+ Column() {
780
+ CropView({
781
+ model: this.model,
782
+ })
783
+ .layoutWeight(1)
784
+ .width('100%')
785
+ }
786
+ .height('100%')
787
+ .width('100%')
788
+ }
789
+ }
@@ -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
+ }
@@ -31,4 +31,8 @@ export class Constants {
31
31
  static readonly IMAGE_FORMAT: string = '.jpg'
32
32
  static readonly IMAGE_PREFIX: string = 'image';
33
33
  static readonly ENCODE_FILE_PERMISSION: string = 'rw'
34
+
35
+ // 添加png支持
36
+ static readonly ENCODE_FORMAT_PNG: string = 'image/png'
37
+ static readonly IMAGE_FORMAT_PNG: string = '.png'
34
38
  }
@@ -50,4 +50,25 @@ export async function encode(component: Object, pixelMap: ESObject) : Promise<st
50
50
  fs.closeSync(newFile.fd);
51
51
  imagePackerApi.release();
52
52
  return imgPath;
53
+ }
54
+
55
+ // PNG编码函数
56
+ export async function encodeToPng(component: Object, pixelMap: ESObject): Promise<string> {
57
+ let imgPath: string = ''
58
+ const newPixelMap: ESObject = pixelMap
59
+ const imagePackerApi = image.createImagePacker()
60
+ const packOptions: image.PackingOption = {
61
+ format: Constants.ENCODE_FORMAT_PNG, // 使用PNG格式
62
+ quality: 100 // PNG质量参数可能无效,但保留
63
+ }
64
+ let packerData = await imagePackerApi.packing(newPixelMap, packOptions)
65
+ Logger.info(TAG, 'into PNG encode data size: ' + packerData.byteLength)
66
+ const context = getContext(component)
67
+ imgPath = context.tempDir + '/circle_image_' + util.generateRandomUUID(true) + Constants.IMAGE_FORMAT_PNG
68
+ let newFile = fs.openSync(imgPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE)
69
+ const number = fs.writeSync(newFile.fd, packerData)
70
+ Logger.info(TAG, 'PNG file saved: ' + imgPath + ', size: ' + number)
71
+ fs.closeSync(newFile.fd)
72
+ imagePackerApi.release()
73
+ return imgPath
53
74
  }
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "src": [
3
- "pages/ImageEditInfo"
3
+ "pages/ImageEditInfo",
4
+ "pages/CircleImageInfo"
4
5
  ]
5
6
  }
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.50.2-rc.3",
3
+ "version": "0.50.2-rc.5",
4
4
  "description": "Select single or multiple images, with cropping option",
5
5
  "main": "js/index.js",
6
6
  "scripts": {