mirror of
https://github.com/cnwhy/QRCode-decode.git
synced 2026-08-12 20:31:40 +08:00
add build
This commit is contained in:
+60
@@ -0,0 +1,60 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
.cache
|
||||
@@ -1,44 +1,50 @@
|
||||
识别 QR码 图片
|
||||
> 整理自 [jsqrcode](https://github.com/LazarSoft/jsqrcode)
|
||||
# qrcode-decode
|
||||
时要的二维码识别代码整理自 [jsqrcode](https://github.com/LazarSoft/jsqrcode);
|
||||
|
||||
**修改点:**
|
||||
1. 模块化
|
||||
2. 剥离识别函数,改成适合服务端使用
|
||||
3. 需要用其它方法把图片转为 `ImageData` 提供给解码函数
|
||||
2. 剥离识别函数,改成适合服务端使用,但入参数还是保留为实现[ImageData](https://developer.mozilla.org/zh-CN/docs/Web/API/ImageData)接口的对像
|
||||
|
||||
**demo web**
|
||||
```html
|
||||
<input id="file" type="file">
|
||||
```
|
||||
**关于 `ImageData`**
|
||||
> `Canvas` 中可以用 `ctx.getImageData` 方法得到;
|
||||
|
||||
js:
|
||||
如果你不想亲自把图片转为 `ImageData`, 根据你你的项目, 请使用这两个JS:
|
||||
- `browser.js` 浏览器项目 有两个API `decodeByUrl`, `decodeByDom`
|
||||
- `server.js` 服务端项目 提供两个API `decodeByPath`, `decodeByBuffer`
|
||||
> 服务端当前支持 `bmp` , `jpg` , `png` , `gif` 格式;
|
||||
|
||||
## demo
|
||||
### web
|
||||
> web端最终是利用`Canvas`获取`ImageData`, 注意兼容及跨域问题
|
||||
```js
|
||||
var qrcodeDecode = require('qrcode-decode');
|
||||
|
||||
document.getElementById('file').onchange = function (event) {
|
||||
var file = event.target.files[0];
|
||||
new Promise(function (ok, no) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = evt => {
|
||||
ok(evt.target.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}).then((src)=>{
|
||||
return new Promise(function(ok){
|
||||
var img = new Image();
|
||||
img.src = src;
|
||||
img.onload = function(){
|
||||
var canvas = document.createElement("canvas");
|
||||
var ctx = canvas.getContext('2d');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
ctx.drawImage(img,0,0,canvas.width,canvas.height)
|
||||
var imageData = ctx.getImageData(0,0,canvas.width,canvas.height)
|
||||
ok(imageData);
|
||||
}
|
||||
var qrcodeDecode = require('qrcode-decode/browser');
|
||||
// 传入二维码图片URL/dataURL
|
||||
qrcodeDecode.decodeByUrl(src, function (err, txt) {
|
||||
if (err) { return console.log(err);}
|
||||
alert(txt);
|
||||
})
|
||||
|
||||
// 传入DOM可以画到canvas的dom都可以 `img` `canvas` 'video' 等
|
||||
var img = document.getElementById('img1');
|
||||
qrcodeDecode.decodeByDom(img, function (err, txt) {
|
||||
if (err) { return console.log(err);}
|
||||
alert(txt);
|
||||
})
|
||||
}).then(data => {
|
||||
return qrcodeDecode(data);
|
||||
}).then(alert);
|
||||
}
|
||||
```
|
||||
|
||||
### nodejs
|
||||
> 注意: 服务器端API是以 `promise` 返回结果,你注意你的`node`版本;
|
||||
```js
|
||||
//解析文件
|
||||
var qrcodeDecode = require('qrcode-decode/server');
|
||||
qrcodeDecode.decodeByPath('xx/code.jpg').then(function(val){
|
||||
console.log(val);
|
||||
},console.error.bind(console))
|
||||
|
||||
//解析Buffer
|
||||
fs.readFile(path, function (err, buffer) {
|
||||
if (err) { return rej(err) }
|
||||
resqrcodeDecode.decodeByBuffer(buffer);
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
+15
-9
@@ -1,25 +1,31 @@
|
||||
var qrDecode = require('./')
|
||||
|
||||
var decode =
|
||||
exports.decode = function (bom) {
|
||||
var decode = require('./src/QRCodeDecode');
|
||||
var decodeByDom = function (dom) {
|
||||
var canvas = document.createElement("canvas")
|
||||
var ctx = canvas.getContext('2d')
|
||||
canvas.width = bom.width;
|
||||
canvas.height = bom.height;
|
||||
ctx.drawImage(bom, 0, 0, canvas.width, canvas.height);
|
||||
canvas.width = dom.width;
|
||||
canvas.height = dom.height;
|
||||
ctx.drawImage(dom, 0, 0, canvas.width, canvas.height);
|
||||
var data = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
return qrDecode(data)
|
||||
return decode(data)
|
||||
}
|
||||
|
||||
exports.decodeByUrl = function (src, cb) {
|
||||
var decodeByUrl = (src, cb) => {
|
||||
var img = new Image();
|
||||
img.crossOrigin="anonymous";
|
||||
img.src = src;
|
||||
img.onload = function () {
|
||||
try {
|
||||
cb(null,decode(img));
|
||||
cb(null, decodeByDom(img));
|
||||
} catch (e) {
|
||||
cb(e);
|
||||
}
|
||||
}
|
||||
img.onerror = cb;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
decodeByImageDate: decode,
|
||||
decodeByDom: decodeByDom,
|
||||
decodeByUrl: decodeByUrl
|
||||
}
|
||||
+4
-9
@@ -1,23 +1,20 @@
|
||||
var qrcodeDecode = require('../browser')
|
||||
console.log(1111)
|
||||
|
||||
document.getElementById('file').onchange = function (event) {
|
||||
var el = event.target;
|
||||
if (!el.files.length) return;
|
||||
// console.log(el.files[0]);
|
||||
var file = el.files[0];
|
||||
new Promise(function (ok, no) {
|
||||
/* if(window.URL && window.URL.createObjectURL){ //缓存预览
|
||||
if (window.URL && window.URL.createObjectURL) {
|
||||
ok(window.URL.createObjectURL(file));
|
||||
}else */
|
||||
if (typeof FileReader) {
|
||||
//urldata预览
|
||||
} else if (typeof FileReader) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = evt => {
|
||||
ok(evt.target.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
ok();
|
||||
no('浏览器不支持');
|
||||
}
|
||||
}).then((src) => {
|
||||
qrcodeDecode.decodeByUrl(src, function (err, txt) {
|
||||
@@ -32,6 +29,4 @@ document.getElementById('file').onchange = function (event) {
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
//el.outerHTML = el.outerHTML;
|
||||
}
|
||||
|
||||
Vendored
+3048
File diff suppressed because one or more lines are too long
Vendored
+7
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
+12
-6
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "qrcode-decode",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"description": "QRCode parser/decode",
|
||||
"main": "src/QRCodeDecode.js",
|
||||
"scripts": {
|
||||
"test": "npm run demo",
|
||||
"demo": "parcel ./demo/index.html"
|
||||
"test": "node test/node.js",
|
||||
"demo": "parcel ./demo/index.html",
|
||||
"build": "bili browser.js --format umd,umd-min --banner --exports named"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -13,15 +14,20 @@
|
||||
},
|
||||
"keywords": [
|
||||
"qrcode",
|
||||
"decode"
|
||||
"decode",
|
||||
"parser"
|
||||
],
|
||||
"author": "cnwhy",
|
||||
"author": {
|
||||
"name": "cnwhy",
|
||||
"email": "w.why@163.com"
|
||||
},
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/cnwhy/QRCode-decode/issues"
|
||||
},
|
||||
"homepage": "https://github.com/cnwhy/QRCode-decode#readme",
|
||||
"devDependencies": {
|
||||
"bili": "^3.1.2",
|
||||
"parcel": "^1.9.7"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
var fs = require('fs')
|
||||
var imgDecode = require('./src/imageDecode')
|
||||
var imgType = require('image-type')
|
||||
var qrDecode = require('./')
|
||||
// var jpg = require('./src/imageDecode/jpg');
|
||||
var imgDecode = require('./src/imageDecode')
|
||||
var qrDecode = require('./src/QRCodeDecode')
|
||||
|
||||
/**
|
||||
* 通过Buffer识别二维码
|
||||
* @param {buufer} buffer 文件的Buffer
|
||||
*/
|
||||
function decodeByBuffer(buffer,debug) {
|
||||
function decodeByBuffer(buffer, options) {
|
||||
options || (options = {});
|
||||
var debug = options.debug;
|
||||
var type;
|
||||
return new Promise(function (res, rej) {
|
||||
type = (imgType(buffer) || {}).ext;
|
||||
@@ -34,6 +35,7 @@ function decodeByBuffer(buffer,debug) {
|
||||
throw 'not image!'
|
||||
}
|
||||
}).then(function (imageData) {
|
||||
//可能有多帧图片处理
|
||||
if (type == 'gif' || type == 'png') {
|
||||
return new Promise(function (res, rej) {
|
||||
var errList = [];
|
||||
@@ -41,26 +43,21 @@ function decodeByBuffer(buffer,debug) {
|
||||
var onerr = function (e) {
|
||||
errList.push(e);
|
||||
if (errList.length < images.length) return;
|
||||
rej('解码失败!')
|
||||
rej(errList);
|
||||
}
|
||||
debug && console.log('length',imageData.length)
|
||||
debug && console.log('gif_length: ', imageData.length)
|
||||
if (imageData.length <= 0) {
|
||||
rej('解码失败!')
|
||||
} else if (imageData.length > 3) {
|
||||
} else if (imageData.length > 5 && !options.allFrames) {
|
||||
var l = 1, i = 0, sp;
|
||||
images = [];
|
||||
var l = 1;
|
||||
var i = 0;
|
||||
while ( Math.pow(2,++l) < imageData.length){
|
||||
}
|
||||
// console.log('l:',l)
|
||||
var sp = (imageData.length-1)/l;
|
||||
// console.log('sp:',sp)
|
||||
while (Math.pow(2, ++l) < imageData.length) {s}
|
||||
sp = (imageData.length - 1) / l;
|
||||
do {
|
||||
// console.log(Math.floor(i*sp));
|
||||
images.push(imageData[Math.floor(i * sp)])
|
||||
} while (i++ < l)
|
||||
}
|
||||
debug && console.log(images.length)
|
||||
debug && console.log('gif_decode_length: ',images.length)
|
||||
images.forEach(function (v) {
|
||||
setTimeout(function () {
|
||||
try {
|
||||
@@ -81,14 +78,17 @@ function decodeByBuffer(buffer,debug) {
|
||||
* 识别二维码图片文件
|
||||
* @param {String} path 文件路径
|
||||
*/
|
||||
exports.decodeByPath = function (path) {
|
||||
function decodeByPath(path,options) {
|
||||
return new Promise(function (res, rej) {
|
||||
fs.readFile(path, function (err, buffer) {
|
||||
if (err) { return rej(err) }
|
||||
res(decodeByBuffer(buffer));
|
||||
res(decodeByBuffer(buffer,options));
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.decodeByBuffer = decodeByBuffer;
|
||||
exports.decodeByImageData = qrDecode;
|
||||
module.exports = {
|
||||
decodeByBuffer: decodeByBuffer,
|
||||
decodeByPath: decodeByPath,
|
||||
decodeByImageData: qrDecode
|
||||
}
|
||||
@@ -1,525 +0,0 @@
|
||||
//https://github.com/shachaf/jsgif
|
||||
|
||||
// Generic functions
|
||||
var bitsToNum = function (ba) {
|
||||
return ba.reduce(function (s, n) { return s * 2 + n; }, 0);
|
||||
};
|
||||
|
||||
var byteToBitArr = function (bite) {
|
||||
var a = [];
|
||||
for (var i = 7; i >= 0; i--) {
|
||||
a.push(!!(bite & (1 << i)));
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
// Stream
|
||||
/**
|
||||
* @constructor
|
||||
*/ // Make compiler happy.
|
||||
var Stream = function (data) {
|
||||
|
||||
this.data = data;
|
||||
var len = this.data.length;
|
||||
var pos = 0;
|
||||
|
||||
this.readByte = function () {
|
||||
if (pos >= len) {
|
||||
throw new Error('Attempted to read past end of stream.');
|
||||
}
|
||||
// return data.charCodeAt(pos++) & 0xFF;
|
||||
// var bit = data[pos++];
|
||||
// return String.fromCharCode(bit).charCodeAt(0) & 0xff;
|
||||
return data[pos++];
|
||||
};
|
||||
|
||||
this.readBytes = function (n) {
|
||||
var bytes = [];
|
||||
for (var i = 0; i < n; i++) {
|
||||
bytes.push(this.readByte());
|
||||
}
|
||||
return bytes;
|
||||
// var start = pos
|
||||
// pos += n;
|
||||
// if(start>= len || pos > len){
|
||||
// throw new Error('Attempted to read past end of stream.');
|
||||
// }
|
||||
// return [].slice.call(data,start,pos)
|
||||
};
|
||||
|
||||
this.read = function (n) {
|
||||
var s = '';
|
||||
for (var i = 0; i < n; i++) {
|
||||
s += String.fromCharCode(this.readByte());
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
this.readUnsigned = function () { // Little-endian.
|
||||
var a = this.readBytes(2);
|
||||
return (a[1] << 8) + a[0];
|
||||
};
|
||||
};
|
||||
|
||||
var lzwDecode = function (minCodeSize, data, gct,GCE) {
|
||||
// TODO: Now that the GIF parser is a bit different, maybe this should get an array of bytes instead of a String?
|
||||
var pos = 0; // Maybe this streaming thing should be merged with the Stream?
|
||||
|
||||
var readCode = function (size) {
|
||||
var code = 0;
|
||||
for (var i = 0; i < size; i++) {
|
||||
if (data[pos >> 3] & (1 << (pos & 7))) {
|
||||
code |= 1 << i;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return code;
|
||||
};
|
||||
|
||||
var output = [];
|
||||
var imageData = [];
|
||||
|
||||
var clearCode = 1 << minCodeSize;
|
||||
var eoiCode = clearCode + 1;
|
||||
|
||||
var codeSize = minCodeSize + 1;
|
||||
|
||||
var dict = [];
|
||||
|
||||
var clear = function () {
|
||||
dict = [];
|
||||
codeSize = minCodeSize + 1;
|
||||
for (var i = 0; i < clearCode; i++) {
|
||||
dict[i] = [i];
|
||||
}
|
||||
dict[clearCode] = [];
|
||||
dict[eoiCode] = null;
|
||||
|
||||
};
|
||||
|
||||
var code;
|
||||
var last;
|
||||
|
||||
while (true) {
|
||||
last = code;
|
||||
code = readCode(codeSize);
|
||||
|
||||
if (code === clearCode) {
|
||||
clear();
|
||||
continue;
|
||||
}
|
||||
if (code === eoiCode) break;
|
||||
|
||||
if (code < dict.length) {
|
||||
if (last !== clearCode) {
|
||||
dict.push(dict[last].concat(dict[code][0]));
|
||||
}
|
||||
} else {
|
||||
if (code !== dict.length) throw new Error('Invalid LZW code.');
|
||||
// try{
|
||||
dict.push(dict[last].concat(dict[last][0]));
|
||||
|
||||
// }catch(e){
|
||||
// console.log(minCodeSize,data);
|
||||
// console.log(dict,last);
|
||||
// throw e
|
||||
// }
|
||||
}
|
||||
var inItem = dict[code];
|
||||
// output.push.apply(output, inItem);
|
||||
|
||||
inItem.forEach(function(v){
|
||||
// output.push(v);
|
||||
var rgb = gct[v]
|
||||
imageData.push(rgb[0],rgb[1],rgb[2],GCE.transparencyIndex == v ? 0 : 255)
|
||||
})
|
||||
|
||||
// imageData.push.apply(imageData, inItem.map(function(v){
|
||||
// var c = gct[v];
|
||||
// var rgba = [c[0],c[1],c[2],GCE.transparencyIndex == v ? 0 : 255]
|
||||
// //rgba.push(GCE.transparencyIndex == v ? 0 : 255)
|
||||
// // return color;
|
||||
// return rgba;
|
||||
// // return color.slice(0).push(GCE.transparencyIndex == v ? 0 : 255)
|
||||
// }))
|
||||
|
||||
if (dict.length === (1 << codeSize) && codeSize < 12) {
|
||||
// If we're at the last code and codeSize is 12, the next code will be a clearCode, and it'll be 12 bits long.
|
||||
codeSize++;
|
||||
}
|
||||
}
|
||||
|
||||
// I don't know if this is technically an error, but some GIFs do it.
|
||||
//if (Math.ceil(pos / 8) !== data.length) throw new Error('Extraneous LZW bytes.');
|
||||
return [output,imageData];
|
||||
};
|
||||
|
||||
// The actual parsing; returns an object with properties.
|
||||
var parseGIF = function (st, handler) {
|
||||
handler || (handler = {});
|
||||
var GCE,gct;
|
||||
// LZW (GIF-specific)
|
||||
var parseCT = function (entries) { // Each entry is 3 bytes, for RGB.
|
||||
var ct = [];
|
||||
for (var i = 0; i < entries; i++) {
|
||||
ct.push(st.readBytes(3));
|
||||
}
|
||||
return ct;
|
||||
};
|
||||
|
||||
var readSubBlocks = function () {
|
||||
var size, data;
|
||||
data = '';
|
||||
do {
|
||||
size = st.readByte();
|
||||
data += st.read(size);
|
||||
} while (size !== 0);
|
||||
return data;
|
||||
};
|
||||
|
||||
var readByteBlocks = function(){
|
||||
var size, data;
|
||||
data = [];
|
||||
do {
|
||||
size = st.readByte();
|
||||
data.push.apply(data,st.readBytes(size))
|
||||
// data = data.concat(st.readBytes(size));
|
||||
} while (size !== 0);
|
||||
return data;
|
||||
}
|
||||
|
||||
var parseHeader = function () {
|
||||
var hdr = {};
|
||||
hdr.sig = st.read(3);
|
||||
hdr.ver = st.read(3);
|
||||
if (hdr.sig !== 'GIF') throw new Error('Not a GIF file.'); // XXX: This should probably be handled more nicely.
|
||||
|
||||
hdr.width = st.readUnsigned();
|
||||
hdr.height = st.readUnsigned();
|
||||
|
||||
var bits = byteToBitArr(st.readByte());
|
||||
hdr.gctFlag = bits.shift();
|
||||
hdr.colorRes = bitsToNum(bits.splice(0, 3));
|
||||
hdr.sorted = bits.shift();
|
||||
hdr.gctSize = bitsToNum(bits.splice(0, 3));
|
||||
|
||||
hdr.bgColor = st.readByte();
|
||||
hdr.pixelAspectRatio = st.readByte(); // if not 0, aspectRatio = (pixelAspectRatio + 15) / 64
|
||||
|
||||
if (hdr.gctFlag) {
|
||||
gct = hdr.gct = parseCT(1 << (hdr.gctSize + 1));
|
||||
}
|
||||
handler.hdr && handler.hdr(hdr);
|
||||
};
|
||||
|
||||
var parseExt = function (block) {
|
||||
var parseGCExt = function (block) {
|
||||
var blockSize = st.readByte(); // Always 4
|
||||
|
||||
var bits = byteToBitArr(st.readByte());
|
||||
block.reserved = bits.splice(0, 3); // Reserved; should be 000.
|
||||
block.disposalMethod = bitsToNum(bits.splice(0, 3));
|
||||
block.userInput = bits.shift();
|
||||
block.transparencyGiven = bits.shift();
|
||||
|
||||
block.delayTime = st.readUnsigned();
|
||||
|
||||
block.transparencyIndex = st.readByte();
|
||||
|
||||
block.terminator = st.readByte();
|
||||
GCE = block;
|
||||
handler.gce && handler.gce(block);
|
||||
};
|
||||
|
||||
var parseComExt = function (block) {
|
||||
block.comment = readSubBlocks();
|
||||
handler.com && handler.com(block);
|
||||
};
|
||||
|
||||
var parsePTExt = function (block) {
|
||||
// No one *ever* uses this. If you use it, deal with parsing it yourself.
|
||||
var blockSize = st.readByte(); // Always 12
|
||||
block.ptHeader = st.readBytes(12);
|
||||
block.ptData = readSubBlocks();
|
||||
handler.pte && handler.pte(block);
|
||||
};
|
||||
|
||||
var parseAppExt = function (block) {
|
||||
var parseNetscapeExt = function (block) {
|
||||
var blockSize = st.readByte(); // Always 3
|
||||
block.unknown = st.readByte(); // ??? Always 1? What is this?
|
||||
block.iterations = st.readUnsigned();
|
||||
block.terminator = st.readByte();
|
||||
handler.app && handler.app.NETSCAPE && handler.app.NETSCAPE(block);
|
||||
};
|
||||
|
||||
var parseUnknownAppExt = function (block) {
|
||||
block.appData = readSubBlocks();
|
||||
// FIXME: This won't work if a handler wants to match on any identifier.
|
||||
handler.app && handler.app[block.identifier] && handler.app[block.identifier](block);
|
||||
};
|
||||
|
||||
var blockSize = st.readByte(); // Always 11
|
||||
block.identifier = st.read(8);
|
||||
block.authCode = st.read(3);
|
||||
switch (block.identifier) {
|
||||
case 'NETSCAPE':
|
||||
parseNetscapeExt(block);
|
||||
break;
|
||||
default:
|
||||
parseUnknownAppExt(block);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var parseUnknownExt = function (block) {
|
||||
block.data = readSubBlocks();
|
||||
handler.unknown && handler.unknown(block);
|
||||
};
|
||||
|
||||
block.label = st.readByte();
|
||||
switch (block.label) {
|
||||
case 0xF9:
|
||||
block.extType = 'gce';
|
||||
parseGCExt(block);
|
||||
break;
|
||||
case 0xFE:
|
||||
block.extType = 'com';
|
||||
parseComExt(block);
|
||||
break;
|
||||
case 0x01:
|
||||
block.extType = 'pte';
|
||||
parsePTExt(block);
|
||||
break;
|
||||
case 0xFF:
|
||||
block.extType = 'app';
|
||||
parseAppExt(block);
|
||||
break;
|
||||
default:
|
||||
block.extType = 'unknown';
|
||||
parseUnknownExt(block);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var parseImg = function (img) {
|
||||
var deinterlace = function (pixels, width) {
|
||||
// Of course this defeats the purpose of interlacing. And it's *probably*
|
||||
// the least efficient way it's ever been implemented. But nevertheless...
|
||||
|
||||
var newPixels = new Array(pixels.length);
|
||||
var rows = pixels.length / width;
|
||||
var cpRow = function (toRow, fromRow) {
|
||||
var fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width);
|
||||
newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels));
|
||||
};
|
||||
|
||||
// See appendix E.
|
||||
var offsets = [0, 4, 2, 1];
|
||||
var steps = [8, 8, 4, 2];
|
||||
|
||||
var fromRow = 0;
|
||||
for (var pass = 0; pass < 4; pass++) {
|
||||
for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) {
|
||||
cpRow(toRow, fromRow)
|
||||
fromRow++;
|
||||
}
|
||||
}
|
||||
|
||||
return newPixels;
|
||||
};
|
||||
|
||||
img.leftPos = st.readUnsigned();
|
||||
img.topPos = st.readUnsigned();
|
||||
img.width = st.readUnsigned();
|
||||
img.height = st.readUnsigned();
|
||||
|
||||
var bits = byteToBitArr(st.readByte());
|
||||
img.lctFlag = bits.shift();
|
||||
img.interlaced = bits.shift();
|
||||
img.sorted = bits.shift();
|
||||
img.reserved = bits.splice(0, 2);
|
||||
img.lctSize = bitsToNum(bits.splice(0, 3));
|
||||
|
||||
if (img.lctFlag) {
|
||||
img.lct = parseCT(1 << (img.lctSize + 1));
|
||||
}
|
||||
|
||||
img.lzwMinCodeSize = st.readByte();
|
||||
|
||||
// var lzwData = readSubBlocks();
|
||||
var lzwData = readByteBlocks();
|
||||
var lzwd = lzwDecode(img.lzwMinCodeSize, lzwData, gct,GCE);
|
||||
img.pixels = lzwd[0];
|
||||
img.data = lzwd[1];
|
||||
// console.log(img.imageData)
|
||||
if (img.interlaced) { // Move
|
||||
console.log(img.pixels)
|
||||
img.pixels = deinterlace(img.pixels, img.width);
|
||||
console.log(img.pixels)
|
||||
|
||||
}
|
||||
|
||||
handler.img && handler.img(img);
|
||||
};
|
||||
|
||||
var parseBlock = function () {
|
||||
var block = {};
|
||||
block.sentinel = st.readByte();
|
||||
|
||||
switch (String.fromCharCode(block.sentinel)) { // For ease of matching
|
||||
case '!':
|
||||
block.type = 'ext';
|
||||
parseExt(block);
|
||||
break;
|
||||
case ',':
|
||||
block.type = 'img';
|
||||
parseImg(block);
|
||||
break;
|
||||
case ';':
|
||||
block.type = 'eof';
|
||||
handler.eof && handler.eof(block);
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unknown block: 0x' + block.sentinel.toString(16)); // TODO: Pad this with a 0.
|
||||
}
|
||||
|
||||
// if (block.type !== 'eof') setTimeout(parseBlock,0);
|
||||
if (block.type !== 'eof') parseBlock();
|
||||
};
|
||||
|
||||
var parse = function () {
|
||||
parseHeader();
|
||||
parseBlock();
|
||||
// setTimeout(parseBlock, 0);
|
||||
};
|
||||
|
||||
parse();
|
||||
};
|
||||
|
||||
// BEGIN_NON_BOOKMARKLET_CODE
|
||||
exports.Stream = Stream;
|
||||
exports.parseGIF = parseGIF;
|
||||
exports.decode = function (buffer) {
|
||||
var gifData = { images: [] };
|
||||
var gct;
|
||||
var GCE;
|
||||
var log = console.log;
|
||||
|
||||
// var showBool = function (b) {
|
||||
// return b ? 'yes' : 'no';
|
||||
// };
|
||||
|
||||
// var showColor = function (rgb) {
|
||||
// // FIXME When I have an Internet connection.
|
||||
// var showHex = function (n) { // Two-digit code.
|
||||
// var hexChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
// return hexChars[(n >>> 4) & 0xF] + hexChars[n & 0xF];
|
||||
// };
|
||||
// return '#' + showHex(rgb[0]) + showHex(rgb[1]) + showHex(rgb[2]);
|
||||
// };
|
||||
|
||||
// var showDisposalMethod = function (dm) {
|
||||
// var map = {
|
||||
// 0: 'None',
|
||||
// 1: 'Do not dispose',
|
||||
// 2: 'Restore to background',
|
||||
// 3: 'Restore to previous'
|
||||
// };
|
||||
// return map[dm] || 'Unknown';
|
||||
// }
|
||||
|
||||
//尺寸 背景色 调色板 等
|
||||
var doHdr = function (hdr) {
|
||||
// log('Header:');
|
||||
// log(' Version: %s', hdr.ver);
|
||||
// log(' Size: %dx%d', hdr.width, hdr.height);
|
||||
// log(' GCT? %s%s', showBool(hdr.gctFlag), hdr.gctFlag ? ' (' + hdr.gct.length + ' entries)' : '');
|
||||
// log(' Color resolution: %d', hdr.colorRes);
|
||||
// log(' Sorted? %s', showBool(hdr.sorted));
|
||||
// log(' Background color: %s (%d)', hdr.gctFlag ? showColor(hdr.bgColor) : 'no GCT', hdr.bgColor);
|
||||
// log(' Pixel aspect ratio: %d FIXME', hdr.pixelAspectRatio);
|
||||
gct = hdr.gct;
|
||||
gifData.width = hdr.width;
|
||||
gifData.height = hdr.height;
|
||||
};
|
||||
//当前帧的非颜色信息 (透明色,延时 等))
|
||||
var doGCE = function (gce) {
|
||||
// log('GCE:');
|
||||
// log(' Disposal method: %d (%s)', gce.disposalMethod, showDisposalMethod(gce.disposalMethod));
|
||||
// log(' User input expected? %s', showBool(gce.userInput));
|
||||
// log(' Transparency given? %s%s', showBool(gce.transparencyGiven),
|
||||
// gce.transparencyGiven ? ' (index: ' + gce.transparencyIndex + ')' : '');
|
||||
// log(' Delay time: %d', gce.delayTime);
|
||||
GCE = gce;
|
||||
};
|
||||
//当前帧的图片图像信息
|
||||
// var doimgTime = 0;
|
||||
var doImg = function (img) {
|
||||
// log('Image descriptor:');
|
||||
// log(Object.assign({},img,{'pixels':Math.max.apply(Math,img.pixels)}));
|
||||
// log(' Geometry: %dx%d+%d+%d', img.width, img.height, img.leftPos, img.topPos);
|
||||
// log(' LCT? %s%s', showBool(img.lctFlag), img.lctFlag ? ' (' + img.lct.length + ' entries)' : '');
|
||||
// log(' Interlaced? %s', showBool(img.interlaced));
|
||||
// log(' %d pixels', img.pixels.length);
|
||||
// var t1 = Date.now();
|
||||
// var data = [];
|
||||
// console.log(data.length , img.width * img.height)
|
||||
var imageData = {
|
||||
left: img.leftPos,
|
||||
top: img.topPos,
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
// data: data,
|
||||
data: img.data,
|
||||
}
|
||||
// img.pixels.forEach(function (v) {
|
||||
// var color = gct[v];
|
||||
// data.push.apply(data, color.concat(GCE.transparencyIndex == v ? 0 : 255));
|
||||
// })
|
||||
// console.log(data.length)
|
||||
// console.log(img.imageData.length)
|
||||
gifData.images.push(imageData);
|
||||
// doimgTime += Date.now() - t1;
|
||||
};
|
||||
|
||||
var doNetscape = function (block) {
|
||||
// log('Netscape application extension:');
|
||||
// log(' Iterations: %d%s', block.iterations, block.iterations === 0 ? ' (infinite)' : '');
|
||||
};
|
||||
|
||||
var doCom = function (com) {
|
||||
// log('Comment extension:');
|
||||
// log(' Comment: [31m%s[0m', com.comment);
|
||||
};
|
||||
|
||||
var doEOF = function (eof) {
|
||||
// log('EOF:');
|
||||
// log(eof);
|
||||
};
|
||||
|
||||
var doUnknownApp = function (block) {
|
||||
// log(block)
|
||||
};
|
||||
|
||||
var doUnknownExt = function (block) {
|
||||
// log(block)
|
||||
}
|
||||
|
||||
var handler = {
|
||||
hdr: doHdr,
|
||||
img: doImg,
|
||||
gce: doGCE,
|
||||
com: doCom,
|
||||
app: {
|
||||
NETSCAPE: doNetscape,
|
||||
unknown: doUnknownApp
|
||||
},
|
||||
eof: doEOF
|
||||
};
|
||||
|
||||
var st = new Stream(buffer);
|
||||
parseGIF(st, handler);
|
||||
// console.log('doimgTime:',doimgTime)
|
||||
return gifData;
|
||||
}
|
||||
+80
-22
@@ -18,15 +18,19 @@ var byteToBitArr = function (bite) {
|
||||
* @constructor
|
||||
*/ // Make compiler happy.
|
||||
var Stream = function (data) {
|
||||
|
||||
this.data = data;
|
||||
this.len = this.data.length;
|
||||
this.pos = 0;
|
||||
var len = this.data.length;
|
||||
var pos = 0;
|
||||
|
||||
this.readByte = function () {
|
||||
if (this.pos >= this.data.length) {
|
||||
if (pos >= len) {
|
||||
throw new Error('Attempted to read past end of stream.');
|
||||
}
|
||||
return data.charCodeAt(this.pos++) & 0xFF;
|
||||
// return data.charCodeAt(pos++) & 0xFF;
|
||||
// var bit = data[pos++];
|
||||
// return String.fromCharCode(bit).charCodeAt(0) & 0xff;
|
||||
return data[pos++];
|
||||
};
|
||||
|
||||
this.readBytes = function (n) {
|
||||
@@ -35,6 +39,12 @@ var Stream = function (data) {
|
||||
bytes.push(this.readByte());
|
||||
}
|
||||
return bytes;
|
||||
// var start = pos
|
||||
// pos += n;
|
||||
// if(start>= len || pos > len){
|
||||
// throw new Error('Attempted to read past end of stream.');
|
||||
// }
|
||||
// return [].slice.call(data,start,pos)
|
||||
};
|
||||
|
||||
this.read = function (n) {
|
||||
@@ -51,14 +61,14 @@ var Stream = function (data) {
|
||||
};
|
||||
};
|
||||
|
||||
var lzwDecode = function (minCodeSize, data) {
|
||||
var lzwDecode = function (minCodeSize, data, gct,GCE) {
|
||||
// TODO: Now that the GIF parser is a bit different, maybe this should get an array of bytes instead of a String?
|
||||
var pos = 0; // Maybe this streaming thing should be merged with the Stream?
|
||||
|
||||
var readCode = function (size) {
|
||||
var code = 0;
|
||||
for (var i = 0; i < size; i++) {
|
||||
if (data.charCodeAt(pos >> 3) & (1 << (pos & 7))) {
|
||||
if (data[pos >> 3] & (1 << (pos & 7))) {
|
||||
code |= 1 << i;
|
||||
}
|
||||
pos++;
|
||||
@@ -67,6 +77,7 @@ var lzwDecode = function (minCodeSize, data) {
|
||||
};
|
||||
|
||||
var output = [];
|
||||
var imageData = [];
|
||||
|
||||
var clearCode = 1 << minCodeSize;
|
||||
var eoiCode = clearCode + 1;
|
||||
@@ -105,9 +116,32 @@ var lzwDecode = function (minCodeSize, data) {
|
||||
}
|
||||
} else {
|
||||
if (code !== dict.length) throw new Error('Invalid LZW code.');
|
||||
// try{
|
||||
dict.push(dict[last].concat(dict[last][0]));
|
||||
|
||||
// }catch(e){
|
||||
// console.log(minCodeSize,data);
|
||||
// console.log(dict,last);
|
||||
// throw e
|
||||
// }
|
||||
}
|
||||
output.push.apply(output, dict[code]);
|
||||
var inItem = dict[code];
|
||||
// output.push.apply(output, inItem);
|
||||
|
||||
inItem.forEach(function(v){
|
||||
// output.push(v);
|
||||
var rgb = gct[v]
|
||||
imageData.push(rgb[0],rgb[1],rgb[2],GCE.transparencyIndex == v ? 0 : 255)
|
||||
})
|
||||
|
||||
// imageData.push.apply(imageData, inItem.map(function(v){
|
||||
// var c = gct[v];
|
||||
// var rgba = [c[0],c[1],c[2],GCE.transparencyIndex == v ? 0 : 255]
|
||||
// //rgba.push(GCE.transparencyIndex == v ? 0 : 255)
|
||||
// // return color;
|
||||
// return rgba;
|
||||
// // return color.slice(0).push(GCE.transparencyIndex == v ? 0 : 255)
|
||||
// }))
|
||||
|
||||
if (dict.length === (1 << codeSize) && codeSize < 12) {
|
||||
// If we're at the last code and codeSize is 12, the next code will be a clearCode, and it'll be 12 bits long.
|
||||
@@ -117,13 +151,13 @@ var lzwDecode = function (minCodeSize, data) {
|
||||
|
||||
// I don't know if this is technically an error, but some GIFs do it.
|
||||
//if (Math.ceil(pos / 8) !== data.length) throw new Error('Extraneous LZW bytes.');
|
||||
return output;
|
||||
return [output,imageData];
|
||||
};
|
||||
|
||||
// The actual parsing; returns an object with properties.
|
||||
var parseGIF = function (st, handler) {
|
||||
handler || (handler = {});
|
||||
|
||||
var GCE,gct;
|
||||
// LZW (GIF-specific)
|
||||
var parseCT = function (entries) { // Each entry is 3 bytes, for RGB.
|
||||
var ct = [];
|
||||
@@ -143,6 +177,17 @@ var parseGIF = function (st, handler) {
|
||||
return data;
|
||||
};
|
||||
|
||||
var readByteBlocks = function(){
|
||||
var size, data;
|
||||
data = [];
|
||||
do {
|
||||
size = st.readByte();
|
||||
data.push.apply(data,st.readBytes(size))
|
||||
// data = data.concat(st.readBytes(size));
|
||||
} while (size !== 0);
|
||||
return data;
|
||||
}
|
||||
|
||||
var parseHeader = function () {
|
||||
var hdr = {};
|
||||
hdr.sig = st.read(3);
|
||||
@@ -162,7 +207,7 @@ var parseGIF = function (st, handler) {
|
||||
hdr.pixelAspectRatio = st.readByte(); // if not 0, aspectRatio = (pixelAspectRatio + 15) / 64
|
||||
|
||||
if (hdr.gctFlag) {
|
||||
hdr.gct = parseCT(1 << (hdr.gctSize + 1));
|
||||
gct = hdr.gct = parseCT(1 << (hdr.gctSize + 1));
|
||||
}
|
||||
handler.hdr && handler.hdr(hdr);
|
||||
};
|
||||
@@ -182,7 +227,7 @@ var parseGIF = function (st, handler) {
|
||||
block.transparencyIndex = st.readByte();
|
||||
|
||||
block.terminator = st.readByte();
|
||||
|
||||
GCE = block;
|
||||
handler.gce && handler.gce(block);
|
||||
};
|
||||
|
||||
@@ -302,12 +347,17 @@ var parseGIF = function (st, handler) {
|
||||
|
||||
img.lzwMinCodeSize = st.readByte();
|
||||
|
||||
var lzwData = readSubBlocks();
|
||||
|
||||
img.pixels = lzwDecode(img.lzwMinCodeSize, lzwData);
|
||||
|
||||
// var lzwData = readSubBlocks();
|
||||
var lzwData = readByteBlocks();
|
||||
var lzwd = lzwDecode(img.lzwMinCodeSize, lzwData, gct,GCE);
|
||||
img.pixels = lzwd[0];
|
||||
img.data = lzwd[1];
|
||||
// console.log(img.imageData)
|
||||
if (img.interlaced) { // Move
|
||||
console.log(img.pixels)
|
||||
img.pixels = deinterlace(img.pixels, img.width);
|
||||
console.log(img.pixels)
|
||||
|
||||
}
|
||||
|
||||
handler.img && handler.img(img);
|
||||
@@ -404,6 +454,7 @@ exports.decode = function (buffer) {
|
||||
GCE = gce;
|
||||
};
|
||||
//当前帧的图片图像信息
|
||||
// var doimgTime = 0;
|
||||
var doImg = function (img) {
|
||||
// log('Image descriptor:');
|
||||
// log(Object.assign({},img,{'pixels':Math.max.apply(Math,img.pixels)}));
|
||||
@@ -411,19 +462,25 @@ exports.decode = function (buffer) {
|
||||
// log(' LCT? %s%s', showBool(img.lctFlag), img.lctFlag ? ' (' + img.lct.length + ' entries)' : '');
|
||||
// log(' Interlaced? %s', showBool(img.interlaced));
|
||||
// log(' %d pixels', img.pixels.length);
|
||||
var data = [];
|
||||
// var t1 = Date.now();
|
||||
// var data = [];
|
||||
// console.log(data.length , img.width * img.height)
|
||||
var imageData = {
|
||||
left: img.leftPos,
|
||||
top: img.topPos,
|
||||
width: img.width,
|
||||
height: img.height,
|
||||
data: data
|
||||
// data: data,
|
||||
data: img.data,
|
||||
}
|
||||
img.pixels.forEach(function (v) {
|
||||
var color = gct[v];
|
||||
data.push.apply(data, color.concat(GCE.transparencyIndex == v ? 0 : 255));
|
||||
})
|
||||
// img.pixels.forEach(function (v) {
|
||||
// var color = gct[v];
|
||||
// data.push.apply(data, color.concat(GCE.transparencyIndex == v ? 0 : 255));
|
||||
// })
|
||||
// console.log(data.length)
|
||||
// console.log(img.imageData.length)
|
||||
gifData.images.push(imageData);
|
||||
// doimgTime += Date.now() - t1;
|
||||
};
|
||||
|
||||
var doNetscape = function (block) {
|
||||
@@ -461,7 +518,8 @@ exports.decode = function (buffer) {
|
||||
eof: doEOF
|
||||
};
|
||||
|
||||
var st = new Stream(buffer.toString('binary'));
|
||||
var st = new Stream(buffer);
|
||||
parseGIF(st, handler);
|
||||
// console.log('doimgTime:',doimgTime)
|
||||
return gifData;
|
||||
}
|
||||
@@ -3,28 +3,18 @@ var jpgDecode = new jpg.JpegDecoder();
|
||||
var upng = require('./png');
|
||||
var bmpDecode = require('./bmp');
|
||||
var gifDecode = require('./gif').decode;
|
||||
var gif1Decode = require('./gif.1').decode;
|
||||
|
||||
|
||||
|
||||
exports.bmp = function (buffer) {
|
||||
// return new Promise(function (res, rej) {
|
||||
// res(bmpDecode(buffer));
|
||||
// })
|
||||
return bmpDecode(buffer);
|
||||
};
|
||||
|
||||
exports.jpg = function (buffer) {
|
||||
// return new Promise(function (res, rej) {
|
||||
// jpgDecode.parse(buffer);
|
||||
// res(jpgDecode.getImageData());
|
||||
// })
|
||||
jpgDecode.parse(buffer);
|
||||
return jpgDecode.getImageData();
|
||||
}
|
||||
|
||||
exports.png = function (buffer) {
|
||||
// console.log('png');
|
||||
var png = upng.decode(buffer);
|
||||
var datas = upng.toRGBA8(png);
|
||||
var images = [];
|
||||
@@ -39,21 +29,7 @@ exports.png = function (buffer) {
|
||||
}
|
||||
|
||||
exports.gif = function (data) {
|
||||
// return new Promise(function (res, rej) {
|
||||
// var gif = gifDecode(data);
|
||||
// res(gif.images);
|
||||
// })
|
||||
var gif = gifDecode(data);
|
||||
return gif.images;
|
||||
|
||||
}
|
||||
|
||||
exports.gif1 = function (data) {
|
||||
// return new Promise(function (res, rej) {
|
||||
// var gif = gifDecode(data);
|
||||
// res(gif.images);
|
||||
// })
|
||||
var gif = gif1Decode(data);
|
||||
return gif.images;
|
||||
|
||||
}
|
||||
+22
-3
@@ -6,12 +6,31 @@ var P = function (p) {
|
||||
return path.join(__dirname, p);
|
||||
}
|
||||
|
||||
var test = function (path, mark) {
|
||||
var _test = function (path, mark) {
|
||||
return imgDecode.decodeByPath(P(path))
|
||||
.then(console.log.bind(console, mark + ': '), console.error.bind(console, mark + ':'))
|
||||
}
|
||||
|
||||
var max = 100
|
||||
async function test_time(path, mark) {
|
||||
var t = Date.now();
|
||||
for (var i = 0; i < max; i++) {
|
||||
await _test(path, mark);
|
||||
}
|
||||
console.log(mark + ' 100次 : ', Date.now() - t,' ms');
|
||||
}
|
||||
console.table({a:1,b:2})
|
||||
// var test = test_time;
|
||||
|
||||
var test = function (path, mark) {
|
||||
return _test(path, mark)
|
||||
.then(console.log.bind(console, mark + ': '))
|
||||
.then(function () {
|
||||
return test_time(path, mark);
|
||||
},console.error.bind(console, mark + ':'))
|
||||
};
|
||||
|
||||
Promise.resolve()
|
||||
.then(function () { test('./img/16.bmp', 'bmp16 ') })
|
||||
.then(function () { return test('./img/16.bmp', 'bmp16 ') })
|
||||
.then(function () { return test('./img/24.bmp', 'bmp24 ') })
|
||||
.then(function () { return test('./img/32.bmp', 'bmp32 ') })
|
||||
.then(function () { return test('/img/lx.jpg', 'jpg_lx') }) //jpg 连续
|
||||
|
||||
Reference in New Issue
Block a user