diff --git a/dist/decoder/decodeData/index.d.ts b/dist/decoder/decodeData/index.d.ts index 732b0f9b..83fb8935 100644 --- a/dist/decoder/decodeData/index.d.ts +++ b/dist/decoder/decodeData/index.d.ts @@ -21,6 +21,6 @@ export declare enum Mode { Alphanumeric = "alphanumeric", Byte = "byte", Kanji = "kanji", - ECI = "eci", + ECI = "eci" } export declare function decode(data: Uint8ClampedArray, version: number): DecodedQR; diff --git a/dist/jsQR.js b/dist/jsQR.js index f749098d..2e756b2c 100644 --- a/dist/jsQR.js +++ b/dist/jsQR.js @@ -225,6 +225,7 @@ var GenericGFPoly = /** @class */ (function () { return this.coefficients[this.coefficients.length - 1 - degree]; }; GenericGFPoly.prototype.addOrSubtract = function (other) { + var _a; if (this.isZero()) { return other; } @@ -245,7 +246,6 @@ var GenericGFPoly = /** @class */ (function () { sumDiff[i] = GenericGF_1.addOrSubtractGF(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); } return new GenericGFPoly(this.field, sumDiff); - var _a; }; GenericGFPoly.prototype.multiply = function (scalar) { if (scalar === 0) { @@ -329,30 +329,33 @@ var decoder_1 = __webpack_require__(5); var extractor_1 = __webpack_require__(11); var locator_1 = __webpack_require__(12); function scan(matrix) { - var location = locator_1.locate(matrix); - if (!location) { + var locations = locator_1.locate(matrix); + if (!locations) { return null; } - var extracted = extractor_1.extract(matrix, location); - var decoded = decoder_1.decode(extracted.matrix); - if (!decoded) { - return null; + for (var _i = 0, locations_1 = locations; _i < locations_1.length; _i++) { + var location_1 = locations_1[_i]; + var extracted = extractor_1.extract(matrix, location_1); + var decoded = decoder_1.decode(extracted.matrix); + if (decoded) { + return { + binaryData: decoded.bytes, + data: decoded.text, + chunks: decoded.chunks, + location: { + topRightCorner: extracted.mappingFunction(location_1.dimension, 0), + topLeftCorner: extracted.mappingFunction(0, 0), + bottomRightCorner: extracted.mappingFunction(location_1.dimension, location_1.dimension), + bottomLeftCorner: extracted.mappingFunction(0, location_1.dimension), + topRightFinderPattern: location_1.topRight, + topLeftFinderPattern: location_1.topLeft, + bottomLeftFinderPattern: location_1.bottomLeft, + bottomRightAlignmentPattern: location_1.alignmentPattern, + }, + }; + } } - return { - binaryData: decoded.bytes, - data: decoded.text, - chunks: decoded.chunks, - location: { - topRightCorner: extracted.mappingFunction(location.dimension, 0), - topLeftCorner: extracted.mappingFunction(0, 0), - bottomRightCorner: extracted.mappingFunction(location.dimension, location.dimension), - bottomLeftCorner: extracted.mappingFunction(0, location.dimension), - topRightFinderPattern: location.topRight, - topLeftFinderPattern: location.topLeft, - bottomLeftFinderPattern: location.bottomLeft, - bottomRightAlignmentPattern: location.alignmentPattern, - }, - }; + return null; } var defaultOptions = { inversionAttempts: "attemptBoth", @@ -599,7 +602,7 @@ function readCodewords(matrix, version, formatInfo) { // Read columns in pairs, from right to left var readingUp = true; for (var columnIndex = dimension - 1; columnIndex > 0; columnIndex -= 2) { - if (columnIndex === 6) { + if (columnIndex === 6) { // Skip whole column with vertical alignment pattern; columnIndex--; } for (var i = 0; i < dimension; i++) { @@ -613,7 +616,7 @@ function readCodewords(matrix, version, formatInfo) { bit = !bit; } currentByte = pushBit(bit, currentByte); - if (bitsRead === 8) { + if (bitsRead === 8) { // Whole bytes codewords.push(currentByte); bitsRead = 0; currentByte = 0; @@ -628,7 +631,7 @@ function readCodewords(matrix, version, formatInfo) { function readVersion(matrix) { var dimension = matrix.height; var provisionalVersion = Math.floor((dimension - 17) / 4); - if (provisionalVersion <= 6) { + if (provisionalVersion <= 6) { // 6 and under dont have version info in the QR code return version_1.VERSIONS[provisionalVersion - 1]; } var topRightVersionBits = 0; @@ -670,21 +673,21 @@ function readVersion(matrix) { function readFormatInformation(matrix) { var topLeftFormatInfoBits = 0; for (var x = 0; x <= 8; x++) { - if (x !== 6) { + if (x !== 6) { // Skip timing pattern bit topLeftFormatInfoBits = pushBit(matrix.get(x, 8), topLeftFormatInfoBits); } } for (var y = 7; y >= 0; y--) { - if (y !== 6) { + if (y !== 6) { // Skip timing pattern bit topLeftFormatInfoBits = pushBit(matrix.get(8, y), topLeftFormatInfoBits); } } var dimension = matrix.height; var topRightBottomRightFormatInfoBits = 0; - for (var y = dimension - 1; y >= dimension - 7; y--) { + for (var y = dimension - 1; y >= dimension - 7; y--) { // bottom left topRightBottomRightFormatInfoBits = pushBit(matrix.get(8, y), topRightBottomRightFormatInfoBits); } - for (var x = dimension - 8; x < dimension; x++) { + for (var x = dimension - 8; x < dimension; x++) { // top right topRightBottomRightFormatInfoBits = pushBit(matrix.get(x, 8), topRightBottomRightFormatInfoBits); } var bestDifference = Infinity; @@ -699,7 +702,7 @@ function readFormatInformation(matrix) { bestFormatInfo = formatInfo; bestDifference = difference; } - if (topLeftFormatInfoBits !== topRightBottomRightFormatInfoBits) { + if (topLeftFormatInfoBits !== topRightBottomRightFormatInfoBits) { // also try the other option difference = numBitsDiffering(topRightBottomRightFormatInfoBits, bits); if (difference < bestDifference) { bestFormatInfo = formatInfo; @@ -945,6 +948,7 @@ function decodeKanji(stream, size) { return { bytes: bytes, text: text }; } function decode(data, version) { + var _a, _b, _c, _d; var stream = new BitStream_1.BitStream(data); // There are 3 'sizes' based on the version. 1-9 is small (0), 10-26 is medium (1) and 27-40 is large (2). var size = version <= 9 ? 0 : version <= 26 ? 1 : 2; @@ -1024,7 +1028,10 @@ function decode(data, version) { }); } } - var _a, _b, _c, _d; + // If there is no data left, or the remaining bits are all 0, then that counts as a termination marker + if (stream.available() === 0 || stream.readBits(stream.available()) === 0) { + return result; + } } exports.decode = decode; @@ -8145,6 +8152,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); var GenericGF_1 = __webpack_require__(1); var GenericGFPoly_1 = __webpack_require__(2); function runEuclideanAlgorithm(field, a, b, R) { + var _a; // Assume a's degree is >= b's if (a.degree() < b.degree()) { _a = [b, a], a = _a[0], b = _a[1]; @@ -8185,7 +8193,6 @@ function runEuclideanAlgorithm(field, a, b, R) { } var inverse = field.inverse(sigmaTildeAtZero); return [t.multiply(inverse), r.multiply(inverse)]; - var _a; } function findErrorLocations(field, errorLocator) { // This is a direct application of Chien's search @@ -9590,7 +9597,7 @@ var BitMatrix_1 = __webpack_require__(0); function squareToQuadrilateral(p1, p2, p3, p4) { var dx3 = p1.x - p2.x + p3.x - p4.x; var dy3 = p1.y - p2.y + p3.y - p4.y; - if (dx3 === 0 && dy3 === 0) { + if (dx3 === 0 && dy3 === 0) { // Affine return { a11: p2.x - p1.x, a12: p2.y - p1.y, @@ -9696,6 +9703,7 @@ function sum(values) { } // Takes three finder patterns and organizes them into topLeft, topRight, etc function reorderFinderPatterns(pattern1, pattern2, pattern3) { + var _a, _b, _c, _d; // Find distances between pattern centers var oneTwoDistance = distance(pattern1, pattern2); var twoThreeDistance = distance(pattern2, pattern3); @@ -9720,7 +9728,6 @@ function reorderFinderPatterns(pattern1, pattern2, pattern3) { _d = [topRight, bottomLeft], bottomLeft = _d[0], topRight = _d[1]; } return { bottomLeft: bottomLeft, topLeft: topLeft, topRight: topRight }; - var _a, _b, _c, _d; } // Computes the dimension (number of modules on a side) of the QR Code based on the position of the finder patterns function computeDimension(topLeft, topRight, bottomLeft, matrix) { @@ -9810,13 +9817,13 @@ function countBlackWhiteRunTowardsPoint(origin, end, matrix, length) { // along the line that intersects with the end point. Returns an array of elements, representing the pixel sizes // of the black white run. Takes a length which represents the number of switches from black to white to look for. function countBlackWhiteRun(origin, end, matrix, length) { + var _a; var rise = end.y - origin.y; var run = end.x - origin.x; var towardsEnd = countBlackWhiteRunTowardsPoint(origin, end, matrix, Math.ceil(length / 2)); var awayFromEnd = countBlackWhiteRunTowardsPoint(origin, { x: origin.x - run, y: origin.y - rise }, matrix, Math.ceil(length / 2)); var middleValue = towardsEnd.shift() + awayFromEnd.shift() - 1; // Substract one so we don't double count a pixel return (_a = awayFromEnd.concat(middleValue)).concat.apply(_a, towardsEnd); - var _a; } // Takes in a black white run and an array of expected ratios. Returns the average size of the run as well as the "error" - // that is the amount the run diverges from the expected ratio @@ -9864,6 +9871,27 @@ function scorePattern(point, ratios, matrix) { return Infinity; } } +function recenterLocation(matrix, p) { + var leftX = Math.round(p.x); + while (matrix.get(leftX, Math.round(p.y))) { + leftX--; + } + var rightX = Math.round(p.x); + while (matrix.get(rightX, Math.round(p.y))) { + rightX++; + } + var x = (leftX + rightX) / 2; + var topY = Math.round(p.y); + while (matrix.get(Math.round(x), topY)) { + topY--; + } + var bottomY = Math.round(p.y); + while (matrix.get(Math.round(x), bottomY)) { + bottomY++; + } + var y = (topY + bottomY) / 2; + return { x: x, y: y }; +} function locate(matrix) { var finderPatternQuads = []; var activeFinderPatternQuads = []; @@ -9966,6 +9994,7 @@ function locate(matrix) { }) .filter(function (q) { return !!q; }) // Filter out any rejected quads from above .sort(function (a, b) { return a.score - b.score; }) + // Now take the top finder pattern options and try to find 2 other options with a similar size. .map(function (point, i, finderPatterns) { if (i > MAX_FINDERPATTERNS_TO_SEARCH) { return null; @@ -9986,12 +10015,49 @@ function locate(matrix) { return null; } var _a = reorderFinderPatterns(finderPatternGroups[0].points[0], finderPatternGroups[0].points[1], finderPatternGroups[0].points[2]), topRight = _a.topRight, topLeft = _a.topLeft, bottomLeft = _a.bottomLeft; + var alignment = findAlignmentPattern(matrix, alignmentPatternQuads, topRight, topLeft, bottomLeft); + var result = []; + if (alignment) { + result.push({ + alignmentPattern: { x: alignment.alignmentPattern.x, y: alignment.alignmentPattern.y }, + bottomLeft: { x: bottomLeft.x, y: bottomLeft.y }, + dimension: alignment.dimension, + topLeft: { x: topLeft.x, y: topLeft.y }, + topRight: { x: topRight.x, y: topRight.y }, + }); + } + // We normally use the center of the quads as the location of the tracking points, which is optimal for most cases and will account + // for a skew in the image. However, In some cases, a slight skew might not be real and instead be caused by image compression + // errors and/or low resolution. For those cases, we'd be better off centering the point exactly in the middle of the black area. We + // compute and return the location data for the naively centered points as it is little additional work and allows for multiple + // attempts at decoding harder images. + var midTopRight = recenterLocation(matrix, topRight); + var midTopLeft = recenterLocation(matrix, topLeft); + var midBottomLeft = recenterLocation(matrix, bottomLeft); + var centeredAlignment = findAlignmentPattern(matrix, alignmentPatternQuads, midTopRight, midTopLeft, midBottomLeft); + if (centeredAlignment) { + result.push({ + alignmentPattern: { x: centeredAlignment.alignmentPattern.x, y: centeredAlignment.alignmentPattern.y }, + bottomLeft: { x: midBottomLeft.x, y: midBottomLeft.y }, + topLeft: { x: midTopLeft.x, y: midTopLeft.y }, + topRight: { x: midTopRight.x, y: midTopRight.y }, + dimension: centeredAlignment.dimension, + }); + } + if (result.length === 0) { + return null; + } + return result; +} +exports.locate = locate; +function findAlignmentPattern(matrix, alignmentPatternQuads, topRight, topLeft, bottomLeft) { + var _a; // Now that we've found the three finder patterns we can determine the blockSize and the size of the QR code. // We'll use these to help find the alignment pattern but also later when we do the extraction. var dimension; var moduleSize; try { - (_b = computeDimension(topLeft, topRight, bottomLeft, matrix), dimension = _b.dimension, moduleSize = _b.moduleSize); + (_a = computeDimension(topLeft, topRight, bottomLeft, matrix), dimension = _a.dimension, moduleSize = _a.moduleSize); } catch (e) { return null; @@ -10025,16 +10091,8 @@ function locate(matrix) { // If there are less than 15 modules between finder patterns it's a version 1 QR code and as such has no alignmemnt pattern // so we can only use our best guess. var alignmentPattern = modulesBetweenFinderPatterns >= 15 && alignmentPatterns.length ? alignmentPatterns[0] : expectedAlignmentPattern; - return { - alignmentPattern: { x: alignmentPattern.x, y: alignmentPattern.y }, - bottomLeft: { x: bottomLeft.x, y: bottomLeft.y }, - dimension: dimension, - topLeft: { x: topLeft.x, y: topLeft.y }, - topRight: { x: topRight.x, y: topRight.y }, - }; - var _b; + return { alignmentPattern: alignmentPattern, dimension: dimension }; } -exports.locate = locate; /***/ }) diff --git a/dist/locator/index.d.ts b/dist/locator/index.d.ts index 5698fb5f..11316062 100644 --- a/dist/locator/index.d.ts +++ b/dist/locator/index.d.ts @@ -10,4 +10,4 @@ export interface QRLocation { alignmentPattern: Point; dimension: number; } -export declare function locate(matrix: BitMatrix): QRLocation; +export declare function locate(matrix: BitMatrix): QRLocation[]; diff --git a/docs/jsQR.js b/docs/jsQR.js index f749098d..2e756b2c 100644 --- a/docs/jsQR.js +++ b/docs/jsQR.js @@ -225,6 +225,7 @@ var GenericGFPoly = /** @class */ (function () { return this.coefficients[this.coefficients.length - 1 - degree]; }; GenericGFPoly.prototype.addOrSubtract = function (other) { + var _a; if (this.isZero()) { return other; } @@ -245,7 +246,6 @@ var GenericGFPoly = /** @class */ (function () { sumDiff[i] = GenericGF_1.addOrSubtractGF(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); } return new GenericGFPoly(this.field, sumDiff); - var _a; }; GenericGFPoly.prototype.multiply = function (scalar) { if (scalar === 0) { @@ -329,30 +329,33 @@ var decoder_1 = __webpack_require__(5); var extractor_1 = __webpack_require__(11); var locator_1 = __webpack_require__(12); function scan(matrix) { - var location = locator_1.locate(matrix); - if (!location) { + var locations = locator_1.locate(matrix); + if (!locations) { return null; } - var extracted = extractor_1.extract(matrix, location); - var decoded = decoder_1.decode(extracted.matrix); - if (!decoded) { - return null; + for (var _i = 0, locations_1 = locations; _i < locations_1.length; _i++) { + var location_1 = locations_1[_i]; + var extracted = extractor_1.extract(matrix, location_1); + var decoded = decoder_1.decode(extracted.matrix); + if (decoded) { + return { + binaryData: decoded.bytes, + data: decoded.text, + chunks: decoded.chunks, + location: { + topRightCorner: extracted.mappingFunction(location_1.dimension, 0), + topLeftCorner: extracted.mappingFunction(0, 0), + bottomRightCorner: extracted.mappingFunction(location_1.dimension, location_1.dimension), + bottomLeftCorner: extracted.mappingFunction(0, location_1.dimension), + topRightFinderPattern: location_1.topRight, + topLeftFinderPattern: location_1.topLeft, + bottomLeftFinderPattern: location_1.bottomLeft, + bottomRightAlignmentPattern: location_1.alignmentPattern, + }, + }; + } } - return { - binaryData: decoded.bytes, - data: decoded.text, - chunks: decoded.chunks, - location: { - topRightCorner: extracted.mappingFunction(location.dimension, 0), - topLeftCorner: extracted.mappingFunction(0, 0), - bottomRightCorner: extracted.mappingFunction(location.dimension, location.dimension), - bottomLeftCorner: extracted.mappingFunction(0, location.dimension), - topRightFinderPattern: location.topRight, - topLeftFinderPattern: location.topLeft, - bottomLeftFinderPattern: location.bottomLeft, - bottomRightAlignmentPattern: location.alignmentPattern, - }, - }; + return null; } var defaultOptions = { inversionAttempts: "attemptBoth", @@ -599,7 +602,7 @@ function readCodewords(matrix, version, formatInfo) { // Read columns in pairs, from right to left var readingUp = true; for (var columnIndex = dimension - 1; columnIndex > 0; columnIndex -= 2) { - if (columnIndex === 6) { + if (columnIndex === 6) { // Skip whole column with vertical alignment pattern; columnIndex--; } for (var i = 0; i < dimension; i++) { @@ -613,7 +616,7 @@ function readCodewords(matrix, version, formatInfo) { bit = !bit; } currentByte = pushBit(bit, currentByte); - if (bitsRead === 8) { + if (bitsRead === 8) { // Whole bytes codewords.push(currentByte); bitsRead = 0; currentByte = 0; @@ -628,7 +631,7 @@ function readCodewords(matrix, version, formatInfo) { function readVersion(matrix) { var dimension = matrix.height; var provisionalVersion = Math.floor((dimension - 17) / 4); - if (provisionalVersion <= 6) { + if (provisionalVersion <= 6) { // 6 and under dont have version info in the QR code return version_1.VERSIONS[provisionalVersion - 1]; } var topRightVersionBits = 0; @@ -670,21 +673,21 @@ function readVersion(matrix) { function readFormatInformation(matrix) { var topLeftFormatInfoBits = 0; for (var x = 0; x <= 8; x++) { - if (x !== 6) { + if (x !== 6) { // Skip timing pattern bit topLeftFormatInfoBits = pushBit(matrix.get(x, 8), topLeftFormatInfoBits); } } for (var y = 7; y >= 0; y--) { - if (y !== 6) { + if (y !== 6) { // Skip timing pattern bit topLeftFormatInfoBits = pushBit(matrix.get(8, y), topLeftFormatInfoBits); } } var dimension = matrix.height; var topRightBottomRightFormatInfoBits = 0; - for (var y = dimension - 1; y >= dimension - 7; y--) { + for (var y = dimension - 1; y >= dimension - 7; y--) { // bottom left topRightBottomRightFormatInfoBits = pushBit(matrix.get(8, y), topRightBottomRightFormatInfoBits); } - for (var x = dimension - 8; x < dimension; x++) { + for (var x = dimension - 8; x < dimension; x++) { // top right topRightBottomRightFormatInfoBits = pushBit(matrix.get(x, 8), topRightBottomRightFormatInfoBits); } var bestDifference = Infinity; @@ -699,7 +702,7 @@ function readFormatInformation(matrix) { bestFormatInfo = formatInfo; bestDifference = difference; } - if (topLeftFormatInfoBits !== topRightBottomRightFormatInfoBits) { + if (topLeftFormatInfoBits !== topRightBottomRightFormatInfoBits) { // also try the other option difference = numBitsDiffering(topRightBottomRightFormatInfoBits, bits); if (difference < bestDifference) { bestFormatInfo = formatInfo; @@ -945,6 +948,7 @@ function decodeKanji(stream, size) { return { bytes: bytes, text: text }; } function decode(data, version) { + var _a, _b, _c, _d; var stream = new BitStream_1.BitStream(data); // There are 3 'sizes' based on the version. 1-9 is small (0), 10-26 is medium (1) and 27-40 is large (2). var size = version <= 9 ? 0 : version <= 26 ? 1 : 2; @@ -1024,7 +1028,10 @@ function decode(data, version) { }); } } - var _a, _b, _c, _d; + // If there is no data left, or the remaining bits are all 0, then that counts as a termination marker + if (stream.available() === 0 || stream.readBits(stream.available()) === 0) { + return result; + } } exports.decode = decode; @@ -8145,6 +8152,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); var GenericGF_1 = __webpack_require__(1); var GenericGFPoly_1 = __webpack_require__(2); function runEuclideanAlgorithm(field, a, b, R) { + var _a; // Assume a's degree is >= b's if (a.degree() < b.degree()) { _a = [b, a], a = _a[0], b = _a[1]; @@ -8185,7 +8193,6 @@ function runEuclideanAlgorithm(field, a, b, R) { } var inverse = field.inverse(sigmaTildeAtZero); return [t.multiply(inverse), r.multiply(inverse)]; - var _a; } function findErrorLocations(field, errorLocator) { // This is a direct application of Chien's search @@ -9590,7 +9597,7 @@ var BitMatrix_1 = __webpack_require__(0); function squareToQuadrilateral(p1, p2, p3, p4) { var dx3 = p1.x - p2.x + p3.x - p4.x; var dy3 = p1.y - p2.y + p3.y - p4.y; - if (dx3 === 0 && dy3 === 0) { + if (dx3 === 0 && dy3 === 0) { // Affine return { a11: p2.x - p1.x, a12: p2.y - p1.y, @@ -9696,6 +9703,7 @@ function sum(values) { } // Takes three finder patterns and organizes them into topLeft, topRight, etc function reorderFinderPatterns(pattern1, pattern2, pattern3) { + var _a, _b, _c, _d; // Find distances between pattern centers var oneTwoDistance = distance(pattern1, pattern2); var twoThreeDistance = distance(pattern2, pattern3); @@ -9720,7 +9728,6 @@ function reorderFinderPatterns(pattern1, pattern2, pattern3) { _d = [topRight, bottomLeft], bottomLeft = _d[0], topRight = _d[1]; } return { bottomLeft: bottomLeft, topLeft: topLeft, topRight: topRight }; - var _a, _b, _c, _d; } // Computes the dimension (number of modules on a side) of the QR Code based on the position of the finder patterns function computeDimension(topLeft, topRight, bottomLeft, matrix) { @@ -9810,13 +9817,13 @@ function countBlackWhiteRunTowardsPoint(origin, end, matrix, length) { // along the line that intersects with the end point. Returns an array of elements, representing the pixel sizes // of the black white run. Takes a length which represents the number of switches from black to white to look for. function countBlackWhiteRun(origin, end, matrix, length) { + var _a; var rise = end.y - origin.y; var run = end.x - origin.x; var towardsEnd = countBlackWhiteRunTowardsPoint(origin, end, matrix, Math.ceil(length / 2)); var awayFromEnd = countBlackWhiteRunTowardsPoint(origin, { x: origin.x - run, y: origin.y - rise }, matrix, Math.ceil(length / 2)); var middleValue = towardsEnd.shift() + awayFromEnd.shift() - 1; // Substract one so we don't double count a pixel return (_a = awayFromEnd.concat(middleValue)).concat.apply(_a, towardsEnd); - var _a; } // Takes in a black white run and an array of expected ratios. Returns the average size of the run as well as the "error" - // that is the amount the run diverges from the expected ratio @@ -9864,6 +9871,27 @@ function scorePattern(point, ratios, matrix) { return Infinity; } } +function recenterLocation(matrix, p) { + var leftX = Math.round(p.x); + while (matrix.get(leftX, Math.round(p.y))) { + leftX--; + } + var rightX = Math.round(p.x); + while (matrix.get(rightX, Math.round(p.y))) { + rightX++; + } + var x = (leftX + rightX) / 2; + var topY = Math.round(p.y); + while (matrix.get(Math.round(x), topY)) { + topY--; + } + var bottomY = Math.round(p.y); + while (matrix.get(Math.round(x), bottomY)) { + bottomY++; + } + var y = (topY + bottomY) / 2; + return { x: x, y: y }; +} function locate(matrix) { var finderPatternQuads = []; var activeFinderPatternQuads = []; @@ -9966,6 +9994,7 @@ function locate(matrix) { }) .filter(function (q) { return !!q; }) // Filter out any rejected quads from above .sort(function (a, b) { return a.score - b.score; }) + // Now take the top finder pattern options and try to find 2 other options with a similar size. .map(function (point, i, finderPatterns) { if (i > MAX_FINDERPATTERNS_TO_SEARCH) { return null; @@ -9986,12 +10015,49 @@ function locate(matrix) { return null; } var _a = reorderFinderPatterns(finderPatternGroups[0].points[0], finderPatternGroups[0].points[1], finderPatternGroups[0].points[2]), topRight = _a.topRight, topLeft = _a.topLeft, bottomLeft = _a.bottomLeft; + var alignment = findAlignmentPattern(matrix, alignmentPatternQuads, topRight, topLeft, bottomLeft); + var result = []; + if (alignment) { + result.push({ + alignmentPattern: { x: alignment.alignmentPattern.x, y: alignment.alignmentPattern.y }, + bottomLeft: { x: bottomLeft.x, y: bottomLeft.y }, + dimension: alignment.dimension, + topLeft: { x: topLeft.x, y: topLeft.y }, + topRight: { x: topRight.x, y: topRight.y }, + }); + } + // We normally use the center of the quads as the location of the tracking points, which is optimal for most cases and will account + // for a skew in the image. However, In some cases, a slight skew might not be real and instead be caused by image compression + // errors and/or low resolution. For those cases, we'd be better off centering the point exactly in the middle of the black area. We + // compute and return the location data for the naively centered points as it is little additional work and allows for multiple + // attempts at decoding harder images. + var midTopRight = recenterLocation(matrix, topRight); + var midTopLeft = recenterLocation(matrix, topLeft); + var midBottomLeft = recenterLocation(matrix, bottomLeft); + var centeredAlignment = findAlignmentPattern(matrix, alignmentPatternQuads, midTopRight, midTopLeft, midBottomLeft); + if (centeredAlignment) { + result.push({ + alignmentPattern: { x: centeredAlignment.alignmentPattern.x, y: centeredAlignment.alignmentPattern.y }, + bottomLeft: { x: midBottomLeft.x, y: midBottomLeft.y }, + topLeft: { x: midTopLeft.x, y: midTopLeft.y }, + topRight: { x: midTopRight.x, y: midTopRight.y }, + dimension: centeredAlignment.dimension, + }); + } + if (result.length === 0) { + return null; + } + return result; +} +exports.locate = locate; +function findAlignmentPattern(matrix, alignmentPatternQuads, topRight, topLeft, bottomLeft) { + var _a; // Now that we've found the three finder patterns we can determine the blockSize and the size of the QR code. // We'll use these to help find the alignment pattern but also later when we do the extraction. var dimension; var moduleSize; try { - (_b = computeDimension(topLeft, topRight, bottomLeft, matrix), dimension = _b.dimension, moduleSize = _b.moduleSize); + (_a = computeDimension(topLeft, topRight, bottomLeft, matrix), dimension = _a.dimension, moduleSize = _a.moduleSize); } catch (e) { return null; @@ -10025,16 +10091,8 @@ function locate(matrix) { // If there are less than 15 modules between finder patterns it's a version 1 QR code and as such has no alignmemnt pattern // so we can only use our best guess. var alignmentPattern = modulesBetweenFinderPatterns >= 15 && alignmentPatterns.length ? alignmentPatterns[0] : expectedAlignmentPattern; - return { - alignmentPattern: { x: alignmentPattern.x, y: alignmentPattern.y }, - bottomLeft: { x: bottomLeft.x, y: bottomLeft.y }, - dimension: dimension, - topLeft: { x: topLeft.x, y: topLeft.y }, - topRight: { x: topRight.x, y: topRight.y }, - }; - var _b; + return { alignmentPattern: alignmentPattern, dimension: dimension }; } -exports.locate = locate; /***/ }) diff --git a/package.json b/package.json index 25706e01..68bd6256 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jsqr", - "version": "1.2.0", + "version": "1.3.0", "description": "A pure javascript QR code reading library that takes in raw images and will locate, extract and parse any QR code found within.", "repository": "https://github.com/cozmo/jsQR", "main": "./dist/jsQR.js", diff --git a/src/index.ts b/src/index.ts index 65116049..c09f8cea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,34 +24,35 @@ export interface QRCode { } function scan(matrix: BitMatrix): QRCode | null { - const location = locate(matrix); - if (!location) { + const locations = locate(matrix); + if (!locations) { return null; } - const extracted = extract(matrix, location); - const decoded = decode(extracted.matrix); - if (!decoded) { - return null; - } - - return { - binaryData: decoded.bytes, - data: decoded.text, - chunks: decoded.chunks, - location: { - topRightCorner: extracted.mappingFunction(location.dimension, 0), - topLeftCorner: extracted.mappingFunction(0, 0), - bottomRightCorner: extracted.mappingFunction(location.dimension, location.dimension), - bottomLeftCorner: extracted.mappingFunction(0, location.dimension), + for (const location of locations) { + const extracted = extract(matrix, location); + const decoded = decode(extracted.matrix); + if (decoded) { + return { + binaryData: decoded.bytes, + data: decoded.text, + chunks: decoded.chunks, + location: { + topRightCorner: extracted.mappingFunction(location.dimension, 0), + topLeftCorner: extracted.mappingFunction(0, 0), + bottomRightCorner: extracted.mappingFunction(location.dimension, location.dimension), + bottomLeftCorner: extracted.mappingFunction(0, location.dimension), - topRightFinderPattern: location.topRight, - topLeftFinderPattern: location.topLeft, - bottomLeftFinderPattern: location.bottomLeft, + topRightFinderPattern: location.topRight, + topLeftFinderPattern: location.topLeft, + bottomLeftFinderPattern: location.bottomLeft, - bottomRightAlignmentPattern: location.alignmentPattern, - }, - }; + bottomRightAlignmentPattern: location.alignmentPattern, + }, + }; + } + } + return null; } export interface Options { diff --git a/src/locator/index.ts b/src/locator/index.ts index 6d6ad146..d837fed1 100644 --- a/src/locator/index.ts +++ b/src/locator/index.ts @@ -212,6 +212,30 @@ function scorePattern(point: Point, ratios: number[], matrix: BitMatrix) { } } +function recenterLocation(matrix: BitMatrix, p: Point): Point { + let leftX = Math.round(p.x); + while (matrix.get(leftX, Math.round(p.y))) { + leftX--; + } + let rightX = Math.round(p.x); + while (matrix.get(rightX, Math.round(p.y))) { + rightX++; + } + const x = (leftX + rightX) / 2; + + let topY = Math.round(p.y); + while (matrix.get(Math.round(x), topY)) { + topY--; + } + let bottomY = Math.round(p.y); + while (matrix.get(Math.round(x), bottomY)) { + bottomY++; + } + const y = (topY + bottomY) / 2; + + return { x, y }; +} + interface Quad { top: { startX: number; @@ -225,7 +249,7 @@ interface Quad { }; } -export function locate(matrix: BitMatrix): QRLocation { +export function locate(matrix: BitMatrix): QRLocation[] { const finderPatternQuads: Quad[] = []; let activeFinderPatternQuads: Quad[] = []; const alignmentPatternQuads: Quad[] = []; @@ -361,7 +385,45 @@ export function locate(matrix: BitMatrix): QRLocation { const { topRight, topLeft, bottomLeft } = reorderFinderPatterns( finderPatternGroups[0].points[0], finderPatternGroups[0].points[1], finderPatternGroups[0].points[2], ); + const alignment = findAlignmentPattern(matrix, alignmentPatternQuads, topRight, topLeft, bottomLeft); + const result: QRLocation[] = []; + if (alignment) { + result.push({ + alignmentPattern: { x: alignment.alignmentPattern.x, y: alignment.alignmentPattern.y }, + bottomLeft: {x: bottomLeft.x, y: bottomLeft.y }, + dimension: alignment.dimension, + topLeft: {x: topLeft.x, y: topLeft.y }, + topRight: {x: topRight.x, y: topRight.y }, + }); + } + // We normally use the center of the quads as the location of the tracking points, which is optimal for most cases and will account + // for a skew in the image. However, In some cases, a slight skew might not be real and instead be caused by image compression + // errors and/or low resolution. For those cases, we'd be better off centering the point exactly in the middle of the black area. We + // compute and return the location data for the naively centered points as it is little additional work and allows for multiple + // attempts at decoding harder images. + const midTopRight = recenterLocation(matrix, topRight); + const midTopLeft = recenterLocation(matrix, topLeft); + const midBottomLeft = recenterLocation(matrix, bottomLeft); + const centeredAlignment = findAlignmentPattern(matrix, alignmentPatternQuads, midTopRight, midTopLeft, midBottomLeft); + if (centeredAlignment) { + result.push({ + alignmentPattern: { x: centeredAlignment.alignmentPattern.x, y: centeredAlignment.alignmentPattern.y }, + bottomLeft: { x: midBottomLeft.x, y: midBottomLeft. y }, + topLeft: { x: midTopLeft.x, y: midTopLeft. y }, + topRight: { x: midTopRight.x, y: midTopRight. y }, + dimension: centeredAlignment.dimension, + }); + } + + if (result.length === 0) { + return null; + } + + return result; +} + +function findAlignmentPattern(matrix: BitMatrix, alignmentPatternQuads: Quad[], topRight: Point, topLeft: Point, bottomLeft: Point) { // Now that we've found the three finder patterns we can determine the blockSize and the size of the QR code. // We'll use these to help find the alignment pattern but also later when we do the extraction. let dimension: number; @@ -405,11 +467,5 @@ export function locate(matrix: BitMatrix): QRLocation { // so we can only use our best guess. const alignmentPattern = modulesBetweenFinderPatterns >= 15 && alignmentPatterns.length ? alignmentPatterns[0] : expectedAlignmentPattern; - return { - alignmentPattern: { x: alignmentPattern.x, y: alignmentPattern.y }, - bottomLeft: {x: bottomLeft.x, y: bottomLeft.y }, - dimension, - topLeft: {x: topLeft.x, y: topLeft.y }, - topRight: {x: topRight.x, y: topRight.y }, - }; + return { alignmentPattern, dimension }; } diff --git a/src/locator/test-data/odd-skew.png b/src/locator/test-data/odd-skew.png new file mode 100644 index 00000000..ce0ccd5c Binary files /dev/null and b/src/locator/test-data/odd-skew.png differ diff --git a/src/locator/test.ts b/src/locator/test.ts index 11c4fea1..cf610350 100644 --- a/src/locator/test.ts +++ b/src/locator/test.ts @@ -10,7 +10,7 @@ describe("locate", () => { it('locates a "perfect" image', async () => { const binarized = await loadBinarized("./src/locator/test-data/perfect.png"); - expect(locate(binarized)).toEqual({ + expect(locate(binarized)[0]).toEqual({ alignmentPattern: {x: 170.5, y: 170.5}, bottomLeft: {x: 3.5, y: 173.5}, dimension: 177, @@ -21,7 +21,7 @@ describe("locate", () => { it("locates a QR in a real world image", async () => { const binarized = await loadBinarized("./src/locator/test-data/real-world.png"); - expect(locate(binarized)).toEqual({ + expect(locate(binarized)[0]).toEqual({ alignmentPattern: { x: 264.25, y: 177 }, bottomLeft: { x: 195.5, y: 191.5 }, dimension: 33, @@ -32,7 +32,7 @@ describe("locate", () => { it("locates a small QR code in real world photo", async () => { const binarized = await loadBinarized("./src/locator/test-data/small-photo.png"); - expect(locate(binarized)).toEqual({ + expect(locate(binarized)[0]).toEqual({ alignmentPattern: { x: 103, y: 147.5 }, bottomLeft: { x: 73.5, y: 152 }, dimension: 29, @@ -43,7 +43,7 @@ describe("locate", () => { it("locates a extremely distored QR code", async () => { const binarized = await loadBinarized("./src/locator/test-data/distorted-extreme.png"); - expect(locate(binarized)).toEqual({ + expect(locate(binarized)[0]).toEqual({ alignmentPattern: { x: 164.5, y: 39 }, bottomLeft: { x: 221.5, y: 18.5 }, dimension: 25, @@ -54,7 +54,7 @@ describe("locate", () => { it("locates a damaged QR code and guesses the finder pattern location", async () => { const binarized = await loadBinarized("./src/locator/test-data/damaged.png"); - expect(locate(binarized)).toEqual({ + expect(locate(binarized)[0]).toEqual({ alignmentPattern: { x: 219.75, y: 221 }, bottomLeft: { x: 81.5, y: 215.5 }, dimension: 29, @@ -65,7 +65,7 @@ describe("locate", () => { it("locates a damaged QR code and guesses the finder pattern location", async () => { const binarized = await loadBinarized("./src/locator/test-data/damaged.png"); - expect(locate(binarized)).toEqual({ + expect(locate(binarized)[0]).toEqual({ alignmentPattern: { x: 219.75, y: 221 }, bottomLeft: { x: 81.5, y: 215.5 }, dimension: 29, @@ -79,4 +79,15 @@ describe("locate", () => { const binarized = await loadBinarized("./src/locator/test-data/malformed-infinity.png"); expect(locate(binarized)).toEqual(null); }); + + it("returns a centered alignment as a fallback", async () => { + const binarized = await loadBinarized("./src/locator/test-data/odd-skew.png"); + expect(locate(binarized)[1]).toEqual({ + alignmentPattern: { x: 163.5, y: 170 }, + bottomLeft: { x: 56.5, y: 185.5 }, + dimension: 29, + topLeft: { x: 57, y: 60 }, + topRight: { x: 185.5, y: 57.5 }, + }); + }); }); diff --git a/tests/end-to-end/148/output.json b/tests/end-to-end/148/output.json index ec747fa4..4df0ed05 100644 --- a/tests/end-to-end/148/output.json +++ b/tests/end-to-end/148/output.json @@ -1 +1,55 @@ -null \ No newline at end of file +{ + "binaryData": [ + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 48 + ], + "data": "1234567890", + "chunks": [ + { + "type": "numeric", + "text": "1234567890" + } + ], + "location": { + "topRightCorner": { + "x": 263.49999999999994, + "y": 291.375 + }, + "topLeftCorner": { + "x": 290.79762807324306, + "y": 234.26604686604492 + }, + "bottomRightCorner": { + "x": 207.9274477212262, + "y": 264.60245217347966 + }, + "bottomLeftCorner": { + "x": 233.49999999999997, + "y": 208.125 + }, + "topRightFinderPattern": { + "x": 258.5, + "y": 277.5 + }, + "topLeftFinderPattern": { + "x": 276.5, + "y": 239.5 + }, + "bottomLeftFinderPattern": { + "x": 238.5, + "y": 222 + }, + "bottomRightAlignmentPattern": { + "x": 232.84618068755663, + "y": 255.48041599830515 + } + } +} \ No newline at end of file diff --git a/tests/end-to-end/61/output.json b/tests/end-to-end/61/output.json index ec747fa4..5808bccc 100644 --- a/tests/end-to-end/61/output.json +++ b/tests/end-to-end/61/output.json @@ -1 +1,143 @@ -null \ No newline at end of file +{ + "binaryData": [ + 71, + 111, + 111, + 103, + 108, + 101, + 32, + 80, + 114, + 105, + 110, + 116, + 32, + 65, + 100, + 115, + 32, + 45, + 32, + 84, + 46, + 71, + 46, + 73, + 46, + 65, + 46, + 70, + 46, + 32, + 45, + 32, + 74, + 97, + 110, + 117, + 97, + 114, + 121, + 32, + 51, + 49, + 44, + 32, + 50, + 48, + 48, + 56 + ], + "data": "Google Print Ads - T.G.I.A.F. - January 31, 2008", + "chunks": [ + { + "type": "byte", + "bytes": [ + 71, + 111, + 111, + 103, + 108, + 101, + 32, + 80, + 114, + 105, + 110, + 116, + 32, + 65, + 100, + 115, + 32, + 45, + 32, + 84, + 46, + 71, + 46, + 73, + 46, + 65, + 46, + 70, + 46, + 32, + 45, + 32, + 74, + 97, + 110, + 117, + 97, + 114, + 121, + 32, + 51, + 49, + 44, + 32, + 50, + 48, + 48, + 56 + ], + "text": "Google Print Ads - T.G.I.A.F. - January 31, 2008" + } + ], + "location": { + "topRightCorner": { + "x": 207.7810586662029, + "y": 35.39166271880641 + }, + "topLeftCorner": { + "x": 37.31970355694221, + "y": 39.672933251301814 + }, + "bottomRightCorner": { + "x": 199.80450482609717, + "y": 207.49761061850413 + }, + "bottomLeftCorner": { + "x": 37.57948911702004, + "y": 204.27384025598016 + }, + "topRightFinderPattern": { + "x": 185.5, + "y": 57.5 + }, + "topLeftFinderPattern": { + "x": 57, + "y": 60 + }, + "bottomLeftFinderPattern": { + "x": 56.5, + "y": 185.5 + }, + "bottomRightAlignmentPattern": { + "x": 163.5, + "y": 170 + } + } +} \ No newline at end of file diff --git a/tests/end-to-end/82/output.json b/tests/end-to-end/82/output.json index ec747fa4..942fd023 100644 --- a/tests/end-to-end/82/output.json +++ b/tests/end-to-end/82/output.json @@ -1 +1,109 @@ -null \ No newline at end of file +{ + "binaryData": [ + 104, + 116, + 116, + 112, + 58, + 47, + 47, + 99, + 111, + 100, + 101, + 46, + 103, + 111, + 111, + 103, + 108, + 101, + 46, + 99, + 111, + 109, + 47, + 112, + 47, + 122, + 120, + 105, + 110, + 103, + 47 + ], + "data": "http://code.google.com/p/zxing/", + "chunks": [ + { + "type": "byte", + "bytes": [ + 104, + 116, + 116, + 112, + 58, + 47, + 47, + 99, + 111, + 100, + 101, + 46, + 103, + 111, + 111, + 103, + 108, + 101, + 46, + 99, + 111, + 109, + 47, + 112, + 47, + 122, + 120, + 105, + 110, + 103, + 47 + ], + "text": "http://code.google.com/p/zxing/" + } + ], + "location": { + "topRightCorner": { + "x": 161.6951269176281, + "y": 61.745332923199555 + }, + "topLeftCorner": { + "x": 77.03759523249865, + "y": 51.8409703690389 + }, + "bottomRightCorner": { + "x": 147.85112998168927, + "y": 146.5334877662124 + }, + "bottomLeftCorner": { + "x": 67.25061775575521, + "y": 142.30094367891465 + }, + "topRightFinderPattern": { + "x": 148.5, + "y": 73 + }, + "topLeftFinderPattern": { + "x": 88, + "y": 66.5 + }, + "bottomLeftFinderPattern": { + "x": 80.5, + "y": 131 + }, + "bottomRightAlignmentPattern": { + "x": 131, + "y": 124 + } + } +} \ No newline at end of file diff --git a/tests/end-to-end/92/output.json b/tests/end-to-end/92/output.json index ec747fa4..e0930425 100644 --- a/tests/end-to-end/92/output.json +++ b/tests/end-to-end/92/output.json @@ -1 +1,55 @@ -null \ No newline at end of file +{ + "binaryData": [ + 50, + 48, + 50, + 49, + 50, + 48, + 48, + 48, + 48, + 48 + ], + "data": "2021200000", + "chunks": [ + { + "type": "numeric", + "text": "2021200000" + } + ], + "location": { + "topRightCorner": { + "x": 190.37500000000003, + "y": 123.24999999999999 + }, + "topLeftCorner": { + "x": 140.82437472806706, + "y": 120.1954804763799 + }, + "bottomRightCorner": { + "x": 189.37498917656237, + "y": 172.48075757475945 + }, + "bottomLeftCorner": { + "x": 140.125, + "y": 169.74999999999997 + }, + "topRightFinderPattern": { + "x": 182, + "y": 131 + }, + "topLeftFinderPattern": { + "x": 149, + "y": 129 + }, + "bottomLeftFinderPattern": { + "x": 148.5, + "y": 162 + }, + "bottomRightAlignmentPattern": { + "x": 174.47561880331884, + "y": 156.4352817881895 + } + } +} \ No newline at end of file diff --git a/tests/end-to-end/94/output.json b/tests/end-to-end/94/output.json index ec747fa4..4190200c 100644 --- a/tests/end-to-end/94/output.json +++ b/tests/end-to-end/94/output.json @@ -1 +1,203 @@ -null \ No newline at end of file +{ + "binaryData": [ + 85, + 73, + 32, + 111, + 102, + 102, + 105, + 99, + 101, + 32, + 104, + 111, + 117, + 114, + 115, + 32, + 115, + 105, + 103, + 110, + 117, + 112, + 13, + 10, + 104, + 116, + 116, + 112, + 58, + 47, + 47, + 119, + 119, + 119, + 46, + 99, + 111, + 114, + 112, + 46, + 103, + 111, + 111, + 103, + 108, + 101, + 46, + 99, + 111, + 109, + 47, + 115, + 112, + 97, + 114, + 114, + 111, + 119, + 47, + 117, + 105, + 95, + 111, + 102, + 102, + 105, + 99, + 101, + 95, + 104, + 111, + 117, + 114, + 115, + 47, + 32, + 13, + 10 + ], + "data": "UI office hours signup\r\nhttp://www.corp.google.com/sparrow/ui_office_hours/ \r\n", + "chunks": [ + { + "type": "byte", + "bytes": [ + 85, + 73, + 32, + 111, + 102, + 102, + 105, + 99, + 101, + 32, + 104, + 111, + 117, + 114, + 115, + 32, + 115, + 105, + 103, + 110, + 117, + 112, + 13, + 10, + 104, + 116, + 116, + 112, + 58, + 47, + 47, + 119, + 119, + 119, + 46, + 99, + 111, + 114, + 112, + 46, + 103, + 111, + 111, + 103, + 108, + 101, + 46, + 99, + 111, + 109, + 47, + 115, + 112, + 97, + 114, + 114, + 111, + 119, + 47, + 117, + 105, + 95, + 111, + 102, + 102, + 105, + 99, + 101, + 95, + 104, + 111, + 117, + 114, + 115, + 47, + 32, + 13, + 10 + ], + "text": "UI office hours signup\r\nhttp://www.corp.google.com/sparrow/ui_office_hours/ \r\n" + } + ], + "location": { + "topRightCorner": { + "x": 209.51355745730348, + "y": 28.862689046730146 + }, + "topLeftCorner": { + "x": 29.23676041336614, + "y": 25.808825443291582 + }, + "bottomRightCorner": { + "x": 195.8683092978863, + "y": 169.81926539702602 + }, + "bottomLeftCorner": { + "x": 44.55958093441372, + "y": 167.70884194729203 + }, + "topRightFinderPattern": { + "x": 194.5, + "y": 41.5 + }, + "topLeftFinderPattern": { + "x": 44.5, + "y": 39 + }, + "bottomLeftFinderPattern": { + "x": 55.5, + "y": 158.5 + }, + "bottomRightAlignmentPattern": { + "x": 175.25, + "y": 152 + } + } +} \ No newline at end of file diff --git a/tests/end-to-end/96/output.json b/tests/end-to-end/96/output.json index ec747fa4..dfae6abe 100644 --- a/tests/end-to-end/96/output.json +++ b/tests/end-to-end/96/output.json @@ -1 +1,203 @@ -null \ No newline at end of file +{ + "binaryData": [ + 85, + 73, + 32, + 111, + 102, + 102, + 105, + 99, + 101, + 32, + 104, + 111, + 117, + 114, + 115, + 32, + 115, + 105, + 103, + 110, + 117, + 112, + 13, + 10, + 104, + 116, + 116, + 112, + 58, + 47, + 47, + 119, + 119, + 119, + 46, + 99, + 111, + 114, + 112, + 46, + 103, + 111, + 111, + 103, + 108, + 101, + 46, + 99, + 111, + 109, + 47, + 115, + 112, + 97, + 114, + 114, + 111, + 119, + 47, + 117, + 105, + 95, + 111, + 102, + 102, + 105, + 99, + 101, + 95, + 104, + 111, + 117, + 114, + 115, + 47, + 32, + 13, + 10 + ], + "data": "UI office hours signup\r\nhttp://www.corp.google.com/sparrow/ui_office_hours/ \r\n", + "chunks": [ + { + "type": "byte", + "bytes": [ + 85, + 73, + 32, + 111, + 102, + 102, + 105, + 99, + 101, + 32, + 104, + 111, + 117, + 114, + 115, + 32, + 115, + 105, + 103, + 110, + 117, + 112, + 13, + 10, + 104, + 116, + 116, + 112, + 58, + 47, + 47, + 119, + 119, + 119, + 46, + 99, + 111, + 114, + 112, + 46, + 103, + 111, + 111, + 103, + 108, + 101, + 46, + 99, + 111, + 109, + 47, + 115, + 112, + 97, + 114, + 114, + 111, + 119, + 47, + 117, + 105, + 95, + 111, + 102, + 102, + 105, + 99, + 101, + 95, + 104, + 111, + 117, + 114, + 115, + 47, + 32, + 13, + 10 + ], + "text": "UI office hours signup\r\nhttp://www.corp.google.com/sparrow/ui_office_hours/ \r\n" + } + ], + "location": { + "topRightCorner": { + "x": 172.95423865294515, + "y": 56.92325495516057 + }, + "topLeftCorner": { + "x": 62.52318449791687, + "y": 41.112591119588885 + }, + "bottomRightCorner": { + "x": 163.7681469521871, + "y": 189.47785056515463 + }, + "bottomLeftCorner": { + "x": 53.70662900165528, + "y": 200.49500388855432 + }, + "topRightFinderPattern": { + "x": 165, + "y": 66.5 + }, + "topLeftFinderPattern": { + "x": 72, + "y": 55 + }, + "bottomLeftFinderPattern": { + "x": 64.5, + "y": 187.5 + }, + "bottomRightAlignmentPattern": { + "x": 151.5, + "y": 171.5 + } + } +} \ No newline at end of file diff --git a/tests/end-to-end/cupcake-2/output.json b/tests/end-to-end/cupcake-2/output.json index ec747fa4..cb0c1c4b 100644 --- a/tests/end-to-end/cupcake-2/output.json +++ b/tests/end-to-end/cupcake-2/output.json @@ -1 +1,85 @@ -null \ No newline at end of file +{ + "binaryData": [ + 104, + 116, + 116, + 112, + 58, + 47, + 47, + 103, + 101, + 111, + 108, + 111, + 113, + 105, + 46, + 99, + 111, + 109, + 47 + ], + "data": "http://geoloqi.com/", + "chunks": [ + { + "type": "byte", + "bytes": [ + 104, + 116, + 116, + 112, + 58, + 47, + 47, + 103, + 101, + 111, + 108, + 111, + 113, + 105, + 46, + 99, + 111, + 109, + 47 + ], + "text": "http://geoloqi.com/" + } + ], + "location": { + "topRightCorner": { + "x": 127.46497636162992, + "y": 34.06874732856506 + }, + "topLeftCorner": { + "x": 41.953364086855075, + "y": 105.74936541101754 + }, + "bottomRightCorner": { + "x": 189.71164362381936, + "y": 96.54063410843052 + }, + "bottomLeftCorner": { + "x": 99.62566806959562, + "y": 178.13716773984265 + }, + "topRightFinderPattern": { + "x": 124, + "y": 52 + }, + "topLeftFinderPattern": { + "x": 62, + "y": 104.5 + }, + "bottomLeftFinderPattern": { + "x": 104, + "y": 155.5 + }, + "bottomRightAlignmentPattern": { + "x": 150.25, + "y": 99 + } + } +} \ No newline at end of file diff --git a/tests/end-to-end/report.json b/tests/end-to-end/report.json index 24fb4f5b..c8cfddc5 100644 --- a/tests/end-to-end/report.json +++ b/tests/end-to-end/report.json @@ -1,7 +1,7 @@ { "counts": { - "failed": 47, - "successful": 207 + "failed": 40, + "successful": 214 }, "tests": { "0": true, @@ -64,7 +64,7 @@ "58": true, "59": true, "60": true, - "61": false, + "61": true, "62": true, "63": true, "64": true, @@ -85,7 +85,7 @@ "79": true, "80": false, "81": false, - "82": false, + "82": true, "83": true, "84": true, "85": false, @@ -95,11 +95,11 @@ "89": true, "90": true, "91": true, - "92": false, + "92": true, "93": true, - "94": false, + "94": true, "95": true, - "96": false, + "96": true, "97": true, "98": true, "99": true, @@ -151,7 +151,7 @@ "145": true, "146": true, "147": true, - "148": false, + "148": true, "149": true, "150": false, "151": true, @@ -207,7 +207,7 @@ "chevy-ad": true, "cupake-5": false, "cupcake-1": true, - "cupcake-2": false, + "cupcake-2": true, "cupcake-3": true, "cupcake-4": true, "damaged": false,