@react-native-ohos/react-native-image-crop-picker 0.40.5-rc.7 → 0.40.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.
@@ -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,14 +255,40 @@ export struct ImageEditInfo {
172
255
 
173
256
  this.maxClipSize = new Rectangle(left, top, right, bottom);
174
257
 
175
- if (this.initWidth > 0 && this.initHeight > 0) {
176
- let flag = imageWidth/this.initWidth < imageHeight/this.initHeight;
177
- let _imgWidth = flag ? imgWidth : Math.round(imgHeight * this.initWidth / this.initHeight)
178
- let _imgHeight = !flag ? imgHeight : Math.round(imgWidth * this.initHeight / this.initWidth)
179
- left = (screenWidth - _imgWidth) / 2;
180
- right = _imgWidth + left;
181
- top = (screenHeight - _imgHeight) / 2 - 66;
182
- bottom = _imgHeight + top;
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
+
278
+ if (this.initWidth > 0) {
279
+ left = (screenWidth - initCropWidth) / 2;
280
+ right = initCropWidth + left;
281
+ } else {
282
+ left = (screenWidth - imgWidth) / 2;
283
+ right = imgWidth + left;
284
+ }
285
+
286
+ if (this.initHeight > 0) {
287
+ top = (screenHeight - initCropHeight) / 2 - 66;
288
+ bottom = initCropHeight + top;
289
+ } else {
290
+ top = (screenHeight - imgHeight) / 2 - 66;
291
+ bottom = imgHeight + top;
183
292
  }
184
293
 
185
294
  this.clipSize = new Rectangle(left, top, right, bottom);
@@ -191,7 +300,8 @@ export struct ImageEditInfo {
191
300
  }
192
301
 
193
302
  aboutToDisappear(): void {
194
- let arrayData = ['filePath', 'textTitle', 'chooseText', 'chooseTextColor', 'cancelText', 'cancelTextColor', 'cropperRotate'];
303
+ let arrayData =
304
+ ['filePath', 'textTitle', 'chooseText', 'chooseTextColor', 'cancelText', 'cancelTextColor', 'cropperRotate'];
195
305
  this.clearData(arrayData);
196
306
  }
197
307
 
@@ -203,15 +313,22 @@ export struct ImageEditInfo {
203
313
 
204
314
  @Builder
205
315
  CropTitleBar() {
206
- Text(this.title)
316
+ Text('')
207
317
  .fontColor(Color.White)
208
318
  .fontSize(20)
209
319
  .textAlign(TextAlign.Center)
210
320
  .width('100%')
211
- .height(66)
321
+ .height(35)
212
322
  .position({ x: 0, y: 0 })
213
323
  .backgroundColor(Color.Black)
214
- .padding({top:32})
324
+ Text(this.title)
325
+ .fontColor(Color.White)
326
+ .fontSize(20)
327
+ .textAlign(TextAlign.Center)
328
+ .width('100%')
329
+ .height(40)
330
+ .position({ x: 0, y: 35 })
331
+ .backgroundColor(Color.Black)
215
332
  }
216
333
 
217
334
  @Builder
@@ -265,6 +382,12 @@ export struct ImageEditInfo {
265
382
  .onActionUpdate((event?: GestureEvent) => {
266
383
  if (event) {
267
384
  this.imgScale = this.currentScale * event.scale;
385
+
386
+ // 缩放时保持图片居中
387
+ this.adjustImageForCentering();
388
+
389
+ // 实时检查并调整裁剪框,防止超出图片范围
390
+ this.constrainCropBoxInRealTime();
268
391
  }
269
392
  })
270
393
  .onActionEnd(() => {
@@ -272,8 +395,14 @@ export struct ImageEditInfo {
272
395
  this.resetImg();
273
396
  this.imgOffSetX = 0;
274
397
  this.imgOffSetY = 0;
398
+ // 重置后检查裁剪框
399
+ this.constrainCropBoxInRealTime();
275
400
  } else {
276
401
  this.currentScale = this.imgScale;
402
+ // 缩放结束后确保图片位置正确
403
+ this.ensureImagePosition();
404
+ // 缩放结束后检查并调整裁剪框
405
+ this.constrainCropBoxInRealTime();
277
406
  }
278
407
  }),
279
408
  PanGesture()
@@ -283,10 +412,22 @@ export struct ImageEditInfo {
283
412
  })
284
413
  .onActionUpdate((event?: GestureEvent) => {
285
414
  if (event && this.imageFlag) {
286
- this.imgOffSetX = this.preOffsetX + event.offsetX;
287
- this.imgOffSetY = this.preOffsetY + event.offsetY;
288
- 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();
289
424
  }
425
+ })
426
+ .onActionEnd(() => {
427
+ // 拖动结束后,自动调整图片位置使其在裁剪框内居中
428
+ this.adjustImageToCenter();
429
+ // 拖动结束后再次检查
430
+ this.constrainCropBoxToImageBounds();
290
431
  }),
291
432
  RotationGesture()
292
433
  .onActionEnd((event: GestureEvent) => {
@@ -305,6 +446,338 @@ export struct ImageEditInfo {
305
446
  })
306
447
  ))
307
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
+ }
308
781
 
309
782
  @Builder
310
783
  BottomToolbar() {
@@ -358,28 +831,30 @@ export struct ImageEditInfo {
358
831
  .fontColor(this.chooseTextColor)
359
832
  .onClick(() => {
360
833
  if (this.icon) {
361
- let cropHeight = this.angle % 180 ===0 ? this.imageHeight : this.imageWith
362
- 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
363
836
  let xOff = cropWidth * (this.imgScale - 1) / 2;
364
837
  let yOff = cropHeight * (this.imgScale - 1) / 2;
365
838
  let region: image.Region = { x: 0, y: 0, size: { height: cropHeight, width: cropWidth } };
366
839
  region.x =
367
- (this.clipSize.left - this.maxClipSize.left) / this.imgScale * this.clb + (xOff / this.imgScale) - this.imgOffSetX / this.imgScale * this.clb;
368
- region.size.width = Math.round(cropWidth - region.x - (this.maxClipSize.right - this.clipSize.right) / this.imgScale * this.clb
369
- - (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;
370
845
  region.y =
371
- (this.clipSize.top - this.maxClipSize.top) / this.imgScale * this.clb + (yOff / this.imgScale) - this.imgOffSetY / this.imgScale * this.clb;
372
- region.size.height = Math.round(cropHeight - region.y - (this.maxClipSize.bottom - this.clipSize.bottom) / this.imgScale * this.clb
373
- - (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;
374
851
  this.icon.crop(region, async (err: BusinessError) => {
375
852
  if (err != undefined) {
376
853
  console.error("Failed to crop pixelmap.");
377
854
  return;
378
855
  } else {
379
- if (this.initWidth > 0 && this.initHeight > 0){
380
- this.icon?.scaleSync( this.initWidth / region.size.width, this.initHeight / region.size.height )
381
- }
382
856
  let imgPath = await encode(this, this.icon);
857
+
383
858
  AppStorage.setOrCreate('cropImagePath', imgPath)
384
859
  AppStorage.setOrCreate('cropRect', {
385
860
  width: region.size.width,
@@ -411,6 +886,8 @@ export struct ImageEditInfo {
411
886
  .then(() => {
412
887
  this.angle = this.angle + Constants.CLOCK_WISE;
413
888
  this.flushPixelMapChange(this.angle);
889
+ // 旋转后约束裁剪框
890
+ this.constrainCropBoxToImageBounds();
414
891
  })
415
892
  } catch (error) {
416
893
  }
@@ -425,6 +902,8 @@ export struct ImageEditInfo {
425
902
  .then(() => {
426
903
  this.angle = this.angle + Constants.ANTI_CLOCK;
427
904
  this.flushPixelMapChange(this.angle);
905
+ // 旋转后约束裁剪框
906
+ this.constrainCropBoxToImageBounds();
428
907
  })
429
908
  } catch (error) {
430
909
  }
@@ -434,103 +913,289 @@ export struct ImageEditInfo {
434
913
  flushPixelMapChange(angle: number) {
435
914
  this.isPixelMapChange = !this.isPixelMapChange;
436
915
  let clipAngle = angle / 90;
916
+
917
+ // 根据旋转角度更新图片显示尺寸
437
918
  switch (Math.abs(clipAngle % 4)) {
438
919
  case 0:
439
920
  case 2:
440
- this.initCropBox(this.imageWith, this.imageHeight, this.screenWidth, this.screenHeight);
921
+ // 0度或180度,宽高不变
922
+ this.updateMaxClipSize(this.imageWith, this.imageHeight);
441
923
  break;
442
924
  default:
443
- this.initCropBox(this.imageHeight, this.imageWith, this.screenWidth, this.screenHeight);
925
+ // 90度或270度,宽高互换
926
+ this.updateMaxClipSize(this.imageHeight, this.imageWith);
444
927
  break;
445
928
  }
446
929
  }
447
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
+
448
1037
  flushPixelMap() {
449
1038
  const temp = this.icon;
450
1039
  this.icon = undefined;
451
1040
  this.icon = temp;
452
1041
  }
453
1042
 
454
- onDragStartFun = (event: TouchEvent, left: boolean, top: boolean, right: boolean, bottom: boolean, multiCrop: boolean): void => {
455
- let downPos = new DownPos(this.clipSize.left, this.clipSize.top, this.clipSize.bottom, this.clipSize.right);
456
- let action = new Action(left, top, right, bottom);
457
- 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
+ }
458
1067
  }
459
1068
 
460
- private endImageDrag(): void {
461
- // 图片的宽高
462
- let imageWidth = this.imageHeight;
463
- let imageHeight = this.imageWith;
464
- if ([0, 2].includes(Math.abs((this.angle / 90) % 4))) {
465
- imageWidth = this.imageWith;
466
- 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;
467
1082
  }
468
1083
 
469
- 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;
470
1087
 
471
- // 裁剪框和图片间的偏移量
472
- let extraLeft = 0;
473
- let extraTop = 0;
474
- let extraRight = 0;
475
- let extraBottom = 0;
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
+ }
476
1093
 
477
- // 图片实际的宽高
478
- let actualHeight = 0;
479
- let actualWidth = 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
+ }
480
1099
 
481
- // 图片偏移量
482
- let imgOffSetX = this.imgOffSetX;
483
- let imgOffSetY = this.imgOffSetY;
1100
+ // 计算图片可移动的最大范围
1101
+ const maxOffsetX = this.getMaxHorizontalOffset();
1102
+ const maxOffsetY = this.getMaxVerticalOffset();
484
1103
 
485
- // 图片缩放偏移量
486
- let scaleOffsetX = 0;
487
- let scaleOffsetY = 0;
1104
+ // 应用调整,但要确保不会超出范围
1105
+ if (deltaX !== 0 && maxOffsetX > 0) {
1106
+ this.imgOffSetX = Math.max(-maxOffsetX, Math.min(maxOffsetX, this.imgOffSetX + deltaX));
1107
+ }
488
1108
 
489
- // 根据图片宽高比计算实际的图片宽高和偏移量
490
- // 计算图片的实际宽高和实际偏移量
491
- actualHeight = Math.round(imageHeight / this.clb);
492
- actualWidth = Math.round(imageWidth / this.clb);
493
- extraLeft = (this.screenWidth - actualWidth) / 2;
494
- extraTop = (this.screenHeight - actualHeight) / 2 - 66;
495
- extraRight = actualWidth + extraLeft;
496
- extraBottom = actualHeight + extraTop;
1109
+ if (deltaY !== 0 && maxOffsetY > 0) {
1110
+ this.imgOffSetY = Math.max(-maxOffsetY, Math.min(maxOffsetY, this.imgOffSetY + deltaY));
1111
+ }
1112
+ }
497
1113
 
498
- // 计算缩放偏移量
499
- scaleOffsetX = this.imgScale > 1 ? 0 - ((extraRight - extraLeft) * this.imgScale - (extraRight - extraLeft)) / 2 : 0;
500
- scaleOffsetY = this.imgScale > 1 ? 0 - ((extraBottom - extraTop) * this.imgScale - (extraBottom - extraTop)) / 2 : 0;
1114
+ forceCropInsideImage() {
1115
+ const imageDisplayBounds = this.getImageDisplayBounds();
501
1116
 
502
- // 计算图片四个边缘的位置
503
- const imageRight = this.imgOffSetX + actualWidth;
504
- const imageBottom = this.imgOffSetY + actualHeight;
1117
+ // 设置允许超出的范围
1118
+ const tolerance = 10;
505
1119
 
506
- // 计算裁剪框四个边缘的位置
507
- const cropRight = crop.right
508
- const cropBottom = crop.bottom;
1120
+ let left = this.clipSize.left;
1121
+ let top = this.clipSize.top;
1122
+ let right = this.clipSize.right;
1123
+ let bottom = this.clipSize.bottom;
509
1124
 
510
- // 判断图片左边缘是否在裁剪框内
511
- if (this.imgOffSetX + scaleOffsetX > crop.left - extraLeft) {
512
- // 图片左边缘已经被拖动到裁剪框内,需要还原位置
513
- imgOffSetX = crop.left - extraLeft - scaleOffsetX;
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;
1130
+
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);
514
1142
  }
515
1143
 
516
- // 判断图片上边缘是否在裁剪框内
517
- if (this.imgOffSetY + scaleOffsetY > crop.top - extraTop) {
518
- // 图片上边缘已经被拖动到裁剪框内,需要还原位置
519
- 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
+ }
520
1161
  }
521
1162
 
522
- // 判断图片右边缘是否在裁剪框内
523
- if (imageRight - scaleOffsetX < cropRight - extraLeft) {
524
- 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
+ }
525
1187
  }
526
1188
 
527
- // 判断图片下边缘是否在裁剪框内
528
- if (imageBottom - scaleOffsetY < cropBottom - extraTop) {
529
- // 图片下边缘已经被拖动到裁剪框内,需要还原位置
530
- 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
+ }
531
1196
  }
532
- this.imgOffSetX = imgOffSetX;
533
- this.imgOffSetY = imgOffSetY;
1197
+
1198
+ return new Rectangle(constrainedLeft, constrainedTop, constrainedRight, constrainedBottom);
534
1199
  }
535
1200
 
536
1201
  touchHandler: (event: TouchEvent | undefined) => void = (event: TouchEvent | undefined): void => {
@@ -540,11 +1205,27 @@ export struct ImageEditInfo {
540
1205
 
541
1206
  if (event.type === TouchType.Up) {
542
1207
  this.dragObj.dragging = false;
543
- 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
+ }
544
1223
  }
545
1224
 
546
1225
  if (event.type === TouchType.Down) {
547
- Logger.info(TAG, "Down")
1226
+ Logger.info(TAG, "Down");
1227
+ // 重置临时变量
1228
+ this.tempClipDuringDrag = new Rectangle(0, 0, 0, 0);
548
1229
  }
549
1230
 
550
1231
  if (event.type === TouchType.Move) {
@@ -559,24 +1240,77 @@ export struct ImageEditInfo {
559
1240
  let touch = event.touches[0];
560
1241
  let delX: number = touch.screenX - this.dragObj.x;
561
1242
  let delY: number = touch.screenY - this.dragObj.y;
562
- // 裁剪和缩放平移收拾互斥
563
1243
 
564
1244
  let newPosition = this.clipSize.clone();
565
-
566
1245
  let direction = this.dragObj.action;
567
-
1246
+
568
1247
  if (this.dragObj.multiCrop) {
569
1248
  this.getMultiCropRect(delX, delY, newPosition, direction, event.pressure);
570
1249
  } else {
571
1250
  this.getSingleCropRect(delX, delY, newPosition, direction, event.pressure);
572
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
+ }
573
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
+ });
574
1298
  }
575
1299
 
576
1300
  getMultiCropRect(delX: number, delY: number, newPosition: Rectangle, direction: Action, pressure: number) {
1301
+ // 获取图片实际显示边界
1302
+ const imageDisplayBounds = this.getImageDisplayBounds();
1303
+
577
1304
  if (direction.left) {
578
1305
  newPosition.left = this.dragObj.downPos.left + delX;
579
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
+ // 确保最小尺寸
580
1314
  if (this.clipRatio > 0) {
581
1315
  let height = width / this.clipRatio;
582
1316
  if (height <= this.minSize) {
@@ -585,13 +1319,17 @@ export struct ImageEditInfo {
585
1319
  } else if (width <= this.minSize) {
586
1320
  newPosition.left = this.clipSize.right - this.minSize;
587
1321
  }
588
- if (newPosition.left < this.maxClipSize.left) {
589
- newPosition.left = this.maxClipSize.left;
590
- }
591
1322
  }
1323
+
592
1324
  if (direction.top) {
593
1325
  newPosition.top = this.dragObj.downPos.top + delY;
594
1326
  let height = this.clipSize.bottom - newPosition.top;
1327
+
1328
+ // 只检查图片边界,不检查 maxClipSize
1329
+ if (newPosition.top < imageDisplayBounds.top) {
1330
+ newPosition.top = imageDisplayBounds.top;
1331
+ }
1332
+
595
1333
  if (this.clipRatio > 0) {
596
1334
  let width = height * this.clipRatio;
597
1335
  if (width <= this.minSize) {
@@ -600,13 +1338,17 @@ export struct ImageEditInfo {
600
1338
  } else if (height <= this.minSize) {
601
1339
  newPosition.top = this.clipSize.bottom - this.minSize;
602
1340
  }
603
- if (newPosition.top < this.maxClipSize.top) {
604
- newPosition.top = this.maxClipSize.top;
605
- }
606
1341
  }
1342
+
607
1343
  if (direction.right) {
608
1344
  newPosition.right = this.dragObj.downPos.right + delX;
609
1345
  let width = newPosition.right - this.clipSize.left;
1346
+
1347
+ // 只检查图片边界,不检查 maxClipSize
1348
+ if (newPosition.right > imageDisplayBounds.right) {
1349
+ newPosition.right = imageDisplayBounds.right;
1350
+ }
1351
+
610
1352
  if (this.clipRatio > 0) {
611
1353
  let height = width / this.clipRatio;
612
1354
  if (height <= this.minSize) {
@@ -615,13 +1357,17 @@ export struct ImageEditInfo {
615
1357
  } else if (width <= this.minSize) {
616
1358
  newPosition.right = this.clipSize.left + this.minSize;
617
1359
  }
618
- if (newPosition.right > this.maxClipSize.right) {
619
- newPosition.right = this.maxClipSize.right;
620
- }
621
1360
  }
1361
+
622
1362
  if (direction.bottom) {
623
1363
  newPosition.bottom = this.dragObj.downPos.bottom + delY;
624
1364
  let height = newPosition.bottom - this.clipSize.top;
1365
+
1366
+ // 只检查图片边界,不检查 maxClipSize
1367
+ if (newPosition.bottom > imageDisplayBounds.bottom) {
1368
+ newPosition.bottom = imageDisplayBounds.bottom;
1369
+ }
1370
+
625
1371
  if (this.clipRatio > 0) {
626
1372
  let width = height * this.clipRatio;
627
1373
  if (width <= this.minSize) {
@@ -630,85 +1376,182 @@ export struct ImageEditInfo {
630
1376
  } else if (height <= this.minSize) {
631
1377
  newPosition.bottom = this.clipSize.top + this.minSize;
632
1378
  }
633
- if (newPosition.bottom > this.maxClipSize.bottom) {
634
- newPosition.bottom = this.maxClipSize.bottom;
635
- }
636
1379
  }
637
- 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);
638
1427
  }
639
1428
 
1429
+
640
1430
  getSingleCropRect(delX: number, delY: number, newPosition: Rectangle, direction: Action, pressure: number) {
641
- if (direction.left) {
642
- let newX = this.dragObj.downPos.left + delX;
643
- newX = newX < this.dragObj.downPos.right - this.minSize ? newX : this.dragObj.downPos.right - this.minSize
644
- if (this.clipRatio > 0) {
645
- let width = this.clipSize.right - newX;
646
- let height = width / this.clipRatio;
647
- newPosition.top = this.clipSize.top - (height - this.clipSize.height()) / 2;
648
- newPosition.bottom = this.clipSize.bottom + (height - this.clipSize.height()) / 2;
649
- if (height <= this.minSize) {
650
- 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;
651
1472
  }
1473
+ break;
652
1474
  }
653
- newPosition.left = newX;
654
- if (newPosition.left < this.maxClipSize.left) {
655
- newPosition.left = this.maxClipSize.left;
656
- }
657
- } else if (direction.right) {
658
- let newX = this.dragObj.downPos.right + delX;
659
- if (newX < this.dragObj.downPos.left + this.minSize) {
660
- newX = this.dragObj.downPos.left + this.minSize;
661
- }
662
- if (this.clipRatio > 0) {
663
- let width = newX - this.clipSize.left;
664
- let height = width / this.clipRatio;
665
- newPosition.top = this.clipSize.top - (height - this.clipSize.height()) / 2;
666
- newPosition.bottom = this.clipSize.bottom + (height - this.clipSize.height()) / 2;
667
- if (height <= this.minSize) {
668
- 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;
669
1496
  }
1497
+ break;
670
1498
  }
671
- newPosition.right = newX;
672
- if (newPosition.right > this.maxClipSize.right) {
673
- newPosition.right = this.maxClipSize.right;
674
- }
675
- } else if (direction.top) {
676
- let newY = this.dragObj.downPos.top + delY;
677
- newY = newY < this.dragObj.downPos.bottom - this.minSize ? newY : this.dragObj.downPos.bottom - this.minSize
678
- if (this.clipRatio > 0) {
679
- let height = this.clipSize.bottom - newY;
680
- let width = height * this.clipRatio;
681
- newPosition.left = this.clipSize.left - (width - this.clipSize.width()) / 2;
682
- newPosition.right = this.clipSize.right + (width - this.clipSize.width()) / 2;
683
- if (width <= this.minSize) {
684
- 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;
685
1520
  }
1521
+ break;
686
1522
  }
687
- newPosition.top = newY;
688
- if (newPosition.top < this.maxClipSize.top) {
689
- newPosition.top = this.maxClipSize.top;
690
- }
691
- } else if (direction.bottom) {
692
- let newY = this.dragObj.downPos.bottom + delY;
693
- if (newY < this.dragObj.downPos.top + this.minSize) {
694
- newY = this.dragObj.downPos.top + this.minSize;
695
- }
696
- if (this.clipRatio > 0) {
697
- let height = newY - this.clipSize.top;
698
- let width = height * this.clipRatio;
699
- newPosition.left = this.clipSize.left - (width - this.clipSize.width()) / 2;
700
- newPosition.right = this.clipSize.right + (width - this.clipSize.width()) / 2;
701
- if (width <= this.minSize) {
702
- 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;
703
1544
  }
704
- }
705
- newPosition.bottom = newY;
706
- if (newPosition.bottom > this.maxClipSize.bottom) {
707
- newPosition.bottom = this.maxClipSize.bottom;
1545
+ break;
708
1546
  }
709
1547
  }
710
- this.clipSize = new Rectangle(newPosition.left, newPosition.top, newPosition.right, newPosition.bottom);
711
1548
 
1549
+ this.tempClipDuringDrag = new Rectangle(
1550
+ newPosition.left,
1551
+ newPosition.top,
1552
+ newPosition.right,
1553
+ newPosition.bottom
1554
+ );
712
1555
  }
713
1556
 
714
1557
  build() {
@@ -723,11 +1566,11 @@ export struct ImageEditInfo {
723
1566
  }
724
1567
  }
725
1568
 
726
-
727
1569
  @Component
728
1570
  export struct RectangleSizer {
729
1571
  @Prop clipSize: Rectangle = new Rectangle(0, 0, 0, 0);
730
- 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
731
1574
  @Prop clipVisible: boolean = true;
732
1575
  @State isScrolling: boolean = false;
733
1576
  @State isTouched: boolean = false;
@@ -735,6 +1578,18 @@ export struct RectangleSizer {
735
1578
  @StorageProp('showCropFrame') showCropFrame: boolean = true;
736
1579
  @StorageProp('showCropGuidelines') showCropGuidelines: boolean = true;
737
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; // 角检测阈值
738
1593
 
739
1594
  build() {
740
1595
  Stack() {
@@ -834,7 +1689,7 @@ export struct RectangleSizer {
834
1689
  x: this.clipSize.left - hotspotsWidth,
835
1690
  y: this.clipSize.top - hotspotsWidth,
836
1691
  })
837
- .onTouch((event?: TouchEvent) => {
1692
+ .onTouch((event?: TouchEvent)=>{
838
1693
  if (!event) {
839
1694
  return;
840
1695
  }
@@ -844,28 +1699,68 @@ export struct RectangleSizer {
844
1699
  if (event.touches.length >= 2) {
845
1700
  return;
846
1701
  }
1702
+
847
1703
  let touch = event.touches[0];
848
1704
  let eventX = touch.x;
849
1705
  let eventY = touch.y;
1706
+
850
1707
  if (event.type == TouchType.Down) {
1708
+ // 记录触摸开始时间和位置
1709
+ this.touchStartTime = Date.now();
1710
+ this.touchStartX = eventX;
1711
+ this.touchStartY = eventY;
1712
+
851
1713
  if (this.isTouched) {
852
1714
  return;
853
1715
  }
854
1716
  this.isTouched = true;
855
- let eventLeft = eventX < (2 * hotspotsWidth);
856
- let eventTop = eventY < (2 * hotspotsWidth);
857
- let eventRight = eventX > this.clipSize.width();
858
- let eventBottom = eventY > this.clipSize.height();
859
-
860
- let eventCount = (eventLeft ? 1 : 0) + (eventTop ? 1 : 0) + (eventRight ? 1 : 0) + (eventBottom ? 1 : 0);
861
- if (eventCount > 0) {
862
- 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);
863
1754
  }
864
1755
  }
1756
+
865
1757
  if (event.type === TouchType.Up) {
866
1758
  this.isTouched = false;
1759
+ // 重置触摸状态
1760
+ this.touchStartTime = 0;
1761
+ this.touchStartX = 0;
1762
+ this.touchStartY = 0;
867
1763
  }
868
1764
  })
869
-
870
1765
  }
871
1766
  }