Issue 583 RSS Expanded support, by Pablo Orduna and the Deusto crew, back-ported by ShumovichY

git-svn-id: https://zxing.googlecode.com/svn/trunk@2510 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen 2012-11-12 15:09:25 +00:00
parent 0e4ef00d34
commit b3839ec5e0
152 changed files with 625 additions and 60 deletions

View file

@ -34,4 +34,23 @@ public class DataCharacter {
return checksumPortion; return checksumPortion;
} }
@Override
public String toString() {
return value + "(" + checksumPortion + ')';
}
@Override
public boolean equals(Object o) {
if(!(o instanceof DataCharacter)) {
return false;
}
DataCharacter that = (DataCharacter) o;
return value == that.value && checksumPortion == that.checksumPortion;
}
@Override
public int hashCode() {
return value ^ checksumPortion;
}
} }

View file

@ -45,4 +45,18 @@ public final class FinderPattern {
return resultPoints; return resultPoints;
} }
@Override
public boolean equals(Object o) {
if(!(o instanceof FinderPattern)) {
return false;
}
FinderPattern that = (FinderPattern) o;
return value == that.value;
}
@Override
public int hashCode() {
return value;
}
} }

View file

@ -43,10 +43,10 @@ final class ExpandedPair {
DataCharacter rightChar, DataCharacter rightChar,
FinderPattern finderPattern, FinderPattern finderPattern,
boolean mayBeLast) { boolean mayBeLast) {
this.leftChar = leftChar; this.leftChar = leftChar;
this.rightChar = rightChar; this.rightChar = rightChar;
this.finderPattern = finderPattern; this.finderPattern = finderPattern;
this.mayBeLast = mayBeLast; this.mayBeLast = mayBeLast;
} }
boolean mayBeLast(){ boolean mayBeLast(){
@ -68,4 +68,37 @@ final class ExpandedPair {
public boolean mustBeLast() { public boolean mustBeLast() {
return this.rightChar == null; return this.rightChar == null;
} }
@Override
public String toString() {
return
"[ " + leftChar + " , " + rightChar + " : " +
(finderPattern == null ? "null" : finderPattern.getValue()) + " ]";
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ExpandedPair)) {
return false;
}
ExpandedPair that = (ExpandedPair) o;
return
equalsOrNull(leftChar, that.leftChar) &&
equalsOrNull(rightChar, that.rightChar) &&
equalsOrNull(finderPattern, that.finderPattern);
}
private static boolean equalsOrNull(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
@Override
public int hashCode() {
return hashNotNull(leftChar) ^ hashNotNull(rightChar) ^ hashNotNull(finderPattern);
}
private static int hashNotNull(Object o) {
return o == null ? 0 : o.hashCode();
}
} }

View file

