luo-image-annotator 0.0.1

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.
@@ -0,0 +1,1152 @@
1
+ var pt = Object.defineProperty;
2
+ var xt = (u, t, e) => t in u ? pt(u, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : u[t] = e;
3
+ var C = (u, t, e) => xt(u, typeof t != "symbol" ? t + "" : t, e);
4
+ import { defineComponent as q, ref as k, computed as bt, onMounted as At, watch as it, resolveComponent as P, openBlock as m, createElementBlock as x, normalizeClass as N, createElementVNode as h, createVNode as b, withCtx as I, Fragment as $, renderList as F, createBlock as X, resolveDynamicComponent as Ct, createCommentVNode as L, normalizeStyle as K, toDisplayString as D, createTextVNode as J, withDirectives as j, vModelText as Y, nextTick as wt } from "vue";
5
+ const Z = (u, t) => Math.sqrt(Math.pow(u.x - t.x, 2) + Math.pow(u.y - t.y, 2)), _t = (u, t) => {
6
+ let e = !1;
7
+ for (let n = 0, a = t.length - 1; n < t.length; a = n++) {
8
+ const i = t[n].x, o = t[n].y, s = t[a].x, r = t[a].y;
9
+ o > u.y != r > u.y && u.x < (s - i) * (u.y - o) / (r - o) + i && (e = !e);
10
+ }
11
+ return e;
12
+ }, nt = (u, t, e) => {
13
+ const n = e * (Math.PI / 180), a = Math.cos(n), i = Math.sin(n), o = u.x - t.x, s = u.y - t.y;
14
+ return {
15
+ x: t.x + (o * a - s * i),
16
+ y: t.y + (o * i + s * a)
17
+ };
18
+ };
19
+ class kt {
20
+ constructor(t) {
21
+ C(this, "canvas");
22
+ C(this, "ctx");
23
+ C(this, "img");
24
+ C(this, "annotations", []);
25
+ // 状态
26
+ C(this, "currentTool", null);
27
+ C(this, "activeAnnotation", null);
28
+ C(this, "hoverAnnotation", null);
29
+ C(this, "isDrawing", !1);
30
+ C(this, "isDragging", !1);
31
+ C(this, "isPanning", !1);
32
+ // 平移模式
33
+ C(this, "panStartPoint", null);
34
+ // 平移起点
35
+ C(this, "dragStartPoint", null);
36
+ C(this, "dragStartAnnotation", null);
37
+ // 快照用于撤销/diff
38
+ C(this, "lastMouseMovePoint", null);
39
+ // 用于绘制辅助线
40
+ C(this, "isHoveringStartPoint", !1);
41
+ // 多边形闭合吸附状态
42
+ // 设置
43
+ C(this, "currentLabelColor", "#FF4081");
44
+ // 当前选中标签颜色
45
+ C(this, "visibleLabels", /* @__PURE__ */ new Set());
46
+ // 可见标签集合 (如果为空则全部可见,或者由外部控制渲染列表)
47
+ // 交互
48
+ C(this, "selectedHandleIndex", -1);
49
+ // -1: 主体, >=0: 顶点索引, -2: 旋转手柄
50
+ C(this, "hoverHandleIndex", -1);
51
+ // 视口
52
+ C(this, "scale", 1);
53
+ C(this, "offset", { x: 0, y: 0 });
54
+ C(this, "listeners", {});
55
+ C(this, "imageUrl", "");
56
+ this.canvas = t;
57
+ const e = t.getContext("2d");
58
+ if (!e) throw new Error("Could not get 2d context");
59
+ this.ctx = e, this.img = new Image(), this.img.crossOrigin = "Anonymous", this.img.onload = () => {
60
+ this.fitImageToCanvas(), this.render();
61
+ }, this.bindEvents();
62
+ }
63
+ // --- 公共 API ---
64
+ on(t, e) {
65
+ this.listeners[t] || (this.listeners[t] = []), this.listeners[t].push(e);
66
+ }
67
+ emit(t, e) {
68
+ this.listeners[t] && this.listeners[t].forEach((n) => n(e));
69
+ }
70
+ loadImage(t) {
71
+ this.imageUrl = t, this.img.src = t, this.activeAnnotation = null, this.isDrawing = !1;
72
+ }
73
+ setAnnotations(t) {
74
+ this.annotations = JSON.parse(JSON.stringify(t)), this.render();
75
+ }
76
+ getAnnotations() {
77
+ return this.annotations;
78
+ }
79
+ setTool(t) {
80
+ t === "pan" ? (this.currentTool = null, this.isPanning = !1, this.canvas.style.cursor = "grab") : t === "select" ? (this.currentTool = null, this.canvas.style.cursor = "default") : (this.currentTool = t, this.canvas.style.cursor = "crosshair"), this.activeAnnotation = null, this.isDrawing = !1, this.render();
81
+ }
82
+ setLabelStyle(t) {
83
+ this.currentLabelColor = t;
84
+ }
85
+ setVisibleLabels(t) {
86
+ this.visibleLabels = new Set(t), this.render();
87
+ }
88
+ zoom(t) {
89
+ const n = t > 0 ? this.scale * 1.1 : this.scale / 1.1;
90
+ if (n < 0.1 || n > 10) return;
91
+ const a = this.canvas.width / 2, i = this.canvas.height / 2, o = this.toImageCoords(a, i);
92
+ this.scale = n, this.offset.x = a - o.x * this.scale, this.offset.y = i - o.y * this.scale, this.render();
93
+ }
94
+ resize() {
95
+ this.fitImageToCanvas(), this.render();
96
+ }
97
+ // --- 坐标系统 ---
98
+ toImageCoords(t, e) {
99
+ return {
100
+ x: (t - this.offset.x) / this.scale,
101
+ y: (e - this.offset.y) / this.scale
102
+ };
103
+ }
104
+ toScreenCoords(t, e) {
105
+ return {
106
+ x: t * this.scale + this.offset.x,
107
+ y: e * this.scale + this.offset.y
108
+ };
109
+ }
110
+ fitImageToCanvas() {
111
+ const t = this.canvas.parentElement;
112
+ if (t) {
113
+ if (this.canvas.width = t.clientWidth, this.canvas.height = t.clientHeight, this.img.width === 0) return;
114
+ const e = this.canvas.width / this.img.width, n = this.canvas.height / this.img.height;
115
+ this.scale = Math.min(e, n), this.offset.x = (this.canvas.width - this.img.width * this.scale) / 2, this.offset.y = (this.canvas.height - this.img.height * this.scale) / 2;
116
+ }
117
+ }
118
+ // --- 事件处理 ---
119
+ bindEvents() {
120
+ this.canvas.addEventListener("mousedown", this.handleMouseDown.bind(this)), this.canvas.addEventListener("mousemove", this.handleMouseMove.bind(this)), this.canvas.addEventListener("mouseup", this.handleMouseUp.bind(this)), this.canvas.addEventListener("mouseleave", this.handleMouseUp.bind(this)), window.addEventListener("keydown", this.handleKeyDown.bind(this));
121
+ }
122
+ handleKeyDown(t) {
123
+ (t.key === "Delete" || t.key === "Backspace") && this.activeAnnotation && this.deleteAnnotation(this.activeAnnotation.id);
124
+ }
125
+ deleteAnnotation(t) {
126
+ const e = this.annotations.findIndex((n) => n.id === t);
127
+ if (e > -1) {
128
+ const n = this.annotations[e];
129
+ this.annotations.splice(e, 1), this.activeAnnotation = null, this.emit("annotationChange", {
130
+ action: "delete",
131
+ changedItem: n,
132
+ imageUrl: this.imageUrl
133
+ }), this.render();
134
+ }
135
+ }
136
+ handleMouseDown(t) {
137
+ const e = this.canvas.getBoundingClientRect(), n = t.clientX - e.left, a = t.clientY - e.top, i = this.toImageCoords(n, a);
138
+ if (this.canvas.style.cursor === "grab" || this.canvas.style.cursor === "grabbing") {
139
+ this.isPanning = !0, this.panStartPoint = { x: n, y: a }, this.canvas.style.cursor = "grabbing";
140
+ return;
141
+ }
142
+ if (this.activeAnnotation) {
143
+ const r = this.getHitHandle(n, a, this.activeAnnotation);
144
+ if (r !== -100) {
145
+ this.isDragging = !0, this.dragStartPoint = i, this.selectedHandleIndex = r, this.dragStartAnnotation = JSON.parse(JSON.stringify(this.activeAnnotation));
146
+ return;
147
+ }
148
+ }
149
+ const o = this.getHitCategory(n, a);
150
+ if (o) {
151
+ this.activeAnnotation = o, this.isDragging = !1, this.selectedHandleIndex = -1, this.emit("annotationChange", { action: "select", changedItem: o, imageUrl: this.imageUrl }), this.render();
152
+ return;
153
+ }
154
+ const s = this.getHitAnnotation(i);
155
+ if (this.currentTool) {
156
+ if (this.isDrawing && this.currentTool === "polygon" && this.activeAnnotation) {
157
+ const r = this.activeAnnotation.coordinates;
158
+ if (r.points.length > 2 && Z(i, r.points[0]) < 20 / this.scale) {
159
+ this.finishDrawing();
160
+ return;
161
+ }
162
+ this.startDrawing(i);
163
+ return;
164
+ }
165
+ if (this.currentTool) {
166
+ if (this.currentTool === "polygon" && !this.isDrawing) {
167
+ this.startDrawing(i);
168
+ return;
169
+ }
170
+ if (s) {
171
+ this.activeAnnotation = s, this.isDragging = !0, this.dragStartPoint = i, this.selectedHandleIndex = -1, this.dragStartAnnotation = JSON.parse(JSON.stringify(s)), this.emit("annotationChange", { action: "select", changedItem: s, imageUrl: this.imageUrl }), this.render();
172
+ return;
173
+ }
174
+ this.startDrawing(i);
175
+ } else s ? (this.activeAnnotation = s, this.isDragging = !0, this.dragStartPoint = i, this.selectedHandleIndex = -1, this.dragStartAnnotation = JSON.parse(JSON.stringify(s)), this.emit("annotationChange", { action: "select", changedItem: s, imageUrl: this.imageUrl }), this.render()) : (this.activeAnnotation = null, this.render());
176
+ } else if (s) {
177
+ if (!(this.visibleLabels.size > 0 && !this.visibleLabels.has(s.label))) {
178
+ this.activeAnnotation = s, this.isDragging = !0, this.dragStartPoint = i, this.selectedHandleIndex = -1, this.dragStartAnnotation = JSON.parse(JSON.stringify(s)), this.emit("annotationChange", { action: "select", changedItem: s, imageUrl: this.imageUrl }), this.render();
179
+ return;
180
+ }
181
+ } else
182
+ this.activeAnnotation = null, this.render();
183
+ }
184
+ handleMouseMove(t) {
185
+ const e = this.canvas.getBoundingClientRect(), n = t.clientX - e.left, a = t.clientY - e.top, i = this.toImageCoords(n, a);
186
+ if (this.isPanning && this.panStartPoint) {
187
+ const o = n - this.panStartPoint.x, s = a - this.panStartPoint.y;
188
+ this.offset.x += o, this.offset.y += s, this.panStartPoint = { x: n, y: a }, this.render();
189
+ return;
190
+ }
191
+ if (this.lastMouseMovePoint = i, this.isDrawing) {
192
+ if (this.currentTool === "polygon" && this.activeAnnotation) {
193
+ const o = this.activeAnnotation.coordinates;
194
+ if (o.points.length > 2) {
195
+ const s = o.points[0], r = Z(i, s);
196
+ this.isHoveringStartPoint = r < 20 / this.scale;
197
+ } else
198
+ this.isHoveringStartPoint = !1;
199
+ }
200
+ this.updateDrawing(i);
201
+ } else this.isDragging && this.activeAnnotation && this.dragStartPoint ? this.updateDragging(i) : this.checkHover(n, a, i);
202
+ this.render();
203
+ }
204
+ handleMouseUp(t) {
205
+ if (this.isPanning) {
206
+ this.isPanning = !1, this.panStartPoint = null, this.canvas.style.cursor = "grab";
207
+ return;
208
+ }
209
+ if (this.isDrawing) {
210
+ if (this.currentTool === "polygon") {
211
+ this.render();
212
+ return;
213
+ }
214
+ this.finishDrawing();
215
+ } else this.isDragging && this.activeAnnotation && this.emit("annotationChange", {
216
+ action: "update",
217
+ changedItem: this.activeAnnotation,
218
+ imageUrl: this.imageUrl
219
+ });
220
+ this.currentTool !== "polygon" && (this.isDrawing = !1), this.isDragging = !1, this.dragStartPoint = null, this.selectedHandleIndex = -1, this.render();
221
+ }
222
+ // --- 绘制逻辑 ---
223
+ // 辅助函数:将 hex 转换为 rgba
224
+ hexToRgba(t, e) {
225
+ if (!t.startsWith("#")) return t;
226
+ const n = parseInt(t.slice(1, 3), 16), a = parseInt(t.slice(3, 5), 16), i = parseInt(t.slice(5, 7), 16);
227
+ return `rgba(${n}, ${a}, ${i}, ${e})`;
228
+ }
229
+ startDrawing(t) {
230
+ if (!this.currentTool) return;
231
+ const e = Date.now().toString();
232
+ if (this.hexToRgba(this.currentLabelColor, 0.2), this.currentLabelColor, this.currentTool === "rectangle")
233
+ this.isDrawing = !0, this.dragStartPoint = t, this.activeAnnotation = {
234
+ id: e,
235
+ type: "rectangle",
236
+ label: "",
237
+ // 组件应填充此项
238
+ coordinates: { x1: t.x, y1: t.y, x2: t.x, y2: t.y },
239
+ style: { strokeColor: this.currentLabelColor }
240
+ };
241
+ else if (this.currentTool === "point") {
242
+ const n = {
243
+ id: e,
244
+ type: "point",
245
+ label: "",
246
+ coordinates: { points: [t] },
247
+ style: { strokeColor: this.currentLabelColor }
248
+ };
249
+ this.annotations.push(n), this.emit("annotationChange", { action: "add", changedItem: n, imageUrl: this.imageUrl }), this.activeAnnotation = n;
250
+ } else if (this.currentTool === "polygon")
251
+ this.activeAnnotation && this.activeAnnotation.type === "polygon" && this.isDrawing ? this.activeAnnotation.coordinates.points.push(t) : (this.isDrawing = !0, this.activeAnnotation = {
252
+ id: e,
253
+ type: "polygon",
254
+ label: "",
255
+ coordinates: { points: [t] },
256
+ style: { strokeColor: this.currentLabelColor }
257
+ });
258
+ else if (this.currentTool === "category") {
259
+ const n = {
260
+ id: e,
261
+ type: "category",
262
+ label: "",
263
+ // 将被填充
264
+ coordinates: null,
265
+ // 或位置?
266
+ style: { strokeColor: this.currentLabelColor }
267
+ };
268
+ this.annotations.push(n), this.emit("annotationChange", { action: "add", changedItem: n, imageUrl: this.imageUrl }), this.activeAnnotation = n;
269
+ } else this.currentTool === "rotatedRect" && (this.isDrawing = !0, this.dragStartPoint = t, this.activeAnnotation = {
270
+ id: e,
271
+ type: "rotatedRect",
272
+ label: "",
273
+ coordinates: { x: t.x, y: t.y, width: 0, height: 0, angle: 0 },
274
+ style: { strokeColor: this.currentLabelColor }
275
+ });
276
+ }
277
+ updateDrawing(t) {
278
+ if (this.activeAnnotation)
279
+ if (this.activeAnnotation.type === "rectangle" && this.dragStartPoint) {
280
+ const e = this.activeAnnotation.coordinates;
281
+ e.x2 = t.x, e.y2 = t.y;
282
+ } else if (this.activeAnnotation.type === "rotatedRect" && this.dragStartPoint) {
283
+ const e = this.activeAnnotation.coordinates, n = Math.abs(t.x - this.dragStartPoint.x), a = Math.abs(t.y - this.dragStartPoint.y);
284
+ e.width = n * 2, e.height = a * 2;
285
+ } else this.activeAnnotation.type;
286
+ }
287
+ finishDrawing() {
288
+ if (this.activeAnnotation) {
289
+ if (this.activeAnnotation.type === "rectangle") {
290
+ const t = this.activeAnnotation.coordinates;
291
+ if (Math.abs(t.x1 - t.x2) < 2 || Math.abs(t.y1 - t.y2) < 2) {
292
+ this.activeAnnotation = null, this.isDrawing = !1;
293
+ return;
294
+ }
295
+ const e = Math.min(t.x1, t.x2), n = Math.max(t.x1, t.x2), a = Math.min(t.y1, t.y2), i = Math.max(t.y1, t.y2);
296
+ t.x1 = e, t.x2 = n, t.y1 = a, t.y2 = i, this.annotations.push(this.activeAnnotation), this.emit("annotationChange", { action: "add", changedItem: this.activeAnnotation, imageUrl: this.imageUrl });
297
+ } else if (this.activeAnnotation.type === "polygon") {
298
+ if (this.activeAnnotation.coordinates.points.length < 3) {
299
+ this.activeAnnotation = null, this.isDrawing = !1;
300
+ return;
301
+ }
302
+ this.annotations.push(this.activeAnnotation), this.emit("annotationChange", { action: "add", changedItem: this.activeAnnotation, imageUrl: this.imageUrl });
303
+ } else if (this.activeAnnotation.type === "rotatedRect") {
304
+ const t = this.activeAnnotation.coordinates;
305
+ if (t.width < 2 || t.height < 2) {
306
+ this.activeAnnotation = null, this.isDrawing = !1;
307
+ return;
308
+ }
309
+ this.annotations.push(this.activeAnnotation), this.emit("annotationChange", { action: "add", changedItem: this.activeAnnotation, imageUrl: this.imageUrl });
310
+ }
311
+ this.activeAnnotation.type;
312
+ }
313
+ this.isDrawing = !1;
314
+ }
315
+ updateDragging(t) {
316
+ if (!this.activeAnnotation || !this.dragStartPoint || !this.dragStartAnnotation) return;
317
+ const e = t.x - this.dragStartPoint.x, n = t.y - this.dragStartPoint.y;
318
+ this.selectedHandleIndex === -1 ? this.moveAnnotation(this.activeAnnotation, this.dragStartAnnotation, e, n) : this.resizeAnnotation(this.activeAnnotation, this.dragStartAnnotation, this.selectedHandleIndex, t);
319
+ }
320
+ moveAnnotation(t, e, n, a) {
321
+ if (t.type === "rectangle") {
322
+ const i = e.coordinates, o = t.coordinates;
323
+ o.x1 = i.x1 + n, o.x2 = i.x2 + n, o.y1 = i.y1 + a, o.y2 = i.y2 + a;
324
+ } else if (t.type === "point") {
325
+ const i = e.coordinates, o = t.coordinates;
326
+ o.points = i.points.map((s) => ({ x: s.x + n, y: s.y + a }));
327
+ } else if (t.type === "rotatedRect") {
328
+ const i = e.coordinates, o = t.coordinates;
329
+ o.x = i.x + n, o.y = i.y + a;
330
+ } else if (t.type === "polygon") {
331
+ const i = e.coordinates, o = t.coordinates;
332
+ o.points = i.points.map((s) => ({ x: s.x + n, y: s.y + a }));
333
+ }
334
+ }
335
+ resizeAnnotation(t, e, n, a) {
336
+ if (t.type === "rectangle") {
337
+ const i = t.coordinates;
338
+ n === 0 && (i.x1 = a.x, i.y1 = a.y), n === 1 && (i.x2 = a.x, i.y1 = a.y), n === 2 && (i.x2 = a.x, i.y2 = a.y), n === 3 && (i.x1 = a.x, i.y2 = a.y);
339
+ } else if (t.type === "polygon") {
340
+ const i = t.coordinates;
341
+ n >= 0 && n < i.points.length && (i.points[n] = a);
342
+ } else if (t.type === "point") {
343
+ const i = t.coordinates;
344
+ n >= 0 && n < i.points.length && (i.points[n] = a);
345
+ } else if (t.type === "rotatedRect") {
346
+ const i = t.coordinates;
347
+ if (n === -2) {
348
+ const o = i.x, s = i.y, r = a.x - o, v = a.y - s;
349
+ let d = Math.atan2(v, r) * 180 / Math.PI;
350
+ d += 90, i.angle = d;
351
+ } else {
352
+ const o = i.angle * Math.PI / 180, s = Math.cos(-o), r = Math.sin(-o), v = a.x - i.x, d = a.y - i.y, g = v * s - d * r, f = v * r + d * s;
353
+ (n === 0 || n === 3) && (i.width / 2, i.width = Math.abs(g) * 2), (n === 1 || n === 2) && (i.width = Math.abs(g) * 2), (n === 0 || n === 1) && (i.height = Math.abs(f) * 2), (n === 2 || n === 3) && (i.height = Math.abs(f) * 2);
354
+ }
355
+ }
356
+ }
357
+ // --- 命中测试和渲染辅助函数 ---
358
+ getHitAnnotation(t) {
359
+ for (let e = this.annotations.length - 1; e >= 0; e--) {
360
+ const n = this.annotations[e];
361
+ if (this.isPointInAnnotation(t, n))
362
+ return n;
363
+ }
364
+ return null;
365
+ }
366
+ isPointInAnnotation(t, e) {
367
+ if (e.type === "rectangle") {
368
+ const n = e.coordinates;
369
+ return t.x >= n.x1 && t.x <= n.x2 && t.y >= n.y1 && t.y <= n.y2;
370
+ } else if (e.type === "polygon") {
371
+ const n = e.coordinates;
372
+ return _t(t, n.points);
373
+ } else if (e.type === "rotatedRect") {
374
+ const n = e.coordinates, a = nt(t, { x: n.x, y: n.y }, -n.angle), i = n.width / 2, o = n.height / 2;
375
+ return a.x >= n.x - i && a.x <= n.x + i && a.y >= n.y - o && a.y <= n.y + o;
376
+ } else if (e.type === "point")
377
+ return e.coordinates.points.some((a) => Z(t, a) < 10 / this.scale);
378
+ return !1;
379
+ }
380
+ getHitHandle(t, e, n) {
381
+ const a = this.getAnnotationHandles(n), i = 6;
382
+ for (let o = 0; o < a.length; o++) {
383
+ const s = a[o], r = this.toScreenCoords(s.x, s.y);
384
+ if (Math.abs(t - r.x) < i && Math.abs(e - r.y) < i)
385
+ return n.type === "rotatedRect" && o === 4 ? -2 : o;
386
+ }
387
+ return -100;
388
+ }
389
+ getAnnotationHandles(t) {
390
+ if (t.type === "rectangle") {
391
+ const e = t.coordinates;
392
+ return [
393
+ { x: e.x1, y: e.y1 },
394
+ // 左上
395
+ { x: e.x2, y: e.y1 },
396
+ // 右上
397
+ { x: e.x2, y: e.y2 },
398
+ // 右下
399
+ { x: e.x1, y: e.y2 }
400
+ // 左下
401
+ ];
402
+ } else {
403
+ if (t.type === "polygon")
404
+ return t.coordinates.points;
405
+ if (t.type === "point")
406
+ return t.coordinates.points;
407
+ if (t.type === "rotatedRect") {
408
+ const e = t.coordinates, n = { x: e.x, y: e.y }, a = e.width / 2, i = e.height / 2, o = { x: e.x - a, y: e.y - i }, s = { x: e.x + a, y: e.y - i }, r = { x: e.x + a, y: e.y + i }, v = { x: e.x - a, y: e.y + i }, d = { x: e.x, y: e.y - i - 20 / this.scale };
409
+ return [o, s, r, v, d].map((g) => nt(g, n, e.angle));
410
+ }
411
+ }
412
+ return [];
413
+ }
414
+ checkHover(t, e, n) {
415
+ const a = this.getHitCategory(t, e);
416
+ if (a) {
417
+ this.canvas.style.cursor = "pointer", this.hoverAnnotation = a;
418
+ return;
419
+ }
420
+ if (this.activeAnnotation && this.getHitHandle(t, e, this.activeAnnotation) !== -100) {
421
+ this.canvas.style.cursor = "pointer";
422
+ return;
423
+ }
424
+ const i = this.getHitAnnotation(n);
425
+ i ? (this.canvas.style.cursor = "move", this.hoverAnnotation = i) : (this.canvas.style.cursor = "default", this.hoverAnnotation = null);
426
+ }
427
+ render() {
428
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.img.complete && this.img.width > 0 && this.ctx.drawImage(this.img, this.offset.x, this.offset.y, this.img.width * this.scale, this.img.height * this.scale), this.annotations.forEach((t) => {
429
+ t !== this.activeAnnotation && this.drawItem(t, !1);
430
+ }), this.activeAnnotation && this.drawItem(this.activeAnnotation, !0), this.renderCategories();
431
+ }
432
+ renderCategories() {
433
+ const t = this.annotations.filter((r) => r.type === "category");
434
+ if (t.length === 0) return;
435
+ this.ctx.save(), this.ctx.font = "14px sans-serif", this.ctx.textBaseline = "top";
436
+ let e = 10;
437
+ const n = 10, a = 8, i = 4, o = 24, s = 8;
438
+ t.forEach((r) => {
439
+ const v = r.label || "Unlabeled", d = this.ctx.measureText(v).width + a * 2, g = this.activeAnnotation === r, f = this.hoverAnnotation === r;
440
+ this.ctx.fillStyle = g ? "#E3F2FD" : f ? "#F5F5F5" : "rgba(255, 255, 255, 0.9)", this.ctx.strokeStyle = g ? "#2196F3" : "#666", this.ctx.lineWidth = g ? 2 : 1, this.ctx.beginPath(), this.ctx.rect(e, n, d, o), this.ctx.fill(), this.ctx.stroke(), this.ctx.fillStyle = g ? "#1976D2" : "#333", this.ctx.fillText(v, e + a, n + i), e += d + s;
441
+ }), this.ctx.restore();
442
+ }
443
+ getHitCategory(t, e) {
444
+ const n = this.annotations.filter((d) => d.type === "category");
445
+ if (n.length === 0) return null;
446
+ this.ctx.save(), this.ctx.font = "14px sans-serif";
447
+ let a = 10;
448
+ const i = 10, o = 8, s = 24, r = 8;
449
+ let v = null;
450
+ for (const d of n) {
451
+ const g = d.label || "Unlabeled", f = this.ctx.measureText(g).width + o * 2;
452
+ if (t >= a && t <= a + f && e >= i && e <= i + s) {
453
+ v = d;
454
+ break;
455
+ }
456
+ a += f + r;
457
+ }
458
+ return this.ctx.restore(), v;
459
+ }
460
+ drawItem(t, e) {
461
+ var i;
462
+ if (this.visibleLabels.size > 0 && !this.visibleLabels.has(t.label) && !e)
463
+ return;
464
+ this.ctx.save();
465
+ const n = ((i = t.style) == null ? void 0 : i.strokeColor) || "#FF4081", a = e ? "#00E5FF" : n;
466
+ if (this.ctx.strokeStyle = a, this.ctx.lineWidth = 2, e ? this.ctx.fillStyle = "rgba(0, 229, 255, 0.2)" : this.ctx.fillStyle = this.hexToRgba(n, 0.2), t.type === "rectangle") {
467
+ const o = t.coordinates, s = this.toScreenCoords(o.x1, o.y1), r = this.toScreenCoords(o.x2, o.y2), v = Math.min(s.x, r.x), d = Math.min(s.y, r.y), g = Math.abs(s.x - r.x), f = Math.abs(s.y - r.y);
468
+ this.ctx.strokeRect(v, d, g, f), this.ctx.fillStyle = e ? "rgba(0, 229, 255, 0.2)" : "rgba(255, 64, 129, 0.2)", this.ctx.fillRect(v, d, g, f), e && this.drawHandles(this.getAnnotationHandles(t));
469
+ } else if (t.type === "polygon") {
470
+ const o = t.coordinates;
471
+ if (o.points.length === 0) {
472
+ this.ctx.restore();
473
+ return;
474
+ }
475
+ this.ctx.beginPath();
476
+ const s = this.toScreenCoords(o.points[0].x, o.points[0].y);
477
+ this.ctx.moveTo(s.x, s.y);
478
+ for (let r = 1; r < o.points.length; r++) {
479
+ const v = this.toScreenCoords(o.points[r].x, o.points[r].y);
480
+ this.ctx.lineTo(v.x, v.y);
481
+ }
482
+ if (!this.isDrawing || t !== this.activeAnnotation)
483
+ this.ctx.closePath();
484
+ else if (this.lastMouseMovePoint) {
485
+ let r = this.lastMouseMovePoint;
486
+ if (this.isHoveringStartPoint && o.points.length > 0) {
487
+ r = o.points[0];
488
+ const d = this.toScreenCoords(o.points[0].x, o.points[0].y);
489
+ this.ctx.save(), this.ctx.beginPath(), this.ctx.arc(d.x, d.y, 10, 0, Math.PI * 2), this.ctx.fillStyle = "rgba(255, 215, 0, 0.6)", this.ctx.strokeStyle = "#FFFFFF", this.ctx.lineWidth = 2, this.ctx.fill(), this.ctx.stroke(), this.ctx.restore();
490
+ }
491
+ const v = this.toScreenCoords(r.x, r.y);
492
+ this.ctx.lineTo(v.x, v.y);
493
+ }
494
+ this.ctx.stroke(), this.ctx.fillStyle = e ? "rgba(0, 229, 255, 0.2)" : "rgba(255, 64, 129, 0.2)", this.ctx.fill(), e && this.drawHandles(this.getAnnotationHandles(t));
495
+ } else if (t.type === "rotatedRect") {
496
+ const o = t.coordinates;
497
+ this.ctx.translate(this.toScreenCoords(o.x, o.y).x, this.toScreenCoords(o.x, o.y).y), this.ctx.rotate(o.angle * Math.PI / 180);
498
+ const s = o.width * this.scale, r = o.height * this.scale;
499
+ this.ctx.strokeRect(-s / 2, -r / 2, s, r), this.ctx.fillStyle = e ? "rgba(0, 229, 255, 0.2)" : "rgba(255, 64, 129, 0.2)", this.ctx.fillRect(-s / 2, -r / 2, s, r), this.ctx.rotate(-o.angle * Math.PI / 180), this.ctx.translate(-this.toScreenCoords(o.x, o.y).x, -this.toScreenCoords(o.x, o.y).y), e && this.drawHandles(this.getAnnotationHandles(t));
500
+ } else t.type === "point" && t.coordinates.points.forEach((s) => {
501
+ const r = this.toScreenCoords(s.x, s.y);
502
+ this.ctx.beginPath(), this.ctx.arc(r.x, r.y, 5, 0, Math.PI * 2), this.ctx.fillStyle = e ? "#00E5FF" : n, this.ctx.fill(), this.ctx.stroke();
503
+ });
504
+ this.ctx.restore();
505
+ }
506
+ drawHandles(t) {
507
+ this.ctx.fillStyle = "#FFFFFF", this.ctx.strokeStyle = "#000000", this.ctx.lineWidth = 1, t.forEach((e) => {
508
+ const n = this.toScreenCoords(e.x, e.y);
509
+ this.ctx.fillRect(n.x - 4, n.y - 4, 8, 8), this.ctx.strokeRect(n.x - 4, n.y - 4, 8, 8);
510
+ });
511
+ }
512
+ }
513
+ const St = {
514
+ key: 0,
515
+ class: "left-sidebar"
516
+ }, It = ["onClick", "title"], Pt = { class: "center-area" }, Dt = {
517
+ key: 0,
518
+ class: "top-bar"
519
+ }, Tt = { class: "label-selector" }, Ht = { class: "tags-row" }, Lt = ["onClick"], Mt = {
520
+ key: 0,
521
+ class: "no-labels"
522
+ }, $t = {
523
+ key: 1,
524
+ class: "batch-nav"
525
+ }, Rt = ["disabled"], Ut = ["disabled"], Ft = {
526
+ key: 1,
527
+ class: "right-sidebar"
528
+ }, Ot = { class: "label-list" }, Et = { class: "label-row" }, zt = ["onUpdate:modelValue", "onChange"], Bt = ["title"], Nt = ["onClick"], Jt = { class: "action-icon more-actions" }, Wt = ["onClick"], Vt = {
529
+ key: 2,
530
+ class: "modal-overlay"
531
+ }, Xt = { class: "modal-content" }, jt = { class: "form-group" }, Yt = { class: "form-group" }, Zt = { class: "color-input-wrapper" }, Kt = /* @__PURE__ */ q({
532
+ __name: "ImageAnnotator",
533
+ props: {
534
+ annotationTypes: { default: () => ["rectangle", "polygon", "point", "rotatedRect"] },
535
+ batchImages: { default: () => [] },
536
+ labels: { default: () => [] },
537
+ defaultActiveType: {},
538
+ theme: { default: "light" },
539
+ readOnly: { type: Boolean, default: !1 }
540
+ },
541
+ emits: ["annotationChange", "batchChange", "labelChange"],
542
+ setup(u, { expose: t, emit: e }) {
543
+ const n = u, a = e, i = k(null), o = k(null), s = k(null), r = k(null), v = k(0), d = k([]), g = k(""), f = k(!1), y = k({ name: "", color: "#FF0000" }), _ = bt(() => n.annotationTypes.filter((l) => l !== "category")), S = (l) => ({
544
+ rectangle: "Crop",
545
+ polygon: "Connection",
546
+ point: "Aim",
547
+ rotatedRect: "RefreshRight",
548
+ category: "PriceTag"
549
+ })[l] || l, O = (l) => ({
550
+ rectangle: "矩形框",
551
+ polygon: "多边形",
552
+ point: "关键点",
553
+ rotatedRect: "旋转矩形",
554
+ category: "分类标签"
555
+ })[l] || l;
556
+ At(() => {
557
+ if (i.value) {
558
+ s.value = new kt(i.value), s.value.on("annotationChange", (c) => {
559
+ if (c.action === "add" && c.changedItem) {
560
+ const A = d.value.find((w) => w.id === g.value);
561
+ A && (c.changedItem.label = A.name);
562
+ }
563
+ a("annotationChange", c);
564
+ }), R(), M();
565
+ const l = new ResizeObserver(() => {
566
+ var c;
567
+ (c = s.value) == null || c.resize();
568
+ });
569
+ o.value && l.observe(o.value);
570
+ }
571
+ });
572
+ const M = () => {
573
+ if (!s.value) return;
574
+ const l = d.value.find((A) => A.id === g.value);
575
+ l && s.value.setLabelStyle(l.color);
576
+ const c = d.value.filter((A) => A.visible).map((A) => A.name);
577
+ s.value.setVisibleLabels(c);
578
+ }, R = () => {
579
+ if (s.value && n.batchImages.length > 0) {
580
+ const l = n.batchImages[v.value];
581
+ s.value.loadImage(l.imageUrl), l.annotations && s.value.setAnnotations(l.annotations);
582
+ }
583
+ }, E = (l) => {
584
+ var c, A;
585
+ if (r.value = l, l !== "pan" && l !== "select" && d.value.length === 0) {
586
+ alert("请先创建标签!");
587
+ return;
588
+ }
589
+ l === "pan" || l === "select" ? (c = s.value) == null || c.setTool(l) : (A = s.value) == null || A.setTool(l);
590
+ }, H = () => {
591
+ var l;
592
+ (l = s.value) != null && l.activeAnnotation && s.value.deleteAnnotation(s.value.activeAnnotation.id);
593
+ }, z = () => {
594
+ var l;
595
+ return (l = s.value) == null ? void 0 : l.zoom(1);
596
+ }, Q = () => {
597
+ var l;
598
+ return (l = s.value) == null ? void 0 : l.zoom(-1);
599
+ }, st = () => {
600
+ y.value = { name: "", color: "#2196F3" }, f.value = !0;
601
+ }, tt = () => {
602
+ f.value = !1;
603
+ }, ot = () => {
604
+ if (!y.value.name.trim()) {
605
+ alert("请输入标签名称");
606
+ return;
607
+ }
608
+ const c = {
609
+ id: Date.now().toString(),
610
+ name: y.value.name,
611
+ color: y.value.color,
612
+ visible: !0
613
+ };
614
+ d.value.push(c), a("labelChange", d.value), d.value.length === 1 && B(c), tt();
615
+ }, B = (l) => {
616
+ var c;
617
+ g.value = l.id, (c = s.value) == null || c.setLabelStyle(l.color);
618
+ }, at = (l, c) => {
619
+ if (!l.startsWith("#")) return l;
620
+ let A = 0, w = 0, T = 0;
621
+ return l.length === 4 ? (A = parseInt(l[1] + l[1], 16), w = parseInt(l[2] + l[2], 16), T = parseInt(l[3] + l[3], 16)) : l.length === 7 && (A = parseInt(l.slice(1, 3), 16), w = parseInt(l.slice(3, 5), 16), T = parseInt(l.slice(5, 7), 16)), `rgba(${A}, ${w}, ${T}, ${c})`;
622
+ }, lt = (l) => {
623
+ var c;
624
+ if (l.id === g.value && ((c = s.value) == null || c.setLabelStyle(l.color)), s.value) {
625
+ const A = s.value.getAnnotations();
626
+ let w = !1;
627
+ A.forEach((T) => {
628
+ T.label === l.name && (T.style || (T.style = {}), T.style.strokeColor = l.color, T.style.fillColor = at(l.color, 0.2), w = !0);
629
+ }), w && s.value.render();
630
+ }
631
+ a("labelChange", d.value);
632
+ }, rt = (l) => {
633
+ l.visible = !l.visible, M();
634
+ }, ct = (l) => {
635
+ const c = d.value.findIndex((A) => A.id === l);
636
+ c > -1 && (d.value.splice(c, 1), a("labelChange", d.value), g.value === l && (g.value = d.value.length > 0 ? d.value[0].id : "", g.value && B(d.value[0])), M());
637
+ };
638
+ it(() => n.labels, (l) => {
639
+ const c = JSON.parse(JSON.stringify(l || []));
640
+ if (d.value = c, d.value.length > 0)
641
+ if (!g.value || !d.value.find((A) => A.id === g.value))
642
+ B(d.value[0]);
643
+ else {
644
+ const A = d.value.find((w) => w.id === g.value);
645
+ A && B(A);
646
+ }
647
+ else
648
+ g.value = "";
649
+ M();
650
+ }, { immediate: !0, deep: !0 });
651
+ const ht = () => {
652
+ v.value > 0 && (W(), v.value--, R(), V());
653
+ }, dt = () => {
654
+ v.value < n.batchImages.length - 1 && (W(), v.value++, R(), V());
655
+ }, W = () => {
656
+ if (s.value) {
657
+ const l = s.value.getAnnotations();
658
+ n.batchImages[v.value].annotations = l;
659
+ }
660
+ }, V = () => {
661
+ const l = n.batchImages[v.value];
662
+ a("batchChange", {
663
+ currentIndex: v.value,
664
+ total: n.batchImages.length,
665
+ currentImageUrl: l.imageUrl,
666
+ currentAnnotations: l.annotations || []
667
+ });
668
+ };
669
+ return t({
670
+ jumpTo: (l) => {
671
+ l >= 0 && l < n.batchImages.length && (W(), v.value = l, R(), V());
672
+ },
673
+ getAllAnnotations: () => n.batchImages,
674
+ getCurrentAnnotation: () => {
675
+ var l;
676
+ return {
677
+ imageUrl: n.batchImages[v.value].imageUrl,
678
+ annotations: ((l = s.value) == null ? void 0 : l.getAnnotations()) || []
679
+ };
680
+ }
681
+ }), (l, c) => {
682
+ const A = P("Rank"), w = P("el-icon"), T = P("Pointer"), gt = P("ZoomIn"), ut = P("ZoomOut"), et = P("Delete"), vt = P("Back"), ft = P("Right"), yt = P("View"), mt = P("Hide");
683
+ return m(), x("div", {
684
+ class: N(["annotation-container", u.theme])
685
+ }, [
686
+ u.readOnly ? L("", !0) : (m(), x("div", St, [
687
+ h("div", {
688
+ class: N(["tool-btn", { active: r.value === "pan" }]),
689
+ onClick: c[0] || (c[0] = (p) => E("pan")),
690
+ title: "拖动"
691
+ }, [
692
+ b(w, null, {
693
+ default: I(() => [
694
+ b(A)
695
+ ]),
696
+ _: 1
697
+ })
698
+ ], 2),
699
+ h("div", {
700
+ class: N(["tool-btn", { active: r.value === "select" }]),
701
+ onClick: c[1] || (c[1] = (p) => E("select")),
702
+ title: "选择"
703
+ }, [
704
+ b(w, null, {
705
+ default: I(() => [
706
+ b(T)
707
+ ]),
708
+ _: 1
709
+ })
710
+ ], 2),
711
+ c[4] || (c[4] = h("div", { class: "divider" }, null, -1)),
712
+ (m(!0), x($, null, F(_.value, (p) => (m(), x("div", {
713
+ key: p,
714
+ class: N(["tool-btn", { active: r.value === p }]),
715
+ onClick: (U) => E(p),
716
+ title: O(p)
717
+ }, [
718
+ b(w, null, {
719
+ default: I(() => [
720
+ (m(), X(Ct(S(p))))
721
+ ]),
722
+ _: 2
723
+ }, 1024)
724
+ ], 10, It))), 128)),
725
+ c[5] || (c[5] = h("div", { class: "divider" }, null, -1)),
726
+ h("div", {
727
+ class: "tool-btn",
728
+ onClick: z,
729
+ title: "放大"
730
+ }, [
731
+ b(w, null, {
732
+ default: I(() => [
733
+ b(gt)
734
+ ]),
735
+ _: 1
736
+ })
737
+ ]),
738
+ h("div", {
739
+ class: "tool-btn",
740
+ onClick: Q,
741
+ title: "缩小"
742
+ }, [
743
+ b(w, null, {
744
+ default: I(() => [
745
+ b(ut)
746
+ ]),
747
+ _: 1
748
+ })
749
+ ]),
750
+ c[6] || (c[6] = h("div", { class: "divider" }, null, -1)),
751
+ h("div", {
752
+ class: "tool-btn",
753
+ onClick: H,
754
+ title: "删除选中"
755
+ }, [
756
+ b(w, null, {
757
+ default: I(() => [
758
+ b(et)
759
+ ]),
760
+ _: 1
761
+ })
762
+ ])
763
+ ])),
764
+ h("div", Pt, [
765
+ u.readOnly ? L("", !0) : (m(), x("div", Dt, [
766
+ h("div", Tt, [
767
+ c[7] || (c[7] = h("span", { class: "label-text" }, "当前标签:", -1)),
768
+ h("div", Ht, [
769
+ (m(!0), x($, null, F(d.value, (p) => (m(), x("div", {
770
+ key: p.id,
771
+ class: N(["tag-chip", { active: g.value === p.id }]),
772
+ style: K({ backgroundColor: p.color, borderColor: p.color }),
773
+ onClick: (U) => B(p)
774
+ }, D(p.name), 15, Lt))), 128)),
775
+ d.value.length === 0 ? (m(), x("div", Mt, "请在右侧创建标签")) : L("", !0)
776
+ ])
777
+ ])
778
+ ])),
779
+ h("div", {
780
+ class: "canvas-wrapper",
781
+ ref_key: "canvasWrapper",
782
+ ref: o
783
+ }, [
784
+ h("canvas", {
785
+ ref_key: "canvasRef",
786
+ ref: i
787
+ }, null, 512)
788
+ ], 512),
789
+ u.batchImages && u.batchImages.length > 0 ? (m(), x("div", $t, [
790
+ h("button", {
791
+ onClick: ht,
792
+ disabled: v.value <= 0
793
+ }, [
794
+ b(w, null, {
795
+ default: I(() => [
796
+ b(vt)
797
+ ]),
798
+ _: 1
799
+ }),
800
+ c[8] || (c[8] = J(" 上一张 ", -1))
801
+ ], 8, Rt),
802
+ h("span", null, D(v.value + 1) + " / " + D(u.batchImages.length), 1),
803
+ h("button", {
804
+ onClick: dt,
805
+ disabled: v.value >= u.batchImages.length - 1
806
+ }, [
807
+ c[9] || (c[9] = J(" 下一张 ", -1)),
808
+ b(w, null, {
809
+ default: I(() => [
810
+ b(ft)
811
+ ]),
812
+ _: 1
813
+ })
814
+ ], 8, Ut)
815
+ ])) : L("", !0)
816
+ ]),
817
+ u.readOnly ? L("", !0) : (m(), x("div", Ft, [
818
+ h("div", { class: "sidebar-header" }, [
819
+ c[10] || (c[10] = h("h3", null, "标签管理", -1)),
820
+ h("button", {
821
+ class: "add-btn",
822
+ onClick: st
823
+ }, "添加标签")
824
+ ]),
825
+ h("div", Ot, [
826
+ (m(!0), x($, null, F(d.value, (p) => (m(), x("div", {
827
+ key: p.id,
828
+ class: "label-item"
829
+ }, [
830
+ h("div", Et, [
831
+ h("label", {
832
+ class: "color-wrapper",
833
+ style: K({ backgroundColor: p.color })
834
+ }, [
835
+ j(h("input", {
836
+ type: "color",
837
+ "onUpdate:modelValue": (U) => p.color = U,
838
+ onChange: (U) => lt(p),
839
+ style: { visibility: "hidden", width: "0", height: "0" }
840
+ }, null, 40, zt), [
841
+ [Y, p.color]
842
+ ])
843
+ ], 4),
844
+ h("span", {
845
+ class: "label-name",
846
+ title: p.name
847
+ }, D(p.name), 9, Bt),
848
+ h("span", {
849
+ class: "action-icon eye",
850
+ onClick: (U) => rt(p)
851
+ }, [
852
+ p.visible ? (m(), X(w, { key: 0 }, {
853
+ default: I(() => [
854
+ b(yt)
855
+ ]),
856
+ _: 1
857
+ })) : (m(), X(w, { key: 1 }, {
858
+ default: I(() => [
859
+ b(mt)
860
+ ]),
861
+ _: 1
862
+ }))
863
+ ], 8, Nt),
864
+ h("div", Jt, [
865
+ c[11] || (c[11] = h("span", { class: "dots" }, "•••", -1)),
866
+ h("span", {
867
+ class: "delete-btn",
868
+ onClick: (U) => ct(p.id),
869
+ title: "删除"
870
+ }, [
871
+ b(w, null, {
872
+ default: I(() => [
873
+ b(et)
874
+ ]),
875
+ _: 1
876
+ })
877
+ ], 8, Wt)
878
+ ])
879
+ ])
880
+ ]))), 128))
881
+ ])
882
+ ])),
883
+ f.value ? (m(), x("div", Vt, [
884
+ h("div", Xt, [
885
+ c[14] || (c[14] = h("h3", null, "新增标签", -1)),
886
+ h("div", jt, [
887
+ c[12] || (c[12] = h("label", null, "名称", -1)),
888
+ j(h("input", {
889
+ "onUpdate:modelValue": c[2] || (c[2] = (p) => y.value.name = p),
890
+ placeholder: "请输入标签名称",
891
+ class: "modal-input"
892
+ }, null, 512), [
893
+ [Y, y.value.name]
894
+ ])
895
+ ]),
896
+ h("div", Yt, [
897
+ c[13] || (c[13] = h("label", null, "颜色", -1)),
898
+ h("div", Zt, [
899
+ j(h("input", {
900
+ type: "color",
901
+ "onUpdate:modelValue": c[3] || (c[3] = (p) => y.value.color = p),
902
+ class: "modal-color-picker"
903
+ }, null, 512), [
904
+ [Y, y.value.color]
905
+ ]),
906
+ h("span", null, D(y.value.color), 1)
907
+ ])
908
+ ]),
909
+ h("div", { class: "modal-actions" }, [
910
+ h("button", {
911
+ onClick: tt,
912
+ class: "cancel-btn"
913
+ }, "取消"),
914
+ h("button", {
915
+ onClick: ot,
916
+ class: "confirm-btn"
917
+ }, "确认")
918
+ ])
919
+ ])
920
+ ])) : L("", !0)
921
+ ], 2);
922
+ };
923
+ }
924
+ }), G = (u, t) => {
925
+ const e = u.__vccOpts || u;
926
+ for (const [n, a] of t)
927
+ e[n] = a;
928
+ return e;
929
+ }, qt = /* @__PURE__ */ G(Kt, [["__scopeId", "data-v-d3dc5a63"]]), Gt = {
930
+ class: "thumbnail-wrapper",
931
+ ref: "wrapper"
932
+ }, Qt = ["src", "alt"], te = ["viewBox"], ee = ["x", "y", "width", "height", "stroke"], ne = ["points", "stroke"], ie = ["x", "y", "fill"], se = {
933
+ key: 1,
934
+ class: "loading-placeholder"
935
+ }, oe = /* @__PURE__ */ q({
936
+ __name: "AnnotationThumbnail",
937
+ props: {
938
+ src: {},
939
+ annotations: {},
940
+ alt: {},
941
+ labels: {}
942
+ },
943
+ setup(u) {
944
+ const t = u, e = k(null), n = k(!1), a = k(0), i = k(0), o = () => {
945
+ e.value && (a.value = e.value.naturalWidth, i.value = e.value.naturalHeight, n.value = !0);
946
+ }, s = (g) => {
947
+ var f;
948
+ if ((f = g.style) != null && f.strokeColor) return g.style.strokeColor;
949
+ if (t.labels) {
950
+ const y = t.labels.find((_) => _.name === g.label);
951
+ if (y) return y.color;
952
+ }
953
+ return "#FF0000";
954
+ }, r = (g) => {
955
+ const f = g.coordinates, y = Math.min(f.x1, f.x2), _ = Math.min(f.y1, f.y2), S = Math.abs(f.x1 - f.x2), O = Math.abs(f.y1 - f.y2);
956
+ return { x: y, y: _, width: S, height: O };
957
+ }, v = (g) => g.coordinates.points.map((y) => `${y.x},${y.y}`).join(" "), d = (g) => {
958
+ if (g.type === "rectangle") {
959
+ const f = r(g);
960
+ return { x: f.x, y: f.y - 5 };
961
+ } else if (g.type === "polygon") {
962
+ const f = g.coordinates.points;
963
+ if (f.length > 0) return { x: f[0].x, y: f[0].y - 5 };
964
+ }
965
+ return { x: 0, y: 0 };
966
+ };
967
+ return (g, f) => (m(), x("div", Gt, [
968
+ h("img", {
969
+ ref_key: "img",
970
+ ref: e,
971
+ src: u.src,
972
+ class: "thumbnail-image",
973
+ onLoad: o,
974
+ alt: u.alt
975
+ }, null, 40, Qt),
976
+ n.value ? (m(), x("svg", {
977
+ key: 0,
978
+ class: "annotation-overlay",
979
+ viewBox: `0 0 ${a.value} ${i.value}`,
980
+ preserveAspectRatio: "none"
981
+ }, [
982
+ (m(!0), x($, null, F(u.annotations, (y) => (m(), x($, {
983
+ key: y.id
984
+ }, [
985
+ y.type === "rectangle" ? (m(), x("rect", {
986
+ key: 0,
987
+ x: r(y).x,
988
+ y: r(y).y,
989
+ width: r(y).width,
990
+ height: r(y).height,
991
+ stroke: s(y),
992
+ "stroke-width": "2",
993
+ fill: "transparent"
994
+ }, null, 8, ee)) : L("", !0),
995
+ y.type === "polygon" ? (m(), x("polygon", {
996
+ key: 1,
997
+ points: v(y),
998
+ stroke: s(y),
999
+ "stroke-width": "2",
1000
+ fill: "transparent"
1001
+ }, null, 8, ne)) : L("", !0),
1002
+ y.label ? (m(), x("text", {
1003
+ key: 2,
1004
+ x: d(y).x,
1005
+ y: d(y).y,
1006
+ fill: s(y),
1007
+ "font-size": "14",
1008
+ "font-weight": "bold",
1009
+ class: "anno-label"
1010
+ }, D(y.label), 9, ie)) : L("", !0)
1011
+ ], 64))), 128))
1012
+ ], 8, te)) : (m(), x("div", se, "Loading..."))
1013
+ ], 512));
1014
+ }
1015
+ }), ae = /* @__PURE__ */ G(oe, [["__scopeId", "data-v-78bcbe0c"]]), le = { class: "batch-annotator" }, re = {
1016
+ key: 0,
1017
+ class: "gallery-view"
1018
+ }, ce = { class: "gallery-header" }, he = { class: "label-summary" }, de = { class: "gallery-grid" }, ge = ["onClick"], ue = { class: "thumbnail-wrapper" }, ve = { class: "img-meta" }, fe = { class: "img-index" }, ye = { class: "anno-count" }, me = { class: "bottom-bar" }, pe = {
1019
+ key: 1,
1020
+ class: "editor-view"
1021
+ }, xe = { class: "editor-header" }, be = { class: "header-left" }, Ae = { class: "editor-title" }, Ce = { class: "editor-content" }, we = /* @__PURE__ */ q({
1022
+ __name: "BatchAnnotator",
1023
+ props: {
1024
+ images: {},
1025
+ labels: {}
1026
+ },
1027
+ emits: ["export", "update:images"],
1028
+ setup(u, { emit: t }) {
1029
+ const e = u, n = t, a = k("gallery"), i = k([]), o = k(0), s = k(null);
1030
+ it(() => e.images, (_) => {
1031
+ i.value = JSON.parse(JSON.stringify(_));
1032
+ }, { immediate: !0, deep: !0 });
1033
+ const r = (_) => {
1034
+ o.value = _, a.value = "editor", wt(() => {
1035
+ s.value && s.value.jumpTo && s.value.jumpTo(_);
1036
+ });
1037
+ }, v = () => {
1038
+ if (s.value && s.value.getCurrentAnnotation) {
1039
+ const _ = s.value.getCurrentAnnotation();
1040
+ i.value[o.value] && (i.value[o.value].annotations = _.annotations);
1041
+ }
1042
+ a.value = "gallery";
1043
+ }, d = () => {
1044
+ n("export", i.value);
1045
+ }, g = (_) => {
1046
+ o.value = _.currentIndex, i.value[_.currentIndex] && (i.value[_.currentIndex].annotations = _.currentAnnotations);
1047
+ }, f = (_) => {
1048
+ if (s.value && s.value.getCurrentAnnotation) {
1049
+ const S = s.value.getCurrentAnnotation();
1050
+ i.value[o.value] && (i.value[o.value].annotations = S.annotations, n("update:images", i.value));
1051
+ }
1052
+ }, y = (_) => {
1053
+ };
1054
+ return (_, S) => {
1055
+ const O = P("Edit"), M = P("el-icon"), R = P("Download"), E = P("Back");
1056
+ return m(), x("div", le, [
1057
+ a.value === "gallery" ? (m(), x("div", re, [
1058
+ h("div", ce, [
1059
+ h("h3", null, "批量查看与标注 (" + D(i.value.length) + " 张)", 1),
1060
+ h("div", he, [
1061
+ (m(!0), x($, null, F(u.labels, (H) => (m(), x("span", {
1062
+ key: H.id,
1063
+ class: "label-badge",
1064
+ style: K({ backgroundColor: H.color })
1065
+ }, D(H.name), 5))), 128))
1066
+ ])
1067
+ ]),
1068
+ h("div", de, [
1069
+ (m(!0), x($, null, F(i.value, (H, z) => (m(), x("div", {
1070
+ key: z,
1071
+ class: "gallery-item",
1072
+ onClick: (Q) => r(z)
1073
+ }, [
1074
+ h("div", ue, [
1075
+ b(ae, {
1076
+ src: H.imageUrl,
1077
+ annotations: H.annotations || [],
1078
+ labels: u.labels
1079
+ }, null, 8, ["src", "annotations", "labels"])
1080
+ ]),
1081
+ h("div", ve, [
1082
+ h("span", fe, "#" + D(z + 1), 1),
1083
+ h("span", ye, D((H.annotations || []).length) + " 标注", 1)
1084
+ ])
1085
+ ], 8, ge))), 128))
1086
+ ]),
1087
+ h("div", me, [
1088
+ h("button", {
1089
+ class: "action-btn primary",
1090
+ onClick: S[0] || (S[0] = (H) => r(0))
1091
+ }, [
1092
+ b(M, null, {
1093
+ default: I(() => [
1094
+ b(O)
1095
+ ]),
1096
+ _: 1
1097
+ }),
1098
+ S[1] || (S[1] = J(" 手动标注 ", -1))
1099
+ ]),
1100
+ h("button", {
1101
+ class: "action-btn success",
1102
+ onClick: d
1103
+ }, [
1104
+ b(M, null, {
1105
+ default: I(() => [
1106
+ b(R)
1107
+ ]),
1108
+ _: 1
1109
+ }),
1110
+ S[2] || (S[2] = J(" 导出 ", -1))
1111
+ ])
1112
+ ])
1113
+ ])) : (m(), x("div", pe, [
1114
+ h("div", xe, [
1115
+ h("div", be, [
1116
+ h("button", {
1117
+ class: "back-btn",
1118
+ onClick: v
1119
+ }, [
1120
+ b(M, null, {
1121
+ default: I(() => [
1122
+ b(E)
1123
+ ]),
1124
+ _: 1
1125
+ }),
1126
+ S[3] || (S[3] = J(" 返回列表 ", -1))
1127
+ ]),
1128
+ h("span", Ae, "正在标注: " + D(o.value + 1) + " / " + D(i.value.length), 1)
1129
+ ])
1130
+ ]),
1131
+ h("div", Ce, [
1132
+ b(qt, {
1133
+ ref_key: "annotatorRef",
1134
+ ref: s,
1135
+ batchImages: i.value,
1136
+ labels: u.labels,
1137
+ annotationTypes: ["rectangle", "polygon", "point", "rotatedRect"],
1138
+ onBatchChange: g,
1139
+ onAnnotationChange: f,
1140
+ onLabelChange: y
1141
+ }, null, 8, ["batchImages", "labels"])
1142
+ ])
1143
+ ]))
1144
+ ]);
1145
+ };
1146
+ }
1147
+ }), Ie = /* @__PURE__ */ G(we, [["__scopeId", "data-v-4cf50076"]]);
1148
+ export {
1149
+ Ie as BatchAnnotator,
1150
+ qt as ImageAnnotator,
1151
+ Ie as default
1152
+ };