id-scanner-lib 1.2.0 → 1.3.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 (67) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +192 -375
  3. package/dist/id-scanner-core.esm.js +427 -221
  4. package/dist/id-scanner-core.esm.js.map +1 -1
  5. package/dist/id-scanner-core.js +427 -221
  6. package/dist/id-scanner-core.js.map +1 -1
  7. package/dist/id-scanner-core.min.js +1 -9
  8. package/dist/id-scanner-core.min.js.map +1 -1
  9. package/dist/id-scanner-ocr.esm.js +439 -265
  10. package/dist/id-scanner-ocr.esm.js.map +1 -1
  11. package/dist/id-scanner-ocr.js +439 -265
  12. package/dist/id-scanner-ocr.js.map +1 -1
  13. package/dist/id-scanner-ocr.min.js +1 -9
  14. package/dist/id-scanner-ocr.min.js.map +1 -1
  15. package/dist/id-scanner-qr.esm.js +483 -233
  16. package/dist/id-scanner-qr.esm.js.map +1 -1
  17. package/dist/id-scanner-qr.js +482 -232
  18. package/dist/id-scanner-qr.js.map +1 -1
  19. package/dist/id-scanner-qr.min.js +1 -9
  20. package/dist/id-scanner-qr.min.js.map +1 -1
  21. package/dist/id-scanner.js +1779 -357
  22. package/dist/id-scanner.js.map +1 -1
  23. package/dist/id-scanner.min.js +1 -9
  24. package/dist/id-scanner.min.js.map +1 -1
  25. package/package.json +23 -5
  26. package/src/demo/demo.ts +178 -62
  27. package/src/id-recognition/id-detector.ts +184 -155
  28. package/src/id-recognition/ocr-processor.ts +193 -146
  29. package/src/id-recognition/ocr-worker.ts +2 -2
  30. package/src/index-umd.ts +347 -110
  31. package/src/index.ts +688 -87
  32. package/src/ocr-module.ts +108 -60
  33. package/src/qr-module.ts +104 -54
  34. package/src/scanner/barcode-scanner.ts +145 -58
  35. package/src/scanner/qr-scanner.ts +86 -47
  36. package/src/utils/image-processing.ts +479 -294
  37. package/src/utils/performance.ts +5 -3
  38. package/dist/core.d.ts +0 -77
  39. package/dist/demo/demo.d.ts +0 -14
  40. package/dist/id-recognition/data-extractor.d.ts +0 -105
  41. package/dist/id-recognition/id-detector.d.ts +0 -100
  42. package/dist/id-recognition/ocr-processor.d.ts +0 -64
  43. package/dist/id-scanner.esm.js +0 -94656
  44. package/dist/id-scanner.esm.js.map +0 -1
  45. package/dist/index-umd.d.ts +0 -96
  46. package/dist/index.d.ts +0 -78
  47. package/dist/ocr-module.d.ts +0 -67
  48. package/dist/qr-module.d.ts +0 -68
  49. package/dist/scanner/barcode-scanner.d.ts +0 -90
  50. package/dist/scanner/qr-scanner.d.ts +0 -80
  51. package/dist/types/core.d.ts +0 -77
  52. package/dist/types/demo/demo.d.ts +0 -14
  53. package/dist/types/id-recognition/data-extractor.d.ts +0 -105
  54. package/dist/types/id-recognition/id-detector.d.ts +0 -100
  55. package/dist/types/id-recognition/ocr-processor.d.ts +0 -64
  56. package/dist/types/index-umd.d.ts +0 -96
  57. package/dist/types/index.d.ts +0 -78
  58. package/dist/types/ocr-module.d.ts +0 -67
  59. package/dist/types/qr-module.d.ts +0 -68
  60. package/dist/types/scanner/barcode-scanner.d.ts +0 -90
  61. package/dist/types/scanner/qr-scanner.d.ts +0 -80
  62. package/dist/types/utils/camera.d.ts +0 -81
  63. package/dist/types/utils/image-processing.d.ts +0 -75
  64. package/dist/types/utils/types.d.ts +0 -65
  65. package/dist/utils/camera.d.ts +0 -81
  66. package/dist/utils/image-processing.d.ts +0 -75
  67. package/dist/utils/types.d.ts +0 -65
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('jsqr'), require('tesseract.js')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'jsqr', 'tesseract.js'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.IDScanner = {}, global.jsQR, global.Tesseract));
5
- })(this, (function (exports, jsQR, tesseract_js) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tesseract.js'), require('jsqr')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'tesseract.js', 'jsqr'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.IDScanner = {}, global.Tesseract, global.jsQR));
5
+ })(this, (function (exports, tesseract_js, jsQR) { 'use strict';
6
6
 
7
7
  /**
8
8
  * @file 相机工具类
@@ -185,7 +185,7 @@
185
185
  this.scanTimer = null;
186
186
  this.options = {
187
187
  scanInterval: 200,
188
- ...options
188
+ ...options,
189
189
  };
190
190
  this.camera = new Camera();
191
191
  }
@@ -242,31 +242,53 @@
242
242
  }
243
243
  this.camera.release();
244
244
  }
245
+ /**
246
+ * 处理图像数据中的二维码
247
+ *
248
+ * @param {ImageData} imageData - 要处理的图像数据
249
+ * @returns {string | null} 识别到的二维码内容,如未识别到则返回null
250
+ */
251
+ processImageData(imageData) {
252
+ try {
253
+ if (!imageData ||
254
+ !imageData.data ||
255
+ imageData.width <= 0 ||
256
+ imageData.height <= 0) {
257
+ throw new Error("无效的图像数据");
258
+ }
259
+ const code = jsQR(imageData.data, imageData.width, imageData.height);
260
+ if (code && code.data) {
261
+ return code.data;
262
+ }
263
+ return null;
264
+ }
265
+ catch (error) {
266
+ if (this.options.onError) {
267
+ this.options.onError(error instanceof Error ? error : new Error(String(error)));
268
+ }
269
+ return null;
270
+ }
271
+ }
245
272
  }
246
273
 
274
+ /**
275
+ * Browser Image Compression
276
+ * v2.0.2
277
+ * by Donald <donaldcwl@gmail.com>
278
+ * https://github.com/Donaldcwl/browser-image-compression
279
+ */
280
+
281
+ function _mergeNamespaces$1(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$1({__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";
282
+
247
283
  /**
248
284
  * @file 图像处理工具类
249
- * @description 提供图像处理相关的辅助功能
285
+ * @description 提供图像预处理功能,用于提高OCR识别率
250
286
  * @module ImageProcessor
251
287
  */
252
288
  /**
253
289
  * 图像处理工具类
254
290
  *
255
- * 提供常用的图像处理功能,如亮度和对比度调整、灰度转换、图像大小调整等。
256
- * 这些功能可用于增强图像质量,提高OCR和扫描的识别率。
257
- *
258
- * @example
259
- * ```typescript
260
- * // 使用图像处理功能增强图像
261
- * const enhancedImage = ImageProcessor.adjustBrightnessContrast(
262
- * originalImageData,
263
- * 15, // 增加亮度
264
- * 25 // 增加对比度
265
- * );
266
- *
267
- * // 转换为灰度图像
268
- * const grayImage = ImageProcessor.toGrayscale(originalImageData);
269
- * ```
291
+ * 提供各种图像处理功能,用于优化识别效果
270
292
  */
271
293
  class ImageProcessor {
272
294
  /**
@@ -276,10 +298,10 @@
276
298
  * @returns {HTMLCanvasElement} 包含图像的Canvas元素
277
299
  */
278
300
  static imageDataToCanvas(imageData) {
279
- const canvas = document.createElement('canvas');
301
+ const canvas = document.createElement("canvas");
280
302
  canvas.width = imageData.width;
281
303
  canvas.height = imageData.height;
282
- const ctx = canvas.getContext('2d');
304
+ const ctx = canvas.getContext("2d");
283
305
  if (ctx) {
284
306
  ctx.putImageData(imageData, 0, 0);
285
307
  }
@@ -292,254 +314,374 @@
292
314
  * @returns {ImageData|null} Canvas的图像数据,如果获取失败则返回null
293
315
  */
294
316
  static canvasToImageData(canvas) {
295
- const ctx = canvas.getContext('2d');
317
+ const ctx = canvas.getContext("2d");
296
318
  return ctx ? ctx.getImageData(0, 0, canvas.width, canvas.height) : null;
297
319
  }
298
320
  /**
299
321
  * 调整图像亮度和对比度
300
322
  *
301
- * @param {ImageData} imageData - 要处理的图像数据
302
- * @param {number} [brightness=0] - 亮度调整值,正值增加亮度,负值降低亮度,范围建议为-100到100
303
- * @param {number} [contrast=0] - 对比度调整值,正值增加对比度,负值降低对比度,范围建议为-100到100
304
- * @returns {ImageData} 处理后的图像数据
323
+ * @param imageData 原始图像数据
324
+ * @param brightness 亮度调整值 (-100到100)
325
+ * @param contrast 对比度调整值 (-100到100)
326
+ * @returns 处理后的图像数据
305
327
  */
306
328
  static adjustBrightnessContrast(imageData, brightness = 0, contrast = 0) {
307
- const canvas = this.imageDataToCanvas(imageData);
308
- const ctx = canvas.getContext('2d');
309
- if (ctx) {
310
- const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
311
- const data = imgData.data;
312
- // 调整对比度算法
313
- const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
314
- for (let i = 0; i < data.length; i += 4) {
315
- // 红色
316
- data[i] = this.truncate(factor * (data[i] - 128) + 128 + brightness);
317
- // 绿色
318
- data[i + 1] = this.truncate(factor * (data[i + 1] - 128) + 128 + brightness);
319
- // 蓝色
320
- data[i + 2] = this.truncate(factor * (data[i + 2] - 128) + 128 + brightness);
321
- // Alpha不变
329
+ // 将亮度和对比度范围限制在 -100 到 100 之间
330
+ brightness = Math.max(-100, Math.min(100, brightness));
331
+ contrast = Math.max(-100, Math.min(100, contrast));
332
+ // 将范围转换为适合计算的值
333
+ const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
334
+ const briAdjust = (brightness / 100) * 255;
335
+ const data = imageData.data;
336
+ const length = data.length;
337
+ for (let i = 0; i < length; i += 4) {
338
+ // 分别处理 RGB 三个通道
339
+ for (let j = 0; j < 3; j++) {
340
+ // 应用亮度和对比度调整公式
341
+ const newValue = factor * (data[i + j] + briAdjust - 128) + 128;
342
+ data[i + j] = Math.max(0, Math.min(255, newValue));
322
343
  }
323
- ctx.putImageData(imgData, 0, 0);
324
- return imgData;
344
+ // Alpha 通道保持不变
325
345
  }
326
346
  return imageData;
327
347
  }
328
348
  /**
329
- * 确保值在0-255范围内
349
+ * 将图像转换为灰度图
330
350
  *
331
- * @private
332
- * @param {number} value - 要截断的值
333
- * @returns {number} 截断后的值,范围为0-255
351
+ * @param imageData 原始图像数据
352
+ * @returns 灰度图像数据
334
353
  */
335
- static truncate(value) {
336
- return Math.min(255, Math.max(0, value));
354
+ static toGrayscale(imageData) {
355
+ const data = imageData.data;
356
+ const length = data.length;
357
+ for (let i = 0; i < length; i += 4) {
358
+ // 使用加权平均法将 RGB 转换为灰度值
359
+ const gray = data[i] * 0.3 + data[i + 1] * 0.59 + data[i + 2] * 0.11;
360
+ data[i] = data[i + 1] = data[i + 2] = gray;
361
+ }
362
+ return imageData;
337
363
  }
338
364
  /**
339
- * 将彩色图像转换为灰度图像
340
- *
341
- * 灰度转换可以简化图像,提高OCR和条形码识别的准确率
365
+ * 锐化图像
342
366
  *
343
- * @param {ImageData} imageData - 要转换的彩色图像
344
- * @returns {ImageData} 转换后的灰度图像
367
+ * @param imageData 原始图像数据
368
+ * @param amount 锐化程度,默认为2
369
+ * @returns 锐化后的图像数据
345
370
  */
346
- static toGrayscale(imageData) {
347
- const canvas = this.imageDataToCanvas(imageData);
348
- const ctx = canvas.getContext('2d');
349
- if (ctx) {
350
- const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
351
- const data = imgData.data;
352
- for (let i = 0; i < data.length; i += 4) {
353
- const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
354
- data[i] = avg; // 红色
355
- data[i + 1] = avg; // 绿色
356
- data[i + 2] = avg; // 蓝色
357
- // Alpha不变
371
+ static sharpen(imageData, amount = 2) {
372
+ if (!imageData || !imageData.data)
373
+ return imageData;
374
+ const width = imageData.width;
375
+ const height = imageData.height;
376
+ const data = imageData.data;
377
+ const outputData = new Uint8ClampedArray(data.length);
378
+ // 锐化卷积核
379
+ const kernel = [
380
+ 0,
381
+ -amount,
382
+ 0,
383
+ -amount,
384
+ 1 + 4 * amount,
385
+ -amount,
386
+ 0,
387
+ -amount,
388
+ 0,
389
+ ];
390
+ // 应用卷积
391
+ for (let y = 1; y < height - 1; y++) {
392
+ for (let x = 1; x < width - 1; x++) {
393
+ const pos = (y * width + x) * 4;
394
+ // 对每个通道应用卷积
395
+ for (let c = 0; c < 3; c++) {
396
+ let val = 0;
397
+ for (let ky = -1; ky <= 1; ky++) {
398
+ for (let kx = -1; kx <= 1; kx++) {
399
+ const kernelPos = (ky + 1) * 3 + (kx + 1);
400
+ const dataPos = ((y + ky) * width + (x + kx)) * 4 + c;
401
+ val += data[dataPos] * kernel[kernelPos];
402
+ }
403
+ }
404
+ outputData[pos + c] = Math.max(0, Math.min(255, val));
405
+ }
406
+ outputData[pos + 3] = data[pos + 3]; // 保持透明度不变
358
407
  }
359
- ctx.putImageData(imgData, 0, 0);
360
- return imgData;
361
408
  }
362
- return imageData;
409
+ // 处理边缘像素
410
+ for (let y = 0; y < height; y++) {
411
+ for (let x = 0; x < width; x++) {
412
+ if (y === 0 || y === height - 1 || x === 0 || x === width - 1) {
413
+ const pos = (y * width + x) * 4;
414
+ outputData[pos] = data[pos];
415
+ outputData[pos + 1] = data[pos + 1];
416
+ outputData[pos + 2] = data[pos + 2];
417
+ outputData[pos + 3] = data[pos + 3];
418
+ }
419
+ }
420
+ }
421
+ // 创建新的ImageData对象
422
+ return new ImageData(outputData, width, height);
363
423
  }
364
424
  /**
365
- * 调整图像大小
425
+ * 对图像应用阈值操作,增强对比度
366
426
  *
367
- * @param {ImageData} imageData - 原图像数据
368
- * @param {number} newWidth - 新宽度
369
- * @param {number} newHeight - 新高度
370
- * @returns {ImageData} 调整大小后的图像数据
427
+ * @param imageData 原始图像数据
428
+ * @param threshold 阈值 (0-255)
429
+ * @returns 处理后的图像数据
371
430
  */
372
- static resize(imageData, newWidth, newHeight) {
373
- const canvas = document.createElement('canvas');
374
- canvas.width = newWidth;
375
- canvas.height = newHeight;
376
- const ctx = canvas.getContext('2d');
377
- if (ctx) {
378
- const tempCanvas = this.imageDataToCanvas(imageData);
379
- ctx.drawImage(tempCanvas, 0, 0, imageData.width, imageData.height, 0, 0, newWidth, newHeight);
380
- return ctx.getImageData(0, 0, newWidth, newHeight);
431
+ static threshold(imageData, threshold = 128) {
432
+ // 先转换为灰度图
433
+ const grayscaleImage = this.toGrayscale(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height));
434
+ const data = grayscaleImage.data;
435
+ const length = data.length;
436
+ for (let i = 0; i < length; i += 4) {
437
+ // 二值化处理
438
+ const value = data[i] < threshold ? 0 : 255;
439
+ data[i] = data[i + 1] = data[i + 2] = value;
381
440
  }
382
- return imageData;
441
+ return grayscaleImage;
383
442
  }
384
443
  /**
385
- * 降低图像分辨率以提高处理速度
444
+ * 将图像转换为黑白图像(二值化)
386
445
  *
387
- * 对于OCR和图像分析,降低分辨率可以在保持识别率的同时大幅提升处理速度
388
- *
389
- * @param {ImageData} imageData - 原图像数据
390
- * @param {number} [maxDimension=1000] - 目标最大尺寸(宽或高)
391
- * @returns {ImageData} 处理后的图像数据
446
+ * @param imageData 原始图像数据
447
+ * @returns 二值化后的图像数据
392
448
  */
393
- static downsampleForProcessing(imageData, maxDimension = 1000) {
394
- const { width, height } = imageData;
395
- // 如果图像尺寸已经小于或等于目标尺寸,则无需处理
396
- if (width <= maxDimension && height <= maxDimension) {
397
- return imageData;
449
+ static toBinaryImage(imageData) {
450
+ // 先转换为灰度图
451
+ const grayscaleImage = this.toGrayscale(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height));
452
+ // 使用OTSU算法自动确定阈值
453
+ const threshold = this.getOtsuThreshold(grayscaleImage);
454
+ return this.threshold(grayscaleImage, threshold);
455
+ }
456
+ /**
457
+ * 使用OTSU算法计算最佳阈值
458
+ *
459
+ * @param imageData 灰度图像数据
460
+ * @returns 最佳阈值
461
+ */
462
+ static getOtsuThreshold(imageData) {
463
+ const data = imageData.data;
464
+ const histogram = new Array(256).fill(0);
465
+ // 统计灰度直方图
466
+ for (let i = 0; i < data.length; i += 4) {
467
+ histogram[data[i]]++;
468
+ }
469
+ const total = imageData.width * imageData.height;
470
+ let sum = 0;
471
+ // 计算总灰度值和
472
+ for (let i = 0; i < 256; i++) {
473
+ sum += i * histogram[i];
474
+ }
475
+ let sumB = 0;
476
+ let wB = 0;
477
+ let wF = 0;
478
+ let maxVariance = 0;
479
+ let threshold = 0;
480
+ // 遍历所有可能的阈值,找到最大类间方差
481
+ for (let t = 0; t < 256; t++) {
482
+ wB += histogram[t]; // 背景权重
483
+ if (wB === 0)
484
+ continue;
485
+ wF = total - wB; // 前景权重
486
+ if (wF === 0)
487
+ break;
488
+ sumB += t * histogram[t];
489
+ const mB = sumB / wB; // 背景平均灰度
490
+ const mF = (sum - sumB) / wF; // 前景平均灰度
491
+ // 计算类间方差
492
+ const variance = wB * wF * (mB - mF) * (mB - mF);
493
+ if (variance > maxVariance) {
494
+ maxVariance = variance;
495
+ threshold = t;
496
+ }
497
+ }
498
+ return threshold;
499
+ }
500
+ /**
501
+ * 批量应用图像处理
502
+ *
503
+ * @param imageData 原始图像数据
504
+ * @param options 处理选项
505
+ * @returns 处理后的图像数据
506
+ */
507
+ static batchProcess(imageData, options) {
508
+ let processedImage = new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height);
509
+ // 应用亮度和对比度调整
510
+ if (options.brightness !== undefined || options.contrast !== undefined) {
511
+ processedImage = this.adjustBrightnessContrast(processedImage, options.brightness || 0, options.contrast || 0);
512
+ }
513
+ // 应用灰度转换
514
+ if (options.grayscale) {
515
+ processedImage = this.toGrayscale(processedImage);
516
+ }
517
+ // 应用锐化
518
+ if (options.sharpen) {
519
+ processedImage = this.sharpen(processedImage);
520
+ }
521
+ // 应用颜色反转
522
+ if (options.invert) {
523
+ const data = processedImage.data;
524
+ for (let i = 0; i < data.length; i += 4) {
525
+ // 反转RGB值
526
+ data[i] = 255 - data[i];
527
+ data[i + 1] = 255 - data[i + 1];
528
+ data[i + 2] = 255 - data[i + 2];
529
+ // Alpha通道保持不变
530
+ }
531
+ }
532
+ return processedImage;
533
+ }
534
+ /**
535
+ * 压缩图片文件
536
+ *
537
+ * @param file 图片文件
538
+ * @param options 压缩选项
539
+ * @returns Promise<File> 压缩后的文件
540
+ */
541
+ static async compressImage(file, options) {
542
+ const defaultOptions = {
543
+ maxSizeMB: 1,
544
+ maxWidthOrHeight: 1920,
545
+ useWebWorker: true,
546
+ quality: 0.8,
547
+ fileType: file.type || "image/jpeg",
548
+ };
549
+ const compressOptions = { ...defaultOptions, ...options };
550
+ try {
551
+ return await imageCompression(file, compressOptions);
552
+ }
553
+ catch (error) {
554
+ console.error("图片压缩失败:", error);
555
+ return file; // 如果压缩失败,返回原始文件
398
556
  }
399
- // 计算缩放比例,保持宽高比
400
- const scale = maxDimension / Math.max(width, height);
401
- const newWidth = Math.round(width * scale);
402
- const newHeight = Math.round(height * scale);
403
- // 调整图像大小
404
- return this.resize(imageData, newWidth, newHeight);
405
557
  }
406
558
  /**
407
- * 转换图像为Base64格式,方便在Worker线程中传递
559
+ * 从图片文件创建ImageData
408
560
  *
409
- * @param {ImageData} imageData - 原图像数据
410
- * @returns {string} base64编码的图像数据
561
+ * @param file 图片文件
562
+ * @returns Promise<ImageData>
411
563
  */
412
- static imageDataToBase64(imageData) {
413
- const canvas = this.imageDataToCanvas(imageData);
414
- return canvas.toDataURL('image/jpeg', 0.7); // 使用较低质量的JPEG格式减少数据量
564
+ static async createImageDataFromFile(file) {
565
+ return new Promise((resolve, reject) => {
566
+ try {
567
+ const img = new Image();
568
+ const url = URL.createObjectURL(file);
569
+ img.onload = () => {
570
+ try {
571
+ // 创建canvas元素
572
+ const canvas = document.createElement("canvas");
573
+ const ctx = canvas.getContext("2d");
574
+ if (!ctx) {
575
+ reject(new Error("无法创建2D上下文"));
576
+ return;
577
+ }
578
+ canvas.width = img.width;
579
+ canvas.height = img.height;
580
+ // 绘制图片到canvas
581
+ ctx.drawImage(img, 0, 0);
582
+ // 获取图像数据
583
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
584
+ // 释放资源
585
+ URL.revokeObjectURL(url);
586
+ resolve(imageData);
587
+ }
588
+ catch (e) {
589
+ reject(e);
590
+ }
591
+ };
592
+ img.onerror = () => {
593
+ URL.revokeObjectURL(url);
594
+ reject(new Error("图片加载失败"));
595
+ };
596
+ img.src = url;
597
+ }
598
+ catch (error) {
599
+ reject(error);
600
+ }
601
+ });
415
602
  }
416
603
  /**
417
- * 从Base64字符串还原图像数据
604
+ * 将ImageData转换为File对象
418
605
  *
419
- * @param {string} base64 - base64编码的图像数据
420
- * @returns {Promise<ImageData>} 还原的图像数据
606
+ * @param imageData ImageData对象
607
+ * @param fileName 输出文件名
608
+ * @param fileType 输出文件类型
609
+ * @param quality 图片质量 (0-1)
610
+ * @returns Promise<File>
421
611
  */
422
- static async base64ToImageData(base64) {
612
+ static async imageDataToFile(imageData, fileName = "image.jpg", fileType = "image/jpeg", quality = 0.8) {
423
613
  return new Promise((resolve, reject) => {
424
- const img = new Image();
425
- img.onload = () => {
426
- const canvas = document.createElement('canvas');
427
- canvas.width = img.width;
428
- canvas.height = img.height;
429
- const ctx = canvas.getContext('2d');
614
+ try {
615
+ const canvas = document.createElement("canvas");
616
+ canvas.width = imageData.width;
617
+ canvas.height = imageData.height;
618
+ const ctx = canvas.getContext("2d");
430
619
  if (!ctx) {
431
- reject(new Error('无法创建Canvas上下文'));
620
+ reject(new Error("无法创建2D上下文"));
432
621
  return;
433
622
  }
434
- ctx.drawImage(img, 0, 0);
435
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
436
- resolve(imageData);
437
- };
438
- img.onerror = () => {
439
- reject(new Error('图像加载失败'));
440
- };
441
- img.src = base64;
623
+ ctx.putImageData(imageData, 0, 0);
624
+ canvas.toBlob((blob) => {
625
+ if (!blob) {
626
+ reject(new Error("无法创建图片Blob"));
627
+ return;
628
+ }
629
+ const file = new File([blob], fileName, { type: fileType });
630
+ resolve(file);
631
+ }, fileType, quality);
632
+ }
633
+ catch (error) {
634
+ reject(error);
635
+ }
442
636
  });
443
637
  }
444
638
  /**
445
- * 使用Web Worker并行处理图像
446
- * 此方法将图像分割为多个部分,并行处理以提高性能
639
+ * 调整图像大小
447
640
  *
448
- * @param {ImageData} imageData - 原图像数据
449
- * @param {Function} processingFunction - 处理函数,接收ImageData返回ImageData
450
- * @param {number} [chunks=4] - 分割的块数
451
- * @returns {Promise<ImageData>} 处理后的图像数据
452
- */
453
- static async processImageInParallel(imageData, processingFunction, chunks = 4) {
454
- // 如果不支持Worker或图像太小,直接处理
455
- if (typeof Worker === 'undefined' || imageData.width * imageData.height < 100000) {
456
- return processingFunction(imageData);
457
- }
458
- // 创建结果canvas
459
- const resultCanvas = document.createElement('canvas');
460
- resultCanvas.width = imageData.width;
461
- resultCanvas.height = imageData.height;
462
- const resultCtx = resultCanvas.getContext('2d');
463
- if (!resultCtx) {
464
- throw new Error('无法创建Canvas上下文');
465
- }
466
- // 根据图像特性确定分割方向和每块大小
467
- const isWide = imageData.width > imageData.height;
468
- const chunkSize = Math.floor((isWide ? imageData.width : imageData.height) / chunks);
469
- // 创建Worker处理每个块
470
- const promises = [];
471
- for (let i = 0; i < chunks; i++) {
472
- const chunkCanvas = document.createElement('canvas');
473
- const chunkCtx = chunkCanvas.getContext('2d');
474
- if (!chunkCtx)
475
- continue;
476
- let chunkImageData;
477
- if (isWide) {
478
- // 水平分割
479
- const startX = i * chunkSize;
480
- const width = (i === chunks - 1) ? imageData.width - startX : chunkSize;
481
- chunkCanvas.width = width;
482
- chunkCanvas.height = imageData.height;
483
- // 复制原图像数据到分块
484
- const tempCanvas = this.imageDataToCanvas(imageData);
485
- chunkCtx.drawImage(tempCanvas, startX, 0, width, imageData.height, 0, 0, width, imageData.height);
486
- chunkImageData = chunkCtx.getImageData(0, 0, width, imageData.height);
487
- }
488
- else {
489
- // 垂直分割
490
- const startY = i * chunkSize;
491
- const height = (i === chunks - 1) ? imageData.height - startY : chunkSize;
492
- chunkCanvas.width = imageData.width;
493
- chunkCanvas.height = height;
494
- // 复制原图像数据到分块
495
- const tempCanvas = this.imageDataToCanvas(imageData);
496
- chunkCtx.drawImage(tempCanvas, 0, startY, imageData.width, height, 0, 0, imageData.width, height);
497
- chunkImageData = chunkCtx.getImageData(0, 0, imageData.width, height);
498
- }
499
- // 使用Worker处理
500
- const workerCode = `
501
- self.onmessage = function(e) {
502
- const imageData = e.data.imageData;
503
- const processingFunction = ${processingFunction.toString()};
504
- const result = processingFunction(imageData);
505
- self.postMessage({ result, index: e.data.index }, [result.data.buffer]);
506
- }
507
- `;
508
- const blob = new Blob([workerCode], { type: 'application/javascript' });
509
- const workerUrl = URL.createObjectURL(blob);
510
- const worker = new Worker(workerUrl);
511
- const promise = new Promise((resolve) => {
512
- worker.onmessage = function (e) {
513
- resolve(e.data);
514
- worker.terminate();
515
- URL.revokeObjectURL(workerUrl);
516
- };
517
- // 传输数据
518
- worker.postMessage({
519
- imageData: chunkImageData,
520
- index: i
521
- }, [chunkImageData.data.buffer]);
522
- });
523
- promises.push(promise);
524
- }
525
- // 等待所有Worker完成并组合结果
526
- const results = await Promise.all(promises);
527
- // 按索引排序结果
528
- results.sort((a, b) => a.index - b.index);
529
- // 将处理后的块绘制到结果canvas
530
- for (let i = 0; i < results.length; i++) {
531
- const { result } = results[i];
532
- const tempCanvas = this.imageDataToCanvas(result);
533
- if (isWide) {
534
- const startX = i * chunkSize;
535
- resultCtx.drawImage(tempCanvas, startX, 0);
536
- }
537
- else {
538
- const startY = i * chunkSize;
539
- resultCtx.drawImage(tempCanvas, 0, startY);
540
- }
641
+ * @param imageData 原始图像数据
642
+ * @param maxWidth 最大宽度
643
+ * @param maxHeight 最大高度
644
+ * @param maintainAspectRatio 是否保持宽高比
645
+ * @returns ImageData 调整大小后的图像数据
646
+ */
647
+ static resizeImage(imageData, maxWidth, maxHeight, maintainAspectRatio = true) {
648
+ const { width, height } = imageData;
649
+ // 如果图像已经小于指定大小,则不需要调整
650
+ if (width <= maxWidth && height <= maxHeight) {
651
+ return imageData;
541
652
  }
542
- return resultCtx.getImageData(0, 0, imageData.width, imageData.height);
653
+ let newWidth = maxWidth;
654
+ let newHeight = maxHeight;
655
+ // 计算新的尺寸,保持宽高比
656
+ if (maintainAspectRatio) {
657
+ const ratio = Math.min(maxWidth / width, maxHeight / height);
658
+ newWidth = Math.floor(width * ratio);
659
+ newHeight = Math.floor(height * ratio);
660
+ }
661
+ // 创建用于调整大小的Canvas
662
+ const canvas = document.createElement("canvas");
663
+ canvas.width = newWidth;
664
+ canvas.height = newHeight;
665
+ const ctx = canvas.getContext("2d");
666
+ if (!ctx) {
667
+ throw new Error("无法创建2D上下文");
668
+ }
669
+ // 创建临时Canvas绘制原始ImageData
670
+ const tempCanvas = document.createElement("canvas");
671
+ tempCanvas.width = width;
672
+ tempCanvas.height = height;
673
+ const tempCtx = tempCanvas.getContext("2d");
674
+ if (!tempCtx) {
675
+ throw new Error("无法创建临时2D上下文");
676
+ }
677
+ tempCtx.putImageData(imageData, 0, 0);
678
+ // 使用缩放平滑算法
679
+ ctx.imageSmoothingEnabled = true;
680
+ ctx.imageSmoothingQuality = "high";
681
+ // 绘制调整大小的图像
682
+ ctx.drawImage(tempCanvas, 0, 0, width, height, 0, 0, newWidth, newHeight);
683
+ // 获取新的ImageData
684
+ return ctx.getImageData(0, 0, newWidth, newHeight);
543
685
  }
544
686
  }
545
687
 
@@ -587,7 +729,7 @@
587
729
  this.scanTimer = null;
588
730
  this.options = {
589
731
  scanInterval: 200,
590
- ...options
732
+ ...options,
591
733
  };
592
734
  this.camera = new Camera();
593
735
  }
@@ -635,7 +777,7 @@
635
777
  this.detectBarcode(enhancedImage);
636
778
  }
637
779
  catch (error) {
638
- console.error('条形码扫描错误:', error);
780
+ console.error("条形码扫描错误:", error);
639
781
  }
640
782
  }
641
783
  this.scanTimer = window.setTimeout(() => this.scan(), this.options.scanInterval);
@@ -652,10 +794,10 @@
652
794
  // 这里应集成条形码识别库
653
795
  // 如 ZXing 或 QuaggaJS
654
796
  // 简化示例,实际项目中请替换为真实实现
655
- console.log('正在扫描条形码...');
797
+ console.log("正在扫描条形码...");
656
798
  // 模拟找到条形码
657
799
  if (Math.random() > 0.95) {
658
- const mockResult = '6901234567890'; // 模拟条形码结果
800
+ const mockResult = "6901234567890"; // 模拟条形码结果
659
801
  if (this.options.onScan) {
660
802
  this.options.onScan(mockResult);
661
803
  }
@@ -674,6 +816,71 @@
674
816
  }
675
817
  this.camera.release();
676
818
  }
819
+ /**
820
+ * 处理图像数据中的条形码
821
+ *
822
+ * @param {ImageData} imageData - 要处理的图像数据
823
+ * @returns {string | null} 识别到的条形码内容,如未识别到则返回null
824
+ */
825
+ processImageData(imageData) {
826
+ try {
827
+ if (!imageData ||
828
+ !imageData.data ||
829
+ imageData.width <= 0 ||
830
+ imageData.height <= 0) {
831
+ throw new Error("无效的图像数据");
832
+ }
833
+ // 图像预处理,提高识别率
834
+ const enhancedImage = ImageProcessor.adjustBrightnessContrast(ImageProcessor.toGrayscale(imageData), 10, // 亮度
835
+ 20 // 对比度
836
+ );
837
+ // 注意:这里是简化实现
838
+ // 实际项目中,应该集成专门的条形码识别库如ZXing或Quagga.js
839
+ // 模拟条形码识别
840
+ // 在真实项目中,请替换为实际的条形码识别算法
841
+ const result = this.simulateBarcodeDetection(enhancedImage);
842
+ return result;
843
+ }
844
+ catch (error) {
845
+ if (this.options.onError) {
846
+ this.options.onError(error instanceof Error ? error : new Error(String(error)));
847
+ }
848
+ return null;
849
+ }
850
+ }
851
+ /**
852
+ * 模拟条形码检测
853
+ * 仅用于演示,实际使用时应该替换为真实的条形码识别算法
854
+ *
855
+ * @private
856
+ * @param {ImageData} imageData - 要检测条形码的图像数据
857
+ * @returns {string | null} 模拟的条形码识别结果
858
+ */
859
+ simulateBarcodeDetection(imageData) {
860
+ // 这里只是模拟,真实环境中应当使用条形码识别库进行识别
861
+ // 在中间区域检测到足够多垂直边缘时,认为可能存在条形码
862
+ const midX = Math.floor(imageData.width / 2);
863
+ const midY = Math.floor(imageData.height / 2);
864
+ const sampleWidth = Math.min(100, Math.floor(imageData.width / 3));
865
+ let edgeCount = 0;
866
+ let lastPixel = 0;
867
+ // 简单的边缘检测,统计中心水平线上像素变化次数
868
+ for (let x = midX - sampleWidth / 2; x < midX + sampleWidth / 2; x++) {
869
+ const pixelPos = (midY * imageData.width + x) * 4;
870
+ const pixelValue = imageData.data[pixelPos];
871
+ if (Math.abs(pixelValue - lastPixel) > 30) {
872
+ edgeCount++;
873
+ }
874
+ lastPixel = pixelValue;
875
+ }
876
+ // 如果边缘变化次数在合理范围内,认为是条形码
877
+ // 实际的条形码具有规律的宽窄条纹
878
+ if (edgeCount > 10 && edgeCount < 50) {
879
+ // 生成一个模拟的条形码结果
880
+ return "690" + Math.floor(Math.random() * 10000000000);
881
+ }
882
+ return null;
883
+ }
677
884
  }
678
885
 
679
886
  /**
@@ -752,7 +959,9 @@
752
959
  // 如果缓存已满,移除最老的项
753
960
  if (this.cache.size >= this.maxSize) {
754
961
  const oldestKey = this.cache.keys().next().value;
755
- this.cache.delete(oldestKey);
962
+ if (oldestKey !== undefined) {
963
+ this.cache.delete(oldestKey);
964
+ }
756
965
  }
757
966
  // 添加新项
758
967
  this.cache.set(key, value);
@@ -875,7 +1084,7 @@
875
1084
  this.frameCount = 0;
876
1085
  this.lastDetectionTime = 0;
877
1086
  this.camera = new Camera();
878
- if (typeof options === 'function') {
1087
+ if (typeof options === "function") {
879
1088
  // 兼容旧的构造函数方式
880
1089
  this.onDetected = options;
881
1090
  this.options = {
@@ -883,7 +1092,7 @@
883
1092
  maxImageDimension: 800,
884
1093
  enableCache: true,
885
1094
  cacheSize: 20,
886
- logger: console.log
1095
+ logger: console.log,
887
1096
  };
888
1097
  }
889
1098
  else if (options) {
@@ -894,7 +1103,7 @@
894
1103
  enableCache: true,
895
1104
  cacheSize: 20,
896
1105
  logger: console.log,
897
- ...options
1106
+ ...options,
898
1107
  };
899
1108
  this.onDetected = options.onDetection;
900
1109
  this.onError = options.onError;
@@ -905,7 +1114,7 @@
905
1114
  maxImageDimension: 800,
906
1115
  enableCache: true,
907
1116
  cacheSize: 20,
908
- logger: console.log
1117
+ logger: console.log,
909
1118
  };
910
1119
  }
911
1120
  this.detectionInterval = this.options.detectionInterval;
@@ -954,7 +1163,8 @@
954
1163
  const now = performance.now();
955
1164
  // 帧率控制 - 只有满足时间间隔的帧才进行检测
956
1165
  // 这样可以显著减少CPU使用率,同时保持良好的用户体验
957
- if (this.frameCount % 3 === 0 || now - this.lastDetectionTime >= this.detectionInterval) {
1166
+ if (this.frameCount % 3 === 0 ||
1167
+ now - this.lastDetectionTime >= this.detectionInterval) {
958
1168
  this.throttledDetect();
959
1169
  this.lastDetectionTime = now;
960
1170
  }
@@ -966,7 +1176,7 @@
966
1176
  this.onError(error);
967
1177
  }
968
1178
  else {
969
- console.error('身份证检测错误:', error);
1179
+ console.error("身份证检测错误:", error);
970
1180
  }
971
1181
  // 出错后延迟重试
972
1182
  setTimeout(() => {
@@ -994,11 +1204,11 @@
994
1204
  const fingerprint = calculateImageFingerprint(frame, 16); // 使用更大的尺寸提高特征区分度
995
1205
  const cachedResult = this.resultCache.get(fingerprint);
996
1206
  if (cachedResult) {
997
- this.options.logger?.('使用缓存的检测结果');
1207
+ this.options.logger?.("使用缓存的检测结果");
998
1208
  // 使用缓存结果,但更新图像数据以确保最新
999
1209
  const updatedResult = {
1000
1210
  ...cachedResult,
1001
- imageData: frame
1211
+ imageData: frame,
1002
1212
  };
1003
1213
  if (this.onDetected) {
1004
1214
  this.onDetected(updatedResult);
@@ -1007,7 +1217,7 @@
1007
1217
  }
1008
1218
  }
1009
1219
  // 降低分辨率以提高性能
1010
- const downsampledFrame = ImageProcessor.downsampleForProcessing(frame, this.maxImageDimension);
1220
+ const downsampledFrame = ImageProcessor.resizeImage(frame, this.maxImageDimension, this.maxImageDimension);
1011
1221
  try {
1012
1222
  // 检测身份证
1013
1223
  const result = await this.detectIDCard(downsampledFrame);
@@ -1030,7 +1240,7 @@
1030
1240
  this.onError(error);
1031
1241
  }
1032
1242
  else {
1033
- console.error('身份证检测错误:', error);
1243
+ console.error("身份证检测错误:", error);
1034
1244
  }
1035
1245
  }
1036
1246
  }
@@ -1051,7 +1261,7 @@
1051
1261
  // 在实际应用中,此处应当实现实际的计算机视觉算法
1052
1262
  const detectionResult = {
1053
1263
  success: Math.random() > 0.3, // 70%的概率成功检测到
1054
- message: '身份证检测完成'
1264
+ message: "身份证检测完成",
1055
1265
  };
1056
1266
  if (detectionResult.success) {
1057
1267
  // 模拟一个身份证矩形区域
@@ -1067,20 +1277,20 @@
1067
1277
  { x: rectX, y: rectY },
1068
1278
  { x: rectX + rectWidth, y: rectY },
1069
1279
  { x: rectX + rectWidth, y: rectY + rectHeight },
1070
- { x: rectX, y: rectY + rectHeight }
1280
+ { x: rectX, y: rectY + rectHeight },
1071
1281
  ];
1072
1282
  // 添加边界框
1073
1283
  detectionResult.boundingBox = {
1074
1284
  x: rectX,
1075
1285
  y: rectY,
1076
1286
  width: rectWidth,
1077
- height: rectHeight
1287
+ height: rectHeight,
1078
1288
  };
1079
1289
  // 裁剪身份证图像
1080
- const canvas = document.createElement('canvas');
1290
+ const canvas = document.createElement("canvas");
1081
1291
  canvas.width = rectWidth;
1082
1292
  canvas.height = rectHeight;
1083
- const ctx = canvas.getContext('2d');
1293
+ const ctx = canvas.getContext("2d");
1084
1294
  if (ctx) {
1085
1295
  const tempCanvas = ImageProcessor.imageDataToCanvas(imageData);
1086
1296
  ctx.drawImage(tempCanvas, rectX, rectY, rectWidth, rectHeight, 0, 0, rectWidth, rectHeight);
@@ -1096,7 +1306,7 @@
1096
1306
  */
1097
1307
  clearCache() {
1098
1308
  this.resultCache.clear();
1099
- this.options.logger?.('检测结果缓存已清除');
1309
+ this.options.logger?.("检测结果缓存已清除");
1100
1310
  }
1101
1311
  /**
1102
1312
  * 释放资源
@@ -1202,9 +1412,9 @@
1202
1412
  // 加载Tesseract.js (Worker 环境下动态导入)
1203
1413
  const { createWorker } = await import('tesseract.js');
1204
1414
  // 创建OCR Worker
1205
- const worker = createWorker(input.tessWorkerOptions || {
1415
+ const worker = await createWorker(input.tessWorkerOptions || {
1206
1416
  logger: (m) => console.log(m)
1207
- });
1417
+ }); // 添加类型断言,避免TypeScript错误
1208
1418
  try {
1209
1419
  // 初始化OCR引擎
1210
1420
  await worker.load();
@@ -1336,7 +1546,7 @@
1336
1546
  cacheSize: 50,
1337
1547
  maxImageDimension: 1000,
1338
1548
  logger: console.log,
1339
- ...options
1549
+ ...options,
1340
1550
  };
1341
1551
  // 初始化缓存
1342
1552
  this.resultCache = new LRUCache(this.options.cacheSize);
@@ -1355,21 +1565,21 @@
1355
1565
  // 使用自定义Worker线程处理OCR
1356
1566
  this.ocrWorker = createWorker(processOCRInWorker);
1357
1567
  this.initialized = true;
1358
- this.options.logger?.('OCR Worker 初始化完成');
1568
+ this.options.logger?.("OCR Worker 初始化完成");
1359
1569
  }
1360
1570
  else {
1361
1571
  // 使用主线程处理OCR
1362
1572
  this.worker = tesseract_js.createWorker({
1363
- logger: this.options.logger
1573
+ logger: this.options.logger,
1364
1574
  });
1365
1575
  await this.worker.load();
1366
- await this.worker.loadLanguage('chi_sim');
1367
- await this.worker.initialize('chi_sim');
1576
+ await this.worker.loadLanguage("chi_sim");
1577
+ await this.worker.initialize("chi_sim");
1368
1578
  await this.worker.setParameters({
1369
- tessedit_char_whitelist: '0123456789X-年月日一二三四五六七八九十零壹贰叁肆伍陆柒捌玖拾ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz民族汉族满族回族维吾尔族藏族苗族彝族壮族朝鲜族侗族瑶族白族土家族哈尼族哈萨克族傣族黎族傈僳族佤族高山族拉祜族水族东乡族钠西族景颇族柯尔克孜族士族达斡尔族仫佬族羌族布朗族撒拉族毛南族仡佬族锡伯族阿昌族普米族塔吉克族怒族乌孜别克族俄罗斯族鄂温克族德昂族保安族裕固族京族塔塔尔族独龙族鄂伦春族赫哲族门巴族珞巴族基诺族男女性别住址出生公民身份号码签发机关有效期'
1579
+ tessedit_char_whitelist: "0123456789X-年月日一二三四五六七八九十零壹贰叁肆伍陆柒捌玖拾ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz民族汉族满族回族维吾尔族藏族苗族彝族壮族朝鲜族侗族瑶族白族土家族哈尼族哈萨克族傣族黎族傈僳族佤族高山族拉祜族水族东乡族钠西族景颇族柯尔克孜族士族达斡尔族仫佬族羌族布朗族撒拉族毛南族仡佬族锡伯族阿昌族普米族塔吉克族怒族乌孜别克族俄罗斯族鄂温克族德昂族保安族裕固族京族塔塔尔族独龙族鄂伦春族赫哲族门巴族珞巴族基诺族男女性别住址出生公民身份号码签发机关有效期",
1370
1580
  });
1371
1581
  this.initialized = true;
1372
- this.options.logger?.('OCR引擎初始化完成');
1582
+ this.options.logger?.("OCR引擎初始化完成");
1373
1583
  }
1374
1584
  }
1375
1585
  /**
@@ -1387,24 +1597,42 @@
1387
1597
  // 检查缓存中是否有结果
1388
1598
  const cachedResult = this.resultCache.get(fingerprint);
1389
1599
  if (cachedResult) {
1390
- this.options.logger?.('使用缓存的OCR结果');
1600
+ this.options.logger?.("使用缓存的OCR结果");
1391
1601
  return cachedResult;
1392
1602
  }
1393
1603
  }
1394
- // 图像预处理:降低分辨率和增强对比度
1395
- const downsampledImage = ImageProcessor.downsampleForProcessing(imageData, this.options.maxImageDimension);
1396
- const enhancedImage = ImageProcessor.adjustBrightnessContrast(downsampledImage, 15, 25);
1604
+ // 调整图像大小以提高性能和准确性
1605
+ const downsampledImage = ImageProcessor.resizeImage(imageData, this.options.maxImageDimension || 1000, this.options.maxImageDimension || 1000, true // 保持宽高比
1606
+ );
1607
+ // 提高图像质量以获得更好的OCR结果
1608
+ const enhancedImage = ImageProcessor.batchProcess(downsampledImage, {
1609
+ brightness: this.options.brightness || 15,
1610
+ contrast: this.options.contrast || 25,
1611
+ sharpen: true,
1612
+ });
1613
+ // 转换为base64供Tesseract处理
1614
+ // 创建一个canvas元素
1615
+ const canvas = document.createElement("canvas");
1616
+ canvas.width = enhancedImage.width;
1617
+ canvas.height = enhancedImage.height;
1618
+ const ctx = canvas.getContext("2d");
1619
+ if (!ctx) {
1620
+ throw new Error("无法创建canvas上下文");
1621
+ }
1622
+ // 将ImageData绘制到canvas
1623
+ ctx.putImageData(enhancedImage, 0, 0);
1624
+ // 转换为Base64
1625
+ const base64Image = canvas.toDataURL("image/jpeg", 0.7);
1397
1626
  // OCR识别
1398
1627
  try {
1399
1628
  let idCardInfo;
1400
1629
  if (this.options.useWorker && this.ocrWorker) {
1401
1630
  // 使用Worker线程处理
1402
- const base64Image = ImageProcessor.imageDataToBase64(enhancedImage);
1403
1631
  const result = await this.ocrWorker.postMessage({
1404
1632
  imageBase64: base64Image,
1405
1633
  tessWorkerOptions: {
1406
- logger: this.options.logger
1407
- }
1634
+ logger: this.options.logger,
1635
+ },
1408
1636
  });
1409
1637
  idCardInfo = result.idCardInfo;
1410
1638
  this.options.logger?.(`OCR处理完成,用时: ${result.processingTime.toFixed(2)}ms`);
@@ -1444,7 +1672,7 @@
1444
1672
  parseIDCardText(text) {
1445
1673
  const info = {};
1446
1674
  // 拆分为行
1447
- const lines = text.split('\n').filter(line => line.trim());
1675
+ const lines = text.split("\n").filter((line) => line.trim());
1448
1676
  // 解析身份证号码(最容易识别的部分)
1449
1677
  const idNumberRegex = /(\d{17}[\dX])/;
1450
1678
  const idNumberMatch = text.match(idNumberRegex);
@@ -1453,8 +1681,9 @@
1453
1681
  }
1454
1682
  // 解析姓名
1455
1683
  for (const line of lines) {
1456
- if (line.includes('姓名') || line.length < 10 && line.length > 1 && !/\d/.test(line)) {
1457
- info.name = line.replace('姓名', '').trim();
1684
+ if (line.includes("姓名") ||
1685
+ (line.length < 10 && line.length > 1 && !/\d/.test(line))) {
1686
+ info.name = line.replace("姓名", "").trim();
1458
1687
  break;
1459
1688
  }
1460
1689
  }
@@ -1464,7 +1693,9 @@
1464
1693
  if (genderMatch) {
1465
1694
  info.gender = genderMatch[1];
1466
1695
  const nationalityText = genderMatch[0];
1467
- info.nationality = nationalityText.substring(nationalityText.indexOf(genderMatch[1]) + 1).trim();
1696
+ info.nationality = nationalityText
1697
+ .substring(nationalityText.indexOf(genderMatch[1]) + 1)
1698
+ .trim();
1468
1699
  }
1469
1700
  // 解析出生日期
1470
1701
  const birthDateRegex = /(\d{4})年(\d{1,2})月(\d{1,2})日/;
@@ -1476,19 +1707,19 @@
1476
1707
  const addressRegex = /住址([\s\S]*?)公民身份号码/;
1477
1708
  const addressMatch = text.match(addressRegex);
1478
1709
  if (addressMatch) {
1479
- info.address = addressMatch[1].replace(/\n/g, '').trim();
1710
+ info.address = addressMatch[1].replace(/\n/g, "").trim();
1480
1711
  }
1481
1712
  // 解析签发机关
1482
1713
  const authorityRegex = /签发机关([\s\S]*?)有效期/;
1483
1714
  const authorityMatch = text.match(authorityRegex);
1484
1715
  if (authorityMatch) {
1485
- info.issuingAuthority = authorityMatch[1].replace(/\n/g, '').trim();
1716
+ info.issuingAuthority = authorityMatch[1].replace(/\n/g, "").trim();
1486
1717
  }
1487
1718
  // 解析有效期限
1488
1719
  const validPeriodRegex = /有效期限([\s\S]*?)(-|至)/;
1489
1720
  const validPeriodMatch = text.match(validPeriodRegex);
1490
1721
  if (validPeriodMatch) {
1491
- info.validPeriod = validPeriodMatch[0].replace('有效期限', '').trim();
1722
+ info.validPeriod = validPeriodMatch[0].replace("有效期限", "").trim();
1492
1723
  }
1493
1724
  return info;
1494
1725
  }
@@ -1497,7 +1728,7 @@
1497
1728
  */
1498
1729
  clearCache() {
1499
1730
  this.resultCache.clear();
1500
- this.options.logger?.('OCR结果缓存已清除');
1731
+ this.options.logger?.("OCR结果缓存已清除");
1501
1732
  }
1502
1733
  /**
1503
1734
  * 终止OCR引擎并释放资源
@@ -1514,7 +1745,7 @@
1514
1745
  this.ocrWorker = null;
1515
1746
  }
1516
1747
  this.initialized = false;
1517
- this.options.logger?.('OCR引擎已终止');
1748
+ this.options.logger?.("OCR引擎已终止");
1518
1749
  }
1519
1750
  /**
1520
1751
  * 释放资源
@@ -1754,30 +1985,33 @@
1754
1985
  }
1755
1986
 
1756
1987
  /**
1757
- * @file ID扫描识别库UMD格式入口文件
1758
- * @description 专门为UMD格式构建的入口,使用静态导入而非动态导入
1988
+ * @file ID扫描识别库主入口文件
1989
+ * @description 提供身份证识别与二维码、条形码扫描功能的纯前端TypeScript库
1759
1990
  * @module IDScannerLib
1760
- * @version 1.1.0
1991
+ * @version 1.0.0
1761
1992
  * @license MIT
1762
1993
  */
1763
1994
  /**
1764
1995
  * IDScanner 主类
1765
- * UMD版本使用静态导入实现
1996
+ *
1997
+ * 整合二维码、条形码扫描和身份证识别功能,提供统一的接口
1998
+ * 使用动态导入实现按需加载
1766
1999
  */
1767
- class IDScanner {
2000
+ let IDScanner$1 = class IDScanner {
1768
2001
  /**
1769
2002
  * 构造函数
1770
2003
  * @param options 配置选项
1771
2004
  */
1772
2005
  constructor(options = {}) {
1773
2006
  this.options = options;
1774
- this.qrScanner = null;
1775
- this.barcodeScanner = null;
1776
- this.idDetector = null;
1777
- this.ocrProcessor = null;
1778
- this.dataExtractor = null;
1779
- this.scanMode = 'qr';
2007
+ this.scanMode = "qr";
1780
2008
  this.videoElement = null;
2009
+ // 延迟加载的模块
2010
+ this.qrModule = null;
2011
+ this.ocrModule = null;
2012
+ // 模块加载状态
2013
+ this.isQRModuleLoaded = false;
2014
+ this.isOCRModuleLoaded = false;
1781
2015
  this.camera = new Camera(options.cameraOptions);
1782
2016
  }
1783
2017
  /**
@@ -1786,11 +2020,20 @@
1786
2020
  */
1787
2021
  async initialize() {
1788
2022
  try {
1789
- // 初始化OCR模块
1790
- this.ocrProcessor = new OCRProcessor();
1791
- this.dataExtractor = new DataExtractor();
1792
- await this.ocrProcessor.initialize();
1793
- console.log('IDScanner initialized');
2023
+ // 预加载OCR模块但不初始化
2024
+ if (!this.isOCRModuleLoaded) {
2025
+ // 动态导入OCR模块
2026
+ const OCRModule = await Promise.resolve().then(function () { return ocrModule; }).then((m) => m.OCRModule);
2027
+ this.ocrModule = new OCRModule({
2028
+ cameraOptions: this.options.cameraOptions,
2029
+ onIDCardScanned: this.options.onIDCardScanned,
2030
+ onError: this.options.onError,
2031
+ });
2032
+ this.isOCRModuleLoaded = true;
2033
+ // 初始化OCR模块
2034
+ await this.ocrModule.initialize();
2035
+ }
2036
+ console.log("IDScanner initialized");
1794
2037
  }
1795
2038
  catch (error) {
1796
2039
  this.handleError(error);
@@ -1804,16 +2047,22 @@
1804
2047
  async startQRScanner(videoElement) {
1805
2048
  this.stop();
1806
2049
  this.videoElement = videoElement;
1807
- this.scanMode = 'qr';
2050
+ this.scanMode = "qr";
1808
2051
  try {
1809
- if (!this.qrScanner) {
1810
- this.qrScanner = new QRScanner({
1811
- ...this.options.qrScannerOptions,
1812
- onScan: this.handleQRScan.bind(this)
2052
+ // 动态加载二维码模块
2053
+ if (!this.isQRModuleLoaded) {
2054
+ const ScannerModule = await Promise.resolve().then(function () { return qrModule; }).then((m) => m.ScannerModule);
2055
+ this.qrModule = new ScannerModule({
2056
+ cameraOptions: this.options.cameraOptions,
2057
+ qrScannerOptions: this.options.qrScannerOptions,
2058
+ barcodeScannerOptions: this.options.barcodeScannerOptions,
2059
+ onQRCodeScanned: this.options.onQRCodeScanned,
2060
+ onBarcodeScanned: this.options.onBarcodeScanned,
2061
+ onError: this.options.onError,
1813
2062
  });
2063
+ this.isQRModuleLoaded = true;
1814
2064
  }
1815
- await this.camera.start(videoElement);
1816
- this.qrScanner.start(videoElement);
2065
+ await this.qrModule.startQRScanner(videoElement);
1817
2066
  }
1818
2067
  catch (error) {
1819
2068
  this.handleError(error);
@@ -1826,16 +2075,22 @@
1826
2075
  async startBarcodeScanner(videoElement) {
1827
2076
  this.stop();
1828
2077
  this.videoElement = videoElement;
1829
- this.scanMode = 'barcode';
2078
+ this.scanMode = "barcode";
1830
2079
  try {
1831
- if (!this.barcodeScanner) {
1832
- this.barcodeScanner = new BarcodeScanner({
1833
- ...this.options.barcodeScannerOptions,
1834
- onScan: this.handleBarcodeScan.bind(this)
2080
+ // 动态加载二维码模块
2081
+ if (!this.isQRModuleLoaded) {
2082
+ const ScannerModule = await Promise.resolve().then(function () { return qrModule; }).then((m) => m.ScannerModule);
2083
+ this.qrModule = new ScannerModule({
2084
+ cameraOptions: this.options.cameraOptions,
2085
+ qrScannerOptions: this.options.qrScannerOptions,
2086
+ barcodeScannerOptions: this.options.barcodeScannerOptions,
2087
+ onQRCodeScanned: this.options.onQRCodeScanned,
2088
+ onBarcodeScanned: this.options.onBarcodeScanned,
2089
+ onError: this.options.onError,
1835
2090
  });
2091
+ this.isQRModuleLoaded = true;
1836
2092
  }
1837
- await this.camera.start(videoElement);
1838
- this.barcodeScanner.start(videoElement);
2093
+ await this.qrModule.startBarcodeScanner(videoElement);
1839
2094
  }
1840
2095
  catch (error) {
1841
2096
  this.handleError(error);
@@ -1848,19 +2103,13 @@
1848
2103
  async startIDCardScanner(videoElement) {
1849
2104
  this.stop();
1850
2105
  this.videoElement = videoElement;
1851
- this.scanMode = 'idcard';
2106
+ this.scanMode = "idcard";
1852
2107
  try {
1853
- if (!this.ocrProcessor) {
2108
+ // 检查OCR模块是否已加载,若未加载则自动初始化
2109
+ if (!this.isOCRModuleLoaded) {
1854
2110
  await this.initialize();
1855
2111
  }
1856
- if (!this.idDetector) {
1857
- this.idDetector = new IDCardDetector({
1858
- onDetection: this.handleIDDetection.bind(this),
1859
- onError: this.handleError.bind(this)
1860
- });
1861
- }
1862
- await this.camera.start(videoElement);
1863
- this.idDetector.start(videoElement);
2112
+ await this.ocrModule.startIDCardScanner(videoElement);
1864
2113
  }
1865
2114
  catch (error) {
1866
2115
  this.handleError(error);
@@ -1870,89 +2119,1262 @@
1870
2119
  * 停止扫描
1871
2120
  */
1872
2121
  stop() {
1873
- if (this.scanMode === 'qr' && this.qrScanner) {
1874
- this.qrScanner.stop();
1875
- }
1876
- else if (this.scanMode === 'barcode' && this.barcodeScanner) {
1877
- this.barcodeScanner.stop();
2122
+ if (this.scanMode === "qr" || this.scanMode === "barcode") {
2123
+ if (this.qrModule) {
2124
+ this.qrModule.stop();
2125
+ }
1878
2126
  }
1879
- else if (this.scanMode === 'idcard' && this.idDetector) {
1880
- this.idDetector.stop();
2127
+ else if (this.scanMode === "idcard") {
2128
+ if (this.ocrModule) {
2129
+ this.ocrModule.stop();
2130
+ }
1881
2131
  }
1882
- this.camera.stop();
1883
2132
  }
1884
2133
  /**
1885
- * 处理二维码扫描结果
2134
+ * 处理错误
1886
2135
  */
1887
- handleQRScan(result) {
1888
- if (this.options.onQRCodeScanned) {
1889
- this.options.onQRCodeScanned(result);
2136
+ handleError(error) {
2137
+ if (this.options.onError) {
2138
+ this.options.onError(error);
2139
+ }
2140
+ else {
2141
+ console.error("IDScanner error:", error);
1890
2142
  }
1891
2143
  }
1892
2144
  /**
1893
- * 处理条形码扫描结果
2145
+ * 释放资源
1894
2146
  */
1895
- handleBarcodeScan(result) {
1896
- if (this.options.onBarcodeScanned) {
1897
- this.options.onBarcodeScanned(result);
2147
+ async terminate() {
2148
+ this.stop();
2149
+ // 释放OCR资源
2150
+ if (this.isOCRModuleLoaded && this.ocrModule) {
2151
+ await this.ocrModule.terminate();
2152
+ this.ocrModule = null;
2153
+ this.isOCRModuleLoaded = false;
2154
+ }
2155
+ // 释放QR扫描资源
2156
+ if (this.isQRModuleLoaded && this.qrModule) {
2157
+ this.qrModule = null;
2158
+ this.isQRModuleLoaded = false;
1898
2159
  }
1899
2160
  }
1900
2161
  /**
1901
- * 处理身份证检测结果
2162
+ * 处理图片中的二维码
2163
+ * @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
2164
+ * @returns 返回Promise,解析为扫描结果
1902
2165
  */
1903
- async handleIDDetection(result) {
1904
- if (!this.ocrProcessor || !this.dataExtractor)
1905
- return;
2166
+ async processQRCodeImage(imageSource) {
1906
2167
  try {
1907
- // 检查 imageData 是否存在
1908
- if (!result.imageData) {
1909
- this.handleError(new Error('无效的图像数据'));
1910
- return;
2168
+ // 动态加载二维码模块
2169
+ if (!this.isQRModuleLoaded) {
2170
+ const ScannerModule = await Promise.resolve().then(function () { return qrModule; }).then((m) => m.ScannerModule);
2171
+ this.qrModule = new ScannerModule({
2172
+ qrScannerOptions: this.options.qrScannerOptions,
2173
+ onQRCodeScanned: this.options.onQRCodeScanned,
2174
+ onError: this.options.onError,
2175
+ });
2176
+ this.isQRModuleLoaded = true;
1911
2177
  }
1912
- const idCardInfo = await this.ocrProcessor.processIDCard(result.imageData);
1913
- const extractedInfo = this.dataExtractor.extractAndValidate(idCardInfo);
1914
- if (this.options.onIDCardScanned) {
1915
- this.options.onIDCardScanned(extractedInfo);
2178
+ // 处理不同类型的图片源
2179
+ let imageElement;
2180
+ if (imageSource instanceof File) {
2181
+ // 如果是File对象,创建新的Image元素并加载图片
2182
+ imageElement = new Image();
2183
+ imageElement.crossOrigin = "anonymous"; // 处理跨域图片
2184
+ const url = URL.createObjectURL(imageSource);
2185
+ await new Promise((resolve, reject) => {
2186
+ imageElement.onload = resolve;
2187
+ imageElement.onerror = reject;
2188
+ imageElement.src = url;
2189
+ });
2190
+ // 使用后释放URL对象
2191
+ URL.revokeObjectURL(url);
2192
+ }
2193
+ else if (typeof imageSource === "string") {
2194
+ // 如果是URL字符串,创建新的Image元素并加载图片
2195
+ imageElement = new Image();
2196
+ imageElement.crossOrigin = "anonymous"; // 处理跨域图片
2197
+ await new Promise((resolve, reject) => {
2198
+ imageElement.onload = resolve;
2199
+ imageElement.onerror = reject;
2200
+ imageElement.src = imageSource;
2201
+ });
2202
+ }
2203
+ else if (imageSource instanceof HTMLImageElement) {
2204
+ // 如果已经是Image元素,直接使用
2205
+ imageElement = imageSource;
2206
+ }
2207
+ else if (imageSource instanceof HTMLCanvasElement) {
2208
+ // 如果是Canvas元素,创建Image并从Canvas获取数据
2209
+ imageElement = new Image();
2210
+ imageElement.src = imageSource.toDataURL();
2211
+ await new Promise((resolve) => {
2212
+ imageElement.onload = resolve;
2213
+ });
2214
+ }
2215
+ else {
2216
+ throw new Error("不支持的图片源类型");
1916
2217
  }
2218
+ // 获取图像数据
2219
+ const canvas = document.createElement("canvas");
2220
+ canvas.width = imageElement.naturalWidth;
2221
+ canvas.height = imageElement.naturalHeight;
2222
+ const ctx = canvas.getContext("2d");
2223
+ if (!ctx) {
2224
+ throw new Error("无法创建Canvas上下文");
2225
+ }
2226
+ ctx.drawImage(imageElement, 0, 0);
2227
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
2228
+ // 使用QR模块处理图像
2229
+ return this.qrModule.processQRCodeImage(imageData);
1917
2230
  }
1918
2231
  catch (error) {
1919
2232
  this.handleError(error);
2233
+ throw error;
1920
2234
  }
1921
2235
  }
1922
2236
  /**
1923
- * 处理错误
2237
+ * 处理图片中的条形码
2238
+ * @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
2239
+ * @returns 返回Promise,解析为扫描结果
1924
2240
  */
1925
- handleError(error) {
1926
- if (this.options.onError) {
1927
- this.options.onError(error);
2241
+ async processBarcodeImage(imageSource) {
2242
+ try {
2243
+ // 动态加载二维码模块
2244
+ if (!this.isQRModuleLoaded) {
2245
+ const ScannerModule = await Promise.resolve().then(function () { return qrModule; }).then((m) => m.ScannerModule);
2246
+ this.qrModule = new ScannerModule({
2247
+ barcodeScannerOptions: this.options.barcodeScannerOptions,
2248
+ onBarcodeScanned: this.options.onBarcodeScanned,
2249
+ onError: this.options.onError,
2250
+ });
2251
+ this.isQRModuleLoaded = true;
2252
+ }
2253
+ // 处理不同类型的图片源
2254
+ let imageElement;
2255
+ if (imageSource instanceof File) {
2256
+ // 如果是File对象,创建新的Image元素并加载图片
2257
+ imageElement = new Image();
2258
+ imageElement.crossOrigin = "anonymous"; // 处理跨域图片
2259
+ const url = URL.createObjectURL(imageSource);
2260
+ await new Promise((resolve, reject) => {
2261
+ imageElement.onload = resolve;
2262
+ imageElement.onerror = reject;
2263
+ imageElement.src = url;
2264
+ });
2265
+ // 使用后释放URL对象
2266
+ URL.revokeObjectURL(url);
2267
+ }
2268
+ else if (typeof imageSource === "string") {
2269
+ // 如果是URL字符串,创建新的Image元素并加载图片
2270
+ imageElement = new Image();
2271
+ imageElement.crossOrigin = "anonymous"; // 处理跨域图片
2272
+ await new Promise((resolve, reject) => {
2273
+ imageElement.onload = resolve;
2274
+ imageElement.onerror = reject;
2275
+ imageElement.src = imageSource;
2276
+ });
2277
+ }
2278
+ else if (imageSource instanceof HTMLImageElement) {
2279
+ // 如果已经是Image元素,直接使用
2280
+ imageElement = imageSource;
2281
+ }
2282
+ else if (imageSource instanceof HTMLCanvasElement) {
2283
+ // 如果是Canvas元素,创建Image并从Canvas获取数据
2284
+ imageElement = new Image();
2285
+ imageElement.src = imageSource.toDataURL();
2286
+ await new Promise((resolve) => {
2287
+ imageElement.onload = resolve;
2288
+ });
2289
+ }
2290
+ else {
2291
+ throw new Error("不支持的图片源类型");
2292
+ }
2293
+ // 获取图像数据
2294
+ const canvas = document.createElement("canvas");
2295
+ canvas.width = imageElement.naturalWidth;
2296
+ canvas.height = imageElement.naturalHeight;
2297
+ const ctx = canvas.getContext("2d");
2298
+ if (!ctx) {
2299
+ throw new Error("无法创建Canvas上下文");
2300
+ }
2301
+ ctx.drawImage(imageElement, 0, 0);
2302
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
2303
+ // 使用Barcode模块处理图像
2304
+ return this.qrModule.processBarcodeImage(imageData);
1928
2305
  }
1929
- else {
1930
- console.error('IDScanner error:', error);
2306
+ catch (error) {
2307
+ this.handleError(error);
2308
+ throw error;
1931
2309
  }
1932
2310
  }
1933
2311
  /**
1934
- * 释放资源
2312
+ * 处理图片中的身份证
2313
+ * @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
2314
+ * @returns 返回Promise,解析为身份证信息
1935
2315
  */
1936
- async terminate() {
1937
- this.stop();
1938
- if (this.ocrProcessor) {
1939
- await this.ocrProcessor.terminate();
1940
- this.ocrProcessor = null;
1941
- }
1942
- this.qrScanner = null;
1943
- this.barcodeScanner = null;
1944
- this.idDetector = null;
1945
- this.dataExtractor = null;
1946
- }
2316
+ async processIDCardImage(imageSource) {
2317
+ try {
2318
+ // 检查OCR模块是否已加载,若未加载则自动初始化
2319
+ if (!this.isOCRModuleLoaded) {
2320
+ await this.initialize();
2321
+ }
2322
+ // 处理不同类型的图片源
2323
+ let imageElement;
2324
+ if (imageSource instanceof File) {
2325
+ // 如果是File对象,先进行压缩
2326
+ const compressedFile = await ImageProcessor.compressImage(imageSource, {
2327
+ maxSizeMB: 2, // 最大2MB
2328
+ maxWidthOrHeight: 1800, // 最大尺寸
2329
+ useWebWorker: true,
2330
+ });
2331
+ // 创建新的Image元素并加载图片
2332
+ imageElement = new Image();
2333
+ imageElement.crossOrigin = "anonymous"; // 处理跨域图片
2334
+ const url = URL.createObjectURL(compressedFile);
2335
+ await new Promise((resolve, reject) => {
2336
+ imageElement.onload = resolve;
2337
+ imageElement.onerror = reject;
2338
+ imageElement.src = url;
2339
+ });
2340
+ // 使用后释放URL对象
2341
+ URL.revokeObjectURL(url);
2342
+ }
2343
+ else if (typeof imageSource === "string") {
2344
+ // 如果是URL字符串,创建新的Image元素并加载图片
2345
+ imageElement = new Image();
2346
+ imageElement.crossOrigin = "anonymous"; // 处理跨域图片
2347
+ await new Promise((resolve, reject) => {
2348
+ imageElement.onload = resolve;
2349
+ imageElement.onerror = reject;
2350
+ imageElement.src = imageSource;
2351
+ });
2352
+ }
2353
+ else if (imageSource instanceof HTMLImageElement) {
2354
+ // 如果已经是Image元素,直接使用
2355
+ imageElement = imageSource;
2356
+ }
2357
+ else if (imageSource instanceof HTMLCanvasElement) {
2358
+ // 如果是Canvas元素,创建Image并从Canvas获取数据
2359
+ imageElement = new Image();
2360
+ imageElement.src = imageSource.toDataURL();
2361
+ await new Promise((resolve) => {
2362
+ imageElement.onload = resolve;
2363
+ });
2364
+ }
2365
+ else {
2366
+ throw new Error("不支持的图片源类型");
2367
+ }
2368
+ // 获取图像数据
2369
+ const canvas = document.createElement("canvas");
2370
+ canvas.width = imageElement.naturalWidth;
2371
+ canvas.height = imageElement.naturalHeight;
2372
+ const ctx = canvas.getContext("2d");
2373
+ if (!ctx) {
2374
+ throw new Error("无法创建Canvas上下文");
2375
+ }
2376
+ ctx.drawImage(imageElement, 0, 0);
2377
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
2378
+ // 对图像进行预处理,提高识别率
2379
+ const enhancedImageData = ImageProcessor.batchProcess(imageData, {
2380
+ brightness: 10,
2381
+ contrast: 15,
2382
+ sharpen: true,
2383
+ });
2384
+ // 使用OCR模块处理图像
2385
+ return this.ocrModule.processIDCard(enhancedImageData);
2386
+ }
2387
+ catch (error) {
2388
+ this.handleError(error);
2389
+ throw error;
2390
+ }
2391
+ }
2392
+ /**
2393
+ * 批量处理图像
2394
+ * @param imageSource 图片源,可以是Image元素、Canvas元素、URL字符串或File对象
2395
+ * @param options 图像处理选项
2396
+ * @param outputFormat 输出格式,'imagedata'或'file'
2397
+ * @returns 返回Promise,解析为处理后的ImageData或File
2398
+ */
2399
+ async processImage(imageSource, options = {}, outputFormat = "imagedata") {
2400
+ try {
2401
+ // 处理不同类型的图片源
2402
+ let imageData;
2403
+ if (imageSource instanceof File) {
2404
+ // 如果是File对象,先进行压缩
2405
+ const compressedFile = await ImageProcessor.compressImage(imageSource, {
2406
+ maxSizeMB: 2, // 最大2MB
2407
+ maxWidthOrHeight: 1920, // 最大尺寸
2408
+ useWebWorker: true,
2409
+ });
2410
+ // 从File创建ImageData
2411
+ imageData = await ImageProcessor.createImageDataFromFile(compressedFile);
2412
+ }
2413
+ else if (typeof imageSource === "string") {
2414
+ // 如果是URL字符串,创建新的Image元素并加载图片
2415
+ const imageElement = new Image();
2416
+ imageElement.crossOrigin = "anonymous"; // 处理跨域图片
2417
+ await new Promise((resolve, reject) => {
2418
+ imageElement.onload = resolve;
2419
+ imageElement.onerror = reject;
2420
+ imageElement.src = imageSource;
2421
+ });
2422
+ // 获取图像数据
2423
+ const canvas = document.createElement("canvas");
2424
+ canvas.width = imageElement.naturalWidth;
2425
+ canvas.height = imageElement.naturalHeight;
2426
+ const ctx = canvas.getContext("2d");
2427
+ if (!ctx) {
2428
+ throw new Error("无法创建Canvas上下文");
2429
+ }
2430
+ ctx.drawImage(imageElement, 0, 0);
2431
+ imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
2432
+ }
2433
+ else if (imageSource instanceof HTMLImageElement) {
2434
+ // 如果是Image元素,从它创建ImageData
2435
+ const canvas = document.createElement("canvas");
2436
+ canvas.width = imageSource.naturalWidth;
2437
+ canvas.height = imageSource.naturalHeight;
2438
+ const ctx = canvas.getContext("2d");
2439
+ if (!ctx) {
2440
+ throw new Error("无法创建Canvas上下文");
2441
+ }
2442
+ ctx.drawImage(imageSource, 0, 0);
2443
+ imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
2444
+ }
2445
+ else if (imageSource instanceof HTMLCanvasElement) {
2446
+ // 如果是Canvas元素,直接获取其ImageData
2447
+ const ctx = imageSource.getContext("2d");
2448
+ if (!ctx) {
2449
+ throw new Error("无法获取Canvas上下文");
2450
+ }
2451
+ imageData = ctx.getImageData(0, 0, imageSource.width, imageSource.height);
2452
+ }
2453
+ else {
2454
+ throw new Error("不支持的图片源类型");
2455
+ }
2456
+ // 进行图像处理
2457
+ const processedImageData = ImageProcessor.batchProcess(imageData, options);
2458
+ // 根据需要的输出格式返回结果
2459
+ if (outputFormat === "file") {
2460
+ // 将ImageData转换为File
2461
+ const file = await ImageProcessor.imageDataToFile(processedImageData, "processed_image.jpg", "image/jpeg", 0.85);
2462
+ // 触发回调
2463
+ if (this.options.onImageProcessed) {
2464
+ this.options.onImageProcessed(file);
2465
+ }
2466
+ return file;
2467
+ }
2468
+ else {
2469
+ // 触发回调
2470
+ if (this.options.onImageProcessed) {
2471
+ this.options.onImageProcessed(processedImageData);
2472
+ }
2473
+ return processedImageData;
2474
+ }
2475
+ }
2476
+ catch (error) {
2477
+ this.handleError(error);
2478
+ throw error;
2479
+ }
2480
+ }
2481
+ /**
2482
+ * 压缩图片
2483
+ * @param file 要压缩的图片文件
2484
+ * @param options 压缩选项
2485
+ * @returns 返回Promise,解析为压缩后的文件
2486
+ */
2487
+ async compressImage(file, options) {
2488
+ try {
2489
+ return await ImageProcessor.compressImage(file, options);
2490
+ }
2491
+ catch (error) {
2492
+ this.handleError(error);
2493
+ throw error;
2494
+ }
2495
+ }
2496
+ };
2497
+
2498
+ class IDScannerDemo {
2499
+ constructor(videoElementId, resultContainerId, switchButtonId, imageInputId) {
2500
+ this.imageInput = null;
2501
+ this.currentMode = "qr";
2502
+ // 获取DOM元素
2503
+ this.videoElement = document.getElementById(videoElementId);
2504
+ this.resultContainer = document.getElementById(resultContainerId);
2505
+ this.switchButton = document.getElementById(switchButtonId);
2506
+ // 如果提供了图片输入元素ID,初始化图片输入功能
2507
+ if (imageInputId) {
2508
+ this.imageInput = document.getElementById(imageInputId);
2509
+ if (this.imageInput) {
2510
+ this.imageInput.addEventListener("change", this.handleImageInput.bind(this));
2511
+ }
2512
+ }
2513
+ try {
2514
+ // 创建IDScanner实例
2515
+ this.scanner = new IDScanner$1({
2516
+ onQRCodeScanned: this.handleQRCodeResult.bind(this),
2517
+ onBarcodeScanned: this.handleQRCodeResult.bind(this), // 复用QR码结果处理
2518
+ onIDCardScanned: this.handleIDCardResult.bind(this),
2519
+ onError: this.handleError.bind(this),
2520
+ });
2521
+ }
2522
+ catch (error) {
2523
+ console.error("创建IDScanner实例失败:", error);
2524
+ this.handleError(error instanceof Error ? error : new Error("初始化失败"));
2525
+ // 创建一个空对象以避免空引用错误
2526
+ this.scanner = {};
2527
+ }
2528
+ // 添加模式切换按钮事件监听
2529
+ this.switchButton.addEventListener("click", this.toggleScanMode.bind(this));
2530
+ }
2531
+ async initialize() {
2532
+ try {
2533
+ await this.scanner.initialize();
2534
+ await this.scanner.startQRScanner(this.videoElement);
2535
+ this.switchButton.textContent = "切换到身份证模式";
2536
+ this.currentMode = "qr";
2537
+ }
2538
+ catch (error) {
2539
+ this.handleError(error instanceof Error ? error : new Error("初始化失败"));
2540
+ }
2541
+ }
2542
+ async toggleScanMode() {
2543
+ try {
2544
+ this.scanner.stop();
2545
+ if (this.currentMode === "qr") {
2546
+ this.currentMode = "barcode";
2547
+ await this.scanner.startBarcodeScanner(this.videoElement);
2548
+ this.switchButton.textContent = "切换到身份证模式";
2549
+ }
2550
+ else if (this.currentMode === "barcode") {
2551
+ this.currentMode = "idcard";
2552
+ await this.scanner.startIDCardScanner(this.videoElement);
2553
+ this.switchButton.textContent = "切换到二维码模式";
2554
+ }
2555
+ else {
2556
+ this.currentMode = "qr";
2557
+ await this.scanner.startQRScanner(this.videoElement);
2558
+ this.switchButton.textContent = "切换到条形码模式";
2559
+ }
2560
+ this.resultContainer.innerHTML = "";
2561
+ }
2562
+ catch (error) {
2563
+ this.handleError(error instanceof Error ? error : new Error("切换模式失败"));
2564
+ }
2565
+ }
2566
+ /**
2567
+ * 处理图片输入
2568
+ * 支持从文件选择器获取图片并进行识别
2569
+ */
2570
+ async handleImageInput(event) {
2571
+ const input = event.target;
2572
+ if (!input.files || input.files.length === 0)
2573
+ return;
2574
+ const file = input.files[0];
2575
+ try {
2576
+ // 检查文件类型
2577
+ if (!file.type.startsWith("image/")) {
2578
+ throw new Error("请选择图片文件");
2579
+ }
2580
+ // 创建一个本地URL以显示图片
2581
+ const imageUrl = URL.createObjectURL(file);
2582
+ // 显示处理中的提示
2583
+ this.resultContainer.innerHTML = `
2584
+ <h3>正在处理图片...</h3>
2585
+ <img src="${imageUrl}" style="max-width: 100%; max-height: 300px; margin-bottom: 10px;">
2586
+ `;
2587
+ // 根据当前模式处理图片
2588
+ try {
2589
+ if (this.currentMode === "qr") {
2590
+ const result = await this.scanner.processQRCodeImage(imageUrl);
2591
+ if (result) {
2592
+ this.handleQRCodeResult(result);
2593
+ }
2594
+ }
2595
+ else if (this.currentMode === "barcode") {
2596
+ const result = await this.scanner.processBarcodeImage(imageUrl);
2597
+ if (result) {
2598
+ this.handleQRCodeResult(result);
2599
+ }
2600
+ }
2601
+ else if (this.currentMode === "idcard") {
2602
+ const result = await this.scanner.processIDCardImage(imageUrl);
2603
+ if (result) {
2604
+ this.handleIDCardResult(result);
2605
+ }
2606
+ }
2607
+ }
2608
+ catch (error) {
2609
+ // 如果处理失败,显示错误
2610
+ this.resultContainer.innerHTML = `
2611
+ <h3>识别结果:</h3>
2612
+ <img src="${imageUrl}" style="max-width: 100%; max-height: 300px; margin-bottom: 10px;">
2613
+ <p class="error">未能识别内容,请尝试其他图片或调整图片质量</p>
2614
+ <p class="error">${error instanceof Error ? error.message : "未知错误"}</p>
2615
+ `;
2616
+ }
2617
+ // 清除文件选择,允许再次选择相同的文件
2618
+ input.value = "";
2619
+ }
2620
+ catch (error) {
2621
+ this.handleError(error instanceof Error ? error : new Error(String(error)));
2622
+ // 清除文件选择
2623
+ input.value = "";
2624
+ }
2625
+ }
2626
+ handleQRCodeResult(result) {
2627
+ this.resultContainer.innerHTML = `
2628
+ <h3>扫描结果:</h3>
2629
+ <p>${result}</p>
2630
+ `;
2631
+ }
2632
+ handleIDCardResult(info) {
2633
+ this.resultContainer.innerHTML = `
2634
+ <h3>身份证信息:</h3>
2635
+ <p>姓名: ${info.name || "未识别"}</p>
2636
+ <p>性别: ${info.gender || "未识别"}</p>
2637
+ <p>民族: ${info.nationality || "未识别"}</p>
2638
+ <p>出生日期: ${info.birthDate || "未识别"}</p>
2639
+ <p>地址: ${info.address || "未识别"}</p>
2640
+ <p>身份证号: ${info.idNumber || "未识别"}</p>
2641
+ <p>签发机关: ${info.issuingAuthority || "未识别"}</p>
2642
+ <p>有效期限: ${info.validPeriod || "未识别"}</p>
2643
+ `;
2644
+ }
2645
+ handleError(error) {
2646
+ console.error("扫描错误:", error);
2647
+ this.resultContainer.innerHTML = `
2648
+ <div class="error">
2649
+ <h3>错误:</h3>
2650
+ <p>${error.message}</p>
2651
+ </div>
2652
+ `;
2653
+ }
2654
+ stop() {
2655
+ if (this.scanner && typeof this.scanner.stop === "function") {
2656
+ this.scanner.stop();
2657
+ }
2658
+ if (this.scanner && typeof this.scanner.terminate === "function") {
2659
+ this.scanner.terminate();
2660
+ }
2661
+ }
2662
+ }
2663
+
2664
+ /**
2665
+ * @file ID扫描识别库UMD格式入口文件
2666
+ * @description 专门为UMD格式构建的入口,使用静态导入而非动态导入
2667
+ * @module IDScannerLib
2668
+ * @version 1.1.0
2669
+ * @license MIT
2670
+ */
2671
+ /**
2672
+ * IDScanner 主类
2673
+ * UMD版本使用静态导入实现
2674
+ */
2675
+ class IDScanner {
2676
+ /**
2677
+ * 构造函数
2678
+ * @param options 配置选项
2679
+ */
2680
+ constructor(options = {}) {
2681
+ this.options = options;
2682
+ this.qrScanner = null;
2683
+ this.barcodeScanner = null;
2684
+ this.idDetector = null;
2685
+ this.ocrProcessor = null;
2686
+ this.dataExtractor = null;
2687
+ this.scanMode = "qr";
2688
+ this.videoElement = null;
2689
+ this.camera = new Camera(options.cameraOptions);
2690
+ }
2691
+ /**
2692
+ * 初始化模块
2693
+ * 根据需要初始化OCR引擎
2694
+ */
2695
+ async initialize() {
2696
+ try {
2697
+ // 初始化OCR模块
2698
+ this.ocrProcessor = new OCRProcessor();
2699
+ this.dataExtractor = new DataExtractor();
2700
+ await this.ocrProcessor.initialize();
2701
+ console.log("IDScanner initialized");
2702
+ }
2703
+ catch (error) {
2704
+ this.handleError(error);
2705
+ throw error;
2706
+ }
2707
+ }
2708
+ /**
2709
+ * 启动二维码扫描
2710
+ * @param videoElement HTML视频元素
2711
+ */
2712
+ async startQRScanner(videoElement) {
2713
+ this.stop();
2714
+ this.videoElement = videoElement;
2715
+ this.scanMode = "qr";
2716
+ try {
2717
+ if (!this.qrScanner) {
2718
+ this.qrScanner = new QRScanner({
2719
+ ...this.options.qrScannerOptions,
2720
+ onScan: this.handleQRScan.bind(this),
2721
+ });
2722
+ }
2723
+ await this.camera.start(videoElement);
2724
+ this.qrScanner.start(videoElement);
2725
+ }
2726
+ catch (error) {
2727
+ this.handleError(error);
2728
+ }
2729
+ }
2730
+ /**
2731
+ * 启动条形码扫描
2732
+ * @param videoElement HTML视频元素
2733
+ */
2734
+ async startBarcodeScanner(videoElement) {
2735
+ this.stop();
2736
+ this.videoElement = videoElement;
2737
+ this.scanMode = "barcode";
2738
+ try {
2739
+ if (!this.barcodeScanner) {
2740
+ this.barcodeScanner = new BarcodeScanner({
2741
+ ...this.options.barcodeScannerOptions,
2742
+ onScan: this.handleBarcodeScan.bind(this),
2743
+ });
2744
+ }
2745
+ await this.camera.start(videoElement);
2746
+ this.barcodeScanner.start(videoElement);
2747
+ }
2748
+ catch (error) {
2749
+ this.handleError(error);
2750
+ }
2751
+ }
2752
+ /**
2753
+ * 启动身份证扫描
2754
+ * @param videoElement HTML视频元素
2755
+ */
2756
+ async startIDCardScanner(videoElement) {
2757
+ this.stop();
2758
+ this.videoElement = videoElement;
2759
+ this.scanMode = "idcard";
2760
+ try {
2761
+ if (!this.ocrProcessor) {
2762
+ await this.initialize();
2763
+ }
2764
+ if (!this.idDetector) {
2765
+ this.idDetector = new IDCardDetector({
2766
+ onDetection: this.handleIDDetection.bind(this),
2767
+ onError: this.handleError.bind(this),
2768
+ });
2769
+ }
2770
+ await this.camera.start(videoElement);
2771
+ this.idDetector.start(videoElement);
2772
+ }
2773
+ catch (error) {
2774
+ this.handleError(error);
2775
+ }
2776
+ }
2777
+ /**
2778
+ * 停止扫描
2779
+ */
2780
+ stop() {
2781
+ if (this.scanMode === "qr" && this.qrScanner) {
2782
+ this.qrScanner.stop();
2783
+ }
2784
+ else if (this.scanMode === "barcode" && this.barcodeScanner) {
2785
+ this.barcodeScanner.stop();
2786
+ }
2787
+ else if (this.scanMode === "idcard" && this.idDetector) {
2788
+ this.idDetector.stop();
2789
+ }
2790
+ this.camera.stop();
2791
+ }
2792
+ /**
2793
+ * 处理二维码扫描结果
2794
+ */
2795
+ handleQRScan(result) {
2796
+ if (this.options.onQRCodeScanned) {
2797
+ this.options.onQRCodeScanned(result);
2798
+ }
2799
+ }
2800
+ /**
2801
+ * 处理条形码扫描结果
2802
+ */
2803
+ handleBarcodeScan(result) {
2804
+ if (this.options.onBarcodeScanned) {
2805
+ this.options.onBarcodeScanned(result);
2806
+ }
2807
+ }
2808
+ /**
2809
+ * 处理身份证检测结果
2810
+ */
2811
+ async handleIDDetection(result) {
2812
+ if (!this.ocrProcessor || !this.dataExtractor)
2813
+ return;
2814
+ try {
2815
+ // 检查 imageData 是否存在
2816
+ if (!result.imageData) {
2817
+ this.handleError(new Error("无效的图像数据"));
2818
+ return;
2819
+ }
2820
+ const idCardInfo = await this.ocrProcessor.processIDCard(result.imageData);
2821
+ const extractedInfo = this.dataExtractor.extractAndValidate(idCardInfo);
2822
+ if (this.options.onIDCardScanned) {
2823
+ this.options.onIDCardScanned(extractedInfo);
2824
+ }
2825
+ }
2826
+ catch (error) {
2827
+ this.handleError(error);
2828
+ }
2829
+ }
2830
+ /**
2831
+ * 处理错误
2832
+ */
2833
+ handleError(error) {
2834
+ if (this.options.onError) {
2835
+ this.options.onError(error);
2836
+ }
2837
+ else {
2838
+ console.error("IDScanner error:", error);
2839
+ }
2840
+ }
2841
+ /**
2842
+ * 释放资源
2843
+ */
2844
+ async terminate() {
2845
+ this.stop();
2846
+ if (this.ocrProcessor) {
2847
+ await this.ocrProcessor.terminate();
2848
+ this.ocrProcessor = null;
2849
+ }
2850
+ this.qrScanner = null;
2851
+ this.barcodeScanner = null;
2852
+ this.idDetector = null;
2853
+ this.dataExtractor = null;
2854
+ }
2855
+ /**
2856
+ * 处理图片中的二维码
2857
+ * @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
2858
+ * @returns 返回Promise,解析为扫描结果
2859
+ */
2860
+ async processQRCodeImage(imageSource) {
2861
+ try {
2862
+ if (!this.qrScanner) {
2863
+ this.qrScanner = new QRScanner({
2864
+ ...this.options.qrScannerOptions,
2865
+ onScan: this.handleQRScan.bind(this),
2866
+ });
2867
+ }
2868
+ // 处理不同类型的图片源
2869
+ let imageElement;
2870
+ if (typeof imageSource === "string") {
2871
+ // 如果是URL字符串,创建新的Image元素并加载图片
2872
+ imageElement = new Image();
2873
+ imageElement.crossOrigin = "anonymous"; // 处理跨域图片
2874
+ await new Promise((resolve, reject) => {
2875
+ imageElement.onload = resolve;
2876
+ imageElement.onerror = reject;
2877
+ imageElement.src = imageSource;
2878
+ });
2879
+ }
2880
+ else if (imageSource instanceof HTMLImageElement) {
2881
+ // 如果已经是Image元素,直接使用
2882
+ imageElement = imageSource;
2883
+ }
2884
+ else if (imageSource instanceof HTMLCanvasElement) {
2885
+ // 如果是Canvas元素,创建新的Image元素并从Canvas获取数据
2886
+ const dataURL = imageSource.toDataURL();
2887
+ imageElement = new Image();
2888
+ await new Promise((resolve, reject) => {
2889
+ imageElement.onload = resolve;
2890
+ imageElement.onerror = reject;
2891
+ imageElement.src = dataURL;
2892
+ });
2893
+ }
2894
+ else {
2895
+ throw new Error("不支持的图片源类型");
2896
+ }
2897
+ // 创建Canvas处理图片
2898
+ const canvas = document.createElement("canvas");
2899
+ const ctx = canvas.getContext("2d");
2900
+ if (!ctx) {
2901
+ throw new Error("无法创建Canvas上下文");
2902
+ }
2903
+ // 设置Canvas尺寸与图片相同
2904
+ canvas.width = imageElement.naturalWidth;
2905
+ canvas.height = imageElement.naturalHeight;
2906
+ ctx.drawImage(imageElement, 0, 0);
2907
+ // 获取图像数据并处理
2908
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
2909
+ return new Promise((resolve, reject) => {
2910
+ try {
2911
+ const result = this.qrScanner?.processImageData(imageData);
2912
+ if (result) {
2913
+ resolve(result);
2914
+ }
2915
+ else {
2916
+ reject(new Error("未检测到二维码"));
2917
+ }
2918
+ }
2919
+ catch (error) {
2920
+ reject(error);
2921
+ }
2922
+ });
2923
+ }
2924
+ catch (error) {
2925
+ this.handleError(error);
2926
+ throw error;
2927
+ }
2928
+ }
2929
+ /**
2930
+ * 处理图片中的条形码
2931
+ * @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
2932
+ * @returns 返回Promise,解析为扫描结果
2933
+ */
2934
+ async processBarcodeImage(imageSource) {
2935
+ try {
2936
+ if (!this.barcodeScanner) {
2937
+ this.barcodeScanner = new BarcodeScanner({
2938
+ ...this.options.barcodeScannerOptions,
2939
+ onScan: this.handleBarcodeScan.bind(this),
2940
+ });
2941
+ }
2942
+ // 处理不同类型的图片源
2943
+ let imageElement;
2944
+ if (typeof imageSource === "string") {
2945
+ // 如果是URL字符串,创建新的Image元素并加载图片
2946
+ imageElement = new Image();
2947
+ imageElement.crossOrigin = "anonymous"; // 处理跨域图片
2948
+ await new Promise((resolve, reject) => {
2949
+ imageElement.onload = resolve;
2950
+ imageElement.onerror = reject;
2951
+ imageElement.src = imageSource;
2952
+ });
2953
+ }
2954
+ else if (imageSource instanceof HTMLImageElement) {
2955
+ // 如果已经是Image元素,直接使用
2956
+ imageElement = imageSource;
2957
+ }
2958
+ else if (imageSource instanceof HTMLCanvasElement) {
2959
+ // 如果是Canvas元素,创建新的Image元素并从Canvas获取数据
2960
+ const dataURL = imageSource.toDataURL();
2961
+ imageElement = new Image();
2962
+ await new Promise((resolve, reject) => {
2963
+ imageElement.onload = resolve;
2964
+ imageElement.onerror = reject;
2965
+ imageElement.src = dataURL;
2966
+ });
2967
+ }
2968
+ else {
2969
+ throw new Error("不支持的图片源类型");
2970
+ }
2971
+ // 创建Canvas处理图片
2972
+ const canvas = document.createElement("canvas");
2973
+ const ctx = canvas.getContext("2d");
2974
+ if (!ctx) {
2975
+ throw new Error("无法创建Canvas上下文");
2976
+ }
2977
+ // 设置Canvas尺寸与图片相同
2978
+ canvas.width = imageElement.naturalWidth;
2979
+ canvas.height = imageElement.naturalHeight;
2980
+ ctx.drawImage(imageElement, 0, 0);
2981
+ // 获取图像数据并处理
2982
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
2983
+ return new Promise((resolve, reject) => {
2984
+ try {
2985
+ const result = this.barcodeScanner?.processImageData(imageData);
2986
+ if (result) {
2987
+ resolve(result);
2988
+ }
2989
+ else {
2990
+ reject(new Error("未检测到条形码"));
2991
+ }
2992
+ }
2993
+ catch (error) {
2994
+ reject(error);
2995
+ }
2996
+ });
2997
+ }
2998
+ catch (error) {
2999
+ this.handleError(error);
3000
+ throw error;
3001
+ }
3002
+ }
3003
+ /**
3004
+ * 处理图片中的身份证
3005
+ * @param imageSource 图片源,可以是Image元素、Canvas元素或URL字符串
3006
+ * @returns 返回Promise,解析为身份证信息
3007
+ */
3008
+ async processIDCardImage(imageSource) {
3009
+ try {
3010
+ if (!this.ocrProcessor || !this.dataExtractor) {
3011
+ await this.initialize();
3012
+ }
3013
+ // 处理不同类型的图片源
3014
+ let imageElement;
3015
+ if (typeof imageSource === "string") {
3016
+ // 如果是URL字符串,创建新的Image元素并加载图片
3017
+ imageElement = new Image();
3018
+ imageElement.crossOrigin = "anonymous"; // 处理跨域图片
3019
+ await new Promise((resolve, reject) => {
3020
+ imageElement.onload = resolve;
3021
+ imageElement.onerror = reject;
3022
+ imageElement.src = imageSource;
3023
+ });
3024
+ }
3025
+ else if (imageSource instanceof HTMLImageElement) {
3026
+ // 如果已经是Image元素,直接使用
3027
+ imageElement = imageSource;
3028
+ }
3029
+ else if (imageSource instanceof HTMLCanvasElement) {
3030
+ // 如果是Canvas元素,创建新的Image元素并从Canvas获取数据
3031
+ const dataURL = imageSource.toDataURL();
3032
+ imageElement = new Image();
3033
+ await new Promise((resolve, reject) => {
3034
+ imageElement.onload = resolve;
3035
+ imageElement.onerror = reject;
3036
+ imageElement.src = dataURL;
3037
+ });
3038
+ }
3039
+ else {
3040
+ throw new Error("不支持的图片源类型");
3041
+ }
3042
+ // 创建Canvas处理图片
3043
+ const canvas = document.createElement("canvas");
3044
+ const ctx = canvas.getContext("2d");
3045
+ if (!ctx) {
3046
+ throw new Error("无法创建Canvas上下文");
3047
+ }
3048
+ // 设置Canvas尺寸与图片相同
3049
+ canvas.width = imageElement.naturalWidth;
3050
+ canvas.height = imageElement.naturalHeight;
3051
+ ctx.drawImage(imageElement, 0, 0);
3052
+ // 获取图像数据并处理
3053
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
3054
+ // 调用OCR处理器处理身份证图像
3055
+ const ocrResult = await this.ocrProcessor.processIDCard(imageData);
3056
+ const extractedInfo = this.dataExtractor.extractAndValidate(ocrResult);
3057
+ if (this.options.onIDCardScanned) {
3058
+ this.options.onIDCardScanned(extractedInfo);
3059
+ }
3060
+ return extractedInfo;
3061
+ }
3062
+ catch (error) {
3063
+ this.handleError(error);
3064
+ throw error;
3065
+ }
3066
+ }
3067
+ }
3068
+ // 添加静态属性IDScannerDemo,使其能被通过IDScanner.IDScannerDemo访问
3069
+ IDScanner.IDScannerDemo = IDScannerDemo;
3070
+
3071
+ /**
3072
+ * @file OCR模块入口文件
3073
+ * @description 包含身份证OCR识别相关功能
3074
+ * @module IDScannerOCR
3075
+ * @version 1.0.0
3076
+ * @license MIT
3077
+ */
3078
+ /**
3079
+ * OCR模块类
3080
+ *
3081
+ * 提供身份证检测和OCR文字识别功能
3082
+ */
3083
+ class OCRModule {
3084
+ /**
3085
+ * 构造函数
3086
+ * @param options 配置选项
3087
+ */
3088
+ constructor(options = {}) {
3089
+ this.options = options;
3090
+ this.isRunning = false;
3091
+ this.videoElement = null;
3092
+ this.camera = new Camera(options.cameraOptions);
3093
+ this.idDetector = new IDCardDetector({
3094
+ onDetection: this.handleIDDetection.bind(this),
3095
+ onError: this.handleError.bind(this),
3096
+ });
3097
+ this.ocrProcessor = new OCRProcessor();
3098
+ this.dataExtractor = new DataExtractor();
3099
+ }
3100
+ /**
3101
+ * 初始化OCR引擎
3102
+ *
3103
+ * @returns Promise<void>
3104
+ */
3105
+ async initialize() {
3106
+ try {
3107
+ await this.ocrProcessor.initialize();
3108
+ console.log("OCR engine initialized");
3109
+ }
3110
+ catch (error) {
3111
+ this.handleError(error);
3112
+ throw error;
3113
+ }
3114
+ }
3115
+ /**
3116
+ * 启动身份证扫描
3117
+ * @param videoElement HTML视频元素
3118
+ */
3119
+ async startIDCardScanner(videoElement) {
3120
+ if (!this.ocrProcessor) {
3121
+ throw new Error("OCR engine not initialized. Call initialize() first.");
3122
+ }
3123
+ this.videoElement = videoElement;
3124
+ this.isRunning = true;
3125
+ await this.camera.start(videoElement);
3126
+ this.idDetector.start(videoElement);
3127
+ }
3128
+ /**
3129
+ * 停止扫描
3130
+ */
3131
+ stop() {
3132
+ this.isRunning = false;
3133
+ this.idDetector.stop();
3134
+ this.camera.stop();
3135
+ }
3136
+ /**
3137
+ * 处理身份证检测结果
3138
+ */
3139
+ async handleIDDetection(result) {
3140
+ if (!this.isRunning)
3141
+ return;
3142
+ try {
3143
+ // 检查 imageData 是否存在
3144
+ if (!result.imageData) {
3145
+ this.handleError(new Error("无效的图像数据"));
3146
+ return;
3147
+ }
3148
+ const idCardInfo = await this.ocrProcessor.processIDCard(result.imageData);
3149
+ const extractedInfo = this.dataExtractor.extractAndValidate(idCardInfo);
3150
+ if (this.options.onIDCardScanned) {
3151
+ this.options.onIDCardScanned(extractedInfo);
3152
+ }
3153
+ }
3154
+ catch (error) {
3155
+ this.handleError(error);
3156
+ }
3157
+ }
3158
+ /**
3159
+ * 处理错误
3160
+ */
3161
+ handleError(error) {
3162
+ if (this.options.onError) {
3163
+ this.options.onError(error);
3164
+ }
3165
+ else {
3166
+ console.error("OCRModule error:", error);
3167
+ }
3168
+ }
3169
+ /**
3170
+ * 释放资源
3171
+ */
3172
+ async terminate() {
3173
+ this.stop();
3174
+ await this.ocrProcessor.terminate();
3175
+ }
3176
+ /**
3177
+ * 直接处理图像数据中的身份证
3178
+ * @param imageData 要处理的图像数据
3179
+ * @returns 返回Promise,解析为身份证信息
3180
+ */
3181
+ async processIDCard(imageData) {
3182
+ try {
3183
+ if (!this.ocrProcessor) {
3184
+ throw new Error("OCR engine not initialized. Call initialize() first.");
3185
+ }
3186
+ // 检查图像数据有效性
3187
+ if (!imageData ||
3188
+ !imageData.data ||
3189
+ imageData.width <= 0 ||
3190
+ imageData.height <= 0) {
3191
+ throw new Error("无效的图像数据");
3192
+ }
3193
+ // 进行图像预处理,提高识别率
3194
+ const processedImage = ImageProcessor.adjustBrightnessContrast(imageData, 5, // 轻微提高亮度
3195
+ 10 // 适度提高对比度
3196
+ );
3197
+ // 调用OCR处理器进行文字识别
3198
+ const idCardInfo = await this.ocrProcessor.processIDCard(processedImage);
3199
+ // 提取和验证身份证信息
3200
+ const extractedInfo = this.dataExtractor.extractAndValidate(idCardInfo);
3201
+ // 如果有回调,触发回调
3202
+ if (this.options.onIDCardScanned) {
3203
+ this.options.onIDCardScanned(extractedInfo);
3204
+ }
3205
+ return extractedInfo;
3206
+ }
3207
+ catch (error) {
3208
+ this.handleError(error);
3209
+ throw error;
3210
+ }
3211
+ }
3212
+ }
3213
+
3214
+ var ocrModule = /*#__PURE__*/Object.freeze({
3215
+ __proto__: null,
3216
+ DataExtractor: DataExtractor,
3217
+ IDCardDetector: IDCardDetector,
3218
+ OCRModule: OCRModule,
3219
+ OCRProcessor: OCRProcessor
3220
+ });
3221
+
3222
+ /**
3223
+ * @file 二维码和条形码扫描模块
3224
+ * @description 包含二维码和条形码扫描功能
3225
+ * @module IDScannerQR
3226
+ * @version 1.0.0
3227
+ * @license MIT
3228
+ */
3229
+ /**
3230
+ * 扫描模块类
3231
+ *
3232
+ * 提供独立的二维码和条形码扫描功能
3233
+ */
3234
+ class ScannerModule {
3235
+ /**
3236
+ * 构造函数
3237
+ * @param options 配置选项
3238
+ */
3239
+ constructor(options = {}) {
3240
+ this.options = options;
3241
+ this.scanMode = null;
3242
+ this.videoElement = null;
3243
+ this.camera = new Camera(options.cameraOptions);
3244
+ this.qrScanner = new QRScanner({
3245
+ ...options.qrScannerOptions,
3246
+ onScan: this.handleQRScan.bind(this),
3247
+ });
3248
+ this.barcodeScanner = new BarcodeScanner({
3249
+ ...options.barcodeScannerOptions,
3250
+ onScan: this.handleBarcodeScan.bind(this),
3251
+ });
3252
+ }
3253
+ /**
3254
+ * 启动二维码扫描
3255
+ * @param videoElement HTML视频元素
3256
+ */
3257
+ async startQRScanner(videoElement) {
3258
+ this.stop(); // 确保先停止可能正在运行的扫描
3259
+ this.videoElement = videoElement;
3260
+ this.scanMode = "qr";
3261
+ await this.camera.start(videoElement);
3262
+ this.qrScanner.start(videoElement);
3263
+ }
3264
+ /**
3265
+ * 启动条形码扫描
3266
+ * @param videoElement HTML视频元素
3267
+ */
3268
+ async startBarcodeScanner(videoElement) {
3269
+ this.stop(); // 确保先停止可能正在运行的扫描
3270
+ this.videoElement = videoElement;
3271
+ this.scanMode = "barcode";
3272
+ await this.camera.start(videoElement);
3273
+ this.barcodeScanner.start(videoElement);
3274
+ }
3275
+ /**
3276
+ * 停止扫描
3277
+ */
3278
+ stop() {
3279
+ if (this.scanMode === "qr") {
3280
+ this.qrScanner.stop();
3281
+ }
3282
+ else if (this.scanMode === "barcode") {
3283
+ this.barcodeScanner.stop();
3284
+ }
3285
+ if (this.videoElement) {
3286
+ this.camera.stop();
3287
+ }
3288
+ this.scanMode = null;
3289
+ }
3290
+ /**
3291
+ * 处理二维码扫描结果
3292
+ */
3293
+ handleQRScan(result) {
3294
+ if (this.options.onQRCodeScanned) {
3295
+ this.options.onQRCodeScanned(result);
3296
+ }
3297
+ }
3298
+ /**
3299
+ * 处理条形码扫描结果
3300
+ */
3301
+ handleBarcodeScan(result) {
3302
+ if (this.options.onBarcodeScanned) {
3303
+ this.options.onBarcodeScanned(result);
3304
+ }
3305
+ }
3306
+ /**
3307
+ * 处理错误
3308
+ */
3309
+ handleError(error) {
3310
+ if (this.options.onError) {
3311
+ this.options.onError(error);
3312
+ }
3313
+ else {
3314
+ console.error("ScannerModule error:", error);
3315
+ }
3316
+ }
3317
+ /**
3318
+ * 处理图像数据中的二维码
3319
+ * @param imageData 要处理的图像数据
3320
+ * @returns 返回Promise,解析为扫描结果
3321
+ */
3322
+ async processQRCodeImage(imageData) {
3323
+ try {
3324
+ const result = this.qrScanner.processImageData(imageData);
3325
+ if (result) {
3326
+ // 如果需要,触发回调
3327
+ if (this.options.onQRCodeScanned) {
3328
+ this.options.onQRCodeScanned(result);
3329
+ }
3330
+ return result;
3331
+ }
3332
+ throw new Error("未检测到二维码");
3333
+ }
3334
+ catch (error) {
3335
+ this.handleError(error);
3336
+ throw error;
3337
+ }
3338
+ }
3339
+ /**
3340
+ * 处理图像数据中的条形码
3341
+ * @param imageData 要处理的图像数据
3342
+ * @returns 返回Promise,解析为扫描结果
3343
+ */
3344
+ async processBarcodeImage(imageData) {
3345
+ try {
3346
+ const result = this.barcodeScanner.processImageData(imageData);
3347
+ if (result) {
3348
+ // 如果需要,触发回调
3349
+ if (this.options.onBarcodeScanned) {
3350
+ this.options.onBarcodeScanned(result);
3351
+ }
3352
+ return result;
3353
+ }
3354
+ throw new Error("未检测到条形码");
3355
+ }
3356
+ catch (error) {
3357
+ this.handleError(error);
3358
+ throw error;
3359
+ }
3360
+ }
1947
3361
  }
1948
3362
 
3363
+ var qrModule = /*#__PURE__*/Object.freeze({
3364
+ __proto__: null,
3365
+ BarcodeScanner: BarcodeScanner,
3366
+ Camera: Camera,
3367
+ QRScanner: QRScanner,
3368
+ ScannerModule: ScannerModule
3369
+ });
3370
+
1949
3371
  exports.BarcodeScanner = BarcodeScanner;
1950
3372
  exports.DataExtractor = DataExtractor;
1951
3373
  exports.IDCardDetector = IDCardDetector;
1952
3374
  exports.IDScanner = IDScanner;
3375
+ exports.IDScannerDemo = IDScannerDemo;
1953
3376
  exports.ImageProcessor = ImageProcessor;
1954
3377
  exports.OCRProcessor = OCRProcessor;
1955
3378
  exports.QRScanner = QRScanner;
1956
3379
 
1957
3380
  }));
1958
- //# sourceMappingURL=id-scanner.js.map