示例#1
0
        public BitArray getBlackColumn(int x, BitArray column, int startY, int getHeight)
        {
            if (column == null || column.getSize() < getHeight)
            {
                column = new BitArray(getHeight);
            }
            else
            {
                column.clear();
            }

            // Reuse the same int array each time
            initLuminances();
            luminances = getLuminanceColumn(x, luminances);

            // We don't handle "row sampling" specially here
            for (int y = 0; y < getHeight; y++)
            {
                if (luminances[startY + y] < blackPoint)
                {
                    column.set(y);
                }
            }
            return(column);
        }
      public BitArray getBlackRow(int y, BitArray row, int startX, int getWidth) {
        if (row == null || row.getSize() < getWidth) {
          row = new BitArray(getWidth);
        } else {
          row.clear();
        }

        // Reuse the same int array each time
        initLuminances();
        luminances = getLuminanceRow(y, luminances);

        // If the current decoder calculated the blackPoint based on one row, assume we're trying to
        // decode a 1D barcode, and apply some sharpening.
        if (lastMethod.Equals(BlackPointEstimationMethod.ROW_SAMPLING)) {
          int left = luminances[startX];
          int center = luminances[startX + 1];
          for (int x = 1; x < getWidth - 1; x++) {
            int right = luminances[startX + x + 1];
            // Simple -1 4 -1 box filter with a weight of 2
            int luminance = ((center << 2) - left - right) >> 1;
            if (luminance < blackPoint) {
              row.set(x);
            }
            left = center;
            center = right;
          }
        } else {
          for (int x = 0; x < getWidth; x++) {
            if (luminances[startX + x] < blackPoint) {
              row.set(x);
            }
          }
        }
        return row;
      }
示例#3
0
        protected 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.getSize();
            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;
        }
示例#4
0
        public BitArray getBlackRow(int y, BitArray row, int startX, int getWidth)
        {
            if (row == null || row.getSize() < getWidth)
            {
                row = new BitArray(getWidth);
            }
            else
            {
                row.clear();
            }

            // Reuse the same int array each time
            initLuminances();
            luminances = getLuminanceRow(y, luminances);

            // If the current decoder calculated the blackPoint based on one row, assume we're trying to
            // decode a 1D barcode, and apply some sharpening.
            if (lastMethod.Equals(BlackPointEstimationMethod.ROW_SAMPLING))
            {
                int left   = luminances[startX];
                int center = luminances[startX + 1];
                for (int x = 1; x < getWidth - 1; x++)
                {
                    int right = luminances[startX + x + 1];
                    // Simple -1 4 -1 box filter with a weight of 2
                    int luminance = ((center << 2) - left - right) >> 1;
                    if (luminance < blackPoint)
                    {
                        row.set(x);
                    }
                    left   = center;
                    center = right;
                }
            }
            else
            {
                for (int x = 0; x < getWidth; x++)
                {
                    if (luminances[startX + x] < blackPoint)
                    {
                        row.set(x);
                    }
                }
            }
            return(row);
        }
          protected 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.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);
              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;
          }
示例#6
0
        /**
           * 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
           */
        private int skipWhiteSpace(BitArray row)
        {
            int width = row.getSize();
            int endStart = 0;
            while (endStart < width) {
              if (row.get(endStart)) {
                break;
              }
              endStart++;
            }
            if (endStart == width) {
              throw new ReaderException();
            }

            return endStart;
        }
