id-scanner-lib 1.3.3 → 1.6.2

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.
Files changed (80) hide show
  1. package/README.md +324 -410
  2. package/dist/id-scanner-lib.esm.js +4826 -0
  3. package/dist/id-scanner-lib.esm.js.map +1 -0
  4. package/dist/id-scanner-lib.js +4858 -0
  5. package/dist/id-scanner-lib.js.map +1 -0
  6. package/dist/types/browser-image-compression.d.ts +19 -0
  7. package/dist/types/tesseract.d.ts +280 -0
  8. package/package.json +89 -78
  9. package/src/core/base-module.ts +78 -0
  10. package/src/core/camera-manager.ts +813 -0
  11. package/src/core/config.ts +305 -0
  12. package/src/core/errors.ts +174 -0
  13. package/src/core/event-emitter.test.ts +42 -0
  14. package/src/core/event-emitter.ts +110 -0
  15. package/src/core/loading-state.test.ts +67 -0
  16. package/src/core/loading-state.ts +156 -0
  17. package/src/core/logger.test.ts +49 -0
  18. package/src/core/logger.ts +549 -0
  19. package/src/core/module-manager.ts +163 -0
  20. package/src/core/plugin-manager.ts +429 -0
  21. package/src/core/resource-manager.ts +762 -0
  22. package/src/core/result.ts +163 -0
  23. package/src/core/scanner-factory.ts +236 -0
  24. package/src/index.ts +117 -939
  25. package/src/interfaces/external-types.ts +200 -0
  26. package/src/interfaces/face-detection.ts +309 -0
  27. package/src/interfaces/scanner-module.ts +384 -0
  28. package/src/modules/face/face-detector.ts +988 -0
  29. package/src/modules/face/index.ts +208 -0
  30. package/src/modules/face/liveness-detector.ts +908 -0
  31. package/src/modules/face/types.ts +133 -0
  32. package/src/{id-recognition → modules/id-card}/anti-fake-detector.ts +274 -240
  33. package/src/modules/id-card/id-card-detector.ts +474 -0
  34. package/src/modules/id-card/index.ts +425 -0
  35. package/src/{id-recognition → modules/id-card}/ocr-processor.ts +149 -92
  36. package/src/modules/id-card/ocr-worker.ts +259 -0
  37. package/src/modules/id-card/types.ts +178 -0
  38. package/src/modules/qrcode/index.ts +175 -0
  39. package/src/modules/qrcode/qr-code-scanner.ts +231 -0
  40. package/src/modules/qrcode/types.ts +169 -0
  41. package/src/types/common.test.ts +99 -0
  42. package/src/types/common.ts +166 -0
  43. package/src/types/tesseract.d.ts +265 -22
  44. package/src/utils/camera.test.ts +30 -0
  45. package/src/utils/camera.ts +4 -1
  46. package/src/utils/error-handler.test.ts +137 -0
  47. package/src/utils/error-handler.ts +110 -0
  48. package/src/utils/image-processing.ts +68 -49
  49. package/src/utils/index.test.ts +186 -0
  50. package/src/utils/index.ts +429 -0
  51. package/src/utils/performance.ts +168 -131
  52. package/src/utils/resource-manager.ts +65 -146
  53. package/src/utils/retry.test.ts +142 -0
  54. package/src/utils/retry.ts +282 -0
  55. package/src/utils/types.ts +90 -2
  56. package/src/utils/utils.test.ts +171 -0
  57. package/src/utils/worker.ts +123 -84
  58. package/src/version.ts +11 -0
  59. package/tools/scaffold.js +543 -0
  60. package/dist/id-scanner-core.esm.js +0 -11349
  61. package/dist/id-scanner-core.js +0 -11361
  62. package/dist/id-scanner-core.min.js +0 -1
  63. package/dist/id-scanner-ocr.esm.js +0 -2319
  64. package/dist/id-scanner-ocr.js +0 -2328
  65. package/dist/id-scanner-ocr.min.js +0 -1
  66. package/dist/id-scanner-qr.esm.js +0 -1296
  67. package/dist/id-scanner-qr.js +0 -1305
  68. package/dist/id-scanner-qr.min.js +0 -1
  69. package/dist/id-scanner.js +0 -4561
  70. package/dist/id-scanner.min.js +0 -1
  71. package/src/core.ts +0 -138
  72. package/src/demo/demo.ts +0 -204
  73. package/src/id-recognition/data-extractor.ts +0 -262
  74. package/src/id-recognition/id-detector.ts +0 -510
  75. package/src/id-recognition/ocr-worker.ts +0 -156
  76. package/src/index-umd.ts +0 -477
  77. package/src/ocr-module.ts +0 -187
  78. package/src/qr-module.ts +0 -179
  79. package/src/scanner/barcode-scanner.ts +0 -251
  80. package/src/scanner/qr-scanner.ts +0 -167
package/src/index.ts CHANGED
@@ -1,989 +1,167 @@
1
1
  /**
2
- * @file ID扫描识别库主入口文件
3
- * @description 提供身份证识别与二维码、条形码扫描功能的纯前端TypeScript库
4
- * @module IDScannerLib
5
- * @version 1.3.0
6
- * @license MIT
2
+ * @file 主入口文件
3
+ * @description ID Scanner库的主入口点,提供统一的API和模块导出
4
+ * @module index
7
5
  */
8
6
 
