kfb-view 2.1.14 → 2.1.17
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/.idea/workspace.xml +56 -45
- package/example/index.js +29 -3
- package/lib/kfb-view.js +1 -1
- package/package.json +1 -1
- package/src/components/common/common.js +48 -28
- package/src/components/shape/index.js +39 -20
- package/src/model/label.model.js +1 -1
- package/src/tool/Brush.js +1 -3
- package/src/tool/Font.js +2 -3
- package/src/tool/Star.js +0 -1
- package/src/util/canvas.js +0 -1
- package/src/view.js +1 -3
- package/test.html +24 -93
package/package.json
CHANGED
|
@@ -71,29 +71,40 @@ export class ViewerCommon extends EventEmitter {
|
|
|
71
71
|
* @return {boolean}
|
|
72
72
|
*/
|
|
73
73
|
isContainerLabel(point, list) {
|
|
74
|
-
const contain = list.find((
|
|
75
|
-
const p1 = {x: region.x, y: region.y};
|
|
76
|
-
const p2 = {x: region.x + region.width, y: region.y};
|
|
77
|
-
const p3 = {x: region.x, y: region.y + region.height};
|
|
78
|
-
const p4 = {x: region.x + region.width, y: region.y + region.height};
|
|
79
|
-
const center = {
|
|
80
|
-
x: region.x + region.width / 2, y: region.y + region.height / 2,
|
|
81
|
-
};
|
|
82
|
-
if (tool === ELLIPSE) {
|
|
83
|
-
return isPointInEllipse(point, center, Math.abs(region.width / 2),
|
|
84
|
-
Math.abs(region.height / 2 >> 0));
|
|
85
|
-
}
|
|
86
|
-
if (tool === RECTANGLE) {
|
|
87
|
-
return isPointInMatrix(p1, p2, p3, p4, point);
|
|
88
|
-
}
|
|
89
|
-
if (tool === POLYGON && isClose) {
|
|
90
|
-
return isPointInPolygon(point, points);
|
|
91
|
-
}
|
|
92
|
-
return false;
|
|
93
|
-
});
|
|
74
|
+
const contain = list.find((item) => this.isContainerInRegion(point, item));
|
|
94
75
|
return !!contain;
|
|
95
76
|
}
|
|
96
77
|
|
|
78
|
+
/**
|
|
79
|
+
* 判断点是否在某个区域内
|
|
80
|
+
* @param {Object} point
|
|
81
|
+
* @param {Object} region
|
|
82
|
+
* @param {String} tool
|
|
83
|
+
* @param {Array} points
|
|
84
|
+
* @param {boolean} isClose
|
|
85
|
+
* @return {boolean}
|
|
86
|
+
*/
|
|
87
|
+
isContainerInRegion(point, {region, tool, points, isClose}) {
|
|
88
|
+
const p1 = {x: region.x, y: region.y};
|
|
89
|
+
const p2 = {x: region.x + region.width, y: region.y};
|
|
90
|
+
const p3 = {x: region.x, y: region.y + region.height};
|
|
91
|
+
const p4 = {x: region.x + region.width, y: region.y + region.height};
|
|
92
|
+
const center = {
|
|
93
|
+
x: region.x + region.width / 2, y: region.y + region.height / 2,
|
|
94
|
+
};
|
|
95
|
+
if (tool === ELLIPSE) {
|
|
96
|
+
return isPointInEllipse(point, center, Math.abs(region.width / 2),
|
|
97
|
+
Math.abs(region.height / 2 >> 0));
|
|
98
|
+
}
|
|
99
|
+
if (tool === RECTANGLE) {
|
|
100
|
+
return isPointInMatrix(p1, p2, p3, p4, point);
|
|
101
|
+
}
|
|
102
|
+
if (tool === POLYGON && isClose) {
|
|
103
|
+
return isPointInPolygon(point, points);
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
97
108
|
/**
|
|
98
109
|
* 转化图像区域到元素坐标区域
|
|
99
110
|
* @param {number} x
|
|
@@ -149,10 +160,14 @@ export class ViewerCommon extends EventEmitter {
|
|
|
149
160
|
* @return {{}[]}
|
|
150
161
|
*/
|
|
151
162
|
imageToViewerElementPoints(points) {
|
|
152
|
-
return (points || []).map((point) =>
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
163
|
+
return (points || []).map((point) => {
|
|
164
|
+
const p = this.imageToViewerElementCoordinates(point.x, point.y);
|
|
165
|
+
return {
|
|
166
|
+
canMove: point.canMove,
|
|
167
|
+
x: p.x,
|
|
168
|
+
y: p.y,
|
|
169
|
+
};
|
|
170
|
+
});
|
|
156
171
|
}
|
|
157
172
|
|
|
158
173
|
/**
|
|
@@ -372,12 +387,17 @@ export class ViewerCommon extends EventEmitter {
|
|
|
372
387
|
/**
|
|
373
388
|
* 判断region是否在canvas中
|
|
374
389
|
* @param {Object} region
|
|
390
|
+
* @param {boolean} status
|
|
375
391
|
* @return {boolean}
|
|
376
392
|
*/
|
|
377
|
-
isInCanvas(region) {
|
|
378
|
-
const startPoint =
|
|
379
|
-
|
|
380
|
-
region.x
|
|
393
|
+
isInCanvas(region, status = false) {
|
|
394
|
+
const startPoint = status ?
|
|
395
|
+
{x: region.x, y: region.y} :
|
|
396
|
+
this.imageToViewerElementCoordinates(region.x, region.y);
|
|
397
|
+
const endPoint = status ?
|
|
398
|
+
{x: region.x + region.width, y: region.y + region.height} :
|
|
399
|
+
this.imageToViewerElementCoordinates(
|
|
400
|
+
region.x + region.width, region.y + region.height);
|
|
381
401
|
if (endPoint.x < startPoint.x) {
|
|
382
402
|
const x = startPoint.x;
|
|
383
403
|
startPoint.x = endPoint.x;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
HAS_REGION_TYPES,
|
|
3
|
-
NO_NORMAL_REGION_TYPES, POINT_TYPES,
|
|
2
|
+
HAS_REGION_TYPES,
|
|
3
|
+
MARKS, NO_NORMAL_REGION_TYPES, POINT_TYPES, POLYGON, REGION_TYPES,
|
|
4
4
|
} from '../../const/mark';
|
|
5
5
|
import {ViewerCommon} from '../common/common';
|
|
6
6
|
import * as Tools from '../../tool';
|
|
@@ -58,20 +58,29 @@ export class Shape extends ViewerCommon {
|
|
|
58
58
|
change() {
|
|
59
59
|
this.clearCanvas();
|
|
60
60
|
// 区域标注列表
|
|
61
|
-
const regionLabelList =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
const regionLabelList = [];
|
|
62
|
+
this.labelList.forEach(
|
|
63
|
+
(item) => {
|
|
64
|
+
if (REGION_TYPES.includes(item.tool) || item.isClose && item.tool ===
|
|
65
|
+
POLYGON) {
|
|
66
|
+
regionLabelList.push({
|
|
67
|
+
...item,
|
|
68
|
+
child: [],
|
|
69
|
+
self: item,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
68
73
|
// strokeStyle, lineWidth, fillStyle 相同认为是一个路径下的图形,一起绘制,优化性能
|
|
69
74
|
const sameFixWidthLabel = {}; // star, flag, star, font lineWidth固定为2
|
|
70
75
|
const sameNormalLabel = {};
|
|
71
76
|
this.labelList.forEach((item) => {
|
|
72
|
-
if (!this.isInCanvas(item.region)) return;
|
|
73
77
|
if (item.show === false) return;
|
|
74
78
|
if (!this[item.tool]) return;
|
|
79
|
+
const point = this.imageToViewerElementCoordinates(item.points[0].x,
|
|
80
|
+
item.points[0].y);
|
|
81
|
+
if ((point.x > this.canvas.width ||
|
|
82
|
+
point.y > this.canvas.height ||
|
|
83
|
+
point.x < 0 || point.y < 0)) return;
|
|
75
84
|
const key = `${item.strokeStyle ?? ''}_${item.lineWidth ??
|
|
76
85
|
''}_${item.fillStyle ?? ''}`;
|
|
77
86
|
const regionKey = `${item.strokeStyle ?? ''}_${item.lineWidth ?? ''}`;
|
|
@@ -89,37 +98,47 @@ export class Shape extends ViewerCommon {
|
|
|
89
98
|
}
|
|
90
99
|
}
|
|
91
100
|
const parent = regionLabelList.find(
|
|
92
|
-
(parent) => parent.
|
|
93
|
-
|
|
101
|
+
(parent) => HAS_REGION_TYPES.includes(parent.tool) && parent.self !==
|
|
102
|
+
item && item.points.every(
|
|
103
|
+
(point) => this.isContainerInRegion(point, parent)));
|
|
94
104
|
if (parent) {
|
|
95
105
|
parent.child.push(item);
|
|
96
106
|
}
|
|
97
107
|
});
|
|
108
|
+
const ctx = this.canvas.getContext('2d');
|
|
98
109
|
Object.values(sameFixWidthLabel).forEach((labels) => {
|
|
99
|
-
const ctx = this.canvas.getContext('2d');
|
|
100
110
|
ctx.beginPath();
|
|
101
111
|
labels.forEach((item) => {
|
|
102
112
|
this.drawLabel(item);
|
|
103
113
|
});
|
|
104
|
-
|
|
105
|
-
if (
|
|
114
|
+
const firstLabel = labels?.[0];
|
|
115
|
+
if (firstLabel?.fillStyle) {
|
|
116
|
+
ctx.fillStyle = firstLabel.fillStyle;
|
|
106
117
|
ctx.fill();
|
|
107
118
|
}
|
|
119
|
+
ctx.strokeStyle = firstLabel.strokeStyle;
|
|
120
|
+
ctx.stroke();
|
|
108
121
|
});
|
|
109
122
|
Object.values(sameNormalLabel).forEach((labels) => {
|
|
110
|
-
const ctx = this.canvas.getContext('2d');
|
|
111
123
|
ctx.beginPath();
|
|
112
124
|
labels.forEach((item) => {
|
|
113
125
|
this.drawLabel(item);
|
|
114
126
|
});
|
|
127
|
+
const firstLabel = labels?.[0];
|
|
128
|
+
ctx.lineWidth = firstLabel.lineWidth;
|
|
129
|
+
ctx.strokeStyle = firstLabel.strokeStyle;
|
|
115
130
|
ctx.stroke();
|
|
116
131
|
});
|
|
117
132
|
|
|
118
133
|
regionLabelList.forEach((item) => {
|
|
119
|
-
if (!this.isInCanvas(item.region)) return;
|
|
120
134
|
if (item.show === false) return;
|
|
121
135
|
if (!this[item.tool]) return;
|
|
122
|
-
this.
|
|
136
|
+
const point = this.imageToViewerElementCoordinates(item.points[0].x,
|
|
137
|
+
item.points[0].y);
|
|
138
|
+
if ((point.x > this.canvas.width ||
|
|
139
|
+
point.y > this.canvas.height ||
|
|
140
|
+
point.x < 0 || point.y < 0)) return;
|
|
141
|
+
this.combination.setContent(this.canvas, item);
|
|
123
142
|
this.combination.draw({
|
|
124
143
|
points: this.imageToViewerElementPoints(item.points),
|
|
125
144
|
tool: item.tool,
|
|
@@ -131,13 +150,13 @@ export class Shape extends ViewerCommon {
|
|
|
131
150
|
}
|
|
132
151
|
|
|
133
152
|
drawLabel(item) {
|
|
134
|
-
const scale = this.getImageZoom(true) / item.scale;
|
|
135
153
|
const points = this.imageToViewerElementPoints(item.points);
|
|
136
|
-
this[item.tool].setContent(this.canvas,
|
|
154
|
+
this[item.tool].setContent(this.canvas, item);
|
|
137
155
|
if (!NO_NORMAL_REGION_TYPES.includes(item.tool)) {
|
|
138
156
|
this[item.tool].draw(points, this.viewport.getRotation(),
|
|
139
157
|
this.imageToViewerElementRectangle(item.region));
|
|
140
158
|
} else {
|
|
159
|
+
const scale = this.getImageZoom(true) / item.scale;
|
|
141
160
|
this[item.tool].draw(points, scale);
|
|
142
161
|
}
|
|
143
162
|
}
|
package/src/model/label.model.js
CHANGED
|
@@ -11,7 +11,7 @@ export class LabelModel {
|
|
|
11
11
|
this.strokeStyle = data.strokeStyle || '#027AFF'; // 标注颜色
|
|
12
12
|
this.fillStyle = data.fillStyle; // 填充颜色
|
|
13
13
|
this.description = data.description; // 描述
|
|
14
|
-
this.fontSize = data.fontSize; // 字号
|
|
14
|
+
this.fontSize = data.fontSize || 14; // 字号
|
|
15
15
|
this.text = data.text; // 文字
|
|
16
16
|
this.angle = data.angle ?? 0; // 旋转角度
|
|
17
17
|
this.measure = data.measure; // 是否显示测量信息
|
package/src/tool/Brush.js
CHANGED
package/src/tool/Font.js
CHANGED
|
@@ -24,13 +24,12 @@ class Font extends Brush {
|
|
|
24
24
|
const angle = this.options.angle || 0;
|
|
25
25
|
const text = this.options.text || '编辑文字';
|
|
26
26
|
const ctx = this.canvas.getContext('2d');
|
|
27
|
-
ctx.fillStyle = this.options.
|
|
28
|
-
'#FDFDFD';
|
|
27
|
+
ctx.fillStyle = this.options.strokeStyle;
|
|
29
28
|
ctx.save();
|
|
30
29
|
ctx.translate(point.x, point.y);
|
|
31
30
|
ctx.rotate(angle / 180 * Math.PI);
|
|
32
31
|
ctx.scale(scale, scale);
|
|
33
|
-
ctx.font = `${this.options.fontSize
|
|
32
|
+
ctx.font = `${this.options.fontSize}px Arial`;
|
|
34
33
|
ctx.fillText(text, 0, 0);
|
|
35
34
|
ctx.restore();
|
|
36
35
|
}
|
package/src/tool/Star.js
CHANGED
|
@@ -24,7 +24,6 @@ class Star extends Brush {
|
|
|
24
24
|
let dist = this.options.lineWidth;
|
|
25
25
|
const point = points[0];
|
|
26
26
|
dist *= scale;
|
|
27
|
-
this.endPoint = {x: point.x, y: point.y};
|
|
28
27
|
const angle = 36 / 180 * Math.PI;
|
|
29
28
|
const angle1 = 54 / 180 * Math.PI;
|
|
30
29
|
const long_side = Math.sin(angle) * dist * 2;
|
package/src/util/canvas.js
CHANGED
|
@@ -60,7 +60,6 @@ CanvasRenderingContext2D.prototype.drawOval = function drawOval(
|
|
|
60
60
|
this.save();
|
|
61
61
|
// 选择a、b中的较大者作为arc方法的半径参数
|
|
62
62
|
const r = (a > b) ? a : b;
|
|
63
|
-
console.log(r);
|
|
64
63
|
const ratioX = a / r; // 横轴缩放比率
|
|
65
64
|
const ratioY = b / r; // 纵轴缩放比率
|
|
66
65
|
this.scale(ratioX, ratioY); // 进行缩放(均匀压缩)
|
package/src/view.js
CHANGED
|
@@ -96,7 +96,6 @@ export default class KfbView extends EventEmitter {
|
|
|
96
96
|
this.labelList = list.map((item) => new LabelModel({
|
|
97
97
|
...item,
|
|
98
98
|
region: item.region || pointsToRegion(item.points),
|
|
99
|
-
strokeStyle: item.strokeStyle || '#0000FF',
|
|
100
99
|
__data__: deepClone(item),
|
|
101
100
|
}));
|
|
102
101
|
this.board?.setLabelList?.(this.labelList);
|
|
@@ -106,10 +105,9 @@ export default class KfbView extends EventEmitter {
|
|
|
106
105
|
|
|
107
106
|
appendLabelList(list) {
|
|
108
107
|
this.labelList = [
|
|
109
|
-
...this.labelList, list.map((item) => new LabelModel({
|
|
108
|
+
...this.labelList, ...list.map((item) => new LabelModel({
|
|
110
109
|
...item,
|
|
111
110
|
region: item.region || pointsToRegion(item.points),
|
|
112
|
-
strokeStyle: item.strokeStyle || '#0000FF',
|
|
113
111
|
__data__: deepClone(item),
|
|
114
112
|
}))];
|
|
115
113
|
this.board?.setLabelList?.(this.labelList);
|
package/test.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<body>
|
|
8
8
|
|
|
9
9
|
<canvas id="canvas" width="800" height="600"></canvas>
|
|
10
|
-
|
|
10
|
+
<button id="reDraw">重绘</button>
|
|
11
11
|
<script>
|
|
12
12
|
/**
|
|
13
13
|
* 清除圆形区域
|
|
@@ -206,109 +206,40 @@
|
|
|
206
206
|
</script>
|
|
207
207
|
<script>
|
|
208
208
|
const canvas = document.getElementById('canvas');
|
|
209
|
+
const reDrawBtn = document.getElementById('reDraw');
|
|
209
210
|
const ctx = canvas.getContext('2d');
|
|
210
|
-
ctx.beginPath();
|
|
211
|
-
|
|
212
|
-
drawA([{x: 10, y: 10}, {x: 50, y: 40}]);
|
|
213
|
-
drawA([{x: 70, y: 70}, {x: 120, y: 120}]);
|
|
214
|
-
|
|
215
|
-
drawB([{x: 130, y: 130}, {x: 90, y: 60}]);
|
|
216
|
-
drawB([{x: 20, y: 20}, {x: 20, y: 90}]);
|
|
217
|
-
drawF([{x: 300, y: 200}]);
|
|
218
|
-
drawF([{x: 100, y: 500}]);
|
|
219
|
-
|
|
220
|
-
function drawA(points) {
|
|
221
|
-
const startPoint = points[0];
|
|
222
|
-
const endPoint = points[points.length - 1];
|
|
223
|
-
ctx.lineWidth = 2;
|
|
224
|
-
ctx.strokeStyle = '#0000FF';
|
|
225
|
-
ctx.moveTo(startPoint.x, startPoint.y);
|
|
226
|
-
ctx.lineTo(endPoint.x, endPoint.y);
|
|
227
|
-
ctx.drawArrow(startPoint, endPoint);
|
|
228
|
-
ctx.stroke();
|
|
229
|
-
}
|
|
230
211
|
|
|
231
|
-
function
|
|
232
|
-
|
|
233
|
-
const endPoint = points[points.length - 1];
|
|
234
|
-
ctx.lineWidth = 2;
|
|
235
|
-
ctx.strokeStyle = '#00FF00';
|
|
236
|
-
ctx.moveTo(startPoint.x, startPoint.y);
|
|
237
|
-
ctx.lineTo(endPoint.x, endPoint.y);
|
|
238
|
-
ctx.drawArrow(startPoint, endPoint);
|
|
239
|
-
ctx.drawArrow(endPoint, startPoint);
|
|
240
|
-
ctx.stroke();
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function drawF(points, scale = 1) {
|
|
244
|
-
let dist = 30;
|
|
245
|
-
dist *= scale;
|
|
212
|
+
function drwaP(points) {
|
|
213
|
+
let dist = 4 * 1;
|
|
246
214
|
const point = points[0];
|
|
215
|
+
const ctx = canvas.getContext('2d');
|
|
216
|
+
const {x, y} = point;
|
|
247
217
|
ctx.lineWidth = 2;
|
|
248
|
-
ctx.
|
|
249
|
-
ctx.
|
|
250
|
-
ctx.lineTo(point.x, point.y - dist);
|
|
251
|
-
const _dist = dist / 5;
|
|
252
|
-
const p1 = {
|
|
253
|
-
x: point.x + _dist,
|
|
254
|
-
y: point.y - dist,
|
|
255
|
-
};
|
|
256
|
-
const p2 = {
|
|
257
|
-
x: point.x + _dist * 2,
|
|
258
|
-
y: point.y - dist - _dist,
|
|
259
|
-
};
|
|
260
|
-
const p3 = {
|
|
261
|
-
x: point.x + _dist * 4,
|
|
262
|
-
y: point.y - dist + _dist,
|
|
263
|
-
};
|
|
264
|
-
const p4 = {
|
|
265
|
-
x: point.x + dist,
|
|
266
|
-
y: point.y - dist,
|
|
267
|
-
};
|
|
268
|
-
ctx.bezierCurve([p1, p2, p3, p4]);
|
|
269
|
-
ctx.moveTo(p4.x, p4.y);
|
|
270
|
-
ctx.lineTo(p4.x, p4.y + dist / 2);
|
|
271
|
-
const p11 = {
|
|
272
|
-
x: point.x + _dist,
|
|
273
|
-
y: point.y - dist / 2,
|
|
274
|
-
};
|
|
275
|
-
const p22 = {
|
|
276
|
-
x: point.x + _dist * 2,
|
|
277
|
-
y: point.y - dist / 2 - _dist,
|
|
278
|
-
};
|
|
279
|
-
const p33 = {
|
|
280
|
-
x: point.x + _dist * 4,
|
|
281
|
-
y: point.y - dist / 2 + _dist,
|
|
282
|
-
};
|
|
283
|
-
const p44 = {
|
|
284
|
-
x: point.x + dist,
|
|
285
|
-
y: point.y - dist / 2,
|
|
286
|
-
};
|
|
287
|
-
ctx.bezierCurve([p11, p22, p33, p44]);
|
|
288
|
-
ctx.moveTo(p1.x, p1.y);
|
|
289
|
-
ctx.lineTo(p11.x, p11.y);
|
|
290
|
-
ctx.stroke();
|
|
218
|
+
ctx.moveTo(x + dist, y);
|
|
219
|
+
ctx.arc(x, y, dist, 0, Math.PI * 2, !1);
|
|
291
220
|
}
|
|
292
221
|
|
|
293
|
-
|
|
294
|
-
console.time('draw');
|
|
222
|
+
ctx.beginPath();
|
|
295
223
|
ctx.fillStyle = '#ff0000';
|
|
296
224
|
ctx.strokeStyle = '#00ff00';
|
|
297
225
|
ctx.lineWidth = 2;
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
for (let j = 0; j < 600; j++) {
|
|
301
|
-
if (i % 50 === 0 && j % 50 === 0) {
|
|
302
|
-
ctx.moveTo(i + 4, j);
|
|
303
|
-
ctx.arc(i, j, 4, 0, Math.PI * 2, !1);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
226
|
+
for (let i = 0; i < 4000; i++) {
|
|
227
|
+
drwaP([{x: Math.random() * 800, y: Math.random() * 600}]);
|
|
306
228
|
}
|
|
307
|
-
ctx.fill();
|
|
308
229
|
ctx.stroke();
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
230
|
+
reDrawBtn.addEventListener('click', (e) => {
|
|
231
|
+
ctx.clearRect(0, 0, 800, 600);
|
|
232
|
+
console.time('draw');
|
|
233
|
+
ctx.beginPath();
|
|
234
|
+
ctx.fillStyle = '#ff0000';
|
|
235
|
+
ctx.strokeStyle = '#00ff00';
|
|
236
|
+
ctx.lineWidth = 2;
|
|
237
|
+
for (let i = 0; i < 10000; i++) {
|
|
238
|
+
drwaP([{x: Math.random() * 800, y: Math.random() * 600}]);
|
|
239
|
+
}
|
|
240
|
+
ctx.stroke();
|
|
241
|
+
console.timeEnd('draw');
|
|
242
|
+
});
|
|
312
243
|
|
|
313
244
|
</script>
|
|
314
245
|
</body>
|