2 * Copyright 2009 ZXing authors
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.google.zxing.oned.rss;
19 import com.google.zxing.BarcodeFormat;
20 import com.google.zxing.DecodeHintType;
21 import com.google.zxing.NotFoundException;
22 import com.google.zxing.Result;
23 import com.google.zxing.ResultPoint;
24 import com.google.zxing.ResultPointCallback;
25 import com.google.zxing.common.BitArray;
26 import com.google.zxing.oned.OneDReader;
28 import java.util.Enumeration;
29 import java.util.Hashtable;
30 import java.util.Vector;
33 * Decodes RSS-14, including truncated and stacked variants. See ISO/IEC 24724:2006.
35 public final class RSS14Reader extends OneDReader {
37 private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.2f);
38 private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.4f);
40 private static final float MIN_FINDER_PATTERN_RATIO = 9.5f / 12.0f;
41 private static final float MAX_FINDER_PATTERN_RATIO = 12.5f / 14.0f;
43 private static final int[] OUTSIDE_EVEN_TOTAL_SUBSET = {1,10,34,70,126};
44 private static final int[] INSIDE_ODD_TOTAL_SUBSET = {4,20,48,81};
45 private static final int[] OUTSIDE_GSUM = {0,161,961,2015,2715};
46 private static final int[] INSIDE_GSUM = {0,336,1036,1516};
47 private static final int[] OUTSIDE_ODD_WIDEST = {8,6,4,3,1};
48 private static final int[] INSIDE_ODD_WIDEST = {2,4,6,8};
50 private static final int[][] FINDER_PATTERNS = {
62 private final int[] decodeFinderCounters;
63 private final int[] dataCharacterCounters;
64 private final float[] oddRoundingErrors;
65 private final float[] evenRoundingErrors;
66 private final int[] oddCounts;
67 private final int[] evenCounts;
68 private final Vector possibleLeftPairs;
69 private final Vector possibleRightPairs;
71 public RSS14Reader() {
72 decodeFinderCounters = new int[4];
73 dataCharacterCounters = new int[8];
74 oddRoundingErrors = new float[4];
75 evenRoundingErrors = new float[4];
76 oddCounts = new int[dataCharacterCounters.length / 2];
77 evenCounts = new int[dataCharacterCounters.length / 2];
78 possibleLeftPairs = new Vector();
79 possibleRightPairs = new Vector();
82 public Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws NotFoundException {
83 Pair leftPair = decodePair(row, false, rowNumber, hints);
84 addOrTally(possibleLeftPairs, leftPair);
86 Pair rightPair = decodePair(row, true, rowNumber, hints);
87 addOrTally(possibleRightPairs, rightPair);
89 int numLeftPairs = possibleLeftPairs.size();
90 int numRightPairs = possibleRightPairs.size();
91 for (int l = 0; l < numLeftPairs; l++) {
92 Pair left = (Pair) possibleLeftPairs.elementAt(l);
93 if (left.getCount() > 1) {
94 for (int r = 0; r < numRightPairs; r++) {
95 Pair right = (Pair) possibleRightPairs.elementAt(r);
96 if (right.getCount() > 1) {
97 if (checkChecksum(left, right)) {
98 return constructResult(left, right);
104 throw NotFoundException.getNotFoundInstance();
107 private static void addOrTally(Vector possiblePairs, Pair pair) {
111 Enumeration e = possiblePairs.elements();
112 boolean found = false;
113 while (e.hasMoreElements()) {
114 Pair other = (Pair) e.nextElement();
115 if (other.getValue() == pair.getValue()) {
116 other.incrementCount();
122 possiblePairs.addElement(pair);
126 public void reset() {
127 possibleLeftPairs.setSize(0);
128 possibleRightPairs.setSize(0);
131 private static Result constructResult(Pair leftPair, Pair rightPair) {
132 long symbolValue = 4537077L * leftPair.getValue() + rightPair.getValue();
133 String text = String.valueOf(symbolValue);
135 StringBuffer buffer = new StringBuffer(14);
136 for (int i = 13 - text.length(); i > 0; i--) {
142 for (int i = 0; i < 13; i++) {
143 int digit = buffer.charAt(i) - '0';
144 checkDigit += (((i & 0x01) == 0) ? 3 * digit : digit);
146 checkDigit = 10 - (checkDigit % 10);
147 if (checkDigit == 10) {
150 buffer.append(checkDigit);
152 ResultPoint[] leftPoints = leftPair.getFinderPattern().getResultPoints();
153 ResultPoint[] rightPoints = rightPair.getFinderPattern().getResultPoints();
155 String.valueOf(buffer.toString()),
157 new ResultPoint[] { leftPoints[0], leftPoints[1], rightPoints[0], rightPoints[1], },
158 BarcodeFormat.RSS14);
161 private static boolean checkChecksum(Pair leftPair, Pair rightPair) {
162 int leftFPValue = leftPair.getFinderPattern().getValue();
163 int rightFPValue = rightPair.getFinderPattern().getValue();
164 if ((leftFPValue == 0 && rightFPValue == 8) ||
165 (leftFPValue == 8 && rightFPValue == 0)) {
167 int checkValue = (leftPair.getChecksumPortion() + 16 * rightPair.getChecksumPortion()) % 79;
168 int targetCheckValue =
169 9 * leftPair.getFinderPattern().getValue() + rightPair.getFinderPattern().getValue();
170 if (targetCheckValue > 72) {
173 if (targetCheckValue > 8) {
176 return checkValue == targetCheckValue;
179 private Pair decodePair(BitArray row, boolean right, int rowNumber, Hashtable hints) {
181 int[] startEnd = findFinderPattern(row, 0, right);
182 FinderPattern pattern = parseFoundFinderPattern(row, rowNumber, right, startEnd);
184 ResultPointCallback resultPointCallback = hints == null ? null :
185 (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
187 if (resultPointCallback != null) {
188 float center = (startEnd[0] + startEnd[1]) / 2.0f;
190 // row is actually reversed
191 center = row.getSize() - 1 - center;
193 resultPointCallback.foundPossibleResultPoint(new ResultPoint(center, rowNumber));
196 DataCharacter outside = decodeDataCharacter(row, pattern, true);
197 DataCharacter inside = decodeDataCharacter(row, pattern, false);
198 return new Pair(1597 * outside.getValue() + inside.getValue(),
199 outside.getChecksumPortion() + 4 * inside.getChecksumPortion(),
201 } catch (NotFoundException re) {
206 private DataCharacter decodeDataCharacter(BitArray row, FinderPattern pattern, boolean outsideChar)
207 throws NotFoundException {
209 int[] counters = dataCharacterCounters;
220 recordPatternInReverse(row, pattern.getStartEnd()[0], counters);
222 recordPattern(row, pattern.getStartEnd()[1] + 1, counters);
224 for (int i = 0, j = counters.length - 1; i < j; i++, j--) {
225 int temp = counters[i];
226 counters[i] = counters[j];
231 int numModules = outsideChar ? 16 : 15;
232 float elementWidth = (float) count(counters) / (float) numModules;
234 int[] oddCounts = this.oddCounts;
235 int[] evenCounts = this.evenCounts;
236 float[] oddRoundingErrors = this.oddRoundingErrors;
237 float[] evenRoundingErrors = this.evenRoundingErrors;
239 for (int i = 0; i < counters.length; i++) {
240 float value = (float) counters[i] / elementWidth;
241 int count = (int) (value + 0.5f); // Round
244 } else if (count > 8) {
248 if ((i & 0x01) == 0) {
249 oddCounts[offset] = count;
250 oddRoundingErrors[offset] = value - count;
252 evenCounts[offset] = count;
253 evenRoundingErrors[offset] = value - count;
257 adjustOddEvenCounts(outsideChar, numModules);
260 int oddChecksumPortion = 0;
261 for (int i = oddCounts.length - 1; i >= 0; i--) {
262 oddChecksumPortion *= 9;
263 oddChecksumPortion += oddCounts[i];
264 oddSum += oddCounts[i];
266 int evenChecksumPortion = 0;
268 for (int i = evenCounts.length - 1; i >= 0; i--) {
269 evenChecksumPortion *= 9;
270 evenChecksumPortion += evenCounts[i];
271 evenSum += evenCounts[i];
273 int checksumPortion = oddChecksumPortion + 3*evenChecksumPortion;
276 if ((oddSum & 0x01) != 0 || oddSum > 12 || oddSum < 4) {
277 throw NotFoundException.getNotFoundInstance();
279 int group = (12 - oddSum) / 2;
280 int oddWidest = OUTSIDE_ODD_WIDEST[group];
281 int evenWidest = 9 - oddWidest;
282 int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, false);
283 int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, true);
284 int tEven = OUTSIDE_EVEN_TOTAL_SUBSET[group];
285 int gSum = OUTSIDE_GSUM[group];
286 return new DataCharacter(vOdd * tEven + vEven + gSum, checksumPortion);
288 if ((evenSum & 0x01) != 0 || evenSum > 10 || evenSum < 4) {
289 throw NotFoundException.getNotFoundInstance();
291 int group = (10 - evenSum) / 2;
292 int oddWidest = INSIDE_ODD_WIDEST[group];
293 int evenWidest = 9 - oddWidest;
294 int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, true);
295 int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, false);
296 int tOdd = INSIDE_ODD_TOTAL_SUBSET[group];
297 int gSum = INSIDE_GSUM[group];
298 return new DataCharacter(vEven * tOdd + vOdd + gSum, checksumPortion);
303 private int[] findFinderPattern(BitArray row, int rowOffset, boolean rightFinderPattern)
304 throws NotFoundException {
306 int[] counters = decodeFinderCounters;
312 int width = row.getSize();
313 boolean isWhite = false;
314 while (rowOffset < width) {
315 isWhite = !row.get(rowOffset);
316 if (rightFinderPattern == isWhite) {
317 // Will encounter white first when searching for right finder pattern
323 int counterPosition = 0;
324 int patternStart = rowOffset;
325 for (int x = rowOffset; x < width; x++) {
326 boolean pixel = row.get(x);
327 if (pixel ^ isWhite) {
328 counters[counterPosition]++;
330 if (counterPosition == 3) {
331 if (isFinderPattern(counters)) {
332 return new int[]{patternStart, x};
334 patternStart += counters[0] + counters[1];
335 counters[0] = counters[2];
336 counters[1] = counters[3];
343 counters[counterPosition] = 1;
347 throw NotFoundException.getNotFoundInstance();
351 private static boolean isFinderPattern(int[] counters) {
352 int firstTwoSum = counters[0] + counters[1];
353 int sum = firstTwoSum + counters[2] + counters[3];
354 float ratio = (float) firstTwoSum / (float) sum;
355 if (ratio >= MIN_FINDER_PATTERN_RATIO && ratio <= MAX_FINDER_PATTERN_RATIO) {
356 // passes ratio test in spec, but see if the counts are unreasonable
357 int minCounter = Integer.MAX_VALUE;
358 int maxCounter = Integer.MIN_VALUE;
359 for (int i = 0; i < counters.length; i++) {
360 int counter = counters[i];
361 if (counter > maxCounter) {
362 maxCounter = counter;
364 if (counter < minCounter) {
365 minCounter = counter;
368 return maxCounter < 10 * minCounter;
373 private FinderPattern parseFoundFinderPattern(BitArray row, int rowNumber, boolean right, int[] startEnd)
374 throws NotFoundException {
375 // Actually we found elements 2-5
376 boolean firstIsBlack = row.get(startEnd[0]);
377 int firstElementStart = startEnd[0] - 1;
379 while (firstElementStart >= 0 && firstIsBlack ^ row.get(firstElementStart)) {
383 int firstCounter = startEnd[0] - firstElementStart;
384 // Make 'counters' hold 1-4
385 int[] counters = decodeFinderCounters;
386 for (int i = counters.length - 1; i > 0; i--) {
387 counters[i] = counters[i-1];
389 counters[0] = firstCounter;
390 int value = parseFinderValue(counters);
391 int start = firstElementStart;
392 int end = startEnd[1];
394 // row is actually reversed
395 start = row.getSize() - 1 - start;
396 end = row.getSize() - 1 - end;
398 return new FinderPattern(value, new int[] {firstElementStart, startEnd[1]}, start, end, rowNumber);
401 private static int parseFinderValue(int[] counters) throws NotFoundException {
402 for (int value = 0; value < FINDER_PATTERNS.length; value++) {
403 if (patternMatchVariance(counters, FINDER_PATTERNS[value], MAX_INDIVIDUAL_VARIANCE) <
408 throw NotFoundException.getNotFoundInstance();
412 private static int[] normalizeE2SEValues(int[] counters) {
414 for (int i = 0; i < counters.length; i++) {
417 int[] normalized = new int[counters.length - 2];
418 for (int i = 0; i < normalized.length; i++) {
419 int e = counters[i] + counters[i+1];
420 float eRatio = (float) e / (float) p;
421 float E = ((eRatio * 32.0f) + 1.0f) / 2.0f;
422 normalized[i] = (int) E;
428 private static int count(int[] array) {
430 for (int i = 0; i < array.length; i++) {
436 private static void increment(int[] array, float[] errors) {
438 float biggestError = errors[0];
439 for (int i = 1; i < array.length; i++) {
440 if (errors[i] > biggestError) {
441 biggestError = errors[i];
448 private static void decrement(int[] array, float[] errors) {
450 float biggestError = errors[0];
451 for (int i = 1; i < array.length; i++) {
452 if (errors[i] < biggestError) {
453 biggestError = errors[i];
460 private void adjustOddEvenCounts(boolean outsideChar, int numModules) throws NotFoundException {
462 int oddSum = count(oddCounts);
463 int evenSum = count(evenCounts);
464 int mismatch = oddSum + evenSum - numModules;
465 boolean oddParityBad = (oddSum & 0x01) == (outsideChar ? 1 : 0);
466 boolean evenParityBad = (evenSum & 0x01) == 1;
468 boolean incrementOdd = false;
469 boolean decrementOdd = false;
470 boolean incrementEven = false;
471 boolean decrementEven = false;
476 } else if (oddSum < 4) {
480 decrementEven = true;
481 } else if (evenSum < 4) {
482 incrementEven = true;
487 } else if (oddSum < 5) {
491 decrementEven = true;
492 } else if (evenSum < 4) {
493 incrementEven = true;
497 /*if (mismatch == 2) {
498 if (!(oddParityBad && evenParityBad)) {
499 throw ReaderException.getInstance();
502 decrementEven = true;
503 } else if (mismatch == -2) {
504 if (!(oddParityBad && evenParityBad)) {
505 throw ReaderException.getInstance();
508 incrementEven = true;
509 } else */if (mismatch == 1) {
512 throw NotFoundException.getNotFoundInstance();
516 if (!evenParityBad) {
517 throw NotFoundException.getNotFoundInstance();
519 decrementEven = true;
521 } else if (mismatch == -1) {
524 throw NotFoundException.getNotFoundInstance();
528 if (!evenParityBad) {
529 throw NotFoundException.getNotFoundInstance();
531 incrementEven = true;
533 } else if (mismatch == 0) {
535 if (!evenParityBad) {
536 throw NotFoundException.getNotFoundInstance();
539 if (oddSum < evenSum) {
541 decrementEven = true;
544 incrementEven = true;
548 throw NotFoundException.getNotFoundInstance();
553 throw NotFoundException.getNotFoundInstance();
558 throw NotFoundException.getNotFoundInstance();
560 increment(oddCounts, oddRoundingErrors);
563 decrement(oddCounts, oddRoundingErrors);
567 throw NotFoundException.getNotFoundInstance();
569 increment(evenCounts, oddRoundingErrors);
572 decrement(evenCounts, evenRoundingErrors);