@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.
- package/CHANGELOG.md +14 -0
- package/harmony/image_crop_picker/oh-package.json5 +1 -1
- package/harmony/image_crop_picker/src/main/ets/ImageCropPickerTurboModule.ts +26 -25
- package/harmony/image_crop_picker/src/main/ets/pages/CircleImageInfo.ets +1 -1
- package/harmony/image_crop_picker/src/main/ets/pages/ImageEditInfo.ets +1074 -179
- package/harmony/image_crop_picker/src/main/ets/utils/EncodeUtil.ets +1 -5
- package/harmony/image_crop_picker.har +0 -0
- package/package.json +1 -1
- package/react-native-ohos-react-native-image-crop-picker-0.40.5-rc.7.tgz +0 -0
|
@@ -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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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 =
|
|
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(
|
|
316
|
+
Text('')
|
|
207
317
|
.fontColor(Color.White)
|
|
208
318
|
.fontSize(20)
|
|
209
319
|
.textAlign(TextAlign.Center)
|
|
210
320
|
.width('100%')
|
|
211
|
-
.height(
|
|
321
|
+
.height(35)
|
|
212
322
|
.position({ x: 0, y: 0 })
|
|
213
323
|
.backgroundColor(Color.Black)
|
|
214
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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) -
|
|
368
|
-
|
|
369
|
-
|
|
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) -
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
921
|
+
// 0度或180度,宽高不变
|
|
922
|
+
this.updateMaxClipSize(this.imageWith, this.imageHeight);
|
|
441
923
|
break;
|
|
442
924
|
default:
|
|
443
|
-
|
|
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 =
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
-
|
|
1084
|
+
// 计算需要调整的偏移量
|
|
1085
|
+
let deltaX = 0;
|
|
1086
|
+
let deltaY = 0;
|
|
470
1087
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
479
|
-
|
|
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
|
-
|
|
483
|
-
|
|
1100
|
+
// 计算图片可移动的最大范围
|
|
1101
|
+
const maxOffsetX = this.getMaxHorizontalOffset();
|
|
1102
|
+
const maxOffsetY = this.getMaxVerticalOffset();
|
|
484
1103
|
|
|
485
|
-
//
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
|
|
492
|
-
|
|
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
|
-
|
|
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
|
|
504
|
-
const imageBottom = this.imgOffSetY + actualHeight;
|
|
1117
|
+
// 设置允许超出的范围
|
|
1118
|
+
const tolerance = 10;
|
|
505
1119
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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.
|
|
518
|
-
|
|
519
|
-
|
|
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
|
-
|
|
524
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
-
|
|
533
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
newPosition.
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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,
|
|
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
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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
|
}
|