Result decodeRow(int rowNumber, BitArray row, int[] extensionStartRange) throws NotFoundException { StringBuilder result = decodeRowStringBuffer; result.setLength(0); int end = decodeMiddle(row, extensionStartRange, result); String resultString = result.toString(); Map<ResultMetadataType,Object> extensionData = parseExtensionString(resultString); Result extensionResult = new Result(resultString, null, new ResultPoint[] { new ResultPoint((extensionStartRange[0] + extensionStartRange[1]) / 2.0f, rowNumber), new ResultPoint(end, rowNumber), }, BarcodeFormat.UPC_EAN_EXTENSION); if (extensionData != null) { extensionResult.putAllMetadata(extensionData); } return extensionResult; }
/** * Append "bytes" in "mode" mode (encoding) into "bits". On success, store the result in "bits". */ static void appendBytes(String content, Mode mode, BitArray bits, String encoding) throws WriterException { switch (mode) { case NUMERIC: appendNumericBytes(content, bits); break; case ALPHANUMERIC: appendAlphanumericBytes(content, bits); break; case BYTE: append8BitBytes(content, bits, encoding); break; case KANJI: appendKanjiBytes(content, bits); break; default: throw new WriterException("Invalid mode: " + mode); } }
static void appendAlphanumericBytes(CharSequence content, BitArray bits) throws WriterException { int length = content.length(); int i = 0; while (i < length) { int code1 = getAlphanumericCode(content.charAt(i)); if (code1 == -1) { throw new WriterException(); } if (i + 1 < length) { int code2 = getAlphanumericCode(content.charAt(i + 1)); if (code2 == -1) { throw new WriterException(); } // Encode two alphanumeric letters in 11 bits. bits.appendBits(code1 * 45 + code2, 11); i += 2; } else { // Encode one alphanumeric letter in six bits. bits.appendBits(code1, 6); i++; } } }
private Pair decodePair(BitArray row, boolean right, int rowNumber, Map<DecodeHintType,?> hints) { try { int[] startEnd = findFinderPattern(row, right); FinderPattern pattern = parseFoundFinderPattern(row, rowNumber, right, startEnd); ResultPointCallback resultPointCallback = hints == null ? null : (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); if (resultPointCallback != null) { float center = (startEnd[0] + startEnd[1]) / 2.0f; if (right) { // row is actually reversed center = row.getSize() - 1 - center; } resultPointCallback.foundPossibleResultPoint(new ResultPoint(center, rowNumber)); } DataCharacter outside = decodeDataCharacter(row, pattern, true); DataCharacter inside = decodeDataCharacter(row, pattern, false); return new Pair(1597 * outside.getValue() + inside.getValue(), outside.getChecksumPortion() + 4 * inside.getChecksumPortion(), pattern); } catch (NotFoundException ignored) { return null; } }
static void appendKanjiBytes(String content, BitArray bits) throws WriterException { byte[] bytes; try { bytes = content.getBytes("Shift_JIS"); } catch (UnsupportedEncodingException uee) { throw new WriterException(uee); } int length = bytes.length; for (int i = 0; i < length; i += 2) { int byte1 = bytes[i] & 0xFF; int byte2 = bytes[i + 1] & 0xFF; int code = (byte1 << 8) | byte2; int subtracted = -1; if (code >= 0x8140 && code <= 0x9ffc) { subtracted = code - 0x8140; } else if (code >= 0xe040 && code <= 0xebbf) { subtracted = code - 0xc140; } if (subtracted == -1) { throw new WriterException("Invalid byte sequence"); } int encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff); bits.appendBits(encoded, 13); } }
/** * 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 */ private void validateQuietZone(BitArray row, int startPattern) throws NotFoundException { int 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 (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.getNotFoundInstance(); } }
static void maybeEmbedVersionInfo(Version version, ByteMatrix matrix) throws WriterException { if (version.getVersionNumber() < 7) { // Version info is necessary if version >= 7. return; // Don't need version info. } BitArray versionInfoBits = new BitArray(); makeVersionInfoBits(version, versionInfoBits); int bitIndex = 6 * 3 - 1; // It will decrease from 17 to 0. for (int i = 0; i < 6; ++i) { for (int j = 0; j < 3; ++j) { // Place bits in LSB (least significant bit) to MSB order. boolean bit = versionInfoBits.get(bitIndex); bitIndex--; // Left bottom corner. matrix.set(i, matrix.getHeight() - 11 + j, bit); // Right bottom corner. matrix.set(matrix.getHeight() - 11 + j, i, bit); } } }
protected int decodeMiddle(BitArray row, int[] startRange, StringBuilder result) throws NotFoundException { int[] counters = this.decodeMiddleCounters; counters[0] = 0; counters[1] = 0; counters[2] = 0; counters[3] = 0; int end = row.getSize(); int rowOffset = startRange[1]; int lgPatternFound = 0; for (int x = 0; x < 6 && rowOffset < end; x++) { int bestMatch = UPCEANReader.decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS); result.append((char) ((bestMatch % 10) + 48)); for (int counter : counters) { rowOffset += counter; } if (bestMatch >= 10) { lgPatternFound |= 1 << (5 - x); } } determineNumSysAndCheckDigit(result, lgPatternFound); return rowOffset; }
static void makeTypeInfoBits(ErrorCorrectionLevel ecLevel, int maskPattern, BitArray bits) throws WriterException { if (!QRCode.isValidMaskPattern(maskPattern)) { throw new WriterException("Invalid mask pattern"); } int typeInfo = (ecLevel.getBits() << 3) | maskPattern; bits.appendBits(typeInfo, 5); int bchCode = calculateBCHCode(typeInfo, TYPE_INFO_POLY); bits.appendBits(bchCode, 10); BitArray maskBits = new BitArray(); maskBits.appendBits(TYPE_INFO_MASK_PATTERN, 15); bits.xor(maskBits); if (bits.getSize() != 15) { // Just in case. throw new WriterException("should not happen but we got: " + bits.getSize()); } }
/** * Attempts to decode a single UPC/EAN-encoded digit. * * @param row row of black/white values to decode * @param counters the counts of runs of observed black/white/black/... values * @param rowOffset horizontal offset to start decoding from * @param patterns the set of patterns to use to decode -- sometimes different encodings * for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should * be used * @return horizontal offset of first pixel beyond the decoded digit * @throws NotFoundException if digit cannot be decoded */ static int decodeDigit(BitArray row, int[] counters, int rowOffset, int[][] patterns) throws NotFoundException { recordPattern(row, rowOffset, counters); float bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept int bestMatch = -1; int max = patterns.length; for (int i = 0; i < max; i++) { int[] pattern = patterns[i]; float variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE); if (variance < bestVariance) { bestVariance = variance; bestMatch = i; } } if (bestMatch >= 0) { return bestMatch; } else { throw NotFoundException.getNotFoundInstance(); } }
/** * 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, a ReaderException is thrown. */ private void validateQuietZone(BitArray row, int startPattern) throws NotFoundException { int 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 (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.getNotFoundInstance(); } }
private static int decodeCode(BitArray row, int[] counters, int rowOffset) throws NotFoundException { recordPattern(row, rowOffset, counters); float bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept int bestMatch = -1; for (int d = 0; d < CODE_PATTERNS.length; d++) { int[] pattern = CODE_PATTERNS[d]; float 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.getNotFoundInstance(); } }
static void appendNumericBytes(CharSequence content, BitArray bits) { int length = content.length(); int i = 0; while (i < length) { int num1 = content.charAt(i) - 48; if (i + 2 < length) { int num3 = content.charAt(i + 2) - 48; bits.appendBits(((num1 * 100) + ((content.charAt(i + 1) - 48) * 10)) + num3, 10); i += 3; } else if (i + 1 < length) { bits.appendBits((num1 * 10) + (content.charAt(i + 1) - 48), 7); i += 2; } else { bits.appendBits(num1, 4); i++; } } }
private Pair decodePair(BitArray row, boolean right, int rowNumber, Map<DecodeHintType,?> hints) { try { int[] startEnd = findFinderPattern(row, 0, right); FinderPattern pattern = parseFoundFinderPattern(row, rowNumber, right, startEnd); ResultPointCallback resultPointCallback = hints == null ? null : (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); if (resultPointCallback != null) { float center = (startEnd[0] + startEnd[1]) / 2.0f; if (right) { // row is actually reversed center = row.getSize() - 1 - center; } resultPointCallback.foundPossibleResultPoint(new ResultPoint(center, rowNumber)); } DataCharacter outside = decodeDataCharacter(row, pattern, true); DataCharacter inside = decodeDataCharacter(row, pattern, false); return new Pair(1597 * outside.getValue() + inside.getValue(), outside.getChecksumPortion() + 4 * inside.getChecksumPortion(), pattern); } catch (NotFoundException ignored) { return null; } }
static void appendKanjiBytes(String content, BitArray bits) throws WriterException { try { byte[] bytes = content.getBytes("Shift_JIS"); int length = bytes.length; for (int i = 0; i < length; i += 2) { int byte2 = bytes[i + 1] & 255; int code = ((bytes[i] & 255) << 8) | byte2; int subtracted = -1; if (code >= 33088 && code <= 40956) { subtracted = code - 33088; } else if (code >= 57408 && code <= 60351) { subtracted = code - 49472; } if (subtracted == -1) { throw new WriterException("Invalid byte sequence"); } bits.appendBits(((subtracted >> 8) * 192) + (subtracted & 255), 13); } } catch (UnsupportedEncodingException uee) { throw new WriterException(uee); } }
static int[] findStartGuardPattern(BitArray row) throws NotFoundException { boolean foundStart = false; int[] startRange = null; int nextStart = 0; int[] counters = new int[START_END_PATTERN.length]; while (!foundStart) { Arrays.fill(counters, 0, START_END_PATTERN.length, 0); startRange = findGuardPattern(row, nextStart, false, START_END_PATTERN, counters); int start = startRange[0]; nextStart = startRange[1]; // Make sure there is a quiet zone at least as big as the start pattern before the barcode. // If this check would run off the left edge of the image, do not accept this barcode, // as it is very likely to be a false positive. int quietStart = start - (nextStart - start); if (quietStart >= 0) { foundStart = row.isRange(quietStart, start, false); } } return startRange; }
/** * 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 */ private void setCounters(BitArray row) throws NotFoundException { counterLength = 0; // Start from the first white bit. int i = row.getNextUnset(0); int end = row.getSize(); if (i >= end) { throw NotFoundException.getNotFoundInstance(); } boolean isWhite = true; int count = 0; while (i < end) { if (row.get(i) != isWhite) { count++; } else { counterAppend(count); count = 1; isWhite = !isWhite; } i++; } counterAppend(count); }
static BitArray stuffBits(BitArray bits, int wordSize) { BitArray out = new BitArray(); int n = bits.getSize(); int mask = (1 << wordSize) - 2; for (int i = 0; i < n; i += wordSize) { int word = 0; for (int j = 0; j < wordSize; j++) { if (i + j >= n || bits.get(i + j)) { word |= 1 << (wordSize - 1 - j); } } if ((word & mask) == mask) { out.appendBits(word & mask, wordSize); i--; } else if ((word & mask) == 0) { out.appendBits(word | 1, wordSize); i--; } else { out.appendBits(word, wordSize); } } return out; }
@Override public void appendTo(BitArray bitArray, byte[] text) { for (int i = 0; i < binaryShiftByteCount; i++) { if (i == 0 || (i == 31 && binaryShiftByteCount <= 62)) { // We need a header before the first character, and before // character 31 when the total byte code is <= 62 bitArray.appendBits(31, 5); // BINARY_SHIFT if (binaryShiftByteCount > 62) { bitArray.appendBits(binaryShiftByteCount - 31, 16); } else if (i == 0) { // 1 <= binaryShiftByteCode <= 62 bitArray.appendBits(Math.min(binaryShiftByteCount, 31), 5); } else { // 32 <= binaryShiftCount <= 62 and i == 31 bitArray.appendBits(binaryShiftByteCount - 31, 5); } } bitArray.appendBits(text[binaryShiftStart + i], 8); } }
protected static void recordPatternInReverse(BitArray row, int start, int[] counters) throws NotFoundException { int numTransitionsLeft = counters.length; boolean last = row.get(start); while (start > 0 && numTransitionsLeft >= 0) { start--; if (row.get(start) != last) { numTransitionsLeft--; last = !last; } } if (numTransitionsLeft >= 0) { throw NotFoundException.getNotFoundInstance(); } recordPattern(row, start + 1, counters); }
static int decodeDigit(BitArray row, int[] counters, int rowOffset, int[][] patterns) throws NotFoundException { OneDReader.recordPattern(row, rowOffset, counters); float bestVariance = MAX_AVG_VARIANCE; int bestMatch = -1; int max = patterns.length; for (int i = 0; i < max; i++) { float variance = OneDReader.patternMatchVariance(counters, patterns[i], MAX_INDIVIDUAL_VARIANCE); if (variance < bestVariance) { bestVariance = variance; bestMatch = i; } } if (bestMatch >= 0) { return bestMatch; } throw NotFoundException.getNotFoundInstance(); }
/** * Terminate bits as described in 8.4.8 and 8.4.9 of JISX0510:2004 (p.24). */ static void terminateBits(int numDataBytes, BitArray bits) throws WriterException { int capacity = numDataBytes * 8; if (bits.getSize() > capacity) { throw new WriterException("data bits cannot fit in the QR Code" + bits.getSize() + " > " + capacity); } for (int i = 0; i < 4 && bits.getSize() < capacity; ++i) { bits.appendBit(false); } // Append termination bits. See 8.4.8 of JISX0510:2004 (p.24) for details. // If the last byte isn't 8-bit aligned, we'll add padding bits. int numBitsInLastByte = bits.getSize() & 0x07; if (numBitsInLastByte > 0) { for (int i = numBitsInLastByte; i < 8; i++) { bits.appendBit(false); } } // If we have more space, we'll fill the space with padding patterns defined in 8.4.9 (p.24). int numPaddingBytes = numDataBytes - bits.getSizeInBytes(); for (int i = 0; i < numPaddingBytes; ++i) { bits.appendBits((i & 0x01) == 0 ? 0xEC : 0x11, 8); } if (bits.getSize() != capacity) { throw new WriterException("Bits size does not equal capacity"); } }
/** * Append length info. On success, store the result in "bits". */ static void appendLengthInfo(int numLetters, Version version, Mode mode, BitArray bits) throws WriterException { int numBits = mode.getCharacterCountBits(version); if (numLetters >= (1 << numBits)) { throw new WriterException(numLetters + " is bigger than " + ((1 << numBits) - 1)); } bits.appendBits(numLetters, numBits); }
static void makeVersionInfoBits(Version version, BitArray bits) throws WriterException { bits.appendBits(version.getVersionNumber(), 6); int bchCode = calculateBCHCode(version.getVersionNumber(), VERSION_INFO_POLY); bits.appendBits(bchCode, 12); if (bits.getSize() != 18) { // Just in case. throw new WriterException("should not happen but we got: " + bits.getSize()); } }
@Override protected int decodeMiddle(BitArray row, int[] startRange, StringBuilder result) throws NotFoundException { int[] counters = decodeMiddleCounters; counters[0] = 0; counters[1] = 0; counters[2] = 0; counters[3] = 0; int end = row.getSize(); 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); result.append((char) ('0' + bestMatch % 10)); for (int counter : counters) { rowOffset += counter; } if (bestMatch >= 10) { lgPatternFound |= 1 << (5 - x); } } determineNumSysAndCheckDigit(result, lgPatternFound); return rowOffset; }
private int[] findAsteriskPattern(BitArray row) throws NotFoundException { int width = row.getSize(); int rowOffset = row.getNextSet(0); Arrays.fill(this.counters, 0); int[] theCounters = this.counters; int patternStart = rowOffset; boolean isWhite = false; int patternLength = theCounters.length; int counterPosition = 0; for (int i = rowOffset; i < width; i++) { if ((row.get(i) ^ isWhite) != 0) { theCounters[counterPosition] = theCounters[counterPosition] + 1; } else { if (counterPosition != patternLength - 1) { counterPosition++; } else if (toPattern(theCounters) == ASTERISK_ENCODING) { return new int[]{patternStart, i}; } else { patternStart += theCounters[0] + theCounters[1]; System.arraycopy(theCounters, 2, theCounters, 0, patternLength - 2); theCounters[patternLength - 2] = 0; theCounters[patternLength - 1] = 0; counterPosition--; } theCounters[counterPosition] = 1; if (isWhite) { isWhite = false; } else { isWhite = true; } } } throw NotFoundException.getNotFoundInstance(); }
/** * 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 */ private static int skipWhiteSpace(BitArray row) throws NotFoundException { int width = row.getSize(); int endStart = row.getNextSet(0); if (endStart == width) { throw NotFoundException.getNotFoundInstance(); } return endStart; }
/** * @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 */ private static int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern) throws NotFoundException { int patternLength = pattern.length; int[] counters = new int[patternLength]; int width = row.getSize(); boolean 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, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { return new int[]{patternStart, x}; } patternStart += counters[0] + counters[1]; System.arraycopy(counters, 2, counters, 0, patternLength - 2); counters[patternLength - 2] = 0; counters[patternLength - 1] = 0; counterPosition--; } else { counterPosition++; } counters[counterPosition] = 1; isWhite = !isWhite; } } throw NotFoundException.getNotFoundInstance(); }
private static int[] findAsteriskPattern(BitArray row, int[] counters) throws NotFoundException { int width = row.getSize(); int rowOffset = row.getNextSet(0); int counterPosition = 0; int patternStart = rowOffset; boolean isWhite = false; int patternLength = counters.length; for (int i = rowOffset; i < width; i++) { if (row.get(i) ^ isWhite) { counters[counterPosition]++; } else { if (counterPosition == patternLength - 1) { // Look for whitespace before start pattern, >= 50% of width of start pattern if (toNarrowWidePattern(counters) == ASTERISK_ENCODING && row.isRange(Math.max(0, patternStart - ((i - patternStart) / 2)), patternStart, false)) { return new int[]{patternStart, i}; } patternStart += counters[0] + counters[1]; System.arraycopy(counters, 2, counters, 0, patternLength - 2); counters[patternLength - 2] = 0; counters[patternLength - 1] = 0; counterPosition--; } else { counterPosition++; } counters[counterPosition] = 1; isWhite = !isWhite; } } throw NotFoundException.getNotFoundInstance(); }
protected int decodeMiddle(BitArray row, int[] startRange, StringBuilder result) throws NotFoundException { int x; int[] counters = this.decodeMiddleCounters; counters[0] = 0; counters[1] = 0; counters[2] = 0; counters[3] = 0; int end = row.getSize(); int rowOffset = startRange[1]; for (x = 0; x < 4 && rowOffset < end; x++) { result.append((char) (UPCEANReader.decodeDigit(row, counters, rowOffset, L_PATTERNS) + 48)); for (int counter : counters) { rowOffset += counter; } } rowOffset = UPCEANReader.findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN)[1]; for (x = 0; x < 4 && rowOffset < end; x++) { result.append((char) (UPCEANReader.decodeDigit(row, counters, rowOffset, L_PATTERNS) + 48)); for (int counter2 : counters) { rowOffset += counter2; } } return rowOffset; }
private static int getNextSecondBar(BitArray row, int initialPos){ int currentPos; if (row.get(initialPos)) { currentPos = row.getNextUnset(initialPos); currentPos = row.getNextSet(currentPos); } else { currentPos = row.getNextSet(initialPos); currentPos = row.getNextUnset(currentPos); } return currentPos; }
/** * Decides the smallest version of QR code that will contain all of the provided data. * * @throws WriterException if the data cannot fit in any version */ private static Version recommendVersion(ErrorCorrectionLevel ecLevel, Mode mode, BitArray headerBits, BitArray dataBits) throws WriterException { // Hard part: need to know version to know how many bits length takes. But need to know how many // bits it takes to know version. First we take a guess at version by assuming version will be // the minimum, 1: int provisionalBitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, Version.getVersionForNumber(1)); Version provisionalVersion = chooseVersion(provisionalBitsNeeded, ecLevel); // Use that guess to calculate the right version. I am still not sure this works in 100% of cases. int bitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, provisionalVersion); return chooseVersion(bitsNeeded, ecLevel); }
/** * @param row row of black/white values to search * @param rowOffset position to start search * @param whiteFirst if true, indicates that the pattern specifies white/black/white/... * pixel counts, otherwise, it is interpreted as black/white/black/... * @param pattern pattern of counts of number of black and white pixels that are being * searched for as a pattern * @param counters array of counters, as long as pattern, to re-use * @return start/end horizontal offset of guard pattern, as an array of two ints * @throws NotFoundException if pattern is not found */ private static int[] findGuardPattern(BitArray row, int rowOffset, boolean whiteFirst, int[] pattern, int[] counters) throws NotFoundException { int width = row.getSize(); rowOffset = whiteFirst ? row.getNextUnset(rowOffset) : row.getNextSet(rowOffset); int counterPosition = 0; int patternStart = rowOffset; int patternLength = pattern.length; boolean isWhite = whiteFirst; for (int x = rowOffset; x < width; x++) { if (row.get(x) ^ isWhite) { counters[counterPosition]++; } else { if (counterPosition == patternLength - 1) { if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { return new int[]{patternStart, x}; } patternStart += counters[0] + counters[1]; System.arraycopy(counters, 2, counters, 0, patternLength - 2); counters[patternLength - 2] = 0; counters[patternLength - 1] = 0; counterPosition--; } else { counterPosition++; } counters[counterPosition] = 1; isWhite = !isWhite; } } throw NotFoundException.getNotFoundInstance(); }