void OneDReader::recordPattern(Ref<BitArray> row, int start, vector<int>& counters) { int numCounters = counters.size(); for (int i = 0; i < numCounters; i++) { counters[i] = 0; } int end = row->getSize(); if (start >= end) { throw NotFoundException(); } bool isWhite = !row->get(start); int counterPosition = 0; int i = start; while (i < end) { if (row->get(i) ^ isWhite) { // that is, exactly one is true counters[counterPosition]++; } else { counterPosition++; if (counterPosition == numCounters) { break; } else { counters[counterPosition] = 1; isWhite = !isWhite; } } i++; } // If we read fully the last section of pixels and filled up our counters -- or filled // the last counter but ran off the side of the image, OK. Otherwise, a problem. if (!(counterPosition == numCounters || (counterPosition == numCounters - 1 && i == end))) { throw NotFoundException(); } }
void CodaBarReader::validatePattern(int start) { // First, sum up the total size of our four categories of stripe sizes; vector<int> sizes (4, 0); vector<int> counts (4, 0); int end = decodeRowResult.length() - 1; // We break out of this loop in the middle, in order to handle // inter-character spaces properly. int pos = start; for (int i = 0; true; i++) { int pattern = CHARACTER_ENCODINGS[(int)decodeRowResult[i]]; for (int j = 6; j >= 0; j--) { // Even j = bars, while odd j = spaces. Categories 2 and 3 are for // long stripes, while 0 and 1 are for short stripes. int category = (j & 1) + (pattern & 1) * 2; sizes[category] += counters[pos + j]; counts[category]++; pattern >>= 1; } if (i >= end) { break; } // We ignore the inter-character space - it could be of any size. pos += 8; } // Calculate our allowable size thresholds using fixed-point math. vector<int> maxes (4, 0); vector<int> mins (4, 0); // Define the threshold of acceptability to be the midpoint between the // average small stripe and the average large stripe. No stripe lengths // should be on the "wrong" side of that line. for (int i = 0; i < 2; i++) { mins[i] = 0; // Accept arbitrarily small "short" stripes. mins[i + 2] = ((sizes[i] << INTEGER_MATH_SHIFT) / counts[i] + (sizes[i + 2] << INTEGER_MATH_SHIFT) / counts[i + 2]) >> 1; maxes[i] = mins[i + 2]; maxes[i + 2] = (sizes[i + 2] * MAX_ACCEPTABLE + PADDING) / counts[i + 2]; } // Now verify that all of the stripes are within the thresholds. pos = start; for (int i = 0; true; i++) { int pattern = CHARACTER_ENCODINGS[(int)decodeRowResult[i]]; for (int j = 6; j >= 0; j--) { // Even j = bars, while odd j = spaces. Categories 2 and 3 are for // long stripes, while 0 and 1 are for short stripes. int category = (j & 1) + (pattern & 1) * 2; int size = counters[pos + j] << INTEGER_MATH_SHIFT; if (size < mins[category] || size > maxes[category]) { throw NotFoundException(); } pattern >>= 1; } if (i >= end) { break; } pos += 8; } }
Ref<Result> UPCEANReader::decodeRow(int rowNumber, Ref<BitArray> row, Range const& startGuardRange) { string& result = decodeRowStringBuffer; result.clear(); int endStart = decodeMiddle(row, startGuardRange, result); Range endRange = decodeEnd(row, endStart); // Make sure there is a quiet zone at least as big as the end pattern after the barcode. // The spec might want more whitespace, but in practice this is the maximum we can count on. int end = endRange[1]; int quietEnd = end + (end - endRange[0]); if (quietEnd >= row->getSize() || !row->isRange(end, quietEnd, false)) { throw NotFoundException(); } Ref<String> resultString (new String(result)); if (!checkChecksum(resultString)) { throw ChecksumException(); } float left = (float) (startGuardRange[1] + startGuardRange[0]) / 2.0f; float right = (float) (endRange[1] + endRange[0]) / 2.0f; BarcodeFormat format = getBarcodeFormat(); ArrayRef< Ref<ResultPoint> > resultPoints(2); resultPoints[0] = Ref<ResultPoint>(new OneDResultPoint(left, (float) rowNumber)); resultPoints[1] = Ref<ResultPoint>(new OneDResultPoint(right, (float) rowNumber)); Ref<Result> decodeResult (new Result(resultString, ArrayRef<char>(), resultPoints, format)); // Java extension and man stuff return decodeResult; }
char Code93Reader::patternToChar(int pattern) { for (int i = 0; i < CHARACTER_ENCODINGS_LENGTH; i++) { if (CHARACTER_ENCODINGS[i] == pattern) { return ALPHABET[i]; } } throw 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 ReaderException Throws exception if no black lines are found in the row */ int ITFReader::skipWhiteSpace(Ref<BitArray> row) { int width = row->getSize(); int endStart = row->getNextSet(0); if (endStart == width) { throw NotFoundException(); } return endStart; }
/** * 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 ReaderException if the quiet zone cannot be found, a ReaderException is thrown. */ void ITFReader::validateQuietZone(Ref<BitArray> row, int startPattern) { int quietCount = this->narrowLineWidth * 10; // expect to find this many pixels of quiet zone for (int 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 NotFoundException(); } }
vector<int> Code128Reader::findStartPattern(Ref<BitArray> row){ int width = row->getSize(); int rowOffset = row->getNextSet(0); int counterPosition = 0; vector<int> counters (6, 0); int patternStart = rowOffset; bool isWhite = false; int patternLength = counters.size(); for (int i = rowOffset; i < width; i++) { if (row->get(i) ^ isWhite) { counters[counterPosition]++; } else { if (counterPosition == patternLength - 1) { int bestVariance = MAX_AVG_VARIANCE; int bestMatch = -1; for (int startCode = CODE_START_A; startCode <= CODE_START_C; startCode++) { int variance = patternMatchVariance(counters, CODE_PATTERNS[startCode], MAX_INDIVIDUAL_VARIANCE); if (variance < bestVariance) { bestVariance = variance; bestMatch = startCode; } } // Look for whitespace before start pattern, >= 50% of width of start pattern if (bestMatch >= 0 && row->isRange(std::max(0, patternStart - (i - patternStart) / 2), patternStart, false)) { vector<int> resultValue (3, 0); resultValue[0] = patternStart; resultValue[1] = i; resultValue[2] = bestMatch; return resultValue; } patternStart += counters[0] + counters[1]; for (int y = 2; y < patternLength; y++) { counters[y - 2] = counters[y]; } counters[patternLength - 2] = 0; counters[patternLength - 1] = 0; counterPosition--; } else { counterPosition++; } counters[counterPosition] = 1; isWhite = !isWhite; } } throw NotFoundException(); }
int CodaBarReader::findStartPattern() { for (int i = 1; i < counterLength; i += 2) { int charOffset = toNarrowWidePattern(i); if (charOffset != -1 && arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) { // Look for whitespace before start pattern, >= 50% of width of start pattern // We make an exception if the whitespace is the first element. int patternSize = 0; for (int j = i; j < i + 7; j++) { patternSize += counters[j]; } if (i == 1 || counters[i-1] >= patternSize / 2) { return i; } } } throw NotFoundException(); }
Ref<Result> MultiFormatUPCEANReader::decodeRow(int rowNumber, Ref<BitArray> row) { // Compute this location once and reuse it on multiple implementations UPCEANReader::Range startGuardPattern = UPCEANReader::findStartGuardPattern(row); for (int i = 0, e = readers.size(); i < e; i++) { Ref<UPCEANReader> reader = readers[i]; Ref<Result> result; try { result = reader->decodeRow(rowNumber, row, startGuardPattern); } catch (ReaderException const& ignored) { continue; } // Special case: a 12-digit code encoded in UPC-A is identical // to a "0" followed by those 12 digits encoded as EAN-13. Each // will recognize such a code, UPC-A as a 12-digit string and // EAN-13 as a 13-digit string starting with "0". Individually // these are correct and their readers will both read such a // code and correctly call it EAN-13, or UPC-A, respectively. // // In this case, if we've been looking for both types, we'd like // to call it a UPC-A code. But for efficiency we only run the // EAN-13 decoder to also read UPC-A. So we special case it // here, and convert an EAN-13 result to a UPC-A result if // appropriate. bool ean13MayBeUPCA = result->getBarcodeFormat() == BarcodeFormat::EAN_13 && result->getText()->charAt(0) == '0'; // Note: doesn't match Java which uses hints bool canReturnUPCA = true; if (ean13MayBeUPCA && canReturnUPCA) { // Transfer the metdata across Ref<Result> resultUPCA (new Result(result->getText()->substring(1), result->getRawBytes(), result->getResultPoints(), BarcodeFormat::UPC_A)); // needs java metadata stuff return resultUPCA; } return result; } throw NotFoundException(); }
UPCEANReader::Range UPCEANReader::findGuardPattern(Ref<BitArray> row, int rowOffset, bool whiteFirst, vector<int> const& pattern, vector<int>& counters) { // cerr << "fGP " << rowOffset << " " << whiteFirst << endl; if (false) { for(int i=0; i < (int)pattern.size(); ++i) { std::cerr << pattern[i]; } std::cerr << std::endl; } int patternLength = (int)pattern.size(); int width = row->getSize(); bool isWhite = whiteFirst; rowOffset = whiteFirst ? row->getNextUnset(rowOffset) : row->getNextSet(rowOffset); int counterPosition = 0; int patternStart = rowOffset; for (int x = rowOffset; x < width; x++) { // std::cerr << "rg " << x << " " << row->get(x) << std::endl; if (row->get(x) ^ isWhite) { counters[counterPosition]++; } else { if (counterPosition == patternLength - 1) { if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { return Range(patternStart, x); } patternStart += counters[0] + counters[1]; for (int y = 2; y < patternLength; y++) { counters[y - 2] = counters[y]; } counters[patternLength - 2] = 0; counters[patternLength - 1] = 0; counterPosition--; } else { counterPosition++; } counters[counterPosition] = 1; isWhite = !isWhite; } } throw 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 ReaderException if digit cannot be decoded */ int ITFReader::decodeDigit(vector<int>& counters){ int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept int bestMatch = -1; int max = sizeof(PATTERNS)/sizeof(PATTERNS[0]); for (int i = 0; i < max; i++) { int const* pattern = PATTERNS[i]; int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE); if (variance < bestVariance) { bestVariance = variance; bestMatch = i; } } if (bestMatch >= 0) { return bestMatch; } else { throw NotFoundException(); } }
int Code128Reader::decodeCode(Ref<BitArray> row, vector<int>& counters, int rowOffset) { recordPattern(row, rowOffset, counters); int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept int bestMatch = -1; for (int d = 0; d < CODE_PATTERNS_LENGTH; d++) { int const* const pattern = CODE_PATTERNS[d]; int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE); if (variance < bestVariance) { bestVariance = variance; bestMatch = d; } } // TODO We're overlooking the fact that the STOP pattern has 7 values, not 6. if (bestMatch >= 0) { return bestMatch; } else { throw NotFoundException(); } }
/** * Records the size of all runs of white and black pixels, starting with white. * This is just like recordPattern, except it records all the counters, and * uses our builtin "counters" member for storage. * @param row row to count from */ void CodaBarReader::setCounters(Ref<BitArray> row) { counterLength = 0; // Start from the first white bit. int i = row->getNextUnset(0); int end = row->getSize(); if (i >= end) { throw NotFoundException(); } bool isWhite = true; int count = 0; for (; i < end; i++) { if (row->get(i) ^ isWhite) { // that is, exactly one is true count++; } else { counterAppend(count); count = 1; isWhite = !isWhite; } } counterAppend(count); }
Code93Reader::Range Code93Reader::findAsteriskPattern(Ref<BitArray> row) { int width = row->getSize(); int rowOffset = row->getNextSet(0); { // Arrays.fill(counters, 0); int size = counters.size(); counters.resize(0); counters.resize(size); } vector<int>& theCounters (counters); int patternStart = rowOffset; bool isWhite = false; int patternLength = theCounters.size(); int counterPosition = 0; for (int i = rowOffset; i < width; i++) { if (row->get(i) ^ isWhite) { theCounters[counterPosition]++; } else { if (counterPosition == patternLength - 1) { if (toPattern(theCounters) == ASTERISK_ENCODING) { return Range(patternStart, i); } patternStart += theCounters[0] + theCounters[1]; for (int y = 2; y < patternLength; y++) { theCounters[y - 2] = theCounters[y]; } theCounters[patternLength - 2] = 0; theCounters[patternLength - 1] = 0; counterPosition--; } else { counterPosition++; } theCounters[counterPosition] = 1; isWhite = !isWhite; } } throw NotFoundException(); }
int UPCEANReader::decodeDigit(Ref<BitArray> row, vector<int> & counters, int rowOffset, vector<int const*> const& patterns) { recordPattern(row, rowOffset, counters); int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept int bestMatch = -1; int max = (int)patterns.size(); for (int i = 0; i < max; i++) { int const* pattern (patterns[i]); int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE); if (variance < bestVariance) { bestVariance = variance; bestMatch = i; } } if (bestMatch >= 0) { return bestMatch; } else { throw NotFoundException(); } }
/** * @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 ReaderException if pattern is not found */ ITFReader::Range ITFReader::findGuardPattern(Ref<BitArray> row, int rowOffset, vector<int> const& pattern) { // TODO: This is very similar to implementation in UPCEANReader. Consider if they can be // merged to a single method. int patternLength = pattern.size(); vector<int> counters(patternLength); int width = row->getSize(); bool isWhite = false; int counterPosition = 0; int patternStart = rowOffset; for (int x = rowOffset; x < width; x++) { if (row->get(x) ^ isWhite) { counters[counterPosition]++; } else { if (counterPosition == patternLength - 1) { if (patternMatchVariance(counters, &pattern[0], MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { return Range(patternStart, x); } patternStart += counters[0] + counters[1]; for (int y = 2; y < patternLength; y++) { counters[y - 2] = counters[y]; } counters[patternLength - 2] = 0; counters[patternLength - 1] = 0; counterPosition--; } else { counterPosition++; } counters[counterPosition] = 1; isWhite = !isWhite; } } throw NotFoundException(); }
Ref<Result> OneDReader::doDecode(Ref<BinaryBitmap> image, DecodeHints hints) { int width = image->getWidth(); int height = image->getHeight(); Ref<BitArray> row(new BitArray(width)); int middle = height >> 1; bool tryHarder = hints.getTryHarder(); int rowStep = std::max(1, height >> (tryHarder ? 8 : 5)); using namespace std; // cerr << "rS " << rowStep << " " << height << " " << tryHarder << endl; int maxLines; if (tryHarder) { maxLines = height; // Look at the whole image, not just the center } else { maxLines = 15; // 15 rows spaced 1/32 apart is roughly the middle half of the image } for (int x = 0; x < maxLines; x++) { // Scanning from the middle out. Determine which row we're looking at next: int rowStepsAboveOrBelow = (x + 1) >> 1; bool isAbove = (x & 0x01) == 0; // i.e. is x even? int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow); if (false) { std::cerr << "rN " << rowNumber << " " << height << " " << middle << " " << rowStep << " " << isAbove << " " << rowStepsAboveOrBelow << std::endl; } if (rowNumber < 0 || rowNumber >= height) { // Oops, if we run off the top or bottom, stop break; } // Estimate black point for this row and load it: try { row = image->getBlackRow(rowNumber, row); } catch (NotFoundException const& ignored) { (void)ignored; continue; } // While we have the image data in a BitArray, it's fairly cheap to reverse it in place to // handle decoding upside down barcodes. for (int attempt = 0; attempt < 2; attempt++) { if (attempt == 1) { row->reverse(); // reverse the row and continue } // Java hints stuff missing try { // Look for a barcode // std::cerr << "rn " << rowNumber << " " << typeid(*this).name() << std::endl; Ref<Result> result = decodeRow(rowNumber, row); // We found our barcode if (attempt == 1) { // But it was upside down, so note that // result.putMetadata(ResultMetadataType.ORIENTATION, new Integer(180)); // And remember to flip the result points horizontally. ArrayRef< Ref<ResultPoint> > points(result->getResultPoints()); if (points) { points[0] = Ref<ResultPoint>(new OneDResultPoint(width - points[0]->getX() - 1, points[0]->getY())); points[1] = Ref<ResultPoint>(new OneDResultPoint(width - points[1]->getX() - 1, points[1]->getY())); } } return result; } catch (ReaderException const& re) { (void)re; continue; } } } throw NotFoundException(); }
Ref<Result> CodaBarReader::decodeRow(int rowNumber, Ref<BitArray> row) { { // Arrays.fill(counters, 0); int size = counters.size(); counters.resize(0); counters.resize(size); } setCounters(row); int startOffset = findStartPattern(); int nextStart = startOffset; decodeRowResult.clear(); do { int charOffset = toNarrowWidePattern(nextStart); if (charOffset == -1) { throw NotFoundException(); } // Hack: We store the position in the alphabet table into a // StringBuilder, so that we can access the decoded patterns in // validatePattern. We'll translate to the actual characters later. decodeRowResult.append(1, (char)charOffset); nextStart += 8; // Stop as soon as we see the end character. if (decodeRowResult.length() > 1 && arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) { break; } } while (nextStart < counterLength); // no fixed end pattern so keep on reading while data is available // Look for whitespace after pattern: int trailingWhitespace = counters[nextStart - 1]; int lastPatternSize = 0; for (int i = -8; i < -1; i++) { lastPatternSize += counters[nextStart + i]; } // We need to see whitespace equal to 50% of the last pattern size, // otherwise this is probably a false positive. The exception is if we are // at the end of the row. (I.e. the barcode barely fits.) if (nextStart < counterLength && trailingWhitespace < lastPatternSize / 2) { throw NotFoundException(); } validatePattern(startOffset); // Translate character table offsets to actual characters. for (int i = 0; i < (int)decodeRowResult.length(); i++) { decodeRowResult[i] = ALPHABET[(int)decodeRowResult[i]]; } // Ensure a valid start and end character char startchar = decodeRowResult[0]; if (!arrayContains(STARTEND_ENCODING, startchar)) { throw NotFoundException(); } char endchar = decodeRowResult[decodeRowResult.length() - 1]; if (!arrayContains(STARTEND_ENCODING, endchar)) { throw NotFoundException(); } // remove stop/start characters character and check if a long enough string is contained if ((int)decodeRowResult.length() <= MIN_CHARACTER_LENGTH) { // Almost surely a false positive ( start + stop + at least 1 character) throw NotFoundException(); } decodeRowResult.erase(decodeRowResult.length() - 1, 1); decodeRowResult.erase(0, 1); int runningCount = 0; for (int i = 0; i < startOffset; i++) { runningCount += counters[i]; } float left = (float) runningCount; for (int i = startOffset; i < nextStart - 1; i++) { runningCount += counters[i]; } float right = (float) runningCount; ArrayRef< Ref<ResultPoint> > resultPoints(2); resultPoints[0] = Ref<OneDResultPoint>(new OneDResultPoint(left, (float) rowNumber)); resultPoints[1] = Ref<OneDResultPoint>(new OneDResultPoint(right, (float) rowNumber)); return Ref<Result>(new Result(Ref<String>(new String(decodeRowResult)), ArrayRef<char>(), resultPoints, BarcodeFormat::CODABAR)); }
Ref<Result> Code93Reader::decodeRow(int rowNumber, Ref<BitArray> row, zxing::DecodeHints /*hints*/) { Range start (findAsteriskPattern(row)); // Read off white space int nextStart = row->getNextSet(start[1]); int end = row->getSize(); vector<int>& theCounters (counters); { // Arrays.fill(counters, 0); int size = theCounters.size(); theCounters.resize(0); theCounters.resize(size); } string& result (decodeRowResult); result.clear(); char decodedChar; int lastStart; do { recordPattern(row, nextStart, theCounters); int pattern = toPattern(theCounters); if (pattern < 0) { throw NotFoundException(); } decodedChar = patternToChar(pattern); result.append(1, decodedChar); lastStart = nextStart; for(int i=0, e=theCounters.size(); i < e; ++i) { nextStart += theCounters[i]; } // Read off white space nextStart = row->getNextSet(nextStart); } while (decodedChar != '*'); result.resize(result.length() - 1); // remove asterisk // Look for whitespace after pattern: int lastPatternSize = 0; for (int i = 0, e = theCounters.size(); i < e; i++) { lastPatternSize += theCounters[i]; } // Should be at least one more black module if (nextStart == end || !row->get(nextStart)) { throw NotFoundException(); } if (result.length() < 2) { // false positive -- need at least 2 checksum digits throw NotFoundException(); } checkChecksums(result); // Remove checksum digits result.resize(result.length() - 2); Ref<String> resultString = decodeExtended(result); float left = (float) (start[1] + start[0]) / 2.0f; float right = lastStart + lastPatternSize / 2.0f; ArrayRef< Ref<ResultPoint> > resultPoints (2); resultPoints[0] = Ref<OneDResultPoint>(new OneDResultPoint(left, (float) rowNumber)); resultPoints[1] = Ref<OneDResultPoint>(new OneDResultPoint(right, (float) rowNumber)); return Ref<Result>(new Result( resultString, ArrayRef<byte>(), resultPoints, BarcodeFormat::CODE_93)); }
Ref<Result> Code128Reader::decodeRow(int rowNumber, Ref<BitArray> row) { // boolean convertFNC1 = hints != null && hints.containsKey(DecodeHintType.ASSUME_GS1); boolean convertFNC1 = false; vector<int> startPatternInfo (findStartPattern(row)); int startCode = startPatternInfo[2]; int codeSet; switch (startCode) { case CODE_START_A: codeSet = CODE_CODE_A; break; case CODE_START_B: codeSet = CODE_CODE_B; break; case CODE_START_C: codeSet = CODE_CODE_C; break; default: throw FormatException(); } bool done = false; bool isNextShifted = false; string result; vector<char> rawCodes(20, 0); int lastStart = startPatternInfo[0]; int nextStart = startPatternInfo[1]; vector<int> counters (6, 0); int lastCode = 0; int code = 0; int checksumTotal = startCode; int multiplier = 0; bool lastCharacterWasPrintable = true; std::ostringstream oss; while (!done) { bool unshift = isNextShifted; isNextShifted = false; // Save off last code lastCode = code; code = decodeCode(row, counters, nextStart); // Remember whether the last code was printable or not (excluding CODE_STOP) if (code != CODE_STOP) { lastCharacterWasPrintable = true; } // Add to checksum computation (if not CODE_STOP of course) if (code != CODE_STOP) { multiplier++; checksumTotal += multiplier * code; } // Advance to where the next code will to start lastStart = nextStart; for (int i = 0, e = counters.size(); i < e; i++) { nextStart += counters[i]; } // Take care of illegal start codes switch (code) { case CODE_START_A: case CODE_START_B: case CODE_START_C: throw FormatException(); } switch (codeSet) { case CODE_CODE_A: if (code < 64) { result.append(1, (char) (' ' + code)); } else if (code < 96) { result.append(1, (char) (code - 64)); } else { // Don't let CODE_STOP, which always appears, affect whether whether we think the // last code was printable or not. if (code != CODE_STOP) { lastCharacterWasPrintable = false; } switch (code) { case CODE_FNC_1: if (convertFNC1) { if (result.length() == 0){ // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code // is FNC1 then this is GS1-128. We add the symbology identifier. result.append("]C1"); } else { // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS) result.append(1, (char) 29); } } break; case CODE_FNC_2: case CODE_FNC_3: case CODE_FNC_4_A: // do nothing? break; case CODE_SHIFT: isNextShifted = true; codeSet = CODE_CODE_B; break; case CODE_CODE_B: codeSet = CODE_CODE_B; break; case CODE_CODE_C: codeSet = CODE_CODE_C; break; case CODE_STOP: done = true; break; } } break; case CODE_CODE_B: if (code < 96) { result.append(1, (char) (' ' + code)); } else { if (code != CODE_STOP) { lastCharacterWasPrintable = false; } switch (code) { case CODE_FNC_1: case CODE_FNC_2: case CODE_FNC_3: case CODE_FNC_4_B: // do nothing? break; case CODE_SHIFT: isNextShifted = true; codeSet = CODE_CODE_A; break; case CODE_CODE_A: codeSet = CODE_CODE_A; break; case CODE_CODE_C: codeSet = CODE_CODE_C; break; case CODE_STOP: done = true; break; } } break; case CODE_CODE_C: if (code < 100) { if (code < 10) { result.append(1, '0'); } oss.clear(); oss.str(""); oss << code; result.append(oss.str()); } else { if (code != CODE_STOP) { lastCharacterWasPrintable = false; } switch (code) { case CODE_FNC_1: // do nothing? break; case CODE_CODE_A: codeSet = CODE_CODE_A; break; case CODE_CODE_B: codeSet = CODE_CODE_B; break; case CODE_STOP: done = true; break; } } break; } // Unshift back to another code set if we were shifted if (unshift) { codeSet = codeSet == CODE_CODE_A ? CODE_CODE_B : CODE_CODE_A; } } // Check for ample whitespace following pattern, but, to do this we first need to remember that // we fudged decoding CODE_STOP since it actually has 7 bars, not 6. There is a black bar left // to read off. Would be slightly better to properly read. Here we just skip it: nextStart = row->getNextUnset(nextStart); if (!row->isRange(nextStart, std::min(row->getSize(), nextStart + (nextStart - lastStart) / 2), false)) { throw NotFoundException(); } // Pull out from sum the value of the penultimate check code checksumTotal -= multiplier * lastCode; // lastCode is the checksum then: if (checksumTotal % 103 != lastCode) { throw ChecksumException(); } // Need to pull out the check digits from string int resultLength = result.length(); if (resultLength == 0) { // false positive throw NotFoundException(); } // Only bother if the result had at least one character, and if the checksum digit happened to // be a printable character. If it was just interpreted as a control code, nothing to remove. if (resultLength > 0 && lastCharacterWasPrintable) { if (codeSet == CODE_CODE_C) { result.erase(resultLength - 2, resultLength); } else { result.erase(resultLength - 1, resultLength); } } float left = (float) (startPatternInfo[1] + startPatternInfo[0]) / 2.0f; float right = (float) (nextStart + lastStart) / 2.0f; int rawCodesSize = rawCodes.size(); ArrayRef<char> rawBytes (rawCodesSize); for (int i = 0; i < rawCodesSize; i++) { rawBytes[i] = rawCodes[i]; } ArrayRef< Ref<ResultPoint> > resultPoints(2); resultPoints[0] = Ref<OneDResultPoint>(new OneDResultPoint(left, (float) rowNumber)); resultPoints[1] = Ref<OneDResultPoint>(new OneDResultPoint(right, (float) rowNumber)); return Ref<Result>(new Result(Ref<String>(new String(result)), rawBytes, resultPoints, BarcodeFormat::CODE_128)); }
Ref<DetectorResult> Detector::detect() { Ref<WhiteRectangleDetector> rectangleDetector_(new WhiteRectangleDetector(image_)); std::vector<Ref<ResultPoint> > ResultPoints = rectangleDetector_->detect(); Ref<ResultPoint> pointA = ResultPoints[0]; Ref<ResultPoint> pointB = ResultPoints[1]; Ref<ResultPoint> pointC = ResultPoints[2]; Ref<ResultPoint> pointD = ResultPoints[3]; // Point A and D are across the diagonal from one another, // as are B and C. Figure out which are the solid black lines // by counting transitions std::vector<Ref<ResultPointsAndTransitions> > transitions(4); transitions[0].reset(transitionsBetween(pointA, pointB)); transitions[1].reset(transitionsBetween(pointA, pointC)); transitions[2].reset(transitionsBetween(pointB, pointD)); transitions[3].reset(transitionsBetween(pointC, pointD)); insertionSort(transitions); // Sort by number of transitions. First two will be the two solid sides; last two // will be the two alternating black/white sides Ref<ResultPointsAndTransitions> lSideOne(transitions[0]); Ref<ResultPointsAndTransitions> lSideTwo(transitions[1]); // Figure out which point is their intersection by tallying up the number of times we see the // endpoints in the four endpoints. One will show up twice. typedef std::map<Ref<ResultPoint>, int> PointMap; PointMap pointCount; increment(pointCount, lSideOne->getFrom()); increment(pointCount, lSideOne->getTo()); increment(pointCount, lSideTwo->getFrom()); increment(pointCount, lSideTwo->getTo()); // Figure out which point is their intersection by tallying up the number of times we see the // endpoints in the four endpoints. One will show up twice. Ref<ResultPoint> maybeTopLeft; Ref<ResultPoint> bottomLeft; Ref<ResultPoint> maybeBottomRight; for (PointMap::const_iterator entry = pointCount.begin(), end = pointCount.end(); entry != end; ++entry) { Ref<ResultPoint> const& point = entry->first; int value = entry->second; if (value == 2) { bottomLeft = point; // this is definitely the bottom left, then -- end of two L sides } else { // Otherwise it's either top left or bottom right -- just assign the two arbitrarily now if (maybeTopLeft == 0) { maybeTopLeft = point; } else { maybeBottomRight = point; } } } if (maybeTopLeft == 0 || bottomLeft == 0 || maybeBottomRight == 0) { throw NotFoundException(); } // Bottom left is correct but top left and bottom right might be switched std::vector<Ref<ResultPoint> > corners(3); corners[0].reset(maybeTopLeft); corners[1].reset(bottomLeft); corners[2].reset(maybeBottomRight); // Use the dot product trick to sort them out ResultPoint::orderBestPatterns(corners); // Now we know which is which: Ref<ResultPoint> bottomRight(corners[0]); bottomLeft = corners[1]; Ref<ResultPoint> topLeft(corners[2]); // Which point didn't we find in relation to the "L" sides? that's the top right corner Ref<ResultPoint> topRight; if (!(pointA->equals(bottomRight) || pointA->equals(bottomLeft) || pointA->equals(topLeft))) { topRight = pointA; } else if (!(pointB->equals(bottomRight) || pointB->equals(bottomLeft) || pointB->equals(topLeft))) { topRight = pointB; } else if (!(pointC->equals(bottomRight) || pointC->equals(bottomLeft) || pointC->equals(topLeft))) { topRight = pointC; } else { topRight = pointD; } // Next determine the dimension by tracing along the top or right side and counting black/white // transitions. Since we start inside a black module, we should see a number of transitions // equal to 1 less than the code dimension. Well, actually 2 less, because we are going to // end on a black module: // The top right point is actually the corner of a module, which is one of the two black modules // adjacent to the white module at the top right. Tracing to that corner from either the top left // or bottom right should work here. int dimensionTop = transitionsBetween(topLeft, topRight)->getTransitions(); int dimensionRight = transitionsBetween(bottomRight, topRight)->getTransitions(); //dimensionTop++; if ((dimensionTop & 0x01) == 1) { // it can't be odd, so, round... up? dimensionTop++; } dimensionTop += 2; //dimensionRight++; if ((dimensionRight & 0x01) == 1) { // it can't be odd, so, round... up? dimensionRight++; } dimensionRight += 2; Ref<BitMatrix> bits; Ref<PerspectiveTransform> transform; Ref<ResultPoint> correctedTopRight; // Rectanguar symbols are 6x16, 6x28, 10x24, 10x32, 14x32, or 14x44. If one dimension is more // than twice the other, it's certainly rectangular, but to cut a bit more slack we accept it as // rectangular if the bigger side is at least 7/4 times the other: if (4 * dimensionTop >= 7 * dimensionRight || 4 * dimensionRight >= 7 * dimensionTop) { // The matrix is rectangular correctedTopRight = correctTopRightRectangular(bottomLeft, bottomRight, topLeft, topRight, dimensionTop, dimensionRight); if (correctedTopRight == NULL) { correctedTopRight = topRight; } dimensionTop = transitionsBetween(topLeft, correctedTopRight)->getTransitions(); dimensionRight = transitionsBetween(bottomRight, correctedTopRight)->getTransitions(); if ((dimensionTop & 0x01) == 1) { // it can't be odd, so, round... up? dimensionTop++; } if ((dimensionRight & 0x01) == 1) { // it can't be odd, so, round... up? dimensionRight++; } transform = createTransform(topLeft, correctedTopRight, bottomLeft, bottomRight, dimensionTop, dimensionRight); bits = sampleGrid(image_, dimensionTop, dimensionRight, transform); } else { // The matrix is square int dimension = min(dimensionRight, dimensionTop); // correct top right point to match the white module correctedTopRight = correctTopRight(bottomLeft, bottomRight, topLeft, topRight, dimension); if (correctedTopRight == NULL) { correctedTopRight = topRight; } // Redetermine the dimension using the corrected top right point int dimensionCorrected = std::max(transitionsBetween(topLeft, correctedTopRight)->getTransitions(), transitionsBetween(bottomRight, correctedTopRight)->getTransitions()); dimensionCorrected++; if ((dimensionCorrected & 0x01) == 1) { dimensionCorrected++; } transform = createTransform(topLeft, correctedTopRight, bottomLeft, bottomRight, dimensionCorrected, dimensionCorrected); bits = sampleGrid(image_, dimensionCorrected, dimensionCorrected, transform); } ArrayRef< Ref<ResultPoint> > points (new Array< Ref<ResultPoint> >(4)); points[0].reset(topLeft); points[1].reset(bottomLeft); points[2].reset(correctedTopRight); points[3].reset(bottomRight); Ref<DetectorResult> detectorResult(new DetectorResult(bits, points)); return detectorResult; }
void LinesSampler::linesMatrixToCodewords(vector<vector<int> >& clusterNumbers, const int symbolsPerLine, const vector<float>& symbolWidths, Ref<BitMatrix> linesMatrix, vector<vector<int> >& codewords) { for (int y = 0; y < linesMatrix->getHeight(); y++) { // Not sure if this is the right way to handle this but avoids an error: if (symbolsPerLine > (int)symbolWidths.size()) { throw NotFoundException("Inconsistent number of symbols in this line."); } // TODO: use symbolWidths.size() instead of symbolsPerLine to at least decode some codewords codewords[y].resize(symbolsPerLine, 0); clusterNumbers[y].resize(symbolsPerLine, -1); int line = y; vector<int> barWidths(1, 0); int barCount = 0; // Runlength encode the bars in the scanned linesMatrix. // We assume that the first bar is black, as determined by the PDF417 standard. bool isSetBar = true; // Filter small white bars at the beginning of the barcode. // Small white bars may occur due to small deviations in scan line sampling. barWidths[0] += BARCODE_START_OFFSET; for (int x = BARCODE_START_OFFSET; x < linesMatrix->getWidth(); x++) { if (linesMatrix->get(x, line)) { if (!isSetBar) { isSetBar = true; barCount++; barWidths.resize(barWidths.size() + 1); } } else { if (isSetBar) { isSetBar = false; barCount++; barWidths.resize(barWidths.size() + 1); } } barWidths[barCount]++; } // Don't forget the last bar. barCount++; barWidths.resize(barWidths.size() + 1); #if PDF417_DIAG && OUTPUT_BAR_WIDTH { for (int i = 0; i < barWidths.size(); i++) { cout << barWidths[i] << ", "; } cout << endl; } #endif ////////////////////////////////////////////////// // Find the symbols in the line by counting bar lengths until we reach symbolWidth. // We make sure, that the last bar of a symbol is always white, as determined by the PDF417 standard. // This helps to reduce the amount of errors done during the symbol recognition. // The symbolWidth usually is not constant over the width of the barcode. int cwWidth = 0; int cwCount = 0; vector<int> cwStarts(symbolsPerLine, 0); cwStarts[0] = 0; cwCount++; for (int i = 0; i < barCount && cwCount < symbolsPerLine; i++) { cwWidth += barWidths[i]; if ((float)cwWidth > symbolWidths[cwCount - 1]) { if ((i % 2) == 1) { // check if bar is white i++; } cwWidth = barWidths[i]; cwStarts[cwCount] = i; cwCount++; } } #if PDF417_DIAG && OUTPUT_CW_STARTS { for (int i = 0; i < cwStarts.size(); i++) { cout << cwStarts[i] << ", "; } cout << endl; } #endif /////////////////////////////////////////// vector<vector<float> > cwRatios(symbolsPerLine); // Distribute bar widths to modules of a codeword. for (int i = 0; i < symbolsPerLine; i++) { cwRatios[i].resize(BARS_IN_SYMBOL, 0.0f); const int cwStart = cwStarts[i]; const int cwEnd = (i == symbolsPerLine - 1) ? barCount : cwStarts[i + 1]; const int cwLength = cwEnd - cwStart; if (cwLength < 7 || cwLength > 9) { // We try to recover smybols with 7 or 9 bars and spaces with heuristics, but everything else is beyond repair. continue; } float cwWidth = 0; // For symbols with 9 bar length simply ignore the last bar. for (int j = 0; j < min(BARS_IN_SYMBOL, cwLength); ++j) { cwWidth += (float)barWidths[cwStart + j]; } // If there were only 7 bars and spaces detected use the following heuristic: // Assume the length of the symbol is symbolWidth and the last (unrecognized) bar uses all remaining space. if (cwLength == 7) { for (int j = 0; j < cwLength; ++j) { cwRatios[i][j] = (float)barWidths[cwStart + j] / symbolWidths[i]; } cwRatios[i][7] = (symbolWidths[i] - cwWidth) / symbolWidths[i]; } else { for (int j = 0; j < (int)cwRatios[i].size(); ++j) { cwRatios[i][j] = (float)barWidths[cwStart + j] / cwWidth; } } float bestMatchError = std::numeric_limits<float>::max(); int bestMatch = 0; // Search for the most possible codeword by comparing the ratios of bar size to symbol width. // The sum of the squared differences is used as similarity metric. // (Picture it as the square euclidian distance in the space of eight tuples where a tuple represents the bar ratios.) for (int j = 0; j < POSSIBLE_SYMBOLS; j++) { float error = 0.0f; for (int k = 0; k < BARS_IN_SYMBOL; k++) { float diff = RATIOS_TABLE[j * BARS_IN_SYMBOL + k] - cwRatios[i][k]; error += diff * diff; if (error >= bestMatchError) { break; } } if (error < bestMatchError) { bestMatchError = error; bestMatch = BitMatrixParser::SYMBOL_TABLE[j]; } } codewords[y][i] = bestMatch; clusterNumbers[y][i] = calculateClusterNumber(bestMatch); } } #if PDF417_DIAG && OUTPUT_CLUSTER_NUMBERS { for (int i = 0; i < clusterNumbers.size(); i++) { for (int j = 0; j < clusterNumbers[i].size(); j++) { cout << clusterNumbers[i][j] << ", "; } cout << endl; } } #endif #if PDF417_DIAG { Ref<BitMatrix> bits(new BitMatrix(symbolsPerLine * MODULES_IN_SYMBOL, codewords.size())); codewordsToBitMatrix(codewords, bits); static int __cnt__ = 0; stringstream ss; ss << "pdf417-detectedRaw" << __cnt__++ << ".png"; bits->writePng(ss.str().c_str(), 8, 16); } #endif }