id-scanner-lib 1.3.2 → 1.5.0

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 (115) hide show
  1. package/README.md +55 -460
  2. package/dist/id-scanner-lib.esm.js +4641 -0
  3. package/dist/id-scanner-lib.esm.js.map +1 -0
  4. package/dist/id-scanner-lib.js +14755 -0
  5. package/dist/id-scanner-lib.js.map +1 -0
  6. package/dist/types/core/base-module.d.ts +44 -0
  7. package/dist/types/core/camera-manager.d.ts +258 -0
  8. package/dist/types/core/config.d.ts +88 -0
  9. package/dist/types/core/errors.d.ts +111 -0
  10. package/dist/types/core/event-emitter.d.ts +55 -0
  11. package/dist/types/core/logger.d.ts +277 -0
  12. package/dist/types/core/module-manager.d.ts +78 -0
  13. package/dist/types/core/plugin-manager.d.ts +158 -0
  14. package/dist/types/core/resource-manager.d.ts +246 -0
  15. package/dist/types/core/result.d.ts +83 -0
  16. package/dist/types/core/scanner-factory.d.ts +93 -0
  17. package/dist/types/index.bundle.d.ts +1303 -0
  18. package/dist/types/index.d.ts +86 -0
  19. package/dist/types/interfaces/external-types.d.ts +174 -0
  20. package/dist/types/interfaces/face-detection.d.ts +293 -0
  21. package/dist/types/interfaces/scanner-module.d.ts +280 -0
  22. package/dist/types/modules/face/face-detector.d.ts +170 -0
  23. package/dist/types/modules/face/index.d.ts +56 -0
  24. package/dist/types/modules/face/liveness-detector.d.ts +177 -0
  25. package/dist/types/modules/face/types.d.ts +136 -0
  26. package/dist/types/modules/id-card/anti-fake-detector.d.ts +170 -0
  27. package/dist/types/modules/id-card/id-card-detector.d.ts +131 -0
  28. package/dist/types/modules/id-card/index.d.ts +89 -0
  29. package/dist/types/modules/id-card/ocr-processor.d.ts +110 -0
  30. package/dist/types/modules/id-card/ocr-worker.d.ts +31 -0
  31. package/dist/types/modules/id-card/types.d.ts +181 -0
  32. package/dist/types/modules/qrcode/index.d.ts +51 -0
  33. package/dist/types/modules/qrcode/qr-code-scanner.d.ts +64 -0
  34. package/dist/types/modules/qrcode/types.d.ts +67 -0
  35. package/dist/types/utils/camera.d.ts +81 -0
  36. package/dist/types/utils/image-processing.d.ts +176 -0
  37. package/dist/types/utils/index.d.ts +175 -0
  38. package/dist/types/utils/performance.d.ts +81 -0
  39. package/dist/types/utils/resource-manager.d.ts +53 -0
  40. package/dist/types/utils/types.d.ts +166 -0
  41. package/dist/types/utils/worker.d.ts +52 -0
  42. package/dist/types/version.d.ts +7 -0
  43. package/package.json +76 -77
  44. package/src/core/base-module.ts +78 -0
  45. package/src/core/camera-manager.ts +798 -0
  46. package/src/core/config.ts +268 -0
  47. package/src/core/errors.ts +174 -0
  48. package/src/core/event-emitter.ts +110 -0
  49. package/src/core/logger.ts +549 -0
  50. package/src/core/module-manager.ts +165 -0
  51. package/src/core/plugin-manager.ts +429 -0
  52. package/src/core/resource-manager.ts +762 -0
  53. package/src/core/result.ts +163 -0
  54. package/src/core/scanner-factory.ts +237 -0
  55. package/src/index.ts +113 -936
  56. package/src/interfaces/external-types.ts +200 -0
  57. package/src/interfaces/face-detection.ts +309 -0
  58. package/src/interfaces/scanner-module.ts +384 -0
  59. package/src/modules/face/face-detector.ts +931 -0
  60. package/src/modules/face/index.ts +208 -0
  61. package/src/modules/face/liveness-detector.ts +908 -0
  62. package/src/modules/face/types.ts +133 -0
  63. package/src/modules/id-card/anti-fake-detector.ts +732 -0
  64. package/src/modules/id-card/id-card-detector.ts +474 -0
  65. package/src/modules/id-card/index.ts +425 -0
  66. package/src/modules/id-card/ocr-processor.ts +538 -0
  67. package/src/modules/id-card/ocr-worker.ts +259 -0
  68. package/src/modules/id-card/types.ts +178 -0
  69. package/src/modules/qrcode/index.ts +175 -0
  70. package/src/modules/qrcode/qr-code-scanner.ts +230 -0
  71. package/src/modules/qrcode/types.ts +65 -0
  72. package/src/types/browser-image-compression.d.ts +19 -0
  73. package/src/types/tesseract.d.ts +280 -0
  74. package/src/utils/image-processing.ts +432 -49
  75. package/src/utils/index.ts +426 -0
  76. package/src/utils/performance.ts +168 -131
  77. package/src/utils/resource-manager.ts +65 -146
  78. package/src/utils/types.ts +90 -2
  79. package/src/utils/worker.ts +123 -84
  80. package/src/version.ts +11 -0
  81. package/tools/scaffold.js +543 -0
  82. package/dist/id-scanner-core.esm.js +0 -11076
  83. package/dist/id-scanner-core.esm.js.map +0 -1
  84. package/dist/id-scanner-core.js +0 -11088
  85. package/dist/id-scanner-core.js.map +0 -1
  86. package/dist/id-scanner-core.min.js +0 -1
  87. package/dist/id-scanner-core.min.js.map +0 -1
  88. package/dist/id-scanner-ocr.esm.js +0 -1802
  89. package/dist/id-scanner-ocr.esm.js.map +0 -1
  90. package/dist/id-scanner-ocr.js +0 -1811
  91. package/dist/id-scanner-ocr.js.map +0 -1
  92. package/dist/id-scanner-ocr.min.js +0 -1
  93. package/dist/id-scanner-ocr.min.js.map +0 -1
  94. package/dist/id-scanner-qr.esm.js +0 -1023
  95. package/dist/id-scanner-qr.esm.js.map +0 -1
  96. package/dist/id-scanner-qr.js +0 -1032
  97. package/dist/id-scanner-qr.js.map +0 -1
  98. package/dist/id-scanner-qr.min.js +0 -1
  99. package/dist/id-scanner-qr.min.js.map +0 -1
  100. package/dist/id-scanner.js +0 -3740
  101. package/dist/id-scanner.js.map +0 -1
  102. package/dist/id-scanner.min.js +0 -1
  103. package/dist/id-scanner.min.js.map +0 -1
  104. package/src/core.ts +0 -138
  105. package/src/demo/demo.ts +0 -204
  106. package/src/id-recognition/anti-fake-detector.ts +0 -317
  107. package/src/id-recognition/data-extractor.ts +0 -262
  108. package/src/id-recognition/id-detector.ts +0 -363
  109. package/src/id-recognition/ocr-processor.ts +0 -334
  110. package/src/id-recognition/ocr-worker.ts +0 -156
  111. package/src/index-umd.ts +0 -477
  112. package/src/ocr-module.ts +0 -187
  113. package/src/qr-module.ts +0 -179
  114. package/src/scanner/barcode-scanner.ts +0 -251
  115. package/src/scanner/qr-scanner.ts +0 -167
