@react-native-ohos/react-native-image-crop-picker 0.40.4-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/ COMMITTERS.md +9 -0
  2. package/.github/FUNDING.yml +1 -0
  3. package/CODE_OF_CONDUCT.md +0 -0
  4. package/CONTRIBUTING.md +68 -0
  5. package/ISSUE_TEMPLATE.md +34 -0
  6. package/LICENSE +21 -0
  7. package/OAT.xml +75 -0
  8. package/README.OpenSource +11 -0
  9. package/README.md +13 -0
  10. package/harmony/image_crop_picker/build-profile.json5 +8 -0
  11. package/harmony/image_crop_picker/hvigorfile.ts +1 -0
  12. package/harmony/image_crop_picker/index.ets +6 -0
  13. package/harmony/image_crop_picker/oh-package.json5 +10 -0
  14. package/harmony/image_crop_picker/src/main/cpp/CMakeLists.txt +9 -0
  15. package/harmony/image_crop_picker/src/main/cpp/ImageCropPickerPackage.h +19 -0
  16. package/harmony/image_crop_picker/src/main/cpp/generated/RNOH/generated/BaseReactNativeImageCropPickerPackage.h +66 -0
  17. package/harmony/image_crop_picker/src/main/cpp/generated/RNOH/generated/turbo_modules/ImageCropPicker.cpp +20 -0
  18. package/harmony/image_crop_picker/src/main/cpp/generated/RNOH/generated/turbo_modules/ImageCropPicker.h +16 -0
  19. package/harmony/image_crop_picker/src/main/ets/ImageCropPickerPackage.ts +28 -0
  20. package/harmony/image_crop_picker/src/main/ets/ImageCropPickerTurboModule.ts +1041 -0
  21. package/harmony/image_crop_picker/src/main/ets/Logger.ts +38 -0
  22. package/harmony/image_crop_picker/src/main/ets/generated/components/ts.ts +5 -0
  23. package/harmony/image_crop_picker/src/main/ets/generated/index.ets +5 -0
  24. package/harmony/image_crop_picker/src/main/ets/generated/ts.ts +6 -0
  25. package/harmony/image_crop_picker/src/main/ets/generated/turboModules/ImageCropPicker.ts +30 -0
  26. package/harmony/image_crop_picker/src/main/ets/generated/turboModules/ts.ts +5 -0
  27. package/harmony/image_crop_picker/src/main/ets/pages/ImageEditInfo.ets +862 -0
  28. package/harmony/image_crop_picker/src/main/ets/utils/Constants.ets +14 -0
  29. package/harmony/image_crop_picker/src/main/ets/utils/CropModel.ets +73 -0
  30. package/harmony/image_crop_picker/src/main/ets/utils/DecodeAndEncodeUtil.ets +54 -0
  31. package/harmony/image_crop_picker/src/main/ets/utils/EncodeUtil.ets +33 -0
  32. package/harmony/image_crop_picker/src/main/ets/utils/jul.ts +7 -0
  33. package/harmony/image_crop_picker/src/main/ets/utils/types.ets +94 -0
  34. package/harmony/image_crop_picker/src/main/ets/viewmodel/viewAndModel.ets +38 -0
  35. package/harmony/image_crop_picker/src/main/module.json5 +9 -0
  36. package/harmony/image_crop_picker/src/main/resources/base/element/string.json +20 -0
  37. package/harmony/image_crop_picker/src/main/resources/base/media/ic_anti_clockwise.png +0 -0
  38. package/harmony/image_crop_picker/src/main/resources/base/media/ic_clockwise.png +0 -0
  39. package/harmony/image_crop_picker/src/main/resources/base/media/ic_reset.png +0 -0
  40. package/harmony/image_crop_picker/src/main/resources/base/media/ic_save.png +0 -0
  41. package/harmony/image_crop_picker/src/main/resources/base/media/icon.png +0 -0
  42. package/harmony/image_crop_picker/src/main/resources/base/profile/main_pages.json +5 -0
  43. package/harmony/image_crop_picker/src/main/resources/en_US/element/string.json +8 -0
  44. package/harmony/image_crop_picker/src/main/resources/zh_CN/element/string.json +8 -0
  45. package/harmony/image_crop_picker/ts.ts +17 -0
  46. package/harmony/image_crop_picker.har +0 -0
  47. package/index.d.ts +512 -0
  48. package/js/NativeRNCImageCropPicker.ts +108 -0
  49. package/js/index.js +31 -0
  50. package/package.json +48 -0
  51. package/svg.svg +122 -0
