mirror of
https://github.com/nimiq/qr-scanner.git
synced 2026-07-27 19:56:28 +00:00
use web worker
This commit is contained in:
+2
-1
@@ -11,6 +11,7 @@ gulp.task('build', function() {
|
||||
.pipe(sourcemaps.init()),
|
||||
|
||||
gulp.src([
|
||||
'./src/worker.js',
|
||||
'./src/binarizer.js',
|
||||
'./src/grid.js',
|
||||
'./src/version.js',
|
||||
@@ -39,7 +40,7 @@ gulp.task('build', function() {
|
||||
gulp.src('./src/suffix.js')
|
||||
.pipe(sourcemaps.init())
|
||||
)
|
||||
.pipe(concat('qr-scanner-lib.min.js'))
|
||||
.pipe(concat('qr-scanner-worker.min.js'))
|
||||
.pipe(uglify())
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest('.'));
|
||||
|
||||
+20
-27
@@ -67,6 +67,13 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
#debug-canvas {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
position: absolute;
|
||||
max-width: 320px;
|
||||
@@ -117,27 +124,6 @@
|
||||
</x-qr-scanner>
|
||||
<a class="label">Test</a>
|
||||
</body>
|
||||
<script src="qr-scanner-lib.min.js"></script>
|
||||
<!--
|
||||
<script type="text/javascript" src="src/binarizer.js"></script>
|
||||
<script type="text/javascript" src="src/grid.js"></script>
|
||||
<script type="text/javascript" src="src/version.js"></script>
|
||||
<script type="text/javascript" src="src/detector.js"></script>
|
||||
<script type="text/javascript" src="src/formatinf.js"></script>
|
||||
<script type="text/javascript" src="src/errorlevel.js"></script>
|
||||
<script type="text/javascript" src="src/bitmat.js"></script>
|
||||
<script type="text/javascript" src="src/datablock.js"></script>
|
||||
<script type="text/javascript" src="src/bmparser.js"></script>
|
||||
<script type="text/javascript" src="src/datamask.js"></script>
|
||||
<script type="text/javascript" src="src/rsdecoder.js"></script>
|
||||
<script type="text/javascript" src="src/gf256poly.js"></script>
|
||||
<script type="text/javascript" src="src/gf256.js"></script>
|
||||
<script type="text/javascript" src="src/decoder.js"></script>
|
||||
<script type="text/javascript" src="src/qrcode.js"></script>
|
||||
<script type="text/javascript" src="src/findpat.js"></script>
|
||||
<script type="text/javascript" src="src/alignpat.js"></script>
|
||||
<script type="text/javascript" src="src/databr.js"></script>
|
||||
-->
|
||||
<script src="/x-element/src/x-element.js"></script>
|
||||
<script src="qr-scanner.js"></script>
|
||||
<script>
|
||||
@@ -148,12 +134,18 @@ scanner.active = true;
|
||||
|
||||
scanner.$el.addEventListener('x-decoded', (e) => {
|
||||
const decoded = e.detail;
|
||||
console.log(decoded);
|
||||
setLabel(decoded);
|
||||
resetClass(label, 'label-active', decoded);
|
||||
resetClass(decoded);
|
||||
});
|
||||
|
||||
function setLabel(decoded) {
|
||||
if (qrscanner.isUrl(decoded)) {
|
||||
var isUrl = false;
|
||||
try {
|
||||
new Url(decoded);
|
||||
isUrl = true;
|
||||
} catch(e) {}
|
||||
if (isUrl) {
|
||||
label.href = decoded;
|
||||
} else {
|
||||
label.removeAttribute('href');
|
||||
@@ -164,13 +156,14 @@ function setLabel(decoded) {
|
||||
var lastDecoded;
|
||||
var lastDecodedTimer;
|
||||
|
||||
function resetClass(el, className, decoded) {
|
||||
if (decoded == lastDecoded) return;
|
||||
function resetClass(decoded) {
|
||||
if (decoded === lastDecoded) return;
|
||||
clearTimeout(lastDecodedTimer);
|
||||
lastDecodedTimer = setTimeout(() => lastDecoded = false, 3000);
|
||||
lastDecoded = decoded;
|
||||
label.classList.remove(className);
|
||||
requestAnimationFrame(_ => label.classList.add(className))
|
||||
label.classList.remove('label-active');
|
||||
label.offsetWidth; // force render
|
||||
label.classList.add('label-active');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Vendored
-2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+37
-14
@@ -1,10 +1,15 @@
|
||||
class QrScanner extends XElement {
|
||||
onCreate() {
|
||||
this._qrWorker = new Worker('qr-scanner-worker.min.js');
|
||||
this._qrWorker.onmessage = event => this._handleWorkerMessage(event);
|
||||
this.$video = this.$('video');
|
||||
this.$canvas = this.$('canvas');
|
||||
this.$debugCanvas = null;
|
||||
this.$debugContext = null;
|
||||
this.$context = this.$canvas.getContext('2d');
|
||||
this.$overlay = this.$('#qr-overlay');
|
||||
this._sourceRectSize = 400;
|
||||
this._canvasSize = this.$canvas.width;
|
||||
this._sourceRectSize = this._canvasSize;
|
||||
window.addEventListener('resize', () => this._updateSourceRect());
|
||||
this.$video.addEventListener('canplay', () => this._updateSourceRect());
|
||||
this.$video.addEventListener('play', () => this._scanFrame(), false);
|
||||
@@ -32,22 +37,24 @@ class QrScanner extends XElement {
|
||||
if (this.$video.paused || this.$video.ended) return false;
|
||||
this.$context.drawImage(this.$video, (this.$video.videoWidth - this._sourceRectSize) / 2,
|
||||
(this.$video.videoHeight - this._sourceRectSize) / 2, this._sourceRectSize, this._sourceRectSize,
|
||||
0, 0, 400, 400);
|
||||
this._decode();
|
||||
requestAnimationFrame(() => this._scanFrame());
|
||||
0, 0, this._canvasSize, this._canvasSize);
|
||||
var imageData = this.$context.getImageData(0, 0, this._canvasSize, this._canvasSize);
|
||||
this._qrWorker.postMessage({
|
||||
type: 'decode',
|
||||
data: imageData
|
||||
}, [imageData.data.buffer]);
|
||||
}
|
||||
|
||||
_decode() {
|
||||
try {
|
||||
var decoded = qrscanner.decode();
|
||||
this.fire('x-decoded', decoded);
|
||||
} catch (e) {
|
||||
if (e.message.startsWith('QR Error')) {
|
||||
// no valid QR code found.
|
||||
console.log(e);
|
||||
} else {
|
||||
throw e; // some different error
|
||||
_handleWorkerMessage(event) {
|
||||
var type = event.data.type;
|
||||
var data = event.data.data;
|
||||
if (type === 'qrResult') {
|
||||
if (data !== null) {
|
||||
this.fire('x-decoded', data);
|
||||
}
|
||||
requestAnimationFrame(() => this._scanFrame());
|
||||
} else if (type === 'debugImage') {
|
||||
this.$debugContext.putImageData(data, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +98,22 @@ class QrScanner extends XElement {
|
||||
this.$video.pause();
|
||||
setTimeout(() => this.$video.srcObject.getTracks()[0].stop(), 3000);
|
||||
}
|
||||
|
||||
set debug(isDebug) {
|
||||
this._qrWorker.postMessage({
|
||||
type: 'setDebug',
|
||||
data: isDebug
|
||||
});
|
||||
if (!this.$debugCanvas) {
|
||||
this.$debugCanvas = document.createElement('canvas');
|
||||
this.$debugCanvas.setAttribute('id', 'debug-canvas');
|
||||
this.$debugCanvas.width = this._canvasSize;
|
||||
this.$debugCanvas.height = this._canvasSize;
|
||||
this.$debugContext = this.$debugCanvas.getContext('2d');
|
||||
document.body.appendChild(this.$debugCanvas);
|
||||
}
|
||||
this.$debugCanvas.style.display = isDebug? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -136,4 +136,4 @@ class Binarizer {
|
||||
}
|
||||
Binarizer.TARGET_BLOCK_COUNT_ALONG_SHORTER_SIDE = 40;
|
||||
Binarizer.MIN_BLOCK_SIZE = 16;
|
||||
Binarizer.MIN_DYNAMIC_RANGE = 24; // if the dynamic range in a block is below this value it's assumed to be single color
|
||||
Binarizer.MIN_DYNAMIC_RANGE = 12; // if the dynamic range in a block is below this value it's assumed to be single color
|
||||
+34
-187
@@ -27,93 +27,24 @@ qrcode.sizeOfDataLengthInfo = [ [ 10, 9, 8, 8 ], [ 12, 11, 16, 10 ], [ 14, 1
|
||||
|
||||
qrcode.callback = null;
|
||||
|
||||
qrcode.decode = function(src){
|
||||
|
||||
if(arguments.length==0)
|
||||
{
|
||||
if(qrcode.canvas_qr2)
|
||||
{
|
||||
var canvas_qr = qrcode.canvas_qr2;
|
||||
var context = qrcode.qrcontext2;
|
||||
}
|
||||
else
|
||||
{
|
||||
var canvas_qr = document.getElementById("qr-canvas");
|
||||
var context = canvas_qr.getContext('2d');
|
||||
}
|
||||
qrcode.width = canvas_qr.width;
|
||||
qrcode.height = canvas_qr.height;
|
||||
qrcode.imagedata = context.getImageData(0, 0, qrcode.width, qrcode.height);
|
||||
qrcode.result = qrcode.process(context);
|
||||
if(qrcode.callback!=null)
|
||||
qrcode.callback(qrcode.result);
|
||||
return qrcode.result;
|
||||
}
|
||||
else
|
||||
{
|
||||
var image = new Image();
|
||||
image.crossOrigin = "Anonymous";
|
||||
image.onload=function(){
|
||||
//var canvas_qr = document.getElementById("qr-canvas");
|
||||
var canvas_out = document.getElementById("out-canvas");
|
||||
if(canvas_out!=null)
|
||||
{
|
||||
var outctx = canvas_out.getContext('2d');
|
||||
outctx.clearRect(0, 0, 320, 240);
|
||||
outctx.drawImage(image, 0, 0, 320, 240);
|
||||
}
|
||||
|
||||
var canvas_qr = document.createElement('canvas');
|
||||
var context = canvas_qr.getContext('2d');
|
||||
var nheight = image.height;
|
||||
var nwidth = image.width;
|
||||
if(image.width*image.height>qrcode.maxImgSize)
|
||||
{
|
||||
var ir = image.width / image.height;
|
||||
nheight = Math.sqrt(qrcode.maxImgSize/ir);
|
||||
nwidth=ir*nheight;
|
||||
}
|
||||
|
||||
canvas_qr.width = nwidth;
|
||||
canvas_qr.height = nheight;
|
||||
|
||||
context.drawImage(image, 0, 0, canvas_qr.width, canvas_qr.height );
|
||||
qrcode.width = canvas_qr.width;
|
||||
qrcode.height = canvas_qr.height;
|
||||
try{
|
||||
qrcode.imagedata = context.getImageData(0, 0, canvas_qr.width, canvas_qr.height);
|
||||
}catch(e){
|
||||
qrcode.result = "Cross domain image reading not supported in your browser! Save it to your computer then drag and drop the file!";
|
||||
if(qrcode.callback!=null)
|
||||
qrcode.callback(qrcode.result);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
qrcode.result = qrcode.process(context);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.log(e);
|
||||
qrcode.result = "error decoding QR Code";
|
||||
}
|
||||
if(qrcode.callback!=null)
|
||||
qrcode.callback(qrcode.result);
|
||||
}
|
||||
image.onerror = function ()
|
||||
{
|
||||
if(qrcode.callback!=null)
|
||||
qrcode.callback("Failed to load the image");
|
||||
}
|
||||
image.src = src;
|
||||
}
|
||||
qrcode.decode = function(imageData) {
|
||||
qrcode.imagedata = imageData;
|
||||
qrcode.width = imageData.width;
|
||||
qrcode.height = imageData.height;
|
||||
qrcode.result = qrcode.process();
|
||||
if(qrcode.callback!=null)
|
||||
qrcode.callback(qrcode.result);
|
||||
return qrcode.result;
|
||||
}
|
||||
|
||||
qrcode.isUrl = function(s)
|
||||
{
|
||||
var regexp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
|
||||
return regexp.test(s);
|
||||
try {
|
||||
new URL(s);
|
||||
return true;
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
qrcode.decode_url = function (s)
|
||||
@@ -147,7 +78,7 @@ qrcode.decode_utf8 = function ( s )
|
||||
return s;
|
||||
}
|
||||
|
||||
qrcode.process = function(ctx){
|
||||
qrcode.process = function(){
|
||||
var inputRgba = qrcode.imagedata.data;
|
||||
// asign the grayscale and binary image within the rgba buffer as the rgba image will not be needed anymore
|
||||
var offset = 0;
|
||||
@@ -177,30 +108,30 @@ qrcode.process = function(ctx){
|
||||
debugImage.data[point+3] = 255; // alpha
|
||||
}
|
||||
}
|
||||
ctx.putImageData(debugImage, 0, 0);
|
||||
}
|
||||
|
||||
//var finderPatternInfo = new FinderPatternFinder().findFinderPattern(image);
|
||||
|
||||
var detector = new Detector(binaryImage);
|
||||
|
||||
var qRCodeMatrix = detector.detect(); // throws if no qr code was found
|
||||
try {
|
||||
var detector = new Detector(binaryImage);
|
||||
|
||||
if(qrcode.debug)
|
||||
{
|
||||
for (var y = 0; y < qRCodeMatrix.bits.Height; y++)
|
||||
{
|
||||
for (var x = 0; x < qRCodeMatrix.bits.Width; x++)
|
||||
{
|
||||
var point = (x * 4*2) + (y*2 * qrcode.width * 4);
|
||||
var isSet = qRCodeMatrix.bits.get_Renamed(x,y)
|
||||
debugImage.data[point] = isSet? 0 : 255;
|
||||
debugImage.data[point+1] = isSet? 0 : 255;
|
||||
debugImage.data[point+2] = 255;
|
||||
var qRCodeMatrix = detector.detect(); // throws if no qr code was found
|
||||
|
||||
if (qrcode.debug) {
|
||||
for (var y = 0; y < qRCodeMatrix.bits.Height; y++) {
|
||||
for (var x = 0; x < qRCodeMatrix.bits.Width; x++) {
|
||||
var point = (x * 4 * 2) + (y * 2 * qrcode.width * 4);
|
||||
var isSet = qRCodeMatrix.bits.get_Renamed(x, y)
|
||||
debugImage.data[point] = isSet ? 0 : 255;
|
||||
debugImage.data[point + 1] = isSet ? 0 : 255;
|
||||
debugImage.data[point + 2] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.putImageData(debugImage, 0, 0);
|
||||
} finally {
|
||||
if (qrcode.debug) {
|
||||
sendDebugImage(debugImage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var reader = Decoder.decode(qRCodeMatrix.bits);
|
||||
@@ -213,89 +144,8 @@ qrcode.process = function(ctx){
|
||||
}
|
||||
|
||||
return qrcode.decode_utf8(str);
|
||||
//alert("Time:" + time + " Code: "+str);
|
||||
}
|
||||
|
||||
qrcode.getMiddleBrightnessPerArea=function(image)
|
||||
{
|
||||
var numSqrtArea = 4;
|
||||
//obtain middle brightness((min + max) / 2) per area
|
||||
var areaWidth = Math.floor(qrcode.width / numSqrtArea);
|
||||
var areaHeight = Math.floor(qrcode.height / numSqrtArea);
|
||||
var minmax = new Array(numSqrtArea);
|
||||
for (var i = 0; i < numSqrtArea; i++)
|
||||
{
|
||||
minmax[i] = new Array(numSqrtArea);
|
||||
for (var i2 = 0; i2 < numSqrtArea; i2++)
|
||||
{
|
||||
minmax[i][i2] = new Array(0,0);
|
||||
}
|
||||
}
|
||||
for (var ay = 0; ay < numSqrtArea; ay++)
|
||||
{
|
||||
for (var ax = 0; ax < numSqrtArea; ax++)
|
||||
{
|
||||
minmax[ax][ay][0] = 0xFF;
|
||||
for (var dy = 0; dy < areaHeight; dy++)
|
||||
{
|
||||
for (var dx = 0; dx < areaWidth; dx++)
|
||||
{
|
||||
var target = image[areaWidth * ax + dx+(areaHeight * ay + dy)*qrcode.width];
|
||||
if (target < minmax[ax][ay][0])
|
||||
minmax[ax][ay][0] = target;
|
||||
if (target > minmax[ax][ay][1])
|
||||
minmax[ax][ay][1] = target;
|
||||
}
|
||||
}
|
||||
//minmax[ax][ay][0] = (minmax[ax][ay][0] + minmax[ax][ay][1]) / 2;
|
||||
}
|
||||
}
|
||||
var middle = new Array(numSqrtArea);
|
||||
for (var i3 = 0; i3 < numSqrtArea; i3++)
|
||||
{
|
||||
middle[i3] = new Array(numSqrtArea);
|
||||
}
|
||||
for (var ay = 0; ay < numSqrtArea; ay++)
|
||||
{
|
||||
for (var ax = 0; ax < numSqrtArea; ax++)
|
||||
{
|
||||
middle[ax][ay] = Math.floor((minmax[ax][ay][0] + minmax[ax][ay][1]) / 2);
|
||||
//Console.out.print(middle[ax][ay] + ",");
|
||||
}
|
||||
//Console.out.println("");
|
||||
}
|
||||
//Console.out.println("");
|
||||
|
||||
return middle;
|
||||
}
|
||||
|
||||
qrcode.grayScaleToBitmap=function(grayScale)
|
||||
{
|
||||
var middle = qrcode.getMiddleBrightnessPerArea(grayScale);
|
||||
var sqrtNumArea = middle.length;
|
||||
var areaWidth = Math.floor(qrcode.width / sqrtNumArea);
|
||||
var areaHeight = Math.floor(qrcode.height / sqrtNumArea);
|
||||
|
||||
var buff = new ArrayBuffer(qrcode.width*qrcode.height);
|
||||
var bitmap = new Uint8Array(buff);
|
||||
|
||||
//var bitmap = new Array(qrcode.height*qrcode.width);
|
||||
|
||||
for (var ay = 0; ay < sqrtNumArea; ay++)
|
||||
{
|
||||
for (var ax = 0; ax < sqrtNumArea; ax++)
|
||||
{
|
||||
for (var dy = 0; dy < areaHeight; dy++)
|
||||
{
|
||||
for (var dx = 0; dx < areaWidth; dx++)
|
||||
{
|
||||
bitmap[areaWidth * ax + dx+ (areaHeight * ay + dy)*qrcode.width] = (grayScale[areaWidth * ax + dx+ (areaHeight * ay + dy)*qrcode.width] < middle[ax][ay])?true:false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
qrcode.grayscale = function(inputRgba, width, height, out_grayscale)
|
||||
{
|
||||
@@ -321,7 +171,4 @@ function URShift( number, bits)
|
||||
return number >> bits;
|
||||
else
|
||||
return (number >> bits) + (2 << ~bits);
|
||||
}
|
||||
|
||||
|
||||
window.qrscanner = qrcode;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
self.onmessage = event => {
|
||||
const type = event.data.type;
|
||||
const data = event.data.data;
|
||||
if (type === 'setDebug') {
|
||||
qrcode.debug = data;
|
||||
} else if (type === 'decode') {
|
||||
let result = null;
|
||||
try {
|
||||
result = qrcode.decode(data);
|
||||
} catch(e) {
|
||||
if (!e.message.startsWith('QR Error')) {
|
||||
throw e; // some unexpected error
|
||||
}
|
||||
} finally {
|
||||
self.postMessage({
|
||||
type: 'qrResult',
|
||||
data: result
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function sendDebugImage(debugImage) {
|
||||
self.postMessage({
|
||||
type: 'debugImage',
|
||||
data: debugImage
|
||||
}, [debugImage.data.buffer]);
|
||||
}
|
||||
Reference in New Issue
Block a user