/*
|
* Copyright 2008 ZXing authors
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
/*namespace com.google.zxing.oned {*/
|
import BarcodeFormat from '../BarcodeFormat';
|
import DecodeHintType from '../DecodeHintType';
|
import FormatException from '../FormatException';
|
import NotFoundException from '../NotFoundException';
|
import Result from '../Result';
|
import ResultPoint from '../ResultPoint';
|
import StringBuilder from '../util/StringBuilder';
|
import System from '../util/System';
|
import OneDReader from './OneDReader';
|
/**
|
* <p>Decodes ITF barcodes.</p>
|
*
|
* @author Tjieco
|
*/
|
export default class ITFReader extends OneDReader {
|
constructor() {
|
// private static W = 3; // Pixel width of a 3x wide line
|
// private static w = 2; // Pixel width of a 2x wide line
|
// private static N = 1; // Pixed width of a narrow line
|
super(...arguments);
|
// Stores the actual narrow line width of the image being decoded.
|
this.narrowLineWidth = -1;
|
}
|
// See ITFWriter.PATTERNS
|
/*
|
|
/!**
|
* Patterns of Wide / Narrow lines to indicate each digit
|
*!/
|
*/
|
decodeRow(rowNumber, row, hints) {
|
// Find out where the Middle section (payload) starts & ends
|
let startRange = this.decodeStart(row);
|
let endRange = this.decodeEnd(row);
|
let result = new StringBuilder();
|
ITFReader.decodeMiddle(row, startRange[1], endRange[0], result);
|
let resultString = result.toString();
|
let allowedLengths = null;
|
if (hints != null) {
|
allowedLengths = hints.get(DecodeHintType.ALLOWED_LENGTHS);
|
}
|
if (allowedLengths == null) {
|
allowedLengths = ITFReader.DEFAULT_ALLOWED_LENGTHS;
|
}
|
// To avoid false positives with 2D barcodes (and other patterns), make
|
// an assumption that the decoded string must be a 'standard' length if it's short
|
let length = resultString.length;
|
let lengthOK = false;
|
let maxAllowedLength = 0;
|
for (let value of allowedLengths) {
|
if (length === value) {
|
lengthOK = true;
|
break;
|
}
|
if (value > maxAllowedLength) {
|
maxAllowedLength = value;
|
}
|
}
|
if (!lengthOK && length > maxAllowedLength) {
|
lengthOK = true;
|
}
|
if (!lengthOK) {
|
throw new FormatException();
|
}
|
const points = [new ResultPoint(startRange[1], rowNumber), new ResultPoint(endRange[0], rowNumber)];
|
let resultReturn = new Result(resultString, null, // no natural byte representation for these barcodes
|
0, points, BarcodeFormat.ITF, new Date().getTime());
|
return resultReturn;
|
}
|
/*
|
/!**
|
* @param row row of black/white values to search
|
* @param payloadStart offset of start pattern
|
* @param resultString {@link StringBuilder} to append decoded chars to
|
* @throws NotFoundException if decoding could not complete successfully
|
*!/*/
|
static decodeMiddle(row, payloadStart, payloadEnd, resultString) {
|
// Digits are interleaved in pairs - 5 black lines for one digit, and the
|
// 5
|
// interleaved white lines for the second digit.
|
// Therefore, need to scan 10 lines and then
|
// split these into two arrays
|
let counterDigitPair = new Int32Array(10); // 10
|
let counterBlack = new Int32Array(5); // 5
|
let counterWhite = new Int32Array(5); // 5
|
counterDigitPair.fill(0);
|
counterBlack.fill(0);
|
counterWhite.fill(0);
|
while (payloadStart < payloadEnd) {
|
// Get 10 runs of black/white.
|
OneDReader.recordPattern(row, payloadStart, counterDigitPair);
|
// Split them into each array
|
for (let k = 0; k < 5; k++) {
|
let twoK = 2 * k;
|
counterBlack[k] = counterDigitPair[twoK];
|
counterWhite[k] = counterDigitPair[twoK + 1];
|
}
|
let bestMatch = ITFReader.decodeDigit(counterBlack);
|
resultString.append(bestMatch.toString());
|
bestMatch = this.decodeDigit(counterWhite);
|
resultString.append(bestMatch.toString());
|
counterDigitPair.forEach(function (counterDigit) {
|
payloadStart += counterDigit;
|
});
|
}
|
}
|
/*/!**
|
* Identify where the start of the middle / payload section starts.
|
*
|
* @param row row of black/white values to search
|
* @return Array, containing index of start of 'start block' and end of
|
* 'start block'
|
*!/*/
|
decodeStart(row) {
|
let endStart = ITFReader.skipWhiteSpace(row);
|
let startPattern = ITFReader.findGuardPattern(row, endStart, ITFReader.START_PATTERN);
|
// Determine the width of a narrow line in pixels. We can do this by
|
// getting the width of the start pattern and dividing by 4 because its
|
// made up of 4 narrow lines.
|
this.narrowLineWidth = (startPattern[1] - startPattern[0]) / 4;
|
this.validateQuietZone(row, startPattern[0]);
|
return startPattern;
|
}
|
/*/!**
|
* The start & end patterns must be pre/post fixed by a quiet zone. This
|
* zone must be at least 10 times the width of a narrow line. Scan back until
|
* we either get to the start of the barcode or match the necessary number of
|
* quiet zone pixels.
|
*
|
* Note: Its assumed the row is reversed when using this method to find
|
* quiet zone after the end pattern.
|
*
|
* ref: http://www.barcode-1.net/i25code.html
|
*
|
* @param row bit array representing the scanned barcode.
|
* @param startPattern index into row of the start or end pattern.
|
* @throws NotFoundException if the quiet zone cannot be found
|
*!/*/
|
validateQuietZone(row, startPattern) {
|
let quietCount = this.narrowLineWidth * 10; // expect to find this many pixels of quiet zone
|
// if there are not so many pixel at all let's try as many as possible
|
quietCount = quietCount < startPattern ? quietCount : startPattern;
|
for (let i = startPattern - 1; quietCount > 0 && i >= 0; i--) {
|
if (row.get(i)) {
|
break;
|
}
|
quietCount--;
|
}
|
if (quietCount !== 0) {
|
// Unable to find the necessary number of quiet zone pixels.
|
throw new NotFoundException();
|
}
|
}
|
/*
|
/!**
|
* Skip all whitespace until we get to the first black line.
|
*
|
* @param row row of black/white values to search
|
* @return index of the first black line.
|
* @throws NotFoundException Throws exception if no black lines are found in the row
|
*!/*/
|
static skipWhiteSpace(row) {
|
const width = row.getSize();
|
const endStart = row.getNextSet(0);
|
if (endStart === width) {
|
throw new NotFoundException();
|
}
|
return endStart;
|
}
|
/*/!**
|
* Identify where the end of the middle / payload section ends.
|
*
|
* @param row row of black/white values to search
|
* @return Array, containing index of start of 'end block' and end of 'end
|
* block'
|
*!/*/
|
decodeEnd(row) {
|
// For convenience, reverse the row and then
|
// search from 'the start' for the end block
|
row.reverse();
|
try {
|
let endStart = ITFReader.skipWhiteSpace(row);
|
let endPattern;
|
try {
|
endPattern = ITFReader.findGuardPattern(row, endStart, ITFReader.END_PATTERN_REVERSED[0]);
|
}
|
catch (error) {
|
if (error instanceof NotFoundException) {
|
endPattern = ITFReader.findGuardPattern(row, endStart, ITFReader.END_PATTERN_REVERSED[1]);
|
}
|
}
|
// The start & end patterns must be pre/post fixed by a quiet zone. This
|
// zone must be at least 10 times the width of a narrow line.
|
// ref: http://www.barcode-1.net/i25code.html
|
this.validateQuietZone(row, endPattern[0]);
|
// Now recalculate the indices of where the 'endblock' starts & stops to
|
// accommodate
|
// the reversed nature of the search
|
let temp = endPattern[0];
|
endPattern[0] = row.getSize() - endPattern[1];
|
endPattern[1] = row.getSize() - temp;
|
return endPattern;
|
}
|
finally {
|
// Put the row back the right way.
|
row.reverse();
|
}
|
}
|
/*
|
/!**
|
* @param row row of black/white values to search
|
* @param rowOffset position to start search
|
* @param pattern pattern of counts of number of black and white pixels that are
|
* being searched for as a pattern
|
* @return start/end horizontal offset of guard pattern, as an array of two
|
* ints
|
* @throws NotFoundException if pattern is not found
|
*!/*/
|
static findGuardPattern(row, rowOffset, pattern) {
|
let patternLength = pattern.length;
|
let counters = new Int32Array(patternLength);
|
let width = row.getSize();
|
let isWhite = false;
|
let counterPosition = 0;
|
let patternStart = rowOffset;
|
counters.fill(0);
|
for (let x = rowOffset; x < width; x++) {
|
if (row.get(x) !== isWhite) {
|
counters[counterPosition]++;
|
}
|
else {
|
if (counterPosition === patternLength - 1) {
|
if (OneDReader.patternMatchVariance(counters, pattern, ITFReader.MAX_INDIVIDUAL_VARIANCE) < ITFReader.MAX_AVG_VARIANCE) {
|
return [patternStart, x];
|
}
|
patternStart += counters[0] + counters[1];
|
System.arraycopy(counters, 2, counters, 0, counterPosition - 1);
|
counters[counterPosition - 1] = 0;
|
counters[counterPosition] = 0;
|
counterPosition--;
|
}
|
else {
|
counterPosition++;
|
}
|
counters[counterPosition] = 1;
|
isWhite = !isWhite;
|
}
|
}
|
throw new NotFoundException();
|
}
|
/*/!**
|
* Attempts to decode a sequence of ITF black/white lines into single
|
* digit.
|
*
|
* @param counters the counts of runs of observed black/white/black/... values
|
* @return The decoded digit
|
* @throws NotFoundException if digit cannot be decoded
|
*!/*/
|
static decodeDigit(counters) {
|
let bestVariance = ITFReader.MAX_AVG_VARIANCE; // worst variance we'll accept
|
let bestMatch = -1;
|
let max = ITFReader.PATTERNS.length;
|
for (let i = 0; i < max; i++) {
|
let pattern = ITFReader.PATTERNS[i];
|
let variance = OneDReader.patternMatchVariance(counters, pattern, ITFReader.MAX_INDIVIDUAL_VARIANCE);
|
if (variance < bestVariance) {
|
bestVariance = variance;
|
bestMatch = i;
|
}
|
else if (variance === bestVariance) {
|
// if we find a second 'best match' with the same variance, we can not reliably report to have a suitable match
|
bestMatch = -1;
|
}
|
}
|
if (bestMatch >= 0) {
|
return bestMatch % 10;
|
}
|
else {
|
throw new NotFoundException();
|
}
|
}
|
}
|
ITFReader.PATTERNS = [
|
Int32Array.from([1, 1, 2, 2, 1]),
|
Int32Array.from([2, 1, 1, 1, 2]),
|
Int32Array.from([1, 2, 1, 1, 2]),
|
Int32Array.from([2, 2, 1, 1, 1]),
|
Int32Array.from([1, 1, 2, 1, 2]),
|
Int32Array.from([2, 1, 2, 1, 1]),
|
Int32Array.from([1, 2, 2, 1, 1]),
|
Int32Array.from([1, 1, 1, 2, 2]),
|
Int32Array.from([2, 1, 1, 2, 1]),
|
Int32Array.from([1, 2, 1, 2, 1]),
|
Int32Array.from([1, 1, 3, 3, 1]),
|
Int32Array.from([3, 1, 1, 1, 3]),
|
Int32Array.from([1, 3, 1, 1, 3]),
|
Int32Array.from([3, 3, 1, 1, 1]),
|
Int32Array.from([1, 1, 3, 1, 3]),
|
Int32Array.from([3, 1, 3, 1, 1]),
|
Int32Array.from([1, 3, 3, 1, 1]),
|
Int32Array.from([1, 1, 1, 3, 3]),
|
Int32Array.from([3, 1, 1, 3, 1]),
|
Int32Array.from([1, 3, 1, 3, 1]) // 9
|
];
|
ITFReader.MAX_AVG_VARIANCE = 0.38;
|
ITFReader.MAX_INDIVIDUAL_VARIANCE = 0.5;
|
/* /!** Valid ITF lengths. Anything longer than the largest value is also allowed. *!/*/
|
ITFReader.DEFAULT_ALLOWED_LENGTHS = [6, 8, 10, 12, 14];
|
/*/!**
|
* Start/end guard pattern.
|
*
|
* Note: The end pattern is reversed because the row is reversed before
|
* searching for the END_PATTERN
|
*!/*/
|
ITFReader.START_PATTERN = Int32Array.from([1, 1, 1, 1]);
|
ITFReader.END_PATTERN_REVERSED = [
|
Int32Array.from([1, 1, 2]),
|
Int32Array.from([1, 1, 3]) // 3x
|
];
|