| /* |
| * Copyright 2009 ZXing authors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.zxing.oned.rss; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| |
| import com.google.zxing.BarcodeFormat; |
| import com.google.zxing.DecodeHintType; |
| import com.google.zxing.NotFoundException; |
| import com.google.zxing.Result; |
| import com.google.zxing.ResultPoint; |
| import com.google.zxing.ResultPointCallback; |
| import com.google.zxing.common.BitArray; |
| |
| /** |
| * Decodes RSS-14, including truncated and stacked variants. See ISO/IEC 24724:2006. |
| */ |
| public final class RSS14Reader extends AbstractRSSReader { |
| |
| private static final int[] OUTSIDE_EVEN_TOTAL_SUBSET = {1,10,34,70,126}; |
| private static final int[] INSIDE_ODD_TOTAL_SUBSET = {4,20,48,81}; |
| private static final int[] OUTSIDE_GSUM = {0,161,961,2015,2715}; |
| private static final int[] INSIDE_GSUM = {0,336,1036,1516}; |
| private static final int[] OUTSIDE_ODD_WIDEST = {8,6,4,3,1}; |
| private static final int[] INSIDE_ODD_WIDEST = {2,4,6,8}; |
| |
| private static final int[][] FINDER_PATTERNS = { |
| {3,8,2,1}, |
| {3,5,5,1}, |
| {3,3,7,1}, |
| {3,1,9,1}, |
| {2,7,4,1}, |
| {2,5,6,1}, |
| {2,3,8,1}, |
| {1,5,7,1}, |
| {1,3,9,1}, |
| }; |
| |
| private final List<Pair> possibleLeftPairs; |
| private final List<Pair> possibleRightPairs; |
| |
| public RSS14Reader() { |
| possibleLeftPairs = new ArrayList<>(); |
| possibleRightPairs = new ArrayList<>(); |
| } |
| |
| @Override |
| public Result decodeRow(int rowNumber, |
| BitArray row, |
| Map<DecodeHintType,?> hints) throws NotFoundException { |
| Pair leftPair = decodePair(row, false, rowNumber, hints); |
| addOrTally(possibleLeftPairs, leftPair); |
| row.reverse(); |
| Pair rightPair = decodePair(row, true, rowNumber, hints); |
| addOrTally(possibleRightPairs, rightPair); |
| row.reverse(); |
| int lefSize = possibleLeftPairs.size(); |
| for (int i = 0; i < lefSize; i++) { |
| Pair left = possibleLeftPairs.get(i); |
| if (left.getCount() > 1) { |
| int rightSize = possibleRightPairs.size(); |
| for (int j = 0; j < rightSize; j++) { |
| Pair right = possibleRightPairs.get(j); |
| if (right.getCount() > 1) { |
| if (checkChecksum(left, right)) { |
| return constructResult(left, right); |
| } |
| } |
| } |
| } |
| } |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| |
| private static void addOrTally(Collection<Pair> possiblePairs, Pair pair) { |
| if (pair == null) { |
| return; |
| } |
| boolean found = false; |
| for (Pair other : possiblePairs) { |
| if (other.getValue() == pair.getValue()) { |
| other.incrementCount(); |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| possiblePairs.add(pair); |
| } |
| } |
| |
| @Override |
| public void reset() { |
| possibleLeftPairs.clear(); |
| possibleRightPairs.clear(); |
| } |
| |
| private static Result constructResult(Pair leftPair, Pair rightPair) { |
| long symbolValue = 4537077L * leftPair.getValue() + rightPair.getValue(); |
| String text = String.valueOf(symbolValue); |
| |
| StringBuilder buffer = new StringBuilder(14); |
| for (int i = 13 - text.length(); i > 0; i--) { |
| buffer.append('0'); |
| } |
| buffer.append(text); |
| |
| int checkDigit = 0; |
| for (int i = 0; i < 13; i++) { |
| int digit = buffer.charAt(i) - '0'; |
| checkDigit += (i & 0x01) == 0 ? 3 * digit : digit; |
| } |
| checkDigit = 10 - (checkDigit % 10); |
| if (checkDigit == 10) { |
| checkDigit = 0; |
| } |
| buffer.append(checkDigit); |
| |
| ResultPoint[] leftPoints = leftPair.getFinderPattern().getResultPoints(); |
| ResultPoint[] rightPoints = rightPair.getFinderPattern().getResultPoints(); |
| return new Result( |
| String.valueOf(buffer.toString()), |
| null, |
| new ResultPoint[] { leftPoints[0], leftPoints[1], rightPoints[0], rightPoints[1], }, |
| BarcodeFormat.RSS_14); |
| } |
| |
| private static boolean checkChecksum(Pair leftPair, Pair rightPair) { |
| //int leftFPValue = leftPair.getFinderPattern().getValue(); |
| //int rightFPValue = rightPair.getFinderPattern().getValue(); |
| //if ((leftFPValue == 0 && rightFPValue == 8) || |
| // (leftFPValue == 8 && rightFPValue == 0)) { |
| //} |
| int checkValue = (leftPair.getChecksumPortion() + 16 * rightPair.getChecksumPortion()) % 79; |
| int targetCheckValue = |
| 9 * leftPair.getFinderPattern().getValue() + rightPair.getFinderPattern().getValue(); |
| if (targetCheckValue > 72) { |
| targetCheckValue--; |
| } |
| if (targetCheckValue > 8) { |
| targetCheckValue--; |
| } |
| return checkValue == targetCheckValue; |
| } |
| |
| 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; |
| } |
| } |
| |
| private DataCharacter decodeDataCharacter(BitArray row, FinderPattern pattern, boolean outsideChar) |
| throws NotFoundException { |
| |
| int[] counters = getDataCharacterCounters(); |
| counters[0] = 0; |
| counters[1] = 0; |
| counters[2] = 0; |
| counters[3] = 0; |
| counters[4] = 0; |
| counters[5] = 0; |
| counters[6] = 0; |
| counters[7] = 0; |
| |
| if (outsideChar) { |
| recordPatternInReverse(row, pattern.getStartEnd()[0], counters); |
| } else { |
| recordPattern(row, pattern.getStartEnd()[1] + 1, counters); |
| // reverse it |
| for (int i = 0, j = counters.length - 1; i < j; i++, j--) { |
| int temp = counters[i]; |
| counters[i] = counters[j]; |
| counters[j] = temp; |
| } |
| } |
| |
| int numModules = outsideChar ? 16 : 15; |
| float elementWidth = (float) count(counters) / (float) numModules; |
| |
| int[] oddCounts = this.getOddCounts(); |
| int[] evenCounts = this.getEvenCounts(); |
| float[] oddRoundingErrors = this.getOddRoundingErrors(); |
| float[] evenRoundingErrors = this.getEvenRoundingErrors(); |
| |
| for (int i = 0; i < counters.length; i++) { |
| float value = (float) counters[i] / elementWidth; |
| int count = (int) (value + 0.5f); // Round |
| if (count < 1) { |
| count = 1; |
| } else if (count > 8) { |
| count = 8; |
| } |
| int offset = i / 2; |
| if ((i & 0x01) == 0) { |
| oddCounts[offset] = count; |
| oddRoundingErrors[offset] = value - count; |
| } else { |
| evenCounts[offset] = count; |
| evenRoundingErrors[offset] = value - count; |
| } |
| } |
| |
| adjustOddEvenCounts(outsideChar, numModules); |
| |
| int oddSum = 0; |
| int oddChecksumPortion = 0; |
| for (int i = oddCounts.length - 1; i >= 0; i--) { |
| oddChecksumPortion *= 9; |
| oddChecksumPortion += oddCounts[i]; |
| oddSum += oddCounts[i]; |
| } |
| int evenChecksumPortion = 0; |
| int evenSum = 0; |
| for (int i = evenCounts.length - 1; i >= 0; i--) { |
| evenChecksumPortion *= 9; |
| evenChecksumPortion += evenCounts[i]; |
| evenSum += evenCounts[i]; |
| } |
| int checksumPortion = oddChecksumPortion + 3*evenChecksumPortion; |
| |
| if (outsideChar) { |
| if ((oddSum & 0x01) != 0 || oddSum > 12 || oddSum < 4) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| int group = (12 - oddSum) / 2; |
| int oddWidest = OUTSIDE_ODD_WIDEST[group]; |
| int evenWidest = 9 - oddWidest; |
| int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, false); |
| int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, true); |
| int tEven = OUTSIDE_EVEN_TOTAL_SUBSET[group]; |
| int gSum = OUTSIDE_GSUM[group]; |
| return new DataCharacter(vOdd * tEven + vEven + gSum, checksumPortion); |
| } else { |
| if ((evenSum & 0x01) != 0 || evenSum > 10 || evenSum < 4) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| int group = (10 - evenSum) / 2; |
| int oddWidest = INSIDE_ODD_WIDEST[group]; |
| int evenWidest = 9 - oddWidest; |
| int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, true); |
| int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, false); |
| int tOdd = INSIDE_ODD_TOTAL_SUBSET[group]; |
| int gSum = INSIDE_GSUM[group]; |
| return new DataCharacter(vEven * tOdd + vOdd + gSum, checksumPortion); |
| } |
| |
| } |
| |
| private int[] findFinderPattern(BitArray row, int rowOffset, boolean rightFinderPattern) |
| throws NotFoundException { |
| |
| int[] counters = getDecodeFinderCounters(); |
| counters[0] = 0; |
| counters[1] = 0; |
| counters[2] = 0; |
| counters[3] = 0; |
| |
| int width = row.getSize(); |
| boolean isWhite = false; |
| while (rowOffset < width) { |
| isWhite = !row.get(rowOffset); |
| if (rightFinderPattern == isWhite) { |
| // Will encounter white first when searching for right finder pattern |
| break; |
| } |
| rowOffset++; |
| } |
| |
| int counterPosition = 0; |
| int patternStart = rowOffset; |
| for (int x = rowOffset; x < width; x++) { |
| if (row.get(x) ^ isWhite) { |
| counters[counterPosition]++; |
| } else { |
| if (counterPosition == 3) { |
| if (isFinderPattern(counters)) { |
| return new int[]{patternStart, x}; |
| } |
| patternStart += counters[0] + counters[1]; |
| counters[0] = counters[2]; |
| counters[1] = counters[3]; |
| counters[2] = 0; |
| counters[3] = 0; |
| counterPosition--; |
| } else { |
| counterPosition++; |
| } |
| counters[counterPosition] = 1; |
| isWhite = !isWhite; |
| } |
| } |
| throw NotFoundException.getNotFoundInstance(); |
| |
| } |
| |
| private FinderPattern parseFoundFinderPattern(BitArray row, int rowNumber, boolean right, int[] startEnd) |
| throws NotFoundException { |
| // Actually we found elements 2-5 |
| boolean firstIsBlack = row.get(startEnd[0]); |
| int firstElementStart = startEnd[0] - 1; |
| // Locate element 1 |
| while (firstElementStart >= 0 && firstIsBlack ^ row.get(firstElementStart)) { |
| firstElementStart--; |
| } |
| firstElementStart++; |
| int firstCounter = startEnd[0] - firstElementStart; |
| // Make 'counters' hold 1-4 |
| int[] counters = getDecodeFinderCounters(); |
| System.arraycopy(counters, 0, counters, 1, counters.length - 1); |
| counters[0] = firstCounter; |
| int value = parseFinderValue(counters, FINDER_PATTERNS); |
| int start = firstElementStart; |
| int end = startEnd[1]; |
| if (right) { |
| // row is actually reversed |
| start = row.getSize() - 1 - start; |
| end = row.getSize() - 1 - end; |
| } |
| return new FinderPattern(value, new int[] {firstElementStart, startEnd[1]}, start, end, rowNumber); |
| } |
| |
| private void adjustOddEvenCounts(boolean outsideChar, int numModules) throws NotFoundException { |
| |
| int oddSum = count(getOddCounts()); |
| int evenSum = count(getEvenCounts()); |
| int mismatch = oddSum + evenSum - numModules; |
| boolean oddParityBad = (oddSum & 0x01) == (outsideChar ? 1 : 0); |
| boolean evenParityBad = (evenSum & 0x01) == 1; |
| |
| boolean incrementOdd = false; |
| boolean decrementOdd = false; |
| boolean incrementEven = false; |
| boolean decrementEven = false; |
| |
| if (outsideChar) { |
| if (oddSum > 12) { |
| decrementOdd = true; |
| } else if (oddSum < 4) { |
| incrementOdd = true; |
| } |
| if (evenSum > 12) { |
| decrementEven = true; |
| } else if (evenSum < 4) { |
| incrementEven = true; |
| } |
| } else { |
| if (oddSum > 11) { |
| decrementOdd = true; |
| } else if (oddSum < 5) { |
| incrementOdd = true; |
| } |
| if (evenSum > 10) { |
| decrementEven = true; |
| } else if (evenSum < 4) { |
| incrementEven = true; |
| } |
| } |
| |
| /*if (mismatch == 2) { |
| if (!(oddParityBad && evenParityBad)) { |
| throw ReaderException.getInstance(); |
| } |
| decrementOdd = true; |
| decrementEven = true; |
| } else if (mismatch == -2) { |
| if (!(oddParityBad && evenParityBad)) { |
| throw ReaderException.getInstance(); |
| } |
| incrementOdd = true; |
| incrementEven = true; |
| } else */if (mismatch == 1) { |
| if (oddParityBad) { |
| if (evenParityBad) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| decrementOdd = true; |
| } else { |
| if (!evenParityBad) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| decrementEven = true; |
| } |
| } else if (mismatch == -1) { |
| if (oddParityBad) { |
| if (evenParityBad) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| incrementOdd = true; |
| } else { |
| if (!evenParityBad) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| incrementEven = true; |
| } |
| } else if (mismatch == 0) { |
| if (oddParityBad) { |
| if (!evenParityBad) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| // Both bad |
| if (oddSum < evenSum) { |
| incrementOdd = true; |
| decrementEven = true; |
| } else { |
| decrementOdd = true; |
| incrementEven = true; |
| } |
| } else { |
| if (evenParityBad) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| // Nothing to do! |
| } |
| } else { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| |
| if (incrementOdd) { |
| if (decrementOdd) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| increment(getOddCounts(), getOddRoundingErrors()); |
| } |
| if (decrementOdd) { |
| decrement(getOddCounts(), getOddRoundingErrors()); |
| } |
| if (incrementEven) { |
| if (decrementEven) { |
| throw NotFoundException.getNotFoundInstance(); |
| } |
| increment(getEvenCounts(), getOddRoundingErrors()); |
| } |
| if (decrementEven) { |
| decrement(getEvenCounts(), getEvenRoundingErrors()); |
| } |
| |
| } |
| |
| } |