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