Comparing version 1.0.0 to 1.1.0
{ | ||
"name": "qr-scanner", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "A javascript QR scanner library", | ||
@@ -19,4 +19,3 @@ "main": "qr-scanner.min.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"build": "gulp" | ||
"build": "rollup --config" | ||
}, | ||
@@ -33,2 +32,5 @@ "repository": { | ||
"javascript", | ||
"lightweight", | ||
"small", | ||
"fast", | ||
"web" | ||
@@ -43,7 +45,8 @@ ], | ||
"devDependencies": { | ||
"@ampproject/rollup-plugin-closure-compiler": "^0.8.5", | ||
"google-closure-compiler": "^20171112.0.0", | ||
"gulp": "^4.0.0", | ||
"gulp-concat": "^2.6.1", | ||
"gulp-sourcemaps": "^2.6.1" | ||
"jsqr-es6": "^1.2.0-1", | ||
"rollup": "^1.1.2", | ||
"rollup-plugin-sourcemaps": "^0.4.2" | ||
} | ||
} |
@@ -1,17 +0,12 @@ | ||
'use strict';export default class QrScanner{static hasCamera(){return navigator.mediaDevices.enumerateDevices().then((devices)=>devices.some((device)=>device.kind==="videoinput")).catch(()=>false)}constructor(video,onDecode,canvasSize=QrScanner.DEFAULT_CANVAS_SIZE){this.$video=video;this.$canvas=document.createElement("canvas");this._onDecode=onDecode;this._active=false;this._paused=false;this.$canvas.width=canvasSize;this.$canvas.height=canvasSize;this._sourceRect={x:0,y:0,width:canvasSize,height:canvasSize}; | ||
this._onCanPlay=this._onCanPlay.bind(this);this._onPlay=this._onPlay.bind(this);this._onVisibilityChange=this._onVisibilityChange.bind(this);this.$video.addEventListener("canplay",this._onCanPlay);this.$video.addEventListener("play",this._onPlay);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrWorker=new Worker(QrScanner.WORKER_PATH)}destroy(){this.$video.removeEventListener("canplay",this._onCanPlay);this.$video.removeEventListener("play",this._onPlay);document.removeEventListener("visibilitychange", | ||
this._onVisibilityChange);this.stop();this._qrWorker.postMessage({type:"close"})}start(){if(this._active&&!this._paused)return Promise.resolve();if(window.location.protocol!=="https:")console.warn("The camera stream is only accessible if the page is transferred via https.");this._active=true;this._paused=false;if(document.hidden)return Promise.resolve();clearTimeout(this._offTimeout);this._offTimeout=null;if(this.$video.srcObject){this.$video.play();return Promise.resolve()}let facingMode="environment"; | ||
return this._getCameraStream("environment",true).catch(()=>{facingMode="user";return this._getCameraStream()}).then((stream)=>{this.$video.srcObject=stream;this._setVideoMirror(facingMode)}).catch((e)=>{this._active=false;throw e;})}stop(){this.pause();this._active=false}pause(){this._paused=true;if(!this._active)return;this.$video.pause();if(this._offTimeout)return;this._offTimeout=setTimeout(()=>{const track=this.$video.srcObject&&this.$video.srcObject.getTracks()[0];if(!track)return;track.stop(); | ||
this.$video.srcObject=null;this._offTimeout=null},300)}static scanImage(imageOrFileOrUrl,sourceRect=null,worker=null,canvas=null,fixedCanvasSize=false,alsoTryWithoutSourceRect=false){const promise=new Promise((resolve,reject)=>{if(!worker){worker=new Worker(QrScanner.WORKER_PATH);worker.postMessage({type:"inversionMode",data:"both"})}let timeout,onMessage,onError;onMessage=(event)=>{if(event.data.type!=="qrResult")return;worker.removeEventListener("message",onMessage);worker.removeEventListener("error", | ||
onError);clearTimeout(timeout);if(event.data.data!==null)resolve(event.data.data);else reject("QR code not found.")};onError=(e)=>{worker.removeEventListener("message",onMessage);worker.removeEventListener("error",onError);clearTimeout(timeout);var errorMessage=!e?"Unknown Error":e.message||e;reject("Scanner error: "+errorMessage)};worker.addEventListener("message",onMessage);worker.addEventListener("error",onError);timeout=setTimeout(()=>onError("timeout"),3E3);QrScanner._loadImage(imageOrFileOrUrl).then((image)=> | ||
{const imageData=QrScanner._getImageData(image,sourceRect,canvas,fixedCanvasSize);worker.postMessage({type:"decode",data:imageData},[imageData.data.buffer])}).catch(reject)});if(sourceRect&&alsoTryWithoutSourceRect)return promise.catch(()=>QrScanner.scanImage(imageOrFileOrUrl,null,worker,canvas,fixedCanvasSize));else return promise}setGrayscaleWeights(red,green,blue){this._qrWorker.postMessage({type:"grayscaleWeights",data:{red,green,blue}})}setInversionMode(inversionMode){this._qrWorker.postMessage({type:"inversionMode", | ||
data:inversionMode})}_onCanPlay(){this._updateSourceRect();this.$video.play()}_onPlay(){this._updateSourceRect();this._scanFrame()}_onVisibilityChange(){if(document.hidden)this.pause();else if(this._active)this.start()}_updateSourceRect(){const smallestDimension=Math.min(this.$video.videoWidth,this.$video.videoHeight);const sourceRectSize=Math.round(2/3*smallestDimension);this._sourceRect.width=this._sourceRect.height=sourceRectSize;this._sourceRect.x=(this.$video.videoWidth-sourceRectSize)/2;this._sourceRect.y= | ||
(this.$video.videoHeight-sourceRectSize)/2}_scanFrame(){if(!this._active||this.$video.paused||this.$video.ended)return false;requestAnimationFrame(()=>{QrScanner.scanImage(this.$video,this._sourceRect,this._qrWorker,this.$canvas,true).then(this._onDecode,(error)=>{if(this._active&&error!=="QR code not found.")console.error(error)}).then(()=>this._scanFrame())})}_getCameraStream(facingMode,exact=false){const constraintsToTry=[{width:{min:1024}},{width:{min:768}},{}];if(facingMode){if(exact)facingMode= | ||
{exact:facingMode};constraintsToTry.forEach((constraint)=>constraint.facingMode=facingMode)}return this._getMatchingCameraStream(constraintsToTry)}_getMatchingCameraStream(constraintsToTry){if(constraintsToTry.length===0)return Promise.reject("Camera not found.");return navigator.mediaDevices.getUserMedia({video:constraintsToTry.shift()}).catch(()=>this._getMatchingCameraStream(constraintsToTry))}_setVideoMirror(facingMode){const scaleFactor=facingMode==="user"?-1:1;this.$video.style.transform="scaleX("+ | ||
scaleFactor+")"}static _getImageData(image,sourceRect=null,canvas=null,fixedCanvasSize=false){canvas=canvas||document.createElement("canvas");const sourceRectX=sourceRect&&sourceRect.x?sourceRect.x:0;const sourceRectY=sourceRect&&sourceRect.y?sourceRect.y:0;const sourceRectWidth=sourceRect&&sourceRect.width?sourceRect.width:image.width||image.videoWidth;const sourceRectHeight=sourceRect&&sourceRect.height?sourceRect.height:image.height||image.videoHeight;if(!fixedCanvasSize&&(canvas.width!==sourceRectWidth|| | ||
canvas.height!==sourceRectHeight)){canvas.width=sourceRectWidth;canvas.height=sourceRectHeight}const context=canvas.getContext("2d",{alpha:false});context.imageSmoothingEnabled=false;context.drawImage(image,sourceRectX,sourceRectY,sourceRectWidth,sourceRectHeight,0,0,canvas.width,canvas.height);return context.getImageData(0,0,canvas.width,canvas.height)}static _loadImage(imageOrFileOrUrl){if(imageOrFileOrUrl instanceof HTMLCanvasElement||imageOrFileOrUrl instanceof HTMLVideoElement||window.ImageBitmap&& | ||
imageOrFileOrUrl instanceof window.ImageBitmap||window.OffscreenCanvas&&imageOrFileOrUrl instanceof window.OffscreenCanvas)return Promise.resolve(imageOrFileOrUrl);else if(imageOrFileOrUrl instanceof Image)return QrScanner._awaitImageLoad(imageOrFileOrUrl).then(()=>imageOrFileOrUrl);else if(imageOrFileOrUrl instanceof File||imageOrFileOrUrl instanceof URL||typeof imageOrFileOrUrl==="string"){const image=new Image;if(imageOrFileOrUrl instanceof File)image.src=URL.createObjectURL(imageOrFileOrUrl); | ||
else image.src=imageOrFileOrUrl;return QrScanner._awaitImageLoad(image).then(()=>{if(imageOrFileOrUrl instanceof File)URL.revokeObjectURL(image.src);return image})}else return Promise.reject("Unsupported image type.")}static _awaitImageLoad(image){return new Promise((resolve,reject)=>{if(image.complete&&image.naturalWidth!==0)resolve();else{let onLoad,onError;onLoad=()=>{image.removeEventListener("load",onLoad);image.removeEventListener("error",onError);resolve()};onError=()=>{image.removeEventListener("load", | ||
onLoad);image.removeEventListener("error",onError);reject("Image load error")};image.addEventListener("load",onLoad);image.addEventListener("error",onError)}})}}QrScanner.DEFAULT_CANVAS_SIZE=400;QrScanner.WORKER_PATH="qr-scanner-worker.min.js"; | ||
class e{static hasCamera(){return navigator.mediaDevices.enumerateDevices().then((a)=>a.some((a)=>"videoinput"===a.kind)).catch(()=>!1)}constructor(a,c,b=e.DEFAULT_CANVAS_SIZE){this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=c;this._paused=this._active=!1;this.$canvas.width=b;this.$canvas.height=b;this._sourceRect={x:0,y:0,width:b,height:b};this._onCanPlay=this._onCanPlay.bind(this);this._onPlay=this._onPlay.bind(this);this._onVisibilityChange=this._onVisibilityChange.bind(this); | ||
this.$video.addEventListener("canplay",this._onCanPlay);this.$video.addEventListener("play",this._onPlay);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrWorker=new Worker(e.WORKER_PATH)}destroy(){this.$video.removeEventListener("canplay",this._onCanPlay);this.$video.removeEventListener("play",this._onPlay);document.removeEventListener("visibilitychange",this._onVisibilityChange);this.stop();this._qrWorker.postMessage({type:"close"})}start(){if(this._active&&!this._paused)return Promise.resolve(); | ||
"https:"!==window.location.protocol&&console.warn("The camera stream is only accessible if the page is transferred via https.");this._active=!0;this._paused=!1;if(document.hidden)return Promise.resolve();clearTimeout(this._offTimeout);this._offTimeout=null;if(this.$video.srcObject)return this.$video.play(),Promise.resolve();let a="environment";return this._getCameraStream("environment",!0).catch(()=>{a="user";return this._getCameraStream()}).then((c)=>{this.$video.srcObject=c;this._setVideoMirror(a)}).catch((a)=> | ||
{this._active=!1;throw a;})}stop(){this.pause();this._active=!1}pause(){this._paused=!0;this._active&&(this.$video.pause(),this._offTimeout||(this._offTimeout=setTimeout(()=>{let a=this.$video.srcObject&&this.$video.srcObject.getTracks()[0];a&&(a.stop(),this._offTimeout=this.$video.srcObject=null)},300)))}static scanImage(a,c=null,b=null,d=null,f=!1,g=!1){let h=!1,l=new Promise((l,g)=>{b||(b=new Worker(e.WORKER_PATH),h=!0,b.postMessage({type:"inversionMode",data:"both"}));let n,m,k;m=(a)=>{"qrResult"=== | ||
a.data.type&&(b.removeEventListener("message",m),b.removeEventListener("error",k),clearTimeout(n),null!==a.data.data?l(a.data.data):g("QR code not found."))};k=(a)=>{b.removeEventListener("message",m);b.removeEventListener("error",k);clearTimeout(n);g("Scanner error: "+(a?a.message||a:"Unknown Error"))};b.addEventListener("message",m);b.addEventListener("error",k);n=setTimeout(()=>k("timeout"),3E3);e._loadImage(a).then((a)=>{a=e._getImageData(a,c,d,f);b.postMessage({type:"decode",data:a},[a.data.buffer])}).catch(k)}); | ||
c&&g&&(l=l.catch(()=>e.scanImage(a,null,b,d,f)));return l=l.finally(()=>{h&&b.postMessage({type:"close"})})}setGrayscaleWeights(a,c,b,d=!0){this._qrWorker.postMessage({type:"grayscaleWeights",data:{red:a,green:c,blue:b,useIntegerApproximation:d}})}setInversionMode(a){this._qrWorker.postMessage({type:"inversionMode",data:a})}_onCanPlay(){this._updateSourceRect();this.$video.play()}_onPlay(){this._updateSourceRect();this._scanFrame()}_onVisibilityChange(){document.hidden?this.pause():this._active&& | ||
this.start()}_updateSourceRect(){let a=Math.round(2/3*Math.min(this.$video.videoWidth,this.$video.videoHeight));this._sourceRect.width=this._sourceRect.height=a;this._sourceRect.x=(this.$video.videoWidth-a)/2;this._sourceRect.y=(this.$video.videoHeight-a)/2}_scanFrame(){if(!this._active||this.$video.paused||this.$video.ended)return!1;requestAnimationFrame(()=>{e.scanImage(this.$video,this._sourceRect,this._qrWorker,this.$canvas,!0).then(this._onDecode,(a)=>{this._active&&"QR code not found."!==a&& | ||
console.error(a)}).then(()=>this._scanFrame())})}_getCameraStream(a,c=!1){let b=[{width:{min:1024}},{width:{min:768}},{}];a&&(c&&(a={exact:a}),b.forEach((b)=>b.facingMode=a));return this._getMatchingCameraStream(b)}_getMatchingCameraStream(a){return 0===a.length?Promise.reject("Camera not found."):navigator.mediaDevices.getUserMedia({video:a.shift()}).catch(()=>this._getMatchingCameraStream(a))}_setVideoMirror(a){this.$video.style.transform="scaleX("+("user"===a?-1:1)+")"}static _getImageData(a,c= | ||
null,b=null,d=!1){b=b||document.createElement("canvas");let f=c&&c.x?c.x:0,g=c&&c.y?c.y:0,h=c&&c.width?c.width:a.width||a.videoWidth;c=c&&c.height?c.height:a.height||a.videoHeight;d||b.width===h&&b.height===c||(b.width=h,b.height=c);d=b.getContext("2d",{alpha:!1});d.imageSmoothingEnabled=!1;d.drawImage(a,f,g,h,c,0,0,b.width,b.height);return d.getImageData(0,0,b.width,b.height)}static _loadImage(a){if(a instanceof HTMLCanvasElement||a instanceof HTMLVideoElement||window.ImageBitmap&&a instanceof window.ImageBitmap|| | ||
window.OffscreenCanvas&&a instanceof window.OffscreenCanvas)return Promise.resolve(a);if(a instanceof Image)return e._awaitImageLoad(a).then(()=>a);if(a instanceof File||a instanceof URL||"string"===typeof a){let c=new Image;c.src=a instanceof File?URL.createObjectURL(a):a;return e._awaitImageLoad(c).then(()=>{a instanceof File&&URL.revokeObjectURL(c.src);return c})}return Promise.reject("Unsupported image type.")}static _awaitImageLoad(a){return new Promise((c,b)=>{if(a.complete&&0!==a.naturalWidth)c(); | ||
else{let d,f;d=()=>{a.removeEventListener("load",d);a.removeEventListener("error",f);c()};f=()=>{a.removeEventListener("load",d);a.removeEventListener("error",f);b("Image load error")};a.addEventListener("load",d);a.addEventListener("error",f)}})}}e.DEFAULT_CANVAS_SIZE=400;e.WORKER_PATH="qr-scanner-worker.min.js";export default e; | ||
//# sourceMappingURL=qr-scanner.min.js.map |
# QR Scanner | ||
Javascript QR Code Scanner based on [Lazar Lazslo's javascript port](https://github.com/LazarSoft/jsqrcode) of [Google's ZXing library](https://github.com/zxing/zxing). | ||
Javascript QR Code Scanner based on [Cosmo Wolfe's javascript port](https://github.com/cozmo/jsqr) of [Google's ZXing library](https://github.com/zxing/zxing). | ||
In this library, several improvements have been applied over the original port: | ||
<!-- | ||
- Lightweight: ~33.7 kB (~12 kB gzipped) minified with Google's closure compiler. | ||
- Improved binarizer which makes it more tolerant to shades and reflections on the screen. | ||
--> | ||
- Improved performance and reduced memory footprint. | ||
- Runs in a WebWorker which keeps the main / UI thread responsive. | ||
- Smaller file size. | ||
- Can be configured for better performance on colored QR codes. | ||
- Runs in a WebWorker which keeps the main / UI thread responsive. | ||
- Works on higher resolution pictures by default. | ||
According to [our benchmarking](https://github.com/danimoh/qr-scanner-benchmark) this project's scanner engine's detection rate is about 2-3 times (and up to 8 times) as high as the one of the most popular javascript QR scanner library [LazarSoft/jsqrcode](https://github.com/LazarSoft/jsqrcode). | ||
The library supports scanning a continuous video stream from a web cam as well as scanning of single images. | ||
The development of this library is sponsored by [nimiq](https://www.nimiq.com), the world's first browser based blockchain. | ||
The development of this library is sponsored by [nimiq](https://www.nimiq.com), world's first browser based blockchain. | ||
[<img src="https://ucb689f1ef4767d4abfb0925e185.previews.dropboxusercontent.com/p/thumb/AAVEuJzxQiFQdRZzaAqyBe7DbR9bX8SSncfAYCBCf4p5ryvIoabV0kBBDE2QQU1xqiZNQsl3JH4mm6K5hOY77dLpx5gsTU5FMsCEYqJiXb-FZg68EjOgMWR5OW0ux2AbUuGqQHebrYg0jwUbaeZt9R8IAKWMIBF99TSdAXTwakC0rnk6KamIGaqbVio80xvAcY1vOeZctnNnjW4nYhUIjYyCsDPhgEbPhBcrVVLJhqoygm9CUgFbXBcDLAdmgLKQSTjeDyR553GV-lqLm0b1Hxw9/p.png?size_mode=5" alt="nimiq.com" width="250">](https://nimiq.com) | ||
[<img src="https://nimiq.github.io/qr-scanner/nimiq_logo_rgb_horizontal.svg" alt="nimiq.com" width="250">](https://nimiq.com) | ||
@@ -27,3 +31,3 @@ | ||
```bash | ||
npm install --safe qr-scanner | ||
npm install --save qr-scanner | ||
``` | ||
@@ -58,3 +62,3 @@ To install via yarn: | ||
If you're using webpack to bundle your project, the file loader might be interesting for you, to automatically copy the worker into your build: | ||
If you're using webpack to bundle your project, the file loader might be interesting for you to automatically copy the worker into your build: | ||
```js | ||
@@ -114,5 +118,5 @@ import QrScannerWorkerPath from '!!file-loader!./node_modules/qr-scanner/qr-scanner-worker.min.js'; | ||
```js | ||
qrScanner.setGrayscaleWeights(red, green, blue); | ||
qrScanner.setGrayscaleWeights(red, green, blue, useIntegerApproximation = true); | ||
``` | ||
Where `red`, `green` and `blue` must sum up to 256. | ||
Where `red`, `green` and `blue` should sum up to 256 if `useIntegerApproximation === true` and `1` otherwise. By default, [these](https://en.wikipedia.org/wiki/YUV#Full_swing_for_BT.601) values are used. | ||
@@ -130,3 +134,3 @@ ### Clean Up | ||
The project is prebuild in qr-scanner.min.js in combination with qr-scanner-worker.min.js. Building yourself is only necessary if you want to change the code in | ||
the /src folder. NodeJs and Java are required for building. | ||
the /src folder. NodeJs is required for building. | ||
@@ -143,19 +147,1 @@ Install required build packages: | ||
## Debug Mode | ||
To enable debug mode: | ||
```js | ||
qrScanner._qrWorker.postMessage({ | ||
type: 'setDebug', | ||
data: true | ||
}); | ||
``` | ||
To handle the debug image: | ||
```js | ||
qrScanner._qrWorker.addEventListener('message', event => { | ||
if (event.data.type === 'debugImage') { | ||
canvasContext.putImageData(event.data.data, 0, 0); | ||
} | ||
}); | ||
``` |
@@ -116,5 +116,7 @@ export default class QrScanner { | ||
alsoTryWithoutSourceRect=false) { | ||
const promise = new Promise((resolve, reject) => { | ||
let createdNewWorker = false; | ||
let promise = new Promise((resolve, reject) => { | ||
if (!worker) { | ||
worker = new Worker(QrScanner.WORKER_PATH); | ||
createdNewWorker = true; | ||
worker.postMessage({ type: 'inversionMode', data: 'both' }); // scan inverted color qr codes too | ||
@@ -140,3 +142,3 @@ } | ||
clearTimeout(timeout); | ||
var errorMessage = !e ? 'Unknown Error' : (e.message || e); | ||
const errorMessage = !e ? 'Unknown Error' : (e.message || e); | ||
reject('Scanner error: ' + errorMessage); | ||
@@ -153,16 +155,23 @@ }; | ||
}, [imageData.data.buffer]); | ||
}).catch(reject); | ||
}).catch(onError); | ||
}); | ||
if (sourceRect && alsoTryWithoutSourceRect) { | ||
return promise.catch(() => QrScanner.scanImage(imageOrFileOrUrl, null, worker, canvas, fixedCanvasSize)); | ||
} else { | ||
return promise; | ||
promise = promise.catch(() => QrScanner.scanImage(imageOrFileOrUrl, null, worker, canvas, fixedCanvasSize)); | ||
} | ||
promise = promise.finally(() => { | ||
if (!createdNewWorker) return; | ||
worker.postMessage({ | ||
type: 'close' | ||
}); | ||
}); | ||
return promise; | ||
} | ||
setGrayscaleWeights(red, green, blue) { | ||
setGrayscaleWeights(red, green, blue, useIntegerApproximation = true) { | ||
this._qrWorker.postMessage({ | ||
type: 'grayscaleWeights', | ||
data: { red, green, blue } | ||
data: { red, green, blue, useIntegerApproximation } | ||
}); | ||
@@ -249,3 +258,2 @@ } | ||
/* async */ | ||
static _getImageData(image, sourceRect=null, canvas=null, fixedCanvasSize=false) { | ||
@@ -252,0 +260,0 @@ canvas = canvas || document.createElement('canvas'); |
@@ -17,5 +17,5 @@ // Type definitions for qr-scanner | ||
pause(): void; | ||
setGrayscaleWeights(red: number, green: number, blue: number): void; | ||
setInversionMode(inverionMode: QrScanner.InversionMode): void; | ||
scanImage( | ||
setGrayscaleWeights(red: number, green: number, blue: number, useIntegerApproximation?: boolean): void; | ||
setInversionMode(inversionMode: QrScanner.InversionMode): void; | ||
static scanImage( | ||
imageOrFileOrUrl: HTMLCanvasElement | HTMLVideoElement | ImageBitmap | HTMLImageElement | File | URL | String, | ||
@@ -22,0 +22,0 @@ sourceRect?: QrScanner.SourceRect | null, |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
558448
880
0
5
143