diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..557bb55 --- /dev/null +++ b/.babelrc @@ -0,0 +1,22 @@ +{ + "presets": [ + ["env", + { + "targets": { + "browsers": "last 2 versions, > 1%, ie >= 6, Android >= 4, iOS >= 6, and_uc > 9", + "node": "0.10" + }, + "modules": false, + "loose": false + }], + "stage-2" + ], + "plugins": [ + ["transform-runtime", { + "helpers": false, + "polyfill": false, + "regenerator": false, + "moduleName": "babel-runtime" + }] + ] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..25b6d64 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[{*.js,*.css,*.html}] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +insert_final_newline = true + +[{package.json,.*rc,*.yml}] +indent_style = space +indent_size = 2 diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..30d74d2 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..a7b704a --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,45 @@ +module.exports = { + root: true, + + parserOptions: { + sourceType: 'module', + }, + + env: { + browser: true, + es6: true, + node: true, + }, + + extends: ['airbnb-base'], + + rules: { + 'no-shadow': [ + 'error', + { + allow: ['state'], + }, + ], + // 'import/extensions': 'off', + 'import/extensions': [ + 'error', + 'always', + { + js: 'never', + }, + ], + 'import/no-unresolved': 'off', + 'no-param-reassign': 'off', + 'consistent-return': 'off', + 'global-require': 'off', + 'import/no-dynamic-require': 'off', + 'import/no-extraneous-dependencies': 'off', + + // 4 行空格缩进 + indent: ['error', 4, { SwitchCase: 1 }], + + 'max-len': ['error', { code: 150 }], + + 'operator-linebreak': 0, + }, +}; diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6acc570 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.DS_Store \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..6c4479a --- /dev/null +++ b/.npmignore @@ -0,0 +1,11 @@ +node_modules +config +demo +src +test +.babelrc +.editorconfig +.travis.yml +CHANGELOG.md +package-lock.json +TODO.md diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..efb0983 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - "8" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..420e6f2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Change Log diff --git a/README.md b/README.md new file mode 100644 index 0000000..431a2b0 --- /dev/null +++ b/README.md @@ -0,0 +1,128 @@ +# [qrcode-decoder](https://github.com/yugasun/qrcode-decoder) + +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/yugasun/qrcode-decoder/blob/master/LICENSE) +[![Build Status](https://travis-ci.org/yugasun/qrcode-decoder.svg?branch=master)](https://travis-ci.org/yugasun/qrcode-decoder) +[![NPM downloads](http://img.shields.io/npm/dm/qrcode-decoder.svg?style=flat-square)](http://www.npmtrends.com/qrcode-decoder) + +[简体中文](./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 + +``` + +## Demo + +### QrcodeDecoder() + +User `new` to create a decoder object. + +```javascript +var qr = new QrcodeDecoder(); +``` + +#### decodeFromImage(img, options) + +Decodes an image from url or an `` 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) diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..786de35 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,128 @@ +# [qrcode-decoder](https://github.com/yugasun/qrcode-decoder) + +[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/yugasun/qrcode-decoder/blob/master/LICENSE) +[![Build Status](https://travis-ci.org/yugasun/qrcode-decoder.svg?branch=master)](https://travis-ci.org/yugasun/qrcode-decoder) +[![NPM downloads](http://img.shields.io/npm/dm/qrcode-decoder.svg?style=flat-square)](http://www.npmtrends.com/qrcode-decoder) + +简体中文 | [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 + +``` + +## 示例 + +### 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) diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..5a0521d --- /dev/null +++ b/TODO.md @@ -0,0 +1,5 @@ +# TODO + +Rewrite unit test. + +- [] Rewrite unit test. diff --git a/config/rollup.config.aio.js b/config/rollup.config.aio.js new file mode 100644 index 0000000..14b9e63 --- /dev/null +++ b/config/rollup.config.aio.js @@ -0,0 +1,32 @@ +// rollup.config.js + +var babel = require('rollup-plugin-babel'); +var nodeResolve = require('rollup-plugin-node-resolve'); +var commonjs = require('rollup-plugin-commonjs'); + +var common = require('./rollup.js'); + +export default { + input: 'src/index.js', + output: { + file: 'dist/index.aio.js', + format: 'umd', + // 如果不同时使用 export 与 export default 可打开legacy + // legacy: true, + // name: common.name, + name: 'QrcodeDecoder', + banner: common.banner, + }, + plugins: [ + nodeResolve({ + main: true, + }), + commonjs({ + include: 'node_modules/**', + }), + babel({ + runtimeHelpers: true, + exclude: 'node_modules/**', + }), + ], +}; diff --git a/config/rollup.config.esm.js b/config/rollup.config.esm.js new file mode 100644 index 0000000..c2d211c --- /dev/null +++ b/config/rollup.config.esm.js @@ -0,0 +1,7 @@ +import config from './rollup.config'; + +// ES output +config.output.format = 'es'; +config.output.file = 'dist/index.esm.js'; + +export default config; diff --git a/config/rollup.config.js b/config/rollup.config.js new file mode 100644 index 0000000..ea3f1a9 --- /dev/null +++ b/config/rollup.config.js @@ -0,0 +1,21 @@ +// rollup.config.js + +var babel = require('rollup-plugin-babel'); +var common = require('./rollup.js'); + +export default { + input: 'src/index.js', + output: { + file: 'dist/index.js', + format: 'cjs', + // 如果不同时使用 export 与 export default 可打开legacy + // legacy: true, + banner: common.banner, + }, + plugins: [ + babel({ + runtimeHelpers: true, + exclude: 'node_modules/**' + }) + ] +}; diff --git a/config/rollup.js b/config/rollup.js new file mode 100644 index 0000000..78a7373 --- /dev/null +++ b/config/rollup.js @@ -0,0 +1,17 @@ +var pkg = require('../package.json'); + +// 兼容 qrcode-decoder 和 @yugasun/qrcode-decoder +var name = pkg.name.split('/').pop(); +var version = pkg.version; + +var banner = +`/*! + * qrcode-decoder ${version} (https://github.com/yugasun/qrcode-decoder) + * API https://github.com/yugasun/qrcode-decoder/blob/master/doc/api.md + * Copyright 2017-${(new Date).getFullYear()} yugasun. All Rights Reserved + * Licensed under MIT (https://github.com/yugasun/qrcode-decoder/blob/master/LICENSE) + */ +`; + +exports.name = name; +exports.banner = banner; diff --git a/demo/camera.html b/demo/camera.html new file mode 100644 index 0000000..97f38cb --- /dev/null +++ b/demo/camera.html @@ -0,0 +1,39 @@ + + + + QrcodeDecoder - Camera + + +
+ Click start to scan qrcode.
+
+ + + + + + diff --git a/demo/image.html b/demo/image.html new file mode 100644 index 0000000..89f7e5c --- /dev/null +++ b/demo/image.html @@ -0,0 +1,31 @@ + + + + + QrcodeDecoder - Image + + +
+
+ + + + + diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..1a731af --- /dev/null +++ b/demo/index.html @@ -0,0 +1,18 @@ + + + + + QrcodeDecoder + + + +

QrcodeDecoder

+

Examples

+ + + + diff --git a/demo/video.html b/demo/video.html new file mode 100644 index 0000000..b7e3ca6 --- /dev/null +++ b/demo/video.html @@ -0,0 +1,39 @@ + + + + + QrcodeDecoder - Video + + +
+ Click start to scan qrcode.
+ + + + + + + diff --git a/doc/api.md b/doc/api.md new file mode 100644 index 0000000..c8d053a --- /dev/null +++ b/doc/api.md @@ -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 `` 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. \ No newline at end of file diff --git a/doc/api.zh-CN.md b/doc/api.zh-CN.md new file mode 100644 index 0000000..9ade3ca --- /dev/null +++ b/doc/api.zh-CN.md @@ -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() + +停止当前视频捕获。 \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..8dd1e39 --- /dev/null +++ b/package.json @@ -0,0 +1,54 @@ +{ + "name": "qrcode-decoder", + "version": "1.1.0", + "description": "Tool for decoding qrcode", + "main": "dist/index.js", + "jsnext:main": "dist/index.esm.js", + "module": "dist/index.esm.js", + "sideEffects": false, + "scripts": { + "clean": "rimraf ./dist", + "lint": "eslint src", + "build:self": "rollup -c config/rollup.config.js", + "build:esm": "rollup -c config/rollup.config.esm.js", + "build:aio": "rollup -c config/rollup.config.aio.js", + "build": "npm run clean && npm run build:self && npm run build:esm && npm run build:aio", + "test": "npm run lint && npm run build", + "zuul-test": "zuul --local 8080 --ui mocha-bdd -- test/test.js", + "release": "git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags" + }, + "author": "yugasun", + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/yugasun/qrcode-decoder.git" + }, + "bugs": { + "url": "https://github.com/yugasun/qrcode-decoder/issues" + }, + "devDependencies": { + "babel-core": "6.26.0", + "babel-plugin-transform-runtime": "6.23.0", + "babel-preset-env": "1.6.1", + "babel-preset-stage-2": "^6.24.1", + "cdkit": "1.1.0", + "es5-shim": "4.5.10", + "eslint": "4.18.2", + "eslint-config-airbnb-base": "^13.1.0", + "eslint-plugin-import": "^2.14.0", + "expect.js": "0.3.1", + "jsdom": "^13.0.0", + "mocha": "3.5.3", + "mocha-jsdom": "^2.0.0", + "rimraf": "2.6.2", + "rollup": "0.57.1", + "rollup-plugin-babel": "3.0.3", + "rollup-plugin-commonjs": "8.3.0", + "rollup-plugin-node-resolve": "3.0.3", + "zuul": "^3.12.0" + }, + "dependencies": { + "babel-polyfill": "^6.26.0", + "jsqr": "^1.1.1" + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..2b0d11e --- /dev/null +++ b/src/index.js @@ -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