From a2c45a2dc74581f9e0e4e35dcdbf107d02046bf9 Mon Sep 17 00:00:00 2001 From: Nils Engelbach Date: Fri, 4 Dec 2020 15:13:59 +0100 Subject: [PATCH 1/4] Add alternative data matrix detector --- src/core/datamatrix/DataMatrixReader.ts | 8 +- .../detector/LPattern/DetectorWithLPattern.ts | 215 ++++++++++++++++++ .../detector/LPattern/EdgeTracer.ts | 193 ++++++++++++++++ .../datamatrix/detector/LPattern/Point.ts | 45 ++++ .../detector/LPattern/RegressionLine.ts | 132 +++++++++++ 5 files changed, 592 insertions(+), 1 deletion(-) create mode 100644 src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts create mode 100644 src/core/datamatrix/detector/LPattern/EdgeTracer.ts create mode 100644 src/core/datamatrix/detector/LPattern/Point.ts create mode 100644 src/core/datamatrix/detector/LPattern/RegressionLine.ts diff --git a/src/core/datamatrix/DataMatrixReader.ts b/src/core/datamatrix/DataMatrixReader.ts index 9f1c5ef7..23fde113 100644 --- a/src/core/datamatrix/DataMatrixReader.ts +++ b/src/core/datamatrix/DataMatrixReader.ts @@ -11,6 +11,7 @@ import ResultPoint from '../ResultPoint'; import System from '../util/System'; import Decoder from './decoder/Decoder'; import Detector from './detector/Detector'; +import DetectorWithLPattern from './detector/LPattern/DetectorWithLPattern'; /* @@ -63,7 +64,12 @@ export default class DataMatrixReader implements Reader { decoderResult = this.decoder.decode(bits); points = DataMatrixReader.NO_POINTS; } else { - const detectorResult = new Detector(image.getBlackMatrix()).detect(); + let detectorResult; + try { + detectorResult = new Detector(image.getBlackMatrix()).detect(); + } catch { + detectorResult = new DetectorWithLPattern(image.getBlackMatrix()).detect(); + } decoderResult = this.decoder.decode(detectorResult.getBits()); points = detectorResult.getPoints(); } diff --git a/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts b/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts new file mode 100644 index 00000000..5a4704c4 --- /dev/null +++ b/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts @@ -0,0 +1,215 @@ +import NotFoundException from '../../../../core/NotFoundException'; +import BitMatrix from '../../../common/BitMatrix'; +import DetectorResult from '../../../common/DetectorResult'; +import GridSamplerInstance from '../../../common/GridSamplerInstance'; +import ResultPoint from '../../../ResultPoint'; +import { EdgeTracer } from './EdgeTracer'; +import Point from './Point'; +import { RegressionLine } from './RegressionLine'; + +/** + * Ported from: ZXing CPP + * https://github.com/nu-book/zxing-cpp/blob/2ceda95cba943b3b2ab30af3c90150c97e84d416/core/src/datamatrix/DMDetector.cpp#L442-L1072 + */ +export default class DetectorWithLPattern { + + private image: BitMatrix; + + constructor(image: BitMatrix) { + this.image = image; + } + + /** + *

Detects a Data Matrix Code in an image.

+ * + * @return {@link DetectorResult} encapsulating results of detecting a Data Matrix Code + * @throws NotFoundException if no Data Matrix Code can be found + */ + public detect(): DetectorResult { + return DetectorWithLPattern.DetectNew(this.image, false); + } + + static SampleGrid(image: BitMatrix, tl: Point, bl: Point, br: Point, tr: Point, dimensionX: number, dimensionY: number): BitMatrix { + // shrink shape by half a pixel to go from center of white pixel outside of code to the edge between white and black + let moveHalfAPixel = (a: Point, b: Point) => { + let a2b = Point.divideBy(Point.sub(b, a), Point.distance(a, b)); + a = Point.add(a, Point.multiplyBy(a2b, 0.5)); + }; + + // move every point towards tr by half a pixel + // reasoning: for very low res scans, the top and right border tend to be around half a pixel moved inward already. + // for high res scans this half a pixel makes no difference anyway. + moveHalfAPixel(tl, tr); + moveHalfAPixel(bl, tr); + moveHalfAPixel(br, tr); + + for (let p of [tl, bl, br, tr]) { + p = Point.add(p, new Point(0.5, 0.5)); + } + + let border = 0.0; + + return GridSamplerInstance.getInstance().sampleGrid( + image, + dimensionX, dimensionY, + border, border, + dimensionX - border, border, + dimensionX - border, dimensionY - border, + border, dimensionY - border, + tl.x, tl.y, + tr.x, tr.y, + br.x, br.y, + bl.x, bl.y); + } + + static DetectNew(image: BitMatrix, tryRotate: boolean): DetectorResult { + // walk to the left at first + for (let startDirection of [new Point(-1, 0), new Point(1, 0), new Point(0, -1), new Point(0, 1)]) { + + let startTracer = new EdgeTracer(image, new Point(image.getWidth() / 2, image.getHeight() / 2), startDirection); + + while (startTracer.step()) { + // go forward until we reach a white/black border + if (!startTracer.isEdgeBehind()) + continue; + + let tl: Point, bl: Point, br: Point, tr: Point; + let lineL: RegressionLine, lineB: RegressionLine, lineR: RegressionLine, lineT: RegressionLine; + + let t = startTracer; + + // follow left leg upwards + t.setDirection(t.right()); + if (!t.traceLine(t.right(), lineL)) + continue; + + tl = t.traceCorner(t.right()); + lineL.reverse(); + let tlTracer = t; + + // follow left leg downwards + t = startTracer; + t.setDirection(t.left()); + if (!t.traceLine(t.left(), lineL)) + continue; + + if (!lineL.isValid()) + t.updateDirectionFromOrigin(tl); + let up = t.back(); + bl = t.traceCorner(t.left()); + + tlTracer.setDirection(t.front()); + + // follow bottom leg right + if (!t.traceLine(t.left(), lineB)) + continue; + + if (!lineL.isValid()) + t.updateDirectionFromOrigin(bl); + let right = t.front(); + br = t.traceCorner(t.left()); + + let lenL = Point.distance(tl, bl); + let lenB = Point.distance(bl, br); + if (lenL < 10 || lenB < 10 || lenB < lenL/4 || lenB > lenL*8) + continue; + + let maxStepSize = (lenB / 5 + 1); // datamatrix dim is at least 10x10 + + // at this point we found a plausible L-shape and are now looking for the b/w pattern at the top and right: + // follow top row right 'half way' (4 gaps), see traceGaps break condition with 'invalid' line + tlTracer.setDirection(right); + if (!tlTracer.traceGaps(tlTracer.right(), lineT, maxStepSize, new RegressionLine())) + continue; + + maxStepSize = Math.max(lineT.length / 4, (lenL / 5 + 1)); + + // follow up until we reach the top line + t.setDirection(up); + if (!t.traceGaps(t.left(), lineR, maxStepSize, lineT)) + continue; + + // continue top row right until we cross the right line + if (!tlTracer.traceGaps(tlTracer.right(), lineT, maxStepSize, lineR)) + continue; + + tr = t.traceCorner(t.left()); + + let lenT = Point.distance(tl, tr); + let lenR = Point.distance(tr, br); + + if (Math.abs(lenT - lenB) / lenB > 0.5 || Math.abs(lenR - lenL) / lenL > 0.5 || + lineT.points.length < 5 || lineR.points.length < 5) + continue; + + // printf("L: %f, %f ^ %f, %f > %f, %f (%d : %d : %d : %d)\n", bl.x, bl.y, + // tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, (int)lenL, (int)lenB, (int)lenT, (int)lenR); + + for (let l of [lineL, lineB, lineT, lineR]) { + l.evaluate(true); + } + + // find the bounding box corners of the code with sub-pixel precision by intersecting the 4 border lines + bl = RegressionLine.intersect(lineB, lineL); + tl = RegressionLine.intersect(lineT, lineL); + tr = RegressionLine.intersect(lineT, lineR); + br = RegressionLine.intersect(lineB, lineR); + + let dimT: number, dimR: number; + let fracT: number, fracR: number; + let splitDouble = (d: number, i: number, f: number) => { + i = Math.abs(d) > 0 ? (d + 0.5) : 0; + f = Math.abs(d) > 0 ? Math.abs(d - i) : Infinity; + }; + + splitDouble(lineT.modules(tl, tr), dimT, fracT); + splitDouble(lineR.modules(br, tr), dimR, fracR); + + // printf("L: %f, %f ^ %f, %f > %f, %f ^> %f, %f\n", bl.x, bl.y, + // tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, tr.x, tr.y); + // printf("dim: %d x %d\n", dimT, dimR); + + // if we have an invalid rectangular data matrix dimension, we try to parse it by assuming a square + // we use the dimension that is closer to an integral value + if (dimT < 2 * dimR || dimT > 4 * dimR) + dimT = dimR = fracR < fracT ? dimR : dimT; + + // the dimension is 2x the number of black/white transitions + dimT *= 2; + dimR *= 2; + + if (dimT < 10 || dimT > 144 || dimR < 8 || dimR > 144 ) + continue; + + let bits = this.SampleGrid(image, tl, bl, br, tr, dimT, dimR); + + // TODO: Debug Output + // printf("modules top: %d, right: %d\n", dimT, dimR); + // printBitMatrix(bits); + + // auto border = lineT.points(); + // border.insert(border.end(), lineR.points().begin(), lineR.points().end()); + // dumpDebugPPM(image, "binary.pnm", border); + + // TODO: Why do we need to check this? + // if (bits.empty()) + // continue; + + return new DetectorResult(bits, [ + new ResultPoint(tl.x, tl.y), + new ResultPoint(bl.x, bl.y), + new ResultPoint(br.x, br.y), + new ResultPoint(tr.x, tr.y), + ]); + } + + // reached border of image -> try next scan direction + if (!tryRotate) + break; // only test left direction + } + + // dumpDebugPPM(image, "binary.pnm");//, { tl, bl, br, tr }); + + throw new NotFoundException(); + } +} diff --git a/src/core/datamatrix/detector/LPattern/EdgeTracer.ts b/src/core/datamatrix/detector/LPattern/EdgeTracer.ts new file mode 100644 index 00000000..a3c82f7d --- /dev/null +++ b/src/core/datamatrix/detector/LPattern/EdgeTracer.ts @@ -0,0 +1,193 @@ +import BitMatrix from '../../../common/BitMatrix'; +import Point from './Point'; +import { RegressionLine } from './RegressionLine'; + +export enum StepResult { + FOUND, + OPEN_END, + CLOSED_END +} + +export enum Value { + INVALID, + WHITE, + BLACK +} + +export class EdgeTracer { + private image: BitMatrix; + private p: Point; // current position + private d: Point; // current direction + + static mainDirection(d: Point): Point { return Math.abs(d.x) > Math.abs(d.y) ? new Point(d.x, 0) : new Point(0, d.y); } + + public pointIsIn(p: Point): boolean { + const b = 0; + return b <= p.x + && p.x < this.image.getWidth() - b + && b <= p.y && p.y < this.image.getHeight() - b; + } + + public isIn() { return this.pointIsIn(this.p); } + + public getAt(p: Point): any { + if (!this.pointIsIn(p)) + return Value.INVALID; + const q: Point = Point.round(p); + return this.image.get(q.x, q.y) ? Value.BLACK : Value.WHITE; + } + + public blackAt(p: Point): boolean { return this.getAt(p) === Value.BLACK; } + public whiteAt(p: Point): boolean { return this.getAt(p) === Value.WHITE; } + + public isEdge(pos: Point, dir: Point): boolean { return this.whiteAt(pos) && this.blackAt(Point.add(pos, dir)); } + + traceStep(dEdge: Point, maxStepSize: number, goodDirection: boolean): StepResult { + dEdge = EdgeTracer.mainDirection(dEdge); + for (let breadth = 1; breadth <= (goodDirection ? 1 : (maxStepSize === 1 ? 2 : 3)); ++breadth) + for (let step = 1; step <= maxStepSize; ++step) + for (let i = 0; i <= 2 * (step / 4 + 1) * breadth; ++i) { + let pEdge = Point.add( + Point.add( + this.p, + Point.multiplyBy(this.d, step) + ), + Point.multiplyBy(dEdge, (i & 1 ? (i + 1) / 2 : -i / 2)) + ); + this.log(pEdge); + + if (!this.blackAt(Point.add(pEdge, dEdge))) + continue; + + // found black pixel -> go 'outward' until we hit the b/w border + for (let j = 0; j < Math.max(maxStepSize, 3) && this.pointIsIn(pEdge); ++j) { + if (this.whiteAt(pEdge)) { + this.p = Point.round(pEdge); + return StepResult.FOUND; + } + pEdge = Point.sub(pEdge, dEdge); + if (this.blackAt(Point.sub(pEdge, this.d))) + pEdge = Point.sub(pEdge, this.d); + this.log(pEdge); + } + // no valid b/w border found within reasonable range + return StepResult.CLOSED_END; + } + return StepResult.OPEN_END; + } + + _log: BitMatrix; + + // TODO: IF DEBUG + public log(p: Point): void { + if (this._log.getHeight() !== this.image.getHeight() || this._log.getWidth() !== this.image.getWidth()) + this._log = new BitMatrix(this.image.getWidth(), this.image.getHeight()); + let q = Point.round(p); + if (this.pointIsIn(q)) + this._log.set(q.x, q.y); + } + + constructor(img: BitMatrix, p: Point, d: Point) { + this.image = img; + this.p = p; + this.d = d; + } + + step(s = 1): boolean { + this.p = Point.add(this.p, Point.multiplyBy(this.d, s)); + this.log(this.p); + return this.pointIsIn(this.p); + } + + setDirection(dir: Point) { + this.d = Point.divideBy(dir, Math.max(Math.abs(dir.x), Math.abs(dir.y))); + } + + updateDirectionFromOrigin(origin: Point): boolean { + let old_d = this.d; + this.setDirection(Point.sub(this.p, origin)); + // it the new direction is pointing "backward", i.e. angle(new, old) > pi/2 -> break + if (Point.mul(this.d, old_d) < 0) + return false; + // printf("new dir: %f, %f\n", d.x, d.y); + // make sure d stays in the same quadrant to prevent an infinite loop + if (EdgeTracer.mainDirection(this.d) !== EdgeTracer.mainDirection(old_d)) + this.d = Point.add(EdgeTracer.mainDirection(old_d), Point.multiplyBy(EdgeTracer.mainDirection(this.d), 0.99)); + return true; + } + + front(): Point { return this.d; } + back(): Point { return new Point(-this.d.x, -this.d.y); } + right(): Point { return new Point(-this.d.y, this.d.x); } + left(): Point { return new Point(this.d.y, -this.d.x); } + + isEdgeBehind(): boolean { return this.isEdge(this.p, this.back()); } + + traceLine(dEdge: Point, line: RegressionLine): boolean { + do { + this.log(this.p); + line.add(Point.round(this.p)); + if (line.points.length % 30 === 10) { + line.evaluate(); + if (!this.updateDirectionFromOrigin(Point.add(Point.sub(this.p, line.project(this.p)), line.points[0]))) + return false; + } + let stepResult = this.traceStep(dEdge, 1, line.isValid()); + if (stepResult !== StepResult.FOUND) + return stepResult === StepResult.OPEN_END; + } while (true); + } + + + traceGaps(dEdge: Point, line: RegressionLine, maxStepSize: number, finishLine: RegressionLine): boolean { + line.setDirectionInward(dEdge); + let gaps = 0; + do { + this.log(this.p); + let next_p = Point.round(this.p); + let diff = line.points.length === 0 ? new Point() : Point.sub(next_p, line.points[line.points.length - 1]); + + if (line.points.length === 0 || line.points[line.points.length - 1] !== next_p) + line.add(next_p); + + if (Math.abs(Point.mul(diff, this.d)) > 1) { + ++gaps; + if (line.length > 5) { + line.evaluate(true); + if (!this.updateDirectionFromOrigin(Point.add(Point.sub(this.p, line.project(this.p)), line.points[0]))) + return false; + } + // the minimum size is 10x10 -> 4 gaps + // TODO: maybe switch to termination condition based on bottom line length + if (!finishLine.isValid() && gaps >= 4) + return true; + } + if (line.isValid()) { + // if we are drifting towards the inside of the code, pull the current position back out onto the line + if (line.signedDistance(this.p) > 2) + this.p = Point.add(line.project(this.p), this.d); + } + + + if (finishLine.isValid()) + maxStepSize = Math.min(maxStepSize, finishLine.signedDistance(this.p)); + + + let stepResult = this.traceStep(dEdge, maxStepSize, line.isValid()); + if (stepResult !== StepResult.FOUND) + return stepResult === StepResult.OPEN_END; + } while (true); + } + + + traceCorner(dir: Point): Point { + this.step(); + let ret = this.p = Point.round(this.p); + let _tmp = dir; + dir = this.d; + this.d = _tmp; + this.traceStep(Point.multiplyBy(dir, -1), 2, false); + // printf("turn: %f x %f -> %f, %f\n", p.x, p.y, d.x, d.y); + return ret; + } +} diff --git a/src/core/datamatrix/detector/LPattern/Point.ts b/src/core/datamatrix/detector/LPattern/Point.ts new file mode 100644 index 00000000..cfcf4198 --- /dev/null +++ b/src/core/datamatrix/detector/LPattern/Point.ts @@ -0,0 +1,45 @@ +export default class Point { + x: number; + y: number; + + constructor(x: number = null, y: number = null) { + this.x = x; + this.y = y; + } + + static equals(a: Point, b: Point) { + return a.x === b.x && a.y === b.y; + } + + static unequals(a: Point, b: Point) { + return !Point.equals(a, b); + } + + static add(a: Point, b: Point): Point { + return new Point(a.x + b.x, a.y + b.y); + } + + static sub(a: Point, b: Point): Point { + return new Point(a.x - b.x, a.y - b.y); + } + + static multiplyBy(a: Point, s: number): Point { + return new Point(s * a.x, s * a.y); + } + + static divideBy(a: Point, d: number): Point { + return new Point(a.x / d, a.y / d); + } + + static mul(a: Point, b: Point): number { + return a.x * b.x + a.y * b.y; + } + + static distance(a: Point, b: Point): number { + return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); + } + + static round(p: Point): Point { + return new Point(Math.round(p.x), Math.round(p.y)); + } +} diff --git a/src/core/datamatrix/detector/LPattern/RegressionLine.ts b/src/core/datamatrix/detector/LPattern/RegressionLine.ts new file mode 100644 index 00000000..0717ecb5 --- /dev/null +++ b/src/core/datamatrix/detector/LPattern/RegressionLine.ts @@ -0,0 +1,132 @@ +import Point from './Point'; + +export class RegressionLine { + + private _points: Point[]; + private _directionInward: Point; + private a: number; + private b: number; + private c: number; + + static intersect(l1: RegressionLine, l2: RegressionLine): Point { + const d = l1.a * l2.b - l1.b * l2.a; + const x = (l1.c * l2.b - l1.b * l2.c) / d; + const y = (l1.a * l2.c - l1.c * l2.a) / d; + return new Point(x, y); + } + + public _evaluate(ps: Point[]): void { + const allSummarized: Point = ps.reduce((acc, point) => Point.add(acc, point), new Point()); + const mean: Point = Point.divideBy(allSummarized, ps.length); + + let sumXX = 0, sumYY = 0, sumXY = 0; + ps.forEach((p) => { + sumXX += (p.x - mean.x) * (p.x - mean.x); + sumYY += (p.y - mean.y) * (p.y - mean.y); + sumXY += (p.x - mean.x) * (p.y - mean.y); + }); + + if (sumYY >= sumXX) { + this.a = +sumYY / Math.sqrt(sumYY * sumYY + sumXY * sumXY); + this.b = -sumXY / Math.sqrt(sumYY * sumYY + sumXY * sumXY); + } else { + this.a = +sumXY / Math.sqrt(sumXX * sumXX + sumXY * sumXY); + this.b = -sumXX / Math.sqrt(sumXX * sumXX + sumXY * sumXY); + } + if (Point.mul(this._directionInward, this.normal()) < 0) { + this.a = -this.a; + this.b = -this.b; + } + this.c = Point.mul(this.normal(), mean); + } + + static average(c: number[], filter: (x: number) => boolean): number { + let sum = 0; + let num = 0; + for (let v of c) + if (filter(v)) { + sum += v; + ++num; + } + return sum / num; + } + + get points(): Point[] { + return this._points; + } + + get length(): number { + return this.points.length >= 2 + ? Point.distance(this._points[0], this._points[this._points.length - 1]) + : 0; + } + + public isValid(): boolean { return this.a !== undefined; } + public normal(): Point { return new Point(this.a, this.b); } + public project(p: Point): Point { + return Point.sub( + p, + Point.multiplyBy(this.normal(), Point.mul(this.normal(), p) - this.c) + ); + } + public signedDistance(p: Point): number { + return (Point.mul(this.normal(), p) - this.c) / Math.sqrt(this.a * this.a + this.b * this.b); + } + public reverse() { this._points = this._points.reverse(); } + public add(p: Point) { this._points.push(p); } + public setDirectionInward(d: Point) { this._directionInward = d; } + + public evaluate(clean = false) { + let ps = this._points; + this._evaluate(ps); + if (clean) { + let old_points_length; + while (true) { + old_points_length = this._points.length; + this._points = this._points.filter(p => this.signedDistance(p) > 1.5); + if (old_points_length === this._points.length) + break; + + // printf("removed %zu points\n", old_points_size - _points.size()); + this._evaluate(this._points); + } + } + } + + public modules(beg: Point, end: Point): number { + // assert(this._points.length > 3); + + let gapSizes: number[]; + + // calculate the distance between the points projected onto the regression line + for (let i = 1; i < this._points.length; ++i) + gapSizes.push(Point.distance(this.project(this._points[i]), this.project(this._points[i - 1]))); + + + // calculate the (average) distance of two adjacent pixels + const unitPixelDist: number = RegressionLine.average(gapSizes, (dist) => { return 0.75 < dist && dist < 1.5; }); + + // calculate the width of 2 modules (first black pixel to first black pixel) + let sum = Point.distance(beg, this.project(this._points[0])) - unitPixelDist; + let i = gapSizes[0]; + for (let dist of gapSizes) { + sum += dist; + if (dist > 1.9 * unitPixelDist) { + i += 1; + gapSizes[i] = sum; + sum = 0; + } + } + i += 1; + gapSizes[i] = sum + Point.distance(end, this.project(this._points[this._points.length - 1])); + gapSizes = gapSizes.slice(0, i); + let lineLength: number = Point.distance(beg, end) - unitPixelDist; + let meanGapSize: number = lineLength / gapSizes.length; + + // printf("unit pixel dist: %f\n", unitPixelDist); + // printf("lineLength: %f, meanGapSize: %f, gaps: %lu\n", lineLength, meanGapSize, gapSizes.size()); + meanGapSize = RegressionLine.average(gapSizes, (dist) => { return Math.abs(dist - meanGapSize) < meanGapSize / 2; }); + // printf("lineLength: %f, meanGapSize: %f, gaps: %lu\n", lineLength, meanGapSize, gapSizes.size()); + return lineLength / meanGapSize; + } +} From 15b079cf916839b53e7cbbb4cdbfa279786b3b1c Mon Sep 17 00:00:00 2001 From: NilsEngelbach Date: Sat, 5 Dec 2020 22:12:30 +0100 Subject: [PATCH 2/4] Fix issues --- src/core/datamatrix/DataMatrixReader.ts | 7 +- .../detector/LPattern/DetectorWithLPattern.ts | 85 +++++++++---------- .../detector/LPattern/EdgeTracer.ts | 81 +++++++++++------- .../datamatrix/detector/LPattern/Point.ts | 12 +-- .../detector/LPattern/RegressionLine.ts | 82 +++++++++++------- 5 files changed, 149 insertions(+), 118 deletions(-) diff --git a/src/core/datamatrix/DataMatrixReader.ts b/src/core/datamatrix/DataMatrixReader.ts index 23fde113..fe2592b3 100644 --- a/src/core/datamatrix/DataMatrixReader.ts +++ b/src/core/datamatrix/DataMatrixReader.ts @@ -65,11 +65,8 @@ export default class DataMatrixReader implements Reader { points = DataMatrixReader.NO_POINTS; } else { let detectorResult; - try { - detectorResult = new Detector(image.getBlackMatrix()).detect(); - } catch { - detectorResult = new DetectorWithLPattern(image.getBlackMatrix()).detect(); - } + detectorResult = new Detector(image.getBlackMatrix()).detect(); + // detectorResult = new DetectorWithLPattern(image.getBlackMatrix()).detect(); decoderResult = this.decoder.decode(detectorResult.getBits()); points = detectorResult.getPoints(); } diff --git a/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts b/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts index 5a4704c4..df35bbf4 100644 --- a/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts +++ b/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts @@ -26,10 +26,21 @@ export default class DetectorWithLPattern { * @throws NotFoundException if no Data Matrix Code can be found */ public detect(): DetectorResult { - return DetectorWithLPattern.DetectNew(this.image, false); + return DetectorWithLPattern.detectWithLPattern(this.image, true); } - static SampleGrid(image: BitMatrix, tl: Point, bl: Point, br: Point, tr: Point, dimensionX: number, dimensionY: number): BitMatrix { + // Print matrix (for debugging) + static printBitMatrix(matrix: BitMatrix):void + { + for (let y = 0; y < matrix.getHeight(); ++y) { + let row = ""; + for (let x = 0; x < matrix.getWidth(); ++x) + row += matrix.get(x, y) ? '+' : '.'; + console.log(row); + } + } + + static sampleGridAndMoveHalfAPixel(image: BitMatrix, tl: Point, bl: Point, br: Point, tr: Point, dimensionX: number, dimensionY: number): BitMatrix { // shrink shape by half a pixel to go from center of white pixel outside of code to the edge between white and black let moveHalfAPixel = (a: Point, b: Point) => { let a2b = Point.divideBy(Point.sub(b, a), Point.distance(a, b)); @@ -47,7 +58,7 @@ export default class DetectorWithLPattern { p = Point.add(p, new Point(0.5, 0.5)); } - let border = 0.0; + let border = 0; return GridSamplerInstance.getInstance().sampleGrid( image, @@ -59,14 +70,18 @@ export default class DetectorWithLPattern { tl.x, tl.y, tr.x, tr.y, br.x, br.y, - bl.x, bl.y); + bl.x, bl.y + ); } - static DetectNew(image: BitMatrix, tryRotate: boolean): DetectorResult { + static detectWithLPattern(image: BitMatrix, tryRotate: boolean): DetectorResult { + // walk to the left at first - for (let startDirection of [new Point(-1, 0), new Point(1, 0), new Point(0, -1), new Point(0, 1)]) { + const directions = [new Point(-1, 0), new Point(1, 0), new Point(0, -1), new Point(0, 1)]; + + for (let direction of directions) { - let startTracer = new EdgeTracer(image, new Point(image.getWidth() / 2, image.getHeight() / 2), startDirection); + let startTracer = new EdgeTracer(image, new Point(image.getWidth() / 2, image.getHeight() / 2), direction); while (startTracer.step()) { // go forward until we reach a white/black border @@ -74,9 +89,12 @@ export default class DetectorWithLPattern { continue; let tl: Point, bl: Point, br: Point, tr: Point; - let lineL: RegressionLine, lineB: RegressionLine, lineR: RegressionLine, lineT: RegressionLine; + let lineL = new RegressionLine(); + let lineB = new RegressionLine(); + let lineR = new RegressionLine(); + let lineT = new RegressionLine(); - let t = startTracer; + let t = startTracer.clone(); // follow left leg upwards t.setDirection(t.right()); @@ -85,10 +103,11 @@ export default class DetectorWithLPattern { tl = t.traceCorner(t.right()); lineL.reverse(); - let tlTracer = t; + + let tlTracer = t.clone(); // follow left leg downwards - t = startTracer; + t = startTracer.clone(); t.setDirection(t.left()); if (!t.traceLine(t.left(), lineL)) continue; @@ -142,8 +161,8 @@ export default class DetectorWithLPattern { lineT.points.length < 5 || lineR.points.length < 5) continue; - // printf("L: %f, %f ^ %f, %f > %f, %f (%d : %d : %d : %d)\n", bl.x, bl.y, - // tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, (int)lenL, (int)lenB, (int)lenT, (int)lenR); + // console.log("L: %f, %f ^ %f, %f > %f, %f (%d : %d : %d : %d)\n", bl.x, bl.y, + // tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, lenL, lenB, lenT, lenR); for (let l of [lineL, lineB, lineT, lineR]) { l.evaluate(true); @@ -155,24 +174,11 @@ export default class DetectorWithLPattern { tr = RegressionLine.intersect(lineT, lineR); br = RegressionLine.intersect(lineB, lineR); - let dimT: number, dimR: number; - let fracT: number, fracR: number; - let splitDouble = (d: number, i: number, f: number) => { - i = Math.abs(d) > 0 ? (d + 0.5) : 0; - f = Math.abs(d) > 0 ? Math.abs(d - i) : Infinity; - }; + let dimT = lineT.modules(tl, tr); + let dimR = lineR.modules(br, tr); - splitDouble(lineT.modules(tl, tr), dimT, fracT); - splitDouble(lineR.modules(br, tr), dimR, fracR); - - // printf("L: %f, %f ^ %f, %f > %f, %f ^> %f, %f\n", bl.x, bl.y, - // tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, tr.x, tr.y); - // printf("dim: %d x %d\n", dimT, dimR); - - // if we have an invalid rectangular data matrix dimension, we try to parse it by assuming a square - // we use the dimension that is closer to an integral value - if (dimT < 2 * dimR || dimT > 4 * dimR) - dimT = dimR = fracR < fracT ? dimR : dimT; + // console.log("L: %f, %f ^ %f, %f > %f, %f ^> %f, %f", bl.x, bl.y, tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, tr.x, tr.y); + // console.log("dim: %d x %d", dimT, dimR); // the dimension is 2x the number of black/white transitions dimT *= 2; @@ -181,19 +187,12 @@ export default class DetectorWithLPattern { if (dimT < 10 || dimT > 144 || dimR < 8 || dimR > 144 ) continue; - let bits = this.SampleGrid(image, tl, bl, br, tr, dimT, dimR); - - // TODO: Debug Output - // printf("modules top: %d, right: %d\n", dimT, dimR); - // printBitMatrix(bits); + let bits = this.sampleGridAndMoveHalfAPixel(image, tl, bl, br, tr, dimT, dimR); - // auto border = lineT.points(); - // border.insert(border.end(), lineR.points().begin(), lineR.points().end()); - // dumpDebugPPM(image, "binary.pnm", border); + // DetectorWithLPattern.printBitMatrix(bits); - // TODO: Why do we need to check this? - // if (bits.empty()) - // continue; + if (bits.getWidth() === 0 && bits.getHeight() === 0) + continue; return new DetectorResult(bits, [ new ResultPoint(tl.x, tl.y), @@ -205,11 +204,9 @@ export default class DetectorWithLPattern { // reached border of image -> try next scan direction if (!tryRotate) - break; // only test left direction + break; // only test first direction (left) } - // dumpDebugPPM(image, "binary.pnm");//, { tl, bl, br, tr }); - throw new NotFoundException(); } } diff --git a/src/core/datamatrix/detector/LPattern/EdgeTracer.ts b/src/core/datamatrix/detector/LPattern/EdgeTracer.ts index a3c82f7d..a6dda55c 100644 --- a/src/core/datamatrix/detector/LPattern/EdgeTracer.ts +++ b/src/core/datamatrix/detector/LPattern/EdgeTracer.ts @@ -15,20 +15,24 @@ export enum Value { } export class EdgeTracer { + private image: BitMatrix; private p: Point; // current position private d: Point; // current direction - static mainDirection(d: Point): Point { return Math.abs(d.x) > Math.abs(d.y) ? new Point(d.x, 0) : new Point(0, d.y); } + static mainDirection(d: Point): Point { + return Math.abs(d.x) > Math.abs(d.y) ? new Point(d.x, 0) : new Point(0, d.y); + } public pointIsIn(p: Point): boolean { const b = 0; - return b <= p.x - && p.x < this.image.getWidth() - b + return b <= p.x && p.x < this.image.getWidth() - b && b <= p.y && p.y < this.image.getHeight() - b; } - public isIn() { return this.pointIsIn(this.p); } + public isIn() { + return this.pointIsIn(Point.round(this.p)); + } public getAt(p: Point): any { if (!this.pointIsIn(p)) @@ -37,10 +41,17 @@ export class EdgeTracer { return this.image.get(q.x, q.y) ? Value.BLACK : Value.WHITE; } - public blackAt(p: Point): boolean { return this.getAt(p) === Value.BLACK; } - public whiteAt(p: Point): boolean { return this.getAt(p) === Value.WHITE; } + public blackAt(p: Point): boolean { + return this.getAt(p) === Value.BLACK; + } - public isEdge(pos: Point, dir: Point): boolean { return this.whiteAt(pos) && this.blackAt(Point.add(pos, dir)); } + public whiteAt(p: Point): boolean { + return this.getAt(p) === Value.WHITE; + } + + public isEdge(pos: Point, dir: Point): boolean { + return this.whiteAt(pos) && this.blackAt(Point.add(pos, dir)); + } traceStep(dEdge: Point, maxStepSize: number, goodDirection: boolean): StepResult { dEdge = EdgeTracer.mainDirection(dEdge); @@ -52,7 +63,7 @@ export class EdgeTracer { this.p, Point.multiplyBy(this.d, step) ), - Point.multiplyBy(dEdge, (i & 1 ? (i + 1) / 2 : -i / 2)) + Point.multiplyBy(dEdge, (i&1 ? (i + 1) / 2 : -i / 2)) ); this.log(pEdge); @@ -62,7 +73,7 @@ export class EdgeTracer { // found black pixel -> go 'outward' until we hit the b/w border for (let j = 0; j < Math.max(maxStepSize, 3) && this.pointIsIn(pEdge); ++j) { if (this.whiteAt(pEdge)) { - this.p = Point.round(pEdge); + this.p = pEdge; return StepResult.FOUND; } pEdge = Point.sub(pEdge, dEdge); @@ -76,21 +87,20 @@ export class EdgeTracer { return StepResult.OPEN_END; } - _log: BitMatrix; - // TODO: IF DEBUG public log(p: Point): void { - if (this._log.getHeight() !== this.image.getHeight() || this._log.getWidth() !== this.image.getWidth()) - this._log = new BitMatrix(this.image.getWidth(), this.image.getHeight()); - let q = Point.round(p); - if (this.pointIsIn(q)) - this._log.set(q.x, q.y); + // if (this._log.getHeight() !== this.image.getHeight() || this._log.getWidth() !== this.image.getWidth()) + // this._log = new BitMatrix(this.image.getWidth(), this.image.getHeight()); + // let q = Point.round(p); + // if (this.pointIsIn(q)) + // this._log.set(q.x, q.y); + // console.log(JSON.stringify(p)); } constructor(img: BitMatrix, p: Point, d: Point) { this.image = img; - this.p = p; - this.d = d; + this.p = new Point(p.x, p.y); + this.d = new Point(d.x, d.y); } step(s = 1): boolean { @@ -104,14 +114,15 @@ export class EdgeTracer { } updateDirectionFromOrigin(origin: Point): boolean { - let old_d = this.d; + let old_d = new Point(this.d.x, this.d.y); this.setDirection(Point.sub(this.p, origin)); // it the new direction is pointing "backward", i.e. angle(new, old) > pi/2 -> break - if (Point.mul(this.d, old_d) < 0) + if (Point.mul(this.d, old_d) < 0) { return false; - // printf("new dir: %f, %f\n", d.x, d.y); + } + // console.log(`new dir: ${this.d.x}, ${this.d.y}`); // make sure d stays in the same quadrant to prevent an infinite loop - if (EdgeTracer.mainDirection(this.d) !== EdgeTracer.mainDirection(old_d)) + if (!Point.equals(EdgeTracer.mainDirection(this.d), EdgeTracer.mainDirection(old_d))) this.d = Point.add(EdgeTracer.mainDirection(old_d), Point.multiplyBy(EdgeTracer.mainDirection(this.d), 0.99)); return true; } @@ -121,12 +132,14 @@ export class EdgeTracer { right(): Point { return new Point(-this.d.y, this.d.x); } left(): Point { return new Point(this.d.y, -this.d.x); } - isEdgeBehind(): boolean { return this.isEdge(this.p, this.back()); } + isEdgeBehind(): boolean { + return this.isEdge(this.p, this.back()); + } traceLine(dEdge: Point, line: RegressionLine): boolean { do { this.log(this.p); - line.add(Point.round(this.p)); + line.add(this.p); if (line.points.length % 30 === 10) { line.evaluate(); if (!this.updateDirectionFromOrigin(Point.add(Point.sub(this.p, line.project(this.p)), line.points[0]))) @@ -147,14 +160,15 @@ export class EdgeTracer { let next_p = Point.round(this.p); let diff = line.points.length === 0 ? new Point() : Point.sub(next_p, line.points[line.points.length - 1]); - if (line.points.length === 0 || line.points[line.points.length - 1] !== next_p) + if (line.points.length === 0 || !Point.equals(line.points[line.points.length - 1], next_p)) line.add(next_p); if (Math.abs(Point.mul(diff, this.d)) > 1) { ++gaps; if (line.length > 5) { + const firstPoint = new Point(line.points[0].x, line.points[0].y); line.evaluate(true); - if (!this.updateDirectionFromOrigin(Point.add(Point.sub(this.p, line.project(this.p)), line.points[0]))) + if (!this.updateDirectionFromOrigin(Point.add(Point.sub(this.p, line.project(this.p)), firstPoint))) return false; } // the minimum size is 10x10 -> 4 gaps @@ -182,12 +196,21 @@ export class EdgeTracer { traceCorner(dir: Point): Point { this.step(); - let ret = this.p = Point.round(this.p); - let _tmp = dir; + let ret = new Point(this.p.x, this.p.y); + this.p = new Point(this.p.x, this.p.y); + let _tmp = new Point(dir.x, dir.y); dir = this.d; this.d = _tmp; this.traceStep(Point.multiplyBy(dir, -1), 2, false); - // printf("turn: %f x %f -> %f, %f\n", p.x, p.y, d.x, d.y); + // console.log(`turn: ${this.p.x} x ${this.p.y} -> ${this.d.x}, ${this.d.y}`); return ret; } + + clone(): EdgeTracer { + return new EdgeTracer( + this.image, + new Point(this.p.x, this.p.y), + new Point(this.d.x, this.d.y), + ); + } } diff --git a/src/core/datamatrix/detector/LPattern/Point.ts b/src/core/datamatrix/detector/LPattern/Point.ts index cfcf4198..c539692b 100644 --- a/src/core/datamatrix/detector/LPattern/Point.ts +++ b/src/core/datamatrix/detector/LPattern/Point.ts @@ -2,17 +2,13 @@ export default class Point { x: number; y: number; - constructor(x: number = null, y: number = null) { + constructor(x: number = 0, y: number = 0) { this.x = x; this.y = y; } static equals(a: Point, b: Point) { - return a.x === b.x && a.y === b.y; - } - - static unequals(a: Point, b: Point) { - return !Point.equals(a, b); + return a.x === b.x && a.y === b.y; } static add(a: Point, b: Point): Point { @@ -24,7 +20,7 @@ export default class Point { } static multiplyBy(a: Point, s: number): Point { - return new Point(s * a.x, s * a.y); + return new Point(a.x * s, a.y * s); } static divideBy(a: Point, d: number): Point { @@ -36,7 +32,7 @@ export default class Point { } static distance(a: Point, b: Point): number { - return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); + return Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); } static round(p: Point): Point { diff --git a/src/core/datamatrix/detector/LPattern/RegressionLine.ts b/src/core/datamatrix/detector/LPattern/RegressionLine.ts index 0717ecb5..fdbf0545 100644 --- a/src/core/datamatrix/detector/LPattern/RegressionLine.ts +++ b/src/core/datamatrix/detector/LPattern/RegressionLine.ts @@ -2,8 +2,9 @@ import Point from './Point'; export class RegressionLine { - private _points: Point[]; - private _directionInward: Point; + private _points: Point[] = []; + private _directionInward: Point = new Point(); + private a: number; private b: number; private c: number; @@ -15,6 +16,18 @@ export class RegressionLine { return new Point(x, y); } + static average(c: number[], filter: (x: number) => boolean): number { + let sum = 0; + let num = 0; + for (let v of c) { + if (filter(v)) { + sum += v; + ++num; + } + } + return sum / num; + } + public _evaluate(ps: Point[]): void { const allSummarized: Point = ps.reduce((acc, point) => Point.add(acc, point), new Point()); const mean: Point = Point.divideBy(allSummarized, ps.length); @@ -40,17 +53,6 @@ export class RegressionLine { this.c = Point.mul(this.normal(), mean); } - static average(c: number[], filter: (x: number) => boolean): number { - let sum = 0; - let num = 0; - for (let v of c) - if (filter(v)) { - sum += v; - ++num; - } - return sum / num; - } - get points(): Point[] { return this._points; } @@ -61,20 +63,36 @@ export class RegressionLine { : 0; } - public isValid(): boolean { return this.a !== undefined; } - public normal(): Point { return new Point(this.a, this.b); } + public isValid(): boolean { + return this.a !== undefined; + } + + public normal(): Point { + return new Point(this.a, this.b); + } + public project(p: Point): Point { return Point.sub( p, Point.multiplyBy(this.normal(), Point.mul(this.normal(), p) - this.c) ); } + public signedDistance(p: Point): number { return (Point.mul(this.normal(), p) - this.c) / Math.sqrt(this.a * this.a + this.b * this.b); } - public reverse() { this._points = this._points.reverse(); } - public add(p: Point) { this._points.push(p); } - public setDirectionInward(d: Point) { this._directionInward = d; } + + public reverse() { + this._points = this._points.reverse(); + } + + public add(p: Point) { + this._points.push(p); + } + + public setDirectionInward(d: Point) { + this._directionInward = d; + } public evaluate(clean = false) { let ps = this._points; @@ -83,50 +101,50 @@ export class RegressionLine { let old_points_length; while (true) { old_points_length = this._points.length; - this._points = this._points.filter(p => this.signedDistance(p) > 1.5); + this._points = this._points.filter(p => !(this.signedDistance(p) > 1.5)); if (old_points_length === this._points.length) break; - // printf("removed %zu points\n", old_points_size - _points.size()); + // console.log("removed %zu points", old_points_length - this._points.length); this._evaluate(this._points); } } } public modules(beg: Point, end: Point): number { - // assert(this._points.length > 3); - let gapSizes: number[]; + console.assert(this._points.length > 3); + + let gapSizes: number[] = []; // calculate the distance between the points projected onto the regression line for (let i = 1; i < this._points.length; ++i) gapSizes.push(Point.distance(this.project(this._points[i]), this.project(this._points[i - 1]))); - // calculate the (average) distance of two adjacent pixels const unitPixelDist: number = RegressionLine.average(gapSizes, (dist) => { return 0.75 < dist && dist < 1.5; }); // calculate the width of 2 modules (first black pixel to first black pixel) let sum = Point.distance(beg, this.project(this._points[0])) - unitPixelDist; - let i = gapSizes[0]; + let i = 0; for (let dist of gapSizes) { sum += dist; if (dist > 1.9 * unitPixelDist) { - i += 1; gapSizes[i] = sum; + i += 1; sum = 0; } } - i += 1; gapSizes[i] = sum + Point.distance(end, this.project(this._points[this._points.length - 1])); - gapSizes = gapSizes.slice(0, i); + i += 1; + gapSizes = gapSizes.slice(0, i); // TODO: Check + let lineLength: number = Point.distance(beg, end) - unitPixelDist; let meanGapSize: number = lineLength / gapSizes.length; - - // printf("unit pixel dist: %f\n", unitPixelDist); - // printf("lineLength: %f, meanGapSize: %f, gaps: %lu\n", lineLength, meanGapSize, gapSizes.size()); - meanGapSize = RegressionLine.average(gapSizes, (dist) => { return Math.abs(dist - meanGapSize) < meanGapSize / 2; }); - // printf("lineLength: %f, meanGapSize: %f, gaps: %lu\n", lineLength, meanGapSize, gapSizes.size()); + // console.log(`Unit pixel dist: ${unitPixelDist}`) + // console.log(`lineLength: ${lineLength}, meanGapSize: ${meanGapSize}, gaps: ${gapSizes.length}`); + meanGapSize = RegressionLine.average(gapSizes, (dist) => { return Math.abs(dist - meanGapSize) < (meanGapSize / 2); }); + // console.log(`lineLength: ${lineLength}, meanGapSize: ${meanGapSize}, gaps: ${gapSizes.length}`); return lineLength / meanGapSize; } } From 9f49dc837840572e1859d0e03c4d78f4e10c463e Mon Sep 17 00:00:00 2001 From: NilsEngelbach Date: Sun, 6 Dec 2020 19:32:43 +0100 Subject: [PATCH 3/4] Fix small issue with dimensions of sampled grid --- .../detector/LPattern/DetectorWithLPattern.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts b/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts index df35bbf4..6992e271 100644 --- a/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts +++ b/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts @@ -32,12 +32,24 @@ export default class DetectorWithLPattern { // Print matrix (for debugging) static printBitMatrix(matrix: BitMatrix):void { + let content = ""; for (let y = 0; y < matrix.getHeight(); ++y) { let row = ""; for (let x = 0; x < matrix.getWidth(); ++x) row += matrix.get(x, y) ? '+' : '.'; - console.log(row); + content += `${row}\n`; } + content.split('\n').forEach((row) => { + console.log(row); + }); + // try { + // const fs = require('fs'); + // fs.writeFileSync('dump.txt', contet, (err) => { + // if (err) return console.log(err); + // }); + // } catch(e) { + // console.log(e); + // } } static sampleGridAndMoveHalfAPixel(image: BitMatrix, tl: Point, bl: Point, br: Point, tr: Point, dimensionX: number, dimensionY: number): BitMatrix { @@ -76,6 +88,8 @@ export default class DetectorWithLPattern { static detectWithLPattern(image: BitMatrix, tryRotate: boolean): DetectorResult { + // DetectorWithLPattern.printBitMatrix(image); + // walk to the left at first const directions = [new Point(-1, 0), new Point(1, 0), new Point(0, -1), new Point(0, 1)]; @@ -187,7 +201,7 @@ export default class DetectorWithLPattern { if (dimT < 10 || dimT > 144 || dimR < 8 || dimR > 144 ) continue; - let bits = this.sampleGridAndMoveHalfAPixel(image, tl, bl, br, tr, dimT, dimR); + let bits = this.sampleGridAndMoveHalfAPixel(image, tl, bl, br, tr, Math.round(dimT), Math.round(dimR)); // DetectorWithLPattern.printBitMatrix(bits); From 51a31a0c86db4f8f678221217bbb97c3687e9041 Mon Sep 17 00:00:00 2001 From: NilsEngelbach Date: Tue, 8 Dec 2020 17:23:52 +0100 Subject: [PATCH 4/4] Comment out debugging code --- .../detector/LPattern/DetectorWithLPattern.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts b/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts index 6992e271..3fd6d81b 100644 --- a/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts +++ b/src/core/datamatrix/detector/LPattern/DetectorWithLPattern.ts @@ -32,19 +32,19 @@ export default class DetectorWithLPattern { // Print matrix (for debugging) static printBitMatrix(matrix: BitMatrix):void { - let content = ""; - for (let y = 0; y < matrix.getHeight(); ++y) { - let row = ""; - for (let x = 0; x < matrix.getWidth(); ++x) - row += matrix.get(x, y) ? '+' : '.'; - content += `${row}\n`; - } - content.split('\n').forEach((row) => { - console.log(row); - }); + // let content = ""; + // for (let y = 0; y < matrix.getHeight(); ++y) { + // let row = ""; + // for (let x = 0; x < matrix.getWidth(); ++x) + // row += matrix.get(x, y) ? '+' : '.'; + // content += `${row}\n`; + // } + // content.split('\n').forEach((row) => { + // console.log(row); + // }); // try { // const fs = require('fs'); - // fs.writeFileSync('dump.txt', contet, (err) => { + // fs.writeFileSync('dump.txt', content, (err) => { // if (err) return console.log(err); // }); // } catch(e) { @@ -88,7 +88,7 @@ export default class DetectorWithLPattern { static detectWithLPattern(image: BitMatrix, tryRotate: boolean): DetectorResult { - // DetectorWithLPattern.printBitMatrix(image); + DetectorWithLPattern.printBitMatrix(image); // walk to the left at first const directions = [new Point(-1, 0), new Point(1, 0), new Point(0, -1), new Point(0, 1)];