kfb-view 3.3.7 → 3.3.9
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/lib/kfb-view.cjs.js +1 -1
- package/lib/kfb-view.es.js +1 -1
- package/lib/kfb-view.umd.js +1 -1
- package/package.json +1 -1
- package/src/components/locale/lang/en.js +3 -0
- package/src/components/locale/lang/zh-cn.js +3 -0
- package/src/components/tailoring/index.js +845 -179
- package/src/view.js +4 -0
|
@@ -8,7 +8,26 @@ import {
|
|
|
8
8
|
pointInOtherPoint,
|
|
9
9
|
isPointInMatrix,
|
|
10
10
|
} from '../../util/calculate';
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
const MOVE_BODY = 4;
|
|
13
|
+
const HANDLE_EDGE_TOP = 5;
|
|
14
|
+
const HANDLE_EDGE_RIGHT = 6;
|
|
15
|
+
const HANDLE_EDGE_BOTTOM = 7;
|
|
16
|
+
const HANDLE_EDGE_LEFT = 8;
|
|
17
|
+
const HANDLE_HIT = 28;
|
|
18
|
+
const CORNER_LEN = 20;
|
|
19
|
+
const EDGE_HANDLE_LEN = 14;
|
|
20
|
+
const TOOLBAR_HEIGHT = 44;
|
|
21
|
+
const TOOLBAR_MIN_WIDTH = 520;
|
|
22
|
+
const TOOLBAR_GAP = 5;
|
|
23
|
+
const MIN_SIZE = 40;
|
|
24
|
+
const STYLE_ID = 'kfb-tailoring-styles';
|
|
25
|
+
|
|
26
|
+
const ASPECT_PRESETS = {
|
|
27
|
+
'16:9': 16 / 9,
|
|
28
|
+
'4:3': 4 / 3,
|
|
29
|
+
'1:1': 1,
|
|
30
|
+
};
|
|
12
31
|
|
|
13
32
|
/**
|
|
14
33
|
* 用来裁剪/截图
|
|
@@ -16,7 +35,6 @@ import {Point, Rect} from '../../plugin/openseadragon/openseadragon';
|
|
|
16
35
|
*/
|
|
17
36
|
export class Tailoring extends ViewerCommon {
|
|
18
37
|
/**
|
|
19
|
-
* 初始化视图测量工具
|
|
20
38
|
* @param {Object} viewer
|
|
21
39
|
* @param {Object} canvas
|
|
22
40
|
* @param {Object} cache
|
|
@@ -29,7 +47,10 @@ export class Tailoring extends ViewerCommon {
|
|
|
29
47
|
this.tailoringPoints = [];
|
|
30
48
|
this.movePointIndex = -1;
|
|
31
49
|
this.color = '#FFF';
|
|
32
|
-
this.
|
|
50
|
+
this.aspectMode = 'custom';
|
|
51
|
+
this.customRatio = 1;
|
|
52
|
+
this.isUpdatingInputs = false;
|
|
53
|
+
this.toolbarEl = null;
|
|
33
54
|
this.options = {
|
|
34
55
|
suffix: 'jpeg',
|
|
35
56
|
quality: 0.92,
|
|
@@ -37,6 +58,132 @@ export class Tailoring extends ViewerCommon {
|
|
|
37
58
|
shape: true,
|
|
38
59
|
...this.options,
|
|
39
60
|
};
|
|
61
|
+
this.ensureStyles();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
ensureStyles() {
|
|
65
|
+
let style = document.getElementById(STYLE_ID);
|
|
66
|
+
if (!style) {
|
|
67
|
+
style = document.createElement('style');
|
|
68
|
+
style.id = STYLE_ID;
|
|
69
|
+
document.head.appendChild(style);
|
|
70
|
+
}
|
|
71
|
+
style.textContent = `
|
|
72
|
+
.kfb-tailoring-toolbar {
|
|
73
|
+
position: absolute;
|
|
74
|
+
z-index: 20;
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: space-between;
|
|
78
|
+
gap: 16px;
|
|
79
|
+
height: ${TOOLBAR_HEIGHT}px;
|
|
80
|
+
padding: 0 12px;
|
|
81
|
+
box-sizing: border-box;
|
|
82
|
+
background: rgba(18, 18, 18, 0.92);
|
|
83
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
84
|
+
border-radius: 8px;
|
|
85
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
|
|
86
|
+
pointer-events: auto;
|
|
87
|
+
user-select: none;
|
|
88
|
+
}
|
|
89
|
+
.kfb-tailoring-toolbar__main {
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
gap: 12px;
|
|
93
|
+
min-width: 0;
|
|
94
|
+
flex: 1;
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
}
|
|
97
|
+
.kfb-tailoring-toolbar__ratios {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: 4px;
|
|
101
|
+
flex-shrink: 0;
|
|
102
|
+
}
|
|
103
|
+
.kfb-tailoring-toolbar__ratios button {
|
|
104
|
+
height: 28px;
|
|
105
|
+
padding: 0 10px;
|
|
106
|
+
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
107
|
+
border-radius: 14px;
|
|
108
|
+
background: rgba(255, 255, 255, 0.06);
|
|
109
|
+
color: rgba(255, 255, 255, 0.85);
|
|
110
|
+
font-size: 12px;
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
transition: all 0.15s ease;
|
|
113
|
+
white-space: nowrap;
|
|
114
|
+
}
|
|
115
|
+
.kfb-tailoring-toolbar__ratios button:hover {
|
|
116
|
+
background: rgba(255, 255, 255, 0.12);
|
|
117
|
+
border-color: rgba(255, 255, 255, 0.28);
|
|
118
|
+
}
|
|
119
|
+
.kfb-tailoring-toolbar__ratios button.is-active {
|
|
120
|
+
background: #01d0b0;
|
|
121
|
+
border-color: #01d0b0;
|
|
122
|
+
color: #fff;
|
|
123
|
+
}
|
|
124
|
+
.kfb-tailoring-toolbar__custom {
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
gap: 6px;
|
|
128
|
+
color: rgba(255, 255, 255, 0.7);
|
|
129
|
+
font-size: 12px;
|
|
130
|
+
flex-shrink: 0;
|
|
131
|
+
}
|
|
132
|
+
.kfb-tailoring-toolbar__custom input {
|
|
133
|
+
width: 62px;
|
|
134
|
+
height: 28px;
|
|
135
|
+
padding: 0 6px;
|
|
136
|
+
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
137
|
+
border-radius: 6px;
|
|
138
|
+
background: rgba(0, 0, 0, 0.35);
|
|
139
|
+
color: #fff;
|
|
140
|
+
font-size: 12px;
|
|
141
|
+
text-align: center;
|
|
142
|
+
outline: none;
|
|
143
|
+
}
|
|
144
|
+
.kfb-tailoring-toolbar__custom input:focus {
|
|
145
|
+
border-color: #01d0b0;
|
|
146
|
+
}
|
|
147
|
+
.kfb-tailoring-toolbar__actions {
|
|
148
|
+
display: flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
gap: 8px;
|
|
151
|
+
flex-shrink: 0;
|
|
152
|
+
margin-left: auto;
|
|
153
|
+
padding-left: 8px;
|
|
154
|
+
}
|
|
155
|
+
.kfb-tailoring-toolbar__actions button {
|
|
156
|
+
width: 32px;
|
|
157
|
+
height: 32px;
|
|
158
|
+
border: none;
|
|
159
|
+
border-radius: 6px;
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
display: flex;
|
|
162
|
+
align-items: center;
|
|
163
|
+
justify-content: center;
|
|
164
|
+
transition: background 0.15s ease;
|
|
165
|
+
}
|
|
166
|
+
.kfb-tailoring-toolbar__cancel {
|
|
167
|
+
background: rgba(255, 255, 255, 0.08);
|
|
168
|
+
color: #fff;
|
|
169
|
+
}
|
|
170
|
+
.kfb-tailoring-toolbar__cancel:hover {
|
|
171
|
+
background: rgba(255, 80, 80, 0.35);
|
|
172
|
+
}
|
|
173
|
+
.kfb-tailoring-toolbar__confirm {
|
|
174
|
+
background: rgba(1, 208, 176, 0.2);
|
|
175
|
+
color: #01d0b0;
|
|
176
|
+
}
|
|
177
|
+
.kfb-tailoring-toolbar__confirm:hover {
|
|
178
|
+
background: rgba(1, 208, 176, 0.45);
|
|
179
|
+
color: #fff;
|
|
180
|
+
}
|
|
181
|
+
.kfb-tailoring-toolbar__actions svg {
|
|
182
|
+
width: 16px;
|
|
183
|
+
height: 16px;
|
|
184
|
+
fill: currentColor;
|
|
185
|
+
}
|
|
186
|
+
`;
|
|
40
187
|
}
|
|
41
188
|
|
|
42
189
|
init({
|
|
@@ -45,180 +192,555 @@ export class Tailoring extends ViewerCommon {
|
|
|
45
192
|
left,
|
|
46
193
|
top,
|
|
47
194
|
color,
|
|
48
|
-
contents = [],
|
|
49
195
|
shape = true,
|
|
50
196
|
now = false,
|
|
197
|
+
aspectMode = 'custom',
|
|
51
198
|
}) {
|
|
52
199
|
this.options.shape = shape;
|
|
53
200
|
this.isInTailoring = true;
|
|
54
201
|
this.movePointIndex = -1;
|
|
202
|
+
this.lastPoint = undefined;
|
|
203
|
+
this.color = color || '#FFF';
|
|
204
|
+
this.customRatio = width / height || 1;
|
|
205
|
+
this.aspectMode = aspectMode in ASPECT_PRESETS || aspectMode === 'custom' ?
|
|
206
|
+
aspectMode : 'custom';
|
|
55
207
|
const pointList = getRectPoint({x: left, y: top},
|
|
56
208
|
{x: left + width, y: top + height});
|
|
57
209
|
this.tailoringPoints = [
|
|
58
210
|
pointList[0],
|
|
59
211
|
pointList[2],
|
|
60
212
|
pointList[6],
|
|
61
|
-
pointList[8]
|
|
213
|
+
pointList[8],
|
|
214
|
+
];
|
|
62
215
|
if (now) {
|
|
63
216
|
this.emitScreenShot();
|
|
64
217
|
this.stopTailoring();
|
|
65
218
|
} else {
|
|
66
|
-
this.
|
|
67
|
-
this.
|
|
68
|
-
contents :
|
|
69
|
-
[];
|
|
219
|
+
this.canvas.style.pointerEvents = 'none';
|
|
220
|
+
this.createToolbar();
|
|
70
221
|
this.change();
|
|
71
222
|
}
|
|
72
223
|
}
|
|
73
224
|
|
|
225
|
+
getBounds() {
|
|
226
|
+
const minX = Math.min(...this.tailoringPoints.map(({x}) => x));
|
|
227
|
+
const minY = Math.min(...this.tailoringPoints.map(({y}) => y));
|
|
228
|
+
const maxX = Math.max(...this.tailoringPoints.map(({x}) => x));
|
|
229
|
+
const maxY = Math.max(...this.tailoringPoints.map(({y}) => y));
|
|
230
|
+
return {
|
|
231
|
+
minX,
|
|
232
|
+
minY,
|
|
233
|
+
maxX,
|
|
234
|
+
maxY,
|
|
235
|
+
width: maxX - minX,
|
|
236
|
+
height: maxY - minY,
|
|
237
|
+
centerX: (minX + maxX) / 2,
|
|
238
|
+
centerY: (minY + maxY) / 2,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
setBounds(left, top, width, height) {
|
|
243
|
+
const pointList = getRectPoint(
|
|
244
|
+
{x: left, y: top},
|
|
245
|
+
{x: left + width, y: top + height},
|
|
246
|
+
);
|
|
247
|
+
this.tailoringPoints = [
|
|
248
|
+
pointList[0],
|
|
249
|
+
pointList[2],
|
|
250
|
+
pointList[6],
|
|
251
|
+
pointList[8],
|
|
252
|
+
];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
getAspectRatioValue() {
|
|
256
|
+
if (this.aspectMode === 'custom') {
|
|
257
|
+
return this.customRatio || 1;
|
|
258
|
+
}
|
|
259
|
+
return ASPECT_PRESETS[this.aspectMode];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
clampBounds() {
|
|
263
|
+
const bounds = this.getBounds();
|
|
264
|
+
let {minX, minY, width, height} = bounds;
|
|
265
|
+
if (width < MIN_SIZE) {
|
|
266
|
+
width = MIN_SIZE;
|
|
267
|
+
}
|
|
268
|
+
if (height < MIN_SIZE) {
|
|
269
|
+
height = MIN_SIZE;
|
|
270
|
+
}
|
|
271
|
+
if (minX < 0) {
|
|
272
|
+
minX = 0;
|
|
273
|
+
}
|
|
274
|
+
if (minY < 0) {
|
|
275
|
+
minY = 0;
|
|
276
|
+
}
|
|
277
|
+
if (minX + width > this.canvas.width) {
|
|
278
|
+
minX = this.canvas.width - width;
|
|
279
|
+
}
|
|
280
|
+
if (minY + height > this.canvas.height) {
|
|
281
|
+
minY = this.canvas.height - height;
|
|
282
|
+
}
|
|
283
|
+
if (minX < 0) {
|
|
284
|
+
width += minX;
|
|
285
|
+
minX = 0;
|
|
286
|
+
}
|
|
287
|
+
if (minY < 0) {
|
|
288
|
+
height += minY;
|
|
289
|
+
minY = 0;
|
|
290
|
+
}
|
|
291
|
+
this.setBounds(minX, minY, width, height);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
applyAspectMode(mode) {
|
|
295
|
+
this.aspectMode = mode;
|
|
296
|
+
if (mode === 'custom') {
|
|
297
|
+
const bounds = this.getBounds();
|
|
298
|
+
this.customRatio = bounds.width / bounds.height || 1;
|
|
299
|
+
} else {
|
|
300
|
+
const ratio = ASPECT_PRESETS[mode];
|
|
301
|
+
const bounds = this.getBounds();
|
|
302
|
+
let width = bounds.width;
|
|
303
|
+
let height = width / ratio;
|
|
304
|
+
if (height > this.canvas.height) {
|
|
305
|
+
height = this.canvas.height;
|
|
306
|
+
width = height * ratio;
|
|
307
|
+
}
|
|
308
|
+
if (width > this.canvas.width) {
|
|
309
|
+
width = this.canvas.width;
|
|
310
|
+
height = width / ratio;
|
|
311
|
+
}
|
|
312
|
+
const left = bounds.centerX - width / 2;
|
|
313
|
+
const top = bounds.centerY - height / 2;
|
|
314
|
+
this.setBounds(left, top, width, height);
|
|
315
|
+
this.clampBounds();
|
|
316
|
+
}
|
|
317
|
+
this.updateToolbarState();
|
|
318
|
+
this.change();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
applyCustomSize(nextWidth, nextHeight, source = 'width') {
|
|
322
|
+
const bounds = this.getBounds();
|
|
323
|
+
let width = bounds.width;
|
|
324
|
+
let height = bounds.height;
|
|
325
|
+
if (this.aspectMode === 'custom') {
|
|
326
|
+
if (source === 'width') {
|
|
327
|
+
width = Math.max(MIN_SIZE, Math.round(nextWidth));
|
|
328
|
+
} else {
|
|
329
|
+
height = Math.max(MIN_SIZE, Math.round(nextHeight));
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
const ratio = ASPECT_PRESETS[this.aspectMode];
|
|
333
|
+
if (source === 'width') {
|
|
334
|
+
width = Math.max(MIN_SIZE, Math.round(nextWidth));
|
|
335
|
+
height = Math.round(width / ratio);
|
|
336
|
+
} else {
|
|
337
|
+
height = Math.max(MIN_SIZE, Math.round(nextHeight));
|
|
338
|
+
width = Math.round(height * ratio);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const left = bounds.centerX - width / 2;
|
|
342
|
+
const top = bounds.centerY - height / 2;
|
|
343
|
+
this.setBounds(left, top, width, height);
|
|
344
|
+
this.clampBounds();
|
|
345
|
+
if (this.aspectMode === 'custom') {
|
|
346
|
+
const nextBounds = this.getBounds();
|
|
347
|
+
this.customRatio = nextBounds.width / nextBounds.height || 1;
|
|
348
|
+
}
|
|
349
|
+
this.updateToolbarState();
|
|
350
|
+
this.change();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
createToolbar() {
|
|
354
|
+
this.destroyToolbar();
|
|
355
|
+
const toolbar = document.createElement('div');
|
|
356
|
+
toolbar.className = 'kfb-tailoring-toolbar';
|
|
357
|
+
toolbar.innerHTML = `
|
|
358
|
+
<div class="kfb-tailoring-toolbar__main">
|
|
359
|
+
<div class="kfb-tailoring-toolbar__ratios">
|
|
360
|
+
<button type="button" data-ratio="16:9">16:9</button>
|
|
361
|
+
<button type="button" data-ratio="4:3">4:3</button>
|
|
362
|
+
<button type="button" data-ratio="1:1">1:1</button>
|
|
363
|
+
<button type="button" data-ratio="custom">${this.translate('custom')}</button>
|
|
364
|
+
</div>
|
|
365
|
+
<div class="kfb-tailoring-toolbar__custom">
|
|
366
|
+
<input type="number" class="kfb-tailoring-width" min="${MIN_SIZE}" step="1" />
|
|
367
|
+
<span>×</span>
|
|
368
|
+
<input type="number" class="kfb-tailoring-height" min="${MIN_SIZE}" step="1" />
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
<div class="kfb-tailoring-toolbar__actions">
|
|
372
|
+
<button type="button" class="kfb-tailoring-toolbar__cancel" title="${this.translate('cancel')}">
|
|
373
|
+
<svg viewBox="0 0 1024 1024"><path d="M768 307.552 716.448 256 512 460.448 307.552 256 256 307.552 460.448 512 256 716.448 307.552 768 512 563.552 716.448 768 768 716.448 563.552 512 768 307.552z"/></svg>
|
|
374
|
+
</button>
|
|
375
|
+
<button type="button" class="kfb-tailoring-toolbar__confirm" title="${this.translate('confirm')}">
|
|
376
|
+
<svg viewBox="0 0 1024 1024"><path d="M836.267 311.467 806.4 281.6c-8.533-8.533-21.333-8.533-29.866 0L435.2 622.933c-4.267 4.267-4.267 4.267-8.534 0L264.533 460.8c-8.533-8.533-21.333-8.533-29.866 0l-29.867 29.867c-8.533 8.533-8.533 21.333 0 29.866l217.6 217.6c4.267 4.267 12.8 4.267 17.067 0l396.8-396.8c8.533-8.533 8.533-21.333 0-29.866z"/></svg>
|
|
377
|
+
</button>
|
|
378
|
+
</div>
|
|
379
|
+
`;
|
|
380
|
+
this.kv.$el.appendChild(toolbar);
|
|
381
|
+
this.toolbarEl = toolbar;
|
|
382
|
+
|
|
383
|
+
toolbar.querySelectorAll('[data-ratio]').forEach((btn) => {
|
|
384
|
+
btn.addEventListener('click', (e) => {
|
|
385
|
+
e.stopPropagation();
|
|
386
|
+
this.applyAspectMode(btn.dataset.ratio);
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
toolbar.querySelector('.kfb-tailoring-toolbar__cancel')
|
|
390
|
+
.addEventListener('click', (e) => {
|
|
391
|
+
e.stopPropagation();
|
|
392
|
+
this.stopTailoring();
|
|
393
|
+
});
|
|
394
|
+
toolbar.querySelector('.kfb-tailoring-toolbar__confirm')
|
|
395
|
+
.addEventListener('click', (e) => {
|
|
396
|
+
e.stopPropagation();
|
|
397
|
+
this.emitScreenShot();
|
|
398
|
+
this.stopTailoring();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const widthInput = toolbar.querySelector('.kfb-tailoring-width');
|
|
402
|
+
const heightInput = toolbar.querySelector('.kfb-tailoring-height');
|
|
403
|
+
widthInput.addEventListener('change', () => {
|
|
404
|
+
if (this.isUpdatingInputs) return;
|
|
405
|
+
this.applyCustomSize(Number(widthInput.value), 0, 'width');
|
|
406
|
+
});
|
|
407
|
+
heightInput.addEventListener('change', () => {
|
|
408
|
+
if (this.isUpdatingInputs) return;
|
|
409
|
+
this.applyCustomSize(0, Number(heightInput.value), 'height');
|
|
410
|
+
});
|
|
411
|
+
widthInput.addEventListener('keydown', (e) => e.stopPropagation());
|
|
412
|
+
heightInput.addEventListener('keydown', (e) => e.stopPropagation());
|
|
413
|
+
|
|
414
|
+
this.updateToolbarState();
|
|
415
|
+
this.updateToolbarPosition();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
destroyToolbar() {
|
|
419
|
+
if (this.toolbarEl) {
|
|
420
|
+
this.toolbarEl.remove();
|
|
421
|
+
this.toolbarEl = null;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
updateToolbarState() {
|
|
426
|
+
if (!this.toolbarEl) return;
|
|
427
|
+
const bounds = this.getBounds();
|
|
428
|
+
this.toolbarEl.querySelectorAll('[data-ratio]').forEach((btn) => {
|
|
429
|
+
btn.classList.toggle('is-active', btn.dataset.ratio === this.aspectMode);
|
|
430
|
+
});
|
|
431
|
+
this.isUpdatingInputs = true;
|
|
432
|
+
const widthInput = this.toolbarEl.querySelector('.kfb-tailoring-width');
|
|
433
|
+
const heightInput = this.toolbarEl.querySelector('.kfb-tailoring-height');
|
|
434
|
+
widthInput.value = Math.round(bounds.width);
|
|
435
|
+
heightInput.value = Math.round(bounds.height);
|
|
436
|
+
this.isUpdatingInputs = false;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
getSizeLabelBounds() {
|
|
440
|
+
const {minX, minY, width, height} = this.getBounds();
|
|
441
|
+
const text = `${Math.round(width)} × ${Math.round(height)}`;
|
|
442
|
+
const ctx = this.canvas.getContext('2d');
|
|
443
|
+
ctx.font = '12px -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif';
|
|
444
|
+
const padX = 8;
|
|
445
|
+
const labelH = 22;
|
|
446
|
+
const labelW = ctx.measureText(text).width + padX * 2;
|
|
447
|
+
let labelX = minX;
|
|
448
|
+
let labelY = minY - labelH - 5;
|
|
449
|
+
if (labelY < 2) {
|
|
450
|
+
labelY = minY + 5;
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
x: labelX,
|
|
454
|
+
y: labelY,
|
|
455
|
+
width: labelW,
|
|
456
|
+
height: labelH,
|
|
457
|
+
top: labelY,
|
|
458
|
+
bottom: labelY + labelH,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
updateToolbarPosition() {
|
|
463
|
+
if (!this.toolbarEl) return;
|
|
464
|
+
const {minX, minY, maxY} = this.getBounds();
|
|
465
|
+
const toolbarWidth = TOOLBAR_MIN_WIDTH;
|
|
466
|
+
let left = minX;
|
|
467
|
+
let top = maxY + TOOLBAR_GAP;
|
|
468
|
+
if (left + toolbarWidth > this.canvas.width - 4) {
|
|
469
|
+
left = Math.max(4, this.canvas.width - toolbarWidth - 4);
|
|
470
|
+
}
|
|
471
|
+
if (left < 4) {
|
|
472
|
+
left = 4;
|
|
473
|
+
}
|
|
474
|
+
const labelBounds = this.getSizeLabelBounds();
|
|
475
|
+
const belowTop = maxY + TOOLBAR_GAP;
|
|
476
|
+
const aboveTop = labelBounds.top - TOOLBAR_HEIGHT - TOOLBAR_GAP;
|
|
477
|
+
if (belowTop + TOOLBAR_HEIGHT > this.canvas.height - 4) {
|
|
478
|
+
if (aboveTop >= 4) {
|
|
479
|
+
top = aboveTop;
|
|
480
|
+
} else {
|
|
481
|
+
top = Math.max(4, aboveTop);
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
top = belowTop;
|
|
485
|
+
}
|
|
486
|
+
this.toolbarEl.style.left = `${left}px`;
|
|
487
|
+
this.toolbarEl.style.top = `${top}px`;
|
|
488
|
+
this.toolbarEl.style.width = `${toolbarWidth}px`;
|
|
489
|
+
}
|
|
490
|
+
|
|
74
491
|
emitTailoringRegion() {
|
|
492
|
+
const bounds = this.getBounds();
|
|
75
493
|
this.mitt.$emit(EVENT_TAILORING_REGION, this.viewerElementToImageRectangle({
|
|
76
|
-
x:
|
|
77
|
-
|
|
78
|
-
|
|
494
|
+
x: bounds.minX,
|
|
495
|
+
y: bounds.minY,
|
|
496
|
+
width: bounds.width,
|
|
497
|
+
height: bounds.height,
|
|
79
498
|
}));
|
|
80
499
|
}
|
|
81
500
|
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
ctx.
|
|
501
|
+
drawDimOverlay(ctx) {
|
|
502
|
+
const {minX, minY, maxX, maxY} = this.getBounds();
|
|
503
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.42)';
|
|
504
|
+
ctx.fillRect(0, 0, this.canvas.width, minY);
|
|
505
|
+
ctx.fillRect(0, minY, minX, maxY - minY);
|
|
506
|
+
ctx.fillRect(maxX, minY, this.canvas.width - maxX, maxY - minY);
|
|
507
|
+
ctx.fillRect(0, maxY, this.canvas.width, this.canvas.height - maxY);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
drawSelectionFrame(ctx) {
|
|
511
|
+
const {minX, minY, width, height} = this.getBounds();
|
|
85
512
|
ctx.strokeStyle = this.color;
|
|
86
|
-
ctx.lineWidth =
|
|
87
|
-
ctx.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
513
|
+
ctx.lineWidth = 2;
|
|
514
|
+
ctx.strokeRect(minX + 0.5, minY + 0.5, width - 1, height - 1);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
drawCornerHandles(ctx) {
|
|
518
|
+
const {minX, minY, maxX, maxY} = this.getBounds();
|
|
519
|
+
const corners = [
|
|
520
|
+
{x: minX, y: minY},
|
|
521
|
+
{x: maxX, y: minY},
|
|
522
|
+
{x: minX, y: maxY},
|
|
523
|
+
{x: maxX, y: maxY},
|
|
524
|
+
];
|
|
525
|
+
const len = Math.min(
|
|
526
|
+
CORNER_LEN,
|
|
527
|
+
(maxX - minX) / 3,
|
|
528
|
+
(maxY - minY) / 3,
|
|
529
|
+
);
|
|
530
|
+
ctx.strokeStyle = this.color;
|
|
531
|
+
ctx.lineWidth = 3;
|
|
532
|
+
ctx.lineCap = 'square';
|
|
533
|
+
corners.forEach((corner, index) => {
|
|
534
|
+
ctx.beginPath();
|
|
535
|
+
if (index === 0) {
|
|
536
|
+
ctx.moveTo(corner.x, corner.y + len);
|
|
537
|
+
ctx.lineTo(corner.x, corner.y);
|
|
538
|
+
ctx.lineTo(corner.x + len, corner.y);
|
|
539
|
+
} else if (index === 1) {
|
|
540
|
+
ctx.moveTo(corner.x - len, corner.y);
|
|
541
|
+
ctx.lineTo(corner.x, corner.y);
|
|
542
|
+
ctx.lineTo(corner.x, corner.y + len);
|
|
543
|
+
} else if (index === 2) {
|
|
544
|
+
ctx.moveTo(corner.x, corner.y - len);
|
|
545
|
+
ctx.lineTo(corner.x, corner.y);
|
|
546
|
+
ctx.lineTo(corner.x + len, corner.y);
|
|
547
|
+
} else {
|
|
548
|
+
ctx.moveTo(corner.x - len, corner.y);
|
|
549
|
+
ctx.lineTo(corner.x, corner.y);
|
|
550
|
+
ctx.lineTo(corner.x, corner.y - len);
|
|
551
|
+
}
|
|
552
|
+
ctx.stroke();
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
drawSizeLabel(ctx) {
|
|
557
|
+
const labelBounds = this.getSizeLabelBounds();
|
|
558
|
+
const {width, height} = this.getBounds();
|
|
559
|
+
const text = `${Math.round(width)} × ${Math.round(height)}`;
|
|
560
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.88)';
|
|
561
|
+
ctx.fillRect(labelBounds.x, labelBounds.y, labelBounds.width, labelBounds.height);
|
|
562
|
+
ctx.fillStyle = '#fff';
|
|
563
|
+
ctx.font = '12px -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif';
|
|
564
|
+
ctx.textBaseline = 'middle';
|
|
565
|
+
ctx.fillText(text, labelBounds.x + 8, labelBounds.y + labelBounds.height / 2 + 0.5);
|
|
91
566
|
}
|
|
92
567
|
|
|
93
568
|
drawTailoring() {
|
|
94
|
-
const minX = Math.min(...this.tailoringPoints.map(({x}) => x));
|
|
95
|
-
const minY = Math.min(...this.tailoringPoints.map(({y}) => y));
|
|
96
|
-
const maxX = Math.max(...this.tailoringPoints.map(({x}) => x));
|
|
97
|
-
const maxY = Math.max(...this.tailoringPoints.map(({y}) => y));
|
|
98
|
-
const leftTop = {x: minX, y: minY};
|
|
99
|
-
const rightTop = {x: maxX, y: minY};
|
|
100
|
-
const leftBottom = {x: minX, y: maxY};
|
|
101
|
-
const rightBottom = {x: maxX, y: maxY};
|
|
102
|
-
const distX = maxX - minX > 30 ? 30 : maxX - minX;
|
|
103
|
-
const distY = maxY - minY > 30 ? 30 : maxY - minY;
|
|
104
|
-
// const deg = this.viewport.getRotation();
|
|
105
569
|
const ctx = this.canvas.getContext('2d');
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
ctx
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
570
|
+
this.drawDimOverlay(ctx);
|
|
571
|
+
this.drawSelectionFrame(ctx);
|
|
572
|
+
this.drawCornerHandles(ctx);
|
|
573
|
+
this.drawEdgeHandles(ctx);
|
|
574
|
+
this.drawSizeLabel(ctx);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
getEdgeMidpoints() {
|
|
578
|
+
const {minX, minY, maxX, maxY, centerX, centerY} = this.getBounds();
|
|
579
|
+
return [
|
|
580
|
+
{x: centerX, y: minY},
|
|
581
|
+
{x: maxX, y: centerY},
|
|
582
|
+
{x: centerX, y: maxY},
|
|
583
|
+
{x: minX, y: centerY},
|
|
584
|
+
];
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
drawEdgeHandles(ctx) {
|
|
588
|
+
const edges = this.getEdgeMidpoints();
|
|
589
|
+
const halfLen = Math.min(
|
|
590
|
+
EDGE_HANDLE_LEN / 2,
|
|
591
|
+
this.getBounds().width / 4,
|
|
592
|
+
this.getBounds().height / 4,
|
|
593
|
+
);
|
|
130
594
|
ctx.fillStyle = this.color;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
595
|
+
edges.forEach((point, index) => {
|
|
596
|
+
if (index === 0 || index === 2) {
|
|
597
|
+
ctx.fillRect(point.x - halfLen, point.y - 2, halfLen * 2, 4);
|
|
598
|
+
} else {
|
|
599
|
+
ctx.fillRect(point.x - 2, point.y - halfLen, 4, halfLen * 2);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
getCornerIndex({x, y}) {
|
|
605
|
+
return this.tailoringPoints.findIndex(
|
|
606
|
+
(point) => pointInOtherPoint(point, {x, y}, HANDLE_HIT),
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
getEdgeIndex({x, y}) {
|
|
611
|
+
return this.getEdgeMidpoints().findIndex(
|
|
612
|
+
(point) => pointInOtherPoint(point, {x, y}, HANDLE_HIT),
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
getHandleIndex({x, y}) {
|
|
617
|
+
const corner = this.getCornerIndex({x, y});
|
|
618
|
+
if (corner !== -1) return corner;
|
|
619
|
+
const edge = this.getEdgeIndex({x, y});
|
|
620
|
+
if (edge !== -1) return HANDLE_EDGE_TOP + edge;
|
|
621
|
+
return -1;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
getHandleCursor(index) {
|
|
625
|
+
const cursors = {
|
|
626
|
+
0: 'nwse-resize',
|
|
627
|
+
1: 'nesw-resize',
|
|
628
|
+
2: 'nesw-resize',
|
|
629
|
+
3: 'nwse-resize',
|
|
630
|
+
[HANDLE_EDGE_TOP]: 'ns-resize',
|
|
631
|
+
[HANDLE_EDGE_RIGHT]: 'ew-resize',
|
|
632
|
+
[HANDLE_EDGE_BOTTOM]: 'ns-resize',
|
|
633
|
+
[HANDLE_EDGE_LEFT]: 'ew-resize',
|
|
634
|
+
};
|
|
635
|
+
return cursors[index] || 'default';
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
isPointOnToolbar({x, y}) {
|
|
639
|
+
if (!this.toolbarEl) return false;
|
|
640
|
+
const rect = this.toolbarEl.getBoundingClientRect();
|
|
641
|
+
const canvasRect = this.canvas.getBoundingClientRect();
|
|
642
|
+
const localX = rect.left - canvasRect.left;
|
|
643
|
+
const localY = rect.top - canvasRect.top;
|
|
644
|
+
return x >= localX && x <= localX + rect.width &&
|
|
645
|
+
y >= localY && y <= localY + rect.height;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
isMouseOnToolbar(e) {
|
|
649
|
+
if (!this.toolbarEl) return false;
|
|
650
|
+
const rect = this.toolbarEl.getBoundingClientRect();
|
|
651
|
+
return e.clientX >= rect.left && e.clientX <= rect.right &&
|
|
652
|
+
e.clientY >= rect.top && e.clientY <= rect.bottom;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
setViewerCursor(cursor) {
|
|
656
|
+
const targets = [
|
|
657
|
+
this.kv.$el,
|
|
658
|
+
this.viewer.container,
|
|
659
|
+
this.viewer.canvas,
|
|
660
|
+
document.getElementById('openseadragon-zrender'),
|
|
661
|
+
];
|
|
662
|
+
const applyCursor = (el) => {
|
|
663
|
+
if (!el) return;
|
|
664
|
+
el.style.cursor = cursor;
|
|
665
|
+
el.querySelectorAll('canvas').forEach((canvas) => {
|
|
666
|
+
canvas.style.cursor = cursor;
|
|
667
|
+
});
|
|
668
|
+
};
|
|
669
|
+
targets.forEach(applyCursor);
|
|
670
|
+
if (this.canvas) {
|
|
671
|
+
this.canvas.style.cursor = cursor;
|
|
162
672
|
}
|
|
163
|
-
ctx.fillStyle = '#ffffff';
|
|
164
|
-
ctx.drawCancel();
|
|
165
|
-
ctx.restore();
|
|
166
673
|
}
|
|
167
674
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
675
|
+
getCursor({x, y}) {
|
|
676
|
+
if (this.movePointIndex >= 0 && this.movePointIndex <= HANDLE_EDGE_LEFT) {
|
|
677
|
+
return this.getHandleCursor(this.movePointIndex);
|
|
678
|
+
}
|
|
679
|
+
if (this.movePointIndex === MOVE_BODY) {
|
|
680
|
+
return 'grabbing';
|
|
681
|
+
}
|
|
682
|
+
const handle = this.getHandleIndex({x, y});
|
|
683
|
+
if (handle !== -1) {
|
|
684
|
+
return this.getHandleCursor(handle);
|
|
685
|
+
}
|
|
686
|
+
if (this.isPointInMatrix({x, y})) {
|
|
687
|
+
return 'grab';
|
|
688
|
+
}
|
|
689
|
+
return 'default';
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
updateCursor(position) {
|
|
693
|
+
if (!this.isInTailoring) return;
|
|
694
|
+
const {x, y} = position;
|
|
695
|
+
if (this.isPointOnToolbar({x, y})) {
|
|
696
|
+
this.setViewerCursor('default');
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
this.setViewerCursor(this.getCursor({x, y}));
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
onCanvasMove(e) {
|
|
703
|
+
if (!this.isInTailoring) return;
|
|
704
|
+
if (this.isMouseOnToolbar(e)) {
|
|
705
|
+
this.setViewerCursor('default');
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const rect = this.viewer.canvas.getBoundingClientRect();
|
|
709
|
+
const x = e.clientX - rect.left;
|
|
710
|
+
const y = e.clientY - rect.top;
|
|
711
|
+
this.setViewerCursor(this.getCursor({x, y}));
|
|
712
|
+
}
|
|
713
|
+
|
|
174
714
|
onCanvasPress({x, y}) {
|
|
175
|
-
this.
|
|
176
|
-
|
|
177
|
-
const rightTopPoint = this.tailoringPoints[1];
|
|
178
|
-
|
|
179
|
-
let areaInterval = 4;
|
|
180
|
-
let areaDist = 100;
|
|
181
|
-
let cancelArea = new Rect(rightTopPoint.x + 14, rightTopPoint.y, 30, 40);
|
|
182
|
-
let confirmArea = new Rect(rightTopPoint.x + 64, rightTopPoint.y, 30, 40);
|
|
183
|
-
if (rightTopPoint.x + areaDist + areaInterval > this.canvas.width) {
|
|
184
|
-
cancelArea = new Rect(rightTopPoint.x - 94, rightTopPoint.y, 30, 40);
|
|
185
|
-
confirmArea = new Rect(rightTopPoint.x - 44, rightTopPoint.y, 30, 40);
|
|
186
|
-
}
|
|
187
|
-
const point = new Point(x, y);
|
|
188
|
-
if (cancelArea.containsPoint(point)) {
|
|
189
|
-
this.stopTailoring();
|
|
715
|
+
if (this.isPointOnToolbar({x, y})) {
|
|
716
|
+
return;
|
|
190
717
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
this.
|
|
718
|
+
this.movePointIndex = this.getHandleIndex({x, y});
|
|
719
|
+
if (this.movePointIndex === -1 && this.isPointInMatrix({x, y})) {
|
|
720
|
+
this.movePointIndex = MOVE_BODY;
|
|
721
|
+
}
|
|
722
|
+
this.lastPoint = {x, y};
|
|
723
|
+
if (this.movePointIndex === MOVE_BODY) {
|
|
724
|
+
this.setViewerCursor('grabbing');
|
|
725
|
+
} else if (this.movePointIndex > -1) {
|
|
726
|
+
this.setViewerCursor(this.getHandleCursor(this.movePointIndex));
|
|
194
727
|
}
|
|
195
728
|
}
|
|
196
729
|
|
|
197
|
-
/**
|
|
198
|
-
* 监听鼠标释放事件
|
|
199
|
-
* @param {Object} e
|
|
200
|
-
* @param {number} e.x
|
|
201
|
-
* @param {number} e.y
|
|
202
|
-
*/
|
|
203
730
|
onCanvasRelease({x, y}) {
|
|
731
|
+
if (this.movePointIndex !== -1 && this.movePointIndex !== MOVE_BODY) {
|
|
732
|
+
const bounds = this.getBounds();
|
|
733
|
+
this.customRatio = bounds.width / bounds.height || 1;
|
|
734
|
+
}
|
|
204
735
|
this.movePointIndex = -1;
|
|
205
736
|
this.lastPoint = undefined;
|
|
737
|
+
if (x !== undefined && y !== undefined) {
|
|
738
|
+
this.updateCursor({x, y});
|
|
739
|
+
}
|
|
206
740
|
}
|
|
207
741
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
* @param {Object} e
|
|
211
|
-
* @param {number} e.x
|
|
212
|
-
* @param {number} e.y
|
|
213
|
-
*/
|
|
214
|
-
onCanvasDrag({x, y}) {
|
|
215
|
-
if (this.movePointIndex === -1) return;
|
|
216
|
-
if (!this.lastPoint) {
|
|
217
|
-
this.lastPoint = {x, y};
|
|
218
|
-
}
|
|
219
|
-
const distX = (x - this.lastPoint.x);
|
|
220
|
-
const distY = (y - this.lastPoint.y);
|
|
221
|
-
switch (this.movePointIndex) {
|
|
742
|
+
resizeCornerFree(index, distX, distY) {
|
|
743
|
+
switch (index) {
|
|
222
744
|
case 0:
|
|
223
745
|
this.tailoringPoints[0] = {
|
|
224
746
|
x: this.tailoringPoints[0].x + distX,
|
|
@@ -275,32 +797,174 @@ export class Tailoring extends ViewerCommon {
|
|
|
275
797
|
y: this.tailoringPoints[3].y + distY,
|
|
276
798
|
};
|
|
277
799
|
break;
|
|
800
|
+
default:
|
|
801
|
+
break;
|
|
278
802
|
}
|
|
279
|
-
this.
|
|
280
|
-
|
|
281
|
-
|
|
803
|
+
this.clampBounds();
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
resizeCornerLocked(index, distX, distY) {
|
|
807
|
+
const ratio = this.getAspectRatioValue();
|
|
808
|
+
const bounds = this.getBounds();
|
|
809
|
+
const dominant = Math.abs(distX) >= Math.abs(distY) ? distX : distY * ratio;
|
|
810
|
+
let {minX, minY, maxX, maxY} = bounds;
|
|
811
|
+
let width = bounds.width;
|
|
812
|
+
let height = bounds.height;
|
|
813
|
+
|
|
814
|
+
switch (index) {
|
|
815
|
+
case 0:
|
|
816
|
+
width = Math.max(MIN_SIZE, width - dominant);
|
|
817
|
+
height = width / ratio;
|
|
818
|
+
minX = maxX - width;
|
|
819
|
+
minY = maxY - height;
|
|
820
|
+
break;
|
|
821
|
+
case 1:
|
|
822
|
+
width = Math.max(MIN_SIZE, width + dominant);
|
|
823
|
+
height = width / ratio;
|
|
824
|
+
minY = maxY - height;
|
|
825
|
+
maxX = minX + width;
|
|
826
|
+
break;
|
|
827
|
+
case 2:
|
|
828
|
+
width = Math.max(MIN_SIZE, width - dominant);
|
|
829
|
+
height = width / ratio;
|
|
830
|
+
minX = maxX - width;
|
|
831
|
+
maxY = minY + height;
|
|
832
|
+
break;
|
|
833
|
+
case 3:
|
|
834
|
+
width = Math.max(MIN_SIZE, width + dominant);
|
|
835
|
+
height = width / ratio;
|
|
836
|
+
maxX = minX + width;
|
|
837
|
+
maxY = minY + height;
|
|
838
|
+
break;
|
|
839
|
+
default:
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
this.setBounds(minX, minY, width, height);
|
|
843
|
+
this.clampBounds();
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
resizeEdgeFree(index, distX, distY) {
|
|
847
|
+
const bounds = this.getBounds();
|
|
848
|
+
switch (index) {
|
|
849
|
+
case HANDLE_EDGE_TOP: {
|
|
850
|
+
let minY = bounds.minY + distY;
|
|
851
|
+
let height = bounds.maxY - minY;
|
|
852
|
+
if (height < MIN_SIZE) {
|
|
853
|
+
minY = bounds.maxY - MIN_SIZE;
|
|
854
|
+
height = MIN_SIZE;
|
|
855
|
+
}
|
|
856
|
+
this.setBounds(bounds.minX, minY, bounds.width, height);
|
|
857
|
+
break;
|
|
282
858
|
}
|
|
283
|
-
|
|
284
|
-
|
|
859
|
+
case HANDLE_EDGE_BOTTOM: {
|
|
860
|
+
const height = Math.max(MIN_SIZE, bounds.height + distY);
|
|
861
|
+
this.setBounds(bounds.minX, bounds.minY, bounds.width, height);
|
|
862
|
+
break;
|
|
285
863
|
}
|
|
286
|
-
|
|
287
|
-
|
|
864
|
+
case HANDLE_EDGE_LEFT: {
|
|
865
|
+
let minX = bounds.minX + distX;
|
|
866
|
+
let width = bounds.maxX - minX;
|
|
867
|
+
if (width < MIN_SIZE) {
|
|
868
|
+
minX = bounds.maxX - MIN_SIZE;
|
|
869
|
+
width = MIN_SIZE;
|
|
870
|
+
}
|
|
871
|
+
this.setBounds(minX, bounds.minY, width, bounds.height);
|
|
872
|
+
break;
|
|
288
873
|
}
|
|
289
|
-
|
|
290
|
-
|
|
874
|
+
case HANDLE_EDGE_RIGHT: {
|
|
875
|
+
const width = Math.max(MIN_SIZE, bounds.width + distX);
|
|
876
|
+
this.setBounds(bounds.minX, bounds.minY, width, bounds.height);
|
|
877
|
+
break;
|
|
291
878
|
}
|
|
292
|
-
|
|
879
|
+
default:
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
this.clampBounds();
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
resizeEdgeLocked(index, distX, distY) {
|
|
886
|
+
const ratio = this.getAspectRatioValue();
|
|
887
|
+
const bounds = this.getBounds();
|
|
888
|
+
switch (index) {
|
|
889
|
+
case HANDLE_EDGE_TOP: {
|
|
890
|
+
const height = Math.max(MIN_SIZE, bounds.height - distY);
|
|
891
|
+
const width = height * ratio;
|
|
892
|
+
const left = bounds.centerX - width / 2;
|
|
893
|
+
const top = bounds.maxY - height;
|
|
894
|
+
this.setBounds(left, top, width, height);
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
case HANDLE_EDGE_BOTTOM: {
|
|
898
|
+
const height = Math.max(MIN_SIZE, bounds.height + distY);
|
|
899
|
+
const width = height * ratio;
|
|
900
|
+
const left = bounds.centerX - width / 2;
|
|
901
|
+
this.setBounds(left, bounds.minY, width, height);
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
case HANDLE_EDGE_LEFT: {
|
|
905
|
+
const width = Math.max(MIN_SIZE, bounds.width - distX);
|
|
906
|
+
const height = width / ratio;
|
|
907
|
+
const top = bounds.centerY - height / 2;
|
|
908
|
+
const left = bounds.maxX - width;
|
|
909
|
+
this.setBounds(left, top, width, height);
|
|
910
|
+
break;
|
|
911
|
+
}
|
|
912
|
+
case HANDLE_EDGE_RIGHT: {
|
|
913
|
+
const width = Math.max(MIN_SIZE, bounds.width + distX);
|
|
914
|
+
const height = width / ratio;
|
|
915
|
+
const top = bounds.centerY - height / 2;
|
|
916
|
+
this.setBounds(bounds.minX, top, width, height);
|
|
917
|
+
break;
|
|
918
|
+
}
|
|
919
|
+
default:
|
|
920
|
+
break;
|
|
921
|
+
}
|
|
922
|
+
this.clampBounds();
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
moveBody(distX, distY) {
|
|
926
|
+
const bounds = this.getBounds();
|
|
927
|
+
let left = bounds.minX + distX;
|
|
928
|
+
let top = bounds.minY + distY;
|
|
929
|
+
left = Math.max(0, Math.min(left, this.canvas.width - bounds.width));
|
|
930
|
+
top = Math.max(0, Math.min(top, this.canvas.height - bounds.height));
|
|
931
|
+
this.setBounds(left, top, bounds.width, bounds.height);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
onCanvasDrag({x, y}) {
|
|
935
|
+
if (this.movePointIndex === -1) return;
|
|
936
|
+
if (!this.lastPoint) {
|
|
937
|
+
this.lastPoint = {x, y};
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
const distX = x - this.lastPoint.x;
|
|
941
|
+
const distY = y - this.lastPoint.y;
|
|
942
|
+
if (this.movePointIndex === MOVE_BODY) {
|
|
943
|
+
this.moveBody(distX, distY);
|
|
944
|
+
} else if (this.movePointIndex >= HANDLE_EDGE_TOP) {
|
|
945
|
+
if (this.aspectMode === 'custom') {
|
|
946
|
+
this.resizeEdgeFree(this.movePointIndex, distX, distY);
|
|
947
|
+
const bounds = this.getBounds();
|
|
948
|
+
this.customRatio = bounds.width / bounds.height || 1;
|
|
949
|
+
} else {
|
|
950
|
+
this.resizeEdgeLocked(this.movePointIndex, distX, distY);
|
|
951
|
+
}
|
|
952
|
+
} else if (this.aspectMode === 'custom') {
|
|
953
|
+
this.resizeCornerFree(this.movePointIndex, distX, distY);
|
|
954
|
+
const bounds = this.getBounds();
|
|
955
|
+
this.customRatio = bounds.width / bounds.height || 1;
|
|
956
|
+
} else {
|
|
957
|
+
this.resizeCornerLocked(this.movePointIndex, distX, distY);
|
|
958
|
+
}
|
|
293
959
|
this.clearCanvas();
|
|
294
|
-
this.drawRect();
|
|
295
960
|
this.drawTailoring();
|
|
961
|
+
this.updateToolbarState();
|
|
962
|
+
this.updateToolbarPosition();
|
|
296
963
|
this.lastPoint = {x, y};
|
|
964
|
+
this.updateCursor({x, y});
|
|
297
965
|
this.emitTailoringRegion();
|
|
298
966
|
}
|
|
299
967
|
|
|
300
|
-
/**
|
|
301
|
-
* 监听鼠标按钮事件
|
|
302
|
-
* @param {Object} e
|
|
303
|
-
*/
|
|
304
968
|
onCanvasKey(e) {
|
|
305
969
|
const {originalEvent} = e;
|
|
306
970
|
if (originalEvent.key === 'Escape') {
|
|
@@ -309,49 +973,45 @@ export class Tailoring extends ViewerCommon {
|
|
|
309
973
|
}
|
|
310
974
|
|
|
311
975
|
onCanvasDblClick(position) {
|
|
976
|
+
if (this.isPointOnToolbar(position)) {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
312
979
|
if (this.tailoringPoints.length > 0 && this.isPointInMatrix(position)) {
|
|
313
980
|
this.emitScreenShot();
|
|
981
|
+
this.stopTailoring();
|
|
982
|
+
} else {
|
|
983
|
+
this.stopTailoring();
|
|
314
984
|
}
|
|
315
|
-
this.stopTailoring();
|
|
316
985
|
}
|
|
317
986
|
|
|
318
987
|
emitScreenShot() {
|
|
319
988
|
this.emitTailoringRegion();
|
|
989
|
+
const bounds = this.getBounds();
|
|
320
990
|
this.mitt.$emit(EVENT_TAILORING_SCREENSHOT, {
|
|
321
991
|
base64: this.getScreenCanvasImage(),
|
|
322
992
|
region: this.viewerElementToImageRectangle({
|
|
323
|
-
x:
|
|
324
|
-
|
|
325
|
-
|
|
993
|
+
x: bounds.minX,
|
|
994
|
+
y: bounds.minY,
|
|
995
|
+
width: bounds.width,
|
|
996
|
+
height: bounds.height,
|
|
326
997
|
}),
|
|
327
998
|
});
|
|
328
999
|
}
|
|
329
1000
|
|
|
330
|
-
/**
|
|
331
|
-
* 判断鼠标在区域内
|
|
332
|
-
* @param {Object} point 鼠标位置
|
|
333
|
-
* @param {number} point.x 鼠标x位置
|
|
334
|
-
* @param {number} point.y 鼠标y位置
|
|
335
|
-
* @return {boolean}
|
|
336
|
-
*/
|
|
337
1001
|
isPointInMatrix(point) {
|
|
338
|
-
const minX =
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return isPointInMatrix(point1, point2, point3, point4, point);
|
|
1002
|
+
const {minX, minY, maxX, maxY} = this.getBounds();
|
|
1003
|
+
return isPointInMatrix(
|
|
1004
|
+
{x: minX, y: minY},
|
|
1005
|
+
{x: maxX, y: minY},
|
|
1006
|
+
{x: minX, y: maxY},
|
|
1007
|
+
{x: maxX, y: maxY},
|
|
1008
|
+
point,
|
|
1009
|
+
);
|
|
347
1010
|
}
|
|
348
1011
|
|
|
349
1012
|
getScreenCanvasImage() {
|
|
350
1013
|
const canvas = this.viewer.drawer.canvas;
|
|
351
|
-
const minX =
|
|
352
|
-
const minY = Math.min(...this.tailoringPoints.map(({y}) => y));
|
|
353
|
-
const maxX = Math.max(...this.tailoringPoints.map(({x}) => x));
|
|
354
|
-
const maxY = Math.max(...this.tailoringPoints.map(({y}) => y));
|
|
1014
|
+
const {minX, minY, maxX, maxY} = this.getBounds();
|
|
355
1015
|
const startPoint = {x: minX, y: minY};
|
|
356
1016
|
const endPoint = {x: maxX, y: maxY};
|
|
357
1017
|
const ctx = canvas.getContext('2d');
|
|
@@ -361,6 +1021,8 @@ export class Tailoring extends ViewerCommon {
|
|
|
361
1021
|
const radioY = canvasHeight / height;
|
|
362
1022
|
const disWidth = Math.abs(endPoint.x - startPoint.x) * radioX;
|
|
363
1023
|
const disHeight = Math.abs(endPoint.y - startPoint.y) * radioY;
|
|
1024
|
+
const shapeSrcW = Math.abs(endPoint.x - startPoint.x);
|
|
1025
|
+
const shapeSrcH = Math.abs(endPoint.y - startPoint.y);
|
|
364
1026
|
if (disWidth.toFixed(5) / 1 === 0 || disHeight.toFixed(5) / 1 === 0) {
|
|
365
1027
|
return;
|
|
366
1028
|
}
|
|
@@ -385,9 +1047,8 @@ export class Tailoring extends ViewerCommon {
|
|
|
385
1047
|
copyCtx.putImageData(imageData, 0, 0, 0, 0, disWidth, disHeight);
|
|
386
1048
|
const shapeCanvas = this.kv.shape?.canvas;
|
|
387
1049
|
if (shapeCanvas && this.options.shape) {
|
|
388
|
-
copyCtx.drawImage(shapeCanvas, startPoint.x
|
|
389
|
-
|
|
390
|
-
disHeight);
|
|
1050
|
+
copyCtx.drawImage(shapeCanvas, startPoint.x, startPoint.y, shapeSrcW,
|
|
1051
|
+
shapeSrcH, 0, 0, disWidth, disHeight);
|
|
391
1052
|
}
|
|
392
1053
|
if (copyCanvas.width > this.options.resolution) {
|
|
393
1054
|
const maxCanvas = document.createElement('canvas');
|
|
@@ -401,10 +1062,9 @@ export class Tailoring extends ViewerCommon {
|
|
|
401
1062
|
0, 0, maxCanvas.width, maxCanvas.height);
|
|
402
1063
|
return maxCanvas.toDataURL(`image/${this.options.suffix || 'jpeg'}`,
|
|
403
1064
|
this.options.quality);
|
|
404
|
-
} else {
|
|
405
|
-
return copyCanvas.toDataURL(`image/${this.options.suffix || 'jpeg'}`,
|
|
406
|
-
this.options.quality);
|
|
407
1065
|
}
|
|
1066
|
+
return copyCanvas.toDataURL(`image/${this.options.suffix || 'jpeg'}`,
|
|
1067
|
+
this.options.quality);
|
|
408
1068
|
} catch (e) {
|
|
409
1069
|
console.error(e);
|
|
410
1070
|
}
|
|
@@ -412,17 +1072,23 @@ export class Tailoring extends ViewerCommon {
|
|
|
412
1072
|
|
|
413
1073
|
stopTailoring() {
|
|
414
1074
|
this.clearCanvas();
|
|
1075
|
+
this.destroyToolbar();
|
|
415
1076
|
this.tailoringPoints = [];
|
|
416
1077
|
this.isInTailoring = false;
|
|
417
1078
|
this.movePointIndex = -1;
|
|
418
1079
|
this.lastPoint = undefined;
|
|
1080
|
+
if (this.canvas) {
|
|
1081
|
+
this.canvas.style.pointerEvents = '';
|
|
1082
|
+
}
|
|
1083
|
+
this.setViewerCursor('default');
|
|
419
1084
|
}
|
|
420
1085
|
|
|
421
1086
|
change() {
|
|
422
1087
|
if (!this.isInTailoring) return;
|
|
423
1088
|
this.clearCanvas();
|
|
424
1089
|
this.emitTailoringRegion();
|
|
425
|
-
this.drawRect();
|
|
426
1090
|
this.drawTailoring();
|
|
1091
|
+
this.updateToolbarState();
|
|
1092
|
+
this.updateToolbarPosition();
|
|
427
1093
|
}
|
|
428
1094
|
}
|