@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.
@@ -126,6 +126,7 @@ export struct ImageEditInfo {
126
126
  @State imageFlag: boolean = true;
127
127
  @State angle: number = 0;
128
128
  @State rotateValue: number = 0;
129
+ @State tempClipDuringDrag: Rectangle = new Rectangle(0, 0, 0, 0);
129
130
  private minSize = PHONE_MIN_CROP // 最小宽高
130
131
  private dragObj: DragObj = new DragObj(false);
131
132
  private clb: number = 1
@@ -144,11 +145,92 @@ export struct ImageEditInfo {
144
145
  this.imageHeight = imageInfo.size == undefined ? 0 : imageInfo.size?.height;
145
146
  this.imageWith = imageInfo.size == undefined ? 0 : imageInfo.size?.width;
146
147
  this.initCropBox(this.imageWith, this.imageHeight, this.screenWidth, this.screenHeight);
148
+
149
+ // 初始化后进行一次边界检查
150
+ this.checkAndAdjustCropSize();
147
151
  })
148
152
  }
149
153
  })
150
154
  }
151
155
 
156
+ // 检查并调整裁剪框大小,确保不超过图片显示范围
157
+ private checkAndAdjustCropSize(): void {
158
+ const imgHeight = Math.round(this.imageHeight / this.clb);
159
+ const imgWidth = Math.round(this.imageWith / this.clb);
160
+
161
+ let width = this.clipSize.width();
162
+ let height = this.clipSize.height();
163
+
164
+ // 如果裁剪框比图片显示区域大,则调整到图片显示区域大小
165
+ if (width > imgWidth) {
166
+ width = imgWidth;
167
+ }
168
+ if (height > imgHeight) {
169
+ height = imgHeight;
170
+ }
171
+
172
+ // 确保最小尺寸
173
+ width = Math.max(width, this.minSize);
174
+ height = Math.max(height, this.minSize);
175
+
176
+ // 重新计算位置,保持中心点不变
177
+ const centerX = this.clipSize.left + this.clipSize.width() / 2;
178
+ const centerY = this.clipSize.top + this.clipSize.height() / 2;
179
+
180
+ const newLeft = centerX - width / 2;
181
+ const newTop = centerY - height / 2;
182
+ const newRight = newLeft + width;
183
+ const newBottom = newTop + height;
184
+
185
+ // 确保在最大区域内
186
+ const constrainedLeft = Math.max(newLeft, this.maxClipSize.left);
187
+ const constrainedTop = Math.max(newTop, this.maxClipSize.top);
188
+ const constrainedRight = Math.min(newRight, this.maxClipSize.right);
189
+ const constrainedBottom = Math.min(newBottom, this.maxClipSize.bottom);
190
+
191
+ // 如果约束导致尺寸变化,重新计算
192
+ const finalWidth = constrainedRight - constrainedLeft;
193
+ const finalHeight = constrainedBottom - constrainedTop;
194
+
195
+ if (finalWidth < this.minSize || finalHeight < this.minSize) {
196
+ // 如果约束后小于最小尺寸,调整到最小尺寸
197
+ const adjustedRect = this.adjustToMinSize(constrainedLeft, constrainedTop, constrainedRight, constrainedBottom);
198
+ this.clipSize = adjustedRect;
199
+ } else {
200
+ this.clipSize = new Rectangle(constrainedLeft, constrainedTop, constrainedRight, constrainedBottom);
201
+ }
202
+ }
203
+
204
+ // 调整到最小尺寸
205
+ private adjustToMinSize(left: number, top: number, right: number, bottom: number): Rectangle {
206
+ let width = right - left;
207
+ let height = bottom - top;
208
+
209
+ if (width < this.minSize) {
210
+ width = this.minSize;
211
+ }
212
+ if (height < this.minSize) {
213
+ height = this.minSize;
214
+ }
215
+
216
+ // 保持中心点
217
+ const centerX = (left + right) / 2;
218
+ const centerY = (top + bottom) / 2;
219
+
220
+ const newLeft = centerX - width / 2;
221
+ const newTop = centerY - height / 2;
222
+ const newRight = newLeft + width;
223
+ const newBottom = newTop + height;
224
+
225
+ // 再次确保在最大区域内
226
+ return new Rectangle(
227
+ Math.max(newLeft, this.maxClipSize.left),
228
+ Math.max(newTop, this.maxClipSize.top),
229
+ Math.min(newRight, this.maxClipSize.right),
230
+ Math.min(newBottom, this.maxClipSize.bottom)
231
+ );
232
+ }
233
+
152
234
  resetImg(): void {
153
235
  this.imgScale = 1;
154
236
  this.currentScale = 1;
@@ -156,6 +238,7 @@ export struct ImageEditInfo {
156
238
  this.preOffsetY = 0;
157
239
  }
158
240
 
241
+ // 修改 initCropBox 方法中的相关部分
159
242
  initCropBox(imageWidth: number, imageHeight: number, screenWidth: number, screenHeight: number) {
160
243
  if (imageHeight / imageWidth < screenHeight / screenWidth) {
161
244
  this.clb = imageWidth / (screenWidth - hotspotsWidth * 2);
@@ -172,13 +255,40 @@ export struct ImageEditInfo {
172
255
 
173
256
  this.maxClipSize = new Rectangle(left, top, right, bottom);
174
257
 
258
+ // 计算初始裁剪框尺寸
259
+ let initCropWidth = this.initWidth;
260
+ let initCropHeight = this.initHeight;
261
+
262
+ // 如果初始宽高比图片显示宽高大,则使用图片的显示宽高
263
+ if (initCropWidth > imgWidth) {
264
+ initCropWidth = imgWidth;
265
+ }
266
+ if (initCropHeight > imgHeight) {
267
+ initCropHeight = imgHeight;
268
+ }
269
+
270
+ // 确保裁剪框不超过图片显示范围
271
+ initCropWidth = Math.min(initCropWidth, imgWidth);
272
+ initCropHeight = Math.min(initCropHeight, imgHeight);
273
+
274
+ // 确保最小尺寸
275
+ initCropWidth = Math.max(initCropWidth, this.minSize);
276
+ initCropHeight = Math.max(initCropHeight, this.minSize);
277
+
175
278
  if (this.initWidth > 0) {
176
- left = (screenWidth - this.initWidth) / 2;
177
- right = this.initWidth + left;
279
+ left = (screenWidth - initCropWidth) / 2;
280
+ right = initCropWidth + left;
281
+ } else {
282
+ left = (screenWidth - imgWidth) / 2;
283
+ right = imgWidth + left;
178
284
  }
285
+
179
286
  if (this.initHeight > 0) {
180
- top = (screenHeight - this.initHeight) / 2 - 66;
181
- bottom = this.initHeight + top;
287
+ top = (screenHeight - initCropHeight) / 2 - 66;
288
+ bottom = initCropHeight + top;
289
+ } else {
290
+ top = (screenHeight - imgHeight) / 2 - 66;
291
+ bottom = imgHeight + top;
182
292
  }
183
293
 
184
294
  this.clipSize = new Rectangle(left, top, right, bottom);
@@ -190,7 +300,8 @@ export struct ImageEditInfo {
190
300
  }
191
301
 
192
302
  aboutToDisappear(): void {
193
- let arrayData = ['filePath', 'textTitle', 'chooseText', 'chooseTextColor', 'cancelText', 'cancelTextColor', 'cropperRotate'];
303
+ let arrayData =
304
+ ['filePath', 'textTitle', 'chooseText', 'chooseTextColor', 'cancelText', 'cancelTextColor', 'cropperRotate'];
194
305
  this.clearData(arrayData);
195
306
  }
196
307
 
@@ -202,13 +313,21 @@ export struct ImageEditInfo {
202
313
 
203
314
  @Builder
204
315
  CropTitleBar() {
316
+ Text('')
317
+ .fontColor(Color.White)
318
+ .fontSize(20)
319
+ .textAlign(TextAlign.Center)
320
+ .width('100%')
321
+ .height(35)
322
+ .position({ x: 0, y: 0 })
323
+ .backgroundColor(Color.Black)
205
324
  Text(this.title)
206
325
  .fontColor(Color.White)
207
326
  .fontSize(20)
208
327
  .textAlign(TextAlign.Center)
209
328
  .width('100%')
210
- .height(46)
211
- .position({ x: 0, y: 20 })
329
+ .height(40)
330
+ .position({ x: 0, y: 35 })
212
331
  .backgroundColor(Color.Black)
213
332
  }
214
333
 
@@ -263,6 +382,12 @@ export struct ImageEditInfo {
263
382
  .onActionUpdate((event?: GestureEvent) => {
264
383
  if (event) {
265
384
  this.imgScale = this.currentScale * event.scale;
385
+
386
+ // 缩放时保持图片居中
387
+ this.adjustImageForCentering();
388
+
389
+ // 实时检查并调整裁剪框,防止超出图片范围
390
+ this.constrainCropBoxInRealTime();
266
391
  }
267
392
  })
268
393
  .onActionEnd(() => {
@@ -270,8 +395,14 @@ export struct ImageEditInfo {
270
395
  this.resetImg();
271
396
  this.imgOffSetX = 0;
272
397
  this.imgOffSetY = 0;
398
+ // 重置后检查裁剪框
399
+ this.constrainCropBoxInRealTime();
273
400
  } else {
274
401
  this.currentScale = this.imgScale;
402
+ // 缩放结束后确保图片位置正确
403
+ this.ensureImagePosition();
404
+ // 缩放结束后检查并调整裁剪框
405
+ this.constrainCropBoxInRealTime();
275
406
  }
276
407
  }),
277
408
  PanGesture()
@@ -281,10 +412,22 @@ export struct ImageEditInfo {
281
412
  })
282
413
  .onActionUpdate((event?: GestureEvent) => {
283
414
  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)
415
+ // 限制拖动的边界,确保图片不会被拖出裁剪区域太多
416
+ const maxOffsetX = this.getMaxHorizontalOffset();
417
+ const maxOffsetY = this.getMaxVerticalOffset();
418
+
419
+ this.imgOffSetX = Math.max(-maxOffsetX, Math.min(maxOffsetX, this.preOffsetX + event.offsetX));
420
+ this.imgOffSetY = Math.max(-maxOffsetY, Math.min(maxOffsetY, this.preOffsetY + event.offsetY));
421
+
422
+ // 图片拖动时实时检查裁剪框
423
+ this.constrainCropBoxInRealTime();
287
424
  }
425
+ })
426
+ .onActionEnd(() => {
427
+ // 拖动结束后,自动调整图片位置使其在裁剪框内居中
428
+ this.adjustImageToCenter();
429
+ // 拖动结束后再次检查
430
+ this.constrainCropBoxToImageBounds();
288
431
  }),
289
432
  RotationGesture()
290
433
  .onActionEnd((event: GestureEvent) => {
@@ -303,6 +446,338 @@ export struct ImageEditInfo {
303
446
  })
304
447
  ))
305
448
  }
449
+ // 添加实时约束裁剪框的方法
450
+ private constrainCropBoxInRealTime(): void {
451
+ const imageDisplayBounds = this.getImageDisplayBounds();
452
+ const cropBox = this.clipSize;
453
+
454
+ // 如果裁剪框完全超出图片范围,需要立即调整
455
+ if (cropBox.left < imageDisplayBounds.left - 5 ||
456
+ cropBox.top < imageDisplayBounds.top - 5 ||
457
+ cropBox.right > imageDisplayBounds.right + 5 ||
458
+ cropBox.bottom > imageDisplayBounds.bottom + 5) {
459
+
460
+ this.constrainCropBoxToImageBounds();
461
+ }
462
+ }
463
+
464
+ // 修改约束裁剪框到图片边界的方法,使其更实时响应
465
+ private constrainCropBoxToImageBounds(): void {
466
+ const imageDisplayBounds = this.getImageDisplayBounds();
467
+
468
+ let left = this.clipSize.left;
469
+ let top = this.clipSize.top;
470
+ let right = this.clipSize.right;
471
+ let bottom = this.clipSize.bottom;
472
+
473
+ // 检查并调整裁剪框,确保在图片显示范围内
474
+ const needsAdjustment =
475
+ left < imageDisplayBounds.left ||
476
+ top < imageDisplayBounds.top ||
477
+ right > imageDisplayBounds.right ||
478
+ bottom > imageDisplayBounds.bottom;
479
+
480
+ if (!needsAdjustment) {
481
+ return;
482
+ }
483
+
484
+ // 计算图片显示区域
485
+ const imageLeft = imageDisplayBounds.left;
486
+ const imageTop = imageDisplayBounds.top;
487
+ const imageRight = imageDisplayBounds.right;
488
+ const imageBottom = imageDisplayBounds.bottom;
489
+
490
+ // 约束裁剪框位置
491
+ left = Math.max(left, imageLeft);
492
+ top = Math.max(top, imageTop);
493
+ right = Math.min(right, imageRight);
494
+ bottom = Math.min(bottom, imageBottom);
495
+
496
+ // 确保最小尺寸
497
+ const width = right - left;
498
+ const height = bottom - top;
499
+
500
+ if (width < this.minSize) {
501
+ if (left === imageLeft) {
502
+ // 如果已经在最左边,向右扩展
503
+ right = left + this.minSize;
504
+ if (right > imageRight) {
505
+ right = imageRight;
506
+ left = right - this.minSize;
507
+ }
508
+ } else if (right === imageRight) {
509
+ // 如果已经在最右边,向左扩展
510
+ left = right - this.minSize;
511
+ if (left < imageLeft) {
512
+ left = imageLeft;
513
+ right = left + this.minSize;
514
+ }
515
+ } else {
516
+ // 否则居中调整
517
+ const centerX = (left + right) / 2;
518
+ left = centerX - this.minSize / 2;
519
+ right = centerX + this.minSize / 2;
520
+
521
+ // 确保在边界内
522
+ if (left < imageLeft) {
523
+ const offset = imageLeft - left;
524
+ left += offset;
525
+ right += offset;
526
+ }
527
+ if (right > imageRight) {
528
+ const offset = right - imageRight;
529
+ left -= offset;
530
+ right -= offset;
531
+ }
532
+ }
533
+ }
534
+
535
+ if (height < this.minSize) {
536
+ if (top === imageTop) {
537
+ // 如果已经在最上边,向下扩展
538
+ bottom = top + this.minSize;
539
+ if (bottom > imageBottom) {
540
+ bottom = imageBottom;
541
+ top = bottom - this.minSize;
542
+ }
543
+ } else if (bottom === imageBottom) {
544
+ // 如果已经在最下边,向上扩展
545
+ top = bottom - this.minSize;
546
+ if (top < imageTop) {
547
+ top = imageTop;
548
+ bottom = top + this.minSize;
549
+ }
550
+ } else {
551
+ // 否则居中调整
552
+ const centerY = (top + bottom) / 2;
553
+ top = centerY - this.minSize / 2;
554
+ bottom = centerY + this.minSize / 2;
555
+
556
+ // 确保在边界内
557
+ if (top < imageTop) {
558
+ const offset = imageTop - top;
559
+ top += offset;
560
+ bottom += offset;
561
+ }
562
+ if (bottom > imageBottom) {
563
+ const offset = bottom - imageBottom;
564
+ top -= offset;
565
+ bottom -= offset;
566
+ }
567
+ }
568
+ }
569
+
570
+ // 如果宽高比固定,需要保持比例
571
+ if (this.clipRatio > 0) {
572
+ const currentWidth = right - left;
573
+ const currentHeight = bottom - top;
574
+ const currentRatio = currentWidth / currentHeight;
575
+
576
+ if (Math.abs(currentRatio - this.clipRatio) > 0.01) {
577
+ const centerX = (left + right) / 2;
578
+ const centerY = (top + bottom) / 2;
579
+
580
+ let newWidth = 0, newHeight = 0;
581
+ if (currentRatio > this.clipRatio) {
582
+ // 太宽,需要减小宽度
583
+ newHeight = currentHeight;
584
+ newWidth = newHeight * this.clipRatio;
585
+ } else {
586
+ // 太高,需要减小高度
587
+ newWidth = currentWidth;
588
+ newHeight = newWidth / this.clipRatio;
589
+ }
590
+
591
+ // 确保最小尺寸
592
+ newWidth = Math.max(newWidth, this.minSize);
593
+ newHeight = Math.max(newHeight, this.minSize);
594
+
595
+ left = centerX - newWidth / 2;
596
+ right = centerX + newWidth / 2;
597
+ top = centerY - newHeight / 2;
598
+ bottom = centerY + newHeight / 2;
599
+
600
+ // 再次确保在边界内
601
+ left = Math.max(left, imageLeft);
602
+ top = Math.max(top, imageTop);
603
+ right = Math.min(right, imageRight);
604
+ bottom = Math.min(bottom, imageBottom);
605
+ }
606
+ }
607
+
608
+ // 应用调整后的裁剪框
609
+ this.clipSize = new Rectangle(left, top, right, bottom);
610
+ }
611
+
612
+ private getImageDisplayBounds(): Rectangle {
613
+
614
+ // 计算不考虑缩放和偏移时的图片显示区域(基于 maxClipSize)
615
+ const containerLeft = this.maxClipSize.left;
616
+ const containerTop = this.maxClipSize.top;
617
+ const containerRight = this.maxClipSize.right;
618
+ const containerBottom = this.maxClipSize.bottom;
619
+
620
+ const containerWidth = containerRight - containerLeft;
621
+ const containerHeight = containerBottom - containerTop;
622
+
623
+ // 图片的实际显示尺寸(考虑缩放)
624
+ const scaledWidth = containerWidth * this.imgScale;
625
+ const scaledHeight = containerHeight * this.imgScale;
626
+
627
+ // 图片中心点位置(考虑偏移)
628
+ const containerCenterX = (containerLeft + containerRight) / 2;
629
+ const containerCenterY = (containerTop + containerBottom) / 2;
630
+
631
+ const centerX = containerCenterX + this.imgOffSetX;
632
+ const centerY = containerCenterY + this.imgOffSetY;
633
+
634
+ // 计算图片的实际显示边界
635
+ const left = centerX - scaledWidth / 2;
636
+ const top = centerY - scaledHeight / 2;
637
+ const right = left + scaledWidth;
638
+ const bottom = top + scaledHeight;
639
+
640
+ return new Rectangle(left, top, right, bottom);
641
+ }
642
+
643
+ // 获取图片可以水平移动的最大偏移量
644
+ private getMaxHorizontalOffset(): number {
645
+ const imageDisplayBounds = this.getImageDisplayBounds();
646
+ const cropBounds = this.clipSize;
647
+
648
+ // 计算图片超出裁剪框的部分
649
+ const imageWidth = imageDisplayBounds.width();
650
+ const cropWidth = cropBounds.width();
651
+
652
+ if (imageWidth <= cropWidth) {
653
+ // 图片比裁剪框小或相等,不需要偏移
654
+ return 0;
655
+ }
656
+
657
+ // 图片比裁剪框大,可以偏移的量为图片超出裁剪框宽度的一半
658
+ return Math.max(0, (imageWidth - cropWidth) / 2);
659
+ }
660
+
661
+ // 获取图片可以垂直移动的最大偏移量
662
+ private getMaxVerticalOffset(): number {
663
+ const imageDisplayBounds = this.getImageDisplayBounds();
664
+ const cropBounds = this.clipSize;
665
+
666
+ // 计算图片超出裁剪框的部分
667
+ const imageHeight = imageDisplayBounds.height();
668
+ const cropHeight = cropBounds.height();
669
+
670
+ if (imageHeight <= cropHeight) {
671
+ // 图片比裁剪框小或相等,不需要偏移
672
+ return 0;
673
+ }
674
+
675
+ // 图片比裁剪框大,可以偏移的量为图片超出裁剪框高度的一半
676
+ return Math.max(0, (imageHeight - cropHeight) / 2);
677
+ }
678
+
679
+ private adjustImageToCenter(): void {
680
+
681
+ // 只进行边界检查,不自动调整位置
682
+ const imageDisplayBounds = this.getImageDisplayBounds();
683
+ const cropBounds = this.clipSize;
684
+
685
+ // 检查裁剪框是否完全在图片范围内,如果超出则进行微调
686
+ const tolerance = 5; // 调整的阈值
687
+
688
+ let deltaX = 0;
689
+ let deltaY = 0;
690
+
691
+ // 只有在超出阈值时才进行调整
692
+ if (cropBounds.left < imageDisplayBounds.left - tolerance) {
693
+ deltaX = imageDisplayBounds.left - cropBounds.left;
694
+ } else if (cropBounds.right > imageDisplayBounds.right + tolerance) {
695
+ deltaX = imageDisplayBounds.right - cropBounds.right;
696
+ }
697
+
698
+ if (cropBounds.top < imageDisplayBounds.top - tolerance) {
699
+ deltaY = imageDisplayBounds.top - cropBounds.top;
700
+ } else if (cropBounds.bottom > imageDisplayBounds.bottom + tolerance) {
701
+ deltaY = imageDisplayBounds.bottom - cropBounds.bottom;
702
+ }
703
+
704
+ // 如果需要进行调整,则应用
705
+ if (deltaX !== 0 || deltaY !== 0) {
706
+ const maxOffsetX = this.getMaxHorizontalOffset();
707
+ const maxOffsetY = this.getMaxVerticalOffset();
708
+
709
+ if (imageDisplayBounds.width() <= cropBounds.width()) {
710
+ this.imgOffSetX = 0;
711
+ } else {
712
+ this.imgOffSetX = Math.max(-maxOffsetX, Math.min(maxOffsetX, this.imgOffSetX + deltaX));
713
+ }
714
+
715
+ if (imageDisplayBounds.height() <= cropBounds.height()) {
716
+ this.imgOffSetY = 0;
717
+ } else {
718
+ this.imgOffSetY = Math.max(-maxOffsetY, Math.min(maxOffsetY, this.imgOffSetY + deltaY));
719
+ }
720
+ }
721
+ }
722
+
723
+ // 缩放时保持图片居中
724
+ private adjustImageForCentering(): void {
725
+ if (this.imgScale <= 1) {
726
+ // 缩放比例小于等于1时,图片居中
727
+ this.imgOffSetX = 0;
728
+ this.imgOffSetY = 0;
729
+ } else {
730
+ // 放大时,根据当前缩放比例调整位置,保持裁剪框在图片中心
731
+ const currentCenterX = this.screenWidth / 2;
732
+ const currentCenterY = (this.screenHeight - 66) / 2;
733
+
734
+ const imageDisplayBounds = this.getImageDisplayBounds();
735
+ const cropBounds = this.clipSize;
736
+
737
+ // 确保裁剪框在图片显示范围内
738
+ if (imageDisplayBounds.width() > cropBounds.width()) {
739
+ const cropCenterX = cropBounds.left + cropBounds.width() / 2;
740
+ const deltaX = cropCenterX - currentCenterX;
741
+ this.imgOffSetX = Math.max(-this.getMaxHorizontalOffset(),
742
+ Math.min(this.getMaxHorizontalOffset(),
743
+ this.imgOffSetX + deltaX * 0.1));
744
+ }
745
+
746
+ if (imageDisplayBounds.height() > cropBounds.height()) {
747
+ const cropCenterY = cropBounds.top + cropBounds.height() / 2;
748
+ const deltaY = cropCenterY - currentCenterY;
749
+ this.imgOffSetY = Math.max(-this.getMaxVerticalOffset(),
750
+ Math.min(this.getMaxVerticalOffset(),
751
+ this.imgOffSetY + deltaY * 0.1));
752
+ }
753
+ }
754
+ }
755
+
756
+ // 确保图片位置正确(缩放结束后调用)
757
+ private ensureImagePosition(): void {
758
+ const imageDisplayBounds = this.getImageDisplayBounds();
759
+ const cropBounds = this.clipSize;
760
+
761
+ // 如果图片比裁剪框小,居中显示
762
+ if (imageDisplayBounds.width() <= cropBounds.width() &&
763
+ imageDisplayBounds.height() <= cropBounds.height()) {
764
+ this.imgOffSetX = 0;
765
+ this.imgOffSetY = 0;
766
+ return;
767
+ }
768
+
769
+ // 检查图片是否完全覆盖裁剪框
770
+ const imageCoversCrop =
771
+ imageDisplayBounds.left <= cropBounds.left &&
772
+ imageDisplayBounds.top <= cropBounds.top &&
773
+ imageDisplayBounds.right >= cropBounds.right &&
774
+ imageDisplayBounds.bottom >= cropBounds.bottom;
775
+
776
+ if (!imageCoversCrop) {
777
+ // 调整图片位置,确保裁剪框在图片范围内
778
+ this.adjustImagePositionToFitCrop();
779
+ }
780
+ }
306
781
 
307
782
  @Builder
308
783
  BottomToolbar() {
@@ -356,25 +831,30 @@ export struct ImageEditInfo {
356
831
  .fontColor(this.chooseTextColor)
357
832
  .onClick(() => {
358
833
  if (this.icon) {
359
- let cropHeight = this.angle % 180 ===0 ? this.imageHeight : this.imageWith
360
- let cropWidth = this.angle % 180 ===0 ? this.imageWith : this.imageHeight
834
+ let cropHeight = this.angle % 180 === 0 ? this.imageHeight : this.imageWith
835
+ let cropWidth = this.angle % 180 === 0 ? this.imageWith : this.imageHeight
361
836
  let xOff = cropWidth * (this.imgScale - 1) / 2;
362
837
  let yOff = cropHeight * (this.imgScale - 1) / 2;
363
838
  let region: image.Region = { x: 0, y: 0, size: { height: cropHeight, width: cropWidth } };
364
839
  region.x =
365
- (this.clipSize.left - this.maxClipSize.left) / this.imgScale * this.clb + (xOff / this.imgScale) - this.imgOffSetX / this.imgScale * this.clb;
366
- region.size.width = cropWidth - region.x - (this.maxClipSize.right - this.clipSize.right) / this.imgScale * this.clb
367
- - (xOff / this.imgScale) - this.imgOffSetX / this.imgScale * this.clb;
840
+ (this.clipSize.left - this.maxClipSize.left) / this.imgScale * this.clb + (xOff / this.imgScale) -
841
+ this.imgOffSetX / this.imgScale * this.clb;
842
+ region.size.width =
843
+ cropWidth - region.x - (this.maxClipSize.right - this.clipSize.right) / this.imgScale * this.clb
844
+ - (xOff / this.imgScale) - this.imgOffSetX / this.imgScale * this.clb;
368
845
  region.y =
369
- (this.clipSize.top - this.maxClipSize.top) / this.imgScale * this.clb + (yOff / this.imgScale) - this.imgOffSetY / this.imgScale * this.clb;
370
- region.size.height = cropHeight - region.y - (this.maxClipSize.bottom - this.clipSize.bottom) / this.imgScale * this.clb
371
- - (yOff / this.imgScale) - this.imgOffSetY / this.imgScale * this.clb;
846
+ (this.clipSize.top - this.maxClipSize.top) / this.imgScale * this.clb + (yOff / this.imgScale) -
847
+ this.imgOffSetY / this.imgScale * this.clb;
848
+ region.size.height =
849
+ cropHeight - region.y - (this.maxClipSize.bottom - this.clipSize.bottom) / this.imgScale * this.clb
850
+ - (yOff / this.imgScale) - this.imgOffSetY / this.imgScale * this.clb;
372
851
  this.icon.crop(region, async (err: BusinessError) => {
373
852
  if (err != undefined) {
374
853
  console.error("Failed to crop pixelmap.");
375
854
  return;
376
855
  } else {
377
856
  let imgPath = await encode(this, this.icon);
857
+
378
858
  AppStorage.setOrCreate('cropImagePath', imgPath)
379
859
  AppStorage.setOrCreate('cropRect', {
380
860
  width: region.size.width,
@@ -389,7 +869,7 @@ export struct ImageEditInfo {
389
869
  })
390
870
  }
391
871
  .width('100%')
392
- .height(56)
872
+ .height(66)
393
873
  .position({ x: 0, y: (this.screenHeight - 66) })
394
874
  .backgroundColor(Color.Black)
395
875
  }
@@ -406,6 +886,8 @@ export struct ImageEditInfo {
406
886
  .then(() => {
407
887
  this.angle = this.angle + Constants.CLOCK_WISE;
408
888
  this.flushPixelMapChange(this.angle);
889
+ // 旋转后约束裁剪框
890
+ this.constrainCropBoxToImageBounds();
409
891
  })
410
892
  } catch (error) {
411
893
  }
@@ -420,6 +902,8 @@ export struct ImageEditInfo {
420
902
  .then(() => {
421
903
  this.angle = this.angle + Constants.ANTI_CLOCK;
422
904
  this.flushPixelMapChange(this.angle);
905
+ // 旋转后约束裁剪框
906
+ this.constrainCropBoxToImageBounds();
423
907
  })
424
908
  } catch (error) {
425
909
  }
@@ -429,103 +913,289 @@ export struct ImageEditInfo {
429
913
  flushPixelMapChange(angle: number) {
430
914
  this.isPixelMapChange = !this.isPixelMapChange;
431
915
  let clipAngle = angle / 90;
916
+
917
+ // 根据旋转角度更新图片显示尺寸
432
918
  switch (Math.abs(clipAngle % 4)) {
433
919
  case 0:
434
920
  case 2:
435
- this.initCropBox(this.imageWith, this.imageHeight, this.screenWidth, this.screenHeight);
921
+ // 0度或180度,宽高不变
922
+ this.updateMaxClipSize(this.imageWith, this.imageHeight);
436
923
  break;
437
924
  default:
438
- this.initCropBox(this.imageHeight, this.imageWith, this.screenWidth, this.screenHeight);
925
+ // 90度或270度,宽高互换
926
+ this.updateMaxClipSize(this.imageHeight, this.imageWith);
439
927
  break;
440
928
  }
441
929
  }
442
930
 
931
+ updateMaxClipSize(imageWidth: number, imageHeight: number) {
932
+ if (imageHeight / imageWidth < (this.screenHeight - 132) / this.screenWidth) {
933
+ this.clb = imageWidth / (this.screenWidth - hotspotsWidth * 2);
934
+ } else {
935
+ this.clb = imageHeight / (this.screenHeight - hotspotsWidth * 2 - 66 * 2);
936
+ }
937
+
938
+ let imgHeight = Math.round(imageHeight / this.clb);
939
+ let imgWidth = Math.round(imageWidth / this.clb);
940
+
941
+ let left = (this.screenWidth - imgWidth) / 2;
942
+ let top = (this.screenHeight - imgHeight) / 2 - 66;
943
+ let right = imgWidth + left;
944
+ let bottom = imgHeight + top;
945
+
946
+ this.maxClipSize = new Rectangle(left, top, right, bottom);
947
+
948
+ // 重置裁剪框位置和大小,确保在最大区域内
949
+ this.resetCropBox();
950
+ }
951
+
952
+ resetCropBox() {
953
+ // 将裁剪框重置为最大区域的中心,大小调整为最大区域的80%(或保持最小尺寸)
954
+ const maxWidth = this.maxClipSize.width();
955
+ const maxHeight = this.maxClipSize.height();
956
+
957
+ const cropWidth = Math.max(this.minSize, maxWidth * 0.8);
958
+ const cropHeight = Math.max(this.minSize, maxHeight * 0.8);
959
+
960
+ const centerX = (this.maxClipSize.left + this.maxClipSize.right) / 2;
961
+ const centerY = (this.maxClipSize.top + this.maxClipSize.bottom) / 2;
962
+
963
+ const left = centerX - cropWidth / 2;
964
+ const top = centerY - cropHeight / 2;
965
+ const right = left + cropWidth;
966
+ const bottom = top + cropHeight;
967
+
968
+ this.clipSize = new Rectangle(left, top, right, bottom);
969
+ }
970
+
971
+ constrainClipToMaxBounds() {
972
+ let constrainedLeft = Math.max(this.clipSize.left, this.maxClipSize.left);
973
+ let constrainedTop = Math.max(this.clipSize.top, this.maxClipSize.top);
974
+ let constrainedRight = Math.min(this.clipSize.right, this.maxClipSize.right);
975
+ let constrainedBottom = Math.min(this.clipSize.bottom, this.maxClipSize.bottom);
976
+
977
+ // 如果裁剪框太小,调整到最小尺寸
978
+ if (constrainedRight - constrainedLeft < this.minSize) {
979
+ if (this.clipRatio > 0) {
980
+ // 有固定比例,保持比例调整
981
+ const width = this.minSize;
982
+ const height = width / this.clipRatio;
983
+ constrainedRight = constrainedLeft + width;
984
+ constrainedBottom = constrainedTop + height;
985
+ } else {
986
+ // 自由比例,只调整宽度
987
+ constrainedRight = constrainedLeft + this.minSize;
988
+ }
989
+ }
990
+
991
+ if (constrainedBottom - constrainedTop < this.minSize) {
992
+ if (this.clipRatio > 0) {
993
+ // 有固定比例,保持比例调整
994
+ const height = this.minSize;
995
+ const width = height * this.clipRatio;
996
+ constrainedRight = constrainedLeft + width;
997
+ constrainedBottom = constrainedTop + height;
998
+ } else {
999
+ // 自由比例,只调整高度
1000
+ constrainedBottom = constrainedTop + this.minSize;
1001
+ }
1002
+ }
1003
+
1004
+ // 确保裁剪框在最大区域内
1005
+ if (constrainedRight > this.maxClipSize.right) {
1006
+ const overflow = constrainedRight - this.maxClipSize.right;
1007
+ constrainedRight -= overflow;
1008
+ constrainedLeft -= overflow;
1009
+ }
1010
+
1011
+ if (constrainedBottom > this.maxClipSize.bottom) {
1012
+ const overflow = constrainedBottom - this.maxClipSize.bottom;
1013
+ constrainedBottom -= overflow;
1014
+ constrainedTop -= overflow;
1015
+ }
1016
+
1017
+ if (constrainedLeft < this.maxClipSize.left) {
1018
+ const underflow = this.maxClipSize.left - constrainedLeft;
1019
+ constrainedLeft += underflow;
1020
+ constrainedRight += underflow;
1021
+ }
1022
+
1023
+ if (constrainedTop < this.maxClipSize.top) {
1024
+ const underflow = this.maxClipSize.top - constrainedTop;
1025
+ constrainedTop += underflow;
1026
+ constrainedBottom += underflow;
1027
+ }
1028
+
1029
+ this.clipSize = new Rectangle(
1030
+ constrainedLeft,
1031
+ constrainedTop,
1032
+ constrainedRight,
1033
+ constrainedBottom
1034
+ );
1035
+ }
1036
+
443
1037
  flushPixelMap() {
444
1038
  const temp = this.icon;
445
1039
  this.icon = undefined;
446
1040
  this.icon = temp;
447
1041
  }
448
1042
 
449
- onDragStartFun = (event: TouchEvent, left: boolean, top: boolean, right: boolean, bottom: boolean, multiCrop: boolean): void => {
450
- let downPos = new DownPos(this.clipSize.left, this.clipSize.top, this.clipSize.bottom, this.clipSize.right);
451
- let action = new Action(left, top, right, bottom);
452
- this.dragObj = new DragObj(true, event.touches[0].screenX, event.touches[0].screenY, action, downPos, multiCrop);
1043
+ onDragStartFun =
1044
+ (event: TouchEvent, left: boolean, top: boolean, right: boolean, bottom: boolean, multiCrop: boolean): void => {
1045
+ let downPos = new DownPos(this.clipSize.left, this.clipSize.top, this.clipSize.bottom, this.clipSize.right);
1046
+ let action = new Action(left, top, right, bottom);
1047
+ this.dragObj = new DragObj(true, event.touches[0].screenX, event.touches[0].screenY, action, downPos, multiCrop);
1048
+ }
1049
+ private endImageDrag(): void {
1050
+ // 移除强制调整逻辑,只在严重超出边界时进行调整
1051
+ const imageDisplayBounds = this.getImageDisplayBounds();
1052
+ const cropBounds = this.clipSize;
1053
+
1054
+ // 设置严重超出的阈值
1055
+ const severeThreshold = 10;
1056
+
1057
+ const isSeverelyOutside =
1058
+ cropBounds.left < imageDisplayBounds.left - severeThreshold ||
1059
+ cropBounds.top < imageDisplayBounds.top - severeThreshold ||
1060
+ cropBounds.right > imageDisplayBounds.right + severeThreshold ||
1061
+ cropBounds.bottom > imageDisplayBounds.bottom + severeThreshold;
1062
+
1063
+ if (isSeverelyOutside) {
1064
+ // 只有在严重超出时才调整
1065
+ this.adjustImagePositionToFitCrop();
1066
+ }
453
1067
  }
454
1068
 
455
- private endImageDrag(): void {
456
- // 图片的宽高
457
- let imageWidth = this.imageHeight;
458
- let imageHeight = this.imageWith;
459
- if ([0, 2].includes(Math.abs((this.angle / 90) % 4))) {
460
- imageWidth = this.imageWith;
461
- imageHeight = this.imageHeight;
1069
+ adjustImagePositionToFitCrop() {
1070
+ const imageDisplayBounds = this.getImageDisplayBounds();
1071
+ const cropBounds = this.clipSize;
1072
+
1073
+ // 只有当裁剪框完全超出图片时才调整
1074
+ const needsAdjustment =
1075
+ cropBounds.left < imageDisplayBounds.left ||
1076
+ cropBounds.right > imageDisplayBounds.right ||
1077
+ cropBounds.top < imageDisplayBounds.top ||
1078
+ cropBounds.bottom > imageDisplayBounds.bottom;
1079
+
1080
+ if (!needsAdjustment) {
1081
+ return;
462
1082
  }
463
1083
 
464
- let crop: Rectangle = new Rectangle(this.clipSize.left, this.clipSize.top, this.clipSize.right, this.clipSize.bottom);
1084
+ // 计算需要调整的偏移量
1085
+ let deltaX = 0;
1086
+ let deltaY = 0;
1087
+
1088
+ if (cropBounds.left < imageDisplayBounds.left) {
1089
+ deltaX = imageDisplayBounds.left - cropBounds.left;
1090
+ } else if (cropBounds.right > imageDisplayBounds.right) {
1091
+ deltaX = imageDisplayBounds.right - cropBounds.right;
1092
+ }
465
1093
 
466
- // 裁剪框和图片间的偏移量
467
- let extraLeft = 0;
468
- let extraTop = 0;
469
- let extraRight = 0;
470
- let extraBottom = 0;
1094
+ if (cropBounds.top < imageDisplayBounds.top) {
1095
+ deltaY = imageDisplayBounds.top - cropBounds.top;
1096
+ } else if (cropBounds.bottom > imageDisplayBounds.bottom) {
1097
+ deltaY = imageDisplayBounds.bottom - cropBounds.bottom;
1098
+ }
471
1099
 
472
- // 图片实际的宽高
473
- let actualHeight = 0;
474
- let actualWidth = 0;
1100
+ // 计算图片可移动的最大范围
1101
+ const maxOffsetX = this.getMaxHorizontalOffset();
1102
+ const maxOffsetY = this.getMaxVerticalOffset();
475
1103
 
476
- // 图片偏移量
477
- let imgOffSetX = this.imgOffSetX;
478
- let imgOffSetY = this.imgOffSetY;
1104
+ // 应用调整,但要确保不会超出范围
1105
+ if (deltaX !== 0 && maxOffsetX > 0) {
1106
+ this.imgOffSetX = Math.max(-maxOffsetX, Math.min(maxOffsetX, this.imgOffSetX + deltaX));
1107
+ }
479
1108
 
480
- // 图片缩放偏移量
481
- let scaleOffsetX = 0;
482
- let scaleOffsetY = 0;
1109
+ if (deltaY !== 0 && maxOffsetY > 0) {
1110
+ this.imgOffSetY = Math.max(-maxOffsetY, Math.min(maxOffsetY, this.imgOffSetY + deltaY));
1111
+ }
1112
+ }
483
1113
 
484
- // 根据图片宽高比计算实际的图片宽高和偏移量
485
- // 计算图片的实际宽高和实际偏移量
486
- actualHeight = Math.round(imageHeight / this.clb);
487
- actualWidth = Math.round(imageWidth / this.clb);
488
- extraLeft = (this.screenWidth - actualWidth) / 2;
489
- extraTop = (this.screenHeight - actualHeight) / 2 - 66;
490
- extraRight = actualWidth + extraLeft;
491
- extraBottom = actualHeight + extraTop;
1114
+ forceCropInsideImage() {
1115
+ const imageDisplayBounds = this.getImageDisplayBounds();
492
1116
 
493
- // 计算缩放偏移量
494
- scaleOffsetX = this.imgScale > 1 ? 0 - ((extraRight - extraLeft) * this.imgScale - (extraRight - extraLeft)) / 2 : 0;
495
- scaleOffsetY = this.imgScale > 1 ? 0 - ((extraBottom - extraTop) * this.imgScale - (extraBottom - extraTop)) / 2 : 0;
1117
+ // 设置允许超出的范围
1118
+ const tolerance = 10;
496
1119
 
497
- // 计算图片四个边缘的位置
498
- const imageRight = this.imgOffSetX + actualWidth;
499
- const imageBottom = this.imgOffSetY + actualHeight;
1120
+ let left = this.clipSize.left;
1121
+ let top = this.clipSize.top;
1122
+ let right = this.clipSize.right;
1123
+ let bottom = this.clipSize.bottom;
500
1124
 
501
- // 计算裁剪框四个边缘的位置
502
- const cropRight = crop.right
503
- const cropBottom = crop.bottom;
1125
+ // 只有在超出容忍范围时才进行调整
1126
+ const needAdjustLeft = left < imageDisplayBounds.left - tolerance;
1127
+ const needAdjustTop = top < imageDisplayBounds.top - tolerance;
1128
+ const needAdjustRight = right > imageDisplayBounds.right + tolerance;
1129
+ const needAdjustBottom = bottom > imageDisplayBounds.bottom + tolerance;
504
1130
 
505
- // 判断图片左边缘是否在裁剪框内
506
- if (this.imgOffSetX + scaleOffsetX > crop.left - extraLeft) {
507
- // 图片左边缘已经被拖动到裁剪框内,需要还原位置
508
- imgOffSetX = crop.left - extraLeft - scaleOffsetX;
1131
+ if (needAdjustLeft) {
1132
+ left = Math.max(left, imageDisplayBounds.left);
1133
+ }
1134
+ if (needAdjustTop) {
1135
+ top = Math.max(top, imageDisplayBounds.top);
1136
+ }
1137
+ if (needAdjustRight) {
1138
+ right = Math.min(right, imageDisplayBounds.right);
1139
+ }
1140
+ if (needAdjustBottom) {
1141
+ bottom = Math.min(bottom, imageDisplayBounds.bottom);
509
1142
  }
510
1143
 
511
- // 判断图片上边缘是否在裁剪框内
512
- if (this.imgOffSetY + scaleOffsetY > crop.top - extraTop) {
513
- // 图片上边缘已经被拖动到裁剪框内,需要还原位置
514
- imgOffSetY = crop.top - extraTop - scaleOffsetY;
1144
+ // 保持比例
1145
+ if (this.clipRatio > 0) {
1146
+ const width = right - left;
1147
+ const height = bottom - top;
1148
+ const currentRatio = width / height;
1149
+
1150
+ if (Math.abs(currentRatio - this.clipRatio) > 0.01) {
1151
+ // 需要调整以保持比例
1152
+ const targetWidth = height * this.clipRatio;
1153
+ const targetHeight = width / this.clipRatio;
1154
+
1155
+ if (targetWidth <= width) {
1156
+ right = left + targetWidth;
1157
+ } else {
1158
+ bottom = top + targetHeight;
1159
+ }
1160
+ }
515
1161
  }
516
1162
 
517
- // 判断图片右边缘是否在裁剪框内
518
- if (imageRight - scaleOffsetX < cropRight - extraLeft) {
519
- imgOffSetX = cropRight - extraRight + scaleOffsetX;
1163
+ this.clipSize = new Rectangle(left, top, right, bottom);
1164
+ }
1165
+
1166
+ // 拖动结束后让裁剪框和图像内容居中
1167
+ private centerCropBoxAfterDrag(): void {
1168
+ this.animateCropToCenter();
1169
+ }
1170
+
1171
+ // 确保裁剪框在允许范围内
1172
+ private constrainCropPosition(left: number, top: number, right: number, bottom: number): Rectangle {
1173
+ // 确保裁剪框在最大允许范围内
1174
+ let constrainedLeft = Math.max(left, this.maxClipSize.left);
1175
+ let constrainedTop = Math.max(top, this.maxClipSize.top);
1176
+ let constrainedRight = Math.min(right, this.maxClipSize.right);
1177
+ let constrainedBottom = Math.min(bottom, this.maxClipSize.bottom);
1178
+
1179
+ // 如果移动后超出边界,保持尺寸但调整位置
1180
+ if (constrainedRight - constrainedLeft < this.clipSize.width()) {
1181
+ // 水平方向被限制,保持宽度调整位置
1182
+ constrainedRight = constrainedLeft + this.clipSize.width();
1183
+ if (constrainedRight > this.maxClipSize.right) {
1184
+ constrainedRight = this.maxClipSize.right;
1185
+ constrainedLeft = constrainedRight - this.clipSize.width();
1186
+ }
520
1187
  }
521
1188
 
522
- // 判断图片下边缘是否在裁剪框内
523
- if (imageBottom - scaleOffsetY < cropBottom - extraTop) {
524
- // 图片下边缘已经被拖动到裁剪框内,需要还原位置
525
- imgOffSetY = cropBottom - extraBottom + scaleOffsetY;
1189
+ if (constrainedBottom - constrainedTop < this.clipSize.height()) {
1190
+ // 垂直方向被限制,保持高度调整位置
1191
+ constrainedBottom = constrainedTop + this.clipSize.height();
1192
+ if (constrainedBottom > this.maxClipSize.bottom) {
1193
+ constrainedBottom = this.maxClipSize.bottom;
1194
+ constrainedTop = constrainedBottom - this.clipSize.height();
1195
+ }
526
1196
  }
527
- this.imgOffSetX = imgOffSetX;
528
- this.imgOffSetY = imgOffSetY;
1197
+
1198
+ return new Rectangle(constrainedLeft, constrainedTop, constrainedRight, constrainedBottom);
529
1199
  }
530
1200
 
531
1201
  touchHandler: (event: TouchEvent | undefined) => void = (event: TouchEvent | undefined): void => {
@@ -535,11 +1205,27 @@ export struct ImageEditInfo {
535
1205
 
536
1206
  if (event.type === TouchType.Up) {
537
1207
  this.dragObj.dragging = false;
538
- this.endImageDrag();
1208
+
1209
+ // 如果是在拖动裁剪框,则居中
1210
+ if (!this.imageFlag) {
1211
+ // 如果有临时保存的裁剪框位置,使用它
1212
+ if (this.tempClipDuringDrag.width() > 0 && this.tempClipDuringDrag.height() > 0) {
1213
+ this.clipSize = this.tempClipDuringDrag;
1214
+ this.tempClipDuringDrag = new Rectangle(0, 0, 0, 0);
1215
+ }
1216
+
1217
+ // 让裁剪框居中
1218
+ this.centerCropBoxAfterDrag();
1219
+ } else {
1220
+ // 如果是拖动图片,调整图片位置
1221
+ this.endImageDrag();
1222
+ }
539
1223
  }
540
1224
 
541
1225
  if (event.type === TouchType.Down) {
542
- Logger.info(TAG, "Down")
1226
+ Logger.info(TAG, "Down");
1227
+ // 重置临时变量
1228
+ this.tempClipDuringDrag = new Rectangle(0, 0, 0, 0);
543
1229
  }
544
1230
 
545
1231
  if (event.type === TouchType.Move) {
@@ -548,29 +1234,83 @@ export struct ImageEditInfo {
548
1234
  return;
549
1235
  }
550
1236
  this.imageFlag = false;
551
- if (this.freeStyleCropEnabled) {
1237
+ if (!this.freeStyleCropEnabled) {
552
1238
  return;
553
1239
  }
554
1240
  let touch = event.touches[0];
555
1241
  let delX: number = touch.screenX - this.dragObj.x;
556
1242
  let delY: number = touch.screenY - this.dragObj.y;
557
- // 裁剪和缩放平移收拾互斥
558
1243
 
559
1244
  let newPosition = this.clipSize.clone();
560
-
561
1245
  let direction = this.dragObj.action;
1246
+
562
1247
  if (this.dragObj.multiCrop) {
563
1248
  this.getMultiCropRect(delX, delY, newPosition, direction, event.pressure);
564
1249
  } else {
565
1250
  this.getSingleCropRect(delX, delY, newPosition, direction, event.pressure);
566
1251
  }
1252
+
1253
+ // 直接应用约束后的裁剪框位置
1254
+ if (this.tempClipDuringDrag.width() > 0 && this.tempClipDuringDrag.height() > 0) {
1255
+ const constrainedClip = this.constrainClipSize(this.tempClipDuringDrag);
1256
+ this.clipSize = constrainedClip;
1257
+ }
567
1258
  }
1259
+
1260
+ };
1261
+
1262
+ // 平滑移动裁剪框到中心
1263
+ private animateCropToCenter(): void {
1264
+ const screenCenterX = this.screenWidth / 2;
1265
+ const screenCenterY = (this.screenHeight - 66 * 2) / 2;
1266
+
1267
+ const cropCenterX = this.clipSize.left + this.clipSize.width() / 2;
1268
+ const cropCenterY = this.clipSize.top + this.clipSize.height() / 2;
1269
+
1270
+ const deltaX = screenCenterX - cropCenterX;
1271
+ const deltaY = screenCenterY - cropCenterY;
1272
+
1273
+ // 使用动画移动
1274
+ animateTo({
1275
+ duration: 300, // 300ms动画
1276
+ tempo: 1.0,
1277
+ curve: Curve.EaseOut
1278
+ }, () => {
1279
+ const newLeft = this.clipSize.left + deltaX;
1280
+ const newTop = this.clipSize.top + deltaY;
1281
+ const newRight = this.clipSize.right + deltaX;
1282
+ const newBottom = this.clipSize.bottom + deltaY;
1283
+
1284
+ const constrainedRect = this.constrainCropPosition(newLeft, newTop, newRight, newBottom);
1285
+ this.clipSize = constrainedRect;
1286
+
1287
+ // 同时移动图像
1288
+ this.imgOffSetX += deltaX;
1289
+ this.imgOffSetY += deltaY;
1290
+
1291
+ // 确保边界
1292
+ const maxOffsetX = this.getMaxHorizontalOffset();
1293
+ const maxOffsetY = this.getMaxVerticalOffset();
1294
+
1295
+ this.imgOffSetX = Math.max(-maxOffsetX, Math.min(maxOffsetX, this.imgOffSetX));
1296
+ this.imgOffSetY = Math.max(-maxOffsetY, Math.min(maxOffsetY, this.imgOffSetY));
1297
+ });
568
1298
  }
569
1299
 
570
1300
  getMultiCropRect(delX: number, delY: number, newPosition: Rectangle, direction: Action, pressure: number) {
1301
+ // 获取图片实际显示边界
1302
+ const imageDisplayBounds = this.getImageDisplayBounds();
1303
+
571
1304
  if (direction.left) {
572
1305
  newPosition.left = this.dragObj.downPos.left + delX;
573
1306
  let width = this.clipSize.right - newPosition.left;
1307
+
1308
+ // 只检查图片边界,不检查 maxClipSize
1309
+ if (newPosition.left < imageDisplayBounds.left) {
1310
+ newPosition.left = imageDisplayBounds.left;
1311
+ }
1312
+
1313
+ // 确保最小尺寸
574
1314
  if (this.clipRatio > 0) {
575
1315
  let height = width / this.clipRatio;
576
1316
  if (height <= this.minSize) {
@@ -579,13 +1319,17 @@ export struct ImageEditInfo {
579
1319
  } else if (width <= this.minSize) {
580
1320
  newPosition.left = this.clipSize.right - this.minSize;
581
1321
  }
582
- if (newPosition.left < this.maxClipSize.left) {
583
- newPosition.left = this.maxClipSize.left;
584
- }
585
1322
  }
1323
+
586
1324
  if (direction.top) {
587
1325
  newPosition.top = this.dragObj.downPos.top + delY;
588
1326
  let height = this.clipSize.bottom - newPosition.top;
1327
+
1328
+ // 只检查图片边界,不检查 maxClipSize
1329
+ if (newPosition.top < imageDisplayBounds.top) {
1330
+ newPosition.top = imageDisplayBounds.top;
1331
+ }
1332
+
589
1333
  if (this.clipRatio > 0) {
590
1334
  let width = height * this.clipRatio;
591
1335
  if (width <= this.minSize) {
@@ -594,13 +1338,17 @@ export struct ImageEditInfo {
594
1338
  } else if (height <= this.minSize) {
595
1339
  newPosition.top = this.clipSize.bottom - this.minSize;
596
1340
  }
597
- if (newPosition.top < this.maxClipSize.top) {
598
- newPosition.top = this.maxClipSize.top;
599
- }
600
1341
  }
1342
+
601
1343
  if (direction.right) {
602
1344
  newPosition.right = this.dragObj.downPos.right + delX;
603
1345
  let width = newPosition.right - this.clipSize.left;
1346
+
1347
+ // 只检查图片边界,不检查 maxClipSize
1348
+ if (newPosition.right > imageDisplayBounds.right) {
1349
+ newPosition.right = imageDisplayBounds.right;
1350
+ }
1351
+
604
1352
  if (this.clipRatio > 0) {
605
1353
  let height = width / this.clipRatio;
606
1354
  if (height <= this.minSize) {
@@ -609,13 +1357,17 @@ export struct ImageEditInfo {
609
1357
  } else if (width <= this.minSize) {
610
1358
  newPosition.right = this.clipSize.left + this.minSize;
611
1359
  }
612
- if (newPosition.right > this.maxClipSize.right) {
613
- newPosition.right = this.maxClipSize.right;
614
- }
615
1360
  }
1361
+
616
1362
  if (direction.bottom) {
617
1363
  newPosition.bottom = this.dragObj.downPos.bottom + delY;
618
1364
  let height = newPosition.bottom - this.clipSize.top;
1365
+
1366
+ // 只检查图片边界,不检查 maxClipSize
1367
+ if (newPosition.bottom > imageDisplayBounds.bottom) {
1368
+ newPosition.bottom = imageDisplayBounds.bottom;
1369
+ }
1370
+
619
1371
  if (this.clipRatio > 0) {
620
1372
  let width = height * this.clipRatio;
621
1373
  if (width <= this.minSize) {
@@ -624,85 +1376,182 @@ export struct ImageEditInfo {
624
1376
  } else if (height <= this.minSize) {
625
1377
  newPosition.bottom = this.clipSize.top + this.minSize;
626
1378
  }
627
- if (newPosition.bottom > this.maxClipSize.bottom) {
628
- newPosition.bottom = this.maxClipSize.bottom;
629
- }
630
1379
  }
631
- this.clipSize = new Rectangle(newPosition.left, newPosition.top, newPosition.right, newPosition.bottom);
1380
+
1381
+ this.tempClipDuringDrag = new Rectangle(
1382
+ newPosition.left,
1383
+ newPosition.top,
1384
+ newPosition.right,
1385
+ newPosition.bottom
1386
+ );
1387
+ }
1388
+
1389
+ constrainClipSize(tempClip: Rectangle): Rectangle {
1390
+ const imageDisplayBounds = this.getImageDisplayBounds();
1391
+
1392
+ let left = tempClip.left;
1393
+ let top = tempClip.top;
1394
+ let right = tempClip.right;
1395
+ let bottom = tempClip.bottom;
1396
+
1397
+ // 只约束在图片边界内
1398
+ left = Math.max(left, imageDisplayBounds.left);
1399
+ top = Math.max(top, imageDisplayBounds.top);
1400
+ right = Math.min(right, imageDisplayBounds.right);
1401
+ bottom = Math.min(bottom, imageDisplayBounds.bottom);
1402
+
1403
+ // 确保最小尺寸
1404
+ const width = right - left;
1405
+ const height = bottom - top;
1406
+
1407
+ // 如果尺寸太小,调整到最小尺寸,保持中心点
1408
+ if (width < this.minSize) {
1409
+ const centerX = (left + right) / 2;
1410
+ left = centerX - this.minSize / 2;
1411
+ right = centerX + this.minSize / 2;
1412
+ }
1413
+
1414
+ if (height < this.minSize) {
1415
+ const centerY = (top + bottom) / 2;
1416
+ top = centerY - this.minSize / 2;
1417
+ bottom = centerY + this.minSize / 2;
1418
+ }
1419
+
1420
+ // 再次确保在图片边界内(调整后可能超出)
1421
+ left = Math.max(left, imageDisplayBounds.left);
1422
+ top = Math.max(top, imageDisplayBounds.top);
1423
+ right = Math.min(right, imageDisplayBounds.right);
1424
+ bottom = Math.min(bottom, imageDisplayBounds.bottom);
1425
+
1426
+ return new Rectangle(left, top, right, bottom);
632
1427
  }
633
1428
 
1429
+
634
1430
  getSingleCropRect(delX: number, delY: number, newPosition: Rectangle, direction: Action, pressure: number) {
635
- if (direction.left) {
636
- let newX = this.dragObj.downPos.left + delX;
637
- newX = newX < this.dragObj.downPos.right - this.minSize ? newX : this.dragObj.downPos.right - this.minSize
638
- if (this.clipRatio > 0) {
639
- let width = this.clipSize.right - newX;
640
- let height = width / this.clipRatio;
641
- newPosition.top = this.clipSize.top - (height - this.clipSize.height()) / 2;
642
- newPosition.bottom = this.clipSize.bottom + (height - this.clipSize.height()) / 2;
643
- if (height <= this.minSize) {
644
- newX = this.clipSize.right - this.minSize * this.clipRatio;
1431
+ // 获取图片实际显示边界
1432
+ const imageDisplayBounds = this.getImageDisplayBounds();
1433
+
1434
+ // 确定主要拖动方向
1435
+ let mainDirection: 'left' | 'right' | 'top' | 'bottom' | null = null;
1436
+
1437
+ if (direction.left && !direction.right && !direction.top && !direction.bottom) {
1438
+ mainDirection = 'left';
1439
+ } else if (direction.right && !direction.left && !direction.top && !direction.bottom) {
1440
+ mainDirection = 'right';
1441
+ } else if (direction.top && !direction.bottom && !direction.left && !direction.right) {
1442
+ mainDirection = 'top';
1443
+ } else if (direction.bottom && !direction.top && !direction.left && !direction.right) {
1444
+ mainDirection = 'bottom';
1445
+ } else {
1446
+ // 如果是角上拖动,应该调用 getMultiCropRect
1447
+ return;
1448
+ }
1449
+
1450
+ // 根据主方向处理
1451
+ switch (mainDirection) {
1452
+ case 'left': {
1453
+ let newLeft = this.dragObj.downPos.left + delX;
1454
+
1455
+ // 确保在图片边界内
1456
+ newLeft = Math.max(newLeft, imageDisplayBounds.left);
1457
+
1458
+ // 确保不会使裁剪框太小
1459
+ const maxLeft = this.clipSize.right - this.minSize;
1460
+ newLeft = Math.min(newLeft, maxLeft);
1461
+ newPosition.left = newLeft;
1462
+
1463
+ // 如果有固定比例,调整另一边
1464
+ if (this.clipRatio > 0) {
1465
+ const newWidth = this.clipSize.right - newLeft;
1466
+ const newHeight = newWidth / this.clipRatio;
1467
+
1468
+ // 保持中心点不变
1469
+ const centerY = (this.clipSize.top + this.clipSize.bottom) / 2;
1470
+ newPosition.top = centerY - newHeight / 2;
1471
+ newPosition.bottom = centerY + newHeight / 2;
645
1472
  }
1473
+ break;
646
1474
  }
647
- newPosition.left = newX;
648
- if (newPosition.left < this.maxClipSize.left) {
649
- newPosition.left = this.maxClipSize.left;
650
- }
651
- } else if (direction.right) {
652
- let newX = this.dragObj.downPos.right + delX;
653
- if (newX < this.dragObj.downPos.left + this.minSize) {
654
- newX = this.dragObj.downPos.left + this.minSize;
655
- }
656
- if (this.clipRatio > 0) {
657
- let width = newX - this.clipSize.left;
658
- let height = width / this.clipRatio;
659
- newPosition.top = this.clipSize.top - (height - this.clipSize.height()) / 2;
660
- newPosition.bottom = this.clipSize.bottom + (height - this.clipSize.height()) / 2;
661
- if (height <= this.minSize) {
662
- newX = this.minSize * this.clipRatio + this.clipSize.left;
1475
+
1476
+ case 'right': {
1477
+ let newRight = this.dragObj.downPos.right + delX;
1478
+
1479
+ // 确保在图片边界内
1480
+ newRight = Math.min(newRight, imageDisplayBounds.right);
1481
+
1482
+ // 确保不会使裁剪框太小
1483
+ const minRight = this.clipSize.left + this.minSize;
1484
+ newRight = Math.max(newRight, minRight);
1485
+ newPosition.right = newRight;
1486
+
1487
+ // 如果有固定比例,调整另一边
1488
+ if (this.clipRatio > 0) {
1489
+ const newWidth = newRight - this.clipSize.left;
1490
+ const newHeight = newWidth / this.clipRatio;
1491
+
1492
+ // 保持中心点不变
1493
+ const centerY = (this.clipSize.top + this.clipSize.bottom) / 2;
1494
+ newPosition.top = centerY - newHeight / 2;
1495
+ newPosition.bottom = centerY + newHeight / 2;
663
1496
  }
1497
+ break;
664
1498
  }
665
- newPosition.right = newX;
666
- if (newPosition.right > this.maxClipSize.right) {
667
- newPosition.right = this.maxClipSize.right;
668
- }
669
- } else if (direction.top) {
670
- let newY = this.dragObj.downPos.top + delY;
671
- newY = newY < this.dragObj.downPos.bottom - this.minSize ? newY : this.dragObj.downPos.bottom - this.minSize
672
- if (this.clipRatio > 0) {
673
- let height = this.clipSize.bottom - newY;
674
- let width = height * this.clipRatio;
675
- newPosition.left = this.clipSize.left - (width - this.clipSize.width()) / 2;
676
- newPosition.right = this.clipSize.right + (width - this.clipSize.width()) / 2;
677
- if (width <= this.minSize) {
678
- newY = this.clipSize.bottom - this.minSize / this.clipRatio;
1499
+
1500
+ case 'top': {
1501
+ let newTop = this.dragObj.downPos.top + delY;
1502
+
1503
+ // 确保在图片边界内
1504
+ newTop = Math.max(newTop, imageDisplayBounds.top);
1505
+
1506
+ // 确保不会使裁剪框太小
1507
+ const maxTop = this.clipSize.bottom - this.minSize;
1508
+ newTop = Math.min(newTop, maxTop);
1509
+ newPosition.top = newTop;
1510
+
1511
+ // 如果有固定比例,调整另一边
1512
+ if (this.clipRatio > 0) {
1513
+ const newHeight = this.clipSize.bottom - newTop;
1514
+ const newWidth = newHeight * this.clipRatio;
1515
+
1516
+ // 保持中心点不变
1517
+ const centerX = (this.clipSize.left + this.clipSize.right) / 2;
1518
+ newPosition.left = centerX - newWidth / 2;
1519
+ newPosition.right = centerX + newWidth / 2;
679
1520
  }
1521
+ break;
680
1522
  }
681
- newPosition.top = newY;
682
- if (newPosition.top < this.maxClipSize.top) {
683
- newPosition.top = this.maxClipSize.top;
684
- }
685
- } else if (direction.bottom) {
686
- let newY = this.dragObj.downPos.bottom + delY;
687
- if (newY < this.dragObj.downPos.top + this.minSize) {
688
- newY = this.dragObj.downPos.top + this.minSize;
689
- }
690
- if (this.clipRatio > 0) {
691
- let height = newY - this.clipSize.top;
692
- let width = height * this.clipRatio;
693
- newPosition.left = this.clipSize.left - (width - this.clipSize.width()) / 2;
694
- newPosition.right = this.clipSize.right + (width - this.clipSize.width()) / 2;
695
- if (width <= this.minSize) {
696
- newY = this.minSize / this.clipRatio + this.clipSize.top;
1523
+
1524
+ case 'bottom': {
1525
+ let newBottom = this.dragObj.downPos.bottom + delY;
1526
+
1527
+ // 确保在图片边界内
1528
+ newBottom = Math.min(newBottom, imageDisplayBounds.bottom);
1529
+
1530
+ // 确保不会使裁剪框太小
1531
+ const minBottom = this.clipSize.top + this.minSize;
1532
+ newBottom = Math.max(newBottom, minBottom);
1533
+ newPosition.bottom = newBottom;
1534
+
1535
+ // 如果有固定比例,调整另一边
1536
+ if (this.clipRatio > 0) {
1537
+ const newHeight = newBottom - this.clipSize.top;
1538
+ const newWidth = newHeight * this.clipRatio;
1539
+
1540
+ // 保持中心点不变
1541
+ const centerX = (this.clipSize.left + this.clipSize.right) / 2;
1542
+ newPosition.left = centerX - newWidth / 2;
1543
+ newPosition.right = centerX + newWidth / 2;
697
1544
  }
698
- }
699
- newPosition.bottom = newY;
700
- if (newPosition.bottom > this.maxClipSize.bottom) {
701
- newPosition.bottom = this.maxClipSize.bottom;
1545
+ break;
702
1546
  }
703
1547
  }
704
- this.clipSize = new Rectangle(newPosition.left, newPosition.top, newPosition.right, newPosition.bottom);
705
1548
 
1549
+ this.tempClipDuringDrag = new Rectangle(
1550
+ newPosition.left,
1551
+ newPosition.top,
1552
+ newPosition.right,
1553
+ newPosition.bottom
1554
+ );
706
1555
  }
707
1556
 
708
1557
  build() {
@@ -717,11 +1566,11 @@ export struct ImageEditInfo {
717
1566
  }
718
1567
  }
719
1568
 
720
-
721
1569
  @Component
722
1570
  export struct RectangleSizer {
723
1571
  @Prop clipSize: Rectangle = new Rectangle(0, 0, 0, 0);
724
- public onDrag: (event: TouchEvent, left: boolean, top: boolean, right: boolean, bottom: boolean, multiCrop: boolean) => void = Event
1572
+ public onDrag: (event: TouchEvent, left: boolean, top: boolean, right: boolean, bottom: boolean,
1573
+ multiCrop: boolean) => void = Event
725
1574
  @Prop clipVisible: boolean = true;
726
1575
  @State isScrolling: boolean = false;
727
1576
  @State isTouched: boolean = false;
@@ -729,6 +1578,18 @@ export struct RectangleSizer {
729
1578
  @StorageProp('showCropFrame') showCropFrame: boolean = true;
730
1579
  @StorageProp('showCropGuidelines') showCropGuidelines: boolean = true;
731
1580
  @Prop isScreenPortrait: boolean = false;
1581
+ // 添加本地触摸状态
1582
+ @State dragStartX: number = 0;
1583
+ @State dragStartY: number = 0;
1584
+ @State isDraggingEdge: boolean = false;
1585
+ // 添加触摸状态变量
1586
+ @State touchStartTime: number = 0;
1587
+ @State touchStartX: number = 0;
1588
+ @State touchStartY: number = 0;
1589
+
1590
+ // 定义边缘检测区域 - 优化边缘检测
1591
+ private readonly EDGE_THRESHOLD: number = 25; // 边缘检测阈值
1592
+ private readonly CORNER_THRESHOLD: number = 25; // 角检测阈值
732
1593
 
733
1594
  build() {
734
1595
  Stack() {
@@ -828,7 +1689,7 @@ export struct RectangleSizer {
828
1689
  x: this.clipSize.left - hotspotsWidth,
829
1690
  y: this.clipSize.top - hotspotsWidth,
830
1691
  })
831
- .onTouch((event?: TouchEvent) => {
1692
+ .onTouch((event?: TouchEvent)=>{
832
1693
  if (!event) {
833
1694
  return;
834
1695
  }
@@ -838,28 +1699,68 @@ export struct RectangleSizer {
838
1699
  if (event.touches.length >= 2) {
839
1700
  return;
840
1701
  }
1702
+
841
1703
  let touch = event.touches[0];
842
1704
  let eventX = touch.x;
843
1705
  let eventY = touch.y;
1706
+
844
1707
  if (event.type == TouchType.Down) {
1708
+ // 记录触摸开始时间和位置
1709
+ this.touchStartTime = Date.now();
1710
+ this.touchStartX = eventX;
1711
+ this.touchStartY = eventY;
1712
+
845
1713
  if (this.isTouched) {
846
1714
  return;
847
1715
  }
848
1716
  this.isTouched = true;
849
- let eventLeft = eventX < (2 * hotspotsWidth);
850
- let eventTop = eventY < (2 * hotspotsWidth);
851
- let eventRight = eventX > this.clipSize.width();
852
- let eventBottom = eventY > this.clipSize.height();
853
-
854
- let eventCount = (eventLeft ? 1 : 0) + (eventTop ? 1 : 0) + (eventRight ? 1 : 0) + (eventBottom ? 1 : 0);
855
- if (eventCount > 0) {
856
- this.onDrag(event, eventLeft, eventTop, eventRight, eventBottom, eventCount > 1);
1717
+
1718
+ // 计算触摸点相对于裁剪框的位置
1719
+ // 计算到各个边的距离
1720
+ const distanceToLeft = eventX;
1721
+ const distanceToTop = eventY;
1722
+ const distanceToRight = (this.clipSize.width() + hotspotsWidth * 2) - eventX;
1723
+ const distanceToBottom = (this.clipSize.height() + hotspotsWidth * 2) - eventY;
1724
+
1725
+ // 检测是否在角上
1726
+ const isNearLeftTop = distanceToLeft <= this.CORNER_THRESHOLD && distanceToTop <= this.CORNER_THRESHOLD;
1727
+ const isNearLeftBottom = distanceToLeft <= this.CORNER_THRESHOLD && distanceToBottom <= this.CORNER_THRESHOLD;
1728
+ const isNearRightTop = distanceToRight <= this.CORNER_THRESHOLD && distanceToTop <= this.CORNER_THRESHOLD;
1729
+ const isNearRightBottom = distanceToRight <= this.CORNER_THRESHOLD && distanceToBottom <= this.CORNER_THRESHOLD;
1730
+
1731
+ // 检测是否在边缘
1732
+ const isNearLeft = distanceToLeft <= this.EDGE_THRESHOLD && !isNearLeftTop && !isNearLeftBottom;
1733
+ const isNearTop = distanceToTop <= this.EDGE_THRESHOLD && !isNearLeftTop && !isNearRightTop;
1734
+ const isNearRight = distanceToRight <= this.EDGE_THRESHOLD && !isNearRightTop && !isNearRightBottom;
1735
+ const isNearBottom = distanceToBottom <= this.EDGE_THRESHOLD && !isNearLeftBottom && !isNearRightBottom;
1736
+
1737
+ // 根据检测结果触发相应的事件
1738
+ if (isNearLeftTop) {
1739
+ this.onDrag(event, true, true, false, false, true);
1740
+ } else if (isNearLeftBottom) {
1741
+ this.onDrag(event, true, false, false, true, true);
1742
+ } else if (isNearRightTop) {
1743
+ this.onDrag(event, false, true, true, false, true);
1744
+ } else if (isNearRightBottom) {
1745
+ this.onDrag(event, false, false, true, true, true);
1746
+ } else if (isNearLeft) {
1747
+ this.onDrag(event, true, false, false, false, false);
1748
+ } else if (isNearTop) {
1749
+ this.onDrag(event, false, true, false, false, false);
1750
+ } else if (isNearRight) {
1751
+ this.onDrag(event, false, false, true, false, false);
1752
+ } else if (isNearBottom) {
1753
+ this.onDrag(event, false, false, false, true, false);
857
1754
  }
858
1755
  }
1756
+
859
1757
  if (event.type === TouchType.Up) {
860
1758
  this.isTouched = false;
1759
+ // 重置触摸状态
1760
+ this.touchStartTime = 0;
1761
+ this.touchStartX = 0;
1762
+ this.touchStartY = 0;
861
1763
  }
862
1764
  })
863
-
864
1765
  }
865
1766
  }