@@ -1,1802 +0,0 @@
1
- import { createWorker as createWorker$1 } from 'tesseract.js';
2
-
3
- /**
4
- * @file 相机工具类
5
- * @description 提供访问和控制设备摄像头的功能
6
- * @module Camera
7
- */
8
- /**
9
- * 相机工具类
10
- *
11
- * 提供访问设备摄像头、获取视频流以及捕获图像帧的功能
12
- *
13
- * @example
14
- * ```typescript
15
- * // 创建相机实例
16
- * const camera = new Camera({
17
- * width: 1280,
18
- * height: 720,
19
- * facingMode: 'environment' // 使用后置摄像头
20
- * });
21
- *
22
- * // 初始化相机
23
- * const videoElement = document.getElementById('video') as HTMLVideoElement;
24
- * await camera.start(videoElement);
25
- *
26
- * // 捕获当前视频帧
27
- * const imageData = camera.captureFrame();
28
- *
29
- * // 使用结束后释放资源
30
- * camera.stop();
31
- * ```
32
- */
33
- class Camera {
34
- /**
35
- * 创建相机实例
36
- * @param {CameraOptions} [options] - 相机配置选项
37
- */
38
- constructor(options = {}) {
39
- this.options = options;
40
- this.stream = null;
41
- this.videoElement = null;
42
- this.options = {
43
- width: 640,
44
- height: 480,
45
- facingMode: 'environment',
46
- ...options
47
- };
48
- }
49
- /**
50
- * 启动摄像头并将视频流绑定到视频元素
51
- * @param videoElement HTML视频元素
52
- * @returns Promise<void>
53
- */
54
- async start(videoElement) {
55
- return this.initialize(videoElement);
56
- }
57
- /**
58
- * 停止摄像头并释放资源
59
- */
60
- stop() {
61
- this.release();
62
- }
63
- /**
64
- * 初始化相机,获取视频流并绑定到视频元素
65
- *
66
- * @param {HTMLVideoElement} videoElement - 用于显示视频流的视频元素
67
- * @returns {Promise<void>} 初始化完成的Promise
68
- * @throws 如果无法访问摄像头,将抛出错误
69
- */
70
- async initialize(videoElement) {
71
- this.videoElement = videoElement;
72
- try {
73
- // 构建媒体约束
74
- const constraints = {
75
- video: {
76
- width: { ideal: this.options.width },
77
- height: { ideal: this.options.height },
78
- facingMode: this.options.facingMode
79
- }
80
- };
81
- // 获取视频流
82
- this.stream = await navigator.mediaDevices.getUserMedia(constraints);
83
- // 绑定到视频元素
84
- if (this.videoElement) {
85
- this.videoElement.srcObject = this.stream;
86
- await new Promise((resolve) => {
87
- if (this.videoElement) {
88
- this.videoElement.onloadedmetadata = () => {
89
- if (this.videoElement) {
90
- this.videoElement.play().then(() => resolve());
91
- }
92
- };
93
- }
94
- });
95
- }
96
- }
97
- catch (error) {
98
- console.error('无法访问摄像头:', error);
99
- throw new Error('无法访问摄像头。请确保已授予摄像头访问权限,并且摄像头未被其他应用程序占用。');
100
- }
101
- }
102
- /**
103
- * 捕获当前视频帧
104
- *
105
- * @returns {ImageData|null} 视频帧的ImageData对象,如果未初始化则返回null
106
- */
107
- captureFrame() {
108
- if (!this.videoElement) {
109
- return null;
110
- }
111
- // 创建Canvas元素用于捕获视频帧
112
- const canvas = document.createElement('canvas');
113
- canvas.width = this.videoElement.videoWidth;
114
- canvas.height = this.videoElement.videoHeight;
115
- const context = canvas.getContext('2d');
116
- if (!context) {
117
- return null;
118
- }
119
- // 将视频内容绘制到Canvas中
120
- context.drawImage(this.videoElement, 0, 0, canvas.width, canvas.height);
121
- // 获取ImageData对象
122
- return context.getImageData(0, 0, canvas.width, canvas.height);
123
- }
124
- /**
125
- * 释放摄像头资源
126
- */
127
- release() {
128
- // 停止视频流的所有轨道
129
- if (this.stream) {
130
- this.stream.getTracks().forEach(track => track.stop());
131
- this.stream = null;
132
- }
133
- // 清除视频元素绑定
134
- if (this.videoElement) {
135
- this.videoElement.srcObject = null;
136
- this.videoElement = null;
137
- }
138
- }
139
- }
140
-
141
- /**
142
- * Browser Image Compression
143
- * v2.0.2
144
- * by Donald <donaldcwl@gmail.com>
145
- * https://github.com/Donaldcwl/browser-image-compression
146
- */
147
-
148
- function _mergeNamespaces(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(r){if("default"!==r&&!(r in e)){var i=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,i.get?i:{enumerable:true,get:function(){return t[r]}});}}));})),Object.freeze(e)}function copyExifWithoutOrientation(e,t){return new Promise((function(r,i){let o;return getApp1Segment(e).then((function(e){try{return o=e,r(new Blob([t.slice(0,2),o,t.slice(2)],{type:"image/jpeg"}))}catch(e){return i(e)}}),i)}))}const getApp1Segment=e=>new Promise(((t,r)=>{const i=new FileReader;i.addEventListener("load",(({target:{result:e}})=>{const i=new DataView(e);let o=0;if(65496!==i.getUint16(o))return r("not a valid JPEG");for(o+=2;;){const a=i.getUint16(o);if(65498===a)break;const s=i.getUint16(o+2);if(65505===a&&1165519206===i.getUint32(o+4)){const a=o+10;let f;switch(i.getUint16(a)){case 18761:f=true;break;case 19789:f=false;break;default:return r("TIFF header contains invalid endian")}if(42!==i.getUint16(a+2,f))return r("TIFF header contains invalid version");const l=i.getUint32(a+4,f),c=a+l+2+12*i.getUint16(a+l,f);for(let e=a+l+2;e<c;e+=12){if(274==i.getUint16(e,f)){if(3!==i.getUint16(e+2,f))return r("Orientation data type is invalid");if(1!==i.getUint32(e+4,f))return r("Orientation data count is invalid");i.setUint16(e+8,1,f);break}}return t(e.slice(o,o+2+s))}o+=2+s;}return t(new Blob)})),i.readAsArrayBuffer(e);}));var e={},t={get exports(){return e},set exports(t){e=t;}};!function(e){var r,i,UZIP={};t.exports=UZIP,UZIP.parse=function(e,t){for(var r=UZIP.bin.readUshort,i=UZIP.bin.readUint,o=0,a={},s=new Uint8Array(e),f=s.length-4;101010256!=i(s,f);)f--;o=f;o+=4;var l=r(s,o+=4);r(s,o+=2);var c=i(s,o+=2),u=i(s,o+=4);o+=4,o=u;for(var h=0;h<l;h++){i(s,o),o+=4,o+=4,o+=4,i(s,o+=4);c=i(s,o+=4);var d=i(s,o+=4),A=r(s,o+=4),g=r(s,o+2),p=r(s,o+4);o+=6;var m=i(s,o+=8);o+=4,o+=A+g+p,UZIP._readLocal(s,m,a,c,d,t);}return a},UZIP._readLocal=function(e,t,r,i,o,a){var s=UZIP.bin.readUshort,f=UZIP.bin.readUint;f(e,t),s(e,t+=4),s(e,t+=2);var l=s(e,t+=2);f(e,t+=2),f(e,t+=4),t+=4;var c=s(e,t+=8),u=s(e,t+=2);t+=2;var h=UZIP.bin.readUTF8(e,t,c);if(t+=c,t+=u,a)r[h]={size:o,csize:i};else {var d=new Uint8Array(e.buffer,t);if(0==l)r[h]=new Uint8Array(d.buffer.slice(t,t+i));else {if(8!=l)throw "unknown compression method: "+l;var A=new Uint8Array(o);UZIP.inflateRaw(d,A),r[h]=A;}}},UZIP.inflateRaw=function(e,t){return UZIP.F.inflate(e,t)},UZIP.inflate=function(e,t){return UZIP.inflateRaw(new Uint8Array(e.buffer,e.byteOffset+2,e.length-6),t)},UZIP.deflate=function(e,t){null==t&&(t={level:6});var r=0,i=new Uint8Array(50+Math.floor(1.1*e.length));i[r]=120,i[r+1]=156,r+=2,r=UZIP.F.deflateRaw(e,i,r,t.level);var o=UZIP.adler(e,0,e.length);return i[r+0]=o>>>24&255,i[r+1]=o>>>16&255,i[r+2]=o>>>8&255,i[r+3]=o>>>0&255,new Uint8Array(i.buffer,0,r+4)},UZIP.deflateRaw=function(e,t){null==t&&(t={level:6});var r=new Uint8Array(50+Math.floor(1.1*e.length)),i=UZIP.F.deflateRaw(e,r,i,t.level);return new Uint8Array(r.buffer,0,i)},UZIP.encode=function(e,t){null==t&&(t=false);var r=0,i=UZIP.bin.writeUint,o=UZIP.bin.writeUshort,a={};for(var s in e){var f=!UZIP._noNeed(s)&&!t,l=e[s],c=UZIP.crc.crc(l,0,l.length);a[s]={cpr:f,usize:l.length,crc:c,file:f?UZIP.deflateRaw(l):l};}for(var s in a)r+=a[s].file.length+30+46+2*UZIP.bin.sizeUTF8(s);r+=22;var u=new Uint8Array(r),h=0,d=[];for(var s in a){var A=a[s];d.push(h),h=UZIP._writeHeader(u,h,s,A,0);}var g=0,p=h;for(var s in a){A=a[s];d.push(h),h=UZIP._writeHeader(u,h,s,A,1,d[g++]);}var m=h-p;return i(u,h,101010256),h+=4,o(u,h+=4,g),o(u,h+=2,g),i(u,h+=2,m),i(u,h+=4,p),h+=4,h+=2,u.buffer},UZIP._noNeed=function(e){var t=e.split(".").pop().toLowerCase();return -1!="png,jpg,jpeg,zip".indexOf(t)},UZIP._writeHeader=function(e,t,r,i,o,a){var s=UZIP.bin.writeUint,f=UZIP.bin.writeUshort,l=i.file;return s(e,t,0==o?67324752:33639248),t+=4,1==o&&(t+=2),f(e,t,20),f(e,t+=2,0),f(e,t+=2,i.cpr?8:0),s(e,t+=2,0),s(e,t+=4,i.crc),s(e,t+=4,l.length),s(e,t+=4,i.usize),f(e,t+=4,UZIP.bin.sizeUTF8(r)),f(e,t+=2,0),t+=2,1==o&&(t+=2,t+=2,s(e,t+=6,a),t+=4),t+=UZIP.bin.writeUTF8(e,t,r),0==o&&(e.set(l,t),t+=l.length),t},UZIP.crc={table:function(){for(var e=new Uint32Array(256),t=0;t<256;t++){for(var r=t,i=0;i<8;i++)1&r?r=3988292384^r>>>1:r>>>=1;e[t]=r;}return e}(),update:function(e,t,r,i){for(var o=0;o<i;o++)e=UZIP.crc.table[255&(e^t[r+o])]^e>>>8;return e},crc:function(e,t,r){return 4294967295^UZIP.crc.update(4294967295,e,t,r)}},UZIP.adler=function(e,t,r){for(var i=1,o=0,a=t,s=t+r;a<s;){for(var f=Math.min(a+5552,s);a<f;)o+=i+=e[a++];i%=65521,o%=65521;}return o<<16|i},UZIP.bin={readUshort:function(e,t){return e[t]|e[t+1]<<8},writeUshort:function(e,t,r){e[t]=255&r,e[t+1]=r>>8&255;},readUint:function(e,t){return 16777216*e[t+3]+(e[t+2]<<16|e[t+1]<<8|e[t])},writeUint:function(e,t,r){e[t]=255&r,e[t+1]=r>>8&255,e[t+2]=r>>16&255,e[t+3]=r>>24&255;},readASCII:function(e,t,r){for(var i="",o=0;o<r;o++)i+=String.fromCharCode(e[t+o]);return i},writeASCII:function(e,t,r){for(var i=0;i<r.length;i++)e[t+i]=r.charCodeAt(i);},pad:function(e){return e.length<2?"0"+e:e},readUTF8:function(e,t,r){for(var i,o="",a=0;a<r;a++)o+="%"+UZIP.bin.pad(e[t+a].toString(16));try{i=decodeURIComponent(o);}catch(i){return UZIP.bin.readASCII(e,t,r)}return i},writeUTF8:function(e,t,r){for(var i=r.length,o=0,a=0;a<i;a++){var s=r.charCodeAt(a);if(0==(4294967168&s))e[t+o]=s,o++;else if(0==(4294965248&s))e[t+o]=192|s>>6,e[t+o+1]=128|s>>0&63,o+=2;else if(0==(4294901760&s))e[t+o]=224|s>>12,e[t+o+1]=128|s>>6&63,e[t+o+2]=128|s>>0&63,o+=3;else {if(0!=(4292870144&s))throw "e";e[t+o]=240|s>>18,e[t+o+1]=128|s>>12&63,e[t+o+2]=128|s>>6&63,e[t+o+3]=128|s>>0&63,o+=4;}}return o},sizeUTF8:function(e){for(var t=e.length,r=0,i=0;i<t;i++){var o=e.charCodeAt(i);if(0==(4294967168&o))r++;else if(0==(4294965248&o))r+=2;else if(0==(4294901760&o))r+=3;else {if(0!=(4292870144&o))throw "e";r+=4;}}return r}},UZIP.F={},UZIP.F.deflateRaw=function(e,t,r,i){var o=[[0,0,0,0,0],[4,4,8,4,0],[4,5,16,8,0],[4,6,16,16,0],[4,10,16,32,0],[8,16,32,32,0],[8,16,128,128,0],[8,32,128,256,0],[32,128,258,1024,1],[32,258,258,4096,1]][i],a=UZIP.F.U,s=UZIP.F._goodIndex;var f=UZIP.F._putsE,l=0,c=r<<3,u=0,h=e.length;if(0==i){for(;l<h;){f(t,c,l+(_=Math.min(65535,h-l))==h?1:0),c=UZIP.F._copyExact(e,l,_,t,c+8),l+=_;}return c>>>3}var d=a.lits,A=a.strt,g=a.prev,p=0,m=0,w=0,v=0,b=0,y=0;for(h>2&&(A[y=UZIP.F._hash(e,0)]=0),l=0;l<h;l++){if(b=y,l+1<h-2){y=UZIP.F._hash(e,l+1);var E=l+1&32767;g[E]=A[y],A[y]=E;}if(u<=l){(p>14e3||m>26697)&&h-l>100&&(u<l&&(d[p]=l-u,p+=2,u=l),c=UZIP.F._writeBlock(l==h-1||u==h?1:0,d,p,v,e,w,l-w,t,c),p=m=v=0,w=l);var F=0;l<h-2&&(F=UZIP.F._bestMatch(e,l,g,b,Math.min(o[2],h-l),o[3]));var _=F>>>16,B=65535&F;if(0!=F){B=65535&F;var U=s(_=F>>>16,a.of0);a.lhst[257+U]++;var C=s(B,a.df0);a.dhst[C]++,v+=a.exb[U]+a.dxb[C],d[p]=_<<23|l-u,d[p+1]=B<<16|U<<8|C,p+=2,u=l+_;}else a.lhst[e[l]]++;m++;}}for(w==l&&0!=e.length||(u<l&&(d[p]=l-u,p+=2,u=l),c=UZIP.F._writeBlock(1,d,p,v,e,w,l-w,t,c),p=0,m=0,p=m=v=0,w=l);0!=(7&c);)c++;return c>>>3},UZIP.F._bestMatch=function(e,t,r,i,o,a){var s=32767&t,f=r[s],l=s-f+32768&32767;if(f==s||i!=UZIP.F._hash(e,t-l))return 0;for(var c=0,u=0,h=Math.min(32767,t);l<=h&&0!=--a&&f!=s;){if(0==c||e[t+c]==e[t+c-l]){var d=UZIP.F._howLong(e,t,l);if(d>c){if(u=l,(c=d)>=o)break;l+2<d&&(d=l+2);for(var A=0,g=0;g<d-2;g++){var p=t-l+g+32768&32767,m=p-r[p]+32768&32767;m>A&&(A=m,f=p);}}}l+=(s=f)-(f=r[s])+32768&32767;}return c<<16|u},UZIP.F._howLong=function(e,t,r){if(e[t]!=e[t-r]||e[t+1]!=e[t+1-r]||e[t+2]!=e[t+2-r])return 0;var i=t,o=Math.min(e.length,t+258);for(t+=3;t<o&&e[t]==e[t-r];)t++;return t-i},UZIP.F._hash=function(e,t){return (e[t]<<8|e[t+1])+(e[t+2]<<4)&65535},UZIP.saved=0,UZIP.F._writeBlock=function(e,t,r,i,o,a,s,f,l){var c,u,h,d,A,g,p,m,w,v=UZIP.F.U,b=UZIP.F._putsF,y=UZIP.F._putsE;v.lhst[256]++,u=(c=UZIP.F.getTrees())[0],h=c[1],d=c[2],A=c[3],g=c[4],p=c[5],m=c[6],w=c[7];var E=32+(0==(l+3&7)?0:8-(l+3&7))+(s<<3),F=i+UZIP.F.contSize(v.fltree,v.lhst)+UZIP.F.contSize(v.fdtree,v.dhst),_=i+UZIP.F.contSize(v.ltree,v.lhst)+UZIP.F.contSize(v.dtree,v.dhst);_+=14+3*p+UZIP.F.contSize(v.itree,v.ihst)+(2*v.ihst[16]+3*v.ihst[17]+7*v.ihst[18]);for(var B=0;B<286;B++)v.lhst[B]=0;for(B=0;B<30;B++)v.dhst[B]=0;for(B=0;B<19;B++)v.ihst[B]=0;var U=E<F&&E<_?0:F<_?1:2;if(b(f,l,e),b(f,l+1,U),l+=3,0==U){for(;0!=(7&l);)l++;l=UZIP.F._copyExact(o,a,s,f,l);}else {var C,I;if(1==U&&(C=v.fltree,I=v.fdtree),2==U){UZIP.F.makeCodes(v.ltree,u),UZIP.F.revCodes(v.ltree,u),UZIP.F.makeCodes(v.dtree,h),UZIP.F.revCodes(v.dtree,h),UZIP.F.makeCodes(v.itree,d),UZIP.F.revCodes(v.itree,d),C=v.ltree,I=v.dtree,y(f,l,A-257),y(f,l+=5,g-1),y(f,l+=5,p-4),l+=4;for(var Q=0;Q<p;Q++)y(f,l+3*Q,v.itree[1+(v.ordr[Q]<<1)]);l+=3*p,l=UZIP.F._codeTiny(m,v.itree,f,l),l=UZIP.F._codeTiny(w,v.itree,f,l);}for(var M=a,x=0;x<r;x+=2){for(var S=t[x],R=S>>>23,T=M+(8388607&S);M<T;)l=UZIP.F._writeLit(o[M++],C,f,l);if(0!=R){var O=t[x+1],P=O>>16,H=O>>8&255,L=255&O;y(f,l=UZIP.F._writeLit(257+H,C,f,l),R-v.of0[H]),l+=v.exb[H],b(f,l=UZIP.F._writeLit(L,I,f,l),P-v.df0[L]),l+=v.dxb[L],M+=R;}}l=UZIP.F._writeLit(256,C,f,l);}return l},UZIP.F._copyExact=function(e,t,r,i,o){var a=o>>>3;return i[a]=r,i[a+1]=r>>>8,i[a+2]=255-i[a],i[a+3]=255-i[a+1],a+=4,i.set(new Uint8Array(e.buffer,t,r),a),o+(r+4<<3)},UZIP.F.getTrees=function(){for(var e=UZIP.F.U,t=UZIP.F._hufTree(e.lhst,e.ltree,15),r=UZIP.F._hufTree(e.dhst,e.dtree,15),i=[],o=UZIP.F._lenCodes(e.ltree,i),a=[],s=UZIP.F._lenCodes(e.dtree,a),f=0;f<i.length;f+=2)e.ihst[i[f]]++;for(f=0;f<a.length;f+=2)e.ihst[a[f]]++;for(var l=UZIP.F._hufTree(e.ihst,e.itree,7),c=19;c>4&&0==e.itree[1+(e.ordr[c-1]<<1)];)c--;return [t,r,l,o,s,c,i,a]},UZIP.F.getSecond=function(e){for(var t=[],r=0;r<e.length;r+=2)t.push(e[r+1]);return t},UZIP.F.nonZero=function(e){for(var t="",r=0;r<e.length;r+=2)0!=e[r+1]&&(t+=(r>>1)+",");return t},UZIP.F.contSize=function(e,t){for(var r=0,i=0;i<t.length;i++)r+=t[i]*e[1+(i<<1)];return r},UZIP.F._codeTiny=function(e,t,r,i){for(var o=0;o<e.length;o+=2){var a=e[o],s=e[o+1];i=UZIP.F._writeLit(a,t,r,i);var f=16==a?2:17==a?3:7;a>15&&(UZIP.F._putsE(r,i,s,f),i+=f);}return i},UZIP.F._lenCodes=function(e,t){for(var r=e.length;2!=r&&0==e[r-1];)r-=2;for(var i=0;i<r;i+=2){var o=e[i+1],a=i+3<r?e[i+3]:-1,s=i+5<r?e[i+5]:-1,f=0==i?-1:e[i-1];if(0==o&&a==o&&s==o){for(var l=i+5;l+2<r&&e[l+2]==o;)l+=2;(c=Math.min(l+1-i>>>1,138))<11?t.push(17,c-3):t.push(18,c-11),i+=2*c-2;}else if(o==f&&a==o&&s==o){for(l=i+5;l+2<r&&e[l+2]==o;)l+=2;var c=Math.min(l+1-i>>>1,6);t.push(16,c-3),i+=2*c-2;}else t.push(o,0);}return r>>>1},UZIP.F._hufTree=function(e,t,r){var i=[],o=e.length,a=t.length,s=0;for(s=0;s<a;s+=2)t[s]=0,t[s+1]=0;for(s=0;s<o;s++)0!=e[s]&&i.push({lit:s,f:e[s]});var f=i.length,l=i.slice(0);if(0==f)return 0;if(1==f){var c=i[0].lit;l=0==c?1:0;return t[1+(c<<1)]=1,t[1+(l<<1)]=1,1}i.sort((function(e,t){return e.f-t.f}));var u=i[0],h=i[1],d=0,A=1,g=2;for(i[0]={lit:-1,f:u.f+h.f,l:u,r:h,d:0};A!=f-1;)u=d!=A&&(g==f||i[d].f<i[g].f)?i[d++]:i[g++],h=d!=A&&(g==f||i[d].f<i[g].f)?i[d++]:i[g++],i[A++]={lit:-1,f:u.f+h.f,l:u,r:h};var p=UZIP.F.setDepth(i[A-1],0);for(p>r&&(UZIP.F.restrictDepth(l,r,p),p=r),s=0;s<f;s++)t[1+(l[s].lit<<1)]=l[s].d;return p},UZIP.F.setDepth=function(e,t){return -1!=e.lit?(e.d=t,t):Math.max(UZIP.F.setDepth(e.l,t+1),UZIP.F.setDepth(e.r,t+1))},UZIP.F.restrictDepth=function(e,t,r){var i=0,o=1<<r-t,a=0;for(e.sort((function(e,t){return t.d==e.d?e.f-t.f:t.d-e.d})),i=0;i<e.length&&e[i].d>t;i++){var s=e[i].d;e[i].d=t,a+=o-(1<<r-s);}for(a>>>=r-t;a>0;){(s=e[i].d)<t?(e[i].d++,a-=1<<t-s-1):i++;}for(;i>=0;i--)e[i].d==t&&a<0&&(e[i].d--,a++);0!=a&&console.log("debt left");},UZIP.F._goodIndex=function(e,t){var r=0;return t[16|r]<=e&&(r|=16),t[8|r]<=e&&(r|=8),t[4|r]<=e&&(r|=4),t[2|r]<=e&&(r|=2),t[1|r]<=e&&(r|=1),r},UZIP.F._writeLit=function(e,t,r,i){return UZIP.F._putsF(r,i,t[e<<1]),i+t[1+(e<<1)]},UZIP.F.inflate=function(e,t){var r=Uint8Array;if(3==e[0]&&0==e[1])return t||new r(0);var i=UZIP.F,o=i._bitsF,a=i._bitsE,s=i._decodeTiny,f=i.makeCodes,l=i.codes2map,c=i._get17,u=i.U,h=null==t;h&&(t=new r(e.length>>>2<<3));for(var d,A,g=0,p=0,m=0,w=0,v=0,b=0,y=0,E=0,F=0;0==g;)if(g=o(e,F,1),p=o(e,F+1,2),F+=3,0!=p){if(h&&(t=UZIP.F._check(t,E+(1<<17))),1==p&&(d=u.flmap,A=u.fdmap,b=511,y=31),2==p){m=a(e,F,5)+257,w=a(e,F+5,5)+1,v=a(e,F+10,4)+4,F+=14;for(var _=0;_<38;_+=2)u.itree[_]=0,u.itree[_+1]=0;var B=1;for(_=0;_<v;_++){var U=a(e,F+3*_,3);u.itree[1+(u.ordr[_]<<1)]=U,U>B&&(B=U);}F+=3*v,f(u.itree,B),l(u.itree,B,u.imap),d=u.lmap,A=u.dmap,F=s(u.imap,(1<<B)-1,m+w,e,F,u.ttree);var C=i._copyOut(u.ttree,0,m,u.ltree);b=(1<<C)-1;var I=i._copyOut(u.ttree,m,w,u.dtree);y=(1<<I)-1,f(u.ltree,C),l(u.ltree,C,d),f(u.dtree,I),l(u.dtree,I,A);}for(;;){var Q=d[c(e,F)&b];F+=15&Q;var M=Q>>>4;if(M>>>8==0)t[E++]=M;else {if(256==M)break;var x=E+M-254;if(M>264){var S=u.ldef[M-257];x=E+(S>>>3)+a(e,F,7&S),F+=7&S;}var R=A[c(e,F)&y];F+=15&R;var T=R>>>4,O=u.ddef[T],P=(O>>>4)+o(e,F,15&O);for(F+=15&O,h&&(t=UZIP.F._check(t,E+(1<<17)));E<x;)t[E]=t[E++-P],t[E]=t[E++-P],t[E]=t[E++-P],t[E]=t[E++-P];E=x;}}}else {0!=(7&F)&&(F+=8-(7&F));var H=4+(F>>>3),L=e[H-4]|e[H-3]<<8;h&&(t=UZIP.F._check(t,E+L)),t.set(new r(e.buffer,e.byteOffset+H,L),E),F=H+L<<3,E+=L;}return t.length==E?t:t.slice(0,E)},UZIP.F._check=function(e,t){var r=e.length;if(t<=r)return e;var i=new Uint8Array(Math.max(r<<1,t));return i.set(e,0),i},UZIP.F._decodeTiny=function(e,t,r,i,o,a){for(var s=UZIP.F._bitsE,f=UZIP.F._get17,l=0;l<r;){var c=e[f(i,o)&t];o+=15&c;var u=c>>>4;if(u<=15)a[l]=u,l++;else {var h=0,d=0;16==u?(d=3+s(i,o,2),o+=2,h=a[l-1]):17==u?(d=3+s(i,o,3),o+=3):18==u&&(d=11+s(i,o,7),o+=7);for(var A=l+d;l<A;)a[l]=h,l++;}}return o},UZIP.F._copyOut=function(e,t,r,i){for(var o=0,a=0,s=i.length>>>1;a<r;){var f=e[a+t];i[a<<1]=0,i[1+(a<<1)]=f,f>o&&(o=f),a++;}for(;a<s;)i[a<<1]=0,i[1+(a<<1)]=0,a++;return o},UZIP.F.makeCodes=function(e,t){for(var r,i,o,a,s=UZIP.F.U,f=e.length,l=s.bl_count,c=0;c<=t;c++)l[c]=0;for(c=1;c<f;c+=2)l[e[c]]++;var u=s.next_code;for(r=0,l[0]=0,i=1;i<=t;i++)r=r+l[i-1]<<1,u[i]=r;for(o=0;o<f;o+=2)0!=(a=e[o+1])&&(e[o]=u[a],u[a]++);},UZIP.F.codes2map=function(e,t,r){for(var i=e.length,o=UZIP.F.U.rev15,a=0;a<i;a+=2)if(0!=e[a+1])for(var s=a>>1,f=e[a+1],l=s<<4|f,c=t-f,u=e[a]<<c,h=u+(1<<c);u!=h;){r[o[u]>>>15-t]=l,u++;}},UZIP.F.revCodes=function(e,t){for(var r=UZIP.F.U.rev15,i=15-t,o=0;o<e.length;o+=2){var a=e[o]<<t-e[o+1];e[o]=r[a]>>>i;}},UZIP.F._putsE=function(e,t,r){r<<=7&t;var i=t>>>3;e[i]|=r,e[i+1]|=r>>>8;},UZIP.F._putsF=function(e,t,r){r<<=7&t;var i=t>>>3;e[i]|=r,e[i+1]|=r>>>8,e[i+2]|=r>>>16;},UZIP.F._bitsE=function(e,t,r){return (e[t>>>3]|e[1+(t>>>3)]<<8)>>>(7&t)&(1<<r)-1},UZIP.F._bitsF=function(e,t,r){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16)>>>(7&t)&(1<<r)-1},UZIP.F._get17=function(e,t){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16)>>>(7&t)},UZIP.F._get25=function(e,t){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16|e[3+(t>>>3)]<<24)>>>(7&t)},UZIP.F.U=(r=Uint16Array,i=Uint32Array,{next_code:new r(16),bl_count:new r(16),ordr:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],of0:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],exb:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],ldef:new r(32),df0:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],dxb:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],ddef:new i(32),flmap:new r(512),fltree:[],fdmap:new r(32),fdtree:[],lmap:new r(32768),ltree:[],ttree:[],dmap:new r(32768),dtree:[],imap:new r(512),itree:[],rev15:new r(32768),lhst:new i(286),dhst:new i(30),ihst:new i(19),lits:new i(15e3),strt:new r(65536),prev:new r(32768)}),function(){for(var e=UZIP.F.U,t=0;t<32768;t++){var r=t;r=(4278255360&(r=(4042322160&(r=(3435973836&(r=(2863311530&r)>>>1|(1431655765&r)<<1))>>>2|(858993459&r)<<2))>>>4|(252645135&r)<<4))>>>8|(16711935&r)<<8,e.rev15[t]=(r>>>16|r<<16)>>>17;}function pushV(e,t,r){for(;0!=t--;)e.push(0,r);}for(t=0;t<32;t++)e.ldef[t]=e.of0[t]<<3|e.exb[t],e.ddef[t]=e.df0[t]<<4|e.dxb[t];pushV(e.fltree,144,8),pushV(e.fltree,112,9),pushV(e.fltree,24,7),pushV(e.fltree,8,8),UZIP.F.makeCodes(e.fltree,9),UZIP.F.codes2map(e.fltree,9,e.flmap),UZIP.F.revCodes(e.fltree,9),pushV(e.fdtree,32,5),UZIP.F.makeCodes(e.fdtree,5),UZIP.F.codes2map(e.fdtree,5,e.fdmap),UZIP.F.revCodes(e.fdtree,5),pushV(e.itree,19,0),pushV(e.ltree,286,0),pushV(e.dtree,30,0),pushV(e.ttree,320,0);}();}();var UZIP=_mergeNamespaces({__proto__:null,default:e},[e]);const UPNG=function(){var e={nextZero(e,t){for(;0!=e[t];)t++;return t},readUshort:(e,t)=>e[t]<<8|e[t+1],writeUshort(e,t,r){e[t]=r>>8&255,e[t+1]=255&r;},readUint:(e,t)=>16777216*e[t]+(e[t+1]<<16|e[t+2]<<8|e[t+3]),writeUint(e,t,r){e[t]=r>>24&255,e[t+1]=r>>16&255,e[t+2]=r>>8&255,e[t+3]=255&r;},readASCII(e,t,r){let i="";for(let o=0;o<r;o++)i+=String.fromCharCode(e[t+o]);return i},writeASCII(e,t,r){for(let i=0;i<r.length;i++)e[t+i]=r.charCodeAt(i);},readBytes(e,t,r){const i=[];for(let o=0;o<r;o++)i.push(e[t+o]);return i},pad:e=>e.length<2?`0${e}`:e,readUTF8(t,r,i){let o,a="";for(let o=0;o<i;o++)a+=`%${e.pad(t[r+o].toString(16))}`;try{o=decodeURIComponent(a);}catch(o){return e.readASCII(t,r,i)}return o}};function decodeImage(t,r,i,o){const a=r*i,s=_getBPP(o),f=Math.ceil(r*s/8),l=new Uint8Array(4*a),c=new Uint32Array(l.buffer),{ctype:u}=o,{depth:h}=o,d=e.readUshort;if(6==u){const e=a<<2;if(8==h)for(var A=0;A<e;A+=4)l[A]=t[A],l[A+1]=t[A+1],l[A+2]=t[A+2],l[A+3]=t[A+3];if(16==h)for(A=0;A<e;A++)l[A]=t[A<<1];}else if(2==u){const e=o.tabs.tRNS;if(null==e){if(8==h)for(A=0;A<a;A++){var g=3*A;c[A]=255<<24|t[g+2]<<16|t[g+1]<<8|t[g];}if(16==h)for(A=0;A<a;A++){g=6*A;c[A]=255<<24|t[g+4]<<16|t[g+2]<<8|t[g];}}else {var p=e[0];const r=e[1],i=e[2];if(8==h)for(A=0;A<a;A++){var m=A<<2;g=3*A;c[A]=255<<24|t[g+2]<<16|t[g+1]<<8|t[g],t[g]==p&&t[g+1]==r&&t[g+2]==i&&(l[m+3]=0);}if(16==h)for(A=0;A<a;A++){m=A<<2,g=6*A;c[A]=255<<24|t[g+4]<<16|t[g+2]<<8|t[g],d(t,g)==p&&d(t,g+2)==r&&d(t,g+4)==i&&(l[m+3]=0);}}}else if(3==u){const e=o.tabs.PLTE,s=o.tabs.tRNS,c=s?s.length:0;if(1==h)for(var w=0;w<i;w++){var v=w*f,b=w*r;for(A=0;A<r;A++){m=b+A<<2;var y=3*(E=t[v+(A>>3)]>>7-((7&A)<<0)&1);l[m]=e[y],l[m+1]=e[y+1],l[m+2]=e[y+2],l[m+3]=E<c?s[E]:255;}}if(2==h)for(w=0;w<i;w++)for(v=w*f,b=w*r,A=0;A<r;A++){m=b+A<<2,y=3*(E=t[v+(A>>2)]>>6-((3&A)<<1)&3);l[m]=e[y],l[m+1]=e[y+1],l[m+2]=e[y+2],l[m+3]=E<c?s[E]:255;}if(4==h)for(w=0;w<i;w++)for(v=w*f,b=w*r,A=0;A<r;A++){m=b+A<<2,y=3*(E=t[v+(A>>1)]>>4-((1&A)<<2)&15);l[m]=e[y],l[m+1]=e[y+1],l[m+2]=e[y+2],l[m+3]=E<c?s[E]:255;}if(8==h)for(A=0;A<a;A++){var E;m=A<<2,y=3*(E=t[A]);l[m]=e[y],l[m+1]=e[y+1],l[m+2]=e[y+2],l[m+3]=E<c?s[E]:255;}}else if(4==u){if(8==h)for(A=0;A<a;A++){m=A<<2;var F=t[_=A<<1];l[m]=F,l[m+1]=F,l[m+2]=F,l[m+3]=t[_+1];}if(16==h)for(A=0;A<a;A++){var _;m=A<<2,F=t[_=A<<2];l[m]=F,l[m+1]=F,l[m+2]=F,l[m+3]=t[_+2];}}else if(0==u)for(p=o.tabs.tRNS?o.tabs.tRNS:-1,w=0;w<i;w++){const e=w*f,i=w*r;if(1==h)for(var B=0;B<r;B++){var U=(F=255*(t[e+(B>>>3)]>>>7-(7&B)&1))==255*p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}else if(2==h)for(B=0;B<r;B++){U=(F=85*(t[e+(B>>>2)]>>>6-((3&B)<<1)&3))==85*p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}else if(4==h)for(B=0;B<r;B++){U=(F=17*(t[e+(B>>>1)]>>>4-((1&B)<<2)&15))==17*p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}else if(8==h)for(B=0;B<r;B++){U=(F=t[e+B])==p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}else if(16==h)for(B=0;B<r;B++){F=t[e+(B<<1)],U=d(t,e+(B<<1))==p?0:255;c[i+B]=U<<24|F<<16|F<<8|F;}}return l}function _decompress(e,r,i,o){const a=_getBPP(e),s=Math.ceil(i*a/8),f=new Uint8Array((s+1+e.interlace)*o);return r=e.tabs.CgBI?t(r,f):_inflate(r,f),0==e.interlace?r=_filterZero(r,e,0,i,o):1==e.interlace&&(r=function _readInterlace(e,t){const r=t.width,i=t.height,o=_getBPP(t),a=o>>3,s=Math.ceil(r*o/8),f=new Uint8Array(i*s);let l=0;const c=[0,0,4,0,2,0,1],u=[0,4,0,2,0,1,0],h=[8,8,8,4,4,2,2],d=[8,8,4,4,2,2,1];let A=0;for(;A<7;){const p=h[A],m=d[A];let w=0,v=0,b=c[A];for(;b<i;)b+=p,v++;let y=u[A];for(;y<r;)y+=m,w++;const E=Math.ceil(w*o/8);_filterZero(e,t,l,w,v);let F=0,_=c[A];for(;_<i;){let t=u[A],i=l+F*E<<3;for(;t<r;){var g;if(1==o)g=(g=e[i>>3])>>7-(7&i)&1,f[_*s+(t>>3)]|=g<<7-((7&t)<<0);if(2==o)g=(g=e[i>>3])>>6-(7&i)&3,f[_*s+(t>>2)]|=g<<6-((3&t)<<1);if(4==o)g=(g=e[i>>3])>>4-(7&i)&15,f[_*s+(t>>1)]|=g<<4-((1&t)<<2);if(o>=8){const r=_*s+t*a;for(let t=0;t<a;t++)f[r+t]=e[(i>>3)+t];}i+=o,t+=m;}F++,_+=p;}w*v!=0&&(l+=v*(1+E)),A+=1;}return f}(r,e)),r}function _inflate(e,r){return t(new Uint8Array(e.buffer,2,e.length-6),r)}var t=function(){const e={H:{}};return e.H.N=function(t,r){const i=Uint8Array;let o,a,s=0,f=0,l=0,c=0,u=0,h=0,d=0,A=0,g=0;if(3==t[0]&&0==t[1])return r||new i(0);const p=e.H,m=p.b,w=p.e,v=p.R,b=p.n,y=p.A,E=p.Z,F=p.m,_=null==r;for(_&&(r=new i(t.length>>>2<<5));0==s;)if(s=m(t,g,1),f=m(t,g+1,2),g+=3,0!=f){if(_&&(r=e.H.W(r,A+(1<<17))),1==f&&(o=F.J,a=F.h,h=511,d=31),2==f){l=w(t,g,5)+257,c=w(t,g+5,5)+1,u=w(t,g+10,4)+4,g+=14;let e=1;for(var B=0;B<38;B+=2)F.Q[B]=0,F.Q[B+1]=0;for(B=0;B<u;B++){const r=w(t,g+3*B,3);F.Q[1+(F.X[B]<<1)]=r,r>e&&(e=r);}g+=3*u,b(F.Q,e),y(F.Q,e,F.u),o=F.w,a=F.d,g=v(F.u,(1<<e)-1,l+c,t,g,F.v);const r=p.V(F.v,0,l,F.C);h=(1<<r)-1;const i=p.V(F.v,l,c,F.D);d=(1<<i)-1,b(F.C,r),y(F.C,r,o),b(F.D,i),y(F.D,i,a);}for(;;){const e=o[E(t,g)&h];g+=15&e;const i=e>>>4;if(i>>>8==0)r[A++]=i;else {if(256==i)break;{let e=A+i-254;if(i>264){const r=F.q[i-257];e=A+(r>>>3)+w(t,g,7&r),g+=7&r;}const o=a[E(t,g)&d];g+=15&o;const s=o>>>4,f=F.c[s],l=(f>>>4)+m(t,g,15&f);for(g+=15&f;A<e;)r[A]=r[A++-l],r[A]=r[A++-l],r[A]=r[A++-l],r[A]=r[A++-l];A=e;}}}}else {0!=(7&g)&&(g+=8-(7&g));const o=4+(g>>>3),a=t[o-4]|t[o-3]<<8;_&&(r=e.H.W(r,A+a)),r.set(new i(t.buffer,t.byteOffset+o,a),A),g=o+a<<3,A+=a;}return r.length==A?r:r.slice(0,A)},e.H.W=function(e,t){const r=e.length;if(t<=r)return e;const i=new Uint8Array(r<<1);return i.set(e,0),i},e.H.R=function(t,r,i,o,a,s){const f=e.H.e,l=e.H.Z;let c=0;for(;c<i;){const e=t[l(o,a)&r];a+=15&e;const i=e>>>4;if(i<=15)s[c]=i,c++;else {let e=0,t=0;16==i?(t=3+f(o,a,2),a+=2,e=s[c-1]):17==i?(t=3+f(o,a,3),a+=3):18==i&&(t=11+f(o,a,7),a+=7);const r=c+t;for(;c<r;)s[c]=e,c++;}}return a},e.H.V=function(e,t,r,i){let o=0,a=0;const s=i.length>>>1;for(;a<r;){const r=e[a+t];i[a<<1]=0,i[1+(a<<1)]=r,r>o&&(o=r),a++;}for(;a<s;)i[a<<1]=0,i[1+(a<<1)]=0,a++;return o},e.H.n=function(t,r){const i=e.H.m,o=t.length;let a,s,f;let l;const c=i.j;for(var u=0;u<=r;u++)c[u]=0;for(u=1;u<o;u+=2)c[t[u]]++;const h=i.K;for(a=0,c[0]=0,s=1;s<=r;s++)a=a+c[s-1]<<1,h[s]=a;for(f=0;f<o;f+=2)l=t[f+1],0!=l&&(t[f]=h[l],h[l]++);},e.H.A=function(t,r,i){const o=t.length,a=e.H.m.r;for(let e=0;e<o;e+=2)if(0!=t[e+1]){const o=e>>1,s=t[e+1],f=o<<4|s,l=r-s;let c=t[e]<<l;const u=c+(1<<l);for(;c!=u;){i[a[c]>>>15-r]=f,c++;}}},e.H.l=function(t,r){const i=e.H.m.r,o=15-r;for(let e=0;e<t.length;e+=2){const a=t[e]<<r-t[e+1];t[e]=i[a]>>>o;}},e.H.M=function(e,t,r){r<<=7&t;const i=t>>>3;e[i]|=r,e[i+1]|=r>>>8;},e.H.I=function(e,t,r){r<<=7&t;const i=t>>>3;e[i]|=r,e[i+1]|=r>>>8,e[i+2]|=r>>>16;},e.H.e=function(e,t,r){return (e[t>>>3]|e[1+(t>>>3)]<<8)>>>(7&t)&(1<<r)-1},e.H.b=function(e,t,r){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16)>>>(7&t)&(1<<r)-1},e.H.Z=function(e,t){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16)>>>(7&t)},e.H.i=function(e,t){return (e[t>>>3]|e[1+(t>>>3)]<<8|e[2+(t>>>3)]<<16|e[3+(t>>>3)]<<24)>>>(7&t)},e.H.m=function(){const e=Uint16Array,t=Uint32Array;return {K:new e(16),j:new e(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new e(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new t(32),J:new e(512),_:[],h:new e(32),$:[],w:new e(32768),C:[],v:[],d:new e(32768),D:[],u:new e(512),Q:[],r:new e(32768),s:new t(286),Y:new t(30),a:new t(19),t:new t(15e3),k:new e(65536),g:new e(32768)}}(),function(){const t=e.H.m;for(var r=0;r<32768;r++){let e=r;e=(2863311530&e)>>>1|(1431655765&e)<<1,e=(3435973836&e)>>>2|(858993459&e)<<2,e=(4042322160&e)>>>4|(252645135&e)<<4,e=(4278255360&e)>>>8|(16711935&e)<<8,t.r[r]=(e>>>16|e<<16)>>>17;}function n(e,t,r){for(;0!=t--;)e.push(0,r);}for(r=0;r<32;r++)t.q[r]=t.S[r]<<3|t.T[r],t.c[r]=t.p[r]<<4|t.z[r];n(t._,144,8),n(t._,112,9),n(t._,24,7),n(t._,8,8),e.H.n(t._,9),e.H.A(t._,9,t.J),e.H.l(t._,9),n(t.$,32,5),e.H.n(t.$,5),e.H.A(t.$,5,t.h),e.H.l(t.$,5),n(t.Q,19,0),n(t.C,286,0),n(t.D,30,0),n(t.v,320,0);}(),e.H.N}();function _getBPP(e){return [1,null,3,1,2,null,4][e.ctype]*e.depth}function _filterZero(e,t,r,i,o){let a=_getBPP(t);const s=Math.ceil(i*a/8);let f,l;a=Math.ceil(a/8);let c=e[r],u=0;if(c>1&&(e[r]=[0,0,1][c-2]),3==c)for(u=a;u<s;u++)e[u+1]=e[u+1]+(e[u+1-a]>>>1)&255;for(let t=0;t<o;t++)if(f=r+t*s,l=f+t+1,c=e[l-1],u=0,0==c)for(;u<s;u++)e[f+u]=e[l+u];else if(1==c){for(;u<a;u++)e[f+u]=e[l+u];for(;u<s;u++)e[f+u]=e[l+u]+e[f+u-a];}else if(2==c)for(;u<s;u++)e[f+u]=e[l+u]+e[f+u-s];else if(3==c){for(;u<a;u++)e[f+u]=e[l+u]+(e[f+u-s]>>>1);for(;u<s;u++)e[f+u]=e[l+u]+(e[f+u-s]+e[f+u-a]>>>1);}else {for(;u<a;u++)e[f+u]=e[l+u]+_paeth(0,e[f+u-s],0);for(;u<s;u++)e[f+u]=e[l+u]+_paeth(e[f+u-a],e[f+u-s],e[f+u-a-s]);}return e}function _paeth(e,t,r){const i=e+t-r,o=i-e,a=i-t,s=i-r;return o*o<=a*a&&o*o<=s*s?e:a*a<=s*s?t:r}function _IHDR(t,r,i){i.width=e.readUint(t,r),r+=4,i.height=e.readUint(t,r),r+=4,i.depth=t[r],r++,i.ctype=t[r],r++,i.compress=t[r],r++,i.filter=t[r],r++,i.interlace=t[r],r++;}function _copyTile(e,t,r,i,o,a,s,f,l){const c=Math.min(t,o),u=Math.min(r,a);let h=0,d=0;for(let r=0;r<u;r++)for(let a=0;a<c;a++)if(s>=0&&f>=0?(h=r*t+a<<2,d=(f+r)*o+s+a<<2):(h=(-f+r)*t-s+a<<2,d=r*o+a<<2),0==l)i[d]=e[h],i[d+1]=e[h+1],i[d+2]=e[h+2],i[d+3]=e[h+3];else if(1==l){var A=e[h+3]*(1/255),g=e[h]*A,p=e[h+1]*A,m=e[h+2]*A,w=i[d+3]*(1/255),v=i[d]*w,b=i[d+1]*w,y=i[d+2]*w;const t=1-A,r=A+w*t,o=0==r?0:1/r;i[d+3]=255*r,i[d+0]=(g+v*t)*o,i[d+1]=(p+b*t)*o,i[d+2]=(m+y*t)*o;}else if(2==l){A=e[h+3],g=e[h],p=e[h+1],m=e[h+2],w=i[d+3],v=i[d],b=i[d+1],y=i[d+2];A==w&&g==v&&p==b&&m==y?(i[d]=0,i[d+1]=0,i[d+2]=0,i[d+3]=0):(i[d]=g,i[d+1]=p,i[d+2]=m,i[d+3]=A);}else if(3==l){A=e[h+3],g=e[h],p=e[h+1],m=e[h+2],w=i[d+3],v=i[d],b=i[d+1],y=i[d+2];if(A==w&&g==v&&p==b&&m==y)continue;if(A<220&&w>20)return false}return true}return {decode:function decode(r){const i=new Uint8Array(r);let o=8;const a=e,s=a.readUshort,f=a.readUint,l={tabs:{},frames:[]},c=new Uint8Array(i.length);let u,h=0,d=0;const A=[137,80,78,71,13,10,26,10];for(var g=0;g<8;g++)if(i[g]!=A[g])throw "The input is not a PNG file!";for(;o<i.length;){const e=a.readUint(i,o);o+=4;const r=a.readASCII(i,o,4);if(o+=4,"IHDR"==r)_IHDR(i,o,l);else if("iCCP"==r){for(var p=o;0!=i[p];)p++;a.readASCII(i,o,p-o);const s=i.slice(p+2,o+e);let f=null;try{f=_inflate(s);}catch(e){f=t(s);}l.tabs[r]=f;}else if("CgBI"==r)l.tabs[r]=i.slice(o,o+4);else if("IDAT"==r){for(g=0;g<e;g++)c[h+g]=i[o+g];h+=e;}else if("acTL"==r)l.tabs[r]={num_frames:f(i,o),num_plays:f(i,o+4)},u=new Uint8Array(i.length);else if("fcTL"==r){if(0!=d)(E=l.frames[l.frames.length-1]).data=_decompress(l,u.slice(0,d),E.rect.width,E.rect.height),d=0;const e={x:f(i,o+12),y:f(i,o+16),width:f(i,o+4),height:f(i,o+8)};let t=s(i,o+22);t=s(i,o+20)/(0==t?100:t);const r={rect:e,delay:Math.round(1e3*t),dispose:i[o+24],blend:i[o+25]};l.frames.push(r);}else if("fdAT"==r){for(g=0;g<e-4;g++)u[d+g]=i[o+g+4];d+=e-4;}else if("pHYs"==r)l.tabs[r]=[a.readUint(i,o),a.readUint(i,o+4),i[o+8]];else if("cHRM"==r){l.tabs[r]=[];for(g=0;g<8;g++)l.tabs[r].push(a.readUint(i,o+4*g));}else if("tEXt"==r||"zTXt"==r){null==l.tabs[r]&&(l.tabs[r]={});var m=a.nextZero(i,o),w=a.readASCII(i,o,m-o),v=o+e-m-1;if("tEXt"==r)y=a.readASCII(i,m+1,v);else {var b=_inflate(i.slice(m+2,m+2+v));y=a.readUTF8(b,0,b.length);}l.tabs[r][w]=y;}else if("iTXt"==r){null==l.tabs[r]&&(l.tabs[r]={});m=0,p=o;m=a.nextZero(i,p);w=a.readASCII(i,p,m-p);const t=i[p=m+1];var y;p+=2,m=a.nextZero(i,p),a.readASCII(i,p,m-p),p=m+1,m=a.nextZero(i,p),a.readUTF8(i,p,m-p);v=e-((p=m+1)-o);if(0==t)y=a.readUTF8(i,p,v);else {b=_inflate(i.slice(p,p+v));y=a.readUTF8(b,0,b.length);}l.tabs[r][w]=y;}else if("PLTE"==r)l.tabs[r]=a.readBytes(i,o,e);else if("hIST"==r){const e=l.tabs.PLTE.length/3;l.tabs[r]=[];for(g=0;g<e;g++)l.tabs[r].push(s(i,o+2*g));}else if("tRNS"==r)3==l.ctype?l.tabs[r]=a.readBytes(i,o,e):0==l.ctype?l.tabs[r]=s(i,o):2==l.ctype&&(l.tabs[r]=[s(i,o),s(i,o+2),s(i,o+4)]);else if("gAMA"==r)l.tabs[r]=a.readUint(i,o)/1e5;else if("sRGB"==r)l.tabs[r]=i[o];else if("bKGD"==r)0==l.ctype||4==l.ctype?l.tabs[r]=[s(i,o)]:2==l.ctype||6==l.ctype?l.tabs[r]=[s(i,o),s(i,o+2),s(i,o+4)]:3==l.ctype&&(l.tabs[r]=i[o]);else if("IEND"==r)break;o+=e,a.readUint(i,o),o+=4;}var E;return 0!=d&&((E=l.frames[l.frames.length-1]).data=_decompress(l,u.slice(0,d),E.rect.width,E.rect.height)),l.data=_decompress(l,c,l.width,l.height),delete l.compress,delete l.interlace,delete l.filter,l},toRGBA8:function toRGBA8(e){const t=e.width,r=e.height;if(null==e.tabs.acTL)return [decodeImage(e.data,t,r,e).buffer];const i=[];null==e.frames[0].data&&(e.frames[0].data=e.data);const o=t*r*4,a=new Uint8Array(o),s=new Uint8Array(o),f=new Uint8Array(o);for(let c=0;c<e.frames.length;c++){const u=e.frames[c],h=u.rect.x,d=u.rect.y,A=u.rect.width,g=u.rect.height,p=decodeImage(u.data,A,g,e);if(0!=c)for(var l=0;l<o;l++)f[l]=a[l];if(0==u.blend?_copyTile(p,A,g,a,t,r,h,d,0):1==u.blend&&_copyTile(p,A,g,a,t,r,h,d,1),i.push(a.buffer.slice(0)),0==u.dispose);else if(1==u.dispose)_copyTile(s,A,g,a,t,r,h,d,0);else if(2==u.dispose)for(l=0;l<o;l++)a[l]=f[l];}return i},_paeth:_paeth,_copyTile:_copyTile,_bin:e}}();!function(){const{_copyTile:e}=UPNG,{_bin:t}=UPNG,r=UPNG._paeth;var i={table:function(){const e=new Uint32Array(256);for(let t=0;t<256;t++){let r=t;for(let e=0;e<8;e++)1&r?r=3988292384^r>>>1:r>>>=1;e[t]=r;}return e}(),update(e,t,r,o){for(let a=0;a<o;a++)e=i.table[255&(e^t[r+a])]^e>>>8;return e},crc:(e,t,r)=>4294967295^i.update(4294967295,e,t,r)};function addErr(e,t,r,i){t[r]+=e[0]*i>>4,t[r+1]+=e[1]*i>>4,t[r+2]+=e[2]*i>>4,t[r+3]+=e[3]*i>>4;}function N(e){return Math.max(0,Math.min(255,e))}function D(e,t){const r=e[0]-t[0],i=e[1]-t[1],o=e[2]-t[2],a=e[3]-t[3];return r*r+i*i+o*o+a*a}function dither(e,t,r,i,o,a,s){null==s&&(s=1);const f=i.length,l=[];for(var c=0;c<f;c++){const e=i[c];l.push([e>>>0&255,e>>>8&255,e>>>16&255,e>>>24&255]);}for(c=0;c<f;c++){let e=4294967295;for(var u=0,h=0;h<f;h++){var d=D(l[c],l[h]);h!=c&&d<e&&(e=d,u=h);}}const A=new Uint32Array(o.buffer),g=new Int16Array(t*r*4),p=[0,8,2,10,12,4,14,6,3,11,1,9,15,7,13,5];for(c=0;c<p.length;c++)p[c]=255*((p[c]+.5)/16-.5);for(let o=0;o<r;o++)for(let w=0;w<t;w++){var m;c=4*(o*t+w);if(2!=s)m=[N(e[c]+g[c]),N(e[c+1]+g[c+1]),N(e[c+2]+g[c+2]),N(e[c+3]+g[c+3])];else {d=p[4*(3&o)+(3&w)];m=[N(e[c]+d),N(e[c+1]+d),N(e[c+2]+d),N(e[c+3]+d)];}u=0;let v=16777215;for(h=0;h<f;h++){const e=D(m,l[h]);e<v&&(v=e,u=h);}const b=l[u],y=[m[0]-b[0],m[1]-b[1],m[2]-b[2],m[3]-b[3]];1==s&&(w!=t-1&&addErr(y,g,c+4,7),o!=r-1&&(0!=w&&addErr(y,g,c+4*t-4,3),addErr(y,g,c+4*t,5),w!=t-1&&addErr(y,g,c+4*t+4,1))),a[c>>2]=u,A[c>>2]=i[u];}}function _main(e,r,o,a,s){null==s&&(s={});const{crc:f}=i,l=t.writeUint,c=t.writeUshort,u=t.writeASCII;let h=8;const d=e.frames.length>1;let A,g=false,p=33+(d?20:0);if(null!=s.sRGB&&(p+=13),null!=s.pHYs&&(p+=21),null!=s.iCCP&&(A=pako.deflate(s.iCCP),p+=21+A.length+4),3==e.ctype){for(var m=e.plte.length,w=0;w<m;w++)e.plte[w]>>>24!=255&&(g=true);p+=8+3*m+4+(g?8+1*m+4:0);}for(var v=0;v<e.frames.length;v++){d&&(p+=38),p+=(F=e.frames[v]).cimg.length+12,0!=v&&(p+=4);}p+=12;const b=new Uint8Array(p),y=[137,80,78,71,13,10,26,10];for(w=0;w<8;w++)b[w]=y[w];if(l(b,h,13),h+=4,u(b,h,"IHDR"),h+=4,l(b,h,r),h+=4,l(b,h,o),h+=4,b[h]=e.depth,h++,b[h]=e.ctype,h++,b[h]=0,h++,b[h]=0,h++,b[h]=0,h++,l(b,h,f(b,h-17,17)),h+=4,null!=s.sRGB&&(l(b,h,1),h+=4,u(b,h,"sRGB"),h+=4,b[h]=s.sRGB,h++,l(b,h,f(b,h-5,5)),h+=4),null!=s.iCCP){const e=13+A.length;l(b,h,e),h+=4,u(b,h,"iCCP"),h+=4,u(b,h,"ICC profile"),h+=11,h+=2,b.set(A,h),h+=A.length,l(b,h,f(b,h-(e+4),e+4)),h+=4;}if(null!=s.pHYs&&(l(b,h,9),h+=4,u(b,h,"pHYs"),h+=4,l(b,h,s.pHYs[0]),h+=4,l(b,h,s.pHYs[1]),h+=4,b[h]=s.pHYs[2],h++,l(b,h,f(b,h-13,13)),h+=4),d&&(l(b,h,8),h+=4,u(b,h,"acTL"),h+=4,l(b,h,e.frames.length),h+=4,l(b,h,null!=s.loop?s.loop:0),h+=4,l(b,h,f(b,h-12,12)),h+=4),3==e.ctype){l(b,h,3*(m=e.plte.length)),h+=4,u(b,h,"PLTE"),h+=4;for(w=0;w<m;w++){const t=3*w,r=e.plte[w],i=255&r,o=r>>>8&255,a=r>>>16&255;b[h+t+0]=i,b[h+t+1]=o,b[h+t+2]=a;}if(h+=3*m,l(b,h,f(b,h-3*m-4,3*m+4)),h+=4,g){l(b,h,m),h+=4,u(b,h,"tRNS"),h+=4;for(w=0;w<m;w++)b[h+w]=e.plte[w]>>>24&255;h+=m,l(b,h,f(b,h-m-4,m+4)),h+=4;}}let E=0;for(v=0;v<e.frames.length;v++){var F=e.frames[v];d&&(l(b,h,26),h+=4,u(b,h,"fcTL"),h+=4,l(b,h,E++),h+=4,l(b,h,F.rect.width),h+=4,l(b,h,F.rect.height),h+=4,l(b,h,F.rect.x),h+=4,l(b,h,F.rect.y),h+=4,c(b,h,a[v]),h+=2,c(b,h,1e3),h+=2,b[h]=F.dispose,h++,b[h]=F.blend,h++,l(b,h,f(b,h-30,30)),h+=4);const t=F.cimg;l(b,h,(m=t.length)+(0==v?0:4)),h+=4;const r=h;u(b,h,0==v?"IDAT":"fdAT"),h+=4,0!=v&&(l(b,h,E++),h+=4),b.set(t,h),h+=m,l(b,h,f(b,r,h-r)),h+=4;}return l(b,h,0),h+=4,u(b,h,"IEND"),h+=4,l(b,h,f(b,h-4,4)),h+=4,b.buffer}function compressPNG(e,t,r){for(let i=0;i<e.frames.length;i++){const o=e.frames[i];const a=o.rect.height,s=new Uint8Array(a*o.bpl+a);o.cimg=_filterZero(o.img,a,o.bpp,o.bpl,s,t,r);}}function compress(t,r,i,o,a){const s=a[0],f=a[1],l=a[2],c=a[3],u=a[4],h=a[5];let d=6,A=8,g=255;for(var p=0;p<t.length;p++){const e=new Uint8Array(t[p]);for(var m=e.length,w=0;w<m;w+=4)g&=e[w+3];}const v=255!=g,b=function framize(t,r,i,o,a,s){const f=[];for(var l=0;l<t.length;l++){const h=new Uint8Array(t[l]),A=new Uint32Array(h.buffer);var c;let g=0,p=0,m=r,w=i,v=o?1:0;if(0!=l){const b=s||o||1==l||0!=f[l-2].dispose?1:2;let y=0,E=1e9;for(let e=0;e<b;e++){var u=new Uint8Array(t[l-1-e]);const o=new Uint32Array(t[l-1-e]);let s=r,f=i,c=-1,h=-1;for(let e=0;e<i;e++)for(let t=0;t<r;t++){A[d=e*r+t]!=o[d]&&(t<s&&(s=t),t>c&&(c=t),e<f&&(f=e),e>h&&(h=e));} -1==c&&(s=f=c=h=0),a&&(1==(1&s)&&s--,1==(1&f)&&f--);const v=(c-s+1)*(h-f+1);v<E&&(E=v,y=e,g=s,p=f,m=c-s+1,w=h-f+1);}u=new Uint8Array(t[l-1-y]);1==y&&(f[l-1].dispose=2),c=new Uint8Array(m*w*4),e(u,r,i,c,m,w,-g,-p,0),v=e(h,r,i,c,m,w,-g,-p,3)?1:0,1==v?_prepareDiff(h,r,i,c,{x:g,y:p,width:m,height:w}):e(h,r,i,c,m,w,-g,-p,0);}else c=h.slice(0);f.push({rect:{x:g,y:p,width:m,height:w},img:c,blend:v,dispose:0});}if(o)for(l=0;l<f.length;l++){if(1==(A=f[l]).blend)continue;const e=A.rect,o=f[l-1].rect,s=Math.min(e.x,o.x),c=Math.min(e.y,o.y),u={x:s,y:c,width:Math.max(e.x+e.width,o.x+o.width)-s,height:Math.max(e.y+e.height,o.y+o.height)-c};f[l-1].dispose=1,l-1!=0&&_updateFrame(t,r,i,f,l-1,u,a),_updateFrame(t,r,i,f,l,u,a);}let h=0;if(1!=t.length)for(var d=0;d<f.length;d++){var A;h+=(A=f[d]).rect.width*A.rect.height;}return f}(t,r,i,s,f,l),y={},E=[],F=[];if(0!=o){const e=[];for(w=0;w<b.length;w++)e.push(b[w].img.buffer);const t=function concatRGBA(e){let t=0;for(var r=0;r<e.length;r++)t+=e[r].byteLength;const i=new Uint8Array(t);let o=0;for(r=0;r<e.length;r++){const t=new Uint8Array(e[r]),a=t.length;for(let e=0;e<a;e+=4){let r=t[e],a=t[e+1],s=t[e+2];const f=t[e+3];0==f&&(r=a=s=0),i[o+e]=r,i[o+e+1]=a,i[o+e+2]=s,i[o+e+3]=f;}o+=a;}return i.buffer}(e),r=quantize(t,o);for(w=0;w<r.plte.length;w++)E.push(r.plte[w].est.rgba);let i=0;for(w=0;w<b.length;w++){const e=(B=b[w]).img.length;var _=new Uint8Array(r.inds.buffer,i>>2,e>>2);F.push(_);const t=new Uint8Array(r.abuf,i,e);h&&dither(B.img,B.rect.width,B.rect.height,E,t,_),B.img.set(t),i+=e;}}else for(p=0;p<b.length;p++){var B=b[p];const e=new Uint32Array(B.img.buffer);var U=B.rect.width;m=e.length,_=new Uint8Array(m);F.push(_);for(w=0;w<m;w++){const t=e[w];if(0!=w&&t==e[w-1])_[w]=_[w-1];else if(w>U&&t==e[w-U])_[w]=_[w-U];else {let e=y[t];if(null==e&&(y[t]=e=E.length,E.push(t),E.length>=300))break;_[w]=e;}}}const C=E.length;C<=256&&0==u&&(A=C<=2?1:C<=4?2:C<=16?4:8,A=Math.max(A,c));for(p=0;p<b.length;p++){(B=b[p]).rect.x;U=B.rect.width;const e=B.rect.height;let t=B.img;let r=4*U,i=4;if(C<=256&&0==u){r=Math.ceil(A*U/8);var I=new Uint8Array(r*e);const o=F[p];for(let t=0;t<e;t++){w=t*r;const e=t*U;if(8==A)for(var Q=0;Q<U;Q++)I[w+Q]=o[e+Q];else if(4==A)for(Q=0;Q<U;Q++)I[w+(Q>>1)]|=o[e+Q]<<4-4*(1&Q);else if(2==A)for(Q=0;Q<U;Q++)I[w+(Q>>2)]|=o[e+Q]<<6-2*(3&Q);else if(1==A)for(Q=0;Q<U;Q++)I[w+(Q>>3)]|=o[e+Q]<<7-1*(7&Q);}t=I,d=3,i=1;}else if(0==v&&1==b.length){I=new Uint8Array(U*e*3);const o=U*e;for(w=0;w<o;w++){const e=3*w,r=4*w;I[e]=t[r],I[e+1]=t[r+1],I[e+2]=t[r+2];}t=I,d=2,i=3,r=3*U;}B.img=t,B.bpl=r,B.bpp=i;}return {ctype:d,depth:A,plte:E,frames:b}}function _updateFrame(t,r,i,o,a,s,f){const l=Uint8Array,c=Uint32Array,u=new l(t[a-1]),h=new c(t[a-1]),d=a+1<t.length?new l(t[a+1]):null,A=new l(t[a]),g=new c(A.buffer);let p=r,m=i,w=-1,v=-1;for(let e=0;e<s.height;e++)for(let t=0;t<s.width;t++){const i=s.x+t,f=s.y+e,l=f*r+i,c=g[l];0==c||0==o[a-1].dispose&&h[l]==c&&(null==d||0!=d[4*l+3])||(i<p&&(p=i),i>w&&(w=i),f<m&&(m=f),f>v&&(v=f));} -1==w&&(p=m=w=v=0),f&&(1==(1&p)&&p--,1==(1&m)&&m--),s={x:p,y:m,width:w-p+1,height:v-m+1};const b=o[a];b.rect=s,b.blend=1,b.img=new Uint8Array(s.width*s.height*4),0==o[a-1].dispose?(e(u,r,i,b.img,s.width,s.height,-s.x,-s.y,0),_prepareDiff(A,r,i,b.img,s)):e(A,r,i,b.img,s.width,s.height,-s.x,-s.y,0);}function _prepareDiff(t,r,i,o,a){e(t,r,i,o,a.width,a.height,-a.x,-a.y,2);}function _filterZero(e,t,r,i,o,a,s){const f=[];let l,c=[0,1,2,3,4];-1!=a?c=[a]:(t*i>5e5||1==r)&&(c=[0]),s&&(l={level:0});const u=UZIP;for(var h=0;h<c.length;h++){for(let a=0;a<t;a++)_filterLine(o,e,a,i,r,c[h]);f.push(u.deflate(o,l));}let d,A=1e9;for(h=0;h<f.length;h++)f[h].length<A&&(d=h,A=f[h].length);return f[d]}function _filterLine(e,t,i,o,a,s){const f=i*o;let l=f+i;if(e[l]=s,l++,0==s)if(o<500)for(var c=0;c<o;c++)e[l+c]=t[f+c];else e.set(new Uint8Array(t.buffer,f,o),l);else if(1==s){for(c=0;c<a;c++)e[l+c]=t[f+c];for(c=a;c<o;c++)e[l+c]=t[f+c]-t[f+c-a]+256&255;}else if(0==i){for(c=0;c<a;c++)e[l+c]=t[f+c];if(2==s)for(c=a;c<o;c++)e[l+c]=t[f+c];if(3==s)for(c=a;c<o;c++)e[l+c]=t[f+c]-(t[f+c-a]>>1)+256&255;if(4==s)for(c=a;c<o;c++)e[l+c]=t[f+c]-r(t[f+c-a],0,0)+256&255;}else {if(2==s)for(c=0;c<o;c++)e[l+c]=t[f+c]+256-t[f+c-o]&255;if(3==s){for(c=0;c<a;c++)e[l+c]=t[f+c]+256-(t[f+c-o]>>1)&255;for(c=a;c<o;c++)e[l+c]=t[f+c]+256-(t[f+c-o]+t[f+c-a]>>1)&255;}if(4==s){for(c=0;c<a;c++)e[l+c]=t[f+c]+256-r(0,t[f+c-o],0)&255;for(c=a;c<o;c++)e[l+c]=t[f+c]+256-r(t[f+c-a],t[f+c-o],t[f+c-a-o])&255;}}}function quantize(e,t){const r=new Uint8Array(e),i=r.slice(0),o=new Uint32Array(i.buffer),a=getKDtree(i,t),s=a[0],f=a[1],l=r.length,c=new Uint8Array(l>>2);let u;if(r.length<2e7)for(var h=0;h<l;h+=4){u=getNearest(s,d=r[h]*(1/255),A=r[h+1]*(1/255),g=r[h+2]*(1/255),p=r[h+3]*(1/255)),c[h>>2]=u.ind,o[h>>2]=u.est.rgba;}else for(h=0;h<l;h+=4){var d=r[h]*(1/255),A=r[h+1]*(1/255),g=r[h+2]*(1/255),p=r[h+3]*(1/255);for(u=s;u.left;)u=planeDst(u.est,d,A,g,p)<=0?u.left:u.right;c[h>>2]=u.ind,o[h>>2]=u.est.rgba;}return {abuf:i.buffer,inds:c,plte:f}}function getKDtree(e,t,r){null==r&&(r=1e-4);const i=new Uint32Array(e.buffer),o={i0:0,i1:e.length,bst:null,est:null,tdst:0,left:null,right:null};o.bst=stats(e,o.i0,o.i1),o.est=estats(o.bst);const a=[o];for(;a.length<t;){let t=0,o=0;for(var s=0;s<a.length;s++)a[s].est.L>t&&(t=a[s].est.L,o=s);if(t<r)break;const f=a[o],l=splitPixels(e,i,f.i0,f.i1,f.est.e,f.est.eMq255);if(f.i0>=l||f.i1<=l){f.est.L=0;continue}const c={i0:f.i0,i1:l,bst:null,est:null,tdst:0,left:null,right:null};c.bst=stats(e,c.i0,c.i1),c.est=estats(c.bst);const u={i0:l,i1:f.i1,bst:null,est:null,tdst:0,left:null,right:null};u.bst={R:[],m:[],N:f.bst.N-c.bst.N};for(s=0;s<16;s++)u.bst.R[s]=f.bst.R[s]-c.bst.R[s];for(s=0;s<4;s++)u.bst.m[s]=f.bst.m[s]-c.bst.m[s];u.est=estats(u.bst),f.left=c,f.right=u,a[o]=c,a.push(u);}a.sort(((e,t)=>t.bst.N-e.bst.N));for(s=0;s<a.length;s++)a[s].ind=s;return [o,a]}function getNearest(e,t,r,i,o){if(null==e.left)return e.tdst=function dist(e,t,r,i,o){const a=t-e[0],s=r-e[1],f=i-e[2],l=o-e[3];return a*a+s*s+f*f+l*l}(e.est.q,t,r,i,o),e;const a=planeDst(e.est,t,r,i,o);let s=e.left,f=e.right;a>0&&(s=e.right,f=e.left);const l=getNearest(s,t,r,i,o);if(l.tdst<=a*a)return l;const c=getNearest(f,t,r,i,o);return c.tdst<l.tdst?c:l}function planeDst(e,t,r,i,o){const{e:a}=e;return a[0]*t+a[1]*r+a[2]*i+a[3]*o-e.eMq}function splitPixels(e,t,r,i,o,a){for(i-=4;r<i;){for(;vecDot(e,r,o)<=a;)r+=4;for(;vecDot(e,i,o)>a;)i-=4;if(r>=i)break;const s=t[r>>2];t[r>>2]=t[i>>2],t[i>>2]=s,r+=4,i-=4;}for(;vecDot(e,r,o)>a;)r-=4;return r+4}function vecDot(e,t,r){return e[t]*r[0]+e[t+1]*r[1]+e[t+2]*r[2]+e[t+3]*r[3]}function stats(e,t,r){const i=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],o=[0,0,0,0],a=r-t>>2;for(let a=t;a<r;a+=4){const t=e[a]*(1/255),r=e[a+1]*(1/255),s=e[a+2]*(1/255),f=e[a+3]*(1/255);o[0]+=t,o[1]+=r,o[2]+=s,o[3]+=f,i[0]+=t*t,i[1]+=t*r,i[2]+=t*s,i[3]+=t*f,i[5]+=r*r,i[6]+=r*s,i[7]+=r*f,i[10]+=s*s,i[11]+=s*f,i[15]+=f*f;}return i[4]=i[1],i[8]=i[2],i[9]=i[6],i[12]=i[3],i[13]=i[7],i[14]=i[11],{R:i,m:o,N:a}}function estats(e){const{R:t}=e,{m:r}=e,{N:i}=e,a=r[0],s=r[1],f=r[2],l=r[3],c=0==i?0:1/i,u=[t[0]-a*a*c,t[1]-a*s*c,t[2]-a*f*c,t[3]-a*l*c,t[4]-s*a*c,t[5]-s*s*c,t[6]-s*f*c,t[7]-s*l*c,t[8]-f*a*c,t[9]-f*s*c,t[10]-f*f*c,t[11]-f*l*c,t[12]-l*a*c,t[13]-l*s*c,t[14]-l*f*c,t[15]-l*l*c],h=u,d=o;let A=[Math.random(),Math.random(),Math.random(),Math.random()],g=0,p=0;if(0!=i)for(let e=0;e<16&&(A=d.multVec(h,A),p=Math.sqrt(d.dot(A,A)),A=d.sml(1/p,A),!(0!=e&&Math.abs(p-g)<1e-9));e++)g=p;const m=[a*c,s*c,f*c,l*c];return {Cov:u,q:m,e:A,L:g,eMq255:d.dot(d.sml(255,m),A),eMq:d.dot(A,m),rgba:(Math.round(255*m[3])<<24|Math.round(255*m[2])<<16|Math.round(255*m[1])<<8|Math.round(255*m[0])<<0)>>>0}}var o={multVec:(e,t)=>[e[0]*t[0]+e[1]*t[1]+e[2]*t[2]+e[3]*t[3],e[4]*t[0]+e[5]*t[1]+e[6]*t[2]+e[7]*t[3],e[8]*t[0]+e[9]*t[1]+e[10]*t[2]+e[11]*t[3],e[12]*t[0]+e[13]*t[1]+e[14]*t[2]+e[15]*t[3]],dot:(e,t)=>e[0]*t[0]+e[1]*t[1]+e[2]*t[2]+e[3]*t[3],sml:(e,t)=>[e*t[0],e*t[1],e*t[2],e*t[3]]};UPNG.encode=function encode(e,t,r,i,o,a,s){null==i&&(i=0),null==s&&(s=false);const f=compress(e,t,r,i,[false,false,false,0,s,false]);return compressPNG(f,-1),_main(f,t,r,o,a)},UPNG.encodeLL=function encodeLL(e,t,r,i,o,a,s,f){const l={ctype:0+(1==i?0:2)+(0==o?0:4),depth:a,frames:[]},c=(i+o)*a,u=c*t;for(let i=0;i<e.length;i++)l.frames.push({rect:{x:0,y:0,width:t,height:r},img:new Uint8Array(e[i]),blend:0,dispose:1,bpp:Math.ceil(c/8),bpl:Math.ceil(u/8)});return compressPNG(l,0,true),_main(l,t,r,s,f)},UPNG.encode.compress=compress,UPNG.encode.dither=dither,UPNG.quantize=quantize,UPNG.quantize.getKDtree=getKDtree,UPNG.quantize.getNearest=getNearest;}();const r={toArrayBuffer(e,t){const i=e.width,o=e.height,a=i<<2,s=e.getContext("2d").getImageData(0,0,i,o),f=new Uint32Array(s.data.buffer),l=(32*i+31)/32<<2,c=l*o,u=122+c,h=new ArrayBuffer(u),d=new DataView(h),A=1<<20;let g,p,m,w,v=A,b=0,y=0,E=0;function set16(e){d.setUint16(y,e,true),y+=2;}function set32(e){d.setUint32(y,e,true),y+=4;}function seek(e){y+=e;}set16(19778),set32(u),seek(4),set32(122),set32(108),set32(i),set32(-o>>>0),set16(1),set16(32),set32(3),set32(c),set32(2835),set32(2835),seek(8),set32(16711680),set32(65280),set32(255),set32(4278190080),set32(1466527264),function convert(){for(;b<o&&v>0;){for(w=122+b*l,g=0;g<a;)v--,p=f[E++],m=p>>>24,d.setUint32(w+g,p<<8|m),g+=4;b++;}E<f.length?(v=A,setTimeout(convert,r._dly)):t(h);}();},toBlob(e,t){this.toArrayBuffer(e,(e=>{t(new Blob([e],{type:"image/bmp"}));}));},_dly:9};var i={CHROME:"CHROME",FIREFOX:"FIREFOX",DESKTOP_SAFARI:"DESKTOP_SAFARI",IE:"IE",IOS:"IOS",ETC:"ETC"},o={[i.CHROME]:16384,[i.FIREFOX]:11180,[i.DESKTOP_SAFARI]:16384,[i.IE]:8192,[i.IOS]:4096,[i.ETC]:8192};const a="undefined"!=typeof window,s="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope,f=a&&window.cordova&&window.cordova.require&&window.cordova.require("cordova/modulemapper"),CustomFile=(a||s)&&(f&&f.getOriginalSymbol(window,"File")||"undefined"!=typeof File&&File),CustomFileReader=(a||s)&&(f&&f.getOriginalSymbol(window,"FileReader")||"undefined"!=typeof FileReader&&FileReader);function getFilefromDataUrl(e,t,r=Date.now()){return new Promise((i=>{const o=e.split(","),a=o[0].match(/:(.*?);/)[1],s=globalThis.atob(o[1]);let f=s.length;const l=new Uint8Array(f);for(;f--;)l[f]=s.charCodeAt(f);const c=new Blob([l],{type:a});c.name=t,c.lastModified=r,i(c);}))}function getDataUrlFromFile(e){return new Promise(((t,r)=>{const i=new CustomFileReader;i.onload=()=>t(i.result),i.onerror=e=>r(e),i.readAsDataURL(e);}))}function loadImage(e){return new Promise(((t,r)=>{const i=new Image;i.onload=()=>t(i),i.onerror=e=>r(e),i.src=e;}))}function getBrowserName(){if(void 0!==getBrowserName.cachedResult)return getBrowserName.cachedResult;let e=i.ETC;const{userAgent:t}=navigator;return /Chrom(e|ium)/i.test(t)?e=i.CHROME:/iP(ad|od|hone)/i.test(t)&&/WebKit/i.test(t)?e=i.IOS:/Safari/i.test(t)?e=i.DESKTOP_SAFARI:/Firefox/i.test(t)?e=i.FIREFOX:(/MSIE/i.test(t)||true==!!document.documentMode)&&(e=i.IE),getBrowserName.cachedResult=e,getBrowserName.cachedResult}function approximateBelowMaximumCanvasSizeOfBrowser(e,t){const r=getBrowserName(),i=o[r];let a=e,s=t,f=a*s;const l=a>s?s/a:a/s;for(;f>i*i;){const e=(i+a)/2,t=(i+s)/2;e<t?(s=t,a=t*l):(s=e*l,a=e),f=a*s;}return {width:a,height:s}}function getNewCanvasAndCtx(e,t){let r,i;try{if(r=new OffscreenCanvas(e,t),i=r.getContext("2d"),null===i)throw new Error("getContext of OffscreenCanvas returns null")}catch(e){r=document.createElement("canvas"),i=r.getContext("2d");}return r.width=e,r.height=t,[r,i]}function drawImageInCanvas(e,t){const{width:r,height:i}=approximateBelowMaximumCanvasSizeOfBrowser(e.width,e.height),[o,a]=getNewCanvasAndCtx(r,i);return t&&/jpe?g/.test(t)&&(a.fillStyle="white",a.fillRect(0,0,o.width,o.height)),a.drawImage(e,0,0,o.width,o.height),o}function isIOS(){return void 0!==isIOS.cachedResult||(isIOS.cachedResult=["iPad Simulator","iPhone Simulator","iPod Simulator","iPad","iPhone","iPod"].includes(navigator.platform)||navigator.userAgent.includes("Mac")&&"undefined"!=typeof document&&"ontouchend"in document),isIOS.cachedResult}function drawFileInCanvas(e,t={}){return new Promise((function(r,o){let a,s;var $Try_2_Post=function(){try{return s=drawImageInCanvas(a,t.fileType||e.type),r([a,s])}catch(e){return o(e)}},$Try_2_Catch=function(t){try{var $Try_3_Catch=function(e){try{throw e}catch(e){return o(e)}};try{let t;return getDataUrlFromFile(e).then((function(e){try{return t=e,loadImage(t).then((function(e){try{return a=e,function(){try{return $Try_2_Post()}catch(e){return o(e)}}()}catch(e){return $Try_3_Catch(e)}}),$Try_3_Catch)}catch(e){return $Try_3_Catch(e)}}),$Try_3_Catch)}catch(e){$Try_3_Catch(e);}}catch(e){return o(e)}};try{if(isIOS()||[i.DESKTOP_SAFARI,i.MOBILE_SAFARI].includes(getBrowserName()))throw new Error("Skip createImageBitmap on IOS and Safari");return createImageBitmap(e).then((function(e){try{return a=e,$Try_2_Post()}catch(e){return $Try_2_Catch()}}),$Try_2_Catch)}catch(e){$Try_2_Catch();}}))}function canvasToFile(e,t,i,o,a=1){return new Promise((function(s,f){let l;if("image/png"===t){let c,u,h;return c=e.getContext("2d"),({data:u}=c.getImageData(0,0,e.width,e.height)),h=UPNG.encode([u.buffer],e.width,e.height,4096*a),l=new Blob([h],{type:t}),l.name=i,l.lastModified=o,$If_4.call(this)}{if("image/bmp"===t)return new Promise((t=>r.toBlob(e,t))).then(function(e){try{return l=e,l.name=i,l.lastModified=o,$If_5.call(this)}catch(e){return f(e)}}.bind(this),f);{if("function"==typeof OffscreenCanvas&&e instanceof OffscreenCanvas)return e.convertToBlob({type:t,quality:a}).then(function(e){try{return l=e,l.name=i,l.lastModified=o,$If_6.call(this)}catch(e){return f(e)}}.bind(this),f);{let d;return d=e.toDataURL(t,a),getFilefromDataUrl(d,i,o).then(function(e){try{return l=e,$If_6.call(this)}catch(e){return f(e)}}.bind(this),f)}function $If_6(){return $If_5.call(this)}}function $If_5(){return $If_4.call(this)}}function $If_4(){return s(l)}}))}function cleanupCanvasMemory(e){e.width=0,e.height=0;}function isAutoOrientationInBrowser(){return new Promise((function(e,t){let i,o,a,s;return void 0!==isAutoOrientationInBrowser.cachedResult?e(isAutoOrientationInBrowser.cachedResult):(getFilefromDataUrl("data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAAAAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/xABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==","test.jpg",Date.now()).then((function(r){try{return i=r,drawFileInCanvas(i).then((function(r){try{return o=r[1],canvasToFile(o,i.type,i.name,i.lastModified).then((function(r){try{return a=r,cleanupCanvasMemory(o),drawFileInCanvas(a).then((function(r){try{return s=r[0],isAutoOrientationInBrowser.cachedResult=1===s.width&&2===s.height,e(isAutoOrientationInBrowser.cachedResult)}catch(e){return t(e)}}),t)}catch(e){return t(e)}}),t)}catch(e){return t(e)}}),t)}catch(e){return t(e)}}),t))}))}function getExifOrientation(e){return new Promise(((t,r)=>{const i=new CustomFileReader;i.onload=e=>{const r=new DataView(e.target.result);if(65496!=r.getUint16(0,false))return t(-2);const i=r.byteLength;let o=2;for(;o<i;){if(r.getUint16(o+2,false)<=8)return t(-1);const e=r.getUint16(o,false);if(o+=2,65505==e){if(1165519206!=r.getUint32(o+=2,false))return t(-1);const e=18761==r.getUint16(o+=6,false);o+=r.getUint32(o+4,e);const i=r.getUint16(o,e);o+=2;for(let a=0;a<i;a++)if(274==r.getUint16(o+12*a,e))return t(r.getUint16(o+12*a+8,e))}else {if(65280!=(65280&e))break;o+=r.getUint16(o,false);}}return t(-1)},i.onerror=e=>r(e),i.readAsArrayBuffer(e);}))}function handleMaxWidthOrHeight(e,t){const{width:r}=e,{height:i}=e,{maxWidthOrHeight:o}=t;let a,s=e;return isFinite(o)&&(r>o||i>o)&&([s,a]=getNewCanvasAndCtx(r,i),r>i?(s.width=o,s.height=i/r*o):(s.width=r/i*o,s.height=o),a.drawImage(e,0,0,s.width,s.height),cleanupCanvasMemory(e)),s}function followExifOrientation(e,t){const{width:r}=e,{height:i}=e,[o,a]=getNewCanvasAndCtx(r,i);switch(t>4&&t<9?(o.width=i,o.height=r):(o.width=r,o.height=i),t){case 2:a.transform(-1,0,0,1,r,0);break;case 3:a.transform(-1,0,0,-1,r,i);break;case 4:a.transform(1,0,0,-1,0,i);break;case 5:a.transform(0,1,1,0,0,0);break;case 6:a.transform(0,1,-1,0,i,0);break;case 7:a.transform(0,-1,-1,0,i,r);break;case 8:a.transform(0,-1,1,0,0,r);}return a.drawImage(e,0,0,r,i),cleanupCanvasMemory(e),o}function compress(e,t,r=0){return new Promise((function(i,o){let a,s,f,l,c,u,h,d,A,g,p,m,w,v,b,y,E,F,_,B;function incProgress(e=5){if(t.signal&&t.signal.aborted)throw t.signal.reason;a+=e,t.onProgress(Math.min(a,100));}function setProgress(e){if(t.signal&&t.signal.aborted)throw t.signal.reason;a=Math.min(Math.max(e,a),100),t.onProgress(a);}return a=r,s=t.maxIteration||10,f=1024*t.maxSizeMB*1024,incProgress(),drawFileInCanvas(e,t).then(function(r){try{return [,l]=r,incProgress(),c=handleMaxWidthOrHeight(l,t),incProgress(),new Promise((function(r,i){var o;if(!(o=t.exifOrientation))return getExifOrientation(e).then(function(e){try{return o=e,$If_2.call(this)}catch(e){return i(e)}}.bind(this),i);function $If_2(){return r(o)}return $If_2.call(this)})).then(function(r){try{return u=r,incProgress(),isAutoOrientationInBrowser().then(function(r){try{return h=r?c:followExifOrientation(c,u),incProgress(),d=t.initialQuality||1,A=t.fileType||e.type,canvasToFile(h,A,e.name,e.lastModified,d).then(function(r){try{{if(g=r,incProgress(),p=g.size>f,m=g.size>e.size,!p&&!m)return setProgress(100),i(g);var a;function $Loop_3(){if(s--&&(b>f||b>w)){let t,r;return t=B?.95*_.width:_.width,r=B?.95*_.height:_.height,[E,F]=getNewCanvasAndCtx(t,r),F.drawImage(_,0,0,t,r),d*="image/png"===A?.85:.95,canvasToFile(E,A,e.name,e.lastModified,d).then((function(e){try{return y=e,cleanupCanvasMemory(_),_=E,b=y.size,setProgress(Math.min(99,Math.floor((v-b)/(v-f)*100))),$Loop_3}catch(e){return o(e)}}),o)}return [1]}return w=e.size,v=g.size,b=v,_=h,B=!t.alwaysKeepResolution&&p,(a=function(e){for(;e;){if(e.then)return void e.then(a,o);try{if(e.pop){if(e.length)return e.pop()?$Loop_3_exit.call(this):e;e=$Loop_3;}else e=e.call(this);}catch(e){return o(e)}}}.bind(this))($Loop_3);function $Loop_3_exit(){return cleanupCanvasMemory(_),cleanupCanvasMemory(E),cleanupCanvasMemory(c),cleanupCanvasMemory(h),cleanupCanvasMemory(l),setProgress(100),i(y)}}}catch(u){return o(u)}}.bind(this),o)}catch(e){return o(e)}}.bind(this),o)}catch(e){return o(e)}}.bind(this),o)}catch(e){return o(e)}}.bind(this),o)}))}const l="\nlet scriptImported = false\nself.addEventListener('message', async (e) => {\n const { file, id, imageCompressionLibUrl, options } = e.data\n options.onProgress = (progress) => self.postMessage({ progress, id })\n try {\n if (!scriptImported) {\n // console.log('[worker] importScripts', imageCompressionLibUrl)\n self.importScripts(imageCompressionLibUrl)\n scriptImported = true\n }\n // console.log('[worker] self', self)\n const compressedFile = await imageCompression(file, options)\n self.postMessage({ file: compressedFile, id })\n } catch (e) {\n // console.error('[worker] error', e)\n self.postMessage({ error: e.message + '\\n' + e.stack, id })\n }\n})\n";let c;function compressOnWebWorker(e,t){return new Promise(((r,i)=>{c||(c=function createWorkerScriptURL(e){const t=[];return t.push(e),URL.createObjectURL(new Blob(t))}(l));const o=new Worker(c);o.addEventListener("message",(function handler(e){if(t.signal&&t.signal.aborted)o.terminate();else if(void 0===e.data.progress){if(e.data.error)return i(new Error(e.data.error)),void o.terminate();r(e.data.file),o.terminate();}else t.onProgress(e.data.progress);})),o.addEventListener("error",i),t.signal&&t.signal.addEventListener("abort",(()=>{i(t.signal.reason),o.terminate();})),o.postMessage({file:e,imageCompressionLibUrl:t.libURL,options:{...t,onProgress:void 0,signal:void 0}});}))}function imageCompression(e,t){return new Promise((function(r,i){let o,a,s,f,l,c;if(o={...t},s=0,({onProgress:f}=o),o.maxSizeMB=o.maxSizeMB||Number.POSITIVE_INFINITY,l="boolean"!=typeof o.useWebWorker||o.useWebWorker,delete o.useWebWorker,o.onProgress=e=>{s=e,"function"==typeof f&&f(s);},!(e instanceof Blob||e instanceof CustomFile))return i(new Error("The file given is not an instance of Blob or File"));if(!/^image/.test(e.type))return i(new Error("The file given is not an image"));if(c="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope,!l||"function"!=typeof Worker||c)return compress(e,o).then(function(e){try{return a=e,$If_4.call(this)}catch(e){return i(e)}}.bind(this),i);var u=function(){try{return $If_4.call(this)}catch(e){return i(e)}}.bind(this),$Try_1_Catch=function(t){try{return compress(e,o).then((function(e){try{return a=e,u()}catch(e){return i(e)}}),i)}catch(e){return i(e)}};try{return o.libURL=o.libURL||"https://cdn.jsdelivr.net/npm/browser-image-compression@2.0.2/dist/browser-image-compression.js",compressOnWebWorker(e,o).then((function(e){try{return a=e,u()}catch(e){return $Try_1_Catch()}}),$Try_1_Catch)}catch(e){$Try_1_Catch();}function $If_4(){try{a.name=e.name,a.lastModified=e.lastModified;}catch(e){}try{o.preserveExif&&"image/jpeg"===e.type&&(!o.fileType||o.fileType&&o.fileType===e.type)&&(a=copyExifWithoutOrientation(e,a));}catch(e){}return r(a)}}))}imageCompression.getDataUrlFromFile=getDataUrlFromFile,imageCompression.getFilefromDataUrl=getFilefromDataUrl,imageCompression.loadImage=loadImage,imageCompression.drawImageInCanvas=drawImageInCanvas,imageCompression.drawFileInCanvas=drawFileInCanvas,imageCompression.canvasToFile=canvasToFile,imageCompression.getExifOrientation=getExifOrientation,imageCompression.handleMaxWidthOrHeight=handleMaxWidthOrHeight,imageCompression.followExifOrientation=followExifOrientation,imageCompression.cleanupCanvasMemory=cleanupCanvasMemory,imageCompression.isAutoOrientationInBrowser=isAutoOrientationInBrowser,imageCompression.approximateBelowMaximumCanvasSizeOfBrowser=approximateBelowMaximumCanvasSizeOfBrowser,imageCompression.copyExifWithoutOrientation=copyExifWithoutOrientation,imageCompression.getBrowserName=getBrowserName,imageCompression.version="2.0.2";
149
-
150
- /**
151
- * @file 图像处理工具类
152
- * @description 提供图像预处理功能,用于提高OCR识别率
153
- * @module ImageProcessor
154
- */
155
- /**
156
- * 图像处理工具类
157
- *
158
- * 提供各种图像处理功能,用于优化识别效果
159
- */
160
- class ImageProcessor {
161
- /**
162
- * 将ImageData转换为Canvas元素
163
- *
164
- * @param {ImageData} imageData - 要转换的图像数据
165
- * @returns {HTMLCanvasElement} 包含图像的Canvas元素
166
- */
167
- static imageDataToCanvas(imageData) {
168
- const canvas = document.createElement("canvas");
169
- canvas.width = imageData.width;
170
- canvas.height = imageData.height;
171
- const ctx = canvas.getContext("2d");
172
- if (ctx) {
173
- ctx.putImageData(imageData, 0, 0);
174
- }
175
- return canvas;
176
- }
177
- /**
178
- * 将Canvas转换为ImageData
179
- *
180
- * @param {HTMLCanvasElement} canvas - 要转换的Canvas元素
181
- * @returns {ImageData|null} Canvas的图像数据,如果获取失败则返回null
182
- */
183
- static canvasToImageData(canvas) {
184
- const ctx = canvas.getContext("2d");
185
- return ctx ? ctx.getImageData(0, 0, canvas.width, canvas.height) : null;
186
- }
187
- /**
188
- * 调整图像亮度和对比度
189
- *
190
- * @param imageData 原始图像数据
191
- * @param brightness 亮度调整值 (-100到100)
192
- * @param contrast 对比度调整值 (-100到100)
193
- * @returns 处理后的图像数据
194
- */
195
- static adjustBrightnessContrast(imageData, brightness = 0, contrast = 0) {
196
- // 将亮度和对比度范围限制在 -100 到 100 之间
197
- brightness = Math.max(-100, Math.min(100, brightness));
198
- contrast = Math.max(-100, Math.min(100, contrast));
199
- // 将范围转换为适合计算的值
200
- const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
201
- const briAdjust = (brightness / 100) * 255;
202
- const data = imageData.data;
203
- const length = data.length;
204
- for (let i = 0; i < length; i += 4) {
205
- // 分别处理 RGB 三个通道
206
- for (let j = 0; j < 3; j++) {
207
- // 应用亮度和对比度调整公式
208
- const newValue = factor * (data[i + j] + briAdjust - 128) + 128;
209
- data[i + j] = Math.max(0, Math.min(255, newValue));
210
- }
211
- // Alpha 通道保持不变
212
- }
213
- return imageData;
214
- }
215
- /**
216
- * 将图像转换为灰度图
217
- *
218
- * @param imageData 原始图像数据
219
- * @returns 灰度图像数据
220
- */
221
- static toGrayscale(imageData) {
222
- const data = imageData.data;
223
- const length = data.length;
224
- for (let i = 0; i < length; i += 4) {
225
- // 使用加权平均法将 RGB 转换为灰度值
226
- const gray = data[i] * 0.3 + data[i + 1] * 0.59 + data[i + 2] * 0.11;
227
- data[i] = data[i + 1] = data[i + 2] = gray;
228
- }
229
- return imageData;
230
- }
231
- /**
232
- * 锐化图像
233
- *
234
- * @param imageData 原始图像数据
235
- * @param amount 锐化程度,默认为2
236
- * @returns 锐化后的图像数据
237
- */
238
- static sharpen(imageData, amount = 2) {
239
- if (!imageData || !imageData.data)
240
- return imageData;
241
- const width = imageData.width;
242
- const height = imageData.height;
243
- const data = imageData.data;
244
- const outputData = new Uint8ClampedArray(data.length);
245
- // 锐化卷积核
246
- const kernel = [
247
- 0,
248
- -amount,
249
- 0,
250
- -amount,
251
- 1 + 4 * amount,
252
- -amount,
253
- 0,
254
- -amount,
255
- 0,
256
- ];
257
- // 应用卷积
258
- for (let y = 1; y < height - 1; y++) {
259
- for (let x = 1; x < width - 1; x++) {
260
- const pos = (y * width + x) * 4;
261
- // 对每个通道应用卷积
262
- for (let c = 0; c < 3; c++) {
263
- let val = 0;
264
- for (let ky = -1; ky <= 1; ky++) {
265
- for (let kx = -1; kx <= 1; kx++) {
266
- const kernelPos = (ky + 1) * 3 + (kx + 1);
267
- const dataPos = ((y + ky) * width + (x + kx)) * 4 + c;
268
- val += data[dataPos] * kernel[kernelPos];
269
- }
270
- }
271
- outputData[pos + c] = Math.max(0, Math.min(255, val));
272
- }
273
- outputData[pos + 3] = data[pos + 3]; // 保持透明度不变
274
- }
275
- }
276
- // 处理边缘像素
277
- for (let y = 0; y < height; y++) {
278
- for (let x = 0; x < width; x++) {
279
- if (y === 0 || y === height - 1 || x === 0 || x === width - 1) {
280
- const pos = (y * width + x) * 4;
281
- outputData[pos] = data[pos];
282
- outputData[pos + 1] = data[pos + 1];
283
- outputData[pos + 2] = data[pos + 2];
284
- outputData[pos + 3] = data[pos + 3];
285
- }
286
- }
287
- }
288
- // 创建新的ImageData对象
289
- return new ImageData(outputData, width, height);
290
- }
291
- /**
292
- * 对图像应用阈值操作,增强对比度
293
- *
294
- * @param imageData 原始图像数据
295
- * @param threshold 阈值 (0-255)
296
- * @returns 处理后的图像数据
297
- */
298
- static threshold(imageData, threshold = 128) {
299
- // 先转换为灰度图
300
- const grayscaleImage = this.toGrayscale(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height));
301
- const data = grayscaleImage.data;
302
- const length = data.length;
303
- for (let i = 0; i < length; i += 4) {
304
- // 二值化处理
305
- const value = data[i] < threshold ? 0 : 255;
306
- data[i] = data[i + 1] = data[i + 2] = value;
307
- }
308
- return grayscaleImage;
309
- }
310
- /**
311
- * 将图像转换为黑白图像(二值化)
312
- *
313
- * @param imageData 原始图像数据
314
- * @returns 二值化后的图像数据
315
- */
316
- static toBinaryImage(imageData) {
317
- // 先转换为灰度图
318
- const grayscaleImage = this.toGrayscale(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height));
319
- // 使用OTSU算法自动确定阈值
320
- const threshold = this.getOtsuThreshold(grayscaleImage);
321
- return this.threshold(grayscaleImage, threshold);
322
- }
323
- /**
324
- * 使用OTSU算法计算最佳阈值
325
- *
326
- * @param imageData 灰度图像数据
327
- * @returns 最佳阈值
328
- */
329
- static getOtsuThreshold(imageData) {
330
- const data = imageData.data;
331
- const histogram = new Array(256).fill(0);
332
- // 统计灰度直方图
333
- for (let i = 0; i < data.length; i += 4) {
334
- histogram[data[i]]++;
335
- }
336
- const total = imageData.width * imageData.height;
337
- let sum = 0;
338
- // 计算总灰度值和
339
- for (let i = 0; i < 256; i++) {
340
- sum += i * histogram[i];
341
- }
342
- let sumB = 0;
343
- let wB = 0;
344
- let wF = 0;
345
- let maxVariance = 0;
346
- let threshold = 0;
347
- // 遍历所有可能的阈值,找到最大类间方差
348
- for (let t = 0; t < 256; t++) {
349
- wB += histogram[t]; // 背景权重
350
- if (wB === 0)
351
- continue;
352
- wF = total - wB; // 前景权重
353
- if (wF === 0)
354
- break;
355
- sumB += t * histogram[t];
356
- const mB = sumB / wB; // 背景平均灰度
357
- const mF = (sum - sumB) / wF; // 前景平均灰度
358
- // 计算类间方差
359
- const variance = wB * wF * (mB - mF) * (mB - mF);
360
- if (variance > maxVariance) {
361
- maxVariance = variance;
362
- threshold = t;
363
- }
364
- }
365
- return threshold;
366
- }
367
- /**
368
- * 批量应用图像处理
369
- *
370
- * @param imageData 原始图像数据
371
- * @param options 处理选项
372
- * @returns 处理后的图像数据
373
- */
374
- static batchProcess(imageData, options) {
375
- let processedImage = new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height);
376
- // 应用亮度和对比度调整
377
- if (options.brightness !== undefined || options.contrast !== undefined) {
378
- processedImage = this.adjustBrightnessContrast(processedImage, options.brightness || 0, options.contrast || 0);
379
- }
380
- // 应用灰度转换
381
- if (options.grayscale) {
382
- processedImage = this.toGrayscale(processedImage);
383
- }
384
- // 应用锐化
385
- if (options.sharpen) {
386
- processedImage = this.sharpen(processedImage);
387
- }
388
- // 应用颜色反转
389
- if (options.invert) {
390
- const data = processedImage.data;
391
- for (let i = 0; i < data.length; i += 4) {
392
- // 反转RGB值
393
- data[i] = 255 - data[i];
394
- data[i + 1] = 255 - data[i + 1];
395
- data[i + 2] = 255 - data[i + 2];
396
- // Alpha通道保持不变
397
- }
398
- }
399
- return processedImage;
400
- }
401
- /**
402
- * 压缩图片文件
403
- *
404
- * @param file 图片文件
405
- * @param options 压缩选项
406
- * @returns Promise<File> 压缩后的文件
407
- */
408
- static async compressImage(file, options) {
409
- const defaultOptions = {
410
- maxSizeMB: 1,
411
- maxWidthOrHeight: 1920,
412
- useWebWorker: true,
413
- quality: 0.8,
414
- fileType: file.type || "image/jpeg",
415
- };
416
- const compressOptions = { ...defaultOptions, ...options };
417
- try {
418
- return await imageCompression(file, compressOptions);
419
- }
420
- catch (error) {
421
- console.error("图片压缩失败:", error);
422
- return file; // 如果压缩失败,返回原始文件
423
- }
424
- }
425
- /**
426
- * 从图片文件创建ImageData
427
- *
428
- * @param file 图片文件
429
- * @returns Promise<ImageData>
430
- */
431
- static async createImageDataFromFile(file) {
432
- return new Promise((resolve, reject) => {
433
- try {
434
- const img = new Image();
435
- const url = URL.createObjectURL(file);
436
- img.onload = () => {
437
- try {
438
- // 创建canvas元素
439
- const canvas = document.createElement("canvas");
440
- const ctx = canvas.getContext("2d");
441
- if (!ctx) {
442
- reject(new Error("无法创建2D上下文"));
443
- return;
444
- }
445
- canvas.width = img.width;
446
- canvas.height = img.height;
447
- // 绘制图片到canvas
448
- ctx.drawImage(img, 0, 0);
449
- // 获取图像数据
450
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
451
- // 释放资源
452
- URL.revokeObjectURL(url);
453
- resolve(imageData);
454
- }
455
- catch (e) {
456
- reject(e);
457
- }
458
- };
459
- img.onerror = () => {
460
- URL.revokeObjectURL(url);
461
- reject(new Error("图片加载失败"));
462
- };
463
- img.src = url;
464
- }
465
- catch (error) {
466
- reject(error);
467
- }
468
- });
469
- }
470
- /**
471
- * 将ImageData转换为File对象
472
- *
473
- * @param imageData ImageData对象
474
- * @param fileName 输出文件名
475
- * @param fileType 输出文件类型
476
- * @param quality 图片质量 (0-1)
477
- * @returns Promise<File>
478
- */
479
- static async imageDataToFile(imageData, fileName = "image.jpg", fileType = "image/jpeg", quality = 0.8) {
480
- return new Promise((resolve, reject) => {
481
- try {
482
- const canvas = document.createElement("canvas");
483
- canvas.width = imageData.width;
484
- canvas.height = imageData.height;
485
- const ctx = canvas.getContext("2d");
486
- if (!ctx) {
487
- reject(new Error("无法创建2D上下文"));
488
- return;
489
- }
490
- ctx.putImageData(imageData, 0, 0);
491
- canvas.toBlob((blob) => {
492
- if (!blob) {
493
- reject(new Error("无法创建图片Blob"));
494
- return;
495
- }
496
- const file = new File([blob], fileName, { type: fileType });
497
- resolve(file);
498
- }, fileType, quality);
499
- }
500
- catch (error) {
501
- reject(error);
502
- }
503
- });
504
- }
505
- /**
506
- * 调整图像大小
507
- *
508
- * @param imageData 原始图像数据
509
- * @param maxWidth 最大宽度
510
- * @param maxHeight 最大高度
511
- * @param maintainAspectRatio 是否保持宽高比
512
- * @returns ImageData 调整大小后的图像数据
513
- */
514
- static resizeImage(imageData, maxWidth, maxHeight, maintainAspectRatio = true) {
515
- const { width, height } = imageData;
516
- // 如果图像已经小于指定大小,则不需要调整
517
- if (width <= maxWidth && height <= maxHeight) {
518
- return imageData;
519
- }
520
- let newWidth = maxWidth;
521
- let newHeight = maxHeight;
522
- // 计算新的尺寸,保持宽高比
523
- if (maintainAspectRatio) {
524
- const ratio = Math.min(maxWidth / width, maxHeight / height);
525
- newWidth = Math.floor(width * ratio);
526
- newHeight = Math.floor(height * ratio);
527
- }
528
- // 创建用于调整大小的Canvas
529
- const canvas = document.createElement("canvas");
530
- canvas.width = newWidth;
531
- canvas.height = newHeight;
532
- const ctx = canvas.getContext("2d");
533
- if (!ctx) {
534
- throw new Error("无法创建2D上下文");
535
- }
536
- // 创建临时Canvas绘制原始ImageData
537
- const tempCanvas = document.createElement("canvas");
538
- tempCanvas.width = width;
539
- tempCanvas.height = height;
540
- const tempCtx = tempCanvas.getContext("2d");
541
- if (!tempCtx) {
542
- throw new Error("无法创建临时2D上下文");
543
- }
544
- tempCtx.putImageData(imageData, 0, 0);
545
- // 使用缩放平滑算法
546
- ctx.imageSmoothingEnabled = true;
547
- ctx.imageSmoothingQuality = "high";
548
- // 绘制调整大小的图像
549
- ctx.drawImage(tempCanvas, 0, 0, width, height, 0, 0, newWidth, newHeight);
550
- // 获取新的ImageData
551
- return ctx.getImageData(0, 0, newWidth, newHeight);
552
- }
553
- }
554
-
555
- /**
556
- * @file 性能优化工具类
557
- * @description 提供节流、防抖、缓存等性能优化功能
558
- * @module PerformanceUtils
559
- */
560
- /**
561
- * 节流函数:限制函数在一定时间内只能执行一次
562
- *
563
- * @param fn 需要节流的函数
564
- * @param delay 延迟时间(毫秒)
565
- * @returns 节流处理后的函数
566
- */
567
- function throttle(fn, delay) {
568
- let lastCall = 0;
569
- let timeoutId = null;
570
- return function (...args) {
571
- const now = Date.now();
572
- const remaining = delay - (now - lastCall);
573
- if (remaining <= 0) {
574
- if (timeoutId) {
575
- clearTimeout(timeoutId);
576
- timeoutId = null;
577
- }
578
- lastCall = now;
579
- fn.apply(this, args);
580
- }
581
- else if (!timeoutId) {
582
- timeoutId = window.setTimeout(() => {
583
- lastCall = Date.now();
584
- timeoutId = null;
585
- fn.apply(this, args);
586
- }, remaining);
587
- }
588
- };
589
- }
590
- /**
591
- * LRU缓存类 - 使用最近最少使用策略的缓存实现
592
- */
593
- class LRUCache {
594
- /**
595
- * 构造LRU缓存
596
- * @param maxSize 缓存最大容量
597
- */
598
- constructor(maxSize = 100) {
599
- this.maxSize = maxSize;
600
- this.cache = new Map();
601
- }
602
- /**
603
- * 获取缓存项
604
- * @param key 缓存键
605
- * @returns 缓存值或undefined
606
- */
607
- get(key) {
608
- if (!this.cache.has(key)) {
609
- return undefined;
610
- }
611
- // 获取值
612
- const value = this.cache.get(key);
613
- // 将项移至最新位置(删除后重新添加)
614
- this.cache.delete(key);
615
- this.cache.set(key, value);
616
- return value;
617
- }
618
- /**
619
- * 设置缓存项
620
- * @param key 缓存键
621
- * @param value 缓存值
622
- */
623
- set(key, value) {
624
- // 如果键已存在,需要先删除
625
- if (this.cache.has(key)) {
626
- this.cache.delete(key);
627
- }
628
- // 如果缓存已满,移除最老的项
629
- if (this.cache.size >= this.maxSize) {
630
- const oldestKey = this.cache.keys().next().value;
631
- if (oldestKey !== undefined) {
632
- this.cache.delete(oldestKey);
633
- }
634
- }
635
- // 添加新项
636
- this.cache.set(key, value);
637
- }
638
- /**
639
- * 删除缓存项
640
- * @param key 缓存键
641
- * @returns 是否成功删除
642
- */
643
- delete(key) {
644
- return this.cache.delete(key);
645
- }
646
- /**
647
- * 清空缓存
648
- */
649
- clear() {
650
- this.cache.clear();
651
- }
652
- /**
653
- * 获取当前缓存大小
654
- */
655
- get size() {
656
- return this.cache.size;
657
- }
658
- /**
659
- * 检查键是否存在
660
- * @param key 缓存键
661
- */
662
- has(key) {
663
- return this.cache.has(key);
664
- }
665
- }
666
- /**
667
- * 图像指纹计算函数 - 用于检测相同或相似图像
668
- *
669
- * @param imageData 图像数据
670
- * @param size 指纹尺寸(默认8x8)
671
- * @returns 图像指纹字符串
672
- */
673
- function calculateImageFingerprint(imageData, size = 8) {
674
- // 1. 缩小图像到指定尺寸
675
- const canvas = document.createElement('canvas');
676
- canvas.width = size;
677
- canvas.height = size;
678
- const ctx = canvas.getContext('2d');
679
- if (!ctx) {
680
- return '';
681
- }
682
- // 创建一个临时canvas来绘制原始imageData
683
- const tempCanvas = document.createElement('canvas');
684
- tempCanvas.width = imageData.width;
685
- tempCanvas.height = imageData.height;
686
- const tempCtx = tempCanvas.getContext('2d');
687
- if (!tempCtx) {
688
- return '';
689
- }
690
- tempCtx.putImageData(imageData, 0, 0);
691
- // 缩小到目标尺寸
692
- ctx.drawImage(tempCanvas, 0, 0, imageData.width, imageData.height, 0, 0, size, size);
693
- // 2. 转换为灰度
694
- const smallImgData = ctx.getImageData(0, 0, size, size);
695
- const grayValues = [];
696
- for (let i = 0; i < smallImgData.data.length; i += 4) {
697
- const r = smallImgData.data[i];
698
- const g = smallImgData.data[i + 1];
699
- const b = smallImgData.data[i + 2];
700
- // 转为灰度: 0.299r + 0.587g + 0.114b
701
- const gray = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
702
- grayValues.push(gray);
703
- }
704
- // 3. 计算平均值
705
- const avg = grayValues.reduce((sum, val) => sum + val, 0) / grayValues.length;
706
- // 4. 比较每个像素与平均值,生成二进制指纹
707
- let fingerprint = '';
708
- for (const gray of grayValues) {
709
- fingerprint += gray >= avg ? '1' : '0';
710
- }
711
- return fingerprint;
712
- }
713
-
714
- /**
715
- * @file 身份证检测模块
716
- * @description 提供自动检测和定位图像中的身份证功能
717
- * @module IDCardDetector
718
- */
719
- /**
720
- * 身份证检测器类
721
- *
722
- * 通过图像处理和计算机视觉技术,实时检测视频流中的身份证,并提取身份证区域
723
- * 注意:当前实现是简化版,实际项目中建议使用OpenCV.js进行更精确的检测
724
- *
725
- * @example
726
- * ```typescript
727
- * // 创建身份证检测器
728
- * const detector = new IDCardDetector((result) => {
729
- * if (result.success && result.croppedImage) {
730
- * console.log('检测到身份证!');
731
- * // 对裁剪出的身份证图像进行处理
732
- * processIDCardImage(result.croppedImage);
733
- * }
734
- * });
735
- *
736
- * // 启动检测
737
- * const videoElement = document.getElementById('video') as HTMLVideoElement;
738
- * await detector.start(videoElement);
739
- *
740
- * // 停止检测
741
- * detector.stop();
742
- * ```
743
- */
744
- class IDCardDetector {
745
- /**
746
- * 创建身份证检测器实例
747
- *
748
- * @param options 身份证检测器配置选项,或者检测回调函数
749
- */
750
- constructor(options) {
751
- this.detecting = false;
752
- this.detectTimer = null;
753
- this.frameCount = 0;
754
- this.lastDetectionTime = 0;
755
- this.camera = new Camera();
756
- if (typeof options === "function") {
757
- // 兼容旧的构造函数方式
758
- this.onDetected = options;
759
- this.options = {
760
- detectionInterval: 200,
761
- maxImageDimension: 800,
762
- enableCache: true,
763
- cacheSize: 20,
764
- logger: console.log,
765
- };
766
- }
767
- else if (options) {
768
- // 使用新的选项对象方式
769
- this.options = {
770
- detectionInterval: 200,
771
- maxImageDimension: 800,
772
- enableCache: true,
773
- cacheSize: 20,
774
- logger: console.log,
775
- ...options,
776
- };
777
- this.onDetected = options.onDetection;
778
- this.onError = options.onError;
779
- }
780
- else {
781
- this.options = {
782
- detectionInterval: 200,
783
- maxImageDimension: 800,
784
- enableCache: true,
785
- cacheSize: 20,
786
- logger: console.log,
787
- };
788
- }
789
- this.detectionInterval = this.options.detectionInterval;
790
- this.maxImageDimension = this.options.maxImageDimension;
791
- // 初始化结果缓存
792
- this.resultCache = new LRUCache(this.options.cacheSize);
793
- // 创建节流版本的检测函数
794
- this.throttledDetect = throttle(this.performDetection.bind(this), this.detectionInterval);
795
- }
796
- /**
797
- * 启动身份证检测
798
- *
799
- * 初始化相机并开始连续检测视频帧中的身份证
800
- *
801
- * @param {HTMLVideoElement} videoElement - 用于显示相机画面的video元素
802
- * @returns {Promise<void>} 启动完成的Promise
803
- */
804
- async start(videoElement) {
805
- await this.camera.initialize(videoElement);
806
- this.detecting = true;
807
- this.frameCount = 0;
808
- this.lastDetectionTime = 0;
809
- this.detect();
810
- }
811
- /**
812
- * 停止身份证检测
813
- */
814
- stop() {
815
- this.detecting = false;
816
- if (this.detectTimer !== null) {
817
- cancelAnimationFrame(this.detectTimer);
818
- this.detectTimer = null;
819
- }
820
- }
821
- /**
822
- * 持续检测视频帧
823
- *
824
- * @private
825
- */
826
- detect() {
827
- if (!this.detecting)
828
- return;
829
- this.detectTimer = requestAnimationFrame(() => {
830
- try {
831
- this.frameCount++;
832
- const now = performance.now();
833
- // 帧率控制 - 只有满足时间间隔的帧才进行检测
834
- // 这样可以显著减少CPU使用率,同时保持良好的用户体验
835
- if (this.frameCount % 3 === 0 ||
836
- now - this.lastDetectionTime >= this.detectionInterval) {
837
- this.throttledDetect();
838
- this.lastDetectionTime = now;
839
- }
840
- // 继续下一帧检测
841
- this.detect();
842
- }
843
- catch (error) {
844
- if (this.onError) {
845
- this.onError(error);
846
- }
847
- else {
848
- console.error("身份证检测错误:", error);
849
- }
850
- // 出错后延迟重试
851
- setTimeout(() => {
852
- if (this.detecting) {
853
- this.detect();
854
- }
855
- }, 1000);
856
- }
857
- });
858
- }
859
- /**
860
- * 执行单帧检测
861
- *
862
- * @private
863
- */
864
- async performDetection() {
865
- if (!this.detecting || !this.camera)
866
- return;
867
- // 获取当前视频帧
868
- const frame = this.camera.captureFrame();
869
- if (!frame)
870
- return;
871
- // 检查缓存
872
- if (this.options.enableCache) {
873
- const fingerprint = calculateImageFingerprint(frame, 16); // 使用更大的尺寸提高特征区分度
874
- const cachedResult = this.resultCache.get(fingerprint);
875
- if (cachedResult) {
876
- this.options.logger?.("使用缓存的检测结果");
877
- // 使用缓存结果,但更新图像数据以确保最新
878
- const updatedResult = {
879
- ...cachedResult,
880
- imageData: frame,
881
- };
882
- if (this.onDetected) {
883
- this.onDetected(updatedResult);
884
- }
885
- return;
886
- }
887
- }
888
- // 降低分辨率以提高性能
889
- const downsampledFrame = ImageProcessor.resizeImage(frame, this.maxImageDimension, this.maxImageDimension);
890
- try {
891
- // 检测身份证
892
- const result = await this.detectIDCard(downsampledFrame);
893
- // 如果检测成功,将原始图像添加到结果中
894
- if (result.success) {
895
- result.imageData = frame;
896
- // 缓存结果
897
- if (this.options.enableCache) {
898
- const fingerprint = calculateImageFingerprint(frame, 16);
899
- this.resultCache.set(fingerprint, result);
900
- }
901
- }
902
- // 处理检测结果
903
- if (this.onDetected) {
904
- this.onDetected(result);
905
- }
906
- }
907
- catch (error) {
908
- if (this.onError) {
909
- this.onError(error);
910
- }
911
- else {
912
- console.error("身份证检测错误:", error);
913
- }
914
- }
915
- }
916
- /**
917
- * 检测图像中的身份证
918
- *
919
- * @private
920
- * @param {ImageData} imageData - 要分析的图像数据
921
- * @returns {Promise<DetectionResult>} 检测结果
922
- */
923
- async detectIDCard(imageData) {
924
- // 1. 图像预处理
925
- ImageProcessor.toGrayscale(imageData);
926
- // 2. 检测矩形和边缘(简化版实现)
927
- // 注意:实际应用中应使用OpenCV.js或其他计算机视觉库进行更精确的检测
928
- // 此处仅作为概念性实现,使用基本矩形检测逻辑
929
- // 模拟检测过程,随机判断是否找到身份证
930
- // 在实际应用中,此处应当实现实际的计算机视觉算法
931
- const detectionResult = {
932
- success: Math.random() > 0.3, // 70%的概率成功检测到
933
- message: "身份证检测完成",
934
- };
935
- if (detectionResult.success) {
936
- // 模拟一个身份证矩形区域
937
- const width = imageData.width;
938
- const height = imageData.height;
939
- // 大致的身份证区域(按比例)
940
- const rectWidth = Math.round(width * 0.7);
941
- const rectHeight = Math.round(rectWidth * 0.618); // 身份证是黄金比例
942
- const rectX = Math.round((width - rectWidth) / 2);
943
- const rectY = Math.round((height - rectHeight) / 2);
944
- // 添加四个角点
945
- detectionResult.corners = [
946
- { x: rectX, y: rectY },
947
- { x: rectX + rectWidth, y: rectY },
948
- { x: rectX + rectWidth, y: rectY + rectHeight },
949
- { x: rectX, y: rectY + rectHeight },
950
- ];
951
- // 添加边界框
952
- detectionResult.boundingBox = {
953
- x: rectX,
954
- y: rectY,
955
- width: rectWidth,
956
- height: rectHeight,
957
- };
958
- // 裁剪身份证图像
959
- const canvas = document.createElement("canvas");
960
- canvas.width = rectWidth;
961
- canvas.height = rectHeight;
962
- const ctx = canvas.getContext("2d");
963
- if (ctx) {
964
- const tempCanvas = ImageProcessor.imageDataToCanvas(imageData);
965
- ctx.drawImage(tempCanvas, rectX, rectY, rectWidth, rectHeight, 0, 0, rectWidth, rectHeight);
966
- detectionResult.croppedImage = ctx.getImageData(0, 0, rectWidth, rectHeight);
967
- }
968
- // 设置置信度
969
- detectionResult.confidence = 0.7 + Math.random() * 0.3;
970
- }
971
- return detectionResult;
972
- }
973
- /**
974
- * 清除检测结果缓存
975
- */
976
- clearCache() {
977
- this.resultCache.clear();
978
- this.options.logger?.("检测结果缓存已清除");
979
- }
980
- /**
981
- * 释放资源
982
- */
983
- dispose() {
984
- this.stop();
985
- this.camera.release();
986
- this.resultCache.clear();
987
- }
988
- }
989
-
990
- /**
991
- * @file Web Worker辅助工具类
992
- * @description 提供Worker线程管理功能,用于将计算密集型任务移至后台线程
993
- * @module WorkerUtils
994
- */
995
- /**
996
- * 创建Worker线程并处理消息通信
997
- *
998
- * @param workerFunction 要在Worker中执行的函数
999
- * @returns 返回包含发送消息方法的Worker控制对象
1000
- */
1001
- function createWorker(workerFunction) {
1002
- // 将函数转换为字符串,然后创建一个Blob URL
1003
- const workerCode = `
1004
- self.onmessage = async function(e) {
1005
- try {
1006
- const result = await (${workerFunction.toString()})(e.data);
1007
- self.postMessage({ success: true, result });
1008
- } catch (error) {
1009
- self.postMessage({
1010
- success: false,
1011
- error: { message: error.message, stack: error.stack }
1012
- });
1013
- }
1014
- }
1015
- `;
1016
- const blob = new Blob([workerCode], { type: 'application/javascript' });
1017
- const workerUrl = URL.createObjectURL(blob);
1018
- const worker = new Worker(workerUrl);
1019
- // 创建一个映射来存储待解析的Promise
1020
- const promiseMap = new Map();
1021
- let messageCounter = 0;
1022
- worker.onmessage = (e) => {
1023
- // 释放Blob URL
1024
- if (promiseMap.size === 0) {
1025
- URL.revokeObjectURL(workerUrl);
1026
- }
1027
- const { id, success, result, error } = e.data;
1028
- const promiseHandlers = promiseMap.get(id);
1029
- if (promiseHandlers) {
1030
- promiseMap.delete(id);
1031
- if (success) {
1032
- promiseHandlers.resolve(result);
1033
- }
1034
- else {
1035
- const workerError = new Error(error.message);
1036
- workerError.stack = error.stack;
1037
- promiseHandlers.reject(workerError);
1038
- }
1039
- }
1040
- };
1041
- return {
1042
- postMessage: (data) => {
1043
- return new Promise((resolve, reject) => {
1044
- const id = messageCounter++;
1045
- promiseMap.set(id, { resolve, reject });
1046
- worker.postMessage({ id, data });
1047
- });
1048
- },
1049
- terminate: () => {
1050
- worker.terminate();
1051
- promiseMap.clear();
1052
- URL.revokeObjectURL(workerUrl);
1053
- }
1054
- };
1055
- }
1056
- /**
1057
- * 判断浏览器是否支持Web Workers
1058
- *
1059
- * @returns 是否支持Web Workers
1060
- */
1061
- function isWorkerSupported() {
1062
- return typeof Worker !== 'undefined';
1063
- }
1064
-
1065
- /**
1066
- * @file OCR Worker处理模块
1067
- * @description 用于在Web Worker中执行OCR处理
1068
- * @module OCRWorker
1069
- */
1070
- /**
1071
- * 在Web Worker中执行OCR处理的函数
1072
- *
1073
- * 该函数用于在使用 createWorker 创建的 Worker 中执行
1074
- *
1075
- * @param input OCR处理输入数据
1076
- * @returns OCR处理结果
1077
- */
1078
- async function processOCRInWorker(input) {
1079
- // 计时开始
1080
- const startTime = performance.now();
1081
- // 加载Tesseract.js (Worker 环境下动态导入)
1082
- const { createWorker } = await import('tesseract.js');
1083
- // 创建OCR Worker
1084
- const worker = (await createWorker(input.tessWorkerOptions || {
1085
- logger: (m) => console.log(m),
1086
- })); // 添加类型断言,避免TypeScript错误
1087
- try {
1088
- // 初始化OCR引擎
1089
- await worker.load();
1090
- await worker.loadLanguage("chi_sim");
1091
- await worker.initialize("chi_sim");
1092
- await worker.setParameters({
1093
- tessedit_char_whitelist: "0123456789X-年月日一二三四五六七八九十零壹贰叁肆伍陆柒捌玖拾ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz民族汉族满族回族维吾尔族藏族苗族彝族壮族朝鲜族侗族瑶族白族土家族哈尼族哈萨克族傣族黎族傈僳族佤族高山族拉祜族水族东乡族钠西族景颇族柯尔克孜族士族达斡尔族仫佬族羌族布朗族撒拉族毛南族仡佬族锡伯族阿昌族普米族塔吉克族怒族乌孜别克族俄罗斯族鄂温克族德昂族保安族裕固族京族塔塔尔族独龙族鄂伦春族赫哲族门巴族珞巴族基诺族男女性别住址出生公民身份号码签发机关有效期",
1094
- });
1095
- // 识别图像
1096
- const { data } = await worker.recognize(input.imageBase64);
1097
- // 解析识别结果
1098
- const idCardInfo = parseIDCardText(data.text);
1099
- // 处理完成后终止worker
1100
- await worker.terminate();
1101
- // 计算处理时间
1102
- const processingTime = performance.now() - startTime;
1103
- // 返回处理结果
1104
- return {
1105
- idCardInfo,
1106
- processingTime,
1107
- };
1108
- }
1109
- catch (error) {
1110
- // 确保资源被释放
1111
- await worker.terminate();
1112
- throw error;
1113
- }
1114
- }
1115
- /**
1116
- * 解析身份证文本信息
1117
- *
1118
- * 从OCR识别到的文本中提取结构化的身份证信息
1119
- *
1120
- * @private
1121
- * @param {string} text - OCR识别到的文本
1122
- * @returns {IDCardInfo} 提取到的身份证信息对象
1123
- */
1124
- function parseIDCardText(text) {
1125
- const info = {};
1126
- // 拆分为行
1127
- const lines = text.split("\n").filter((line) => line.trim());
1128
- // 解析身份证号码(最容易识别的部分)
1129
- const idNumberRegex = /(\d{17}[\dX])/;
1130
- const idNumberMatch = text.match(idNumberRegex);
1131
- if (idNumberMatch) {
1132
- info.idNumber = idNumberMatch[1];
1133
- }
1134
- // 解析姓名
1135
- for (const line of lines) {
1136
- if (line.includes("姓名") ||
1137
- (line.length < 10 && line.length > 1 && !/\d/.test(line))) {
1138
- info.name = line.replace("姓名", "").trim();
1139
- break;
1140
- }
1141
- }
1142
- // 解析性别和民族
1143
- const genderNationalityRegex = /(男|女).*(族)/;
1144
- const genderMatch = text.match(genderNationalityRegex);
1145
- if (genderMatch) {
1146
- info.gender = genderMatch[1];
1147
- const nationalityText = genderMatch[0];
1148
- info.nationality = nationalityText
1149
- .substring(nationalityText.indexOf(genderMatch[1]) + 1)
1150
- .trim();
1151
- }
1152
- // 解析出生日期
1153
- const birthDateRegex = /(\d{4})年(\d{1,2})月(\d{1,2})日/;
1154
- const birthDateMatch = text.match(birthDateRegex);
1155
- if (birthDateMatch) {
1156
- info.birthDate = `${birthDateMatch[1]}-${birthDateMatch[2]}-${birthDateMatch[3]}`;
1157
- }
1158
- // 解析地址
1159
- const addressRegex = /住址([\s\S]*?)公民身份号码/;
1160
- const addressMatch = text.match(addressRegex);
1161
- if (addressMatch) {
1162
- info.address = addressMatch[1].replace(/\n/g, "").trim();
1163
- }
1164
- // 解析签发机关
1165
- const authorityRegex = /签发机关([\s\S]*?)有效期/;
1166
- const authorityMatch = text.match(authorityRegex);
1167
- if (authorityMatch) {
1168
- info.issuingAuthority = authorityMatch[1].replace(/\n/g, "").trim();
1169
- }
1170
- // 解析有效期限
1171
- const validPeriodRegex = /有效期限([\s\S]*?)(-|至)/;
1172
- const validPeriodMatch = text.match(validPeriodRegex);
1173
- if (validPeriodMatch) {
1174
- info.validPeriod = validPeriodMatch[0].replace("有效期限", "").trim();
1175
- }
1176
- return info;
1177
- }
1178
-
1179
- /**
1180
- * @file OCR处理模块
1181
- * @description 提供身份证文字识别和信息提取功能
1182
- * @module OCRProcessor
1183
- */
1184
- /**
1185
- * OCR处理器类
1186
- *
1187
- * 使用Tesseract.js实现对身份证图像的OCR文字识别和信息提取功能
1188
- *
1189
- * @example
1190
- * ```typescript
1191
- * // 创建OCR处理器
1192
- * const ocrProcessor = new OCRProcessor();
1193
- *
1194
- * // 初始化OCR引擎
1195
- * await ocrProcessor.initialize();
1196
- *
1197
- * // 处理身份证图像
1198
- * const idInfo = await ocrProcessor.processIDCard(idCardImageData);
1199
- * console.log('识别到的身份证信息:', idInfo);
1200
- *
1201
- * // 使用结束后释放资源
1202
- * await ocrProcessor.terminate();
1203
- * ```
1204
- */
1205
- class OCRProcessor {
1206
- /**
1207
- * 创建OCR处理器实例
1208
- *
1209
- * @param options OCR处理器选项
1210
- */
1211
- constructor(options = {}) {
1212
- this.worker = null;
1213
- this.ocrWorker = null;
1214
- this.initialized = false;
1215
- this.options = {
1216
- useWorker: isWorkerSupported(),
1217
- enableCache: true,
1218
- cacheSize: 50,
1219
- maxImageDimension: 1000,
1220
- logger: console.log,
1221
- ...options,
1222
- };
1223
- // 初始化缓存
1224
- this.resultCache = new LRUCache(this.options.cacheSize);
1225
- }
1226
- /**
1227
- * 初始化OCR引擎
1228
- *
1229
- * 加载Tesseract OCR引擎和中文简体语言包,并设置适合身份证识别的参数
1230
- *
1231
- * @returns {Promise<void>} 初始化完成的Promise
1232
- */
1233
- async initialize() {
1234
- if (this.initialized)
1235
- return;
1236
- if (this.options.useWorker) {
1237
- // 使用自定义Worker线程处理OCR
1238
- this.ocrWorker = createWorker(processOCRInWorker);
1239
- this.initialized = true;
1240
- this.options.logger?.("OCR Worker 初始化完成");
1241
- }
1242
- else {
1243
- // 使用主线程处理OCR
1244
- this.worker = createWorker$1({
1245
- logger: this.options.logger,
1246
- });
1247
- await this.worker.load();
1248
- await this.worker.loadLanguage("chi_sim");
1249
- await this.worker.initialize("chi_sim");
1250
- await this.worker.setParameters({
1251
- tessedit_char_whitelist: "0123456789X-年月日一二三四五六七八九十零壹贰叁肆伍陆柒捌玖拾ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz民族汉族满族回族维吾尔族藏族苗族彝族壮族朝鲜族侗族瑶族白族土家族哈尼族哈萨克族傣族黎族傈僳族佤族高山族拉祜族水族东乡族钠西族景颇族柯尔克孜族士族达斡尔族仫佬族羌族布朗族撒拉族毛南族仡佬族锡伯族阿昌族普米族塔吉克族怒族乌孜别克族俄罗斯族鄂温克族德昂族保安族裕固族京族塔塔尔族独龙族鄂伦春族赫哲族门巴族珞巴族基诺族男女性别住址出生公民身份号码签发机关有效期",
1252
- });
1253
- this.initialized = true;
1254
- this.options.logger?.("OCR引擎初始化完成");
1255
- }
1256
- }
1257
- /**
1258
- * 处理身份证图像并提取信息
1259
- * @param imageData 要处理的身份证图像数据
1260
- * @returns 提取的身份证信息
1261
- */
1262
- async processIDCard(imageData) {
1263
- if (!this.initialized) {
1264
- await this.initialize();
1265
- }
1266
- // 计算图像指纹,用于缓存查找
1267
- if (this.options.enableCache) {
1268
- const fingerprint = calculateImageFingerprint(imageData);
1269
- // 检查缓存中是否有结果
1270
- const cachedResult = this.resultCache.get(fingerprint);
1271
- if (cachedResult) {
1272
- this.options.logger?.("使用缓存的OCR结果");
1273
- return cachedResult;
1274
- }
1275
- }
1276
- // 调整图像大小以提高性能和准确性
1277
- const downsampledImage = ImageProcessor.resizeImage(imageData, this.options.maxImageDimension || 1000, this.options.maxImageDimension || 1000, true // 保持宽高比
1278
- );
1279
- // 提高图像质量以获得更好的OCR结果
1280
- const enhancedImage = ImageProcessor.batchProcess(downsampledImage, {
1281
- brightness: this.options.brightness || 15,
1282
- contrast: this.options.contrast || 25,
1283
- sharpen: true,
1284
- });
1285
- // 转换为base64供Tesseract处理
1286
- // 创建一个canvas元素
1287
- const canvas = document.createElement("canvas");
1288
- canvas.width = enhancedImage.width;
1289
- canvas.height = enhancedImage.height;
1290
- const ctx = canvas.getContext("2d");
1291
- if (!ctx) {
1292
- throw new Error("无法创建canvas上下文");
1293
- }
1294
- // 将ImageData绘制到canvas
1295
- ctx.putImageData(enhancedImage, 0, 0);
1296
- // 转换为Base64
1297
- const base64Image = canvas.toDataURL("image/jpeg", 0.7);
1298
- // OCR识别
1299
- try {
1300
- let idCardInfo;
1301
- if (this.options.useWorker && this.ocrWorker) {
1302
- // 使用Worker线程处理
1303
- const result = await this.ocrWorker.postMessage({
1304
- imageBase64: base64Image,
1305
- tessWorkerOptions: {
1306
- logger: this.options.logger,
1307
- },
1308
- });
1309
- idCardInfo = result.idCardInfo;
1310
- this.options.logger?.(`OCR处理完成,用时: ${result.processingTime.toFixed(2)}ms`);
1311
- }
1312
- else {
1313
- // 使用主线程处理
1314
- const startTime = performance.now();
1315
- // 转换ImageData为Canvas
1316
- const canvas = ImageProcessor.imageDataToCanvas(enhancedImage);
1317
- const { data } = await this.worker.recognize(canvas);
1318
- // 解析身份证信息
1319
- idCardInfo = this.parseIDCardText(data.text);
1320
- const processingTime = performance.now() - startTime;
1321
- this.options.logger?.(`OCR处理完成,用时: ${processingTime.toFixed(2)}ms`);
1322
- }
1323
- // 缓存结果
1324
- if (this.options.enableCache) {
1325
- const fingerprint = calculateImageFingerprint(imageData);
1326
- this.resultCache.set(fingerprint, idCardInfo);
1327
- }
1328
- return idCardInfo;
1329
- }
1330
- catch (error) {
1331
- this.options.logger?.(`OCR识别错误: ${error}`);
1332
- return {};
1333
- }
1334
- }
1335
- /**
1336
- * 解析身份证文本信息
1337
- *
1338
- * 从OCR识别到的文本中提取结构化的身份证信息
1339
- *
1340
- * @private
1341
- * @param {string} text - OCR识别到的文本
1342
- * @returns {IDCardInfo} 提取到的身份证信息对象
1343
- */
1344
- parseIDCardText(text) {
1345
- const info = {};
1346
- // 拆分为行
1347
- const lines = text.split("\n").filter((line) => line.trim());
1348
- // 解析身份证号码(最容易识别的部分)
1349
- const idNumberRegex = /(\d{17}[\dX])/;
1350
- const idNumberMatch = text.match(idNumberRegex);
1351
- if (idNumberMatch) {
1352
- info.idNumber = idNumberMatch[1];
1353
- }
1354
- // 解析姓名
1355
- for (const line of lines) {
1356
- if (line.includes("姓名") ||
1357
- (line.length < 10 && line.length > 1 && !/\d/.test(line))) {
1358
- info.name = line.replace("姓名", "").trim();
1359
- break;
1360
- }
1361
- }
1362
- // 解析性别和民族
1363
- const genderNationalityRegex = /(男|女).*(族)/;
1364
- const genderMatch = text.match(genderNationalityRegex);
1365
- if (genderMatch) {
1366
- info.gender = genderMatch[1];
1367
- const nationalityText = genderMatch[0];
1368
- info.nationality = nationalityText
1369
- .substring(nationalityText.indexOf(genderMatch[1]) + 1)
1370
- .trim();
1371
- }
1372
- // 解析出生日期
1373
- const birthDateRegex = /(\d{4})年(\d{1,2})月(\d{1,2})日/;
1374
- const birthDateMatch = text.match(birthDateRegex);
1375
- if (birthDateMatch) {
1376
- info.birthDate = `${birthDateMatch[1]}-${birthDateMatch[2]}-${birthDateMatch[3]}`;
1377
- }
1378
- // 解析地址
1379
- const addressRegex = /住址([\s\S]*?)公民身份号码/;
1380
- const addressMatch = text.match(addressRegex);
1381
- if (addressMatch) {
1382
- info.address = addressMatch[1].replace(/\n/g, "").trim();
1383
- }
1384
- // 解析签发机关
1385
- const authorityRegex = /签发机关([\s\S]*?)有效期/;
1386
- const authorityMatch = text.match(authorityRegex);
1387
- if (authorityMatch) {
1388
- info.issuingAuthority = authorityMatch[1].replace(/\n/g, "").trim();
1389
- }
1390
- // 解析有效期限
1391
- const validPeriodRegex = /有效期限([\s\S]*?)(-|至)/;
1392
- const validPeriodMatch = text.match(validPeriodRegex);
1393
- if (validPeriodMatch) {
1394
- info.validPeriod = validPeriodMatch[0].replace("有效期限", "").trim();
1395
- }
1396
- return info;
1397
- }
1398
- /**
1399
- * 清除结果缓存
1400
- */
1401
- clearCache() {
1402
- this.resultCache.clear();
1403
- this.options.logger?.("OCR结果缓存已清除");
1404
- }
1405
- /**
1406
- * 终止OCR引擎并释放资源
1407
- *
1408
- * @returns {Promise<void>} 终止完成的Promise
1409
- */
1410
- async terminate() {
1411
- if (this.worker) {
1412
- await this.worker.terminate();
1413
- this.worker = null;
1414
- }
1415
- if (this.ocrWorker) {
1416
- this.ocrWorker.terminate();
1417
- this.ocrWorker = null;
1418
- }
1419
- this.initialized = false;
1420
- this.options.logger?.("OCR引擎已终止");
1421
- }
1422
- /**
1423
- * 释放资源
1424
- */
1425
- dispose() {
1426
- return this.terminate();
1427
- }
1428
- }
1429
-
1430
- /**
1431
- * @file 数据提取工具类
1432
- * @description 提供身份证信息的验证和格式化功能
1433
- * @module DataExtractor
1434
- */
1435
- /**
1436
- * 数据提取工具类
1437
- *
1438
- * 提供身份证信息的验证、提取和增强功能,可以从身份证号码中提取出生日期、性别等信息,
1439
- * 并对OCR识别结果进行补充和验证
1440
- *
1441
- * @example
1442
- * ```typescript
1443
- * // 验证身份证号码
1444
- * const isValid = DataExtractor.validateIDNumber('110101199001011234');
1445
- *
1446
- * // 从身份证号码提取出生日期
1447
- * const birthDate = DataExtractor.extractBirthDateFromID('110101199001011234');
1448
- * // 结果: '1990-01-01'
1449
- *
1450
- * // 增强OCR识别结果
1451
- * const enhancedInfo = DataExtractor.enhanceIDCardInfo({
1452
- * name: '张三',
1453
- * idNumber: '110101199001011234'
1454
- * });
1455
- * // 结果会自动补充性别和出生日期
1456
- * ```
1457
- */
1458
- class DataExtractor {
1459
- /**
1460
- * 验证身份证号码格式
1461
- *
1462
- * 检查身份证号码的长度、格式和出生日期部分是否有效
1463
- *
1464
- * @param {string} idNumber - 要验证的身份证号码
1465
- * @returns {boolean} 是否是有效的身份证号码
1466
- */
1467
- static validateIDNumber(idNumber) {
1468
- // 简单校验长度
1469
- if (!idNumber || idNumber.length !== 18) {
1470
- return false;
1471
- }
1472
- // 校验格式 (前17位必须是数字,最后一位可以是数字或X)
1473
- const pattern = /^\d{17}[\dX]$/;
1474
- if (!pattern.test(idNumber)) {
1475
- return false;
1476
- }
1477
- // 校验出生日期
1478
- const year = parseInt(idNumber.substr(6, 4));
1479
- const month = parseInt(idNumber.substr(10, 2));
1480
- const day = parseInt(idNumber.substr(12, 2));
1481
- const date = new Date(year, month - 1, day);
1482
- if (date.getFullYear() !== year ||
1483
- date.getMonth() + 1 !== month ||
1484
- date.getDate() !== day) {
1485
- return false;
1486
- }
1487
- // 简单的校验规则,实际项目中可以加入更完善的验证
1488
- return true;
1489
- }
1490
- /**
1491
- * 从身份证号码提取出生日期
1492
- *
1493
- * @param {string} idNumber - 身份证号码
1494
- * @returns {string|null} 格式化的出生日期(YYYY-MM-DD),如果身份证号码无效则返回null
1495
- */
1496
- static extractBirthDateFromID(idNumber) {
1497
- if (!this.validateIDNumber(idNumber)) {
1498
- return null;
1499
- }
1500
- const year = idNumber.substr(6, 4);
1501
- const month = idNumber.substr(10, 2);
1502
- const day = idNumber.substr(12, 2);
1503
- return `${year}-${month}-${day}`;
1504
- }
1505
- /**
1506
- * 从身份证号码提取性别
1507
- *
1508
- * 根据身份证号码第17位判断性别,奇数为男,偶数为女
1509
- *
1510
- * @param {string} idNumber - 身份证号码
1511
- * @returns {string|null} '男'或'女',如果身份证号码无效则返回null
1512
- */
1513
- static extractGenderFromID(idNumber) {
1514
- if (!this.validateIDNumber(idNumber)) {
1515
- return null;
1516
- }
1517
- // 第17位,奇数为男,偶数为女
1518
- const genderCode = parseInt(idNumber.charAt(16));
1519
- return genderCode % 2 === 1 ? '男' : '女';
1520
- }
1521
- /**
1522
- * 从身份证号码提取地区编码
1523
- *
1524
- * @param {string} idNumber - 身份证号码
1525
- * @returns {string|null} 地区编码(前6位),如果身份证号码无效则返回null
1526
- */
1527
- static extractRegionFromID(idNumber) {
1528
- if (!this.validateIDNumber(idNumber)) {
1529
- return null;
1530
- }
1531
- return idNumber.substr(0, 6);
1532
- }
1533
- /**
1534
- * 合并并优化身份证信息
1535
- *
1536
- * 使用多个来源的数据进行交叉验证和补充,如果OCR识别结果缺少某些信息,
1537
- * 但有身份证号码,则可以从号码中提取出生日期和性别等信息
1538
- *
1539
- * @param {IDCardInfo} ocrInfo - OCR识别到的身份证信息
1540
- * @param {string} [idNumber] - 可选的外部提供的身份证号码,优先级高于OCR识别结果
1541
- * @returns {IDCardInfo} 增强后的身份证信息
1542
- */
1543
- static enhanceIDCardInfo(ocrInfo, idNumber) {
1544
- const result = { ...ocrInfo };
1545
- // 如果OCR识别出身份证号,但没有识别出生日期或性别,则从身份证号码提取
1546
- if (result.idNumber && this.validateIDNumber(result.idNumber)) {
1547
- // 从身份证号提取出生日期
1548
- if (!result.birthDate) {
1549
- result.birthDate = this.extractBirthDateFromID(result.idNumber) || undefined;
1550
- }
1551
- // 从身份证号提取性别
1552
- if (!result.gender) {
1553
- result.gender = this.extractGenderFromID(result.idNumber) || undefined;
1554
- }
1555
- }
1556
- // 如果外部传入了身份证号,则优先使用它并提取信息
1557
- if (idNumber && this.validateIDNumber(idNumber)) {
1558
- result.idNumber = idNumber;
1559
- // 使用身份证号码再次验证或补充信息
1560
- const birthDate = this.extractBirthDateFromID(idNumber);
1561
- if (birthDate) {
1562
- result.birthDate = birthDate;
1563
- }
1564
- const gender = this.extractGenderFromID(idNumber);
1565
- if (gender) {
1566
- result.gender = gender;
1567
- }
1568
- }
1569
- return result;
1570
- }
1571
- /**
1572
- * 提取并验证身份证信息
1573
- *
1574
- * @param idCardInfo 初步提取的身份证信息
1575
- * @returns 验证和增强后的身份证信息
1576
- */
1577
- extractAndValidate(idCardInfo) {
1578
- const enhancedInfo = { ...idCardInfo };
1579
- // 验证和规范化身份证号
1580
- if (enhancedInfo.idNumber) {
1581
- enhancedInfo.idNumber = this.normalizeIDNumber(enhancedInfo.idNumber);
1582
- // 如果身份证号有效,推断出生日期
1583
- if (this.validateIDNumber(enhancedInfo.idNumber)) {
1584
- if (!enhancedInfo.birthDate) {
1585
- enhancedInfo.birthDate = this.extractBirthDateFromID(enhancedInfo.idNumber);
1586
- }
1587
- // 推断性别
1588
- if (!enhancedInfo.gender) {
1589
- enhancedInfo.gender = this.extractGenderFromID(enhancedInfo.idNumber);
1590
- }
1591
- }
1592
- }
1593
- // 规范化日期格式
1594
- if (enhancedInfo.birthDate) {
1595
- enhancedInfo.birthDate = this.normalizeDate(enhancedInfo.birthDate);
1596
- }
1597
- // 规范化地址信息
1598
- if (enhancedInfo.address) {
1599
- enhancedInfo.address = this.normalizeAddress(enhancedInfo.address);
1600
- }
1601
- return enhancedInfo;
1602
- }
1603
- /**
1604
- * 规范化身份证号码
1605
- */
1606
- normalizeIDNumber(idNumber) {
1607
- // 移除空格和特殊字符
1608
- return idNumber.replace(/[\s\-]/g, '').toUpperCase();
1609
- }
1610
- /**
1611
- * 验证身份证号码是否有效
1612
- */
1613
- validateIDNumber(idNumber) {
1614
- // 简单验证身份证号码长度和格式
1615
- const idRegex = /(^\d{15}$)|(^\d{17}([0-9]|X)$)/;
1616
- return idRegex.test(idNumber);
1617
- }
1618
- /**
1619
- * 从身份证号中提取出生日期
1620
- */
1621
- extractBirthDateFromID(idNumber) {
1622
- if (idNumber.length === 18) {
1623
- return `${idNumber.substring(6, 10)}-${idNumber.substring(10, 12)}-${idNumber.substring(12, 14)}`;
1624
- }
1625
- else if (idNumber.length === 15) {
1626
- return `19${idNumber.substring(6, 8)}-${idNumber.substring(8, 10)}-${idNumber.substring(10, 12)}`;
1627
- }
1628
- return '';
1629
- }
1630
- /**
1631
- * 从身份证号中提取性别信息
1632
- */
1633
- extractGenderFromID(idNumber) {
1634
- let sexCode;
1635
- if (idNumber.length === 18) {
1636
- sexCode = parseInt(idNumber.charAt(16));
1637
- }
1638
- else {
1639
- sexCode = parseInt(idNumber.charAt(14));
1640
- }
1641
- return sexCode % 2 === 1 ? '男' : '女';
1642
- }
1643
- /**
1644
- * 规范化日期格式
1645
- */
1646
- normalizeDate(date) {
1647
- // 简单的日期格式化逻辑
1648
- return date.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
1649
- }
1650
- /**
1651
- * 规范化地址信息
1652
- */
1653
- normalizeAddress(address) {
1654
- // 地址格式化逻辑
1655
- return address.trim();
1656
- }
1657
- }
1658
-
1659
- /**
1660
- * @file OCR模块入口文件
1661
- * @description 包含身份证OCR识别相关功能
1662
- * @module IDScannerOCR
1663
- * @version 1.0.0
1664
- * @license MIT
1665
- */
1666
- /**
1667
- * OCR模块类
1668
- *
1669
- * 提供身份证检测和OCR文字识别功能
1670
- */
1671
- class OCRModule {
1672
- /**
1673
- * 构造函数
1674
- * @param options 配置选项
1675
- */
1676
- constructor(options = {}) {
1677
- this.options = options;
1678
- this.isRunning = false;
1679
- this.videoElement = null;
1680
- this.camera = new Camera(options.cameraOptions);
1681
- this.idDetector = new IDCardDetector({
1682
- onDetection: this.handleIDDetection.bind(this),
1683
- onError: this.handleError.bind(this),
1684
- });
1685
- this.ocrProcessor = new OCRProcessor();
1686
- this.dataExtractor = new DataExtractor();
1687
- }
1688
- /**
1689
- * 初始化OCR引擎
1690
- *
1691
- * @returns Promise<void>
1692
- */
1693
- async initialize() {
1694
- try {
1695
- await this.ocrProcessor.initialize();
1696
- console.log("OCR engine initialized");
1697
- }
1698
- catch (error) {
1699
- this.handleError(error);
1700
- throw error;
1701
- }
1702
- }
1703
- /**
1704
- * 启动身份证扫描
1705
- * @param videoElement HTML视频元素
1706
- */
1707
- async startIDCardScanner(videoElement) {
1708
- if (!this.ocrProcessor) {
1709
- throw new Error("OCR engine not initialized. Call initialize() first.");
1710
- }
1711
- this.videoElement = videoElement;
1712
- this.isRunning = true;
1713
- await this.camera.start(videoElement);
1714
- this.idDetector.start(videoElement);
1715
- }
1716
- /**
1717
- * 停止扫描
1718
- */
1719
- stop() {
1720
- this.isRunning = false;
1721
- this.idDetector.stop();
1722
- this.camera.stop();
1723
- }
1724
- /**
1725
- * 处理身份证检测结果
1726
- */
1727
- async handleIDDetection(result) {
1728
- if (!this.isRunning)
1729
- return;
1730
- try {
1731
- // 检查 imageData 是否存在
1732
- if (!result.imageData) {
1733
- this.handleError(new Error("无效的图像数据"));
1734
- return;
1735
- }
1736
- const idCardInfo = await this.ocrProcessor.processIDCard(result.imageData);
1737
- const extractedInfo = this.dataExtractor.extractAndValidate(idCardInfo);
1738
- if (this.options.onIDCardScanned) {
1739
- this.options.onIDCardScanned(extractedInfo);
1740
- }
1741
- }
1742
- catch (error) {
1743
- this.handleError(error);
1744
- }
1745
- }
1746
- /**
1747
- * 处理错误
1748
- */
1749
- handleError(error) {
1750
- if (this.options.onError) {
1751
- this.options.onError(error);
1752
- }
1753
- else {
1754
- console.error("OCRModule error:", error);
1755
- }
1756
- }
1757
- /**
1758
- * 释放资源
1759
- */
1760
- async terminate() {
1761
- this.stop();
1762
- await this.ocrProcessor.terminate();
1763
- }
1764
- /**
1765
- * 直接处理图像数据中的身份证
1766
- * @param imageData 要处理的图像数据
1767
- * @returns 返回Promise,解析为身份证信息
1768
- */
1769
- async processIDCard(imageData) {
1770
- try {
1771
- if (!this.ocrProcessor) {
1772
- throw new Error("OCR engine not initialized. Call initialize() first.");
1773
- }
1774
- // 检查图像数据有效性
1775
- if (!imageData ||
1776
- !imageData.data ||
1777
- imageData.width <= 0 ||
1778
- imageData.height <= 0) {
1779
- throw new Error("无效的图像数据");
1780
- }
1781
- // 进行图像预处理,提高识别率
1782
- const processedImage = ImageProcessor.adjustBrightnessContrast(imageData, 5, // 轻微提高亮度
1783
- 10 // 适度提高对比度
1784
- );
1785
- // 调用OCR处理器进行文字识别
1786
- const idCardInfo = await this.ocrProcessor.processIDCard(processedImage);
1787
- // 提取和验证身份证信息
1788
- const extractedInfo = this.dataExtractor.extractAndValidate(idCardInfo);
1789
- // 如果有回调,触发回调
1790
- if (this.options.onIDCardScanned) {
1791
- this.options.onIDCardScanned(extractedInfo);
1792
- }
1793
- return extractedInfo;
1794
- }
1795
- catch (error) {
1796
- this.handleError(error);
1797
- throw error;
1798
- }
1799
- }
1800
- }
1801
-
1802
- export { DataExtractor, IDCardDetector, OCRModule, OCRProcessor };