// Working buffers: // data input and ecc append, image working buffer, fixed part of image, run lengths for badness var strinbuf = [], eccbuf = [], qrframe = [], framask = [], rlens = []; // Control values - width is based on version, last 4 are from table. var version, width, neccblk1, neccblk2, datablkw, eccblkwid; var ecclevel = 2; // set bit to indicate cell in qrframe is immutable. symmetric around diagonal functionsetmask(x, y) { var bt; if (x > y) { bt = x; x = y; y = bt; } // y*y = 1+3+5... bt = y; bt *= y; bt += y; bt >>= 1; bt += x; framask[bt] = 1; }
// enter alignment pattern - black to qrframe, white to mask (later black frame merged to mask) functionputalign(x, y) { var j;
//======================================================================== // Reed Solomon error correction // exponentiation mod N functionmodnn(x) { while (x >= 255) { x -= 255; x = (x >> 8) + (x & 255); } return x; }
var genpoly = [];
// Calculate and append ECC data to data block. Block is in strinbuf, indexes to buffers given. functionappendrs(data, dlen, ecbuf, eclen) { var i, j, fb;
for (i = 0; i < eclen; i++) strinbuf[ecbuf + i] = 0; for (i = 0; i < dlen; i++) { fb = glog[strinbuf[data + i] ^ strinbuf[ecbuf]]; if (fb != 255) /* fb term is non-zero */ for (j = 1; j < eclen; j++) strinbuf[ecbuf + j - 1] = strinbuf[ecbuf + j] ^ gexp[modnn(fb + genpoly[eclen - j])]; else for (j = ecbuf; j < ecbuf + eclen; j++) strinbuf[j] = strinbuf[j + 1]; strinbuf[ecbuf + eclen - 1] = fb == 255 ? 0 : gexp[modnn(fb + genpoly[0])]; } }
//======================================================================== // Frame data insert following the path rules
// check mask - since symmetrical use half. functionismasked(x, y) { var bt; if (x > y) { bt = x; x = y; y = bt; } bt = y; bt += y * y; bt >>= 1; bt += x; return framask[bt]; }
//======================================================================== // Apply the selected mask out of the 8. functionapplymask(m) { var x, y, r3x, r3y;
switch (m) { case0: for (y = 0; y < width; y++) for (x = 0; x < width; x++) if (!((x + y) & 1) && !ismasked(x, y)) qrframe[x + y * width] ^= 1; break; case1: for (y = 0; y < width; y++) for (x = 0; x < width; x++) if (!(y & 1) && !ismasked(x, y)) qrframe[x + y * width] ^= 1; break; case2: for (y = 0; y < width; y++) for (r3x = 0, x = 0; x < width; x++ , r3x++) { if (r3x == 3) r3x = 0; if (!r3x && !ismasked(x, y)) qrframe[x + y * width] ^= 1; } break; case3: for (r3y = 0, y = 0; y < width; y++ , r3y++) { if (r3y == 3) r3y = 0; for (r3x = r3y, x = 0; x < width; x++ , r3x++) { if (r3x == 3) r3x = 0; if (!r3x && !ismasked(x, y)) qrframe[x + y * width] ^= 1; } } break; case4: for (y = 0; y < width; y++) for (r3x = 0, r3y = ((y >> 1) & 1), x = 0; x < width; x++ , r3x++) { if (r3x == 3) { r3x = 0; r3y = !r3y; } if (!r3y && !ismasked(x, y)) qrframe[x + y * width] ^= 1; } break; case5: for (r3y = 0, y = 0; y < width; y++ , r3y++) { if (r3y == 3) r3y = 0; for (r3x = 0, x = 0; x < width; x++ , r3x++) { if (r3x == 3) r3x = 0; if (!((x & y & 1) + !(!r3x | !r3y)) && !ismasked(x, y)) qrframe[x + y * width] ^= 1; } } break; case6: for (r3y = 0, y = 0; y < width; y++ , r3y++) { if (r3y == 3) r3y = 0; for (r3x = 0, x = 0; x < width; x++ , r3x++) { if (r3x == 3) r3x = 0; if (!(((x & y & 1) + (r3x && (r3x == r3y))) & 1) && !ismasked(x, y)) qrframe[x + y * width] ^= 1; } } break; case7: for (r3y = 0, y = 0; y < width; y++ , r3y++) { if (r3y == 3) r3y = 0; for (r3x = 0, x = 0; x < width; x++ , r3x++) { if (r3x == 3) r3x = 0; if (!(((r3x && (r3x == r3y)) + ((x + y) & 1)) & 1) && !ismasked(x, y)) qrframe[x + y * width] ^= 1; } } break; } return; }
// Using the table of the length of each run, calculate the amount of bad image // - long runs or those that look like finders; called twice, once each for X and Y functionbadruns(length) { var i; var runsbad = 0; for (i = 0; i <= length; i++) if (rlens[i] >= 5) runsbad += N1 + rlens[i] - 5; // BwBBBwB as in finder for (i = 3; i < length - 1; i += 2) if (rlens[i - 2] == rlens[i + 2] && rlens[i + 2] == rlens[i - 1] && rlens[i - 1] == rlens[i + 1] && rlens[i - 1] * 3 == rlens[i] // white around the black pattern? Not part of spec && (rlens[i - 3] == 0// beginning || i + 3 > length // end || rlens[i - 3] * 3 >= rlens[i] * 4 || rlens[i + 3] * 3 >= rlens[i] * 4) ) runsbad += N3; return runsbad; }
// Calculate how bad the masked image is - blocks, imbalance, runs, or finders. functionbadcheck() { var x, y, h, b, b1; var thisbad = 0; var bw = 0;
// blocks of same color. for (y = 0; y < width - 1; y++) for (x = 0; x < width - 1; x++) if ((qrframe[x + width * y] && qrframe[(x + 1) + width * y] && qrframe[x + width * (y + 1)] && qrframe[(x + 1) + width * (y + 1)]) // all black || !(qrframe[x + width * y] || qrframe[(x + 1) + width * y] || qrframe[x + width * (y + 1)] || qrframe[(x + 1) + width * (y + 1)])) // all white thisbad += N2;
// X runs for (y = 0; y < width; y++) { rlens[0] = 0; for (h = b = x = 0; x < width; x++) { if ((b1 = qrframe[x + width * y]) == b) rlens[h]++; else rlens[++h] = 1; b = b1; bw += b ? 1 : -1; } thisbad += badruns(h); }
// black/white imbalance if (bw < 0) bw = -bw;
var big = bw; var count = 0; big += big << 2; big <<= 1; while (big > width * width) big -= width * width, count++; thisbad += count * N4;
// Y runs for (x = 0; x < width; x++) { rlens[0] = 0; for (h = b = y = 0; y < width; y++) { if ((b1 = qrframe[x + width * y]) == b) rlens[h]++; else rlens[++h] = 1; b = b1; } thisbad += badruns(h); } return thisbad; }
functiongenframe(instring) { var x, y, k, t, v, i, j, m;
// find the smallest version that fits the string t = instring.length; version = 0; do { version++; k = (ecclevel - 1) * 4 + (version - 1) * 16; neccblk1 = eccblocks[k++]; neccblk2 = eccblocks[k++]; datablkw = eccblocks[k++]; eccblkwid = eccblocks[k]; k = datablkw * (neccblk1 + neccblk2) + neccblk2 - 3 + (version <= 9); if (t <= k) break; } while (version < 40);
// FIXME - insure that it fits insted of being truncated width = 17 + 4 * version;
// allocate, clear and setup data structures v = datablkw + (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2; for (t = 0; t < v; t++) eccbuf[t] = 0; strinbuf = instring.slice(0);
for (t = 0; t < width * width; t++) qrframe[t] = 0;
for (t = 0; t < (width * (width + 1) + 1) / 2; t++) framask[t] = 0;
// insert finders - black to frame, white to mask for (t = 0; t < 3; t++) { k = 0; y = 0; if (t == 1) k = (width - 7); if (t == 2) y = (width - 7); qrframe[(y + 3) + width * (k + 3)] = 1; for (x = 0; x < 6; x++) { qrframe[(y + x) + width * k] = 1; qrframe[y + width * (k + x + 1)] = 1; qrframe[(y + 6) + width * (k + x)] = 1; qrframe[(y + x + 1) + width * (k + 6)] = 1; } for (x = 1; x < 5; x++) { setmask(y + x, k + 1); setmask(y + 1, k + x + 1); setmask(y + 5, k + x); setmask(y + x + 1, k + 5); } for (x = 2; x < 4; x++) { qrframe[(y + x) + width * (k + 2)] = 1; qrframe[(y + 2) + width * (k + x + 1)] = 1; qrframe[(y + 4) + width * (k + x)] = 1; qrframe[(y + x + 1) + width * (k + 4)] = 1; } }
// alignment blocks if (version > 1) { t = adelta[version]; y = width - 7; for (; ;) { x = width - 7; while (x > t - 3) { putalign(x, y); if (x < t) break; x -= t; } if (y <= t + 9) break; y -= t; putalign(6, y); putalign(y, 6); } }
// single black qrframe[8 + width * (width - 8)] = 1;
// timing gap - mask only for (y = 0; y < 7; y++) { setmask(7, y); setmask(width - 8, y); setmask(7, y + width - 7); } for (x = 0; x < 8; x++) { setmask(x, 7); setmask(x + width - 8, 7); setmask(x, width - 8); }
// reserve mask-format area for (x = 0; x < 9; x++) setmask(x, 8); for (x = 0; x < 8; x++) { setmask(x + width - 8, 8); setmask(8, x); } for (y = 0; y < 7; y++) setmask(8, y + width - 7);
// version block if (version > 6) { t = vpat[version - 7]; k = 17; for (x = 0; x < 6; x++) for (y = 0; y < 3; y++ , k--) if (1 & (k > 11 ? version >> (k - 12) : t >> k)) { qrframe[(5 - x) + width * (2 - y + width - 11)] = 1; qrframe[(2 - y + width - 11) + width * (5 - x)] = 1; } else { setmask(5 - x, 2 - y + width - 11); setmask(2 - y + width - 11, 5 - x); } }
// sync mask bits - only set above for white spaces, so add in black bits for (y = 0; y < width; y++) for (x = 0; x <= y; x++) if (qrframe[x + width * y]) setmask(x, y);
// convert string to bitstream // 8 bit data to QR-coded 8 bit data (numeric or alphanum, or kanji not supported) v = strinbuf.length;
// string to array for (i = 0; i < v; i++) eccbuf[i] = strinbuf.charCodeAt(i); strinbuf = eccbuf.slice(0);
// calculate max string length x = datablkw * (neccblk1 + neccblk2) + neccblk2; if (v >= x - 2) { v = x - 2; if (version > 9) v--; }
// shift and repack to insert length prefix i = v; if (version > 9) { strinbuf[i + 2] = 0; strinbuf[i + 3] = 0; while (i--) { t = strinbuf[i]; strinbuf[i + 3] |= 255 & (t << 4); strinbuf[i + 2] = t >> 4; } strinbuf[2] |= 255 & (v << 4); strinbuf[1] = v >> 4; strinbuf[0] = 0x40 | (v >> 12); } else { strinbuf[i + 1] = 0; strinbuf[i + 2] = 0; while (i--) { t = strinbuf[i]; strinbuf[i + 2] |= 255 & (t << 4); strinbuf[i + 1] = t >> 4; } strinbuf[1] |= 255 & (v << 4); strinbuf[0] = 0x40 | (v >> 4); } // fill to end with pad pattern i = v + 3 - (version < 10); while (i < x) { strinbuf[i++] = 0xec; // buffer has room if (i == x) break; strinbuf[i++] = 0x11; }
// calculate and append ECC
// calculate generator polynomial genpoly[0] = 1; for (i = 0; i < eccblkwid; i++) { genpoly[i + 1] = 1; for (j = i; j > 0; j--) genpoly[j] = genpoly[j] ? genpoly[j - 1] ^ gexp[modnn(glog[genpoly[j]] + i)] : genpoly[j - 1]; genpoly[0] = gexp[modnn(glog[genpoly[0]] + i)]; } for (i = 0; i <= eccblkwid; i++) genpoly[i] = glog[genpoly[i]]; // use logs for genpoly[] to save calc step
// append ecc to data buffer k = x; y = 0; for (i = 0; i < neccblk1; i++) { appendrs(y, datablkw, k, eccblkwid); y += datablkw; k += eccblkwid; } for (i = 0; i < neccblk2; i++) { appendrs(y, datablkw + 1, k, eccblkwid); y += datablkw + 1; k += eccblkwid; } // interleave blocks y = 0; for (i = 0; i < datablkw; i++) { for (j = 0; j < neccblk1; j++) eccbuf[y++] = strinbuf[i + j * datablkw]; for (j = 0; j < neccblk2; j++) eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))]; } for (j = 0; j < neccblk2; j++) eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))]; for (i = 0; i < eccblkwid; i++) for (j = 0; j < neccblk1 + neccblk2; j++) eccbuf[y++] = strinbuf[x + i + j * eccblkwid]; strinbuf = eccbuf;
// pack bits into frame avoiding masked area. x = y = width - 1; k = v = 1; // up, minus /* inteleaved data and ecc codes */ m = (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2; for (i = 0; i < m; i++) { t = strinbuf[i]; for (j = 0; j < 8; j++ , t <<= 1) { if (0x80 & t) qrframe[x + width * y] = 1; do { // find next fill position if (v) x--; else { x++; if (k) { if (y != 0) y--; else { x -= 2; k = !k; if (x == 6) { x--; y = 9; } } } else { if (y != width - 1) y++; else { x -= 2; k = !k; if (x == 6) { x--; y -= 8; } } } } v = !v; } while (ismasked(x, y)); } }
// save pre-mask copy of frame strinbuf = qrframe.slice(0); t = 0; // best y = 30000; // demerit // for instead of while since in original arduino code // if an early mask was "good enough" it wouldn't try for a better one // since they get more complex and take longer. for (k = 0; k < 8; k++) { applymask(k); // returns black-white imbalance x = badcheck(); if (x < y) { // current mask better than previous best? y = x; t = k; } if (t == 7) break; // don't increment i to a void redoing mask qrframe = strinbuf.slice(0); // reset for next pass } if (t != k) // redo best mask - none good enough, last wasn't t applymask(t);
// add in final mask/ecclevel bytes y = fmtword[t + ((ecclevel - 1) << 3)]; // low byte for (k = 0; k < 8; k++ , y >>= 1) if (y & 1) { qrframe[(width - 1 - k) + width * 8] = 1; if (k < 6) qrframe[8 + width * k] = 1; else qrframe[8 + width * (k + 1)] = 1; } // high byte for (k = 0; k < 7; k++ , y >>= 1) if (y & 1) { qrframe[8 + width * (width - 7 + k)] = 1; if (k) qrframe[(6 - k) + width * 8] = 1; else qrframe[7 + width * 8] = 1; } return qrframe; }
var _canvas = null;
var api = {
get ecclevel() { return ecclevel; },
set ecclevel(val) { ecclevel = val; },
get size() { return _size; },
set size(val) { _size = val },
get canvas() { return _canvas; },
set canvas(el) { _canvas = el; },
getFrame: function (string) { return genframe(string); }, //这里的utf16to8(str)是对Text中的字符串进行转码,让其支持中文 utf16to8: function (str) { var out, i, len, c;
out = ""; len = str.length; for (i = 0; i < len; i++) { c = str.charCodeAt(i); if ((c >= 0x0001) && (c <= 0x007F)) { out += str.charAt(i); } elseif (c > 0x07FF) { out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F)); out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F)); out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); } else { out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F)); out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); } } return out; }, /** * 新增$this参数,传入组件的this,兼容在组件中生成 */ draw: function (str, canvas, cavW, cavH, $this, ecc) { var that = this; ecclevel = ecc || ecclevel; canvas = canvas || _canvas; if (!canvas) { console.warn('No canvas provided to draw QR code in!') return; }
var size = Math.min(cavW, cavH); str = that.utf16to8(str);//增加中文显示