123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873 |
- import ErrorCorrection from './correction.js'
- import Alignment from './alignment.js'
- import Galois from './galois.js'
- import Version from './version.js'
- class Frame {
- _addAlignment(x, y) {
- let i;
- let buffer = this.buffer;
- let width = this.width;
- buffer[x + (width * y)] = 1;
- for (i = -2; i < 2; i++) {
- buffer[x + i + (width * (y - 2))] = 1;
- buffer[x - 2 + (width * (y + i + 1))] = 1;
- buffer[x + 2 + (width * (y + i))] = 1;
- buffer[x + i + 1 + (width * (y + 2))] = 1;
- }
- for (i = 0; i < 2; i++) {
- this._setMask(x - 1, y + i);
- this._setMask(x + 1, y - i);
- this._setMask(x - i, y - 1);
- this._setMask(x + i, y + 1);
- }
- }
- _appendData(data, dataLength, ecc, eccLength) {
- let bit, i, j;
- let polynomial = this._polynomial;
- let stringBuffer = this._stringBuffer;
- for (i = 0; i < eccLength; i++) {
- stringBuffer[ecc + i] = 0;
- }
- for (i = 0; i < dataLength; i++) {
- bit = Galois.LOG[stringBuffer[data + i] ^ stringBuffer[ecc]];
- if (bit !== 255) {
- for (j = 1; j < eccLength; j++) {
- stringBuffer[ecc + j - 1] = stringBuffer[ecc + j] ^
- Galois.EXPONENT[this._modN(bit + polynomial[eccLength - j])];
- }
- } else {
- for (j = ecc; j < ecc + eccLength; j++) {
- stringBuffer[j] = stringBuffer[j + 1];
- }
- }
- stringBuffer[ecc + eccLength - 1] = bit === 255 ? 0 : Galois.EXPONENT[this._modN(bit + polynomial[0])];
- }
- }
- _appendEccToData() {
- let i;
- let data = 0;
- let dataBlock = this._dataBlock;
- let ecc = this._calculateMaxLength();
- let eccBlock = this._eccBlock;
- for (i = 0; i < this._neccBlock1; i++) {
- this._appendData(data, dataBlock, ecc, eccBlock);
- data += dataBlock;
- ecc += eccBlock;
- }
- for (i = 0; i < this._neccBlock2; i++) {
- this._appendData(data, dataBlock + 1, ecc, eccBlock);
- data += dataBlock + 1;
- ecc += eccBlock;
- }
- }
- _applyMask(mask) {
- let r3x, r3y, x, y;
- let buffer = this.buffer;
- let width = this.width;
- switch (mask) {
- case 0:
- for (y = 0; y < width; y++) {
- for (x = 0; x < width; x++) {
- if (!((x + y) & 1) && !this._isMasked(x, y)) {
- buffer[x + (y * width)] ^= 1;
- }
- }
- }
- break;
- case 1:
- for (y = 0; y < width; y++) {
- for (x = 0; x < width; x++) {
- if (!(y & 1) && !this._isMasked(x, y)) {
- buffer[x + (y * width)] ^= 1;
- }
- }
- }
- break;
- case 2:
- for (y = 0; y < width; y++) {
- for (r3x = 0, x = 0; x < width; x++, r3x++) {
- if (r3x === 3) {
- r3x = 0;
- }
- if (!r3x && !this._isMasked(x, y)) {
- buffer[x + (y * width)] ^= 1;
- }
- }
- }
- break;
- case 3:
- 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 && !this._isMasked(x, y)) {
- buffer[x + (y * width)] ^= 1;
- }
- }
- }
- break;
- case 4:
- 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 && !this._isMasked(x, y)) {
- buffer[x + (y * width)] ^= 1;
- }
- }
- }
- break;
- case 5:
- 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)) && !this._isMasked(x, y)) {
- buffer[x + (y * width)] ^= 1;
- }
- }
- }
- break;
- case 6:
- 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) && !this._isMasked(x, y)) {
- buffer[x + (y * width)] ^= 1;
- }
- }
- }
- break;
- case 7:
- 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) && !this._isMasked(x, y)) {
- buffer[x + (y * width)] ^= 1;
- }
- }
- }
- break;
- }
- }
- _calculateMaxLength() {
- return (this._dataBlock * (this._neccBlock1 + this._neccBlock2)) + this._neccBlock2;
- }
- _calculatePolynomial() {
- let i, j;
- let eccBlock = this._eccBlock;
- let polynomial = this._polynomial;
- polynomial[0] = 1;
- for (i = 0; i < eccBlock; i++) {
- polynomial[i + 1] = 1;
- for (j = i; j > 0; j--) {
- polynomial[j] = polynomial[j] ? polynomial[j - 1] ^
- Galois.EXPONENT[this._modN(Galois.LOG[polynomial[j]] + i)] : polynomial[j - 1];
- }
- polynomial[0] = Galois.EXPONENT[this._modN(Galois.LOG[polynomial[0]] + i)];
- }
- // Use logs for generator polynomial to save calculation step.
- for (i = 0; i <= eccBlock; i++) {
- polynomial[i] = Galois.LOG[polynomial[i]];
- }
- }
- _checkBadness() {
- let b, b1, h, x, y;
- let bad = 0;
- let badness = this._badness;
- let buffer = this.buffer;
- let width = this.width;
- // Blocks of same colour.
- for (y = 0; y < width - 1; y++) {
- for (x = 0; x < width - 1; x++) {
- // All foreground colour.
- if ((buffer[x + (width * y)] &&
- buffer[x + 1 + (width * y)] &&
- buffer[x + (width * (y + 1))] &&
- buffer[x + 1 + (width * (y + 1))]) ||
- // All background colour.
- !(buffer[x + (width * y)] ||
- buffer[x + 1 + (width * y)] ||
- buffer[x + (width * (y + 1))] ||
- buffer[x + 1 + (width * (y + 1))])) {
- bad += this.N2;
- }
- }
- }
- let bw = 0;
- // X runs.
- for (y = 0; y < width; y++) {
- h = 0;
- badness[0] = 0;
- for (b = 0, x = 0; x < width; x++) {
- b1 = buffer[x + (width * y)];
- if (b === b1) {
- badness[h]++;
- } else {
- badness[++h] = 1;
- }
- b = b1;
- bw += b ? 1 : -1;
- }
- bad += this._getBadness(h);
- }
- if (bw < 0) {
- bw = -bw;
- }
- let count = 0;
- let big = bw;
- big += big << 2;
- big <<= 1;
- while (big > width * width) {
- big -= width * width;
- count++;
- }
- bad += count * this.N4;
- // Y runs.
- for (x = 0; x < width; x++) {
- h = 0;
- badness[0] = 0;
- for (b = 0, y = 0; y < width; y++) {
- b1 = buffer[x + (width * y)];
- if (b === b1) {
- badness[h]++;
- } else {
- badness[++h] = 1;
- }
- b = b1;
- }
- bad += this._getBadness(h);
- }
- return bad;
- }
- _convertBitStream(length) {
- let bit, i;
- let ecc = this._ecc;
- let version = this._version;
- // Convert string to bit stream. 8-bit data to QR-coded 8-bit data (numeric, alphanumeric, or kanji not supported).
- for (i = 0; i < length; i++) {
- ecc[i] = this._value.charCodeAt(i);
- }
- let stringBuffer = this._stringBuffer = ecc.slice();
- let maxLength = this._calculateMaxLength();
- if (length >= maxLength - 2) {
- length = maxLength - 2;
- if (version > 9) {
- length--;
- }
- }
- // Shift and re-pack to insert length prefix.
- let index = length;
- if (version > 9) {
- stringBuffer[index + 2] = 0;
- stringBuffer[index + 3] = 0;
- while (index--) {
- bit = stringBuffer[index];
- stringBuffer[index + 3] |= 255 & (bit << 4);
- stringBuffer[index + 2] = bit >> 4;
- }
- stringBuffer[2] |= 255 & (length << 4);
- stringBuffer[1] = length >> 4;
- stringBuffer[0] = 0x40 | (length >> 12);
- } else {
- stringBuffer[index + 1] = 0;
- stringBuffer[index + 2] = 0;
- while (index--) {
- bit = stringBuffer[index];
- stringBuffer[index + 2] |= 255 & (bit << 4);
- stringBuffer[index + 1] = bit >> 4;
- }
- stringBuffer[1] |= 255 & (length << 4);
- stringBuffer[0] = 0x40 | (length >> 4);
- }
- // Fill to end with pad pattern.
- index = length + 3 - (version < 10);
- while (index < maxLength) {
- stringBuffer[index++] = 0xec;
- stringBuffer[index++] = 0x11;
- }
- }
- _getBadness(length) {
- let i;
- let badRuns = 0;
- let badness = this._badness;
- for (i = 0; i <= length; i++) {
- if (badness[i] >= 5) {
- badRuns += this.N1 + badness[i] - 5;
- }
- }
- // FBFFFBF as in finder.
- for (i = 3; i < length - 1; i += 2) {
- if (badness[i - 2] === badness[i + 2] &&
- badness[i + 2] === badness[i - 1] &&
- badness[i - 1] === badness[i + 1] &&
- badness[i - 1] * 3 === badness[i] &&
- // Background around the foreground pattern? Not part of the specs.
- (badness[i - 3] === 0 || i + 3 > length ||
- badness[i - 3] * 3 >= badness[i] * 4 ||
- badness[i + 3] * 3 >= badness[i] * 4)) {
- badRuns += this.N3;
- }
- }
- return badRuns;
- }
- _finish() {
- // Save pre-mask copy of frame.
- this._stringBuffer = this.buffer.slice();
- let currentMask, i;
- let bit = 0;
- let mask = 30000;
- /*
- * Using 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 (i = 0; i < 8; i++) {
- // Returns foreground-background imbalance.
- this._applyMask(i);
- currentMask = this._checkBadness();
- // Is current mask better than previous best?
- if (currentMask < mask) {
- mask = currentMask;
- bit = i;
- }
- // Don't increment "i" to a void redoing mask.
- if (bit === 7) {
- break;
- }
- // Reset for next pass.
- this.buffer = this._stringBuffer.slice();
- }
- // Redo best mask as none were "good enough" (i.e. last wasn't bit).
- if (bit !== i) {
- this._applyMask(bit);
- }
- // Add in final mask/ECC level bytes.
- mask = ErrorCorrection.FINAL_FORMAT[bit + (this._level - 1 << 3)];
- let buffer = this.buffer;
- let width = this.width;
- // Low byte.
- for (i = 0; i < 8; i++, mask >>= 1) {
- if (mask & 1) {
- buffer[width - 1 - i + (width * 8)] = 1;
- if (i < 6) {
- buffer[8 + (width * i)] = 1;
- } else {
- buffer[8 + (width * (i + 1))] = 1;
- }
- }
- }
- // High byte.
- for (i = 0; i < 7; i++, mask >>= 1) {
- if (mask & 1) {
- buffer[8 + (width * (width - 7 + i))] = 1;
- if (i) {
- buffer[6 - i + (width * 8)] = 1;
- } else {
- buffer[7 + (width * 8)] = 1;
- }
- }
- }
- }
- _interleaveBlocks() {
- let i, j;
- let dataBlock = this._dataBlock;
- let ecc = this._ecc;
- let eccBlock = this._eccBlock;
- let k = 0;
- let maxLength = this._calculateMaxLength();
- let neccBlock1 = this._neccBlock1;
- let neccBlock2 = this._neccBlock2;
- let stringBuffer = this._stringBuffer;
- for (i = 0; i < dataBlock; i++) {
- for (j = 0; j < neccBlock1; j++) {
- ecc[k++] = stringBuffer[i + (j * dataBlock)];
- }
- for (j = 0; j < neccBlock2; j++) {
- ecc[k++] = stringBuffer[(neccBlock1 * dataBlock) + i + (j * (dataBlock + 1))];
- }
- }
- for (j = 0; j < neccBlock2; j++) {
- ecc[k++] = stringBuffer[(neccBlock1 * dataBlock) + i + (j * (dataBlock + 1))];
- }
- for (i = 0; i < eccBlock; i++) {
- for (j = 0; j < neccBlock1 + neccBlock2; j++) {
- ecc[k++] = stringBuffer[maxLength + i + (j * eccBlock)];
- }
- }
- this._stringBuffer = ecc;
- }
- _insertAlignments() {
- let i, x, y;
- let version = this._version;
- let width = this.width;
- if (version > 1) {
- i = Alignment.BLOCK[version];
- y = width - 7;
- for (; ;) {
- x = width - 7;
- while (x > i - 3) {
- this._addAlignment(x, y);
- if (x < i) {
- break;
- }
- x -= i;
- }
- if (y <= i + 9) {
- break;
- }
- y -= i;
- this._addAlignment(6, y);
- this._addAlignment(y, 6);
- }
- }
- }
- _insertFinders() {
- let i, j, x, y;
- let buffer = this.buffer;
- let width = this.width;
- for (i = 0; i < 3; i++) {
- j = 0;
- y = 0;
- if (i === 1) {
- j = width - 7;
- }
- if (i === 2) {
- y = width - 7;
- }
- buffer[y + 3 + (width * (j + 3))] = 1;
- for (x = 0; x < 6; x++) {
- buffer[y + x + (width * j)] = 1;
- buffer[y + (width * (j + x + 1))] = 1;
- buffer[y + 6 + (width * (j + x))] = 1;
- buffer[y + x + 1 + (width * (j + 6))] = 1;
- }
- for (x = 1; x < 5; x++) {
- this._setMask(y + x, j + 1);
- this._setMask(y + 1, j + x + 1);
- this._setMask(y + 5, j + x);
- this._setMask(y + x + 1, j + 5);
- }
- for (x = 2; x < 4; x++) {
- buffer[y + x + (width * (j + 2))] = 1;
- buffer[y + 2 + (width * (j + x + 1))] = 1;
- buffer[y + 4 + (width * (j + x))] = 1;
- buffer[y + x + 1 + (width * (j + 4))] = 1;
- }
- }
- }
- _insertTimingGap() {
- let x, y;
- let width = this.width;
- for (y = 0; y < 7; y++) {
- this._setMask(7, y);
- this._setMask(width - 8, y);
- this._setMask(7, y + width - 7);
- }
- for (x = 0; x < 8; x++) {
- this._setMask(x, 7);
- this._setMask(x + width - 8, 7);
- this._setMask(x, width - 8);
- }
- }
- _insertTimingRowAndColumn() {
- let x;
- let buffer = this.buffer;
- let width = this.width;
- for (x = 0; x < width - 14; x++) {
- if (x & 1) {
- this._setMask(8 + x, 6);
- this._setMask(6, 8 + x);
- } else {
- buffer[8 + x + (width * 6)] = 1;
- buffer[6 + (width * (8 + x))] = 1;
- }
- }
- }
- _insertVersion() {
- let i, j, x, y;
- let buffer = this.buffer;
- let version = this._version;
- let width = this.width;
- if (version > 6) {
- i = Version.BLOCK[version - 7];
- j = 17;
- for (x = 0; x < 6; x++) {
- for (y = 0; y < 3; y++, j--) {
- if (1 & (j > 11 ? version >> j - 12 : i >> j)) {
- buffer[5 - x + (width * (2 - y + width - 11))] = 1;
- buffer[2 - y + width - 11 + (width * (5 - x))] = 1;
- } else {
- this._setMask(5 - x, 2 - y + width - 11);
- this._setMask(2 - y + width - 11, 5 - x);
- }
- }
- }
- }
- }
- _isMasked(x, y) {
- let bit = this._getMaskBit(x, y);
- return this._mask[bit] === 1;
- }
- _pack() {
- let bit, i, j;
- let k = 1;
- let v = 1;
- let width = this.width;
- let x = width - 1;
- let y = width - 1;
- // Interleaved data and ECC codes.
- let length = ((this._dataBlock + this._eccBlock) * (this._neccBlock1 + this._neccBlock2)) + this._neccBlock2;
- for (i = 0; i < length; i++) {
- bit = this._stringBuffer[i];
- for (j = 0; j < 8; j++, bit <<= 1) {
- if (0x80 & bit) {
- this.buffer[x + (width * y)] = 1;
- }
- // Find next fill position.
- do {
- 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 (this._isMasked(x, y));
- }
- }
- }
- _reverseMask() {
- let x, y;
- let width = this.width;
- for (x = 0; x < 9; x++) {
- this._setMask(x, 8);
- }
- for (x = 0; x < 8; x++) {
- this._setMask(x + width - 8, 8);
- this._setMask(8, x);
- }
- for (y = 0; y < 7; y++) {
- this._setMask(8, y + width - 7);
- }
- }
- _setMask(x, y) {
- let bit = this._getMaskBit(x, y);
- this._mask[bit] = 1;
- }
- _syncMask() {
- let x, y;
- let width = this.width;
- for (y = 0; y < width; y++) {
- for (x = 0; x <= y; x++) {
- if (this.buffer[x + (width * y)]) {
- this._setMask(x, y);
- }
- }
- }
- }
- _createArray(length) {
- let i;
- let array = [];
- for (i = 0; i < length; i++) {
- array[i] = 0;
- }
- return array;
- }
- _getMaskBit(x, y) {
- let bit;
- if (x > y) {
- bit = x;
- x = y;
- y = bit;
- }
- bit = y;
- bit += y * y;
- bit >>= 1;
- bit += x;
- return bit;
- }
- _modN(x) {
- while (x >= 255) {
- x -= 255;
- x = (x >> 8) + (x & 255);
- }
- return x;
- }
- // *Badness* coefficients.
- N1 = 3;
- N2 = 3;
- N3 = 40;
- N4 = 10;
- constructor(options) {
- let dataBlock, eccBlock, index, neccBlock1, neccBlock2;
- let valueLength = options.value.length;
- this._badness = [];
- this._level = ErrorCorrection.LEVELS[options.level];
- this._polynomial = [];
- this._value = options.value;
- this._version = 0;
- this._stringBuffer = [];
- while (this._version < 40) {
- this._version++;
- index = ((this._level - 1) * 4) + ((this._version - 1) * 16);
- neccBlock1 = ErrorCorrection.BLOCKS[index++];
- neccBlock2 = ErrorCorrection.BLOCKS[index++];
- dataBlock = ErrorCorrection.BLOCKS[index++];
- eccBlock = ErrorCorrection.BLOCKS[index];
- index = (dataBlock * (neccBlock1 + neccBlock2)) + neccBlock2 - 3 + (this._version <= 9);
- if (valueLength <= index) {
- break;
- }
- }
- this._dataBlock = dataBlock;
- this._eccBlock = eccBlock;
- this._neccBlock1 = neccBlock1;
- this._neccBlock2 = neccBlock2;
- /**
- * The data width is based on version.
- *
- * @public
- * @type {number}
- * @memberof Frame#
- */
- // FIXME: Ensure that it fits instead of being truncated.
- let width = this.width = 17 + (4 * this._version);
- /**
- * The image buffer.
- *
- * @public
- * @type {number[]}
- * @memberof Frame#
- */
- this.buffer = this._createArray(width * width);
- this._ecc = this._createArray(dataBlock + ((dataBlock + eccBlock) * (neccBlock1 + neccBlock2)) + neccBlock2);
- this._mask = this._createArray(((width * (width + 1)) + 1) / 2);
- this._insertFinders();
- this._insertAlignments();
- // Insert single foreground cell.
- this.buffer[8 + (width * (width - 8))] = 1;
- this._insertTimingGap();
- this._reverseMask();
- this._insertTimingRowAndColumn();
- this._insertVersion();
- this._syncMask();
- this._convertBitStream(valueLength);
- this._calculatePolynomial();
- this._appendEccToData();
- this._interleaveBlocks();
- this._pack();
- this._finish();
- }
- }
- export default Frame
|