@@ -0,0 +1,862 @@
1
+ // Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved
2
+ // Use of this source code is governed by a MIT license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import image from '@ohos.multimedia.image';
6
+ import Logger from '../Logger';
7
+ import display from '@ohos.display';
8
+ import router from '@ohos.router';
9
+ import { BusinessError } from '@ohos.base';
10
+ import { Rectangle } from '../utils/types';
11
+ import { Event } from '../utils/jul';
12
+ import { Action, DownPos, DragObj } from '../utils/CropModel';
13
+ import { Constants } from '../utils/Constants';
14
+ import { getPixelMap } from '../utils/DecodeAndEncodeUtil';
15
+ import { RotateType } from '../viewmodel/viewAndModel';
16
+ import { encode } from '../utils/EncodeUtil';
17
+
18
+ let hotspotsWidth: number = 18;
19
+ const PHONE_MIN_CROP: number = 43;
20
+ const TAG: string = 'ImageEditInfo';
21
+
22
+ @Extend(Row)
23
+ function rowStyle() {
24
+ .width('100%')
25
+ .height(1)
26
+ .backgroundColor('#FFFFFF')
27
+ .opacity(0.5)
28
+ }
29
+
30
+ @Extend(Row)
31
+ function rowStyle1(hasSplitLine: boolean) {
32
+ .width('100%')
33
+ .height(0.5)
34
+ .backgroundColor('#FFFFFF')
35
+ .opacity(hasSplitLine ? 0.5 : 0)
36
+ }
37
+
38
+ @Extend(Row)
39
+ function rowStyle2(hasMoreSplitLine: boolean) {
40
+ .width('100%')
41
+ .height(0.5)
42
+ .backgroundColor('#FFFFFF')
43
+ .opacity(hasMoreSplitLine ? 0.2 : 0)
44
+ }
45
+
46
+ @Extend(Column)
47
+ function columnStyle() {
48
+ .width(1)
49
+ .height('100%')
50
+ .backgroundColor('#FFFFFF')
51
+ .opacity(0.5)
52
+ }
53
+
54
+ @Extend(Column)
55
+ function columnStyle1(hasSplitLine: boolean) {
56
+ .width(0.5)
57
+ .height('100%')
58
+ .backgroundColor('#FFFFFF')
59
+ .opacity(hasSplitLine ? 0.5 : 0)
60
+ }
61
+
62
+ @Extend(Column)
63
+ function columnStyle2(hasMoreSplitLine: boolean) {
64
+ .width(0.5)
65
+ .height('100%')
66
+ .backgroundColor('#FFFFFF')
67
+ .opacity(hasMoreSplitLine ? 0.2 : 0)
68
+ }
69
+
70
+ @Extend(Image)
71
+ function iconStyle() {
72
+ .size({ width: 24, height: 24 })
73
+ .fillColor('#FFFFFF')
74
+ .opacity(0.9)
75
+ }
76
+
77
+ @Extend(Row)
78
+ function bottomIconStyle() {
79
+ .size({ width: '20%', height: '100%' })
80
+ .justifyContent(FlexAlign.Center)
81
+ }
82
+
83
+ @Extend(Text)
84
+ function textStyle() {
85
+ .size({ width: '20%', height: '100%' })
86
+ .textAlign(TextAlign.Center)
87
+ }
88
+
89
+ @Entry
90
+ @Component
91
+ export struct ImageEditInfo {
92
+ @StorageProp('filePath') filePath: string = '';
93
+ @StorageProp('cropperRotate') cropperRotate: string = '';
94
+ setting: RenderingContextSettings = new RenderingContextSettings(true);
95
+ context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.setting);
96
+ @Provide('isPixelMapChange') @Watch('flushPixelMap') isPixelMapChange: boolean = false;
97
+ @StorageProp('initWidth') initWidth: number = 0;
98
+ @StorageProp('initHeight') initHeight: number = 0;
99
+ @StorageProp('textTitle') title: string = '';
100
+ @StorageProp('cancelText') cancel: string = '';
101
+ @StorageProp('cancelTextColor') cancelTextColor: string = '';
102
+ @StorageProp('chooseText') choose: string = '';
103
+ @StorageProp('chooseTextColor') chooseTextColor: string = '';
104
+ @StorageProp('freeStyleCropEnabled') freeStyleCropEnabled: boolean = false;
105
+ @StorageProp('enableRotationGesture') enableRotationGesture: boolean = false;
106
+ @State uri: string = '';
107
+ @State icon?: image.PixelMap = undefined;
108
+ @State imageWith: number = 0;
109
+ @State imageHeight: number = 0;
110
+ @State screenWidth: number = 0;
111
+ @State screenHeight: number = 0;
112
+ DEFAULT_MASK_STYLE: string = 'rgba(0, 0, 0, 0.3)';
113
+ @State clipSize: Rectangle = new Rectangle(0, 0, 0, 0);
114
+ @State maxClipSize: Rectangle = new Rectangle(0, 0, 0, 0);
115
+ @State display: Rectangle = new Rectangle(0, 0, 0, 0);
116
+ @State crop: number = 0;
117
+ @State hasSplitLine: boolean = true;
118
+ @State clipVisible: boolean = true;
119
+ @State imgOffSetX: number = 0;
120
+ @State imgOffSetY: number = 0;
121
+ @State imgScale: number = 1;
122
+ @State currentScale: number = 1;
123
+ @State preOffsetX: number = 0;
124
+ @State preOffsetY: number = 0;
125
+ @State clipRatio: number = 0;
126
+ @State imageFlag: boolean = true;
127
+ @State angle: number = 0;
128
+ @State rotateValue: number = 0;
129
+ private minSize = PHONE_MIN_CROP // 最小宽高
130
+ private dragObj: DragObj = new DragObj(false);
131
+ private clb: number = 1
132
+
133
+ pixelInit(path: string) {
134
+ this.angle = 0;
135
+ this.screenWidth = px2vp(display.getDefaultDisplaySync().width);
136
+ this.screenHeight = px2vp(display.getDefaultDisplaySync().height);
137
+ this.uri = path;
138
+ getPixelMap(this.uri)
139
+ .then((pixelMap: image.PixelMap) => {
140
+ if (pixelMap) {
141
+ this.isPixelMapChange = !this.isPixelMapChange;
142
+ this.icon = pixelMap;
143
+ pixelMap.getImageInfo().then((imageInfo) => {
144
+ this.imageHeight = imageInfo.size == undefined ? 0 : imageInfo.size?.height;
145
+ this.imageWith = imageInfo.size == undefined ? 0 : imageInfo.size?.width;
146
+ this.initCropBox(this.imageWith, this.imageHeight, this.screenWidth, this.screenHeight);
147
+ })
148
+ }
149
+ })
150
+ }
151
+
152
+ resetImg(): void {
153
+ this.imgScale = 1;
154
+ this.currentScale = 1;
155
+ this.preOffsetX = 0;
156
+ this.preOffsetY = 0;
157
+ }
158
+
159
+ initCropBox(imageWidth: number, imageHeight: number, screenWidth: number, screenHeight: number) {
160
+ if (imageHeight / imageWidth < screenHeight / screenWidth) {
161
+ this.clb = imageWidth / (screenWidth - hotspotsWidth * 2);
162
+ } else {
163
+ this.clb = imageHeight / (screenHeight - hotspotsWidth * 2 - 56 * 2);
164
+ }
165
+ let imgHeight = Math.round(imageHeight / this.clb);
166
+ let imgWidth = Math.round(imageWidth / this.clb);
167
+
168
+ let left = (screenWidth - imgWidth) / 2;
169
+ let top = (screenHeight - imgHeight) / 2 - 56;
170
+ let right = imgWidth + left;
171
+ let bottom = imgHeight + top;
172
+
173
+ this.maxClipSize = new Rectangle(left, top, right, bottom);
174
+
175
+ if (this.initWidth > 0) {
176
+ left = (screenWidth - this.initWidth) / 2;
177
+ right = this.initWidth + left;
178
+ }
179
+ if (this.initHeight > 0) {
180
+ top = (screenHeight - this.initHeight) / 2 - 56;
181
+ bottom = this.initHeight + top;
182
+ }
183
+
184
+ this.clipSize = new Rectangle(left, top, right, bottom);
185
+ this.display = new Rectangle(0, 0, this.screenWidth, this.screenHeight - 56 * 2);
186
+ }
187
+
188
+ aboutToAppear() {
189
+ this.pixelInit(this.filePath);
190
+ }
191
+
192
+ aboutToDisappear(): void {
193
+ let arrayData = ['filePath', 'textTitle', 'chooseText', 'chooseTextColor', 'cancelText', 'cancelTextColor', 'cropperRotate'];
194
+ this.clearData(arrayData);
195
+ }
196
+
197
+ clearData(arrayData: Array<string>) {
198
+ for (let i = 0; i < arrayData.length; i++) {
199
+ AppStorage.setOrCreate(arrayData[i], '');
200
+ }
201
+ }
202
+
203
+ @Builder
204
+ CropTitleBar() {
205
+ Text(this.title)
206
+ .fontColor(Color.White)
207
+ .fontSize(20)
208
+ .textAlign(TextAlign.Center)
209
+ .width('100%')
210
+ .height(56)
211
+ .position({ x: 0, y: 0 })
212
+ .backgroundColor(Color.Black)
213
+ }
214
+
215
+ @Builder
216
+ CropImageArea() {
217
+ Stack() {
218
+ Image(this.icon)
219
+ .objectFit(ImageFit.Contain)
220
+ .position({ x: this.imgOffSetX, y: this.imgOffSetY })
221
+ .scale({ x: this.imgScale, y: this.imgScale })
222
+ .padding(hotspotsWidth)
223
+ .width('100%')
224
+ .height('100%')
225
+
226
+ Row()
227
+ .height(this.clipSize.height())
228
+ .width(this.clipSize.left - this.display.left)
229
+ .position({ x: this.display.left, y: this.clipSize.top })
230
+ .backgroundColor(this.DEFAULT_MASK_STYLE)
231
+
232
+ Row()
233
+ .height(this.clipSize.top - this.display.top)
234
+ .width(this.display.width())
235
+ .position({ x: this.display.left, y: this.display.top })
236
+ .backgroundColor(this.DEFAULT_MASK_STYLE)
237
+
238
+ Row()
239
+ .height(this.clipSize.height())
240
+ .width(this.display.right - this.clipSize.right)
241
+ .position({ x: this.clipSize.right, y: this.clipSize.top })
242
+ .backgroundColor(this.DEFAULT_MASK_STYLE)
243
+
244
+ Row()
245
+ .height(this.display.bottom - this.clipSize.bottom)
246
+ .width(this.display.width())
247
+ .position({ x: this.display.left, y: this.clipSize.bottom })
248
+ .backgroundColor(this.DEFAULT_MASK_STYLE)
249
+
250
+ RectangleSizer({
251
+ clipSize: this.clipSize,
252
+ onDrag: this.onDragStartFun,
253
+ clipVisible: this.clipVisible,
254
+ hasSplitLine: this.hasSplitLine
255
+ })
256
+ }
257
+ .height(this.screenHeight - 56 * 2)
258
+ .width('100%')
259
+ .backgroundColor(Color.Black)
260
+ .onTouch(this.touchHandler)
261
+ .gesture(GestureGroup(GestureMode.Exclusive,
262
+ PinchGesture({ fingers: Constants.DOUBLE_NUMBER })
263
+ .onActionUpdate((event?: GestureEvent) => {
264
+ if (event) {
265
+ this.imgScale = this.currentScale * event.scale;
266
+ }
267
+ })
268
+ .onActionEnd(() => {
269
+ if (this.imgScale < 1) {
270
+ this.resetImg();
271
+ this.imgOffSetX = 0;
272
+ this.imgOffSetY = 0;
273
+ } else {
274
+ this.currentScale = this.imgScale;
275
+ }
276
+ }),
277
+ PanGesture()
278
+ .onActionStart(() => {
279
+ this.preOffsetX = this.imgOffSetX;
280
+ this.preOffsetY = this.imgOffSetY;
281
+ })
282
+ .onActionUpdate((event?: GestureEvent) => {
283
+ if (event && this.imageFlag) {
284
+ this.imgOffSetX = this.preOffsetX + event.offsetX;
285
+ this.imgOffSetY = this.preOffsetY + event.offsetY;
286
+ Logger.info(TAG, "into flushPixelMapChange x : " + this.imgOffSetX + " y : " + this.imgOffSetY)
287
+ }
288
+ }),
289
+ RotationGesture()
290
+ .onActionEnd((event: GestureEvent) => {
291
+ if (this.enableRotationGesture) {
292
+ this.rotateValue = event.angle;
293
+ const sum = Math.ceil(this.rotateValue / 90);
294
+ this.resetImg();
295
+ this.imgOffSetX = 0;
296
+ this.imgOffSetY = 0;
297
+ if (sum > 0) {
298
+ this.rotateImage(RotateType.CLOCKWISE);
299
+ } else {
300
+ this.rotateImage(RotateType.ANTI_CLOCK);
301
+ }
302
+ }
303
+ })
304
+ ))
305
+ }
306
+
307
+ @Builder
308
+ BottomToolbar() {
309
+ Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center }) {
310
+ Text(this.cancel)
311
+ .textStyle()
312
+ .fontColor(this.cancelTextColor)
313
+ .onClick(async () => {
314
+ router.back()
315
+ });
316
+ Row() {
317
+ Image($r('app.media.ic_anti_clockwise'))
318
+ .iconStyle()
319
+ .visibility(this.cropperRotate === 'true' ? Visibility.Hidden : Visibility.Visible)
320
+ .onClick(async () => {
321
+ this.resetImg();
322
+ this.imgOffSetX = 0;
323
+ this.imgOffSetY = 0;
324
+ this.rotateImage(RotateType.ANTI_CLOCK);
325
+ });
326
+ }
327
+ .bottomIconStyle();
328
+
329
+ Row() {
330
+ Image($r('app.media.ic_reset'))
331
+ .iconStyle()
332
+ .onClick(() => {
333
+ this.pixelInit(this.uri);
334
+ this.resetImg();
335
+ this.imgOffSetX = 0;
336
+ this.imgOffSetY = 0;
337
+ });
338
+ }
339
+ .bottomIconStyle();
340
+
341
+ Row() {
342
+ Image($r('app.media.ic_clockwise'))
343
+ .iconStyle()
344
+ .visibility(this.cropperRotate === 'true' ? Visibility.Hidden : Visibility.Visible)
345
+ .onClick(() => {
346
+ this.resetImg();
347
+ this.imgOffSetX = 0;
348
+ this.imgOffSetY = 0;
349
+ this.rotateImage(RotateType.CLOCKWISE);
350
+ });
351
+ }
352
+ .bottomIconStyle();
353
+
354
+ Text(this.choose)
355
+ .textStyle()
356
+ .fontColor(this.chooseTextColor)
357
+ .onClick(() => {
358
+ if (this.icon) {
359
+ let xOff = this.imageWith * (this.imgScale - 1) / 2;
360
+ let yOff = this.imageHeight * (this.imgScale - 1) / 2;
361
+ let region: image.Region = { x: 0, y: 0, size: { height: this.imageHeight, width: this.imageWith } };
362
+ region.x =
363
+ (this.clipSize.left - this.maxClipSize.left) / this.imgScale * this.clb + (xOff / this.imgScale) - this.imgOffSetX / this.imgScale * this.clb;
364
+ region.size.width = this.imageWith - region.x - (this.maxClipSize.right - this.clipSize.right) / this.imgScale * this.clb
365
+ - (xOff / this.imgScale) - this.imgOffSetX / this.imgScale * this.clb;
366
+ region.y =
367
+ (this.clipSize.top - this.maxClipSize.top) / this.imgScale * this.clb + (yOff / this.imgScale) - this.imgOffSetY / this.imgScale * this.clb;
368
+ region.size.height = this.imageHeight - region.y - (this.maxClipSize.bottom - this.clipSize.bottom) / this.imgScale * this.clb
369
+ - (yOff / this.imgScale) - this.imgOffSetY / this.imgScale * this.clb;
370
+ this.icon.crop(region, async (err: BusinessError) => {
371
+ if (err != undefined) {
372
+ console.error("Failed to crop pixelmap.");
373
+ return;
374
+ } else {
375
+ let imgPath = await encode(this, this.icon);
376
+ AppStorage.setOrCreate('cropImagePath', imgPath)
377
+ AppStorage.setOrCreate('cropRect', {
378
+ width: region.size.width,
379
+ height: region.size.height,
380
+ x: region.x,
381
+ y: region.y
382
+ })
383
+ router.back()
384
+ }
385
+ })
386
+ }
387
+ })
388
+ }
389
+ .width('100%')
390
+ .height(56)
391
+ .position({ x: 0, y: (this.screenHeight - 56) })
392
+ .backgroundColor(Color.Black)
393
+ }
394
+
395
+ rotateImage(rotateType: RotateType) {
396
+ Logger.info(TAG, "into rotateImage rotateType : " + rotateType)
397
+ if (rotateType === RotateType.CLOCKWISE) {
398
+ if (!this.icon) {
399
+ Logger.info(TAG, "into rotateImage return")
400
+ return;
401
+ }
402
+ try {
403
+ this.icon.rotate(Constants.CLOCK_WISE)
404
+ .then(() => {
405
+ this.angle = this.angle + Constants.CLOCK_WISE;
406
+ this.flushPixelMapChange(this.angle);
407
+ })
408
+ } catch (error) {
409
+ }
410
+ }
411
+ if (rotateType === RotateType.ANTI_CLOCK) {
412
+ if (!this.icon) {
413
+ Logger.info(TAG, "into rotateImage return")
414
+ return;
415
+ }
416
+ try {
417
+ this.icon.rotate(Constants.ANTI_CLOCK)
418
+ .then(() => {
419
+ this.angle = this.angle + Constants.ANTI_CLOCK;
420
+ this.flushPixelMapChange(this.angle);
421
+ })
422
+ } catch (error) {
423
+ }
424
+ }
425
+ }
426
+
427
+ flushPixelMapChange(angle: number) {
428
+ this.isPixelMapChange = !this.isPixelMapChange;
429
+ let clipAngle = angle / 90;
430
+ switch (Math.abs(clipAngle % 4)) {
431
+ case 0:
432
+ case 2:
433
+ this.initCropBox(this.imageWith, this.imageHeight, this.screenWidth, this.screenHeight);
434
+ break;
435
+ default:
436
+ this.initCropBox(this.imageHeight, this.imageWith, this.screenWidth, this.screenHeight);
437
+ break;
438
+ }
439
+ }
440
+
441
+ flushPixelMap() {
442
+ const temp = this.icon;
443
+ this.icon = undefined;
444
+ this.icon = temp;
445
+ }
446
+
447
+ onDragStartFun = (event: TouchEvent, left: boolean, top: boolean, right: boolean, bottom: boolean, multiCrop: boolean): void => {
448
+ let downPos = new DownPos(this.clipSize.left, this.clipSize.top, this.clipSize.bottom, this.clipSize.right);
449
+ let action = new Action(left, top, right, bottom);
450
+ this.dragObj = new DragObj(true, event.touches[0].screenX, event.touches[0].screenY, action, downPos, multiCrop);
451
+ }
452
+
453
+ private endImageDrag(): void {
454
+ // 图片的宽高
455
+ let imageWidth = this.imageHeight;
456
+ let imageHeight = this.imageWith;
457
+ if ([0, 2].includes(Math.abs((this.angle / 90) % 4))) {
458
+ imageWidth = this.imageWith;
459
+ imageHeight = this.imageHeight;
460
+ }
461
+
462
+ let crop: Rectangle = new Rectangle(this.clipSize.left, this.clipSize.top, this.clipSize.right, this.clipSize.bottom);
463
+
464
+ // 裁剪框和图片间的偏移量
465
+ let extraLeft = 0;
466
+ let extraTop = 0;
467
+ let extraRight = 0;
468
+ let extraBottom = 0;
469
+
470
+ // 图片实际的宽高
471
+ let actualHeight = 0;
472
+ let actualWidth = 0;
473
+
474
+ // 图片偏移量
475
+ let imgOffSetX = this.imgOffSetX;
476
+ let imgOffSetY = this.imgOffSetY;
477
+
478
+ // 图片缩放偏移量
479
+ let scaleOffsetX = 0;
480
+ let scaleOffsetY = 0;
481
+
482
+ // 根据图片宽高比计算实际的图片宽高和偏移量
483
+ // 计算图片的实际宽高和实际偏移量
484
+ actualHeight = Math.round(imageHeight / this.clb);
485
+ actualWidth = Math.round(imageWidth / this.clb);
486
+ extraLeft = (this.screenWidth - actualWidth) / 2;
487
+ extraTop = (this.screenHeight - actualHeight) / 2 - 56;
488
+ extraRight = actualWidth + extraLeft;
489
+ extraBottom = actualHeight + extraTop;
490
+
491
+ // 计算缩放偏移量
492
+ scaleOffsetX = this.imgScale > 1 ? 0 - ((extraRight - extraLeft) * this.imgScale - (extraRight - extraLeft)) / 2 : 0;
493
+ scaleOffsetY = this.imgScale > 1 ? 0 - ((extraBottom - extraTop) * this.imgScale - (extraBottom - extraTop)) / 2 : 0;
494
+
495
+ // 计算图片四个边缘的位置
496
+ const imageRight = this.imgOffSetX + actualWidth;
497
+ const imageBottom = this.imgOffSetY + actualHeight;
498
+
499
+ // 计算裁剪框四个边缘的位置
500
+ const cropRight = crop.right
501
+ const cropBottom = crop.bottom;
502
+
503
+ // 判断图片左边缘是否在裁剪框内
504
+ if (this.imgOffSetX + scaleOffsetX > crop.left - extraLeft) {
505
+ // 图片左边缘已经被拖动到裁剪框内,需要还原位置
506
+ imgOffSetX = crop.left - extraLeft - scaleOffsetX;
507
+ }
508
+
509
+ // 判断图片上边缘是否在裁剪框内
510
+ if (this.imgOffSetY + scaleOffsetY > crop.top - extraTop) {
511
+ // 图片上边缘已经被拖动到裁剪框内,需要还原位置
512
+ imgOffSetY = crop.top - extraTop - scaleOffsetY;
513
+ }
514
+
515
+ // 判断图片右边缘是否在裁剪框内
516
+ if (imageRight - scaleOffsetX < cropRight - extraLeft) {
517
+ imgOffSetX = cropRight - extraRight + scaleOffsetX;
518
+ }
519
+
520
+ // 判断图片下边缘是否在裁剪框内
521
+ if (imageBottom - scaleOffsetY < cropBottom - extraTop) {
522
+ // 图片下边缘已经被拖动到裁剪框内,需要还原位置
523
+ imgOffSetY = cropBottom - extraBottom + scaleOffsetY;
524
+ }
525
+ this.imgOffSetX = imgOffSetX;
526
+ this.imgOffSetY = imgOffSetY;
527
+ }
528
+
529
+ touchHandler: (event: TouchEvent | undefined) => void = (event: TouchEvent | undefined): void => {
530
+ if (!event) {
531
+ return;
532
+ }
533
+
534
+ if (event.type === TouchType.Up) {
535
+ this.dragObj.dragging = false;
536
+ this.endImageDrag();
537
+ }
538
+
539
+ if (event.type === TouchType.Down) {
540
+ Logger.info(TAG, "Down")
541
+ }
542
+
543
+ if (event.type === TouchType.Move) {
544
+ if (!this.dragObj.dragging) {
545
+ this.imageFlag = true;
546
+ return;
547
+ }
548
+ this.imageFlag = false;
549
+ if (this.freeStyleCropEnabled) {
550
+ return;
551
+ }
552
+ let touch = event.touches[0];
553
+ let delX: number = touch.screenX - this.dragObj.x;
554
+ let delY: number = touch.screenY - this.dragObj.y;
555
+ // 裁剪和缩放平移收拾互斥
556
+
557
+ let newPosition = this.clipSize.clone();
558
+
559
+ let direction = this.dragObj.action;
560
+ if (this.dragObj.multiCrop) {
561
+ this.getMultiCropRect(delX, delY, newPosition, direction, event.pressure);
562
+ } else {
563
+ this.getSingleCropRect(delX, delY, newPosition, direction, event.pressure);
564
+ }
565
+ }
566
+ }
567
+
568
+ getMultiCropRect(delX: number, delY: number, newPosition: Rectangle, direction: Action, pressure: number) {
569
+ if (direction.left) {
570
+ newPosition.left = this.dragObj.downPos.left + delX;
571
+ let width = this.clipSize.right - newPosition.left;
572
+ if (this.clipRatio > 0) {
573
+ let height = width / this.clipRatio;
574
+ if (height <= this.minSize) {
575
+ newPosition.left = this.clipSize.right - this.minSize * this.clipRatio;
576
+ }
577
+ } else if (width <= this.minSize) {
578
+ newPosition.left = this.clipSize.right - this.minSize;
579
+ }
580
+ if (newPosition.left < this.maxClipSize.left) {
581
+ newPosition.left = this.maxClipSize.left;
582
+ }
583
+ }
584
+ if (direction.top) {
585
+ newPosition.top = this.dragObj.downPos.top + delY;
586
+ let height = this.clipSize.bottom - newPosition.top;
587
+ if (this.clipRatio > 0) {
588
+ let width = height * this.clipRatio;
589
+ if (width <= this.minSize) {
590
+ newPosition.top = this.clipSize.bottom - this.minSize / this.clipRatio;
591
+ }
592
+ } else if (height <= this.minSize) {
593
+ newPosition.top = this.clipSize.bottom - this.minSize;
594
+ }
595
+ if (newPosition.top < this.maxClipSize.top) {
596
+ newPosition.top = this.maxClipSize.top;
597
+ }
598
+ }
599
+ if (direction.right) {
600
+ newPosition.right = this.dragObj.downPos.right + delX;
601
+ let width = newPosition.right - this.clipSize.left;
602
+ if (this.clipRatio > 0) {
603
+ let height = width / this.clipRatio;
604
+ if (height <= this.minSize) {
605
+ newPosition.right = this.clipSize.left + this.minSize * this.clipRatio;
606
+ }
607
+ } else if (width <= this.minSize) {
608
+ newPosition.right = this.clipSize.left + this.minSize;
609
+ }
610
+ if (newPosition.right > this.maxClipSize.right) {
611
+ newPosition.right = this.maxClipSize.right;
612
+ }
613
+ }
614
+ if (direction.bottom) {
615
+ newPosition.bottom = this.dragObj.downPos.bottom + delY;
616
+ let height = newPosition.bottom - this.clipSize.top;
617
+ if (this.clipRatio > 0) {
618
+ let width = height * this.clipRatio;
619
+ if (width <= this.minSize) {
620
+ newPosition.bottom = this.clipSize.top + this.minSize / this.clipRatio;
621
+ }
622
+ } else if (height <= this.minSize) {
623
+ newPosition.bottom = this.clipSize.top + this.minSize;
624
+ }
625
+ if (newPosition.bottom > this.maxClipSize.bottom) {
626
+ newPosition.bottom = this.maxClipSize.bottom;
627
+ }
628
+ }
629
+ this.clipSize = new Rectangle(newPosition.left, newPosition.top, newPosition.right, newPosition.bottom);
630
+ }
631
+
632
+ getSingleCropRect(delX: number, delY: number, newPosition: Rectangle, direction: Action, pressure: number) {
633
+ if (direction.left) {
634
+ let newX = this.dragObj.downPos.left + delX;
635
+ newX = newX < this.dragObj.downPos.right - this.minSize ? newX : this.dragObj.downPos.right - this.minSize
636
+ if (this.clipRatio > 0) {
637
+ let width = this.clipSize.right - newX;
638
+ let height = width / this.clipRatio;
639
+ newPosition.top = this.clipSize.top - (height - this.clipSize.height()) / 2;
640
+ newPosition.bottom = this.clipSize.bottom + (height - this.clipSize.height()) / 2;
641
+ if (height <= this.minSize) {
642
+ newX = this.clipSize.right - this.minSize * this.clipRatio;
643
+ }
644
+ }
645
+ newPosition.left = newX;
646
+ if (newPosition.left < this.maxClipSize.left) {
647
+ newPosition.left = this.maxClipSize.left;
648
+ }
649
+ } else if (direction.right) {
650
+ let newX = this.dragObj.downPos.right + delX;
651
+ if (newX < this.dragObj.downPos.left + this.minSize) {
652
+ newX = this.dragObj.downPos.left + this.minSize;
653
+ }
654
+ if (this.clipRatio > 0) {
655
+ let width = newX - this.clipSize.left;
656
+ let height = width / this.clipRatio;
657
+ newPosition.top = this.clipSize.top - (height - this.clipSize.height()) / 2;
658
+ newPosition.bottom = this.clipSize.bottom + (height - this.clipSize.height()) / 2;
659
+ if (height <= this.minSize) {
660
+ newX = this.minSize * this.clipRatio + this.clipSize.left;
661
+ }
662
+ }
663
+ newPosition.right = newX;
664
+ if (newPosition.right > this.maxClipSize.right) {
665
+ newPosition.right = this.maxClipSize.right;
666
+ }
667
+ } else if (direction.top) {
668
+ let newY = this.dragObj.downPos.top + delY;
669
+ newY = newY < this.dragObj.downPos.bottom - this.minSize ? newY : this.dragObj.downPos.bottom - this.minSize
670
+ if (this.clipRatio > 0) {
671
+ let height = this.clipSize.bottom - newY;
672
+ let width = height * this.clipRatio;
673
+ newPosition.left = this.clipSize.left - (width - this.clipSize.width()) / 2;
674
+ newPosition.right = this.clipSize.right + (width - this.clipSize.width()) / 2;
675
+ if (width <= this.minSize) {
676
+ newY = this.clipSize.bottom - this.minSize / this.clipRatio;
677
+ }
678
+ }
679
+ newPosition.top = newY;
680
+ if (newPosition.top < this.maxClipSize.top) {
681
+ newPosition.top = this.maxClipSize.top;
682
+ }
683
+ } else if (direction.bottom) {
684
+ let newY = this.dragObj.downPos.bottom + delY;
685
+ if (newY < this.dragObj.downPos.top + this.minSize) {
686
+ newY = this.dragObj.downPos.top + this.minSize;
687
+ }
688
+ if (this.clipRatio > 0) {
689
+ let height = newY - this.clipSize.top;
690
+ let width = height * this.clipRatio;
691
+ newPosition.left = this.clipSize.left - (width - this.clipSize.width()) / 2;
692
+ newPosition.right = this.clipSize.right + (width - this.clipSize.width()) / 2;
693
+ if (width <= this.minSize) {
694
+ newY = this.minSize / this.clipRatio + this.clipSize.top;
695
+ }
696
+ }
697
+ newPosition.bottom = newY;
698
+ if (newPosition.bottom > this.maxClipSize.bottom) {
699
+ newPosition.bottom = this.maxClipSize.bottom;
700
+ }
701
+ }
702
+ this.clipSize = new Rectangle(newPosition.left, newPosition.top, newPosition.right, newPosition.bottom);
703
+
704
+ }
705
+
706
+ build() {
707
+ Stack() {
708
+ this.CropImageArea();
709
+ this.CropTitleBar();
710
+ this.BottomToolbar();
711
+ }
712
+ .width('100%')
713
+ .height('100%')
714
+ }
715
+ }
716
+
717
+
718
+ @Component
719
+ export struct RectangleSizer {
720
+ @Prop clipSize: Rectangle = new Rectangle(0, 0, 0, 0);
721
+ public onDrag: (event: TouchEvent, left: boolean, top: boolean, right: boolean, bottom: boolean, multiCrop: boolean) => void = Event
722
+ @Prop clipVisible: boolean = true;
723
+ @State isScrolling: boolean = false;
724
+ @State isTouched: boolean = false;
725
+ @Prop hasSplitLine: boolean = true;
726
+ @StorageProp('showCropFrame') showCropFrame: boolean = true;
727
+ @StorageProp('showCropGuidelines') showCropGuidelines: boolean = true;
728
+ @Prop isScreenPortrait: boolean = false;
729
+
730
+ build() {
731
+ Stack() {
732
+ Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {
733
+ Row().rowStyle().id('petalClip_frostedGlass_top')
734
+ Row().rowStyle2(this.isScrolling).id('petalClip_frostedGlass_row_81_1')
735
+ Row().rowStyle2(this.isScrolling).id('petalClip_frostedGlass_row_81_2')
736
+ Row().rowStyle1(this.hasSplitLine || this.isScrolling).id('petalClip_frostedGlass_row_9_1')
737
+ Row().rowStyle2(this.isScrolling).id('petalClip_frostedGlass_row_81_3')
738
+ Row().rowStyle2(this.isScrolling).id('petalClip_frostedGlass_row_81_4')
739
+ Row().rowStyle1(this.hasSplitLine || this.isScrolling).id('petalClip_frostedGlass_row_9_2')
740
+ Row().rowStyle2(this.isScrolling).id('petalClip_frostedGlass_row_81_5')
741
+ Row().rowStyle2(this.isScrolling).id('petalClip_frostedGlass_row_81_6')
742
+ Row().rowStyle().id('petalClip_frostedGlass_bottom')
743
+ }
744
+ .visibility(this.showCropGuidelines ? Visibility.Visible : Visibility.Hidden)
745
+ .padding(hotspotsWidth)
746
+
747
+ Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
748
+ Column().columnStyle().id('petalClip_frostedGlass_left')
749
+ Column().columnStyle2(this.isScrolling).id('petalClip_frostedGlass_column_81_1')
750
+ Column().columnStyle2(this.isScrolling).id('petalClip_frostedGlass_column_81_2')
751
+ Column()
752
+ .columnStyle1(this.hasSplitLine || this.isScrolling)
753
+ .id('petalClip_frostedGlass_column_9_1')
754
+ Column().columnStyle2(this.isScrolling).id('petalClip_frostedGlass_column_81_3')
755
+ Column().columnStyle2(this.isScrolling).id('petalClip_frostedGlass_column_81_4')
756
+ Column()
757
+ .columnStyle1(this.hasSplitLine || this.isScrolling)
758
+ .id('petalClip_frostedGlass_column_9_2')
759
+ Column().columnStyle2(this.isScrolling).id('petalClip_frostedGlass_column_81_5')
760
+ Column().columnStyle2(this.isScrolling).id('petalClip_frostedGlass_column_81_6')
761
+ Column().columnStyle().id('petalClip_frostedGlass_right')
762
+ }
763
+ .visibility(this.showCropGuidelines ? Visibility.Visible : Visibility.Hidden)
764
+ .padding(hotspotsWidth)
765
+
766
+ Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {
767
+ Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
768
+ Row()
769
+ .width(20)
770
+ .height(20)
771
+ .border({ width: { top: 3, left: 3 }, color: { top: '#FFFFFF', left: '#FFFFFF' } })
772
+ .id('petalClip_frostedGlass_rect_leftTop')
773
+ Row()
774
+ .width(20)
775
+ .height(20)
776
+ .border({ width: { top: 3 }, color: { top: '#FFFFFF' } })
777
+ .id('petalClip_frostedGlass_rect_topMiddle')
778
+ Row()
779
+ .width(20)
780
+ .height(20)
781
+ .border({ width: { top: 3, right: 3 }, color: { top: '#FFFFFF', right: '#FFFFFF' } })
782
+ .id('petalClip_frostedGlass_rect_rightTop')
783
+ }
784
+ .height(Math.min(20, this.clipSize.height() / 3 + 4)).clip(true)
785
+
786
+ Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
787
+ Row()
788
+ .width(20)
789
+ .height(20)
790
+ .border({ width: { left: 3 }, color: { left: '#FFFFFF' } })
791
+ .id('petalClip_frostedGlass_rect_leftMiddle')
792
+ Row()
793
+ .width(20)
794
+ .height(20)
795
+ .border({ width: { right: 3 }, color: { right: '#FFFFFF' } })
796
+ .id('petalClip_frostedGlass_rect_rightMiddle')
797
+ }
798
+ .height(Math.min(20, this.clipSize.height() / 3 + 1)).clip(true)
799
+
800
+ Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
801
+ Row()
802
+ .width(20)
803
+ .height(Math.min(20, this.clipSize.height() / 3 + 4))
804
+ .border({ width: { bottom: 3, left: 3 }, color: { bottom: '#FFFFFF', left: '#FFFFFF' } })
805
+ .id('petalClip_frostedGlass_rect_leftBottom')
806
+ Row()
807
+ .width(20)
808
+ .height(Math.min(20, this.clipSize.height() / 3 + 4))
809
+ .border({ width: { bottom: 3 }, color: { bottom: '#FFFFFF' } })
810
+ .id('petalClip_frostedGlass_rect_MiddleBottom')
811
+ Row()
812
+ .width(20)
813
+ .height(Math.min(20, this.clipSize.height() / 3 + 4))
814
+ .border({ width: { bottom: 3, right: 3 }, color: { bottom: '#FFFFFF', right: '#FFFFFF' } })
815
+ .id('petalClip_frostedGlass_rect_rightBottom')
816
+ }
817
+ }
818
+ .visibility(this.showCropFrame ? Visibility.Visible : Visibility.Hidden)
819
+ .padding(12)
820
+ }
821
+ .width(this.clipSize.width() + hotspotsWidth * 2)
822
+ .height(this.clipSize.height() + hotspotsWidth * 2)
823
+ .visibility(this.clipVisible ? Visibility.Visible : Visibility.Hidden)
824
+ .position({
825
+ x: this.clipSize.left - hotspotsWidth,
826
+ y: this.clipSize.top - hotspotsWidth,
827
+ })
828
+ .onTouch((event?: TouchEvent) => {
829
+ if (!event) {
830
+ return;
831
+ }
832
+ if (this.isScrolling) {
833
+ return;
834
+ }
835
+ if (event.touches.length >= 2) {
836
+ return;
837
+ }
838
+ let touch = event.touches[0];
839
+ let eventX = touch.x;
840
+ let eventY = touch.y;
841
+ if (event.type == TouchType.Down) {
842
+ if (this.isTouched) {
843
+ return;
844
+ }
845
+ this.isTouched = true;
846
+ let eventLeft = eventX < (2 * hotspotsWidth);
847
+ let eventTop = eventY < (2 * hotspotsWidth);
848
+ let eventRight = eventX > this.clipSize.width();
849
+ let eventBottom = eventY > this.clipSize.height();
850
+
851
+ let eventCount = (eventLeft ? 1 : 0) + (eventTop ? 1 : 0) + (eventRight ? 1 : 0) + (eventBottom ? 1 : 0);
852
+ if (eventCount > 0) {
853
+ this.onDrag(event, eventLeft, eventTop, eventRight, eventBottom, eventCount > 1);
854
+ }
855
+ }
856
+ if (event.type === TouchType.Up) {
857
+ this.isTouched = false;
858
+ }
859
+ })
860
+
861
+ }
862
+ }