med-viewer-sdk 0.1.11 → 0.1.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "med-viewer-sdk",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "main": "dist/med-viewer-sdk.umd.js",
5
5
  "module": "dist/med-viewer-sdk.mjs",
6
6
  "types": "dist/med-viewer-sdk.d.ts",
@@ -1,62 +1,65 @@
1
- import OpenSeadragon from "openseadragon";
2
- import type OpenSeadragonType from "openseadragon";
3
-
4
- import {
5
- ShapeLabelsFormatter,
6
- injectShapeLabelStyles,
7
- } from "@/plugins/ShapeLabelsFormatter";
8
-
9
- import { AnnoAnnotator } from "./AnnoAnnotator";
10
- import { MedToolbar, type ToolbarOptions } from "./Toolbar";
11
- import {
12
- ColorAdjustPlugin,
13
- type ColorAdjustOptions,
14
- } from "./ColorAdjustPlugin";
15
- import { SelectionPlugin, type SelectionOptions } from "./SelectionPlugin";
16
- import { ScalebarPlugin, type ScalebarOptions } from "./Scalebar";
17
- import {
18
- MagnificationPlugin,
19
- type MagnificationOptions,
20
- } from "./Magnification";
21
-
22
- import { Locale, setLocale } from "../i18n/i18n";
1
+ import type OpenSeadragonType from 'openseadragon'
2
+ import OpenSeadragon from 'openseadragon'
3
+
4
+ import { ShapeLabelsFormatter, injectShapeLabelStyles } from '@/plugins/ShapeLabelsFormatter'
5
+
6
+ import { AnnoAnnotator } from './AnnoAnnotator'
7
+ import { ColorAdjustPlugin, type ColorAdjustOptions } from './ColorAdjustPlugin'
8
+ import { MagnificationPlugin, type MagnificationOptions } from './Magnification'
9
+ import { ScalebarPlugin, type ScalebarOptions } from './Scalebar'
10
+ import { SelectionPlugin, type SelectionOptions } from './SelectionPlugin'
11
+ import { MedToolbar, type ToolbarOptions } from './Toolbar'
12
+
13
+ import { Locale, setLocale } from '../i18n/i18n'
23
14
  /**
24
15
  * 引擎配置接口
25
16
  */
26
17
  export interface MedEngineOptions {
27
- osdOptions: OpenSeadragonType.Options;
28
- locale?: Locale; // 国际化语言设置
18
+ osdOptions: OpenSeadragonType.Options
19
+ locale?: Locale // 国际化语言设置
29
20
  plugins?: {
30
- annotorious?: boolean | any; // 是否启用 Annotorious 插件
31
- toolbar?: boolean | ToolbarOptions; // 是否启用工具栏插件
32
- colorAdjust?: boolean | ColorAdjustOptions; // 是否启用色彩调节插件
33
- selection?: boolean | SelectionOptions; // 是否启用选择插件
34
- scalebar?: boolean | ScalebarOptions; // 是否启用比例尺插件
35
- magnification?: boolean | MagnificationOptions; // 是否启用放大镜插件
36
- };
21
+ annotorious?: boolean | any // 是否启用 Annotorious 插件
22
+ toolbar?: boolean | ToolbarOptions // 是否启用工具栏插件
23
+ colorAdjust?: boolean | ColorAdjustOptions // 是否启用色彩调节插件
24
+ selection?: boolean | SelectionOptions // 是否启用选择插件
25
+ scalebar?: boolean | ScalebarOptions // 是否启用比例尺插件
26
+ magnification?: boolean | MagnificationOptions // 是否启用放大镜插件
27
+ }
37
28
  }
38
29
 
39
30
  export interface AiBoxRectLabel {
40
- label: string;
41
- value: string;
31
+ label: string
32
+ value: string
42
33
  style?: {
43
- color: string;
44
- fontSize: number;
45
- [key: string]: any; // 允许任意扩展
46
- };
34
+ color: string
35
+ fontSize: number
36
+ [key: string]: any // 允许任意扩展
37
+ }
47
38
  }
48
39
 