9
- import { Camera, CameraOptions } from "./utils/camera"
10
- import { IDCardInfo, DetectionResult } from "./utils/types"
11
- import {
12
- ImageProcessor,
13
- ImageProcessorOptions,
14
- ImageCompressionOptions,
15
- } from "./utils/image-processing"
16
-
17
- // 先只导入类型定义,不导入实际实现
18
- import type { QRScannerOptions } from "./scanner/qr-scanner"
19
- import type { BarcodeScannerOptions } from "./scanner/barcode-scanner"
20
-
21
- // 导入防伪检测器
22
- import {
23
- AntiFakeDetector,
24
- AntiFakeDetectionResult,
25
- } from "./id-recognition/anti-fake-detector"
7
+ import { ModuleManager } from './core/module-manager';
8
+ import { IDCardModule } from './modules/id-card';
9
+ import { QRCodeModule } from './modules/qrcode';
10
+ import { FaceModule } from './modules/face';
11
+ import { VERSION, BUILD_DATE } from './version';
12
+ import { Logger, LogLevel } from './core/logger';
13
+ import { IDCardModuleOptions } from './modules/id-card/types';
14
+ import { QRCodeModuleOptions } from './modules/qrcode/types';
15
+ import { FaceModuleOptions } from './modules/face/types';
26
16
 
27
17
  /**
28
- * IDScanner配置选项接口
18
+ * IDScanner配置选项
29
19
  */
30
20
  export interface IDScannerOptions {
31
- cameraOptions?: CameraOptions
32
- qrScannerOptions?: QRScannerOptions
33
- barcodeScannerOptions?: BarcodeScannerOptions
34
- onQRCodeScanned?: (result: string) => void
35
- onBarcodeScanned?: (result: string) => void
36
- onIDCardScanned?: (info: IDCardInfo) => void
37
- onImageProcessed?: (processedImage: ImageData | File) => void
38
- onAntiFakeDetected?: (result: AntiFakeDetectionResult) => void
39
- onError?: (error: Error) => void
21
+ /** 日志级别 */
22
+ logLevel?: LogLevel;
23
+ /** 是否启用身份证识别模块 */
24
+ enableIDCard?: boolean;
25
+ /** 是否启用二维码识别模块 */
26
+ enableQRCode?: boolean;
27
+ /** 是否启用人脸识别模块 */
28
+ enableFace?: boolean;
29
+ /** 身份证模块配置 */
30
+ idCard?: IDCardModuleOptions;
31
+ /** 二维码模块配置 */
32
+ qrCode?: QRCodeModuleOptions;
33
+ /** 人脸识别模块配置 */
34
+ face?: FaceModuleOptions;
40
35
  }
41
36
 
42
37
  /**
43
- * IDScanner 主类
44
- *
45
- * 整合二维码、条形码扫描和身份证识别功能,提供统一的接口
46
- * 使用动态导入实现按需加载
38
+ * IDScanner
39
+ * 提供整合的身份证、二维码和人脸识别功能
47
40
  */