示例#7
0
        /**
           * @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
           */
        int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern)
        {
            // TODO: This is very similar to implementation in AbstractUPCEANReader. Consider if they can be merged to
            // a single method.

            int patternLength = pattern.Length;
            int[] counters = new int[patternLength];
            int width = row.getSize();
            bool isWhite = false;

            int counterPosition = 0;
            int patternStart = rowOffset;
            for (int x = rowOffset; x < width; x++) {
              bool pixel = row.get(x);
              if ((!pixel && isWhite) || (pixel && !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];
                  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 new ReaderException();
        }
示例#8
0
        /**
           * 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'
           * @throws ReaderException
           */
        int[] decodeEnd(BitArray row)
        {
            // For convenience, reverse the row and then
            // search from 'the start' for the end block
            row.reverse();

            int endStart = skipWhiteSpace(row);
            int[] endPattern;
            try {
              endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED);
            } catch (ReaderException e) {
              // Put our row of data back the right way before throwing
              row.reverse();
              throw e;
            }

            // 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 recalc the indicies of where the 'endblock' starts & stops to
            // accomodate
            // the reversed nature of the search
            int temp = endPattern[0];
            endPattern[0] = row.getSize() - endPattern[1];
            endPattern[1] = row.getSize() - temp;

            // Put the row back the righ way.
            row.reverse();
            return endPattern;
        }
        public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange)
        {
            StringBuilder result = decodeRowStringBuffer;
            result.Length = 0;
            int endStart = decodeMiddle(row, startGuardRange, result);
            int[] 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 new ReaderException();
            }

            String resultString = result.ToString();
            if (!checkChecksum(resultString)) {
              throw new ReaderException();
            }

            float left = (float) (startGuardRange[1] + startGuardRange[0]) / 2.0f;
            float right = (float) (endRange[1] + endRange[0]) / 2.0f;
            return new Result(resultString,
                null, // no natural byte representation for these barcodes
                new ResultPoint[]{
                    new GenericResultPoint(left, (float) rowNumber),
                    new GenericResultPoint(right, (float) rowNumber)},
                getBarcodeFormat());
        }
        /**
           * @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
           * @return start/end horizontal offset of guard pattern, as an array of two ints
           * @throws ReaderException if pattern is not found
           */
        public static int[] findGuardPattern(BitArray row, int rowOffset, bool whiteFirst, int[] pattern)
        {
            int patternLength = pattern.Length;
            int[] counters = new int[patternLength];
            int width = row.getSize();
            bool isWhite = false;
            while (rowOffset < width) {
              isWhite = !row.get(rowOffset);
              if (whiteFirst == isWhite) {
                break;
              }
              rowOffset++;
            }

            int counterPosition = 0;
            int patternStart = rowOffset;
            for (int x = rowOffset; x < width; x++) {
              bool pixel = row.get(x);
              if ((!pixel && isWhite) || (pixel && !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];
                  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 new ReaderException();
        }
        /**
           * Records the size of successive runs of white and black pixels in a row, starting at a given point.
           * The values are recorded in the given array, and the number of runs recorded is equal to the size
           * of the array. If the row starts on a white pixel at the given start point, then the first count
           * recorded is the run of white pixels starting from that point; likewise it is the count of a run
           * of black pixels if the row begin on a black pixels at that point.
           *
           * @param row row to count from
           * @param start offset into row to start at
           * @param counters array into which to record counts
           * @throws ReaderException if counters cannot be filled entirely from row before running out of pixels
           */
        public static void recordPattern(BitArray row, int start, int[] counters)
        {
            int numCounters = counters.Length;
            for (int i = 0; i < numCounters; i++) {
              counters[i] = 0;
            }
            int end = row.getSize();
            if (start >= end) {
              throw new ReaderException();
            }
            bool isWhite = !row.get(start);
            int counterPosition = 0;

            int k = start;
            while (k < end) {
              bool pixel = row.get(k);
              if ((!pixel && isWhite) || (pixel && !isWhite)) {
                counters[counterPosition]++;
              } else {
                counterPosition++;
                if (counterPosition == numCounters) {
                  break;
                } else {
                  counters[counterPosition] = 1;
                  isWhite = !isWhite;
                }
              }
              k++;
            }
            // 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 && k == end))) {
              throw new ReaderException();
            }
        }
      public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints) {

        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 new ReaderException();
        }

        bool done = false;
        bool isNextShifted = false;

        StringBuilder result = new StringBuilder();
        int lastStart = startPatternInfo[0];
        int nextStart = startPatternInfo[1];
        int[] counters = new int[6];

        int lastCode = 0;
        int code = 0;
        int checksumTotal = startCode;
        int multiplier = 0;
        bool lastCharacterWasPrintable = true;

        while (!done) {

          bool unshift = isNextShifted;
          isNextShifted = false;

          // Save off last code
          lastCode = code;

          // Decode another code from image
          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; i < counters.Length; 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 new ReaderException();
          }

          switch (codeSet) {

            case CODE_CODE_A:
              if (code < 64) {
                result.Append((char) (' ' + code));
              } else if (code < 96) {
                result.Append((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:
                  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((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_C;
                    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('0');
                }
                result.Append(code);
              } 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) {
            switch (codeSet) {
              case CODE_CODE_A:
                codeSet = CODE_CODE_C;
                break;
              case CODE_CODE_B:
                codeSet = CODE_CODE_A;
                break;
              case CODE_CODE_C:
                codeSet = CODE_CODE_B;
                break;
            }
          }

        }

        // Check for ample whitespice 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:
        while (row.get(nextStart)) {
          nextStart++;
        }
        if (!row.isRange(nextStart, Math.Min(row.getSize(), nextStart + (nextStart - lastStart) / 2), false)) {
          throw new ReaderException();
        }

        // Pull out from sum the value of the penultimate check code
        checksumTotal -= multiplier * lastCode;
        // lastCode is the checksum then:
        if (checksumTotal % 103 != lastCode) {
          throw new ReaderException();
        }

        // Need to pull out the check digits from string
        int resultLength = result.Length;
        // Only bother if, well, 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.Remove(resultLength - 2, 2);
          } else {
              result.Remove(resultLength - 1, 1);
          }
        }

        String resultString = result.ToString();

        if (resultString.Length == 0) {
          // Almost surely a false positive
          throw new ReaderException();
        }

        float left = (float) (startPatternInfo[1] + startPatternInfo[0]) / 2.0f;
        float right = (float) (nextStart + lastStart) / 2.0f;
        return new Result(
            resultString,
            null,
            new ResultPoint[]{
                new GenericResultPoint(left, (float) rowNumber),
                new GenericResultPoint(right, (float) rowNumber)},
            BarcodeFormat.CODE_128);

      }
      private static int[] findStartPattern(BitArray row) {
        int width = row.getSize();
        int rowOffset = 0;
        while (rowOffset < width) {
          if (row.get(rowOffset)) {
            break;
          }
          rowOffset++;
        }

        int counterPosition = 0;
        int[] counters = new int[6];
        int patternStart = rowOffset;
        bool isWhite = false;
        int patternLength = counters.Length;

        for (int i = rowOffset; i < width; i++) {
          bool pixel = row.get(i);
          if ((!pixel && isWhite) || (pixel && !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;
                }
              }
              if (bestMatch >= 0) {
                // 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 int[]{patternStart, i, bestMatch};
                }
              }
              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 new ReaderException();
      }
      public BitArray getBlackColumn(int x, BitArray column, int startY, int getHeight) {
        if (column == null || column.getSize() < getHeight) {
          column = new BitArray(getHeight);
        } else {
          column.clear();
        }

        // Reuse the same int array each time
        initLuminances();
        luminances = getLuminanceColumn(x, luminances);

        // We don't handle "row sampling" specially here
        for (int y = 0; y < getHeight; y++) {
          if (luminances[startY + y] < blackPoint) {
            column.set(y);
          }
        }
        return column;
      }
