A simple, fast array of bits, represented compactly by an array of ints internally.
protected internal override int decodeMiddle(BitArray row, int[] startRange, StringBuilder result) { int[] counters = decodeMiddleCounters; counters[0] = 0; counters[1] = 0; counters[2] = 0; counters[3] = 0; int end = row.Size; int rowOffset = startRange[1]; for (int x = 0; x < 4 && rowOffset < end; x++) { int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS); result.Append((char) ('0' + bestMatch)); for (int i = 0; i < counters.Length; i++) { rowOffset += counters[i]; } } int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN); rowOffset = middleRange[1]; for (int x = 0; x < 4 && rowOffset < end; x++) { int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS); result.Append((char) ('0' + bestMatch)); for (int i = 0; i < counters.Length; i++) { rowOffset += counters[i]; } } return rowOffset; }
public override Result decodeRow(int rowNumber, BitArray row, Dictionary<DecodeHintType, Object> hints) { int size = readers.Count; for (int i = 0; i < size; i++) { var reader = (OneDReader) readers[i]; try { return reader.decodeRow(rowNumber, row, hints); } catch (ReaderException) { // continue } } throw ReaderException.Instance; }
public override Result decodeRow(int rowNumber, BitArray row, Dictionary<DecodeHintType, Object> hints) { // Find out where the Middle section (payload) starts & ends int[] startRange = decodeStart(row); int[] endRange = decodeEnd(row); var result = new StringBuilder(20); decodeMiddle(row, startRange[1], endRange[0], result); String resultString = result.ToString(); int[] allowedLengths = null; if (hints != null) { allowedLengths = (int[]) hints[DecodeHintType.ALLOWED_LENGTHS]; } if (allowedLengths == null) { allowedLengths = DEFAULT_ALLOWED_LENGTHS; } // To avoid false positives with 2D barcodes (and other patterns), make // an assumption that the decoded string must be 6, 10 or 14 digits. int length = resultString.Length; bool lengthOK = false; for (int i = 0; i < allowedLengths.Length; i++) { if (length == allowedLengths[i]) { lengthOK = true; break; } } if (!lengthOK) { throw ReaderException.Instance; } return new Result(resultString, null, new[] {new ResultPoint(startRange[1], rowNumber), new ResultPoint(endRange[0], rowNumber)}, BarcodeFormat.ITF); }
public override Result decodeRow(int rowNumber, BitArray row, Dictionary<DecodeHintType, Object> hints) { // Compute this location once and reuse it on multiple implementations int[] startGuardPattern = UPCEANReader.findStartGuardPattern(row); int size = readers.Count; for (int i = 0; i < size; i++) { var reader = (UPCEANReader) readers[i]; Result result; try { result = reader.decodeRow(rowNumber, row, startGuardPattern, hints); } catch (ReaderException) { 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. if (result.BarcodeFormat.Equals(BarcodeFormat.EAN_13) && result.Text[0] == '0') { return new Result(result.Text.Substring(1), null, result.ResultPoints, BarcodeFormat.UPC_A); } return result; } throw ReaderException.Instance; }
/// <summary> Converts one row of luminance data to 1 bit data. May actually do the conversion, or return /// cached data. Callers should assume this method is expensive and call it as seldom as possible. /// This method is intended for decoding 1D barcodes and may choose to apply sharpening. /// /// </summary> /// <param name="y">The row to fetch, 0 <= y < bitmap height. /// </param> /// <param name="row">An optional preallocated array. If null or too small, it will be ignored. /// If used, the Binarizer will call BitArray.clear(). Always use the returned object. /// </param> /// <returns> The array of bits for this row (true means black). /// </returns> public BitArray getBlackRow(int y, BitArray row) { return binarizer.getBlackRow(y, row); }
/// <summary> A fast method to retrieve one row of data from the matrix as a BitArray. /// /// </summary> /// <param name="y">The row to retrieve /// </param> /// <param name="row">An optional caller-allocated BitArray, will be allocated if null or too small /// </param> /// <returns> The resulting BitArray - this reference should always be used even when passing /// your own row /// </returns> public BitArray getRow(int y, BitArray row) { if (row == null || row.Size < width) { row = new BitArray(width); } int offset = y*rowSize; for (int x = 0; x < rowSize; x++) { row.setBulk(x << 5, bits[offset + x]); } return row; }
protected internal override int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString) { int[] counters = decodeMiddleCounters; counters[0] = 0; counters[1] = 0; counters[2] = 0; counters[3] = 0; int end = row.Size; int rowOffset = startRange[1]; int lgPatternFound = 0; for (int x = 0; x < 6 && rowOffset < end; x++) { int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS); resultString.Append((char) ('0' + bestMatch%10)); for (int i = 0; i < counters.Length; i++) { rowOffset += counters[i]; } if (bestMatch >= 10) { lgPatternFound |= 1 << (5 - x); } } determineFirstDigit(resultString, lgPatternFound); int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN); rowOffset = middleRange[1]; for (int x = 0; x < 6 && rowOffset < end; x++) { int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS); resultString.Append((char) ('0' + bestMatch)); for (int i = 0; i < counters.Length; i++) { rowOffset += counters[i]; } } return rowOffset; }
/// <summary> Converts one row of luminance data to 1 bit data. May actually do the conversion, or return /// cached data. Callers should assume this method is expensive and call it as seldom as possible. /// This method is intended for decoding 1D barcodes and may choose to apply sharpening. /// For callers which only examine one row of pixels at a time, the same BitArray should be reused /// and passed in with each call for performance. However it is legal to keep more than one row /// at a time if needed. /// /// </summary> /// <param name="y">The row to fetch, 0 <= y < bitmap height. /// </param> /// <param name="row">An optional preallocated array. If null or too small, it will be ignored. /// If used, the Binarizer will call BitArray.clear(). Always use the returned object. /// </param> /// <returns> The array of bits for this row (true means black). /// </returns> public abstract BitArray getBlackRow(int y, BitArray row);
public override Result decodeRow(int rowNumber, BitArray row, Dictionary<DecodeHintType, Object> hints) { int[] start = findAsteriskPattern(row); int nextStart = start[1]; int end = row.Size; // Read off white space while (nextStart < end && !row.get_Renamed(nextStart)) { nextStart++; } var result = new StringBuilder(20); var counters = new int[9]; char decodedChar; int lastStart; do { recordPattern(row, nextStart, counters); int pattern = toNarrowWidePattern(counters); if (pattern < 0) { throw ReaderException.Instance; } decodedChar = patternToChar(pattern); result.Append(decodedChar); lastStart = nextStart; for (int i = 0; i < counters.Length; i++) { nextStart += counters[i]; } // Read off white space while (nextStart < end && !row.get_Renamed(nextStart)) { nextStart++; } } while (decodedChar != '*'); result.Remove(result.Length - 1, 1); // remove asterisk // Look for whitespace after pattern: int lastPatternSize = 0; for (int i = 0; i < counters.Length; i++) { lastPatternSize += counters[i]; } int whiteSpaceAfterEnd = nextStart - lastStart - lastPatternSize; // If 50% of last pattern size, following last pattern, is not whitespace, fail // (but if it's whitespace to the very end of the image, that's OK) if (nextStart != end && whiteSpaceAfterEnd/2 < lastPatternSize) { throw ReaderException.Instance; } if (usingCheckDigit) { int max = result.Length - 1; int total = 0; for (int i = 0; i < max; i++) { total += ALPHABET_STRING.IndexOf((Char) result[i]); } if (total%43 != ALPHABET_STRING.IndexOf((Char) result[max])) { throw ReaderException.Instance; } result.Remove(max, 1); } String resultString = result.ToString(); if (extendedMode) { resultString = decodeExtended(resultString); } if (resultString.Length == 0) { // Almost surely a false positive throw ReaderException.Instance; } float left = (start[1] + start[0])/2.0f; float right = (nextStart + lastStart)/2.0f; return new Result(resultString, null, new[] {new ResultPoint(left, rowNumber), new ResultPoint(right, rowNumber)}, BarcodeFormat.CODE_39); }
/// <param name="row"> row of black/white values to search /// </param> /// <param name="payloadStart">offset of start pattern /// </param> /// <param name="resultString">{@link StringBuffer} to append decoded chars to /// </param> /// <throws> ReaderException if decoding could not complete successfully </throws> private static void decodeMiddle(BitArray row, int payloadStart, int payloadEnd, StringBuilder 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 var counterDigitPair = new int[10]; var counterBlack = new int[5]; var counterWhite = new int[5]; while (payloadStart < payloadEnd) { // Get 10 runs of black/white. recordPattern(row, payloadStart, counterDigitPair); // Split them into each array for (int k = 0; k < 5; k++) { int twoK = k << 1; counterBlack[k] = counterDigitPair[twoK]; counterWhite[k] = counterDigitPair[twoK + 1]; } int bestMatch = decodeDigit(counterBlack); resultString.Append((char) ('0' + bestMatch)); bestMatch = decodeDigit(counterWhite); resultString.Append((char) ('0' + bestMatch)); for (int i = 0; i < counterDigitPair.Length; i++) { payloadStart += counterDigitPair[i]; } } }
/// <param name="row"> row of black/white values to search /// </param> /// <param name="rowOffset">position to start search /// </param> /// <param name="pattern"> pattern of counts of number of black and white pixels that are /// being searched for as a pattern /// </param> /// <returns> start/end horizontal offset of guard pattern, as an array of two /// ints /// </returns> /// <throws> ReaderException if pattern is not found </throws> private static int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern) { // TODO: This is very similar to implementation in UPCEANReader. Consider if they can be // merged to a single method. int patternLength = pattern.Length; var counters = new int[patternLength]; int width = row.Size; bool isWhite = false; int counterPosition = 0; int patternStart = rowOffset; for (int x = rowOffset; x < width; x++) { bool pixel = row.get_Renamed(x); if (pixel ^ isWhite) { counters[counterPosition]++; } else { if (counterPosition == patternLength - 1) { if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { return new[] {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 ReaderException.Instance; }
/// <summary> Identify where the end of the middle / payload section ends. /// /// </summary> /// <param name="row">row of black/white values to search /// </param> /// <returns> Array, containing index of start of 'end block' and end of 'end /// block' /// </returns> /// <throws> ReaderException </throws> internal int[] decodeEnd(BitArray row) { // For convenience, reverse the row and then // search from 'the start' for the end block row.reverse(); try { int endStart = skipWhiteSpace(row); int[] endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED); // 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 validateQuietZone(row, endPattern[0]); // Now recalculate the indices of where the 'endblock' starts & stops to // accommodate // the reversed nature of the search int temp = endPattern[0]; endPattern[0] = row.Size - endPattern[1]; endPattern[1] = row.Size - temp; return endPattern; } finally { // Put the row back the right way. row.reverse(); } }
/// <summary> Skip all whitespace until we get to the first black line. /// /// </summary> /// <param name="row">row of black/white values to search /// </param> /// <returns> index of the first black line. /// </returns> /// <throws> ReaderException Throws exception if no black lines are found in the row </throws> private static int skipWhiteSpace(BitArray row) { int width = row.Size; int endStart = 0; while (endStart < width) { if (row.get_Renamed(endStart)) { break; } endStart++; } if (endStart == width) { throw ReaderException.Instance; } return endStart; }
/// <summary> 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 /// /// </summary> /// <param name="row">bit array representing the scanned barcode. /// </param> /// <param name="startPattern">index into row of the start or end pattern. /// </param> /// <throws> ReaderException if the quiet zone cannot be found, a ReaderException is thrown. </throws> private void validateQuietZone(BitArray row, int startPattern) { int quietCount = narrowLineWidth*10; // expect to find this many pixels of quiet zone for (int i = startPattern - 1; quietCount > 0 && i >= 0; i--) { if (row.get_Renamed(i)) { break; } quietCount--; } if (quietCount != 0) { // Unable to find the necessary number of quiet zone pixels. throw ReaderException.Instance; } }
/// <summary> Identify where the start of the middle / payload section starts. /// /// </summary> /// <param name="row">row of black/white values to search /// </param> /// <returns> Array, containing index of start of 'start block' and end of /// 'start block' /// </returns> /// <throws> ReaderException </throws> internal int[] decodeStart(BitArray row) { int endStart = skipWhiteSpace(row); int[] startPattern = findGuardPattern(row, endStart, 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. narrowLineWidth = (startPattern[1] - startPattern[0]) >> 2; validateQuietZone(row, startPattern[0]); return startPattern; }
public override Result decodeRow(int rowNumber, BitArray row, Dictionary<DecodeHintType, Object> hints) { return maybeReturnResult(ean13Reader.decodeRow(rowNumber, row, hints)); }
protected internal override int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString) { return ean13Reader.decodeMiddle(row, startRange, resultString); }
private static int[] findAsteriskPattern(BitArray row) { int width = row.Size; int rowOffset = 0; while (rowOffset < width) { if (row.get_Renamed(rowOffset)) { break; } rowOffset++; } int counterPosition = 0; var counters = new int[9]; int patternStart = rowOffset; bool isWhite = false; int patternLength = counters.Length; for (int i = rowOffset; i < width; i++) { bool pixel = row.get_Renamed(i); if (pixel ^ isWhite) { counters[counterPosition]++; } else { if (counterPosition == patternLength - 1) { if (toNarrowWidePattern(counters) == ASTERISK_ENCODING) { // Look for whitespace before start pattern, >= 50% of width of start pattern if (row.isRange(Math.Max(0, patternStart - (i - patternStart)/2), patternStart, false)) { return new[] {patternStart, i}; } } 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 ReaderException.Instance; }