48
41
  export class IDScanner {
49
- private camera: Camera
50
- private scanMode: "qr" | "barcode" | "idcard" = "qr"
51
- private videoElement: HTMLVideoElement | null = null
52
- private scanning = false
53
- private qrModule: any = null
54
- private ocrModule: any = null
55
- private scanTimer: number | null = null
56
- private isQRModuleLoaded: boolean = false
57
- private isOCRModuleLoaded: boolean = false
58
-
59
- // 新增防伪检测器
60
- private antiFakeDetector: AntiFakeDetector | null = null
61
- private isAntiFakeModuleLoaded: boolean = false
62
-
42
+ /** 版本号 */
43
+ public static readonly VERSION = VERSION;
44
+ /** 构建日期 */
45
+ public static readonly BUILD_DATE = BUILD_DATE;
46
+
47
+ /** 模块管理器 */
48
+ private moduleManager: ModuleManager;
49
+ /** 是否已经初始化 */
50
+ private initialized = false;
51
+ /** 日志工具 */
52
+ private logger: Logger;
53
+
63
54
  /**
64
55
  * 构造函数
65
56
  * @param options 配置选项
66
57
  */
67
- constructor(private options: IDScannerOptions = {}) {
68
- this.camera = new Camera(options.cameraOptions)
69
- }
70
-
71
- /**
72
- * 初始化模块
73
- * 根据需要初始化OCR引擎和防伪检测模块
74
- */
75
- async initialize(): Promise<void> {
76
- try {
77
- // 预加载OCR模块但不初始化
78
- if (!this.isOCRModuleLoaded) {
79
- // 动态导入OCR模块
80
- const OCRModule = await import("./ocr-module").then((m) => m.OCRModule)
81
- this.ocrModule = new OCRModule({
82
- cameraOptions: this.options.cameraOptions,
83
- onIDCardScanned: this.options.onIDCardScanned,
84
- onError: this.options.onError,
85
- })
86
- this.isOCRModuleLoaded = true
87
-
88
- // 初始化OCR模块
89
- await this.ocrModule.initialize()
90
- }
91
-
92
- // 初始化防伪检测模块
93
- if (!this.isAntiFakeModuleLoaded) {
94
- this.antiFakeDetector = new AntiFakeDetector()
95
- this.isAntiFakeModuleLoaded = true
96
- }
97
-
98
- console.log("IDScanner初始化完成")
99
- } catch (error) {
100
- console.error("初始化失败:", error)
101
- this.handleError(error as Error)
102
- throw error
103
- }
104
- }
105
-
106
- /**
107
- * 初始化OCR模块
108
- */
109
- private async initOCRModule(): Promise<void> {
110
- if (this.isOCRModuleLoaded) return
111
-
112
- try {
113
- // 动态导入OCR模块
114
- const OCRModule = await import("./ocr-module").then((m) => m.OCRModule)
115
- this.ocrModule = new OCRModule({
116
- cameraOptions: this.options.cameraOptions,
117
- onIDCardScanned: this.options.onIDCardScanned,
118
- onError: this.options.onError,
119
- })
120
- this.isOCRModuleLoaded = true
121
-
122
- // 初始化OCR模块
123
- await this.ocrModule.initialize()
124
- } catch (error) {
125
- console.error("OCR模块初始化失败:", error)
126
- throw error
127
- }
128
- }
129
-
130
- /**
131
- * 启动二维码扫描
132
- * @param videoElement HTML视频元素
133
- */
134
- async startQRScanner(videoElement: HTMLVideoElement): Promise<void> {
135
- this.stop()
136
- this.videoElement = videoElement
137
- this.scanMode = "qr"
138
-
139
- try {
140
- // 动态加载二维码模块
141
- if (!this.isQRModuleLoaded) {
142
- const ScannerModule = await import("./qr-module").then(
143
- (m) => m.ScannerModule
144
- )
145
- this.qrModule = new ScannerModule({
146
- cameraOptions: this.options.cameraOptions,
147
- qrScannerOptions: this.options.qrScannerOptions,
148
- barcodeScannerOptions: this.options.barcodeScannerOptions,
149
- onQRCodeScanned: this.options.onQRCodeScanned,
150
- onBarcodeScanned: this.options.onBarcodeScanned,
151
- onError: this.options.onError,
152
- })
153
- this.isQRModuleLoaded = true
154
- }
155
-
156
- await this.qrModule.startQRScanner(videoElement)
157
- } catch (error) {
158
- this.handleError(error as Error)
159
- }
160
- }
161
-
162
- /**
163
- * 启动条形码扫描
164
- * @param videoElement HTML视频元素
165
- */
166
- async startBarcodeScanner(videoElement: HTMLVideoElement): Promise<void> {
167
- this.stop()
168
- this.videoElement = videoElement
169
- this.scanMode = "barcode"
170
-
171
- try {
172
- // 动态加载二维码模块
173
- if (!this.isQRModuleLoaded) {
174
- const ScannerModule = await import("./qr-module").then(
175
- (m) => m.ScannerModule
176
- )
177
- this.qrModule = new ScannerModule({
178
- cameraOptions: this.options.cameraOptions,
179
- qrScannerOptions: this.options.qrScannerOptions,
180
- barcodeScannerOptions: this.options.barcodeScannerOptions,
181
- onQRCodeScanned: this.options.onQRCodeScanned,
182
- onBarcodeScanned: this.options.onBarcodeScanned,
183
- onError: this.options.onError,
184
- })
185
- this.isQRModuleLoaded = true
186
- }
187
-
188
- await this.qrModule.startBarcodeScanner(videoElement)
189
- } catch (error) {
190
- this.handleError(error as Error)
58
+ constructor(options: IDScannerOptions = {}) {
59
+ // 配置日志级别
60
+ this.logger = Logger.getInstance();
61
+ if (options.logLevel !== undefined) {
62
+ this.logger.setLevel(options.logLevel);
191
63
  }
192
- }
193
-
194
- /**
195
- * 启动身份证扫描
196
- * @param videoElement HTML视频元素
197
- */
198
- async startIDCardScanner(videoElement: HTMLVideoElement): Promise<void> {
199
- this.stop()
200
- this.videoElement = videoElement
201
- this.scanMode = "idcard"
202
-
203
- try {
204
- // 检查OCR模块是否已加载,若未加载则自动初始化
205
- if (!this.isOCRModuleLoaded) {
206
- await this.initialize()
207
- }
208
-
209
- await this.ocrModule.startIDCardScanner(videoElement)
210
- } catch (error) {
211
- this.handleError(error as Error)
64
+
65
+ this.moduleManager = ModuleManager.getInstance();
66
+
67
+ // 注册模块
68
+ if (options.enableIDCard !== false) {
69
+ this.moduleManager.register(new IDCardModule(options.idCard));
212
70
  }
213
- }
214
-
215
- /**
216
- * 停止扫描
217
- */
218
- stop(): void {
219
- if (this.scanMode === "qr" || this.scanMode === "barcode") {
220
- if (this.qrModule) {
221
- this.qrModule.stop()
222
- }
223
- } else if (this.scanMode === "idcard") {
224
- if (this.ocrModule) {
225
- this.ocrModule.stop()
226
- }
71
+
72
+ if (options.enableQRCode !== false) {
73
+ this.moduleManager.register(new QRCodeModule(options.qrCode));
227
74
  }
228
- }
229
-
230
- /**
231
- * 处理错误
232
- */
233
- private handleError(error: Error): void {
234
- if (this.options.onError) {
235
- this.options.onError(error)
236
- } else {
237
- console.error("IDScanner错误:", error)
75
+
76
+ if (options.enableFace !== false) {
77
+ this.moduleManager.register(new FaceModule(options.face));
238
78
  }
239
79
  }
240
80
 
241
81
  /**
242
- * 释放资源
82
+ * 初始化库
243
83
  */
244
- async terminate(): Promise<void> {
245
- this.stop()
246
-
247
- // 释放OCR资源
248
- if (this.isOCRModuleLoaded && this.ocrModule) {
249
- await this.ocrModule.terminate()
250
- this.ocrModule = null
251
- this.isOCRModuleLoaded = false
84
+ public async initialize(): Promise<void> {
85
+ if (this.initialized) {
86
+ return;
252
87
  }
253
88
 
254
- // 释放QR扫描资源
255
- if (this.isQRModuleLoaded && this.qrModule) {
256
- this.qrModule = null
257
- this.isQRModuleLoaded = false
258
- }
89
+ this.logger.info('IDScanner', `初始化 IDScanner v${VERSION}`);
259
90
 
260
- // 释放防伪检测资源
261
- if (this.antiFakeDetector) {
262
- this.antiFakeDetector.dispose()
263
- this.antiFakeDetector = null
264
- this.isAntiFakeModuleLoaded = false
265
- }
266
- }
267
-
268
- /**
269
- * 处理图片中的二维码
270
- * @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
271
- * @returns 返回Promise,解析为扫描结果
272
- */
273
- async processQRCodeImage(
274
- imageSource: HTMLImageElement | HTMLCanvasElement | string | File
275
- ): Promise<string> {
276
91
  try {
277
- // 动态加载二维码模块
278
- if (!this.isQRModuleLoaded) {
279
- const ScannerModule = await import("./qr-module").then(
280
- (m) => m.ScannerModule
281
- )
282
- this.qrModule = new ScannerModule({
283
- qrScannerOptions: this.options.qrScannerOptions,
284
- onQRCodeScanned: this.options.onQRCodeScanned,
285
- onError: this.options.onError,
286
- })
287
- this.isQRModuleLoaded = true
288
- }
289
-
290
- // 处理不同类型的图片源
291
- let imageElement: HTMLImageElement
292
-
293
- if (imageSource instanceof File) {
294
- // 如果是File对象,创建新的Image元素并加载图片
295
- imageElement = new Image()
296
- imageElement.crossOrigin = "anonymous" // 处理跨域图片
297
- const url = URL.createObjectURL(imageSource)
298
- await new Promise((resolve, reject) => {
299
- imageElement.onload = resolve
300
- imageElement.onerror = reject
301
- imageElement.src = url
302
- })
303
- // 使用后释放URL对象
304
- URL.revokeObjectURL(url)
305
- } else if (typeof imageSource === "string") {
306
- // 如果是URL字符串,创建新的Image元素并加载图片
307
- imageElement = new Image()
308
- imageElement.crossOrigin = "anonymous" // 处理跨域图片
309
- await new Promise((resolve, reject) => {
310
- imageElement.onload = resolve
311
- imageElement.onerror = reject
312
- imageElement.src = imageSource
313
- })
314
- } else if (imageSource instanceof HTMLImageElement) {
315
- // 如果已经是Image元素,直接使用
316
- imageElement = imageSource
317
- } else if (imageSource instanceof HTMLCanvasElement) {
318
- // 如果是Canvas元素,创建Image并从Canvas获取数据
319
- imageElement = new Image()
320
- imageElement.src = imageSource.toDataURL()
321
- await new Promise((resolve) => {
322
- imageElement.onload = resolve
323
- })
324
- } else {
325
- throw new Error("不支持的图片源类型")
326
- }
92
+ // 初始化所有模块
93
+ await this.moduleManager.initialize();
327
94
 
328
- // 获取图像数据
329
- const canvas = document.createElement("canvas")
330
- canvas.width = imageElement.naturalWidth
331
- canvas.height = imageElement.naturalHeight
332
- const ctx = canvas.getContext("2d")
333
-
334
- if (!ctx) {
335
- throw new Error("无法创建Canvas上下文")
336
- }
337
-
338
- ctx.drawImage(imageElement, 0, 0)
339
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
340
-
341
- // 使用QR模块处理图像
342
- return this.qrModule.processQRCodeImage(imageData)
95
+ this.initialized = true;
96
+ this.logger.info('IDScanner', 'IDScanner初始化完成');
343
97
  } catch (error) {
344
- this.handleError(error as Error)
345
- throw error
98
+ this.logger.error('IDScanner', 'IDScanner初始化失败', error as Error);
99
+ throw new Error(`IDScanner初始化失败: ${error instanceof Error ? error.message : String(error)}`);
346
100
  }
347
101
  }
348
102
 
349
103
  /**
350
- * 处理图片中的条形码
351
- * @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
352
- * @returns 返回Promise,解析为扫描结果
104
+ * 获取身份证模块实例
105
+ * @returns 身份证模块
353
106
  */
354
- async processBarcodeImage(
355
- imageSource: HTMLImageElement | HTMLCanvasElement | string | File
356
- ): Promise<string> {
357
- try {
358
- // 动态加载二维码模块
359
- if (!this.isQRModuleLoaded) {
360
- const ScannerModule = await import("./qr-module").then(
361
- (m) => m.ScannerModule
362
- )
363
- this.qrModule = new ScannerModule({
364
- barcodeScannerOptions: this.options.barcodeScannerOptions,
365
- onBarcodeScanned: this.options.onBarcodeScanned,
366
- onError: this.options.onError,
367
- })
368
- this.isQRModuleLoaded = true
369
- }
370
-
371
- // 处理不同类型的图片源
372
- let imageElement: HTMLImageElement
373
-
374
- if (imageSource instanceof File) {
375
- // 如果是File对象,创建新的Image元素并加载图片
376
- imageElement = new Image()
377
- imageElement.crossOrigin = "anonymous" // 处理跨域图片
378
- const url = URL.createObjectURL(imageSource)
379
- await new Promise((resolve, reject) => {
380
- imageElement.onload = resolve
381
- imageElement.onerror = reject
382
- imageElement.src = url
383
- })
384
- // 使用后释放URL对象
385
- URL.revokeObjectURL(url)
386
- } else if (typeof imageSource === "string") {
387
- // 如果是URL字符串,创建新的Image元素并加载图片
388
- imageElement = new Image()
389
- imageElement.crossOrigin = "anonymous" // 处理跨域图片
390
- await new Promise((resolve, reject) => {
391
- imageElement.onload = resolve
392
- imageElement.onerror = reject
393
- imageElement.src = imageSource
394
- })
395
- } else if (imageSource instanceof HTMLImageElement) {
396
- // 如果已经是Image元素,直接使用
397
- imageElement = imageSource
398
- } else if (imageSource instanceof HTMLCanvasElement) {
399
- // 如果是Canvas元素,创建Image并从Canvas获取数据
400
- imageElement = new Image()
401
- imageElement.src = imageSource.toDataURL()
402
- await new Promise((resolve) => {
403
- imageElement.onload = resolve
404
- })
405
- } else {
406
- throw new Error("不支持的图片源类型")
407
- }
408
-
409
- // 获取图像数据
410
- const canvas = document.createElement("canvas")
411
- canvas.width = imageElement.naturalWidth
412
- canvas.height = imageElement.naturalHeight
413
- const ctx = canvas.getContext("2d")
414
-
415
- if (!ctx) {
416
- throw new Error("无法创建Canvas上下文")
417
- }
418
-
419
- ctx.drawImage(imageElement, 0, 0)
420
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
421
-
422
- // 使用Barcode模块处理图像
423
- return this.qrModule.processBarcodeImage(imageData)
424
- } catch (error) {
425
- this.handleError(error as Error)
426
- throw error
427
- }
107
+ public getIDCardModule(): IDCardModule | undefined {
108
+ return this.moduleManager.getModule<IDCardModule>('id-card');
428
109
  }
429
-
110
+
430
111
  /**
431
- * 处理图片中的身份证
432
- * @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
433
- * @returns 返回Promise,解析为身份证信息
112
+ * 获取二维码模块实例
113
+ * @returns 二维码模块
434
114
  */
435
- async processIDCardImage(
436
- imageSource: HTMLImageElement | HTMLCanvasElement | string | File
437
- ): Promise<IDCardInfo> {
438
- if (!this.isOCRModuleLoaded) {
439
- await this.initOCRModule()
440
- }
441
-
442
- try {
443
- // 处理不同类型的图片源
444
- let imageElement: HTMLImageElement
445
-
446
- if (imageSource instanceof File) {
447
- // 如果是File对象,先进行压缩
448
- const compressedFile = await ImageProcessor.compressImage(imageSource, {
449
- maxSizeMB: 2, // 最大2MB
450
- maxWidthOrHeight: 1800, // 最大尺寸
451
- useWebWorker: true,
452
- })
453
-
454
- // 创建新的Image元素并加载图片
455
- imageElement = new Image()
456
- imageElement.crossOrigin = "anonymous" // 处理跨域图片
457
- const url = URL.createObjectURL(compressedFile)
458
- await new Promise((resolve, reject) => {
459
- imageElement.onload = resolve
460
- imageElement.onerror = reject
461
- imageElement.src = url
462
- })
463
- // 使用后释放URL对象
464
- URL.revokeObjectURL(url)
465
- } else if (typeof imageSource === "string") {
466
- // 如果是URL字符串,创建新的Image元素并加载图片
467
- imageElement = new Image()
468
- imageElement.crossOrigin = "anonymous" // 处理跨域图片
469
- await new Promise((resolve, reject) => {
470
- imageElement.onload = resolve
471
- imageElement.onerror = reject
472
- imageElement.src = imageSource
473
- })
474
- } else if (imageSource instanceof HTMLImageElement) {
475
- // 如果已经是Image元素,直接使用
476
- imageElement = imageSource
477
- } else if (imageSource instanceof HTMLCanvasElement) {
478
- // 如果是Canvas元素,创建Image并从Canvas获取数据
479
- imageElement = new Image()
480
- imageElement.src = imageSource.toDataURL()
481
- await new Promise((resolve) => {
482
- imageElement.onload = resolve
483
- })
484
- } else {
485
- throw new Error("不支持的图片源类型")
486
- }
487
-
488
- // 获取图像数据
489
- const canvas = document.createElement("canvas")
490
- canvas.width = imageElement.naturalWidth
491
- canvas.height = imageElement.naturalHeight
492
- const ctx = canvas.getContext("2d")
493
-
494
- if (!ctx) {
495
- throw new Error("无法创建Canvas上下文")
496
- }
497
-
498
- ctx.drawImage(imageElement, 0, 0)
499
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
500
-
501
- // 对图像进行预处理,提高识别率
502
- const enhancedImageData = ImageProcessor.batchProcess(imageData, {
503
- brightness: 10,
504
- contrast: 15,
505
- sharpen: true,
506
- })
507
-
508
- // 使用OCR模块处理图像
509
- const idInfo = await this.ocrModule.processIDCard(enhancedImageData)
510
-
511
- // 进行防伪检测并将结果添加到身份证信息中
512
- if (this.isAntiFakeModuleLoaded && this.antiFakeDetector) {
513
- try {
514
- const result = await this.antiFakeDetector.detect(enhancedImageData)
515
- // 将防伪检测结果添加到身份证信息对象中
516
- const extendedInfo = idInfo as any
517
- extendedInfo.antiFakeResult = result
518
-
519
- // 触发防伪检测回调
520
- if (this.options.onAntiFakeDetected) {
521
- this.options.onAntiFakeDetected(result)
522
- }
523
- } catch (error) {
524
- console.warn("身份证防伪检测失败:", error)
525
- }
526
- }
527
-
528
- return idInfo
529
- } catch (error) {
530
- this.handleError(error as Error)
531
- throw error
532
- }
115
+ public getQRCodeModule(): QRCodeModule | undefined {
116
+ return this.moduleManager.getModule<QRCodeModule>('qrcode');
533
117
  }
534
-
118
+
535
119
  /**
536
- * 批量处理图像
537
- * @param imageSource 图片源,可以是Image元素、Canvas元素、URL字符串或File对象
538
- * @param options 图像处理选项
539
- * @param outputFormat 输出格式,'imagedata'或'file'
540
- * @returns 返回Promise,解析为处理后的ImageData或File
120
+ * 获取人脸识别模块实例
121
+ * @returns 人脸识别模块
541
122
  */
542
- async processImage(
543
- imageSource: HTMLImageElement | HTMLCanvasElement | string | File,
544
- options: ImageProcessorOptions = {},
545
- outputFormat: "imagedata" | "file" = "imagedata"
546
- ): Promise<ImageData | File> {
547
- try {
548
- // 处理不同类型的图片源
549
- let imageData: ImageData
550
-
551
- if (imageSource instanceof File) {
552
- // 如果是File对象,先进行压缩
553
- const compressedFile = await ImageProcessor.compressImage(imageSource, {
554
- maxSizeMB: 2, // 最大2MB
555
- maxWidthOrHeight: 1920, // 最大尺寸
556
- useWebWorker: true,
557
- })
558
-
559
- // 从File创建ImageData
560
- imageData = await ImageProcessor.createImageDataFromFile(compressedFile)
561
- } else if (typeof imageSource === "string") {
562
- // 如果是URL字符串,创建新的Image元素并加载图片
563
- const imageElement = new Image()
564
- imageElement.crossOrigin = "anonymous" // 处理跨域图片
565
- await new Promise((resolve, reject) => {
566
- imageElement.onload = resolve
567
- imageElement.onerror = reject
568
- imageElement.src = imageSource
569
- })
570
-
571
- // 获取图像数据
572
- const canvas = document.createElement("canvas")
573
- canvas.width = imageElement.naturalWidth
574
- canvas.height = imageElement.naturalHeight
575
- const ctx = canvas.getContext("2d")
576
-
577
- if (!ctx) {
578
- throw new Error("无法创建Canvas上下文")
579
- }
580
-
581
- ctx.drawImage(imageElement, 0, 0)
582
- imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
583
- } else if (imageSource instanceof HTMLImageElement) {
584
- // 如果是Image元素,从它创建ImageData
585
- const canvas = document.createElement("canvas")
586
- canvas.width = imageSource.naturalWidth
587
- canvas.height = imageSource.naturalHeight
588
- const ctx = canvas.getContext("2d")
589
-
590
- if (!ctx) {
591
- throw new Error("无法创建Canvas上下文")
592
- }
593
-
594
- ctx.drawImage(imageSource, 0, 0)
595
- imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
596
- } else if (imageSource instanceof HTMLCanvasElement) {
597
- // 如果是Canvas元素,直接获取其ImageData
598
- const ctx = imageSource.getContext("2d")
599
-
600
- if (!ctx) {
601
- throw new Error("无法获取Canvas上下文")
602
- }
603
-
604
- imageData = ctx.getImageData(
605
- 0,
606
- 0,
607
- imageSource.width,
608
- imageSource.height
609
- )
610
- } else {
611
- throw new Error("不支持的图片源类型")
612
- }
613
-
614
- // 进行图像处理
615
- const processedImageData = ImageProcessor.batchProcess(imageData, options)
616
-
617
- // 根据需要的输出格式返回结果
618
- if (outputFormat === "file") {
619
- // 将ImageData转换为File
620
- const file = await ImageProcessor.imageDataToFile(
621
- processedImageData,
622
- "processed_image.jpg",
623
- "image/jpeg",
624
- 0.85
625
- )
626
-
627
- // 触发回调
628
- if (this.options.onImageProcessed) {
629
- this.options.onImageProcessed(file)
630
- }
631
-
632
- return file
633
- } else {
634
- // 触发回调
635
- if (this.options.onImageProcessed) {
636
- this.options.onImageProcessed(processedImageData)
637
- }
638
-
639
- return processedImageData
640
- }
641
- } catch (error) {
642
- this.handleError(error as Error)
643
- throw error
644
- }
645
- }
646
-
647
- /**
648
- * 压缩图片
649
- * @param file 要压缩的图片文件
650
- * @param options 压缩选项
651
- * @returns 返回Promise,解析为压缩后的文件
652
- */
653
- async compressImage(
654
- file: File,
655
- options?: ImageCompressionOptions
656
- ): Promise<File> {
657
- try {
658
- return await ImageProcessor.compressImage(file, options)
659
- } catch (error) {
660
- this.handleError(error as Error)
661
- throw error
662
- }
123
+ public getFaceModule(): FaceModule | undefined {
124
+ return this.moduleManager.getModule<FaceModule>('face');
663
125
  }
664
-
126
+
665
127
  /**
666
- * 身份证防伪检测
667
- * @param imageSource 图片源
668
- * @returns 防伪检测结果
128
+ * 释放所有资源
669
129
  */
670
- async detectIDCardAntiFake(
671
- imageSource: HTMLImageElement | HTMLCanvasElement | string | File
672
- ): Promise<AntiFakeDetectionResult> {
673
- if (!this.isAntiFakeModuleLoaded || !this.antiFakeDetector) {
674
- await this.initialize()
675
- if (!this.antiFakeDetector) {
676
- throw new Error("防伪检测模块初始化失败")
677
- }
130
+ public async dispose(): Promise<void> {
131
+ if (!this.initialized) {
132
+ return;
678
133
  }
679
-
134
+
135
+ this.logger.info('IDScanner', '释放IDScanner资源');
136
+
680
137
  try {
681
- // 转换输入为ImageData
682
- let imageData: ImageData
683
-
684
- if (typeof imageSource === "string") {
685
- // 处理URL或Base64
686
- const img = new Image()
687
- await new Promise<void>((resolve, reject) => {
688
- img.onload = () => resolve()
689
- img.onerror = () => reject(new Error("图像加载失败"))
690
- img.src = imageSource
691
- })
692
-
693
- const canvas = document.createElement("canvas")
694
- canvas.width = img.width
695
- canvas.height = img.height
696
- const ctx = canvas.getContext("2d")
697
- if (!ctx) {
698
- throw new Error("无法创建Canvas上下文")
699
- }
700
-
701
- ctx.drawImage(img, 0, 0)
702
- imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
703
- } else if (imageSource instanceof File) {
704
- // 处理文件
705
- imageData = await ImageProcessor.createImageDataFromFile(imageSource)
706
- } else if (imageSource instanceof HTMLImageElement) {
707
- // 处理Image元素
708
- const canvas = document.createElement("canvas")
709
- canvas.width = imageSource.width
710
- canvas.height = imageSource.height
711
- const ctx = canvas.getContext("2d")
712
- if (!ctx) {
713
- throw new Error("无法创建Canvas上下文")
714
- }
715
-
716
- ctx.drawImage(imageSource, 0, 0)
717
- imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
718
- } else {
719
- // 处理Canvas元素
720
- const ctx = (imageSource as HTMLCanvasElement).getContext("2d")
721
- if (!ctx) {
722
- throw new Error("无法获取Canvas上下文")
723
- }
724
-
725
- imageData = ctx.getImageData(
726
- 0,
727
- 0,
728
- imageSource.width,
729
- imageSource.height
730
- )
731
- }
732
-
733
- // 执行防伪检测
734
- const result = await this.antiFakeDetector.detect(imageData)
735
-
736
- // 触发回调
737
- if (this.options.onAntiFakeDetected) {
738
- this.options.onAntiFakeDetected(result)
739
- }
740
-
741
- return result
138
+ await this.moduleManager.dispose();
139
+
140
+ this.initialized = false;
141
+ this.logger.info('IDScanner', 'IDScanner资源已释放');
742
142
  } catch (error) {
743
- this.handleError(error as Error)
744
- throw error
745
- }
746
- }
747
- }
748
-
749
- // 导出工具类和类型
750
- export { Camera, CameraOptions } from "./utils/camera"
751
- export {
752
- ImageProcessor,
753
- ImageProcessorOptions,
754
- ImageCompressionOptions,
755
- } from "./utils/image-processing"
756
- export { IDCardInfo, DetectionResult } from "./utils/types"
757
-
758
- // 导出防伪检测相关类型和类
759
- export { AntiFakeDetector, AntiFakeDetectionResult }
760
-
761
- // 为了向后兼容,我们创建一个演示类
762
- export class IDScannerDemo {
763
- private scanner: IDScanner
764
- private currentMode: "qr" | "idcard" = "qr"
765
- private videoElement: HTMLVideoElement
766
- private resultElement: HTMLElement
767
-
768
- /**
769
- * 创建演示类实例
770
- * @param videoElementId 视频元素ID
771
- * @param resultElementId 结果显示元素ID
772
- * @param switchButtonId 切换按钮ID
773
- * @param imageInputId 图片输入元素ID
774
- */
775
- constructor(
776
- videoElementId: string,
777
- resultElementId: string,
778
- switchButtonId?: string,
779
- imageInputId?: string
780
- ) {
781
- this.videoElement = document.getElementById(
782
- videoElementId
783
- ) as HTMLVideoElement
784
- this.resultElement = document.getElementById(resultElementId) as HTMLElement
785
-
786
- // 创建扫描器实例
787
- this.scanner = new IDScanner({
788
- onQRCodeScanned: (result) => this.handleScanResult(result),
789
- onIDCardScanned: (info) => this.handleIDCardResult(info),
790
- onError: (error) => this.handleError(error),
791
- })
792
-
793
- // 设置切换按钮事件
794
- if (switchButtonId) {
795
- const switchButton = document.getElementById(switchButtonId)
796
- if (switchButton) {
797
- switchButton.addEventListener("click", () => this.toggleMode())
798
- }
799
- }
800
-
801
- // 设置图片输入事件
802
- if (imageInputId) {
803
- const imageInput = document.getElementById(
804
- imageInputId
805
- ) as HTMLInputElement
806
- if (imageInput) {
807
- imageInput.addEventListener("change", (e) => this.handleImageInput(e))
808
- }
809
- }
143
+ this.logger.error('IDScanner', 'IDScanner资源释放失败', error as Error);
144
+ throw new Error(`IDScanner资源释放失败: ${error instanceof Error ? error.message : String(error)}`);
810
145
  }
811
-
812
- /**
813
- * 初始化扫描器
814
- */
815
- async initialize(): Promise<void> {
816
- try {
817
- // 初始化身份证识别引擎
818
- await this.scanner.initialize()
819
-
820
- // 默认启动二维码扫描
821
- await this.startQRMode()
822
- } catch (error) {
823
- this.handleError(error as Error)
824
- }
825
- }
826
-
827
- /**
828
- * 切换扫描模式
829
- */
830
- async toggleMode(): Promise<void> {
831
- try {
832
- this.scanner.stop()
833
-
834
- if (this.currentMode === "qr") {
835
- this.currentMode = "idcard"
836
- await this.startIDCardMode()
837
- } else {
838
- this.currentMode = "qr"
839
- await this.startQRMode()
840
- }
841
- } catch (error) {
842
- this.handleError(error as Error)
843
- }
844
- }
845
-
846
- /**
847
- * 启动二维码扫描模式
848
- */
849
- private async startQRMode(): Promise<void> {
850
- try {
851
- this.updateResultDisplay("等待扫描二维码...")
852
- await this.scanner.startQRScanner(this.videoElement)
853
- } catch (error) {
854
- this.handleError(error as Error)
855
- }
856
- }
857
-
858
- /**
859
- * 启动身份证扫描模式
860
- */
861
- private async startIDCardMode(): Promise<void> {
862
- try {
863
- this.updateResultDisplay("等待扫描身份证...")
864
- await this.scanner.startIDCardScanner(this.videoElement)
865
- } catch (error) {
866
- this.handleError(error as Error)
867
- }
868
- }
869
-
870
- /**
871
- * 处理图片输入
872
- */
873
- private async handleImageInput(event: Event): Promise<void> {
874
- try {
875
- const input = event.target as HTMLInputElement
876
-
877
- if (!input.files || input.files.length === 0) {
878
- return
879
- }
880
-
881
- const file = input.files[0]
882
- this.updateResultDisplay("正在处理图片...")
883
-
884
- // 根据当前模式处理图片
885
- if (this.currentMode === "qr") {
886
- const result = await this.scanner.processQRCodeImage(file)
887
- this.handleScanResult(result)
888
- } else {
889
- const info = await this.scanner.processIDCardImage(file)
890
- this.handleIDCardResult(info)
891
- }
892
- } catch (error) {
893
- this.handleError(error as Error)
894
- }
895
- }
896
-
897
- /**
898
- * 处理扫描结果
899
- */
900
- private handleScanResult(result: string): void {
901
- this.updateResultDisplay(`
902
- <h3>扫描结果:</h3>
903
- <p>${result}</p>
904
- `)
905
- }
906
-
907
- /**
908
- * 处理身份证识别结果
909
- */
910
- private handleIDCardResult(info: IDCardInfo): void {
911
- // 格式化显示身份证信息
912
- const infoHtml = Object.entries(info)
913
- .filter(([key, value]) => value && key !== "antiFakeResult") // 过滤掉空值和防伪结果
914
- .map(([key, value]) => {
915
- // 转换键名为中文显示
916
- const keyMap: { [key: string]: string } = {
917
- name: "姓名",
918
- gender: "性别",
919
- nationality: "民族",
920
- birthDate: "出生日期",
921
- address: "地址",
922
- idNumber: "身份证号",
923
- issuingAuthority: "签发机关",
924
- validPeriod: "有效期限",
925
- }
926
-
927
- const displayKey = keyMap[key] || key
928
- return `<div><strong>${displayKey}:</strong> ${value}</div>`
929
- })
930
- .join("")
931
-
932
- // 检查是否有防伪检测结果
933
- let antiFakeHtml = ""
934
- const anyInfo = info as any
935
- if (anyInfo.antiFakeResult) {
936
- const antiFakeResult = anyInfo.antiFakeResult
937
- antiFakeHtml = `
938
- <h3>防伪检测结果:</h3>
939
- <div style="color: ${antiFakeResult.isAuthentic ? "green" : "red"}">
940
- ${
941
- antiFakeResult.isAuthentic
942
- ? "✓ 身份证真实"
943
- : "⚠ 警告:可能为伪造证件"
944
- }
945
- </div>
946
- <div>置信度: ${(antiFakeResult.confidence * 100).toFixed(1)}%</div>
947
- <div>检测到的特征: ${
948
- antiFakeResult.detectedFeatures.join(", ") || "无"
949
- }</div>
950
- <div>${antiFakeResult.message}</div>
951
- `
952
- }
953
-
954
- this.updateResultDisplay(`
955
- <h3>身份证信息:</h3>
956
- ${infoHtml}
957
- ${antiFakeHtml}
958
- `)
959
- }
960
-
961
- /**
962
- * 处理错误
963
- */
964
- private handleError(error: Error): void {
965
- console.error("识别错误:", error)
966
- this.updateResultDisplay(`
967
- <div class="error">
968
- <h3>错误:</h3>
969
- <p>${error.message}</p>
970
- </div>
971
- `)
972
- }
973
-
974
- /**
975
- * 更新结果显示
976
- */
977
- private updateResultDisplay(html: string): void {
978
- if (this.resultElement) {
979
- this.resultElement.innerHTML = html
980
- }
981
- }
982
-
983
- /**
984
- * 停止扫描
985
- */
986
- stop(): void {
987
- this.scanner.stop()
988
146
  }
989
147
  }
148
+
149
+ // 导出核心模块
150
+ export * from './core/loading-state';
151
+ export * from './core/module-manager';
152
+ export * from './core/logger';
153
+ export * from './core/errors';
154
+
155
+ // 导出功能模块 (明确导出以避免命名冲突)
156
+ export { IDCardModule } from './modules/id-card';
157
+ export { QRCodeModule } from './modules/qrcode';
158
+ export { FaceModule } from './modules/face';
159
+
160
+ // 导出类型
161
+ export * from './utils/types';
162
+ export { IDCardModuleOptions, IDCardInfo, IDCardType, IDCardVerificationResult } from './modules/id-card/types';
163
+ export { QRCodeModuleOptions, QRCodeResult, BarcodeFormat, DEFAULT_FORMATS } from './modules/qrcode/types';
164
+ export { FaceModuleOptions, FaceDetectionResult, FaceComparisonResult } from './modules/face/types';
165
+
166
+ // 默认导出IDScanner类
167
+ export default IDScanner;