示例#15
0
          public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints) {

            int[] start = findAsteriskPattern(row);
            int nextStart = start[1];
            int end = row.getSize();

            // Read off white space
            while (nextStart < end && !row.get(nextStart)) {
              nextStart++;
            }

            StringBuilder result = new StringBuilder();
            int[] counters = new int[9];
            char decodedChar;
            int lastStart;
            do {
              recordPattern(row, nextStart, counters);
              int pattern = toNarrowWidePattern(counters);
              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(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 new ReaderException();
            }

            if (usingCheckDigit) {
              int max = result.Length - 1;
              int total = 0;
              for (int i = 0; i < max; i++) {
                total += ALPHABET_STRING.IndexOf(result[i]);
              }
              if (total % 43 != ALPHABET_STRING.IndexOf(result[max]))
              {
                throw new ReaderException();
              }
              result.Remove(max,1);
            }

            String resultString = result.ToString();
            if (extendedMode) {
              resultString = decodeExtended(resultString);
            }

            if (resultString.Length == 0) {
              // Almost surely a false positive
              throw new ReaderException();
            }

            float left = (float) (start[1] + start[0]) / 2.0f;
            float right = (float) (nextStart + lastStart) / 2.0f;
            return new Result(
                resultString,
                null,
                new ResultPoint[]{
                    new GenericResultPoint(left, (float) rowNumber),
                    new GenericResultPoint(right, (float) rowNumber)},
                BarcodeFormat.CODE_39);

          }
示例#16
0
          private static int[] findAsteriskPattern(BitArray row) {
            int width = row.getSize();
            int rowOffset = 0;
            while (rowOffset < width) {
              if (row.get(rowOffset)) {
                break;
              }
              rowOffset++;
            }

            int counterPosition = 0;
            int[] 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(i);
              if ((!pixel && isWhite) || (pixel && !isWhite)) {
                counters[counterPosition]++;
              } else {
                if (counterPosition == patternLength - 1) {
                  try {
                    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 int[]{patternStart, i};
                      }
                    }
                  } catch (ReaderException re) {
                    // no match, continue
                  }
                  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 new ReaderException();
          }