mirror of
https://github.com/yugasun/qrcode-decoder.git
synced 2026-08-12 20:27:56 +08:00
feat: init project
This commit is contained in:
+370
@@ -0,0 +1,370 @@
|
||||
import 'babel-polyfill';
|
||||
import jsQR from 'jsqr';
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
class QrcodeDecoder {
|
||||
/**
|
||||
* Constructor for QrcodeDecoder
|
||||
*/
|
||||
constructor() {
|
||||
this.timerCapture = null;
|
||||
this.canvasElem = null;
|
||||
this.gCtx = null;
|
||||
this.stream = null;
|
||||
this.videoElem = null;
|
||||
this.getUserMediaHandler = null;
|
||||
this.videoConstraints = { video: true, audio: false };
|
||||
|
||||
this.defaultOption = { inversionAttempts: 'attemptBoth' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if canvas element is supported.
|
||||
*/
|
||||
/* eslint-disable class-methods-use-this */
|
||||
isCanvasSupported() {
|
||||
const elem = document.createElement('canvas');
|
||||
return !!(elem.getContext && elem.getContext('2d'));
|
||||
}
|
||||
|
||||
/**
|
||||
* draw lint
|
||||
*
|
||||
* @param {object} begin line begin point
|
||||
* @param {object} end line end point
|
||||
* @param {string} color color string
|
||||
*/
|
||||
// _drawLine(begin, end, color) {
|
||||
// this.gCtx.beginPath();
|
||||
// this.gCtx.moveTo(begin.x, begin.y);
|
||||
// this.gCtx.lineTo(end.x, end.y);
|
||||
// this.gCtx.lineWidth = 4;
|
||||
// this.gCtx.strokeStyle = color;
|
||||
// this.gCtx.stroke();
|
||||
// }
|
||||
|
||||
/**
|
||||
* create qrcode marker
|
||||
*
|
||||
* @param {object} code jsqr parse code object
|
||||
*/
|
||||
// _createQrcodeMark(code) {
|
||||
// this._drawLine(
|
||||
// code.location.topLeftCorner,
|
||||
// code.location.topRightCorner,
|
||||
// '#FF3B58',
|
||||
// );
|
||||
// this._drawLine(
|
||||
// code.location.topRightCorner,
|
||||
// code.location.bottomRightCorner,
|
||||
// '#FF3B58',
|
||||
// );
|
||||
// this._drawLine(
|
||||
// code.location.bottomRightCorner,
|
||||
// code.location.bottomLeftCorner,
|
||||
// '#FF3B58',
|
||||
// );
|
||||
// this._drawLine(
|
||||
// code.location.bottomLeftCorner,
|
||||
// code.location.topLeftCorner,
|
||||
// '#FF3B58',
|
||||
// );
|
||||
// }
|
||||
|
||||
_createImageData(target, width, height) {
|
||||
if (!this.canvasElem) {
|
||||
this._prepareCanvas(width, height);
|
||||
}
|
||||
|
||||
this.gCtx.clearRect(0, 0, width, height);
|
||||
this.gCtx.drawImage(target, 0, 0, width, height);
|
||||
|
||||
const imageData = this.gCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
this.canvasElem.width,
|
||||
this.canvasElem.height,
|
||||
);
|
||||
|
||||
return imageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the canvas element (which will
|
||||
* receive the image from the camera and provide
|
||||
* what the algorithm needs for checking for a
|
||||
* QRCode and then decoding it.)
|
||||
*
|
||||
*
|
||||
* @param {DOMElement} canvasElem the canvas
|
||||
* element
|
||||
* @param {number} width The width that
|
||||
* the canvas element
|
||||
* should have
|
||||
* @param {number} height The height that
|
||||
* the canvas element
|
||||
* should have
|
||||
* @return {DOMElement} the canvas
|
||||
* after the resize if width and height
|
||||
* provided.
|
||||
*/
|
||||
_prepareCanvas(width, height) {
|
||||
if (!this.canvasElem) {
|
||||
this.canvasElem = document.createElement('canvas');
|
||||
this.canvasElem.style.width = `${width}px`;
|
||||
this.canvasElem.style.height = `${height}px`;
|
||||
this.canvasElem.width = width;
|
||||
this.canvasElem.height = height;
|
||||
}
|
||||
|
||||
this.gCtx = this.canvasElem.getContext('2d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the video dimensions and the canvas
|
||||
* that was previously generated captures the
|
||||
* video/image source and then paints into the
|
||||
* canvas so that the decoder is able to work as
|
||||
* it expects.
|
||||
* @param {DOMElement} videoElem <video> dom element
|
||||
* @param {Object} options options (optional) - Additional options.
|
||||
* inversionAttempts - (attemptBoth (default), dontInvert, onlyInvert, or invertFirst)
|
||||
* refer to jsqr options: https://github.com/cozmo/jsQR
|
||||
*/
|
||||
async _captureToCanvas(videoElem, options) {
|
||||
if (this.timerCapture) {
|
||||
clearTimeout(this.timerCapture);
|
||||
}
|
||||
const proms = () => {
|
||||
const p = new Promise(async (resolve) => {
|
||||
let code;
|
||||
if (videoElem.videoWidth && videoElem.videoHeight) {
|
||||
const imageData = this._createImageData(
|
||||
videoElem,
|
||||
videoElem.videoWidth,
|
||||
videoElem.videoHeight,
|
||||
);
|
||||
|
||||
code = jsQR(
|
||||
imageData.data,
|
||||
imageData.width,
|
||||
imageData.height,
|
||||
options,
|
||||
);
|
||||
|
||||
if (code) {
|
||||
resolve(code);
|
||||
} else {
|
||||
this.timerCapture = setTimeout(async () => {
|
||||
code = await this._captureToCanvas(
|
||||
videoElem,
|
||||
options,
|
||||
);
|
||||
resolve(code);
|
||||
}, 500);
|
||||
}
|
||||
} else {
|
||||
this.timerCapture = setTimeout(async () => {
|
||||
code = await this._captureToCanvas(videoElem, options);
|
||||
resolve(code);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
return p;
|
||||
};
|
||||
|
||||
const result = await proms();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the video element for receiving
|
||||
* camera's input. Releases a stream if there
|
||||
* was any (resets).
|
||||
*
|
||||
* @param {DOMElement} videoElem <video> dom element
|
||||
* @param {Object} options options (optional) - Additional options.
|
||||
* inversionAttempts - (attemptBoth (default), dontInvert, onlyInvert, or invertFirst)
|
||||
* refer to jsqr options: https://github.com/cozmo/jsQR
|
||||
*/
|
||||
async decodeFromCamera(videoElem, options = {}) {
|
||||
const opts = {
|
||||
...this.defaultOption,
|
||||
...options,
|
||||
};
|
||||
|
||||
this.stop();
|
||||
if (!navigator.mediaDevices.getUserMedia) {
|
||||
throw new Error("Couldn't get video from camera");
|
||||
}
|
||||
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia(
|
||||
this.videoConstraints,
|
||||
);
|
||||
videoElem.srcObject = stream;
|
||||
// videoElem.src = window.URL.createObjectURL(stream);
|
||||
this.videoElem = videoElem;
|
||||
this.stream = stream;
|
||||
this.videoDimensions = false;
|
||||
|
||||
const code = await this.decodeFromVideo(videoElem, opts);
|
||||
return code;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the video element for video file.
|
||||
*
|
||||
* @param {DOMElement} videoElem <video> dom element
|
||||
* @param {Object} options options (optional) - Additional options.
|
||||
* inversionAttempts - (attemptBoth (default), dontInvert, onlyInvert, or invertFirst)
|
||||
* refer to jsqr options: https://github.com/cozmo/jsQR
|
||||
*/
|
||||
async decodeFromVideo(videoElem, options = {}) {
|
||||
const opts = {
|
||||
...this.defaultOption,
|
||||
...options,
|
||||
};
|
||||
try {
|
||||
this.videoElem = videoElem;
|
||||
const code = await this._captureToCanvas(videoElem, opts);
|
||||
return code;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes an image from its src.
|
||||
* @param {DOMElement} imageElem
|
||||
* @param {Object} options options (optional) - Additional options.
|
||||
* inversionAttempts - (attemptBoth (default), dontInvert, onlyInvert, or invertFirst)
|
||||
* refer to jsqr options: https://github.com/cozmo/jsQR
|
||||
*/
|
||||
async decodeFromImage(img, options = {}) {
|
||||
let imgDom;
|
||||
const opts = {
|
||||
...this.defaultOption,
|
||||
...options,
|
||||
};
|
||||
if (+img.nodeType > 0) {
|
||||
if (!img.src) {
|
||||
throw new Error('The ImageElement must contain a src');
|
||||
}
|
||||
imgDom = img;
|
||||
} else if (typeof img === 'string') {
|
||||
imgDom = document.createElement('img');
|
||||
imgDom.src = img;
|
||||
// const proms = () => new Promise((resolve) => {
|
||||
// imgDom.onload = () => resolve(true);
|
||||
// });
|
||||
// await proms();
|
||||
}
|
||||
|
||||
let code = false;
|
||||
if (imgDom) {
|
||||
code = this._decodeFromImageElm(imgDom, opts);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
_decodeFromImageElm(imgObj, options = {}) {
|
||||
const opts = {
|
||||
...this.defaultOption,
|
||||
...options,
|
||||
};
|
||||
const imageData = this._createImageData(
|
||||
imgObj,
|
||||
imgObj.width,
|
||||
imgObj.height,
|
||||
);
|
||||
|
||||
const code = jsQR(
|
||||
imageData.data,
|
||||
imageData.width,
|
||||
imageData.height,
|
||||
opts,
|
||||
);
|
||||
|
||||
if (code) {
|
||||
return code;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases a video stream that was being
|
||||
* captured by prepareToVideo
|
||||
*/
|
||||
stop() {
|
||||
if (this.stream) {
|
||||
const track = this.stream.getTracks()[0];
|
||||
track.stop();
|
||||
this.stream = undefined;
|
||||
}
|
||||
|
||||
if (this.timerCapture) {
|
||||
clearTimeout(this.timerCapture);
|
||||
this.timerCapture = undefined;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the sourceId for the camera to use.
|
||||
*
|
||||
* The sourceId can be found using the
|
||||
* getVideoSources function on a browser that
|
||||
* supports it (currently only Chrome).
|
||||
*
|
||||
* @param {String} sourceId The id of the
|
||||
* video source you want to use (or false to use
|
||||
* the current default)
|
||||
*/
|
||||
setSourceId(sourceId) {
|
||||
if (sourceId) {
|
||||
this.videoConstraints.video = {
|
||||
optional: [{ sourceId }],
|
||||
};
|
||||
} else {
|
||||
this.videoConstraints.video = true;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all available video sources on
|
||||
* the current device.
|
||||
* @param {Function} cb callback to be resolved
|
||||
* with error (first param) ou results (second
|
||||
* param) - a list containing all of the sources
|
||||
* that are of the 'video' kind.
|
||||
*/
|
||||
getVideoSources() {
|
||||
const sources = [];
|
||||
|
||||
if (!(MediaStreamTrack && MediaStreamTrack.getSources)) {
|
||||
throw new Error(
|
||||
'Current browser doest not support MediaStreamTrack.getSources',
|
||||
);
|
||||
}
|
||||
|
||||
MediaStreamTrack.getSources((sourceInfos) => {
|
||||
sourceInfos.forEach((sourceInfo) => {
|
||||
if (sourceInfo.kind === 'video') {
|
||||
sources.push(sourceInfo);
|
||||
}
|
||||
});
|
||||
});
|
||||
return sources;
|
||||
}
|
||||
}
|
||||
|
||||
export default QrcodeDecoder;
|
||||
Reference in New Issue
Block a user