49
40
  export interface AiBoxRect {
50
- x: number;
51
- y: number;
52
- width: number;
53
- height: number;
41
+ x: number
42
+ y: number
43
+ width: number
44
+ height: number
45
+ style: {
46
+ border: string
47
+ fontSize: number
48
+ [key: string]: any // 允许任意扩展
49
+ }
50
+ labels: Array<AiBoxRectLabel>
51
+ }
52
+
53
+ export interface SelectionBox {
54
+ x: number
55
+ y: number
56
+ width: number
57
+ height: number
54
58
  style: {
55
- border: string;
56
- fontSize: number;
57
- [key: string]: any; // 允许任意扩展
58
- };
59
- labels: Array<AiBoxRectLabel>;
59
+ border: string
60
+ fontSize: number
61
+ [key: string]: any // 允许任意扩展
62
+ }
60
63
  }
61
64
 
62
65
  /**
@@ -64,32 +67,31 @@ export interface AiBoxRect {
64
67
  * 职责:初始化 OpenSeadragon,管理插件生命周期
65
68
  */
66
69
  export class MedViewerEngine {
67
- public viewer: OpenSeadragon.Viewer;
70
+ public viewer: OpenSeadragon.Viewer
68
71
  // public konva: KonvaAnnotator | null = null;
69
- public anno: AnnoAnnotator | null = null;
70
- public toolbar: MedToolbar | null = null;
71
- public colorAdjust: ColorAdjustPlugin | null = null;
72
- public selection: SelectionPlugin | null = null;
73
- public scalebar: ScalebarPlugin | null = null;
74
- public magnification: MagnificationPlugin | null = null;
75
- private options: MedEngineOptions;
76
- private events: Map<string, Set<Function>> = new Map();
77
-
72
+ public anno: AnnoAnnotator | null = null
73
+ public toolbar: MedToolbar | null = null
74
+ public colorAdjust: ColorAdjustPlugin | null = null
75
+ public selection: SelectionPlugin | null = null
76
+ public scalebar: ScalebarPlugin | null = null
77
+ public magnification: MagnificationPlugin | null = null
78
+ private options: MedEngineOptions
79
+ private events: Map<string, Set<Function>> = new Map()
78
80
 
79
81
  constructor(options: MedEngineOptions) {
80
82
  if (!options.osdOptions) {
81
- throw new Error("osdOptions is required");
83
+ throw new Error('osdOptions is required')
82
84
  }
83
85
 
84
86
  if (options.locale) {
85
- setLocale(options.locale);
87
+ setLocale(options.locale)
86
88
  }
87
89
 
88
90
  // 合并默认配置
89
91
  const osdOptions: OpenSeadragonType.Options = {
90
- id: "osd-container",
91
- crossOriginPolicy: "Anonymous",
92
- prefixUrl: options.osdOptions?.prefixUrl || "",
92
+ id: 'osd-container',
93
+ crossOriginPolicy: 'Anonymous',
94
+ prefixUrl: options.osdOptions?.prefixUrl || '',
93
95
  opacity: 1,
94
96
  preload: false,
95
97
  immediateRender: false,
@@ -119,12 +121,12 @@ export class MedViewerEngine {
119
121
  // --- Navigator 小图 ---
120
122
  showNavigator: true,
121
123
  navigatorSizeRatio: 0.15,
122
- navigatorPosition: "TOP_RIGHT",
124
+ navigatorPosition: 'TOP_RIGHT',
123
125
  navigatorAutoFade: false,
124
126
  navigatorOpacity: 0.8,
125
- navigatorBackground: "#fff",
126
- navigatorBorderColor: "#aaa",
127
- navigatorDisplayRegionColor: "#f21616",
127
+ navigatorBackground: '#fff',
128
+ navigatorBorderColor: '#aaa',
129
+ navigatorDisplayRegionColor: '#f21616',
128
130
 
129
131
  // --- 控件 ---
130
132
  showNavigationControl: false, // 你自己做 UI 更专业
@@ -138,22 +140,22 @@ export class MedViewerEngine {
138
140
  gestureSettingsMouse: {
139
141
  dragToPan: true,
140
142
  clickToZoom: false,
141
- dblClickToZoom: false,
143
+ dblClickToZoom: false
142
144
  },
143
145
  gestureSettingsTouch: {
144
146
  flickEnabled: false,
145
- pinchToZoom: true,
147
+ pinchToZoom: true
146
148
  },
147
149
 
148
- ...options.osdOptions,
149
- };
150
- options.osdOptions = osdOptions;
150
+ ...options.osdOptions
151
+ }
152
+ options.osdOptions = osdOptions
151
153
 
152
- this.options = options;
154
+ this.options = options
153
155
 
154
156
  // 1. 初始化 OpenSeadragon
155
157
 
156
- this.viewer = OpenSeadragon(osdOptions);
158
+ this.viewer = OpenSeadragon(osdOptions)
157
159
 
158
160
  // this.viewer.addOnceHandler("open", () => {
159
161
  // this.viewer.viewport && this.viewer.viewport.resize();
@@ -161,20 +163,11 @@ export class MedViewerEngine {
161
163
  // // const isWebGL = this.viewer.drawer instanceof OpenSeadragon.WebGLDrawer;
162
164
  // });
163
165
 
164
- this.viewer.addOnceHandler("open", () => {
166
+ this.viewer.addOnceHandler('open', () => {
165
167
  // this.viewer.viewport && this.viewer.viewport.resize();
166
- this.viewer.viewport.goHome();
167
- this.emit("ready");
168
- // 3. 加载AI标注
169
- // if (this.options?.aiMarks?.aiBoxes) {
170
- // this.loadAIMarks(
171
- // this.options?.aiMarks?.aiBoxes || [],
172
- // this.options?.aiMarks?.showLabel || false,
173
- // );
174
- // }
175
- console.log(
176
- "[MedViewerEngine] First tile loaded, refreshing viewer layout.",
177
- );
168
+ this.viewer.viewport.goHome()
169
+ this.emit('ready')
170
+ console.log('[MedViewerEngine] First tile loaded, refreshing viewer layout.')
178
171
  // console.log(this.viewer.viewport.getBoundsNoRotate());
179
172
 
180
173
  // setTimeout(() => {
@@ -183,13 +176,10 @@ export class MedViewerEngine {
183
176
  // console.log("[MedViewerEngine] Viewer layout refreshed after tile load.");
184
177
  // })
185
178
  // this.viewer.viewport.resize();
186
- });
179
+ })
187
180
 
188
181
  // 2. 初始化插件
189
- this.initPlugins();
190
-
191
-
192
-
182
+ this.initPlugins()
193
183
 
194
184
  // 优化:强制刷新 OSD 布局,确保显示完整
195
185
  }
@@ -199,81 +189,66 @@ export class MedViewerEngine {
199
189
  * 解决异步 DOM 挂载问题,防止 Annotorious 初始化时找不到 Canvas
200
190
  */
201
191
  private initPlugins(): void {
202
- const { plugins } = this.options;
203
- if (!plugins) return;
192
+ const { plugins } = this.options
193
+ if (!plugins) return
204
194
  // --- Scalebar 插件初始化 ---
205
195
  if (plugins.scalebar) {
206
- const scalebarConfig =
207
- typeof plugins.scalebar === "object" ? plugins.scalebar : {};
208
- this.scalebar = new ScalebarPlugin(this.viewer, scalebarConfig);
209
- console.log("[MedEngine] Scalebar plugin initialized.");
196
+ const scalebarConfig = typeof plugins.scalebar === 'object' ? plugins.scalebar : {}
197
+ this.scalebar = new ScalebarPlugin(this.viewer, scalebarConfig)
198
+ console.log('[MedEngine] Scalebar plugin initialized.')
210
199
  }
211
200
 
212
201
  // --- Annotorious 插件初始化 ---
213
202
  // 关键:解决 "Cannot set properties of undefined (setting 'display')"
214
203
  // Annotorious v3 必须等待 OSD 的 Canvas 元素创建并挂载后才能初始化
215
204
  if (plugins.annotorious) {
216
- const annoConfig =
217
- typeof plugins.annotorious === "object" ? plugins.annotorious : {};
205
+ const annoConfig = typeof plugins.annotorious === 'object' ? plugins.annotorious : {}
218
206
  if (!annoConfig.locale) {
219
- annoConfig.locale = this.options.locale || "zh-CN";
207
+ annoConfig.locale = this.options.locale || 'zh-CN'
220
208
  }
221
209
 
222
210
  // 如果formatter为对象,则渲染默认格式化,需要添加样式
223
- if (typeof annoConfig.options?.formatter === "object") {
224
- const pixelsPerMeter =
225
- annoConfig.options?.formatter?.pixelsPerMeter || 1;
226
- const showMeasure = annoConfig.options?.formatter?.showMeasure || false;
227
- injectShapeLabelStyles();
228
- annoConfig.formatter = ShapeLabelsFormatter(
229
- pixelsPerMeter,
230
- showMeasure,
231
- );
211
+ if (typeof annoConfig.options?.formatter === 'object') {
212
+ const pixelsPerMeter = annoConfig.options?.formatter?.pixelsPerMeter || 1
213
+ const showMeasure = annoConfig.options?.formatter?.showMeasure || false
214
+ injectShapeLabelStyles()
215
+ annoConfig.formatter = ShapeLabelsFormatter(pixelsPerMeter, showMeasure)
232
216
  }
233
217
  if (this.viewer.isOpen()) {
234
- console.log(
235
- "[MedEngine] Viewer is already open, mounting Annotorious.",
236
- annoConfig,
237
- );
218
+ console.log('[MedEngine] Viewer is already open, mounting Annotorious.', annoConfig)
238
219
  // 如果已经打开(极少见,除非 TileSource 是同步加载的本地对象)
239
- this.mountAnnotorious(annoConfig);
220
+ this.mountAnnotorious(annoConfig)
240
221
  } else {
241
222
  // 推荐:监听 open 事件,确保 OSD 内部 DOM 结构完全就绪
242
- this.viewer.addOnceHandler("open", () => {
243
- console.log(
244
- "[MedEngine] Viewer is already open, mounting Annotorious.",
245
- annoConfig,
246
- );
247
- this.mountAnnotorious(annoConfig);
248
- });
223
+ this.viewer.addOnceHandler('open', () => {
224
+ console.log('[MedEngine] Viewer is already open, mounting Annotorious.', annoConfig)
225
+ this.mountAnnotorious(annoConfig)
226
+ })
249
227
  }
250
228
  }
251
229
 
252
230
  // --- Toolbar 插件初始化 ---
253
231
  if (plugins.toolbar) {
254
- const toolbarConfig =
255
- typeof plugins.toolbar === "object" ? plugins.toolbar : {};
256
- this.toolbar = new MedToolbar(this, toolbarConfig);
257
- console.log("[MedEngine] Toolbar plugin initialized.");
232
+ const toolbarConfig = typeof plugins.toolbar === 'object' ? plugins.toolbar : {}
233
+ this.toolbar = new MedToolbar(this, toolbarConfig)
234
+ console.log('[MedEngine] Toolbar plugin initialized.')
258
235
  }
259
236
 
260
237
  // --- Selection 插件初始化 ---
261
238
  if (plugins.selection) {
262
- const selectionConfig =
263
- typeof plugins.selection === "object" ? plugins.selection : {};
264
- this.selection = new SelectionPlugin(this.viewer, selectionConfig);
265
- console.log("[MedEngine] Selection plugin initialized.");
239
+ const selectionConfig = typeof plugins.selection === 'object' ? plugins.selection : {}
240
+ this.selection = new SelectionPlugin(this.viewer, selectionConfig)
241
+ console.log('[MedEngine] Selection plugin initialized.')
266
242
  }
267
243
 
268
244
  // --- ColorAdjust 插件初始化 ---
269
245
  if (plugins.colorAdjust) {
270
246
  // 1. 显式提取配置对象,如果是 boolean 则给空对象
271
- const config: any =
272
- typeof plugins.colorAdjust === "object" ? plugins.colorAdjust : {};
247
+ const config: any = typeof plugins.colorAdjust === 'object' ? plugins.colorAdjust : {}
273
248
 
274
249
  // 2. 传入配置
275
- this.colorAdjust = new ColorAdjustPlugin(this.viewer, config);
276
- console.log("[MedEngine] ColorAdjust plugin initialized.");
250
+ this.colorAdjust = new ColorAdjustPlugin(this.viewer, config)
251
+ console.log('[MedEngine] ColorAdjust plugin initialized.')
277
252
  // if(this.viewer.isOpen()){
278
253
 
279
254
  // }
@@ -287,10 +262,9 @@ export class MedViewerEngine {
287
262
 
288
263
  // --- Magnification 插件初始化 ---
289
264
  if (plugins.magnification) {
290
- const config: any =
291
- typeof plugins.magnification === "object" ? plugins.magnification : {};
292
- this.magnification = new MagnificationPlugin(this.viewer, config);
293
- console.log("[MedEngine] Magnification plugin initialized.");
265
+ const config: any = typeof plugins.magnification === 'object' ? plugins.magnification : {}
266
+ this.magnification = new MagnificationPlugin(this.viewer, config)
267
+ console.log('[MedEngine] Magnification plugin initialized.')
294
268
  }
295
269
  }
296
270
 
@@ -299,111 +273,129 @@ export class MedViewerEngine {
299
273
  */
300
274
  private mountAnnotorious(config: any): void {
301
275
  try {
302
- this.anno = new AnnoAnnotator(this, config);
303
- console.log(
304
- "[MedEngine] Annotorious plugin initialized. version: 2.7.17",
305
- );
276
+ this.anno = new AnnoAnnotator(this, config)
277
+ console.log('[MedEngine] Annotorious plugin initialized. version: 2.7.17')
306
278
  } catch (error) {
307
- console.error("[MedEngine] Failed to initialize Annotorious:", error);
279
+ console.error('[MedEngine] Failed to initialize Annotorious:', error)
308
280
  }
309
281
  }
310
282
 
311
283
  public loadAIMarks(aiBoxes: AiBoxRect[] = [], showLabel = false) {
312
-
313
284
  // 清理旧的标注(如果需要)
314
- this.clearAIMarks();
285
+ this.clearAIMarks()
315
286
  aiBoxes.forEach((box: AiBoxRect) => {
316
- var elt = document.createElement("div");
317
- elt.classList.add("ld_ai-box");
287
+ var elt = document.createElement('div')
288
+ elt.classList.add('ld-ai-box')
318
289
  if (box.style) {
319
- Object.assign(elt.style, box.style);
290
+ Object.assign(elt.style, box.style)
320
291
  }
321
292
  if (showLabel) {
322
- let text = document.createElement("div");
323
- text.className = "celltext";
324
- text.style.position = "absolute";
293
+ let text = document.createElement('div')
294
+ text.className = 'celltext'
295
+ text.style.position = 'absolute'
325
296
  // text.style.display = "none";
326
- text.style.right = "102%";
327
- text.style.top = "0";
328
- text.style.color = "#333";
329
- text.style.background = "#F5F5F5";
330
- text.style.opacity = "0.7";
331
- text.style.width = "max-content";
332
- text.style.padding = "5px";
333
- text.style.borderRadius = "8px";
297
+ text.style.right = '102%'
298
+ text.style.top = '0'
299
+ text.style.color = '#333'
300
+ text.style.background = '#F5F5F5'
301
+ text.style.opacity = '0.7'
302
+ text.style.width = 'max-content'
303
+ text.style.padding = '5px'
304
+ text.style.borderRadius = '8px'
334
305
  box.labels.forEach((label: AiBoxRectLabel) => {
335
- const labelStyle = label.style || {};
336
- text.innerHTML += `<div style="${Object.entries(labelStyle).map(([key, value]) => `${key}:${value}`).join(';')}">${label.label}&nbsp;:&nbsp;${label.value}</div>`;
337
- });
338
- elt.appendChild(text);
306
+ const labelStyle = label.style || {}
307
+ text.innerHTML += `<div style="${Object.entries(labelStyle)
308
+ .map(([key, value]) => `${key}:${value}`)
309
+ .join(';')}">${label.label}&nbsp;:&nbsp;${label.value}</div>`
310
+ })
311
+ elt.appendChild(text)
339
312
  }
340
- const img = this.viewer.world.getItemAt(0);
341
- const size = img.getContentSize(); // { x: 10240, y: 10240 }
313
+ const img = this.viewer.world.getItemAt(0)
314
+ const size = img.getContentSize() // { x: 10240, y: 10240 }
342
315
 
343
- const x = box.x / size.x;
344
- const y = box.y / size.y;
345
- const w = box.width / size.x;
346
- const h = box.height / size.y;
316
+ const x = box.x / size.x
317
+ const y = box.y / size.y
318
+ const w = box.width / size.x
319
+ const h = box.height / size.y
347
320
  this.viewer.addOverlay({
348
321
  element: elt,
349
- location: new OpenSeadragon.Rect(
350
- x,
351
- y,
352
- w,
353
- h
354
- )
355
- });
356
- });
357
-
322
+ location: new OpenSeadragon.Rect(x, y, w, h)
323
+ })
324
+ })
358
325
  }
326
+
359
327
  updateAIMarks(aiBoxes: AiBoxRect[] = [], showLabel = false) {
360
- this.loadAIMarks(aiBoxes, showLabel);
328
+ this.loadAIMarks(aiBoxes, showLabel)
361
329
  }
362
330
 
363
331
  public clearAIMarks() {
364
332
  const aiBoxesList = Array.from(
365
- document.getElementsByClassName("ld_ai-box") as HTMLCollectionOf<HTMLDivElement>
366
- );
333
+ document.getElementsByClassName('ld-ai-box') as HTMLCollectionOf<HTMLDivElement>
334
+ )
367
335
  // 删除所有 overlay
368
- aiBoxesList.forEach(box => {
369
- this.viewer.removeOverlay(box);
370
- });
336
+ aiBoxesList.forEach((box) => {
337
+ this.viewer.removeOverlay(box)
338
+ })
339
+ }
340
+ public loadSelectionBox(selections: SelectionBox[] = []) {
341
+ const aiBoxesList = Array.from(
342
+ document.getElementsByClassName('ld-selection-box') as HTMLCollectionOf<HTMLDivElement>
343
+ )
344
+ // 删除所有 overlay
345
+ aiBoxesList.forEach((box) => {
346
+ this.viewer.removeOverlay(box)
347
+ })
348
+ selections.forEach((selection: SelectionBox) => {
349
+ let elt = document.createElement('div')
350
+ elt.classList.add('ld-selection-box')
351
+ if (selection.style) {
352
+ Object.assign(elt.style, selection.style)
353
+ }
354
+ const img = this.viewer.world.getItemAt(0)
355
+ const size = img.getContentSize() // { x: 10240, y: 10240 }
356
+ const x = selection.x / size.x
357
+ const y = selection.y / size.y
358
+ const w = selection.width / size.x
359
+ const h = selection.height / size.y
360
+ this.viewer.addOverlay({
361
+ element: elt,
362
+ location: new OpenSeadragon.Rect(x, y, w, h)
363
+ })
364
+ })
371
365
  }
372
-
373
366
  public addHandler(event: string, handler: Function) {
374
367
  if (!this.events.has(event)) {
375
- this.events.set(event, new Set());
368
+ this.events.set(event, new Set())
376
369
  }
377
- this.events.get(event)!.add(handler);
370
+ this.events.get(event)!.add(handler)
378
371
  }
379
372
 
380
373
  public addOnceHandler(event: string, handler: Function) {
381
374
  const wrapper = (...args: any[]) => {
382
- handler(...args);
383
- this.removeHandler(event, wrapper);
384
- };
385
- this.addHandler(event, wrapper);
375
+ handler(...args)
376
+ this.removeHandler(event, wrapper)
377
+ }
378
+ this.addHandler(event, wrapper)
386
379
  }
387
380
 
388
381
  public removeHandler(event: string, handler: Function) {
389
- this.events.get(event)?.delete(handler);
382
+ this.events.get(event)?.delete(handler)
390
383
  }
391
384
 
392
385
  private emit(event: string, payload?: any) {
393
- this.events.get(event)?.forEach(fn => fn(payload));
386
+ this.events.get(event)?.forEach((fn) => fn(payload))
394
387
  }
395
388
 
396
-
397
389
  /**
398
390
  * 销毁引擎与所有插件
399
391
  */
400
392
  public destroy(): void {
401
393
  // this.konva?.destroy();
402
- this.anno?.destroy();
403
- this.toolbar?.destroy();
404
- this.selection?.destroy();
405
- this.scalebar?.destroy();
406
- this.colorAdjust?.destroy();
407
- this.viewer.destroy();
394
+ this.anno?.destroy()
395
+ this.toolbar?.destroy()
396
+ this.selection?.destroy()
397
+ this.scalebar?.destroy()
398
+ this.colorAdjust?.destroy()
399
+ this.viewer.destroy()
408
400
  }
409
401
  }
