21 Commits
Author SHA1 Message Date
yugasun 44737ddfe7 0.2.0 2021-05-19 10:46:47 +08:00
yugasun 032f9ba16a chore: update deploy to pages 2021-05-19 10:41:53 +08:00
yugasun d1a5b9de6b chore: update demo 2021-05-19 10:40:00 +08:00
yugasun fd6daa86a1 chore: fix deps 2021-05-12 20:52:14 +08:00
yugasun b8d25e8969 docs: update readme 2021-05-12 20:29:45 +08:00
yugasun 455b0deb25 0.1.3 2021-05-12 20:26:35 +08:00
yugasun d77b666da2 fix: crossOrigin on IOS, close #14 2021-05-12 20:26:14 +08:00
yugasun 32c08b7599 chore: update package version 2021-04-21 15:39:55 +08:00
yugasun 900b451788 0.1.2 2021-04-21 15:39:27 +08:00
Lőrik LeventeandGitHub 2b19762d21 fix: typo & require (#13)
* Fix typo

* Fix require

* Remove deprecated nextjs
2021-04-21 15:38:20 +08:00
yugasunandYuga Sun d55e4ddcef feat: refactor with ts 2021-01-31 16:02:09 +08:00
dependabot[bot]andYuga Sun 2737d1970b build(deps): bump elliptic from 6.5.1 to 6.5.3
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.1 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.1...v6.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-14 21:54:47 +08:00
yugasun dfccc2f09a fix: add vconsole for mobile debug 2020-01-16 12:38:06 +08:00
yugasun 0996ab57ad fix: yarn.lock dependencies version 2019-11-20 13:05:00 +08:00
yugasun 8c3144caf2 0.1.2 2019-11-20 12:58:03 +08:00
yugasun 8f15498b05 feat: update demo 2019-01-15 12:00:12 +08:00
yugasun 70c701a9b5 fix: clear black bg after camera capture 2019-01-15 11:53:37 +08:00
yugasun 9633aafc01 feat: add aio.min build 2018-11-22 11:17:24 +08:00
yugasun 410007d2d5 feat: update rollup config and demo 2018-11-22 11:17:01 +08:00
yugasun 82f77ad9e7 feat: init project 2018-11-20 21:35:47 +08:00
Yuga SunandGitHub 28cf46138a Initial commit 2018-11-20 16:42:06 +08:00
36 changed files with 1313 additions and 167 deletions
+71
View File
@@ -0,0 +1,71 @@
{
"presets": [
[
"@babel/env",
{
"modules": false,
"targets": {
"browsers": [
">0.25%"
]
},
"useBuiltIns": "usage"
}
],
"@babel/typescript"
],
"env": {
"development": {
"plugins": [
"@babel/transform-runtime"
]
},
"production": {
"plugins": [
"@babel/transform-runtime",
"transform-remove-console"
]
},
"test": {
"presets": [
[
"@babel/env",
{
"modules": "commonjs",
"targets": {
"node": "current"
}
}
],
"@babel/typescript"
]
}
},
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-json-strings",
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions",
"@babel/plugin-proposal-export-default-from",
"@babel/plugin-proposal-logical-assignment-operators",
"@babel/plugin-proposal-optional-chaining",
[
"@babel/plugin-proposal-pipeline-operator",
{
"proposal": "minimal"
}
],
"@babel/plugin-proposal-nullish-coalescing-operator",
"@babel/plugin-proposal-do-expressions"
]
}
+7
View File
@@ -0,0 +1,7 @@
root = true
[*]
insert_final_newline = true
[*.{js,ts}]
indent_style = space
indent_size = 2
charset = utf-8
View File
+11
View File
@@ -0,0 +1,11 @@
node_modules
.DS_STORE
lerna-debug.log
yarn-error.log
dist
includes
tsconfig.tsbuildinfo
.env
coverage
yarn.lock
.rpt2_cache
+22
View File
@@ -0,0 +1,22 @@
node_modules
*.map
tests
test
__tests__
__test__
.github
.env*
jest.config.js
.eslintrc.js
.eslintignore
.prettierignore
prettier.config.js
release.config.js
commitlint.config.js
.editorconfig
lerna.json
tsconfig.tsbuildinfo
tsconfig.json
tsconfig.base.json
.rpt2_cache
.vscode
+1
View File
@@ -0,0 +1 @@
package-lock=false
+9
View File
@@ -0,0 +1,9 @@
# Development folders and files #
#################################
.tmp/
node_modules/
package.json
.travis.yml
dist
+14
View File
@@ -0,0 +1,14 @@
{
"printWidth": 80,
"singleQuote": true,
"trailingComma": "all",
"arrowParens": "always",
"overrides": [
{
"files": "*.json",
"options": {
"parser": "json"
}
}
]
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Yuga Sun
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+128
View File
@@ -0,0 +1,128 @@
# [qrcode-decoder](https://github.com/yugasun/qrcode-decoder)
[![npm](https://img.shields.io/npm/v/qrcode-decoder)](http://www.npmtrends.com/qrcode-decoder)
[![NPM downloads](http://img.shields.io/npm/dm/qrcode-decoder.svg?style=flat-square)](http://www.npmtrends.com/qrcode-decoder)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/yugasun/qrcode-decoder/blob/master/LICENSE)
[简体中文](./README.zh-CN.md) | English
A tool for decoding qrcode.
## Directory
```
.
├── demo code demo
├── dist build output
├── doc docs
├── src source code
├── test unit test
├── CHANGELOG.md change log
└── TODO.md todo list
```
## Guide
Use `npm` to install.
```bash
$ npm install --save qrcode-decoder
```
Using in webpack:
```js
import QrcodeDecoder from 'qrcode-decoder';
```
Using in browser:
```html
<script src="https://unpkg.com/qrcode-decoder"></script>
```
## Demo
### QrcodeDecoder()
User `new` to create a decoder object.
```javascript
var qr = new QrcodeDecoder();
```
#### decodeFromImage(img, options)
Decodes an image from url or an `<img>` element with a `src` attribute set.
```javascript
qr.decodeFromImage(img).then((res) => {
console.log(res);
});
```
[Demo](./demo/image.html)
#### decodeFromVideo(videoElem, options)
Decodes directly from a video with a well specified `src` attribute
```javascript
qr.decodeFromVideo(videoElement).then((res) => {
console.log(res);
});
```
[Demo](./demo/video.html)
#### decodeFromCamera(videoElem, options)
Decodes from a videoElement.
```javascript
qr.decodeFromCamera(videoElem).then((res) => {
console.log(res);
});
```
[Demo](./demo/camera.html)
#### stop()
Stops the current `qr` from searching for a QRCode.
## Develop
Install dependencies:
```bash
$ npm install
```
Build code:
```bash
$ npm run build
```
Run unit test:
```bash
$ npm test
```
Modify version in `package.json`, run `release` script:
```bash
$ npm run release
```
Publish
```bash
$ npm publish
```
## License
[MIT](./LICENSE)
+128
View File
@@ -0,0 +1,128 @@
# [qrcode-decoder](https://github.com/yugasun/qrcode-decoder)
[![npm](https://img.shields.io/npm/v/qrcode-decoder)](http://www.npmtrends.com/qrcode-decoder)
[![NPM downloads](http://img.shields.io/npm/dm/qrcode-decoder.svg?style=flat-square)](http://www.npmtrends.com/qrcode-decoder)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/yugasun/qrcode-decoder/blob/master/LICENSE)
简体中文 | [English](./README.md)
二维码解析工具。
## 目录介绍
```
.
├── demo 使用demo
├── dist 编译产出代码
├── doc 项目文档
├── src 源代码目录
├── test 单元测试
├── CHANGELOG.md 变更日志
└── TODO.md 计划功能
```
## 使用者指南
通过 npm 下载安装代码
```bash
$ npm install --save qrcode-decoder
```
如果你是 webpack 等环境
```js
import QrcodeDecoder from 'qrcode-decoder';
```
如果你是浏览器环境
```html
<script src="https://unpkg.com/qrcode-decoder"></script>
```
## 示例
### QrcodeDecoder()
通过 `new` 关键字生成处理对象。
```javascript
var qr = new QrcodeDecoder();
```
#### decodeFromImage(img, options)
解析页面中的图片二维码。
```javascript
qr.decodeFromImage(img).then((res) => {
console.log(res);
});
```
[Demo](./demo/image.html)
#### decodeFromVideo(videoElem, options)
解析页面中的视频中的二维码。
```javascript
qr.decodeFromVideo(videoElement).then((res) => {
console.log(res);
});
```
[Demo](./demo/video.html)
#### decodeFromCamera(videoElem, options)
通过获取摄像头视频来扫描解析二维码。
```javascript
qr.decodeFromCamera(videoElem).then((res) => {
console.log(res);
});
```
[Demo](./demo/camera.html)
#### stop()
停止当前视频捕获。
## 开发
首次运行需要先安装依赖
```bash
$ npm install
```
一键打包生成生产代码
```bash
$ npm run build
```
运行单元测试,浏览器环境需要手动测试,位于`test/browser`
```bash
$ npm test
```
修改 package.json 中的版本号,修改 README.md 中的版本号,修改 CHANGELOG.md,然后发布新版
```bash
$ npm run release
```
将新版本发布到 npm
```bash
$ npm publish
```
## License
[MIT](./LICENSE)
+5
View File
@@ -0,0 +1,5 @@
# TODO
Rewrite unit test.
- [] Rewrite unit test.
-54
View File
@@ -1,54 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>QrcodeDecoder - Camera</title>
</head>
<body>
<button id="start">Start</button> <button id="stop">Stop</button><br />
<span id="result">Click start to scan qrcode.</span><br />
<hr />
<video id="video" autoplay></video>
<script src="./lib/vconsole.min.js"></script>
<script src="./lib/index.min.js"></script>
<script type="text/javascript">
var vConsole = new VConsole();
console.log('Hello world');
function main() {
var qr = new QrcodeDecoder.default();
var video = document.querySelector('#video');
var start = document.querySelector('#start');
var stop = document.querySelector('#stop');
var result = document.querySelector('#result');
async function startScan() {
if (!qr.isCanvasSupported()) {
alert("Your browser doesn't match the required specs.");
throw new Error('Canvas and getUserMedia are required');
}
let code = await qr.decodeFromCamera(video,
// you can customize your camera size like below
// {
// width: 400,
// height: 400,
// }
);
console.log('code', code);
result.innerText = 'Result: ' + code.data;
}
start.onclick = startScan;
stop.onclick = function () {
qr.stop();
};
}
main();
</script>
</body>
</html>

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

+45
View File
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>QrcodeDecoder - Camera</title>
</head>
<body>
<button id="start">Start</button> <button id="stop">Stop</button><br />
<span id="result">Click start to scan qrcode.</span><br />
<hr />
<video id="video" autoplay></video>
<script src="./lib/vconsole.min.js"></script>
<script src="./index.min.js"></script>
<script type="text/javascript">
var vConsole = new VConsole();
console.log('Hello world');
function main() {
var qr = new QrcodeDecoder();
var video = document.querySelector('#video');
var start = document.querySelector('#start');
var stop = document.querySelector('#stop');
var result = document.querySelector('#result');
async function startScan() {
if (!qr.isCanvasSupported()) {
alert("Your browser doesn't match the required specs.");
throw new Error('Canvas and getUserMedia are required');
}
let code = await qr.decodeFromCamera(video);
console.log('code', code);
result.innerText = 'Result: ' + code.data;
}
start.onclick = startScan;
stop.onclick = function() {
qr.stop();
};
}
main();
</script>
</body>
</html>
+64
View File
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>QrcodeDecoder - Image</title>
</head>
<body>
<section>
<h3>Same domain image</h3>
<img id="img1" src="./assets/qrcode.png" alt="qr code" /><br />
<button id="decode1">Decode!</button><br />
<span id="result1"></span><br />
</section>
<hr />
<section>
<h3>Different domain image</h3>
<input
id="img2"
value="https://yugasun.com/static/wechat.jpg"
style="width: 400px"
/><br />
<button id="decode2">Decode!</button><br />
<span id="result2"></span><br />
</section>
<script src="./lib/vconsole.min.js"></script>
<script src="./index.min.js"></script>
<script type="module">
var vConsole = new VConsole();
function main() {
var qr = new QrcodeDecoder();
var btn1 = document.querySelector('button#decode1');
var btn2 = document.querySelector('button#decode2');
var result1 = document.querySelector('#result1');
var result2 = document.querySelector('#result2');
var img1 = document.querySelector('#img1');
var img2 = document.querySelector('#img2');
btn1.onclick = async () => {
// you can also decode from image path
// const code = await qr.decodeFromImage('./assets/qrcode.png');
const code = await qr.decodeFromImage(img1);
console.log(code);
result1.innerText = code.data;
};
btn2.onclick = async () => {
// you can also decode from image path
// const code = await qr.decodeFromImage('./assets/qrcode.png');
const code = await qr.decodeFromImage(img2.value, {
crossOrigin: 'anonymous',
});
console.log(code);
result2.innerText = code.data;
};
}
window.onload = () => {
main();
};
</script>
</body>
</html>
View File
+1
View File
File diff suppressed because one or more lines are too long
View File
+43
View File
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>QrcodeDecoder - Video</title>
</head>
<body>
<button id="start">Start</button> <button id="stop">Stop</button><br />
<span id="result">Click start to scan qrcode.</span><br />
<video src="./assets/qrcode-video.mp4"></video>
<script src="./lib/vconsole.min.js"></script>
<script src="./index.min.js"></script>
<script type="text/javascript">
var vConsole = new VConsole();
console.log('Hello world');
function main() {
var video = document.querySelector('video');
var result = document.querySelector('#result');
var start = document.querySelector('#start');
var stop = document.querySelector('#stop');
var qr = new QrcodeDecoder();
start.onclick = startScan;
stop.onclick = function() {
qr.stop();
video.pause();
};
async function startScan() {
video.play();
const code = await qr.decodeFromVideo(video);
console.log('code', code);
result.innerText = 'Result: ' + code.data;
}
}
main();
</script>
</body>
</html>
+49
View File
@@ -0,0 +1,49 @@
# Api
### QrcodeDecoder()
User `new` to create a decoder object.
```javascript
var qr = new QrcodeDecoder();
```
#### decodeFromImage(img, options)
Decodes an image from url or an `<img>` element with a `src` attribute set.
```javascript
qr.decodeFromImage(img).then((res) => {
console.log(res);
});
```
[Demo](./demo/image.html)
#### decodeFromVideo(videoElem, options)
Decodes directly from a video with a well specified `src` attribute
```javascript
qr.decodeFromVideo(videoElement).then((res) => {
console.log(res);
});
```
[Demo](./demo/video.html)
#### decodeFromCamera(videoElem, options)
Decodes from a videoElement.
```javascript
qr.decodeFromCamera(videoElem).then((res) => {
console.log(res);
});
```
[Demo](./demo/camera.html)
#### stop()
Stops the current `qr` from searching for a QRCode.
+49
View File
@@ -0,0 +1,49 @@
# Api
### QrcodeDecoder()
通过 `new` 关键字生成处理对象。
```javascript
var qr = new QrcodeDecoder();
```
#### decodeFromImage(img, options)
解析页面中的图片二维码。
```javascript
qr.decodeFromImage(img).then((res) => {
console.log(res);
});
```
[Demo](./demo/image.html)
#### decodeFromVideo(videoElem, options)
解析页面中的视频中的二维码。
```javascript
qr.decodeFromVideo(videoElement).then((res) => {
console.log(res);
});
```
[Demo](./demo/video.html)
#### decodeFromCamera(videoElem, options)
通过获取摄像头视频来扫描解析二维码。
```javascript
qr.decodeFromCamera(videoElem).then((res) => {
console.log(res);
});
```
[Demo](./demo/camera.html)
#### stop()
停止当前视频捕获。
-66
View File
@@ -1,66 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>QrcodeDecoder - Image</title>
</head>
<body>
<section>
<h3>Same domain image</h3>
<img id="img1" src="./assets/qrcode.png" alt="qr code" /><br />
<button id="decode1">Decode!</button><br />
<span id="result1"></span><br />
</section>
<hr />
<section>
<h3>Different domain image</h3>
<input
id="img2"
value="https://yugasun.com/static/wechat.jpg"
style="width: 400px" /><br />
<button id="decode2">Decode!</button><br />
<span id="result2"></span><br />
</section>
<script src="./lib/vconsole.min.js"></script>
<script src="./lib/index.min.js"></script>
<script type="module">
var vConsole = new VConsole();
function main() {
var qr = new QrcodeDecoder.default();
var btn1 = document.querySelector('button#decode1');
var btn2 = document.querySelector('button#decode2');
var result1 = document.querySelector('#result1');
var result2 = document.querySelector('#result2');
var img1 = document.querySelector('#img1');
var img2 = document.querySelector('#img2');
btn1.onclick = async () => {
// you can also decode from image path
// const code = await qr.decodeFromImage('./assets/qrcode.png');
const code = await qr.decodeFromImage(img1);
console.log(code);
result1.innerText = code.data;
};
btn2.onclick = async () => {
// you can also decode from image path
// const code = await qr.decodeFromImage('./assets/qrcode.png');
const code = await qr.decodeFromImage(img2.value, {
crossOrigin: 'anonymous',
});
console.log(code);
result2.innerText = code.data;
};
}
window.onload = () => {
main();
};
</script>
</body>
</html>
+10
View File
@@ -0,0 +1,10 @@
module.exports = {
verbose: true,
testTimeout: 60000,
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
testRegex: '(/__tests__/.*\\.(test|spec))\\.tsx?$',
testPathIgnorePatterns: ['/node_modules/', '/dist/', '/demo/'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};
-2
View File
File diff suppressed because one or more lines are too long
+92
View File
@@ -0,0 +1,92 @@
{
"name": "qrcode-decoder",
"version": "0.2.0",
"description": "Tool for decoding qrcode",
"main": "dist/index.js",
"sideEffects": false,
"scripts": {
"build": "ts-node -P scripts/tsconfig.json scripts/bundle.ts umd,esm,aio && npm run copy-file",
"prettier": "prettier --check '**/*.{ts,tsx,md}' --config .prettierrc",
"prettier:fix": "prettier --write '**/*.{ts,tsx,md}' --config .prettierrc",
"test": "jest",
"codecov": "jest --coverage && codecov",
"copy-file": "cp dist/index.min.js demo/",
"release": "git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags",
"pages": "gh-pages -d demo --remote origin"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{ts,tsx,md}": [
"npm run prettier:fix",
"git add ."
]
},
"devDependencies": {
"@babel/cli": "^7.7.0",
"@babel/core": "^7.7.2",
"@babel/plugin-proposal-class-properties": "^7.7.0",
"@babel/plugin-proposal-decorators": "^7.7.0",
"@babel/plugin-proposal-do-expressions": "^7.6.0",
"@babel/plugin-proposal-export-default-from": "^7.5.2",
"@babel/plugin-proposal-export-namespace-from": "^7.5.2",
"@babel/plugin-proposal-function-sent": "^7.7.0",
"@babel/plugin-proposal-json-strings": "^7.2.0",
"@babel/plugin-proposal-logical-assignment-operators": "^7.2.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4",
"@babel/plugin-proposal-numeric-separator": "^7.2.0",
"@babel/plugin-proposal-optional-chaining": "^7.6.0",
"@babel/plugin-proposal-pipeline-operator": "^7.5.0",
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.6.2",
"@babel/polyfill": "^7.7.0",
"@babel/preset-env": "^7.7.1",
"@babel/preset-typescript": "^7.7.2",
"@babel/runtime": "^7.7.2",
"@types/jest": "^24.9.0",
"@types/node": "^13.1.8",
"chalk": "^3.0.0",
"codecov": "^3.8.1",
"cross-env": "^6.0.3",
"fancy-log": "^1.3.3",
"gh-pages": "^3.1.0",
"husky": "^3.0.9",
"jest": "^24.9.0",
"lint-staged": "^9.5.0",
"prettier": "^1.18.2",
"rollup": "^2.38.1",
"rollup-plugin-commonjs": "^8.3.0",
"rollup-plugin-json": "^3.1.0",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-node-resolve": "^3.4.0",
"rollup-plugin-typescript2": "0.17.1",
"rollup-plugin-uglify": "^6.0.4",
"ts-jest": "^24.3.0",
"ts-node": "^8.4.1",
"tslint": "^5.20.1",
"tslint-config-prettier": "^1.18.0",
"typescript": "^3.7.5"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/yugasun/qrcode-decoder.git"
},
"keywords": [
"node-utils"
],
"author": "yugasun",
"bugs": {
"url": "https://github.com/yugasun/qrcode-decoder/issues"
},
"homepage": "https://github.com/yugasun/qrcode-decoder#readme",
"dependencies": {
"jsqr": "^1.4.0"
}
}
+127
View File
@@ -0,0 +1,127 @@
import * as path from 'path';
import * as rollup from 'rollup';
import resolve from 'rollup-plugin-node-resolve';
import typescript2 from 'rollup-plugin-typescript2';
import { uglify } from 'rollup-plugin-uglify';
import globals from 'rollup-plugin-node-globals';
import commonjs from 'rollup-plugin-commonjs';
import ts from 'typescript';
import { createLogger } from './logger';
const logBundle = createLogger('bundle');
const plugins: rollup.Plugin[] = [
resolve({
browser: true,
jsnext: true,
preferBuiltins: true,
}),
commonjs(),
typescript2({
tsconfig: path.join(__dirname, '..', 'tsconfig.json'),
typescript: ts, // ensure we're using the same typescript (3.x) for rollup as for regular builds etc
tsconfigOverride: {
module: 'esnext',
stripInternal: true,
emitDeclarationOnly: false,
composite: false,
declaration: false,
declarationMap: false,
sourceMap: false,
},
}),
];
async function bundleEsm() {
const bundle = await rollup.rollup({
input: path.join(process.cwd(), 'src/index.ts'),
plugins: [...plugins, globals()],
external: ['jsqr'],
});
await bundle.write({
file: 'dist/index.esm.js',
name: 'QrcodeDecoder',
format: 'esm',
sourcemap: false,
globals: {
jsqr: 'jsqr',
tslib: 'tslib',
},
});
}
async function bundleUmd() {
const bundle = await rollup.rollup({
input: path.join(process.cwd(), 'src/index.ts'),
plugins: [...plugins],
external: ['jsqr'],
});
await bundle.write({
file: 'dist/index.js',
exports: 'named',
name: 'QrcodeDecoder',
format: 'umd',
sourcemap: false,
globals: {
jsqr: 'jsqr',
tslib: 'tslib',
},
});
}
async function bundleAio() {
const bundle = await rollup.rollup({
input: path.join(process.cwd(), 'src/index.ts'),
plugins: [
...plugins,
uglify({
compress: {
drop_debugger: true,
drop_console: true,
},
}),
],
});
await bundle.write({
file: 'dist/index.min.js',
name: 'QrcodeDecoder',
format: 'iife',
sourcemap: false,
});
}
async function bundle() {
try {
const outputs = process.argv.slice(2)[0].split(',');
logBundle(`Creating bundle`);
if (outputs.indexOf('esm') === -1) {
logBundle(`Skipping esm`);
} else {
logBundle(`Creating esm`);
await bundleEsm();
}
if (outputs.indexOf('umd') === -1) {
logBundle(`Skipping umd`);
} else {
logBundle(`Creating umd`);
await bundleUmd();
}
if (outputs.indexOf('aio') === -1) {
logBundle(`Skipping umd aio`);
} else {
logBundle(`Creating umd aio`);
await bundleAio();
}
} catch (err) {
logBundle('Failed to bundle:');
logBundle(err);
process.exit(1);
}
}
bundle();
+16
View File
@@ -0,0 +1,16 @@
import * as l from 'fancy-log';
const log = <typeof import('fancy-log')>(<any>(<any>l).default || l);
import * as c from 'chalk';
const chalk = <import('chalk').Chalk>(c.default || c);
export function createLogger(name: string): typeof log {
const prefix = `> ${chalk.green(name)} `;
const logger = <typeof log>log.bind(log, prefix);
logger.info = log.info.bind(log, prefix);
logger.dir = log.dir.bind(log, prefix);
logger.warn = log.warn.bind(log, prefix);
logger.error = log.error.bind(log, prefix);
return logger;
}
export { chalk as c };
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"esModuleInterop": true,
"module": "commonjs",
"target": "es2015",
"resolveJsonModule": true
}
}
+320
View File
@@ -0,0 +1,320 @@
import { Options } from 'jsqr';
import jsQR from 'jsqr';
class QrcodeDecoder {
timerCapture: null | NodeJS.Timeout;
canvasElem: null | HTMLCanvasElement;
gCtx: null | CanvasRenderingContext2D;
stream: null | MediaStream;
videoElem: null | HTMLVideoElement;
getUserMediaHandler: null;
videoConstraints: MediaStreamConstraints;
defaultOption: Options;
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.
*/
isCanvasSupported() {
const elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}
_createImageData(target: CanvasImageSource, width: number, height: number) {
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: number, height: number) {
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: HTMLVideoElement, options: 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: HTMLVideoElement, 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;
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: HTMLVideoElement, 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: HTMLImageElement,
options: { crossOrigin?: string } = {},
) {
let imgDom: HTMLImageElement | null = null;
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');
if (options.crossOrigin) {
imgDom.crossOrigin = options.crossOrigin;
}
imgDom.src = img;
const proms = () =>
new Promise((resolve) => {
imgDom!.onload = () => resolve(true);
});
await proms();
}
let code = null;
if (imgDom) {
code = this._decodeFromImageElm(imgDom, opts);
}
return code;
}
_decodeFromImageElm(imgObj: HTMLImageElement, options = {}) {
const opts: Options = {
...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 = null;
// fix: clear black bg after camera capture
this.videoElem!.srcObject = null;
}
if (this.timerCapture) {
clearTimeout(this.timerCapture);
this.timerCapture = null;
}
return this;
}
/**
* Sets the sourceId for the camera to use.
*/
setGroupId(groupId: string) {
if (groupId) {
this.videoConstraints.video = {
advanced: [{ groupId }],
};
} else {
this.videoConstraints.video = true;
}
return this;
}
/**
* Gets a list of all available video sources on
* the current device.
*/
async getVideoDevices() {
if (navigator.mediaDevices.enumerateDevices) {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter((item: MediaDeviceInfo) => {
if (item.kind === 'videoinput') {
return true;
}
return false;
});
} else {
throw new Error(
'Current browser doest not support MediaStreamTrack.getSources',
);
}
}
}
export default QrcodeDecoder;
+24
View File
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"baseUrl": ".",
"downlevelIteration": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"preserveConstEnums": true,
"importHelpers": false,
"target": "es5",
"module": "commonjs",
"lib": ["es5", "es6", "dom"],
"moduleResolution": "node",
"sourceMap": false,
"paths": {
"*": ["typings/*", "includes/*"]
},
"resolveJsonModule": true,
"noUnusedLocals": true,
"strict": true
}
}
+25
View File
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"baseUrl": ".",
"composite": true,
"declaration": true,
"declarationMap": true,
"downlevelIteration": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"preserveConstEnums": true,
"importHelpers": false,
"lib": ["esnext", "dom"],
"module": "ESNext",
"moduleResolution": "node",
"sourceMap": false,
"paths": {
"*": ["typings/*", "includes/*"]
},
"resolveJsonModule": true,
"noUnusedLocals": true,
"strict": true,
"target": "es5"
}
}
+23
View File
@@ -0,0 +1,23 @@
{
"extends": ["tslint:latest", "tslint-config-prettier"],
"rules": {
"interface-name": [true, "never-prefix"],
"member-access": false,
"no-angle-bracket-type-assertion": false,
"no-bitwise": false,
"no-console": false,
"no-default-export": true,
"no-empty-interface": false,
"no-implicit-dependencies": false,
"no-submodule-imports": false,
"ordered-imports": [false],
"object-literal-sort-keys": false,
"object-literal-key-quotes": [true, "as-needed"],
"quotemark": [true, "single"],
"semicolon": [true, "always", "ignore-bound-class-methods"],
"jsx-boolean-value": false
},
"linterOptions": {
"exclude": ["config/**/*.js", "node_modules/**/*.ts"]
}
}
-45
View File
@@ -1,45 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>QrcodeDecoder - Video</title>
</head>
<body>
<button id="start">Start</button> <button id="stop">Stop</button><br />
<span id="result">Click start to scan qrcode.</span><br />
<video src="./assets/qrcode-video.mp4"></video>
<script src="./lib/vconsole.min.js"></script>
<script src="./lib/index.min.js"></script>
<script type="text/javascript">
var vConsole = new VConsole();
function main() {
var video = document.querySelector('video');
var result = document.querySelector('#result');
var start = document.querySelector('#start');
var stop = document.querySelector('#stop');
var qr = new QrcodeDecoder.default();
start.onclick = startScan;
stop.onclick = function () {
qr.stop();
video.pause();
};
async function startScan() {
video.play();
const code = await qr.decodeFromVideo(video);
console.log('code', code);
result.innerText = 'Result: ' + code.data;
}
}
main();
</script>
</body>
</html>