diff --git a/src/oned/rss/abstract_rss_reader.rs b/src/oned/rss/abstract_rss_reader.rs index 7a0727a..c684c24 100644 --- a/src/oned/rss/abstract_rss_reader.rs +++ b/src/oned/rss/abstract_rss_reader.rs @@ -46,10 +46,7 @@ pub trait AbstractRSSReaderTrait: OneDReader { // evenCounts = new int[dataCharacterCounters.length / 2]; // } - fn parseFinderValue( - counters: &[u32], - finderPatterns: &[[u32; 4]; 9], - ) -> Result { + fn parseFinderValue(counters: &[u32], finderPatterns: &[[u32; 4]]) -> Result { for value in 0..finderPatterns.len() { // for (int value = 0; value < finderPatterns.length; value++) { if one_d_reader::patternMatchVariance( diff --git a/src/oned/rss/data_character.rs b/src/oned/rss/data_character.rs index 95a188f..01ff559 100644 --- a/src/oned/rss/data_character.rs +++ b/src/oned/rss/data_character.rs @@ -25,7 +25,7 @@ pub(crate) trait DataCharacterTrait: Display + Eq + PartialEq + Hash { /** * Encapsulates a since character value in an RSS barcode, including its checksum information. */ -#[derive(Hash, PartialEq, Eq, Debug)] +#[derive(Hash, PartialEq, Eq, Debug, Copy, Clone)] pub struct DataCharacter { value: u32, checksumPortion: u32, diff --git a/src/oned/rss/expanded/RSSExpandedInternalTestCase.java b/src/oned/rss/expanded/RSSExpandedInternalTestCase.java deleted file mode 100644 index 34ccdc3..0000000 --- a/src/oned/rss/expanded/RSSExpandedInternalTestCase.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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. - */ - -/* - * These authors would like to acknowledge the Spanish Ministry of Industry, - * Tourism and Trade, for the support in the project TSI020301-2008-2 - * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled - * Mobile Dynamic Environments", led by Treelogic - * ( http://www.treelogic.com/ ): - * - * http://www.piramidepse.com/ - */ - -package com.google.zxing.oned.rss.expanded; - -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import javax.imageio.ImageIO; - -import com.google.zxing.BinaryBitmap; -import com.google.zxing.BufferedImageLuminanceSource; -import com.google.zxing.NotFoundException; -import com.google.zxing.common.AbstractBlackBoxTestCase; -import com.google.zxing.common.BitArray; -import com.google.zxing.common.GlobalHistogramBinarizer; -import com.google.zxing.oned.rss.DataCharacter; -import com.google.zxing.oned.rss.FinderPattern; - -import org.junit.Assert; -import org.junit.Test; - -/** - * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es) - * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es) - */ -public final class RSSExpandedInternalTestCase extends Assert { - - @Test - public void testFindFinderPatterns() throws Exception { - BufferedImage image = readImage("2.png"); - BinaryBitmap binaryMap = new BinaryBitmap(new GlobalHistogramBinarizer(new BufferedImageLuminanceSource(image))); - int rowNumber = binaryMap.getHeight() / 2; - BitArray row = binaryMap.getBlackRow(rowNumber, null); - List previousPairs = new ArrayList<>(); - - RSSExpandedReader rssExpandedReader = new RSSExpandedReader(); - ExpandedPair pair1 = rssExpandedReader.retrieveNextPair(row, previousPairs, rowNumber); - previousPairs.add(pair1); - FinderPattern finderPattern = pair1.getFinderPattern(); - assertNotNull(finderPattern); - assertEquals(0, finderPattern.getValue()); - - ExpandedPair pair2 = rssExpandedReader.retrieveNextPair(row, previousPairs, rowNumber); - previousPairs.add(pair2); - finderPattern = pair2.getFinderPattern(); - assertNotNull(finderPattern); - assertEquals(1, finderPattern.getValue()); - - ExpandedPair pair3 = rssExpandedReader.retrieveNextPair(row, previousPairs, rowNumber); - previousPairs.add(pair3); - finderPattern = pair3.getFinderPattern(); - assertNotNull(finderPattern); - assertEquals(1, finderPattern.getValue()); - - try { - rssExpandedReader.retrieveNextPair(row, previousPairs, rowNumber); - // the previous was the last pair - fail(NotFoundException.class.getName() + " expected"); - } catch (NotFoundException nfe) { - // ok - } - } - - @Test - public void testRetrieveNextPairPatterns() throws Exception { - BufferedImage image = readImage("3.png"); - BinaryBitmap binaryMap = new BinaryBitmap(new GlobalHistogramBinarizer(new BufferedImageLuminanceSource(image))); - int rowNumber = binaryMap.getHeight() / 2; - BitArray row = binaryMap.getBlackRow(rowNumber, null); - List previousPairs = new ArrayList<>(); - - RSSExpandedReader rssExpandedReader = new RSSExpandedReader(); - ExpandedPair pair1 = rssExpandedReader.retrieveNextPair(row, previousPairs, rowNumber); - previousPairs.add(pair1); - FinderPattern finderPattern = pair1.getFinderPattern(); - assertNotNull(finderPattern); - assertEquals(0, finderPattern.getValue()); - - ExpandedPair pair2 = rssExpandedReader.retrieveNextPair(row, previousPairs, rowNumber); - previousPairs.add(pair2); - finderPattern = pair2.getFinderPattern(); - assertNotNull(finderPattern); - assertEquals(0, finderPattern.getValue()); - } - - @Test - public void testDecodeCheckCharacter() throws Exception { - BufferedImage image = readImage("3.png"); - BinaryBitmap binaryMap = new BinaryBitmap(new GlobalHistogramBinarizer(new BufferedImageLuminanceSource(image))); - BitArray row = binaryMap.getBlackRow(binaryMap.getHeight() / 2, null); - - int[] startEnd = {145, 243}; //image pixels where the A1 pattern starts (at 124) and ends (at 214) - int value = 0; // A - FinderPattern finderPatternA1 = new FinderPattern(value, startEnd, startEnd[0], startEnd[1], image.getHeight() / 2); - //{1, 8, 4, 1, 1}; - RSSExpandedReader rssExpandedReader = new RSSExpandedReader(); - DataCharacter dataCharacter = rssExpandedReader.decodeDataCharacter(row, finderPatternA1, true, true); - - assertEquals(98, dataCharacter.getValue()); - } - - @Test - public void testDecodeDataCharacter() throws Exception { - BufferedImage image = readImage("3.png"); - BinaryBitmap binaryMap = new BinaryBitmap(new GlobalHistogramBinarizer(new BufferedImageLuminanceSource(image))); - BitArray row = binaryMap.getBlackRow(binaryMap.getHeight() / 2, null); - - int[] startEnd = {145, 243}; //image pixels where the A1 pattern starts (at 124) and ends (at 214) - int value = 0; // A - FinderPattern finderPatternA1 = new FinderPattern(value, startEnd, startEnd[0], startEnd[1], image.getHeight() / 2); - //{1, 8, 4, 1, 1}; - RSSExpandedReader rssExpandedReader = new RSSExpandedReader(); - DataCharacter dataCharacter = rssExpandedReader.decodeDataCharacter(row, finderPatternA1, true, false); - - assertEquals(19, dataCharacter.getValue()); - assertEquals(1007, dataCharacter.getChecksumPortion()); - } - - private static BufferedImage readImage(String fileName) throws IOException { - Path path = AbstractBlackBoxTestCase.buildTestBase("src/test/resources/blackbox/rssexpanded-1/").resolve(fileName); - return ImageIO.read(path.toFile()); - } - -} diff --git a/src/oned/rss/expanded/RSSExpandedReader.java b/src/oned/rss/expanded/RSSExpandedReader.java deleted file mode 100644 index 44bdcfc..0000000 --- a/src/oned/rss/expanded/RSSExpandedReader.java +++ /dev/null @@ -1,768 +0,0 @@ -/* - * 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. - */ - -/* - * These authors would like to acknowledge the Spanish Ministry of Industry, - * Tourism and Trade, for the support in the project TSI020301-2008-2 - * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled - * Mobile Dynamic Environments", led by Treelogic - * ( http://www.treelogic.com/ ): - * - * http://www.piramidepse.com/ - */ - -package com.google.zxing.oned.rss.expanded; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.DecodeHintType; -import com.google.zxing.FormatException; -import com.google.zxing.NotFoundException; -import com.google.zxing.RXingResult; -import com.google.zxing.RXingResultMetadataType; -import com.google.zxing.RXingResultPoint; -import com.google.zxing.common.BitArray; -import com.google.zxing.common.detector.MathUtils; -import com.google.zxing.oned.rss.AbstractRSSReader; -import com.google.zxing.oned.rss.DataCharacter; -import com.google.zxing.oned.rss.FinderPattern; -import com.google.zxing.oned.rss.RSSUtils; -import com.google.zxing.oned.rss.expanded.decoders.AbstractExpandedDecoder; - -import java.util.Arrays; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Collections; - -/** - * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es) - * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es) - */ -public final class RSSExpandedReader extends AbstractRSSReader { - - private static final int[] SYMBOL_WIDEST = {7, 5, 4, 3, 1}; - private static final int[] EVEN_TOTAL_SUBSET = {4, 20, 52, 104, 204}; - private static final int[] GSUM = {0, 348, 1388, 2948, 3988}; - - private static final int[][] FINDER_PATTERNS = { - {1,8,4,1}, // A - {3,6,4,1}, // B - {3,4,6,1}, // C - {3,2,8,1}, // D - {2,6,5,1}, // E - {2,2,9,1} // F - }; - - private static final int[][] WEIGHTS = { - { 1, 3, 9, 27, 81, 32, 96, 77}, - { 20, 60, 180, 118, 143, 7, 21, 63}, - {189, 145, 13, 39, 117, 140, 209, 205}, - {193, 157, 49, 147, 19, 57, 171, 91}, - { 62, 186, 136, 197, 169, 85, 44, 132}, - {185, 133, 188, 142, 4, 12, 36, 108}, - {113, 128, 173, 97, 80, 29, 87, 50}, - {150, 28, 84, 41, 123, 158, 52, 156}, - { 46, 138, 203, 187, 139, 206, 196, 166}, - { 76, 17, 51, 153, 37, 111, 122, 155}, - { 43, 129, 176, 106, 107, 110, 119, 146}, - { 16, 48, 144, 10, 30, 90, 59, 177}, - {109, 116, 137, 200, 178, 112, 125, 164}, - { 70, 210, 208, 202, 184, 130, 179, 115}, - {134, 191, 151, 31, 93, 68, 204, 190}, - {148, 22, 66, 198, 172, 94, 71, 2}, - { 6, 18, 54, 162, 64, 192,154, 40}, - {120, 149, 25, 75, 14, 42,126, 167}, - { 79, 26, 78, 23, 69, 207,199, 175}, - {103, 98, 83, 38, 114, 131, 182, 124}, - {161, 61, 183, 127, 170, 88, 53, 159}, - { 55, 165, 73, 8, 24, 72, 5, 15}, - { 45, 135, 194, 160, 58, 174, 100, 89} - }; - - private static final int FINDER_PAT_A = 0; - private static final int FINDER_PAT_B = 1; - private static final int FINDER_PAT_C = 2; - private static final int FINDER_PAT_D = 3; - private static final int FINDER_PAT_E = 4; - private static final int FINDER_PAT_F = 5; - - @SuppressWarnings("checkstyle:lineLength") - private static final int[][] FINDER_PATTERN_SEQUENCES = { - { FINDER_PAT_A, FINDER_PAT_A }, - { FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B }, - { FINDER_PAT_A, FINDER_PAT_C, FINDER_PAT_B, FINDER_PAT_D }, - { FINDER_PAT_A, FINDER_PAT_E, FINDER_PAT_B, FINDER_PAT_D, FINDER_PAT_C }, - { FINDER_PAT_A, FINDER_PAT_E, FINDER_PAT_B, FINDER_PAT_D, FINDER_PAT_D, FINDER_PAT_F }, - { FINDER_PAT_A, FINDER_PAT_E, FINDER_PAT_B, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_F, FINDER_PAT_F }, - { FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_D }, - { FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_E }, - { FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_F, FINDER_PAT_F }, - { FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_E, FINDER_PAT_F, FINDER_PAT_F }, - }; - - private static final int MAX_PAIRS = 11; - - private final List pairs = new ArrayList<>(MAX_PAIRS); - private final List rows = new ArrayList<>(); - private final int [] startEnd = new int[2]; - private boolean startFromEven; - - @Override - public RXingResult decodeRow(int rowNumber, - BitArray row, - Map hints) throws NotFoundException, FormatException { - // Rows can start with even pattern in case in prev rows there where odd number of patters. - // So lets try twice - this.pairs.clear(); - this.startFromEven = false; - try { - return constructRXingResult(decodeRow2pairs(rowNumber, row)); - } catch (NotFoundException e) { - // OK - } - - this.pairs.clear(); - this.startFromEven = true; - return constructRXingResult(decodeRow2pairs(rowNumber, row)); - } - - @Override - public void reset() { - this.pairs.clear(); - this.rows.clear(); - } - - // Not private for testing - List decodeRow2pairs(int rowNumber, BitArray row) throws NotFoundException { - boolean done = false; - while (!done) { - try { - this.pairs.add(retrieveNextPair(row, this.pairs, rowNumber)); - } catch (NotFoundException nfe) { - if (this.pairs.isEmpty()) { - throw nfe; - } - // exit this loop when retrieveNextPair() fails and throws - done = true; - } - } - - // TODO: verify sequence of finder patterns as in checkPairSequence() - if (checkChecksum()) { - return this.pairs; - } - - boolean tryStackedDecode = !this.rows.isEmpty(); - storeRow(rowNumber); // TODO: deal with reversed rows - if (tryStackedDecode) { - // When the image is 180-rotated, then rows are sorted in wrong direction. - // Try twice with both the directions. - List ps = checkRows(false); - if (ps != null) { - return ps; - } - ps = checkRows(true); - if (ps != null) { - return ps; - } - } - - throw NotFoundException.getNotFoundInstance(); - } - - private List checkRows(boolean reverse) { - // Limit number of rows we are checking - // We use recursive algorithm with pure complexity and don't want it to take forever - // Stacked barcode can have up to 11 rows, so 25 seems reasonable enough - if (this.rows.size() > 25) { - this.rows.clear(); // We will never have a chance to get result, so clear it - return null; - } - - this.pairs.clear(); - if (reverse) { - Collections.reverse(this.rows); - } - - List ps = null; - try { - ps = checkRows(new ArrayList<>(), 0); - } catch (NotFoundException e) { - // OK - } - - if (reverse) { - Collections.reverse(this.rows); - } - - return ps; - } - - // Try to construct a valid rows sequence - // Recursion is used to implement backtracking - private List checkRows(List collectedRows, int currentRow) throws NotFoundException { - for (int i = currentRow; i < rows.size(); i++) { - ExpandedRow row = rows.get(i); - this.pairs.clear(); - for (ExpandedRow collectedRow : collectedRows) { - this.pairs.addAll(collectedRow.getPairs()); - } - this.pairs.addAll(row.getPairs()); - - if (isValidSequence(this.pairs)) { - if (checkChecksum()) { - return this.pairs; - } - - List rs = new ArrayList<>(collectedRows); - rs.add(row); - try { - // Recursion: try to add more rows - return checkRows(rs, i + 1); - } catch (NotFoundException e) { - // We failed, try the next candidate - } - } - } - - throw NotFoundException.getNotFoundInstance(); - } - - // Whether the pairs form a valid find pattern sequence, - // either complete or a prefix - private static boolean isValidSequence(List pairs) { - for (int[] sequence : FINDER_PATTERN_SEQUENCES) { - if (pairs.size() <= sequence.length) { - boolean stop = true; - for (int j = 0; j < pairs.size(); j++) { - if (pairs.get(j).getFinderPattern().getValue() != sequence[j]) { - stop = false; - break; - } - } - if (stop) { - return true; - } - } - - } - - return false; - } - - private void storeRow(int rowNumber) { - // 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++; - } - 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 already detected row - if (isPartialRow(this.pairs, this.rows)) { - return; - } - - this.rows.add(insertPos, new ExpandedRow(this.pairs, rowNumber)); - - removePartialRows(this.pairs, this.rows); - } - - // Remove all the rows that contains only specified pairs - private static void removePartialRows(Collection pairs, Collection rows) { - for (Iterator iterator = rows.iterator(); iterator.hasNext();) { - ExpandedRow r = iterator.next(); - if (r.getPairs().size() != pairs.size()) { - boolean allFound = true; - for (ExpandedPair p : r.getPairs()) { - if (!pairs.contains(p)) { - allFound = false; - break; - } - } - if (allFound) { - // 'pairs' contains all the pairs from the row 'r' - iterator.remove(); - } - } - } - } - - // Returns true when one of the rows already contains all the pairs - private static boolean isPartialRow(Iterable pairs, Iterable rows) { - for (ExpandedRow r : rows) { - boolean allFound = true; - for (ExpandedPair p : pairs) { - boolean found = false; - for (ExpandedPair pp : r.getPairs()) { - if (p.equals(pp)) { - found = true; - break; - } - } - if (!found) { - allFound = false; - break; - } - } - if (allFound) { - // the row 'r' contain all the pairs from 'pairs' - return true; - } - } - return false; - } - - // Only used for unit testing - List getRows() { - return this.rows; - } - - // Not private for unit testing - static RXingResult constructRXingResult(List pairs) throws NotFoundException, FormatException { - BitArray binary = BitArrayBuilder.buildBitArray(pairs); - - AbstractExpandedDecoder decoder = AbstractExpandedDecoder.createDecoder(binary); - String resultingString = decoder.parseInformation(); - - RXingResultPoint[] firstPoints = pairs.get(0).getFinderPattern().getRXingResultPoints(); - RXingResultPoint[] lastPoints = pairs.get(pairs.size() - 1).getFinderPattern().getRXingResultPoints(); - - RXingResult result = new RXingResult( - resultingString, - null, - new RXingResultPoint[]{firstPoints[0], firstPoints[1], lastPoints[0], lastPoints[1]}, - BarcodeFormat.RSS_EXPANDED - ); - result.putMetadata(RXingResultMetadataType.SYMBOLOGY_IDENTIFIER, "]e0"); - return result; - } - - private boolean checkChecksum() { - ExpandedPair firstPair = this.pairs.get(0); - DataCharacter checkCharacter = firstPair.getLeftChar(); - DataCharacter firstCharacter = firstPair.getRightChar(); - - if (firstCharacter == null) { - return false; - } - - int checksum = firstCharacter.getChecksumPortion(); - int s = 2; - - for (int i = 1; i < this.pairs.size(); ++i) { - ExpandedPair currentPair = this.pairs.get(i); - checksum += currentPair.getLeftChar().getChecksumPortion(); - s++; - DataCharacter currentRightChar = currentPair.getRightChar(); - if (currentRightChar != null) { - checksum += currentRightChar.getChecksumPortion(); - s++; - } - } - - checksum %= 211; - - int checkCharacterValue = 211 * (s - 4) + checksum; - - return checkCharacterValue == checkCharacter.getValue(); - } - - 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; - } - - // not private for testing - ExpandedPair retrieveNextPair(BitArray row, List previousPairs, int rowNumber) - throws NotFoundException { - boolean isOddPattern = previousPairs.size() % 2 == 0; - if (startFromEven) { - isOddPattern = !isOddPattern; - } - - FinderPattern pattern; - - boolean keepFinding = true; - int forcedOffset = -1; - do { - this.findNextPair(row, previousPairs, forcedOffset); - pattern = parseFoundFinderPattern(row, rowNumber, isOddPattern); - if (pattern == null) { - forcedOffset = getNextSecondBar(row, this.startEnd[0]); - } else { - keepFinding = false; - } - } while (keepFinding); - - // 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); - - if (!previousPairs.isEmpty() && previousPairs.get(previousPairs.size() - 1).mustBeLast()) { - throw NotFoundException.getNotFoundInstance(); - } - - DataCharacter rightChar; - try { - rightChar = this.decodeDataCharacter(row, pattern, isOddPattern, false); - } catch (NotFoundException ignored) { - rightChar = null; - } - return new ExpandedPair(leftChar, rightChar, pattern); - } - - private void findNextPair(BitArray row, List previousPairs, int forcedOffset) - throws NotFoundException { - int[] counters = this.getDecodeFinderCounters(); - counters[0] = 0; - counters[1] = 0; - counters[2] = 0; - counters[3] = 0; - - int width = row.getSize(); - - int rowOffset; - if (forcedOffset >= 0) { - rowOffset = forcedOffset; - } else if (previousPairs.isEmpty()) { - rowOffset = 0; - } else { - ExpandedPair lastPair = previousPairs.get(previousPairs.size() - 1); - rowOffset = lastPair.getFinderPattern().getStartEnd()[1]; - } - boolean searchingEvenPair = previousPairs.size() % 2 != 0; - if (startFromEven) { - searchingEvenPair = !searchingEvenPair; - } - - boolean isWhite = false; - while (rowOffset < width) { - isWhite = !row.get(rowOffset); - if (!isWhite) { - 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 (searchingEvenPair) { - reverseCounters(counters); - } - - if (isFinderPattern(counters)) { - this.startEnd[0] = patternStart; - this.startEnd[1] = x; - return; - } - - if (searchingEvenPair) { - reverseCounters(counters); - } - - 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 static void reverseCounters(int [] counters) { - int length = counters.length; - for (int i = 0; i < length / 2; ++i) { - int tmp = counters[i]; - counters[i] = counters[length - i - 1]; - counters[length - i - 1] = tmp; - } - } - - private FinderPattern parseFoundFinderPattern(BitArray row, int rowNumber, boolean oddPattern) { - // Actually we found elements 2-5. - int firstCounter; - int start; - int end; - - if (oddPattern) { - // If pattern number is odd, we need to locate element 1 *before* the current block. - - int firstElementStart = this.startEnd[0] - 1; - // Locate element 1 - while (firstElementStart >= 0 && !row.get(firstElementStart)) { - firstElementStart--; - } - - firstElementStart++; - firstCounter = this.startEnd[0] - firstElementStart; - start = firstElementStart; - end = this.startEnd[1]; - - } else { - // If pattern number is even, the pattern is reversed, so we need to locate element 1 *after* the current block. - - start = this.startEnd[0]; - - end = row.getNextUnset(this.startEnd[1] + 1); - firstCounter = end - this.startEnd[1]; - } - - // Make 'counters' hold 1-4 - int [] counters = this.getDecodeFinderCounters(); - System.arraycopy(counters, 0, counters, 1, counters.length - 1); - - counters[0] = firstCounter; - int value; - try { - value = parseFinderValue(counters, FINDER_PATTERNS); - } catch (NotFoundException ignored) { - return null; - } - return new FinderPattern(value, new int[] {start, end}, start, end, rowNumber); - } - - DataCharacter decodeDataCharacter(BitArray row, - FinderPattern pattern, - boolean isOddPattern, - boolean leftChar) throws NotFoundException { - int[] counters = this.getDataCharacterCounters(); - Arrays.fill(counters, 0); - - if (leftChar) { - recordPatternInReverse(row, pattern.getStartEnd()[0], counters); - } else { - recordPattern(row, pattern.getStartEnd()[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; - } - } //counters[] has the pixels of the module - - int numModules = 17; //left and right data characters have all the same length - float elementWidth = MathUtils.sum(counters) / (float) numModules; - - // Sanity check: element width for pattern and the character should match - float expectedElementWidth = (pattern.getStartEnd()[1] - pattern.getStartEnd()[0]) / 15.0f; - if (Math.abs(elementWidth - expectedElementWidth) / expectedElementWidth > 0.3f) { - throw NotFoundException.getNotFoundInstance(); - } - - 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 = 1.0f * counters[i] / elementWidth; - int count = (int) (value + 0.5f); // Round - if (count < 1) { - if (value < 0.3f) { - throw NotFoundException.getNotFoundInstance(); - } - count = 1; - } else if (count > 8) { - if (value > 8.7f) { - throw NotFoundException.getNotFoundInstance(); - } - 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(numModules); - - int weightRowNumber = 4 * pattern.getValue() + (isOddPattern ? 0 : 2) + (leftChar ? 0 : 1) - 1; - - int oddSum = 0; - int oddChecksumPortion = 0; - for (int i = oddCounts.length - 1; i >= 0; i--) { - if (isNotA1left(pattern, isOddPattern, leftChar)) { - int weight = WEIGHTS[weightRowNumber][2 * i]; - oddChecksumPortion += oddCounts[i] * weight; - } - oddSum += oddCounts[i]; - } - int evenChecksumPortion = 0; - for (int i = evenCounts.length - 1; i >= 0; i--) { - if (isNotA1left(pattern, isOddPattern, leftChar)) { - int weight = WEIGHTS[weightRowNumber][2 * i + 1]; - evenChecksumPortion += evenCounts[i] * weight; - } - } - int checksumPortion = oddChecksumPortion + evenChecksumPortion; - - if ((oddSum & 0x01) != 0 || oddSum > 13 || oddSum < 4) { - throw NotFoundException.getNotFoundInstance(); - } - - int group = (13 - oddSum) / 2; - int oddWidest = SYMBOL_WIDEST[group]; - int evenWidest = 9 - oddWidest; - int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, true); - int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, false); - int tEven = EVEN_TOTAL_SUBSET[group]; - int gSum = GSUM[group]; - int value = vOdd * tEven + vEven + gSum; - - return new DataCharacter(value, checksumPortion); - } - - private static boolean isNotA1left(FinderPattern pattern, boolean isOddPattern, boolean leftChar) { - // A1: pattern.getValue is 0 (A), and it's an oddPattern, and it is a left char - return !(pattern.getValue() == 0 && isOddPattern && leftChar); - } - - private void adjustOddEvenCounts(int numModules) throws NotFoundException { - - int oddSum = MathUtils.sum(this.getOddCounts()); - int evenSum = MathUtils.sum(this.getEvenCounts()); - - boolean incrementOdd = false; - boolean decrementOdd = false; - - if (oddSum > 13) { - decrementOdd = true; - } else if (oddSum < 4) { - incrementOdd = true; - } - boolean incrementEven = false; - boolean decrementEven = false; - if (evenSum > 13) { - decrementEven = true; - } else if (evenSum < 4) { - incrementEven = true; - } - - int mismatch = oddSum + evenSum - numModules; - boolean oddParityBad = (oddSum & 0x01) == 1; - boolean evenParityBad = (evenSum & 0x01) == 0; - switch (mismatch) { - case 1: - if (oddParityBad) { - if (evenParityBad) { - throw NotFoundException.getNotFoundInstance(); - } - decrementOdd = true; - } else { - if (!evenParityBad) { - throw NotFoundException.getNotFoundInstance(); - } - decrementEven = true; - } - break; - case -1: - if (oddParityBad) { - if (evenParityBad) { - throw NotFoundException.getNotFoundInstance(); - } - incrementOdd = true; - } else { - if (!evenParityBad) { - throw NotFoundException.getNotFoundInstance(); - } - incrementEven = true; - } - break; - case 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! - } - break; - default: - throw NotFoundException.getNotFoundInstance(); - } - - if (incrementOdd) { - if (decrementOdd) { - throw NotFoundException.getNotFoundInstance(); - } - increment(this.getOddCounts(), this.getOddRoundingErrors()); - } - if (decrementOdd) { - decrement(this.getOddCounts(), this.getOddRoundingErrors()); - } - if (incrementEven) { - if (decrementEven) { - throw NotFoundException.getNotFoundInstance(); - } - increment(this.getEvenCounts(), this.getOddRoundingErrors()); - } - if (decrementEven) { - decrement(this.getEvenCounts(), this.getEvenRoundingErrors()); - } - } -} diff --git a/src/oned/rss/expanded/expanded_pair.rs b/src/oned/rss/expanded/expanded_pair.rs index 1d51288..61f53b5 100644 --- a/src/oned/rss/expanded/expanded_pair.rs +++ b/src/oned/rss/expanded/expanded_pair.rs @@ -31,7 +31,7 @@ use crate::oned::rss::{DataCharacter, FinderPattern}; /** * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es) */ -#[derive(PartialEq, Eq, Hash)] +#[derive(PartialEq, Eq, Hash, Clone)] pub struct ExpandedPair { leftChar: Option, rightChar: Option, diff --git a/src/oned/rss/expanded/expanded_row.rs b/src/oned/rss/expanded/expanded_row.rs index ab6684e..902d62c 100644 --- a/src/oned/rss/expanded/expanded_row.rs +++ b/src/oned/rss/expanded/expanded_row.rs @@ -21,7 +21,7 @@ use super::ExpandedPair; /** * One row of an RSS Expanded Stacked symbol, consisting of 1+ expanded pairs. */ -#[derive(Hash)] +#[derive(Hash, Clone)] pub struct ExpandedRow { pairs: Vec, rowNumber: u32, diff --git a/src/oned/rss/expanded/mod.rs b/src/oned/rss/expanded/mod.rs index a002378..0103c32 100644 --- a/src/oned/rss/expanded/mod.rs +++ b/src/oned/rss/expanded/mod.rs @@ -11,3 +11,12 @@ mod expanded_information_decoder_test; mod expanded_row; pub use expanded_row::*; + +mod rss_expanded_reader; +pub use rss_expanded_reader::*; + +#[cfg(test)] +mod rss_expanded_internal_test_case; + +#[cfg(test)] +mod rss_expanded_image_2_binary_test_tase; \ No newline at end of file diff --git a/src/oned/rss/expanded/RSSExpandedImage2binaryTestCase.java b/src/oned/rss/expanded/rss_expanded_image_2_binary_test_tase.rs similarity index 63% rename from src/oned/rss/expanded/RSSExpandedImage2binaryTestCase.java rename to src/oned/rss/expanded/rss_expanded_image_2_binary_test_tase.rs index 6436123..5c594ee 100644 --- a/src/oned/rss/expanded/RSSExpandedImage2binaryTestCase.java +++ b/src/oned/rss/expanded/rss_expanded_image_2_binary_test_tase.rs @@ -24,175 +24,158 @@ * http://www.piramidepse.com/ */ -package com.google.zxing.oned.rss.expanded; - -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; - -import javax.imageio.ImageIO; - -import com.google.zxing.BinaryBitmap; -import com.google.zxing.BufferedImageLuminanceSource; -import com.google.zxing.NotFoundException; -import com.google.zxing.ReaderException; -import com.google.zxing.common.AbstractBlackBoxTestCase; -import com.google.zxing.common.BitArray; -import com.google.zxing.common.GlobalHistogramBinarizer; -import org.junit.Assert; -import org.junit.Test; +use std::rc::Rc; /** * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es) * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es) */ -public final class RSSExpandedImage2binaryTestCase extends Assert { - @Test - public void testDecodeRow2binary1() throws Exception { +use crate::{common::{BitArray, GlobalHistogramBinarizer}, BinaryBitmap, BufferedImageLuminanceSource, oned::rss::expanded::RSSExpandedReader}; + +use super::bit_array_builder; + + #[test] + fn testDecodeRow2binary1() { // (11)100224(17)110224(3102)000100 assertCorrectImage2binary("1.png", " ...X...X .X....X. .XX...X. X..X...X ...XX.X. ..X.X... ..X.X..X ...X..X. X.X....X .X....X. .....X.. X...X..."); } - @Test - public void testDecodeRow2binary2() throws Exception { + #[test] + fn testDecodeRow2binary2() { // (01)90012345678908(3103)001750 assertCorrectImage2binary("2.png", " ..X..... ......X. .XXX.X.X .X...XX. XXXXX.XX XX.X.... .XX.XX.X .XX."); } - @Test - public void testDecodeRow2binary3() throws Exception { + #[test] + fn testDecodeRow2binary3() { // (10)12A assertCorrectImage2binary("3.png", " .......X ..XX..X. X.X....X .......X ...."); } - @Test - public void testDecodeRow2binary4() throws Exception { + #[test] + fn testDecodeRow2binary4() { // (01)98898765432106(3202)012345(15)991231 assertCorrectImage2binary( "4.png", " ..XXXX.X XX.XXXX. .XXX.XX. XX..X... .XXXXX.. XX.X..X. ..XX..XX XX.X.XXX X..XX..X .X.XXXXX XXXX"); } - @Test - public void testDecodeRow2binary5() throws Exception { + #[test] + fn testDecodeRow2binary5() { // (01)90614141000015(3202)000150 assertCorrectImage2binary( "5.png", " ..X.X... .XXXX.X. XX..XXXX ....XX.. X....... ....X... ....X..X .XX."); } - @SuppressWarnings("checkstyle:lineLength") - @Test - public void testDecodeRow2binary10() throws Exception { + #[test] + fn testDecodeRow2binary10() { // (01)98898765432106(15)991231(3103)001750(10)12A(422)123(21)123456(423)0123456789012 assertCorrectImage2binary("10.png", " .X.XX..X XX.XXXX. .XXX.XX. XX..X... .XXXXX.. XX.X..X. ..XX...X XX.X.... X.X.X.X. X.X..X.X .X....X. XX...X.. ...XX.X. .XXXXXX. .X..XX.. X.X.X... .X...... XXXX.... XX.XX... XXXXX.X. ...XXXXX .....X.X ...X.... X.XXX..X X.X.X... XX.XX..X .X..X..X .X.X.X.X X.XX...X .XX.XXX. XXX.X.XX ..X."); } - @SuppressWarnings("checkstyle:lineLength") - @Test - public void testDecodeRow2binary11() throws Exception { + #[test] + fn testDecodeRow2binary11() { // (01)98898765432106(15)991231(3103)001750(10)12A(422)123(21)123456 assertCorrectImage2binary("11.png", " .X.XX..X XX.XXXX. .XXX.XX. XX..X... .XXXXX.. XX.X..X. ..XX...X XX.X.... X.X.X.X. X.X..X.X .X....X. XX...X.. ...XX.X. .XXXXXX. .X..XX.. X.X.X... .X...... XXXX.... XX.XX... XXXXX.X. ...XXXXX .....X.X ...X.... X.XXX..X X.X.X... ...."); } - @Test - public void testDecodeRow2binary12() throws Exception { + #[test] + fn testDecodeRow2binary12() { // (01)98898765432106(3103)001750 assertCorrectImage2binary( "12.png", " ..X..XX. XXXX..XX X.XX.XX. .X....XX XXX..XX. X..X.... .XX.XX.X .XX."); } - @Test - public void testDecodeRow2binary13() throws Exception { + #[test] + fn testDecodeRow2binary13() { // (01)90012345678908(3922)795 assertCorrectImage2binary( "13.png", " ..XX..X. ........ .X..XXX. X.X.X... XX.XXXXX .XXXX.X. X.X.XXXX .X..X..X ......X."); } - @Test - public void testDecodeRow2binary14() throws Exception { + #[test] + fn testDecodeRow2binary14() { // (01)90012345678908(3932)0401234 assertCorrectImage2binary( "14.png", " ..XX.X.. ........ .X..XXX. X.X.X... XX.XXXXX .XXXX.X. X.....X. X.....X. X.X.X.XX .X...... X..."); } - @Test - public void testDecodeRow2binary15() throws Exception { + #[test] + fn testDecodeRow2binary15() { // (01)90012345678908(3102)001750(11)100312 assertCorrectImage2binary( "15.png", " ..XXX... ........ .X..XXX. X.X.X... XX.XXXXX .XXXX.X. ..XX...X .X.....X .XX..... XXXX.X.. XX.."); } - @Test - public void testDecodeRow2binary16() throws Exception { + #[test] + fn testDecodeRow2binary16() { // (01)90012345678908(3202)001750(11)100312 assertCorrectImage2binary( "16.png", " ..XXX..X ........ .X..XXX. X.X.X... XX.XXXXX .XXXX.X. ..XX...X .X.....X .XX..... XXXX.X.. XX.."); } - @Test - public void testDecodeRow2binary17() throws Exception { + #[test] + fn testDecodeRow2binary17() { // (01)90012345678908(3102)001750(13)100312 assertCorrectImage2binary( "17.png", " ..XXX.X. ........ .X..XXX. X.X.X... XX.XXXXX .XXXX.X. ..XX...X .X.....X .XX..... XXXX.X.. XX.."); } - @Test - public void testDecodeRow2binary18() throws Exception { + #[test] + fn testDecodeRow2binary18() { // (01)90012345678908(3202)001750(13)100312 assertCorrectImage2binary( "18.png", " ..XXX.XX ........ .X..XXX. X.X.X... XX.XXXXX .XXXX.X. ..XX...X .X.....X .XX..... XXXX.X.. XX.."); } - @Test - public void testDecodeRow2binary19() throws Exception { + #[test] + fn testDecodeRow2binary19() { // (01)90012345678908(3102)001750(15)100312 assertCorrectImage2binary( "19.png", " ..XXXX.. ........ .X..XXX. X.X.X... XX.XXXXX .XXXX.X. ..XX...X .X.....X .XX..... XXXX.X.. XX.."); } - @Test - public void testDecodeRow2binary20() throws Exception { + #[test] + fn testDecodeRow2binary20() { // (01)90012345678908(3202)001750(15)100312 assertCorrectImage2binary( "20.png", " ..XXXX.X ........ .X..XXX. X.X.X... XX.XXXXX .XXXX.X. ..XX...X .X.....X .XX..... XXXX.X.. XX.."); } - @Test - public void testDecodeRow2binary21() throws Exception { + #[test] + fn testDecodeRow2binary21() { // (01)90012345678908(3102)001750(17)100312 assertCorrectImage2binary( "21.png", " ..XXXXX. ........ .X..XXX. X.X.X... XX.XXXXX .XXXX.X. ..XX...X .X.....X .XX..... XXXX.X.. XX.."); } - @Test - public void testDecodeRow2binary22() throws Exception { + #[test] + fn testDecodeRow2binary22() { // (01)90012345678908(3202)001750(17)100312 assertCorrectImage2binary( "22.png", " ..XXXXXX ........ .X..XXX. X.X.X... XX.XXXXX .XXXX.X. ..XX...X .X.....X .XX..... XXXX.X.. XX.."); } - private static void assertCorrectImage2binary(String fileName, String expected) - throws IOException, NotFoundException { - Path path = AbstractBlackBoxTestCase.buildTestBase("src/test/resources/blackbox/rssexpanded-1/").resolve(fileName); + fn assertCorrectImage2binary( fileName:&str, expected:&str) + { + let path = format!("test_resources/blackbox/rssexpanded-1/{}",fileName); - BufferedImage image = ImageIO.read(path.toFile()); - BinaryBitmap binaryMap = new BinaryBitmap(new GlobalHistogramBinarizer(new BufferedImageLuminanceSource(image))); - int rowNumber = binaryMap.getHeight() / 2; - BitArray row = binaryMap.getBlackRow(rowNumber, null); + let image = image::open(path).expect("file exists"); + let binaryMap = BinaryBitmap::new(Rc::new( GlobalHistogramBinarizer::new(Box::new(BufferedImageLuminanceSource::new(image))))); + let rowNumber = binaryMap.getHeight() / 2; + let row = binaryMap.getBlackRow(rowNumber, &mut BitArray::new()).expect("row"); - List pairs; - try { - RSSExpandedReader rssExpandedReader = new RSSExpandedReader(); - pairs = rssExpandedReader.decodeRow2pairs(rowNumber, row); - } catch (ReaderException re) { - fail(re.toString()); - return; - } - BitArray binary = BitArrayBuilder.buildBitArray(pairs); - assertEquals(expected, binary.toString()); + // let pairs = Vec::new(); + // try { + let mut rssExpandedReader = RSSExpandedReader::new(); + let pairs = rssExpandedReader.decodeRow2pairs(rowNumber as u32, &row).expect("decode"); + // } catch (ReaderException re) { + // fail(re.toString()); + // return; + // } + let binary = bit_array_builder::buildBitArray(&pairs); + assert_eq!(expected, binary.to_string()); } -} diff --git a/src/oned/rss/expanded/rss_expanded_internal_test_case.rs b/src/oned/rss/expanded/rss_expanded_internal_test_case.rs new file mode 100644 index 0000000..cf16149 --- /dev/null +++ b/src/oned/rss/expanded/rss_expanded_internal_test_case.rs @@ -0,0 +1,185 @@ +/* + * 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. + */ + +/* + * These authors would like to acknowledge the Spanish Ministry of Industry, + * Tourism and Trade, for the support in the project TSI020301-2008-2 + * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled + * Mobile Dynamic Environments", led by Treelogic + * ( http://www.treelogic.com/ ): + * + * http://www.piramidepse.com/ + */ + +use std::rc::Rc; + +use crate::{ + common::{BitArray, GlobalHistogramBinarizer}, + oned::rss::{DataCharacterTrait, FinderPattern}, + BinaryBitmap, BufferedImageLuminanceSource, +}; + +use super::RSSExpandedReader; + +/** + * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es) + * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es) + */ + +#[test] +fn testFindFinderPatterns() { + let image = readImage("2.png"); + let binaryMap = BinaryBitmap::new(Rc::new(GlobalHistogramBinarizer::new(Box::new( + BufferedImageLuminanceSource::new(image), + )))); + let rowNumber = binaryMap.getHeight() as u32 / 2; + let row = binaryMap + .getBlackRow(rowNumber as usize, &mut BitArray::new()) + .expect("ok"); + let mut previousPairs = Vec::new(); //new ArrayList<>(); + + let mut rssExpandedReader = RSSExpandedReader::new(); + let pair1 = rssExpandedReader + .retrieveNextPair(&row, &previousPairs, rowNumber) + .expect("finder"); + previousPairs.push(pair1.clone()); + let mut finderPattern = pair1.getFinderPattern().as_ref().unwrap(); + // assertNotNull(finderPattern); + assert_eq!(0, finderPattern.getValue()); + + let pair2 = rssExpandedReader + .retrieveNextPair(&row, &previousPairs, rowNumber) + .expect("finder"); + previousPairs.push(pair2.clone()); + finderPattern = pair2.getFinderPattern().as_ref().unwrap(); + // assertNotNull(finderPattern); + assert_eq!(1, finderPattern.getValue()); + + let pair3 = rssExpandedReader + .retrieveNextPair(&row, &previousPairs, rowNumber) + .expect("finder"); + previousPairs.push(pair3.clone()); + finderPattern = pair3.getFinderPattern().as_ref().unwrap(); + // assertNotNull(finderPattern); + assert_eq!(1, finderPattern.getValue()); + + // try { + assert!(rssExpandedReader + .retrieveNextPair(&row, &previousPairs, rowNumber) + .is_err()); + // the previous was the last pair + // fail(NotFoundException.class.getName() + " expected"); + // } catch (NotFoundException nfe) { + // ok + // } +} + +#[test] +fn testRetrieveNextPairPatterns() { + let image = readImage("3.png"); + let binaryMap = BinaryBitmap::new(Rc::new(GlobalHistogramBinarizer::new(Box::new( + BufferedImageLuminanceSource::new(image), + )))); + let rowNumber = binaryMap.getHeight() as u32 / 2; + let row = binaryMap + .getBlackRow(rowNumber as usize, &mut BitArray::new()) + .expect("create"); + let mut previousPairs = Vec::new(); //new ArrayList<>(); + + let mut rssExpandedReader = RSSExpandedReader::new(); + let pair1 = rssExpandedReader + .retrieveNextPair(&row, &previousPairs, rowNumber) + .expect("finder"); + previousPairs.push(pair1.clone()); + let mut finderPattern = pair1.getFinderPattern().as_ref().unwrap(); + // assertNotNull(finderPattern); + assert_eq!(0, finderPattern.getValue()); + + let pair2 = rssExpandedReader + .retrieveNextPair(&row, &previousPairs, rowNumber) + .expect("finder"); + previousPairs.push(pair2.clone()); + finderPattern = pair2.getFinderPattern().as_ref().unwrap(); + // assertNotNull(finderPattern); + assert_eq!(0, finderPattern.getValue()); +} + +#[test] +fn testDecodeCheckCharacter() { + let image = readImage("3.png"); + let binaryMap = BinaryBitmap::new(Rc::new(GlobalHistogramBinarizer::new(Box::new( + BufferedImageLuminanceSource::new(image.clone()), + )))); + let row = binaryMap + .getBlackRow(binaryMap.getHeight() / 2, &mut BitArray::new()) + .expect("create"); + + let startEnd = [145, 243]; //image pixels where the A1 pattern starts (at 124) and ends (at 214) + let value = 0; // A + let finderPatternA1 = FinderPattern::new( + value, + startEnd, + startEnd[0], + startEnd[1], + image.height() / 2, + ); + //{1, 8, 4, 1, 1}; + let mut rssExpandedReader = RSSExpandedReader::new(); + let dataCharacter = rssExpandedReader + .decodeDataCharacter(&row, &finderPatternA1, true, true) + .expect("decode"); + + assert_eq!(98, dataCharacter.getValue()); +} + +#[test] +fn testDecodeDataCharacter() { + let image = readImage("3.png"); + let binaryMap = BinaryBitmap::new(Rc::new(GlobalHistogramBinarizer::new(Box::new( + BufferedImageLuminanceSource::new(image.clone()), + )))); + let row = binaryMap + .getBlackRow(binaryMap.getHeight() / 2, &mut BitArray::new()) + .expect("create"); + + let startEnd = [145, 243]; //image pixels where the A1 pattern starts (at 124) and ends (at 214) + let value = 0; // A + let finderPatternA1 = FinderPattern::new( + value, + startEnd, + startEnd[0], + startEnd[1], + image.height() / 2, + ); + //{1, 8, 4, 1, 1}; + let mut rssExpandedReader = RSSExpandedReader::new(); + let dataCharacter = rssExpandedReader + .decodeDataCharacter(&row, &finderPatternA1, true, false) + .expect("decode"); + + assert_eq!(19, dataCharacter.getValue()); + assert_eq!(1007, dataCharacter.getChecksumPortion()); +} + +fn readImage(fileName: &str) -> image::DynamicImage { + image::open(format!( + "test_resources/blackbox/rssexpanded-1/{}", + fileName + )) + .unwrap() + // Path path = AbstractBlackBoxTestCase.buildTestBase("src/test/resources/blackbox/rssexpanded-1/").resolve(fileName); + // return ImageIO.read(path.toFile()); +} diff --git a/src/oned/rss/expanded/rss_expanded_reader.rs b/src/oned/rss/expanded/rss_expanded_reader.rs new file mode 100644 index 0000000..595c664 --- /dev/null +++ b/src/oned/rss/expanded/rss_expanded_reader.rs @@ -0,0 +1,1033 @@ +/* + * 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. + */ + +/* + * These authors would like to acknowledge the Spanish Ministry of Industry, + * Tourism and Trade, for the support in the project TSI020301-2008-2 + * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled + * Mobile Dynamic Environments", led by Treelogic + * ( http://www.treelogic.com/ ): + * + * http://www.piramidepse.com/ + */ + +use std::collections::HashMap; + +use crate::{ + common::{detector::MathUtils, BitArray}, + oned::{ + recordPattern, recordPatternInReverse, + rss::{ + rss_utils, AbstractRSSReaderTrait, DataCharacter, DataCharacterTrait, FinderPattern, + Pair, + }, + OneDReader, + }, + BarcodeFormat, DecodeHintType, DecodingHintDictionary, Exceptions, RXingResult, + RXingResultMetadataType, RXingResultMetadataValue, RXingResultPoint, Reader, ResultPoint, +}; + +use super::{bit_array_builder, decoders::abstract_expanded_decoder, ExpandedPair, ExpandedRow}; + +const FINDER_PAT_A: u32 = 0; +const FINDER_PAT_B: u32 = 1; +const FINDER_PAT_C: u32 = 2; +const FINDER_PAT_D: u32 = 3; +const FINDER_PAT_E: u32 = 4; +const FINDER_PAT_F: u32 = 5; + +use lazy_static::lazy_static; +lazy_static! { + static ref FINDER_PATTERN_SEQUENCES: Vec> = vec![ + vec![FINDER_PAT_A, FINDER_PAT_A], + vec![FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B], + vec![FINDER_PAT_A, FINDER_PAT_C, FINDER_PAT_B, FINDER_PAT_D], + vec![ + FINDER_PAT_A, + FINDER_PAT_E, + FINDER_PAT_B, + FINDER_PAT_D, + FINDER_PAT_C + ], + vec![ + FINDER_PAT_A, + FINDER_PAT_E, + FINDER_PAT_B, + FINDER_PAT_D, + FINDER_PAT_D, + FINDER_PAT_F + ], + vec![ + FINDER_PAT_A, + FINDER_PAT_E, + FINDER_PAT_B, + FINDER_PAT_D, + FINDER_PAT_E, + FINDER_PAT_F, + FINDER_PAT_F + ], + vec![ + FINDER_PAT_A, + FINDER_PAT_A, + FINDER_PAT_B, + FINDER_PAT_B, + FINDER_PAT_C, + FINDER_PAT_C, + FINDER_PAT_D, + FINDER_PAT_D + ], + vec![ + FINDER_PAT_A, + FINDER_PAT_A, + FINDER_PAT_B, + FINDER_PAT_B, + FINDER_PAT_C, + FINDER_PAT_C, + FINDER_PAT_D, + FINDER_PAT_E, + FINDER_PAT_E + ], + vec![ + FINDER_PAT_A, + FINDER_PAT_A, + FINDER_PAT_B, + FINDER_PAT_B, + FINDER_PAT_C, + FINDER_PAT_C, + FINDER_PAT_D, + FINDER_PAT_E, + FINDER_PAT_F, + FINDER_PAT_F + ], + vec![ + FINDER_PAT_A, + FINDER_PAT_A, + FINDER_PAT_B, + FINDER_PAT_B, + FINDER_PAT_C, + FINDER_PAT_D, + FINDER_PAT_D, + FINDER_PAT_E, + FINDER_PAT_E, + FINDER_PAT_F, + FINDER_PAT_F + ], + ]; +} + +/** + * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es) + * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es) + */ +pub struct RSSExpandedReader { + _possibleLeftPairs: Vec, + _possibleRightPairs: Vec, + decodeFinderCounters: [u32; 4], + dataCharacterCounters: [u32; 8], + oddRoundingErrors: [f32; 4], + evenRoundingErrors: [f32; 4], + oddCounts: [u32; 4], + evenCounts: [u32; 4], + + pairs: Vec, //new ArrayList<>(MAX_PAIRS); + rows: Vec, // new ArrayList<>(); + startEnd: [u32; 2], // new int[2]; + startFromEven: bool, +} +impl AbstractRSSReaderTrait for RSSExpandedReader {} +impl OneDReader for RSSExpandedReader { + fn decodeRow( + &mut self, + rowNumber: u32, + row: &crate::common::BitArray, + _hints: &crate::DecodingHintDictionary, + ) -> Result { + // Rows can start with even pattern in case in prev rows there where odd number of patters. + // So lets try twice + self.pairs.clear(); + self.startFromEven = false; + // try { + let possible = Self::constructRXingResult(&self.decodeRow2pairs(rowNumber, row)?); + if possible.is_ok() { + return possible; + } + // } catch (NotFoundException e) { + // OK + // } + + self.pairs.clear(); + self.startFromEven = true; + Self::constructRXingResult(&self.decodeRow2pairs(rowNumber, row)?) + } +} +impl Reader for RSSExpandedReader { + fn decode(&mut self, image: &crate::BinaryBitmap) -> Result { + self.decode_with_hints(image, &HashMap::new()) + } + + // Note that we don't try rotation without the try harder flag, even if rotation was supported. + fn decode_with_hints( + &mut self, + image: &crate::BinaryBitmap, + hints: &DecodingHintDictionary, + ) -> Result { + if let Ok(res) = self.doDecode(image, hints) { + Ok(res) + } else { + let tryHarder = hints.contains_key(&DecodeHintType::TRY_HARDER); + if tryHarder && image.isRotateSupported() { + let rotatedImage = image.rotateCounterClockwise(); + let mut result = self.doDecode(&rotatedImage, hints)?; + // Record that we found it rotated 90 degrees CCW / 270 degrees CW + let metadata = result.getRXingResultMetadata(); + let mut orientation = 270; + if metadata.contains_key(&RXingResultMetadataType::ORIENTATION) { + // But if we found it reversed in doDecode(), add in that result here: + orientation = (orientation + + if let Some(crate::RXingResultMetadataValue::Orientation(or)) = + metadata.get(&RXingResultMetadataType::ORIENTATION) + { + *or + } else { + 0 + }) + % 360; + } + result.putMetadata( + RXingResultMetadataType::ORIENTATION, + RXingResultMetadataValue::Orientation(orientation), + ); + // Update result points + // let points = result.getRXingResultPoints(); + // if points != null { + let height = rotatedImage.getHeight(); + // for point in result.getRXingResultPointsMut().iter_mut() { + let total_points = result.getRXingResultPoints().len(); + let points = result.getRXingResultPointsMut(); + for i in 0..total_points { + // for (int i = 0; i < points.length; i++) { + points[i] = RXingResultPoint::new( + height as f32 - points[i].getY() - 1.0, + points[i].getX(), + ); + } + // } + + Ok(result) + } else { + return Err(Exceptions::NotFoundException("".to_owned())); + } + } + } + + fn reset(&mut self) { + self.pairs.clear(); + self.rows.clear(); + } +} + +impl RSSExpandedReader { + pub fn new() -> Self { + Self::default() + } + const SYMBOL_WIDEST: [u32; 5] = [7, 5, 4, 3, 1]; + const EVEN_TOTAL_SUBSET: [u32; 5] = [4, 20, 52, 104, 204]; + const GSUM: [u32; 5] = [0, 348, 1388, 2948, 3988]; + + const FINDER_PATTERNS: [[u32; 4]; 6] = [ + [1, 8, 4, 1], // A + [3, 6, 4, 1], // B + [3, 4, 6, 1], // C + [3, 2, 8, 1], // D + [2, 6, 5, 1], // E + [2, 2, 9, 1], // F + ]; + + const WEIGHTS: [[u32; 8]; 23] = [ + [1, 3, 9, 27, 81, 32, 96, 77], + [20, 60, 180, 118, 143, 7, 21, 63], + [189, 145, 13, 39, 117, 140, 209, 205], + [193, 157, 49, 147, 19, 57, 171, 91], + [62, 186, 136, 197, 169, 85, 44, 132], + [185, 133, 188, 142, 4, 12, 36, 108], + [113, 128, 173, 97, 80, 29, 87, 50], + [150, 28, 84, 41, 123, 158, 52, 156], + [46, 138, 203, 187, 139, 206, 196, 166], + [76, 17, 51, 153, 37, 111, 122, 155], + [43, 129, 176, 106, 107, 110, 119, 146], + [16, 48, 144, 10, 30, 90, 59, 177], + [109, 116, 137, 200, 178, 112, 125, 164], + [70, 210, 208, 202, 184, 130, 179, 115], + [134, 191, 151, 31, 93, 68, 204, 190], + [148, 22, 66, 198, 172, 94, 71, 2], + [6, 18, 54, 162, 64, 192, 154, 40], + [120, 149, 25, 75, 14, 42, 126, 167], + [79, 26, 78, 23, 69, 207, 199, 175], + [103, 98, 83, 38, 114, 131, 182, 124], + [161, 61, 183, 127, 170, 88, 53, 159], + [55, 165, 73, 8, 24, 72, 5, 15], + [45, 135, 194, 160, 58, 174, 100, 89], + ]; + + const MAX_PAIRS: usize = 11; + + // Not private for testing + pub(super) fn decodeRow2pairs( + &mut self, + rowNumber: u32, + row: &BitArray, + ) -> Result, Exceptions> { + let mut done = false; + while !done { + let previousPairs = self.pairs.clone(); + let to_add_res = self.retrieveNextPair(row, &previousPairs, rowNumber); + if let Ok(to_add) = to_add_res { + self.pairs.push(to_add); + } else if self.pairs.is_empty() { + return Err(to_add_res.err().unwrap()); + } else { + // exit this loop when retrieveNextPair() fails and throws + done = true; + } + // self.pairs = previousPairs; + } + // try { + // this.pairs.add(retrieveNextPair(row, this.pairs, rowNumber)); + // } catch (NotFoundException nfe) { + // if (this.pairs.isEmpty()) { + // throw nfe; + // } + // // exit this loop when retrieveNextPair() fails and throws + // done = true; + // } + // } + + // TODO: verify sequence of finder patterns as in checkPairSequence() + if self.checkChecksum() { + return Ok(self.pairs.clone()); + } + + let tryStackedDecode = !self.rows.is_empty(); + self.storeRow(rowNumber); // TODO: deal with reversed rows + if tryStackedDecode { + // When the image is 180-rotated, then rows are sorted in wrong direction. + // Try twice with both the directions. + let ps = self.checkRows(false); + if ps.is_some() { + return Ok(ps.unwrap()); + } + let ps = self.checkRows(true); + if ps.is_some() { + return Ok(ps.unwrap()); + } + } + + return Err(Exceptions::NotFoundException("".to_owned())); + } + + fn checkRows(&mut self, reverse: bool) -> Option> { + // Limit number of rows we are checking + // We use recursive algorithm with pure complexity and don't want it to take forever + // Stacked barcode can have up to 11 rows, so 25 seems reasonable enough + if self.rows.len() > 25 { + self.rows.clear(); // We will never have a chance to get result, so clear it + return None; + } + + self.pairs.clear(); + if reverse { + self.rows.reverse(); + } + + let mut c_rows = Vec::new(); + let ps = if let Ok(res) = self.checkRowsDetails(&mut c_rows, 0) { + Some(res) + } else { + None + }; + // } catch (NotFoundException e) { + // OK + // } + + if reverse { + self.rows.reverse(); + } + + ps + } + + // Try to construct a valid rows sequence + // Recursion is used to implement backtracking + fn checkRowsDetails( + &mut self, + collectedRows: &mut Vec, + currentRow: usize, + ) -> Result, Exceptions> { + for i in currentRow..self.rows.len() { + // for (int i = currentRow; i < rows.size(); i++) { + let row = self.rows.get(i).unwrap(); + self.pairs.clear(); + for collectedRow in &collectedRows.clone() { + // for (ExpandedRow collectedRow : collectedRows) { + + self.pairs.append(&mut collectedRow.getPairs().to_vec()); + } + self.pairs.append(&mut row.getPairs().to_vec()); + + if Self::isValidSequence(&self.pairs) { + if self.checkChecksum() { + return Ok(self.pairs.clone()); + } + + // let rs = collectedRows; + collectedRows.push(row.clone()); + // try { + // Recursion: try to add more rows + if let Ok(cr) = self.checkRowsDetails(collectedRows, i + 1) { + return Ok(cr); + } + // return checkRows(rs, i + 1); + // } catch (NotFoundException e) { + // We failed, try the next candidate + // } + } + } + + return Err(Exceptions::NotFoundException("".to_owned())); + } + + // Whether the pairs form a valid find pattern sequence, + // either complete or a prefix + fn isValidSequence(pairs: &[ExpandedPair]) -> bool { + for i in 0..FINDER_PATTERN_SEQUENCES.len() { + // for sequence in &FINDER_PATTERN_SEQUENCES.iter() { + let sequence = FINDER_PATTERN_SEQUENCES.get(i).unwrap(); + // for (int[] sequence : FINDER_PATTERN_SEQUENCES) { + if pairs.len() <= sequence.len() { + let mut stop = true; + for j in 0..pairs.len() { + // for (int j = 0; j < pairs.size(); j++) { + if pairs + .get(j) + .unwrap() + .getFinderPattern() + .as_ref() + .unwrap() + .getValue() + != sequence[j] + { + stop = false; + break; + } + } + if stop { + return true; + } + } + } + + return false; + } + + fn storeRow(&mut self, rowNumber: u32) { + // Discard if duplicate above or below; otherwise insert in order by row number. + let mut insertPos = 0; + let mut prevIsSame = false; + let mut nextIsSame = false; + while insertPos < self.rows.len() { + let erow = self.rows.get(insertPos).unwrap(); + if erow.getRowNumber() > rowNumber { + nextIsSame = erow.isEquivalent(&self.pairs); + break; + } + prevIsSame = erow.isEquivalent(&self.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 already detected row + if Self::isPartialRow(&self.pairs, &self.rows) { + return; + } + + self.rows + .insert(insertPos, ExpandedRow::new(self.pairs.clone(), rowNumber)); + + Self::removePartialRows(&self.pairs, &mut self.rows); + } + + // Remove all the rows that contains only specified pairs + fn removePartialRows(pairs: &[ExpandedPair], rows: &mut Vec) { + for i in 0..rows.len() { + // for r in rows { + // for (Iterator iterator = rows.iterator(); iterator.hasNext();) { + // ExpandedRow r = iterator.next(); + let r = rows.get(i).unwrap(); + if r.getPairs().len() != pairs.len() { + let mut allFound = true; + for p in r.getPairs() { + // for (ExpandedPair p : r.getPairs()) { + if !pairs.contains(p) { + allFound = false; + break; + } + } + if allFound { + // 'pairs' contains all the pairs from the row 'r' + // iterator.remove(); + rows.remove(i); + } + } + } + } + + // Returns true when one of the rows already contains all the pairs + fn isPartialRow(pairs: &[ExpandedPair], rows: &[ExpandedRow]) -> bool { + for r in rows { + // for (ExpandedRow r : rows) { + let mut allFound = true; + for p in pairs { + // for (ExpandedPair p : pairs) { + let mut found = false; + for pp in r.getPairs() { + // for (ExpandedPair pp : r.getPairs()) { + if p == pp { + found = true; + break; + } + } + if !found { + allFound = false; + break; + } + } + if allFound { + // the row 'r' contain all the pairs from 'pairs' + return true; + } + } + return false; + } + + // Only used for unit testing + fn getRows(&self) -> &[ExpandedRow] { + &self.rows + } + + // Not private for unit testing + fn constructRXingResult(pairs: &[ExpandedPair]) -> Result { + let binary = bit_array_builder::buildBitArray(&pairs.to_vec()); + + let mut decoder = abstract_expanded_decoder::createDecoder(&binary)?; + let resultingString = decoder.parseInformation()?; + + let firstPoints = pairs + .get(0) + .unwrap() + .getFinderPattern() + .as_ref() + .unwrap() + .getRXingResultPoints(); + let lastPoints = pairs + .get(pairs.len() - 1) + .unwrap() + .getFinderPattern() + .as_ref() + .unwrap() + .getRXingResultPoints(); + + let mut result = RXingResult::new( + &resultingString, + Vec::new(), + vec![firstPoints[0], firstPoints[1], lastPoints[0], lastPoints[1]], + BarcodeFormat::RSS_EXPANDED, + ); + + result.putMetadata( + RXingResultMetadataType::SYMBOLOGY_IDENTIFIER, + RXingResultMetadataValue::SymbologyIdentifier("]e0".to_owned()), + ); + + Ok(result) + } + + fn checkChecksum(&self) -> bool { + let firstPair = self.pairs.get(0).unwrap(); + let checkCharacter = firstPair.getLeftChar(); + let Some(firstCharacter) = firstPair.getRightChar() else { + return false; + }; + + let mut checksum = firstCharacter.getChecksumPortion(); + let mut s = 2; + + // for i in 1..self.pairs.len() { + // for (int i = 1; i < this.pairs.size(); ++i) { + for currentPair in &self.pairs { + // let currentPair = self.pairs.get(i).unwrap(); + checksum += currentPair.getLeftChar().unwrap().getChecksumPortion(); + s += 1; + if let Some(currentRightChar) = currentPair.getRightChar() { + // if (currentRightChar != null) { + checksum += currentRightChar.getChecksumPortion(); + s += 1; + } + } + + checksum %= 211; + + let checkCharacterValue = 211 * (s - 4) + checksum; + + checkCharacterValue == checkCharacter.unwrap().getValue() + } + + fn getNextSecondBar(row: &BitArray, initialPos: usize) -> usize { + let mut currentPos; + if row.get(initialPos) { + currentPos = row.getNextUnset(initialPos); + currentPos = row.getNextSet(currentPos); + } else { + currentPos = row.getNextSet(initialPos); + currentPos = row.getNextUnset(currentPos); + } + currentPos + } + + // not private for testing + pub(super) fn retrieveNextPair( + &mut self, + row: &BitArray, + previousPairs: &[ExpandedPair], + rowNumber: u32, + ) -> Result { + let mut isOddPattern = previousPairs.len() % 2 == 0; + if self.startFromEven { + isOddPattern = !isOddPattern; + } + + let mut pattern; + + let mut keepFinding = true; + let mut forcedOffset = -1_i32; + loop { + self.findNextPair(row, previousPairs, forcedOffset)?; + pattern = self.parseFoundFinderPattern(row, rowNumber, isOddPattern); + if pattern.is_none() { + forcedOffset = Self::getNextSecondBar(row, self.startEnd[0] as usize) as i32; + } else { + keepFinding = false; + } + if !keepFinding { + break; + } + } //while (keepFinding); + + // 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); + + let leftChar = + self.decodeDataCharacter(row, pattern.as_ref().unwrap(), isOddPattern, true)?; + + if !previousPairs.is_empty() + && previousPairs + .get(previousPairs.len() - 1) + .unwrap() + .mustBeLast() + { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + let rightChar = if let Ok(ch) = + self.decodeDataCharacter(row, pattern.as_ref().unwrap(), isOddPattern, false) + { + Some(ch) + } else { + None + }; + // try { + // rightChar = this.decodeDataCharacter(row, pattern, isOddPattern, false); + // } catch (NotFoundException ignored) { + // rightChar = null; + // } + + Ok(ExpandedPair::new(Some(leftChar), rightChar, pattern)) + } + + fn findNextPair( + &mut self, + row: &BitArray, + previousPairs: &[ExpandedPair], + forcedOffset: i32, + ) -> Result<(), Exceptions> { + let counters = &mut self.decodeFinderCounters; + counters[0] = 0; + counters[1] = 0; + counters[2] = 0; + counters[3] = 0; + + let width = row.getSize(); + + let mut rowOffset; + if forcedOffset >= 0 { + rowOffset = forcedOffset; + } else if previousPairs.is_empty() { + rowOffset = 0; + } else { + let lastPair = previousPairs.get(previousPairs.len() - 1).unwrap(); + rowOffset = lastPair.getFinderPattern().as_ref().unwrap().getStartEnd()[1] as i32; + } + let mut searchingEvenPair = previousPairs.len() % 2 != 0; + if self.startFromEven { + searchingEvenPair = !searchingEvenPair; + } + + let mut isWhite = false; + while rowOffset < width as i32 { + isWhite = !row.get(rowOffset as usize); + if !isWhite { + break; + } + rowOffset += 1; + } + + let mut counterPosition = 0; + let mut patternStart = rowOffset; + for x in rowOffset..width as i32 { + // for (int x = rowOffset; x < width; x++) { + if row.get(x as usize) != isWhite { + counters[counterPosition] += 1; + } else { + if counterPosition == 3 { + if searchingEvenPair { + Self::reverseCounters(counters); + } + + if Self::isFinderPattern(counters) { + self.startEnd[0] = patternStart as u32; + self.startEnd[1] = x as u32; + return Ok(()); + } + + if searchingEvenPair { + Self::reverseCounters(counters); + } + + patternStart += (counters[0] + counters[1]) as i32; + counters[0] = counters[2]; + counters[1] = counters[3]; + counters[2] = 0; + counters[3] = 0; + counterPosition -= 1; + } else { + counterPosition += 1; + } + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + return Err(Exceptions::NotFoundException("".to_owned())); + } + + fn reverseCounters(counters: &mut [u32]) { + let length = counters.len(); + for i in 0..length / 2 { + // for (int i = 0; i < length / 2; ++i) { + let tmp = counters[i]; + counters[i] = counters[length - i - 1]; + counters[length - i - 1] = tmp; + } + } + + fn parseFoundFinderPattern( + &self, + row: &BitArray, + rowNumber: u32, + oddPattern: bool, + ) -> Option { + // Actually we found elements 2-5. + let firstCounter; + let start; + let end; + + if oddPattern { + // If pattern number is odd, we need to locate element 1 *before* the current block. + + let mut firstElementStart = self.startEnd[0] as i32 - 1; + // Locate element 1 + while firstElementStart >= 0 && !row.get(firstElementStart as usize) { + firstElementStart -= 1; + } + + firstElementStart += 1; + firstCounter = self.startEnd[0] as i32 - firstElementStart; + start = firstElementStart; + end = self.startEnd[1]; + } else { + // If pattern number is even, the pattern is reversed, so we need to locate element 1 *after* the current block. + + start = self.startEnd[0] as i32; + + end = row.getNextUnset(self.startEnd[1] as usize + 1) as u32; + firstCounter = end as i32 - self.startEnd[1] as i32; + } + + // Make 'counters' hold 1-4 + let mut counters = self.decodeFinderCounters; + let slc = counters[..counters.len() - 1].to_vec(); + counters[1..].copy_from_slice(&slc); + // System.arraycopy(counters, 0, counters, 1, counters.length - 1); + + counters[0] = firstCounter as u32; + let Ok(value) = Self::parseFinderValue(&counters, &Self::FINDER_PATTERNS) else { + return None + }; + + Some(FinderPattern::new( + value, + [start as usize, end as usize], + start as usize, + end as usize, + rowNumber, + )) + } + + pub(super) fn decodeDataCharacter( + &mut self, + row: &BitArray, + pattern: &FinderPattern, + isOddPattern: bool, + leftChar: bool, + ) -> Result { + let counters = &mut self.dataCharacterCounters; + counters.fill(0); + + if leftChar { + recordPatternInReverse(row, pattern.getStartEnd()[0], counters)?; + } else { + recordPattern(row, pattern.getStartEnd()[1], counters)?; + // reverse it + let mut i = 0; + let mut j = counters.len() - 1; + while i < j { + // for (int i = 0, j = counters.length - 1; i < j; i++, j--) { + let temp = counters[i]; + counters[i] = counters[j]; + counters[j] = temp; + + i += 1; + j -= 1; + } + } //counters[] has the pixels of the module + + let numModules = 17; //left and right data characters have all the same length + // let elementWidth: f32 = MathUtils::sum(counters) / numModules; + let elementWidth: f32 = (counters.iter().sum::() as f32) / numModules as f32; + + // Sanity check: element width for pattern and the character should match + let expectedElementWidth: f32 = + (pattern.getStartEnd()[1] - pattern.getStartEnd()[0]) as f32 / 15.0; + if (elementWidth - expectedElementWidth).abs() / expectedElementWidth > 0.3 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + // let oddCounts = &mut self.oddCounts; + // let evenCounts = &mut self.evenCounts; + // let oddRoundingErrors = &mut self.oddRoundingErrors; + // let evenRoundingErrors = &mut self.evenRoundingErrors; + + for i in 0..counters.len() { + // for (int i = 0; i < counters.length; i++) { + let value: f32 = 1.0 * counters[i] as f32 / elementWidth; + let mut count = (value + 0.5) as i32; // Round + if count < 1 { + if value < 0.3 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + count = 1; + } else if count > 8 { + if value > 8.7 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + count = 8; + } + let offset = i / 2; + if (i & 0x01) == 0 { + self.oddCounts[offset] = count as u32; + self.oddRoundingErrors[offset] = value - count as f32; + } else { + self.evenCounts[offset] = count as u32; + self.evenRoundingErrors[offset] = value - count as f32; + } + } + + self.adjustOddEvenCounts(numModules as u32)?; + + let weightRowNumber = (4 * pattern.getValue() as isize + + (if isOddPattern { 0 } else { 2 }) + + (if leftChar { 0 } else { 1 }) + - 1) as usize; + + let mut oddSum = 0; + let mut oddChecksumPortion = 0; + for i in (0..self.oddCounts.len()).rev() { + // for (int i = oddCounts.length - 1; i >= 0; i--) { + if Self::isNotA1left(pattern, isOddPattern, leftChar) { + let weight = Self::WEIGHTS[weightRowNumber][2 * i]; + oddChecksumPortion += self.oddCounts[i] * weight; + } + oddSum += self.oddCounts[i]; + } + let mut evenChecksumPortion = 0; + for i in (0..self.evenCounts.len()).rev() { + // for (int i = evenCounts.length - 1; i >= 0; i--) { + if Self::isNotA1left(pattern, isOddPattern, leftChar) { + let weight = Self::WEIGHTS[weightRowNumber][2 * i + 1]; + evenChecksumPortion += self.evenCounts[i] * weight; + } + } + let checksumPortion = oddChecksumPortion + evenChecksumPortion; + + if (oddSum & 0x01) != 0 || oddSum > 13 || oddSum < 4 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + let group = ((13 - oddSum) / 2) as usize; + let oddWidest = Self::SYMBOL_WIDEST[group]; + let evenWidest = 9 - oddWidest; + let vOdd = rss_utils::getRSSvalue(&self.oddCounts, oddWidest, true); + let vEven = rss_utils::getRSSvalue(&self.evenCounts, evenWidest, false); + let tEven = Self::EVEN_TOTAL_SUBSET[group]; + let gSum = Self::GSUM[group]; + let value = vOdd * tEven + vEven + gSum; + + Ok(DataCharacter::new(value, checksumPortion)) + } + + fn isNotA1left(pattern: &FinderPattern, isOddPattern: bool, leftChar: bool) -> bool { + // A1: pattern.getValue is 0 (A), and it's an oddPattern, and it is a left char + !(pattern.getValue() == 0 && isOddPattern && leftChar) + } + + fn adjustOddEvenCounts(&mut self, numModules: u32) -> Result<(), Exceptions> { + // let oddSum = MathUtils::sum(&self.oddCounts); + // let evenSum = MathUtils::sum(&self.evenCounts); + let oddSum = self.oddCounts.iter().sum::(); + let evenSum = self.evenCounts.iter().sum::(); + + let mut incrementOdd = false; + let mut decrementOdd = false; + + if oddSum > 13 { + decrementOdd = true; + } else if oddSum < 4 { + incrementOdd = true; + } + let mut incrementEven = false; + let mut decrementEven = false; + if evenSum > 13 { + decrementEven = true; + } else if evenSum < 4 { + incrementEven = true; + } + + let mismatch = oddSum as isize + evenSum as isize - numModules as isize; + let oddParityBad = (oddSum & 0x01) == 1; + let evenParityBad = (evenSum & 0x01) == 0; + match mismatch { + 1 => { + if oddParityBad { + if evenParityBad { + return Err(Exceptions::NotFoundException("".to_owned())); + } + decrementOdd = true; + } else { + if !evenParityBad { + return Err(Exceptions::NotFoundException("".to_owned())); + } + decrementEven = true; + } + } + -1 => { + if oddParityBad { + if evenParityBad { + return Err(Exceptions::NotFoundException("".to_owned())); + } + incrementOdd = true; + } else { + if !evenParityBad { + return Err(Exceptions::NotFoundException("".to_owned())); + } + incrementEven = true; + } + } + 0 => { + if oddParityBad { + if !evenParityBad { + return Err(Exceptions::NotFoundException("".to_owned())); + } + // Both bad + if oddSum < evenSum { + incrementOdd = true; + decrementEven = true; + } else { + decrementOdd = true; + incrementEven = true; + } + } else { + if evenParityBad { + return Err(Exceptions::NotFoundException("".to_owned())); + } + // Nothing to do! + } + } + + _ => return Err(Exceptions::NotFoundException("".to_owned())), + } + + if incrementOdd { + if decrementOdd { + return Err(Exceptions::NotFoundException("".to_owned())); + } + Self::increment(&mut self.oddCounts, &self.oddRoundingErrors); + } + if decrementOdd { + Self::decrement(&mut self.oddCounts, &self.oddRoundingErrors); + } + if incrementEven { + if decrementEven { + return Err(Exceptions::NotFoundException("".to_owned())); + } + Self::increment(&mut self.evenCounts, &self.oddRoundingErrors); + } + if decrementEven { + Self::decrement(&mut self.evenCounts, &self.evenRoundingErrors); + } + + Ok(()) + } +} + +impl Default for RSSExpandedReader { + fn default() -> Self { + Self { _possibleLeftPairs: Default::default(), _possibleRightPairs: Default::default(), decodeFinderCounters: Default::default(), dataCharacterCounters: Default::default(), oddRoundingErrors: Default::default(), evenRoundingErrors: Default::default(), oddCounts: Default::default(), evenCounts: Default::default(), pairs: Default::default(), rows: Default::default(), startEnd: Default::default(), startFromEven: Default::default() } + } +} diff --git a/src/oned/rss/finder_pattern.rs b/src/oned/rss/finder_pattern.rs index c614cbd..26ae4d6 100644 --- a/src/oned/rss/finder_pattern.rs +++ b/src/oned/rss/finder_pattern.rs @@ -21,6 +21,7 @@ use crate::RXingResultPoint; /** * Encapsulates an RSS barcode finder pattern, including its start/end position and row. */ +#[derive(Clone)] pub struct FinderPattern { value: u32, startEnd: [usize; 2],