@@ -45,7 +45,6 @@ export class MagnificationPlugin {
45
45
  this.viewer = viewer;
46
46
  this.options = options;
47
47
 
48
-
49
48
  this.init();
50
49
  }
51
50
 
@@ -99,24 +98,22 @@ export class MagnificationPlugin {
99
98
 
100
99
  // 修改 updateMagnificationDisplay 方法
101
100
  private updateMagnificationDisplay = (): void => {
102
-
103
101
  if (this.options.type === "LD") {
104
102
  const tiledImage = this.viewer.world.getItemAt(0);
105
103
  if (!this.magnificationDisplay || !tiledImage) return;
106
- const source =
107
- ((this.viewer as any).source as any);
104
+ const source = (this.viewer as any).source as any;
108
105
  const width = (source as any).width;
109
106
  const height = (source as any).height;
110
- const pixelsPerMeter = this.options.pixelsPerMeter || ((this.viewer as any).scalebarInstance?.pixelsPerMeter || 96);
107
+ const pixelsPerMeter =
108
+ this.options.pixelsPerMeter ||
109
+ (this.viewer as any).scalebarInstance?.pixelsPerMeter ||
110
+ 96;
111
111
  const currentZoom = this.viewer.viewport.getZoom();
112
112
  // const len = Math.max(width, height);
113
113
  const conversionFactor = (20 * 0.0011 * pixelsPerMeter) / width;
114
114
  const magnification = conversionFactor * currentZoom;
115
115
 
116
116
  this.magnificationDisplay.innerHTML = `<span>${magnification.toFixed(2)}X</span>`;
117
-
118
-
119
-
120
117
  } else {
121
118
  const tiledImage = this.viewer.world.getItemAt(0);
122
119
  if (!this.magnificationDisplay || !tiledImage) return;
@@ -136,28 +133,57 @@ export class MagnificationPlugin {
136
133
  this.magnificationDisplay.innerHTML = `<span>${magnification.toFixed(2)}X</span>`;
137
134
  }
138
135
  };
136
+ public getMagnification(): number {
137
+ const tiledImage = this.viewer.world.getItemAt(0);
138
+ if (!tiledImage) return 0;
139
+
140
+ if (this.options.type === "LD") {
141
+ const source = (this.viewer as any).source as any;
142
+ const width = source?.width;
143
+ const pixelsPerMeter =
144
+ this.options.pixelsPerMeter ||
145
+ (this.viewer as any).scalebarInstance?.pixelsPerMeter ||
146
+ 96;
147
+
148
+ const currentZoom = this.viewer.viewport.getZoom();
149
+ const conversionFactor = (20 * 0.0011 * pixelsPerMeter) / width;
150
+
151
+ return conversionFactor * currentZoom;
152
+ }
153
+
154
+ // --- OSD 模式 ---
155
+ const currentZoom = this.viewer.viewport.viewportToImageZoom(
156
+ this.viewer.viewport.getZoom(),
157
+ );
158
+
159
+ const baseMag =
160
+ ((this.viewer as any).source as any)?.max_magnification || 40;
161
+
162
+ return currentZoom * baseMag;
163
+ }
139
164
 
140
165
  // 修改 setMagnification 方法
141
166
  private setMagnification(mag: number): void {
142
167
  if (this.options.type === "LD") {
143
-
144
168
  const tiledImage = this.viewer.world.getItemAt(0);
145
169
  if (!this.magnificationDisplay || !tiledImage) return;
146
- const source =
147
- ((this.viewer as any).source as any);
170
+ const source = (this.viewer as any).source as any;
148
171
  const width = (source as any).width;
149
172
  const height = (source as any).height;
150
- const pixelsPerMeter = this.options.pixelsPerMeter || ((this.viewer as any).scalebarInstance?.pixelsPerMeter || 96);
173
+ const pixelsPerMeter =
174
+ this.options.pixelsPerMeter ||
175
+ (this.viewer as any).scalebarInstance?.pixelsPerMeter ||
176
+ 96;
151
177
  const currentZoom = this.viewer.viewport.getZoom();
152
178
  // const len = Math.max(width, height);
153
179
  const conversionFactor = (20 * 0.0011 * pixelsPerMeter) / width;
154
180
  const targetZoom = mag / conversionFactor;
155
- this.viewer.viewport.zoomTo(targetZoom, this.viewer.viewport.getCenter(), false);
181
+ this.viewer.viewport.zoomTo(
182
+ targetZoom,
183
+ this.viewer.viewport.getCenter(),
184
+ false,
185
+ );
156
186
  this.updateMagnificationDisplay();
157
-
158
-
159
-
160
-
161
187
  } else {
162
188
  const tiledImage = this.viewer.world.getItemAt(0);
163
189
  if (!tiledImage) return;