@ -0,0 +1,76 @@
/*
* Copyright (C) 2010 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.expanded;
import java.util.ArrayList;
import java.util.List;
/**
* One row of an RSS Expanded Stacked symbol, consisting of 1+ expanded pairs.
*/
final class ExpandedRow {
private final List<ExpandedPair> pairs;
private final int rowNumber;
/** Did this row of the image have to be reversed (mirrored) to recognize the pairs? */
private final boolean wasReversed;
ExpandedRow(List<ExpandedPair> pairs, int rowNumber, boolean wasReversed) {
this.pairs = new ArrayList<ExpandedPair>(pairs);
this.rowNumber = rowNumber;
this.wasReversed = wasReversed;
}
List<ExpandedPair> getPairs() {
return this.pairs;
}
int getRowNumber() {
return this.rowNumber;
}
boolean isReversed() {
return this.wasReversed;
}
boolean isEquivalent(List<ExpandedPair> otherPairs) {
return this.pairs.equals(otherPairs);
}
@Override
public String toString() {
return "{ " + pairs + " }";
}
/**
* Two rows are equal if they contain the same pairs in the same order.
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof ExpandedRow)) {
return false;
}
ExpandedRow that = (ExpandedRow) o;
return this.pairs.equals(that.getPairs()) && wasReversed == that.wasReversed;
}
@Override
public int hashCode() {
return pairs.hashCode() ^ Boolean.valueOf(wasReversed).hashCode();
}
}

View file

@ -41,6 +41,7 @@ import com.google.zxing.oned.rss.expanded.decoders.AbstractExpandedDecoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Collections;
/** /**
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es) * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
@ -112,41 +113,186 @@ public final class RSSExpandedReader extends AbstractRSSReader {
private static final int MAX_PAIRS = 11; private static final int MAX_PAIRS = 11;
private final List<ExpandedPair> pairs = new ArrayList<ExpandedPair>(MAX_PAIRS); private final List<ExpandedPair> pairs = new ArrayList<ExpandedPair>(MAX_PAIRS);
private final List<ExpandedRow> rows = new ArrayList<ExpandedRow>();
private final int [] startEnd = new int[2]; private final int [] startEnd = new int[2];
private final int [] currentSequence = new int[LONGEST_SEQUENCE_SIZE]; private final int [] currentSequence = new int[LONGEST_SEQUENCE_SIZE];
private boolean startFromEven = false;
@Override @Override
public Result decodeRow(int rowNumber, public Result decodeRow(int rowNumber,
BitArray row, BitArray row,
Map<DecodeHintType,?> hints) throws NotFoundException { Map<DecodeHintType,?> hints) throws NotFoundException {
this.reset(); // Rows can start with even pattern in case in prev rows there where odd number of patters.
decodeRow2pairs(rowNumber, row); // So lets try twice
return constructResult(this.pairs); this.pairs.clear();
this.startFromEven = false;
try {
List<ExpandedPair> pairs = decodeRow2pairs(rowNumber, row);
return constructResult(pairs);
} catch (NotFoundException e) {
// OK
}
this.pairs.clear();
this.startFromEven = true;
List<ExpandedPair> pairs = decodeRow2pairs(rowNumber, row);
return constructResult(pairs);
} }
@Override @Override
public void reset() { public void reset() {
this.pairs.clear(); this.pairs.clear();
this.rows.clear();
} }
// Not private for testing // Not private for testing
List<ExpandedPair> decodeRow2pairs(int rowNumber, BitArray row) throws NotFoundException { List<ExpandedPair> decodeRow2pairs(int rowNumber, BitArray row) throws NotFoundException {
while(true){ try {
ExpandedPair nextPair = retrieveNextPair(row, this.pairs, rowNumber); while (true){
this.pairs.add(nextPair); ExpandedPair nextPair = retrieveNextPair(row, this.pairs, rowNumber);
this.pairs.add(nextPair);
//System.out.println(this.pairs.size()+" pairs found so far on row "+rowNumber+": "+this.pairs);
// exit this loop when retrieveNextPair() fails and throws
}
} catch (NotFoundException nfe) {
if (this.pairs.isEmpty()) {
throw nfe;
}
}
if (nextPair.mayBeLast()) { // TODO: verify sequence of finder patterns as in checkPairSequence()
if (checkChecksum()) { if (checkChecksum()) {
return this.pairs; return this.pairs;
}
boolean tryStackedDecode = !this.rows.isEmpty();
boolean wasReversed = false; // TODO: deal with reversed rows
storeRow(rowNumber, wasReversed);
if (tryStackedDecode) {
// When the image is 180-rotated, then rows are sorted in wrong dirrection.
// Try twice with both the directions.
List<ExpandedPair> ps = checkRows(false);
if (ps != null) {
return ps;
}
ps = checkRows(true);
if (ps != null) {
return ps;
}
}
throw NotFoundException.getNotFoundInstance();
}
private List<ExpandedPair> checkRows(boolean reverse) {
this.pairs.clear();
if (reverse) {
Collections.reverse(this.rows);
}
for(ExpandedRow erow : this.rows) {
this.pairs.addAll(erow.getPairs());
}
//System.out.println(this.pairs.size()+" pairs on MULTIPLE ROWS: "+this.rows);
if (checkChecksum()) {
return this.pairs;
}
if (reverse) {
Collections.reverse(this.rows);
}
return null;
}
private void storeRow(int rowNumber, boolean wasReversed) {
// Discard if duplicate above or below; otherwise insert in order by row number.
int insertPos = 0;
boolean prevIsSame = false;
boolean nextIsSame = false;
while (insertPos < this.rows.size()) {
ExpandedRow erow = this.rows.get(insertPos);
if (erow.getRowNumber() > rowNumber) {
nextIsSame = erow.isEquivalent(this.pairs);
break;
}
prevIsSame = erow.isEquivalent(this.pairs);
insertPos += 1;
}
if (nextIsSame || prevIsSame) {
return;
}
// When the row was partially decoded (e.g. 2 pairs found instead of 3),
// it will prevent us from detecting the barcode.
// Try to merge partial rows
// Check whether the row is part of an allready detected row
if (isPartialRow(this.pairs, this.rows)) {
return;
}
this.rows.add(insertPos, new ExpandedRow(this.pairs, rowNumber, wasReversed));
removePartialRows(this.pairs, this.rows);
}
// Remove all the rows that contains only specified pairs
private static void removePartialRows(List<ExpandedPair> pairs, List<ExpandedRow> rows) {
check:
for (ExpandedRow r : rows) {
if (r.getPairs().size() == pairs.size()) {
continue;
}
for (ExpandedPair p : r.getPairs()) {
boolean found = false;
for (ExpandedPair pp : pairs) {
if (p.equals(pp)) {
found = true;
break;
}
} }
if (nextPair.mustBeLast()) { if (!found) {
throw NotFoundException.getNotFoundInstance(); continue check;
} }
} }
// 'pairs' contains all the pairs from the row 'r'
rows.remove(r);
// start from the begining
removePartialRows(pairs, rows);
return;
} }
} }
private static Result constructResult(List<ExpandedPair> pairs) throws NotFoundException{ // Returns true when one of the rows already contains all the pairs
private static boolean isPartialRow(Iterable<ExpandedPair> pairs, Iterable<ExpandedRow> rows) {
check:
for (ExpandedRow r : rows) {
for (ExpandedPair p : pairs) {
boolean found = false;
for (ExpandedPair pp : r.getPairs()) {
if (p.equals(pp)) {
found = true;
break;
}
}
if (!found) {
continue check;
}
}
// the row 'r' contain all the pairs from 'pairs'
return true;
}
return false;
}
// Only used for unit testing
List<ExpandedRow> getRows() {
return this.rows;
}
// Not private for unit testing
static Result constructResult(List<ExpandedPair> pairs) throws NotFoundException{
BitArray binary = BitArrayBuilder.buildBitArray(pairs); BitArray binary = BitArrayBuilder.buildBitArray(pairs);
AbstractExpandedDecoder decoder = AbstractExpandedDecoder.createDecoder(binary); AbstractExpandedDecoder decoder = AbstractExpandedDecoder.createDecoder(binary);
@ -168,6 +314,10 @@ public final class RSSExpandedReader extends AbstractRSSReader {
DataCharacter checkCharacter = firstPair.getLeftChar(); DataCharacter checkCharacter = firstPair.getLeftChar();
DataCharacter firstCharacter = firstPair.getRightChar(); DataCharacter firstCharacter = firstPair.getRightChar();
if (firstCharacter == null) {
return false;
}
int checksum = firstCharacter.getChecksumPortion(); int checksum = firstCharacter.getChecksumPortion();
int s = 2; int s = 2;
@ -205,6 +355,9 @@ public final class RSSExpandedReader extends AbstractRSSReader {
ExpandedPair retrieveNextPair(BitArray row, List<ExpandedPair> previousPairs, int rowNumber) ExpandedPair retrieveNextPair(BitArray row, List<ExpandedPair> previousPairs, int rowNumber)
throws NotFoundException { throws NotFoundException {
boolean isOddPattern = previousPairs.size() % 2 == 0; boolean isOddPattern = previousPairs.size() % 2 == 0;
if (startFromEven) {
isOddPattern = !isOddPattern;
}
FinderPattern pattern; FinderPattern pattern;
@ -220,54 +373,27 @@ public final class RSSExpandedReader extends AbstractRSSReader {
} }
}while(keepFinding); }while(keepFinding);
boolean mayBeLast = checkPairSequence(previousPairs, pattern); // When stacked symbol is split over multiple rows, there's no way to guess if this pair can be last or not.
// boolean mayBeLast = checkPairSequence(previousPairs, pattern);
DataCharacter leftChar = this.decodeDataCharacter(row, pattern, isOddPattern, true); DataCharacter leftChar = this.decodeDataCharacter(row, pattern, isOddPattern, true);
if (!previousPairs.isEmpty()){
if (previousPairs.get(previousPairs.size()-1).mustBeLast()){
throw NotFoundException.getNotFoundInstance();
}
}
DataCharacter rightChar; DataCharacter rightChar;
try{ try {
rightChar = this.decodeDataCharacter(row, pattern, isOddPattern, false); rightChar = this.decodeDataCharacter(row, pattern, isOddPattern, false);
}catch(NotFoundException nfe){ } catch(NotFoundException nfe) {
if(mayBeLast) { rightChar = null;
rightChar = null;
} else {
throw nfe;
}
} }
boolean mayBeLast = true;
return new ExpandedPair(leftChar, rightChar, pattern, mayBeLast); return new ExpandedPair(leftChar, rightChar, pattern, mayBeLast);
} }
private boolean checkPairSequence(List<ExpandedPair> previousPairs, FinderPattern pattern)
throws NotFoundException {
int currentSequenceLength = previousPairs.size() + 1;
if(currentSequenceLength > this.currentSequence.length) {
throw NotFoundException.getNotFoundInstance();
}
for(int pos = 0; pos < previousPairs.size(); ++pos) {
this.currentSequence[pos] = previousPairs.get(pos).getFinderPattern().getValue();
}
this.currentSequence[currentSequenceLength - 1] = pattern.getValue();
for (int[] validSequence : FINDER_PATTERN_SEQUENCES) {
if (validSequence.length >= currentSequenceLength) {
boolean valid = true;
for (int pos = 0; pos < currentSequenceLength; ++pos) {
if (this.currentSequence[pos] != validSequence[pos]) {
valid = false;
break;
}
}
if (valid) {
return currentSequenceLength == validSequence.length;
}
}
}
throw NotFoundException.getNotFoundInstance();
}
private void findNextPair(BitArray row, List<ExpandedPair> previousPairs, int forcedOffset) private void findNextPair(BitArray row, List<ExpandedPair> previousPairs, int forcedOffset)
throws NotFoundException { throws NotFoundException {
int[] counters = this.getDecodeFinderCounters(); int[] counters = this.getDecodeFinderCounters();
@ -288,6 +414,9 @@ public final class RSSExpandedReader extends AbstractRSSReader {
rowOffset = lastPair.getFinderPattern().getStartEnd()[1]; rowOffset = lastPair.getFinderPattern().getStartEnd()[1];
} }
boolean searchingEvenPair = previousPairs.size() % 2 != 0; boolean searchingEvenPair = previousPairs.size() % 2 != 0;
if (startFromEven) {
searchingEvenPair = !searchingEvenPair;
}
boolean isWhite = false; boolean isWhite = false;
while (rowOffset < width) { while (rowOffset < width) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

View file

@ -0,0 +1 @@
(01)90012345678908(3103)012233(15)991231

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

View file

@ -0,0 +1 @@
(01)90012345678908(3203)010000

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

View file

@ -0,0 +1 @@
(01)90012345678908(3203)032767

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

View file

@ -0,0 +1 @@
(01)90012345678908(3922)795

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

View file

@ -0,0 +1 @@
(01)90012345678908(3922)795888888888888888888888888888888888888888888888888888

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

View file

@ -0,0 +1 @@
(01)90012345678908(3932)0401234

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

View file

@ -0,0 +1 @@
(01)90012345678908(3932)040EUR

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

View file

@ -0,0 +1 @@
(01)90012345678908(3932)04055GBP

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

View file

@ -0,0 +1 @@
(01)90012345678908(3932)04066USD778899

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

View file

@ -0,0 +1 @@
(01)00012345678905(10)ABC123

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

View file

@ -0,0 +1 @@
(01)12345678901231(10)UNIVERSITY-OF-DEUSTO

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

View file

@ -0,0 +1 @@
(01)91234567980129(3103)012233(15)991231

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

View file

@ -0,0 +1 @@
(01)12345678901231(10)PIRAMIDE-PROJECT

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

View file

@ -0,0 +1 @@
(01)12345678901231(10)TREELOGIC

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

View file

@ -0,0 +1 @@
(01)98898765432106(15)991231(3103)001750(10)12A(422)123(21)123456(423)012345678901

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

View file

@ -0,0 +1 @@
(15)991231(3103)001750(10)12A(422)123(21)123456(423)0123456789012

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)5678(11)010101

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)5678(11)001010

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

View file

@ -0,0 +1 @@
(10)567890(11)010101

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

View file

@ -0,0 +1 @@
(10)567(11)010101

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)1098-1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)1098,1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

View file

@ -0,0 +1 @@
(01)91234567980129(3102)012233(15)991231

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

View file

@ -0,0 +1 @@
(10)1098/1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)1098.1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

View file

@ -0,0 +1 @@
(10)1098*1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

View file

@ -0,0 +1 @@
(10)1098a1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)1098!1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)1098"1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)1098%1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

View file

@ -0,0 +1 @@
(10)1098&1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

View file

@ -0,0 +1 @@
(10)1098'1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)1098+1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

View file

@ -0,0 +1 @@
(01)91234567980129(3102)012233(15)000101

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)1098:1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

View file

@ -0,0 +1 @@
(10)1098;1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)1098<1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

View file

@ -0,0 +1 @@
(10)1098=1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

View file

@ -0,0 +1 @@
(10)1098>1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

View file

@ -0,0 +1 @@
(10)1098?1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

View file

@ -0,0 +1 @@
(10)1098_1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

View file

@ -0,0 +1 @@
(10)1098 1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)123456A

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)123456A1

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

View file

@ -0,0 +1 @@
(01)90012345678908(3103)001750

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

View file

@ -0,0 +1 @@
(10)123456A123

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1 @@
(10)123456A1234

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Some files were not shown because too many files have changed in this diff Show more