From ab4dab420b47100e236af2915d291bd0b5a2de1f Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Tue, 13 Dec 2022 17:39:12 -0600 Subject: [PATCH] rss_14 ported but does not pass integration --- src/oned/multi_format_one_d_reader.rs | 9 +- src/oned/rss/AbstractRSSReader.java | 140 ------ src/oned/rss/DataCharacter.java | 59 --- src/oned/rss/FinderPattern.java | 65 --- src/oned/rss/Pair.java | 41 -- src/oned/rss/RSS14Reader.java | 471 ------------------- src/oned/rss/RSSUtils.java | 87 ---- src/oned/rss/abstract_rss_reader.rs | 134 ++++++ src/oned/rss/data_character.rs | 54 +++ src/oned/rss/finder_pattern.rs | 65 +++ src/oned/rss/mod.rs | 17 + src/oned/rss/pair.rs | 63 +++ src/oned/rss/rss_14_reader.rs | 641 ++++++++++++++++++++++++++ src/oned/rss/rss_utils.rs | 99 ++++ tests/RSS14BlackBox1TestCase.java | 34 -- tests/RSS14BlackBox2TestCase.java | 34 -- tests/rss_14_blackbox.rs | 60 +++ 17 files changed, 1138 insertions(+), 935 deletions(-) delete mode 100644 src/oned/rss/AbstractRSSReader.java delete mode 100644 src/oned/rss/DataCharacter.java delete mode 100644 src/oned/rss/FinderPattern.java delete mode 100644 src/oned/rss/Pair.java delete mode 100644 src/oned/rss/RSS14Reader.java delete mode 100644 src/oned/rss/RSSUtils.java create mode 100644 src/oned/rss/abstract_rss_reader.rs create mode 100644 src/oned/rss/data_character.rs create mode 100644 src/oned/rss/finder_pattern.rs create mode 100644 src/oned/rss/pair.rs create mode 100644 src/oned/rss/rss_14_reader.rs create mode 100644 src/oned/rss/rss_utils.rs delete mode 100644 tests/RSS14BlackBox1TestCase.java delete mode 100644 tests/RSS14BlackBox2TestCase.java create mode 100644 tests/rss_14_blackbox.rs diff --git a/src/oned/multi_format_one_d_reader.rs b/src/oned/multi_format_one_d_reader.rs index c1a5557..1d031f5 100644 --- a/src/oned/multi_format_one_d_reader.rs +++ b/src/oned/multi_format_one_d_reader.rs @@ -14,6 +14,7 @@ * limitations under the License. */ +use super::rss::RSS14Reader; use super::CodaBarReader; use super::Code128Reader; use super::Code39Reader; @@ -86,9 +87,9 @@ impl MultiFormatOneDReader { if possibleFormats.contains(&BarcodeFormat::CODABAR) { readers.push(Box::new(CodaBarReader::new())); } - // if (possibleFormats.contains(&BarcodeFormat::RSS_14)) { - // readers.add(new RSS14Reader()); - // } + if possibleFormats.contains(&BarcodeFormat::RSS_14) { + readers.push(Box::new(RSS14Reader::new())); + } // if (possibleFormats.contains(&BarcodeFormat::RSS_EXPANDED)) { // readers.add(new RSSExpandedReader()); // } @@ -100,7 +101,7 @@ impl MultiFormatOneDReader { readers.push(Box::new(Code93Reader::new())); readers.push(Box::new(Code128Reader {})); readers.push(Box::new(ITFReader::default())); - // readers.push(new RSS14Reader()); + readers.push(Box::new(RSS14Reader::new())); // readers.push(new RSSExpandedReader()); } diff --git a/src/oned/rss/AbstractRSSReader.java b/src/oned/rss/AbstractRSSReader.java deleted file mode 100644 index 5c77512..0000000 --- a/src/oned/rss/AbstractRSSReader.java +++ /dev/null @@ -1,140 +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. - */ - -package com.google.zxing.oned.rss; - -import com.google.zxing.NotFoundException; -import com.google.zxing.common.detector.MathUtils; -import com.google.zxing.oned.OneDReader; - -/** - * Superclass of {@link OneDReader} implementations that read barcodes in the RSS family - * of formats. - */ -public abstract class AbstractRSSReader extends OneDReader { - - private static final float MAX_AVG_VARIANCE = 0.2f; - private static final float MAX_INDIVIDUAL_VARIANCE = 0.45f; - - private static final float MIN_FINDER_PATTERN_RATIO = 9.5f / 12.0f; - private static final float MAX_FINDER_PATTERN_RATIO = 12.5f / 14.0f; - - private final int[] decodeFinderCounters; - private final int[] dataCharacterCounters; - private final float[] oddRoundingErrors; - private final float[] evenRoundingErrors; - private final int[] oddCounts; - private final int[] evenCounts; - - protected AbstractRSSReader() { - decodeFinderCounters = new int[4]; - dataCharacterCounters = new int[8]; - oddRoundingErrors = new float[4]; - evenRoundingErrors = new float[4]; - oddCounts = new int[dataCharacterCounters.length / 2]; - evenCounts = new int[dataCharacterCounters.length / 2]; - } - - protected final int[] getDecodeFinderCounters() { - return decodeFinderCounters; - } - - protected final int[] getDataCharacterCounters() { - return dataCharacterCounters; - } - - protected final float[] getOddRoundingErrors() { - return oddRoundingErrors; - } - - protected final float[] getEvenRoundingErrors() { - return evenRoundingErrors; - } - - protected final int[] getOddCounts() { - return oddCounts; - } - - protected final int[] getEvenCounts() { - return evenCounts; - } - - protected static int parseFinderValue(int[] counters, - int[][] finderPatterns) throws NotFoundException { - for (int value = 0; value < finderPatterns.length; value++) { - if (patternMatchVariance(counters, finderPatterns[value], MAX_INDIVIDUAL_VARIANCE) < - MAX_AVG_VARIANCE) { - return value; - } - } - throw NotFoundException.getNotFoundInstance(); - } - - /** - * @param array values to sum - * @return sum of values - * @deprecated call {@link MathUtils#sum(int[])} - */ - @Deprecated - protected static int count(int[] array) { - return MathUtils.sum(array); - } - - protected static void increment(int[] array, float[] errors) { - int index = 0; - float biggestError = errors[0]; - for (int i = 1; i < array.length; i++) { - if (errors[i] > biggestError) { - biggestError = errors[i]; - index = i; - } - } - array[index]++; - } - - protected static void decrement(int[] array, float[] errors) { - int index = 0; - float biggestError = errors[0]; - for (int i = 1; i < array.length; i++) { - if (errors[i] < biggestError) { - biggestError = errors[i]; - index = i; - } - } - array[index]--; - } - - protected static boolean isFinderPattern(int[] counters) { - int firstTwoSum = counters[0] + counters[1]; - int sum = firstTwoSum + counters[2] + counters[3]; - float ratio = firstTwoSum / (float) sum; - if (ratio >= MIN_FINDER_PATTERN_RATIO && ratio <= MAX_FINDER_PATTERN_RATIO) { - // passes ratio test in spec, but see if the counts are unreasonable - int minCounter = Integer.MAX_VALUE; - int maxCounter = Integer.MIN_VALUE; - for (int counter : counters) { - if (counter > maxCounter) { - maxCounter = counter; - } - if (counter < minCounter) { - minCounter = counter; - } - } - return maxCounter < 10 * minCounter; - } - return false; - } -} diff --git a/src/oned/rss/DataCharacter.java b/src/oned/rss/DataCharacter.java deleted file mode 100644 index 1c3c502..0000000 --- a/src/oned/rss/DataCharacter.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2009 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.oned.rss; - -/** - * Encapsulates a since character value in an RSS barcode, including its checksum information. - */ -public class DataCharacter { - - private final int value; - private final int checksumPortion; - - public DataCharacter(int value, int checksumPortion) { - this.value = value; - this.checksumPortion = checksumPortion; - } - - public final int getValue() { - return value; - } - - public final int getChecksumPortion() { - return checksumPortion; - } - - @Override - public final String toString() { - return value + "(" + checksumPortion + ')'; - } - - @Override - public final boolean equals(Object o) { - if (!(o instanceof DataCharacter)) { - return false; - } - DataCharacter that = (DataCharacter) o; - return value == that.value && checksumPortion == that.checksumPortion; - } - - @Override - public final int hashCode() { - return value ^ checksumPortion; - } - -} diff --git a/src/oned/rss/FinderPattern.java b/src/oned/rss/FinderPattern.java deleted file mode 100644 index 25d6b68..0000000 --- a/src/oned/rss/FinderPattern.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2009 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.oned.rss; - -import com.google.zxing.RXingResultPoint; - -/** - * Encapsulates an RSS barcode finder pattern, including its start/end position and row. - */ -public final class FinderPattern { - - private final int value; - private final int[] startEnd; - private final RXingResultPoint[] resultPoints; - - public FinderPattern(int value, int[] startEnd, int start, int end, int rowNumber) { - this.value = value; - this.startEnd = startEnd; - this.resultPoints = new RXingResultPoint[] { - new RXingResultPoint(start, rowNumber), - new RXingResultPoint(end, rowNumber), - }; - } - - public int getValue() { - return value; - } - - public int[] getStartEnd() { - return startEnd; - } - - public RXingResultPoint[] getRXingResultPoints() { - 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; - } - -} diff --git a/src/oned/rss/Pair.java b/src/oned/rss/Pair.java deleted file mode 100644 index e2371d2..0000000 --- a/src/oned/rss/Pair.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2009 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.oned.rss; - -final class Pair extends DataCharacter { - - private final FinderPattern finderPattern; - private int count; - - Pair(int value, int checksumPortion, FinderPattern finderPattern) { - super(value, checksumPortion); - this.finderPattern = finderPattern; - } - - FinderPattern getFinderPattern() { - return finderPattern; - } - - int getCount() { - return count; - } - - void incrementCount() { - count++; - } - -} \ No newline at end of file diff --git a/src/oned/rss/RSS14Reader.java b/src/oned/rss/RSS14Reader.java deleted file mode 100644 index 0111d62..0000000 --- a/src/oned/rss/RSS14Reader.java +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Copyright 2009 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.oned.rss; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.DecodeHintType; -import com.google.zxing.NotFoundException; -import com.google.zxing.RXingResult; -import com.google.zxing.RXingResultMetadataType; -import com.google.zxing.RXingResultPoint; -import com.google.zxing.RXingResultPointCallback; -import com.google.zxing.common.BitArray; -import com.google.zxing.common.detector.MathUtils; - -import java.util.Arrays; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * Decodes RSS-14, including truncated and stacked variants. See ISO/IEC 24724:2006. - */ -public final class RSS14Reader extends AbstractRSSReader { - - private static final int[] OUTSIDE_EVEN_TOTAL_SUBSET = {1,10,34,70,126}; - private static final int[] INSIDE_ODD_TOTAL_SUBSET = {4,20,48,81}; - private static final int[] OUTSIDE_GSUM = {0,161,961,2015,2715}; - private static final int[] INSIDE_GSUM = {0,336,1036,1516}; - private static final int[] OUTSIDE_ODD_WIDEST = {8,6,4,3,1}; - private static final int[] INSIDE_ODD_WIDEST = {2,4,6,8}; - - private static final int[][] FINDER_PATTERNS = { - {3,8,2,1}, - {3,5,5,1}, - {3,3,7,1}, - {3,1,9,1}, - {2,7,4,1}, - {2,5,6,1}, - {2,3,8,1}, - {1,5,7,1}, - {1,3,9,1}, - }; - - private final List possibleLeftPairs; - private final List possibleRightPairs; - - public RSS14Reader() { - possibleLeftPairs = new ArrayList<>(); - possibleRightPairs = new ArrayList<>(); - } - - @Override - public RXingResult decodeRow(int rowNumber, - BitArray row, - Map hints) throws NotFoundException { - Pair leftPair = decodePair(row, false, rowNumber, hints); - addOrTally(possibleLeftPairs, leftPair); - row.reverse(); - Pair rightPair = decodePair(row, true, rowNumber, hints); - addOrTally(possibleRightPairs, rightPair); - row.reverse(); - for (Pair left : possibleLeftPairs) { - if (left.getCount() > 1) { - for (Pair right : possibleRightPairs) { - if (right.getCount() > 1 && checkChecksum(left, right)) { - return constructRXingResult(left, right); - } - } - } - } - throw NotFoundException.getNotFoundInstance(); - } - - private static void addOrTally(Collection possiblePairs, Pair pair) { - if (pair == null) { - return; - } - boolean found = false; - for (Pair other : possiblePairs) { - if (other.getValue() == pair.getValue()) { - other.incrementCount(); - found = true; - break; - } - } - if (!found) { - possiblePairs.add(pair); - } - } - - @Override - public void reset() { - possibleLeftPairs.clear(); - possibleRightPairs.clear(); - } - - private static RXingResult constructRXingResult(Pair leftPair, Pair rightPair) { - long symbolValue = 4537077L * leftPair.getValue() + rightPair.getValue(); - String text = String.valueOf(symbolValue); - - StringBuilder buffer = new StringBuilder(14); - for (int i = 13 - text.length(); i > 0; i--) { - buffer.append('0'); - } - buffer.append(text); - - int checkDigit = 0; - for (int i = 0; i < 13; i++) { - int digit = buffer.charAt(i) - '0'; - checkDigit += (i & 0x01) == 0 ? 3 * digit : digit; - } - checkDigit = 10 - (checkDigit % 10); - if (checkDigit == 10) { - checkDigit = 0; - } - buffer.append(checkDigit); - - RXingResultPoint[] leftPoints = leftPair.getFinderPattern().getRXingResultPoints(); - RXingResultPoint[] rightPoints = rightPair.getFinderPattern().getRXingResultPoints(); - RXingResult result = new RXingResult( - buffer.toString(), - null, - new RXingResultPoint[] { leftPoints[0], leftPoints[1], rightPoints[0], rightPoints[1], }, - BarcodeFormat.RSS_14); - result.putMetadata(RXingResultMetadataType.SYMBOLOGY_IDENTIFIER, "]e0"); - return result; - } - - private static boolean checkChecksum(Pair leftPair, Pair rightPair) { - int checkValue = (leftPair.getChecksumPortion() + 16 * rightPair.getChecksumPortion()) % 79; - int targetCheckValue = - 9 * leftPair.getFinderPattern().getValue() + rightPair.getFinderPattern().getValue(); - if (targetCheckValue > 72) { - targetCheckValue--; - } - if (targetCheckValue > 8) { - targetCheckValue--; - } - return checkValue == targetCheckValue; - } - - private Pair decodePair(BitArray row, boolean right, int rowNumber, Map hints) { - try { - int[] startEnd = findFinderPattern(row, right); - FinderPattern pattern = parseFoundFinderPattern(row, rowNumber, right, startEnd); - - RXingResultPointCallback resultPointCallback = hints == null ? null : - (RXingResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); - - if (resultPointCallback != null) { - startEnd = pattern.getStartEnd(); - float center = (startEnd[0] + startEnd[1] - 1) / 2.0f; - if (right) { - // row is actually reversed - center = row.getSize() - 1 - center; - } - resultPointCallback.foundPossibleRXingResultPoint(new RXingResultPoint(center, rowNumber)); - } - - DataCharacter outside = decodeDataCharacter(row, pattern, true); - DataCharacter inside = decodeDataCharacter(row, pattern, false); - return new Pair(1597 * outside.getValue() + inside.getValue(), - outside.getChecksumPortion() + 4 * inside.getChecksumPortion(), - pattern); - } catch (NotFoundException ignored) { - return null; - } - } - - private DataCharacter decodeDataCharacter(BitArray row, FinderPattern pattern, boolean outsideChar) - throws NotFoundException { - - int[] counters = getDataCharacterCounters(); - Arrays.fill(counters, 0); - - if (outsideChar) { - 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; - } - } - - int numModules = outsideChar ? 16 : 15; - float elementWidth = MathUtils.sum(counters) / (float) numModules; - - int[] oddCounts = this.getOddCounts(); - int[] evenCounts = this.getEvenCounts(); - float[] oddRoundingErrors = this.getOddRoundingErrors(); - float[] evenRoundingErrors = this.getEvenRoundingErrors(); - - for (int i = 0; i < counters.length; i++) { - float value = counters[i] / elementWidth; - int count = (int) (value + 0.5f); // Round - if (count < 1) { - count = 1; - } else if (count > 8) { - count = 8; - } - int offset = i / 2; - if ((i & 0x01) == 0) { - oddCounts[offset] = count; - oddRoundingErrors[offset] = value - count; - } else { - evenCounts[offset] = count; - evenRoundingErrors[offset] = value - count; - } - } - - adjustOddEvenCounts(outsideChar, numModules); - - int oddSum = 0; - int oddChecksumPortion = 0; - for (int i = oddCounts.length - 1; i >= 0; i--) { - oddChecksumPortion *= 9; - oddChecksumPortion += oddCounts[i]; - oddSum += oddCounts[i]; - } - int evenChecksumPortion = 0; - int evenSum = 0; - for (int i = evenCounts.length - 1; i >= 0; i--) { - evenChecksumPortion *= 9; - evenChecksumPortion += evenCounts[i]; - evenSum += evenCounts[i]; - } - int checksumPortion = oddChecksumPortion + 3 * evenChecksumPortion; - - if (outsideChar) { - if ((oddSum & 0x01) != 0 || oddSum > 12 || oddSum < 4) { - throw NotFoundException.getNotFoundInstance(); - } - int group = (12 - oddSum) / 2; - int oddWidest = OUTSIDE_ODD_WIDEST[group]; - int evenWidest = 9 - oddWidest; - int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, false); - int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, true); - int tEven = OUTSIDE_EVEN_TOTAL_SUBSET[group]; - int gSum = OUTSIDE_GSUM[group]; - return new DataCharacter(vOdd * tEven + vEven + gSum, checksumPortion); - } else { - if ((evenSum & 0x01) != 0 || evenSum > 10 || evenSum < 4) { - throw NotFoundException.getNotFoundInstance(); - } - int group = (10 - evenSum) / 2; - int oddWidest = INSIDE_ODD_WIDEST[group]; - int evenWidest = 9 - oddWidest; - int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, true); - int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, false); - int tOdd = INSIDE_ODD_TOTAL_SUBSET[group]; - int gSum = INSIDE_GSUM[group]; - return new DataCharacter(vEven * tOdd + vOdd + gSum, checksumPortion); - } - - } - - private int[] findFinderPattern(BitArray row, boolean rightFinderPattern) - throws NotFoundException { - - int[] counters = getDecodeFinderCounters(); - counters[0] = 0; - counters[1] = 0; - counters[2] = 0; - counters[3] = 0; - - int width = row.getSize(); - boolean isWhite = false; - int rowOffset = 0; - while (rowOffset < width) { - isWhite = !row.get(rowOffset); - if (rightFinderPattern == isWhite) { - // Will encounter white first when searching for right finder pattern - break; - } - rowOffset++; - } - - int counterPosition = 0; - int patternStart = rowOffset; - for (int x = rowOffset; x < width; x++) { - if (row.get(x) != isWhite) { - counters[counterPosition]++; - } else { - if (counterPosition == 3) { - if (isFinderPattern(counters)) { - return new int[]{patternStart, x}; - } - patternStart += counters[0] + counters[1]; - counters[0] = counters[2]; - counters[1] = counters[3]; - counters[2] = 0; - counters[3] = 0; - counterPosition--; - } else { - counterPosition++; - } - counters[counterPosition] = 1; - isWhite = !isWhite; - } - } - throw NotFoundException.getNotFoundInstance(); - - } - - private FinderPattern parseFoundFinderPattern(BitArray row, int rowNumber, boolean right, int[] startEnd) - throws NotFoundException { - // Actually we found elements 2-5 - boolean firstIsBlack = row.get(startEnd[0]); - int firstElementStart = startEnd[0] - 1; - // Locate element 1 - while (firstElementStart >= 0 && firstIsBlack != row.get(firstElementStart)) { - firstElementStart--; - } - firstElementStart++; - int firstCounter = startEnd[0] - firstElementStart; - // Make 'counters' hold 1-4 - int[] counters = getDecodeFinderCounters(); - System.arraycopy(counters, 0, counters, 1, counters.length - 1); - counters[0] = firstCounter; - int value = parseFinderValue(counters, FINDER_PATTERNS); - int start = firstElementStart; - int end = startEnd[1]; - if (right) { - // row is actually reversed - start = row.getSize() - 1 - start; - end = row.getSize() - 1 - end; - } - return new FinderPattern(value, new int[] {firstElementStart, startEnd[1]}, start, end, rowNumber); - } - - private void adjustOddEvenCounts(boolean outsideChar, int numModules) throws NotFoundException { - - int oddSum = MathUtils.sum(getOddCounts()); - int evenSum = MathUtils.sum(getEvenCounts()); - - boolean incrementOdd = false; - boolean decrementOdd = false; - boolean incrementEven = false; - boolean decrementEven = false; - - if (outsideChar) { - if (oddSum > 12) { - decrementOdd = true; - } else if (oddSum < 4) { - incrementOdd = true; - } - if (evenSum > 12) { - decrementEven = true; - } else if (evenSum < 4) { - incrementEven = true; - } - } else { - if (oddSum > 11) { - decrementOdd = true; - } else if (oddSum < 5) { - incrementOdd = true; - } - if (evenSum > 10) { - decrementEven = true; - } else if (evenSum < 4) { - incrementEven = true; - } - } - - int mismatch = oddSum + evenSum - numModules; - boolean oddParityBad = (oddSum & 0x01) == (outsideChar ? 1 : 0); - boolean evenParityBad = (evenSum & 0x01) == 1; - /*if (mismatch == 2) { - if (!(oddParityBad && evenParityBad)) { - throw ReaderException.getInstance(); - } - decrementOdd = true; - decrementEven = true; - } else if (mismatch == -2) { - if (!(oddParityBad && evenParityBad)) { - throw ReaderException.getInstance(); - } - incrementOdd = true; - incrementEven = true; - } else */ - 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(getOddCounts(), getOddRoundingErrors()); - } - if (decrementOdd) { - decrement(getOddCounts(), getOddRoundingErrors()); - } - if (incrementEven) { - if (decrementEven) { - throw NotFoundException.getNotFoundInstance(); - } - increment(getEvenCounts(), getOddRoundingErrors()); - } - if (decrementEven) { - decrement(getEvenCounts(), getEvenRoundingErrors()); - } - - } - -} diff --git a/src/oned/rss/RSSUtils.java b/src/oned/rss/RSSUtils.java deleted file mode 100644 index 94f93e5..0000000 --- a/src/oned/rss/RSSUtils.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2009 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.oned.rss; - -/** Adapted from listings in ISO/IEC 24724 Appendix B and Appendix G. */ -public final class RSSUtils { - - private RSSUtils() {} - - public static int getRSSvalue(int[] widths, int maxWidth, boolean noNarrow) { - int n = 0; - for (int width : widths) { - n += width; - } - int val = 0; - int narrowMask = 0; - int elements = widths.length; - for (int bar = 0; bar < elements - 1; bar++) { - int elmWidth; - for (elmWidth = 1, narrowMask |= 1 << bar; - elmWidth < widths[bar]; - elmWidth++, narrowMask &= ~(1 << bar)) { - int subVal = combins(n - elmWidth - 1, elements - bar - 2); - if (noNarrow && (narrowMask == 0) && - (n - elmWidth - (elements - bar - 1) >= elements - bar - 1)) { - subVal -= combins(n - elmWidth - (elements - bar), - elements - bar - 2); - } - if (elements - bar - 1 > 1) { - int lessVal = 0; - for (int mxwElement = n - elmWidth - (elements - bar - 2); - mxwElement > maxWidth; mxwElement--) { - lessVal += combins(n - elmWidth - mxwElement - 1, - elements - bar - 3); - } - subVal -= lessVal * (elements - 1 - bar); - } else if (n - elmWidth > maxWidth) { - subVal--; - } - val += subVal; - } - n -= elmWidth; - } - return val; - } - - private static int combins(int n, int r) { - int maxDenom; - int minDenom; - if (n - r > r) { - minDenom = r; - maxDenom = n - r; - } else { - minDenom = n - r; - maxDenom = r; - } - int val = 1; - int j = 1; - for (int i = n; i > maxDenom; i--) { - val *= i; - if (j <= minDenom) { - val /= j; - j++; - } - } - while (j <= minDenom) { - val /= j; - j++; - } - return val; - } - -} diff --git a/src/oned/rss/abstract_rss_reader.rs b/src/oned/rss/abstract_rss_reader.rs new file mode 100644 index 0000000..b3af23d --- /dev/null +++ b/src/oned/rss/abstract_rss_reader.rs @@ -0,0 +1,134 @@ +/* + * 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. + */ + +use crate::{oned::OneDReader, Exceptions}; + +/** + * Superclass of {@link OneDReader} implementations that read barcodes in the RSS family + * of formats. + */ +pub trait AbstractRSSReaderTrait: OneDReader { + const MAX_AVG_VARIANCE: f32 = 0.2; + const MAX_INDIVIDUAL_VARIANCE: f32 = 0.45; + + const MIN_FINDER_PATTERN_RATIO: f32 = 9.5 / 12.0; + const MAX_FINDER_PATTERN_RATIO: f32 = 12.5 / 14.0; + + // private final int[] decodeFinderCounters; + // private final int[] dataCharacterCounters; + // private final float[] oddRoundingErrors; + // private final float[] evenRoundingErrors; + // private final int[] oddCounts; + // private final int[] evenCounts; + + // protected AbstractRSSReader() { + // decodeFinderCounters = new int[4]; + // dataCharacterCounters = new int[8]; + // oddRoundingErrors = new float[4]; + // evenRoundingErrors = new float[4]; + // oddCounts = new int[dataCharacterCounters.length / 2]; + // evenCounts = new int[dataCharacterCounters.length / 2]; + // } + + fn getDecodeFinderCounters(&mut self) -> &mut [u32]; + + fn getDataCharacterCounters(&mut self) -> &mut [u32]; + + fn getOddRoundingErrors(&mut self) -> &mut [f32]; + + fn getEvenRoundingErrors(&mut self) -> &mut [f32]; + + fn getOddCounts(&mut self) -> &mut [u32]; + + fn getEvenCounts(&mut self) -> &mut [u32]; + + fn parseFinderValue( + &self, + counters: &[u32], + finderPatterns: &[[u32; 4]; 9], + ) -> Result { + for value in 0..finderPatterns.len() { + // for (int value = 0; value < finderPatterns.length; value++) { + if self.patternMatchVariance( + counters, + &finderPatterns[value], + Self::MAX_INDIVIDUAL_VARIANCE, + ) < Self::MAX_AVG_VARIANCE + { + return Ok(value as u32); + } + } + Err(Exceptions::NotFoundException("".to_owned())) + } + + /** + * @param array values to sum + * @return sum of values + * @deprecated call {@link MathUtils#sum(int[])} + */ + #[deprecated] + fn count(array: &[u32]) -> u32 { + array.iter().sum::() + } + + fn increment(array: &mut [u32], errors: &[f32]) { + let mut index = 0; + let mut biggestError = errors[0]; + for i in 1..array.len() { + // for (int i = 1; i < array.length; i++) { + if errors[i] > biggestError { + biggestError = errors[i]; + index = i; + } + } + array[index] += 1; + } + + fn decrement(array: &mut [u32], errors: &[f32]) { + let mut index = 0; + let mut biggestError = errors[0]; + for i in 1..array.len() { + // for (int i = 1; i < array.length; i++) { + if errors[i] < biggestError { + biggestError = errors[i]; + index = i; + } + } + array[index] -= 1; + } + + fn isFinderPattern(&self, counters: &[u32]) -> bool { + let firstTwoSum = counters[0] + counters[1]; + let sum = firstTwoSum + counters[2] + counters[3]; + let ratio: f32 = (firstTwoSum as f32) / (sum as f32); + if ratio >= Self::MIN_FINDER_PATTERN_RATIO && ratio <= Self::MAX_FINDER_PATTERN_RATIO { + // passes ratio test in spec, but see if the counts are unreasonable + let mut minCounter = u32::MAX; + let mut maxCounter = u32::MIN; + for counter in counters { + // for (int counter : counters) { + if *counter > maxCounter { + maxCounter = *counter; + } + if *counter < minCounter { + minCounter = *counter; + } + } + return maxCounter < 10 * minCounter; + } + return false; + } +} diff --git a/src/oned/rss/data_character.rs b/src/oned/rss/data_character.rs new file mode 100644 index 0000000..6d333bc --- /dev/null +++ b/src/oned/rss/data_character.rs @@ -0,0 +1,54 @@ +/* + * Copyright 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::{fmt::Display, hash::Hash}; + +pub(crate) trait DataCharacterTrait: Display + Eq + PartialEq + Hash { + fn getValue(&self) -> u32; + + fn getChecksumPortion(&self) -> u32; +} + +/** + * Encapsulates a since character value in an RSS barcode, including its checksum information. + */ +#[derive(Hash, PartialEq, Eq)] +pub struct DataCharacter { + value: u32, + checksumPortion: u32, +} +impl DataCharacterTrait for DataCharacter { + fn getValue(&self) -> u32 { + self.value + } + + fn getChecksumPortion(&self) -> u32 { + self.checksumPortion + } +} +impl DataCharacter { + pub fn new(value: u32, checksumPortion: u32) -> Self { + Self { + value, + checksumPortion, + } + } +} +impl Display for DataCharacter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}({})", self.value, self.checksumPortion) + } +} diff --git a/src/oned/rss/finder_pattern.rs b/src/oned/rss/finder_pattern.rs new file mode 100644 index 0000000..c614cbd --- /dev/null +++ b/src/oned/rss/finder_pattern.rs @@ -0,0 +1,65 @@ +/* + * Copyright 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::hash::Hash; + +use crate::RXingResultPoint; + +/** + * Encapsulates an RSS barcode finder pattern, including its start/end position and row. + */ +pub struct FinderPattern { + value: u32, + startEnd: [usize; 2], + resultPoints: Vec, +} + +impl FinderPattern { + pub fn new(value: u32, startEnd: [usize; 2], start: usize, end: usize, rowNumber: u32) -> Self { + Self { + value, + startEnd, + resultPoints: vec![ + RXingResultPoint::new(start as f32, rowNumber as f32), + RXingResultPoint::new(end as f32, rowNumber as f32), + ], + } + } + + pub fn getValue(&self) -> u32 { + self.value + } + + pub fn getStartEnd(&self) -> &[usize] { + &self.startEnd + } + + pub fn getRXingResultPoints(&self) -> &[RXingResultPoint] { + &self.resultPoints + } +} + +impl PartialEq for FinderPattern { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} +impl Eq for FinderPattern {} +impl Hash for FinderPattern { + fn hash(&self, state: &mut H) { + self.value.hash(state); + } +} diff --git a/src/oned/rss/mod.rs b/src/oned/rss/mod.rs index ff1db53..9de9673 100644 --- a/src/oned/rss/mod.rs +++ b/src/oned/rss/mod.rs @@ -1 +1,18 @@ pub mod expanded; + +mod finder_pattern; +pub use finder_pattern::*; + +mod pair; +pub use pair::*; + +mod data_character; +pub use data_character::*; + +pub mod rss_utils; + +mod abstract_rss_reader; +pub use abstract_rss_reader::*; + +mod rss_14_reader; +pub use rss_14_reader::*; diff --git a/src/oned/rss/pair.rs b/src/oned/rss/pair.rs new file mode 100644 index 0000000..723eca9 --- /dev/null +++ b/src/oned/rss/pair.rs @@ -0,0 +1,63 @@ +use std::fmt::Display; + +use super::{DataCharacter, DataCharacterTrait, FinderPattern}; + +/* + * Copyright 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#[derive(Hash, Eq, PartialEq)] +pub struct Pair { + finderPattern: FinderPattern, + count: u32, + internal_data_character: DataCharacter, +} + +impl DataCharacterTrait for Pair { + fn getValue(&self) -> u32 { + self.internal_data_character.getValue() + } + + fn getChecksumPortion(&self) -> u32 { + self.internal_data_character.getChecksumPortion() + } +} + +impl Pair { + pub fn new(value: u32, checksumPortion: u32, finderPattern: FinderPattern) -> Self { + Self { + finderPattern, + count: 0, + internal_data_character: DataCharacter::new(value, checksumPortion), + } + } + + pub fn getFinderPattern(&self) -> &FinderPattern { + &self.finderPattern + } + + pub fn getCount(&self) -> u32 { + self.count + } + + pub fn incrementCount(&mut self) { + self.count += 1; + } +} + +impl Display for Pair { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.internal_data_character.fmt(f) + } +} diff --git a/src/oned/rss/rss_14_reader.rs b/src/oned/rss/rss_14_reader.rs new file mode 100644 index 0000000..2bff9b6 --- /dev/null +++ b/src/oned/rss/rss_14_reader.rs @@ -0,0 +1,641 @@ +/* + * Copyright 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::collections::HashMap; + +use crate::{ + common::{detector::MathUtils, BitArray}, + oned::OneDReader, + BarcodeFormat, DecodeHintType, DecodingHintDictionary, Exceptions, RXingResult, + RXingResultMetadataType, RXingResultMetadataValue, RXingResultPoint, Reader, ResultPoint, +}; + +use super::{ + rss_utils, AbstractRSSReaderTrait, DataCharacter, DataCharacterTrait, FinderPattern, Pair, +}; + +/** + * Decodes RSS-14, including truncated and stacked variants. See ISO/IEC 24724:2006. + */ +pub struct RSS14Reader { + possibleLeftPairs: Vec, + possibleRightPairs: Vec, + decodeFinderCounters: [u32; 4], + dataCharacterCounters: [u32; 8], + oddRoundingErrors: [f32; 4], + evenRoundingErrors: [f32; 4], + oddCounts: [u32; 4], + evenCounts: [u32; 4], +} + +impl AbstractRSSReaderTrait for RSS14Reader { + fn getDecodeFinderCounters(&mut self) -> &mut [u32] { + &mut self.decodeFinderCounters + } + + fn getDataCharacterCounters(&mut self) -> &mut [u32] { + &mut self.dataCharacterCounters + } + + fn getOddRoundingErrors(&mut self) -> &mut [f32] { + &mut self.oddRoundingErrors + } + + fn getEvenRoundingErrors(&mut self) -> &mut [f32] { + &mut self.evenRoundingErrors + } + + fn getOddCounts(&mut self) -> &mut [u32] { + &mut self.oddCounts + } + + fn getEvenCounts(&mut self) -> &mut [u32] { + &mut self.evenCounts + } +} +impl OneDReader for RSS14Reader { + fn decodeRow( + &mut self, + rowNumber: u32, + row: &crate::common::BitArray, + hints: &crate::DecodingHintDictionary, + ) -> Result { + let mut row = row.clone(); + let leftPair = self.decodePair(&row, false, rowNumber, hints); + Self::addOrTally(&mut self.possibleLeftPairs, leftPair); + row.reverse(); + let rightPair = self.decodePair(&row, true, rowNumber, hints); + Self::addOrTally(&mut self.possibleRightPairs, rightPair); + row.reverse(); + for left in &self.possibleLeftPairs { + // for (Pair left : possibleLeftPairs) { + if left.getCount() > 1 { + for right in &self.possibleRightPairs { + // for (Pair right : possibleRightPairs) { + if right.getCount() > 1 && self.checkChecksum(&left, &right) { + return Ok(self.constructRXingResult(&left, &right)); + } + } + } + } + return Err(Exceptions::NotFoundException("".to_owned())); + } +} +impl Reader for RSS14Reader { + 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.possibleLeftPairs.clear(); + self.possibleRightPairs.clear(); + } +} + +impl RSS14Reader { + const OUTSIDE_EVEN_TOTAL_SUBSET: [u32; 5] = [1, 10, 34, 70, 126]; + const INSIDE_ODD_TOTAL_SUBSET: [u32; 4] = [4, 20, 48, 81]; + const OUTSIDE_GSUM: [u32; 5] = [0, 161, 961, 2015, 2715]; + const INSIDE_GSUM: [u32; 4] = [0, 336, 1036, 1516]; + const OUTSIDE_ODD_WIDEST: [u32; 5] = [8, 6, 4, 3, 1]; + const INSIDE_ODD_WIDEST: [u32; 4] = [2, 4, 6, 8]; + + const FINDER_PATTERNS: [[u32; 4]; 9] = [ + [3, 8, 2, 1], + [3, 5, 5, 1], + [3, 3, 7, 1], + [3, 1, 9, 1], + [2, 7, 4, 1], + [2, 5, 6, 1], + [2, 3, 8, 1], + [1, 5, 7, 1], + [1, 3, 9, 1], + ]; + + pub fn new() -> Self { + Self { + possibleLeftPairs: Vec::new(), + possibleRightPairs: Vec::new(), + decodeFinderCounters: [0u32; 4], + dataCharacterCounters: [0u32; 8], + oddRoundingErrors: [0.0; 4], + evenRoundingErrors: [0.0; 4], + oddCounts: [0u32; 4], //vec![0u32;4], + evenCounts: [0u32; 4], //vec![0u32;4], + } + } + + fn addOrTally(possiblePairs: &mut Vec, pair: Option) { + let Some(pair) = pair else { + return; + }; + + let mut found = false; + for other in possiblePairs.iter_mut() { + // for (Pair other : possiblePairs) { + if other.getValue() == pair.getValue() { + other.incrementCount(); + found = true; + break; + } + } + if !found { + possiblePairs.push(pair); + } + } + + fn constructRXingResult(&self, leftPair: &Pair, rightPair: &Pair) -> RXingResult { + let symbolValue: u64 = 4537077 * leftPair.getValue() as u64 + rightPair.getValue() as u64; + let text = symbolValue.to_string(); + + let mut buffer = String::with_capacity(14); + let mut i = 13 - text.chars().count() as isize; + while i > 0 { + // for (int i = 13 - text.length(); i > 0; i--) { + buffer.push('0'); + + i -= 1; + } + buffer.push_str(&text); + + let mut checkDigit = 0; + for i in 0..13 { + // for (int i = 0; i < 13; i++) { + let digit = buffer.chars().nth(i).unwrap() as u32 - '0' as u32; + checkDigit += if (i & 0x01) == 0 { 3 * digit } else { digit }; + } + checkDigit = 10 - (checkDigit % 10); + if checkDigit == 10 { + checkDigit = 0; + } + buffer.push_str(&checkDigit.to_string()); + + let leftPoints = leftPair.getFinderPattern().getRXingResultPoints(); + let rightPoints = rightPair.getFinderPattern().getRXingResultPoints(); + let mut result = RXingResult::new( + &buffer, + Vec::new(), + vec![leftPoints[0], leftPoints[1], rightPoints[0], rightPoints[1]], + BarcodeFormat::RSS_14, + ); + + result.putMetadata( + RXingResultMetadataType::SYMBOLOGY_IDENTIFIER, + RXingResultMetadataValue::SymbologyIdentifier("]e0".to_owned()), + ); + + result + } + + fn checkChecksum(&self, leftPair: &Pair, rightPair: &Pair) -> bool { + let checkValue = (leftPair.getChecksumPortion() + 16 * rightPair.getChecksumPortion()) % 79; + let mut targetCheckValue = + 9 * leftPair.getFinderPattern().getValue() + rightPair.getFinderPattern().getValue(); + if targetCheckValue > 72 { + targetCheckValue -= 1; + } + if targetCheckValue > 8 { + targetCheckValue -= 1; + } + checkValue == targetCheckValue + } + + fn decodePair( + &mut self, + row: &BitArray, + right: bool, + rowNumber: u32, + hints: &DecodingHintDictionary, + ) -> Option { + let pos_pair = || -> Result { + let startEnd = self.findFinderPattern(row, right)?; + let pattern = self.parseFoundFinderPattern(row, rowNumber, right, &startEnd)?; + + // RXingResultPointCallback resultPointCallback = hints == null ? null : + // (RXingResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); + + // if (resultPointCallback != null) { + // startEnd = pattern.getStartEnd(); + // float center = (startEnd[0] + startEnd[1] - 1) / 2.0f; + // if (right) { + // // row is actually reversed + // center = row.getSize() - 1 - center; + // } + // resultPointCallback.foundPossibleRXingResultPoint(new RXingResultPoint(center, rowNumber)); + // } + + let outside = self.decodeDataCharacter(row, &pattern, true)?; + let inside = self.decodeDataCharacter(row, &pattern, false)?; + + // todo!("must add callback"); + + Ok(Pair::new( + 1597 * outside.getValue() + inside.getValue(), + outside.getChecksumPortion() + 4 * inside.getChecksumPortion(), + pattern, + )) + }(); + if pos_pair.is_err() { + None + } else { + Some(pos_pair.unwrap()) + } + } + + fn decodeDataCharacter( + &mut self, + row: &BitArray, + pattern: &FinderPattern, + outsideChar: bool, + ) -> Result { + let mut counters = [0_u32; 8]; //self.getDataCharacterCounters(); + //Arrays.fill(counters, 0); + // counters.fill(0); + + if outsideChar { + self.recordPatternInReverse(row, pattern.getStartEnd()[0], &mut counters)?; + } else { + self.recordPattern(row, pattern.getStartEnd()[1], &mut counters)?; + // reverse it + let mut i = 0; + let mut j = counters.len(); + 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; + } + } + + let numModules = if outsideChar { 16 } else { 15 }; + + let elementWidth: f32 = counters.iter().sum::() as f32 / numModules as f32; + + let mut oddCounts = [0u32; 4]; //self.getOddCounts(); + let mut evenCounts = [0u32; 4]; // self.getEvenCounts(); + let mut oddRoundingErrors = [0f32; 4]; // self.getOddRoundingErrors(); + let mut evenRoundingErrors = [0f32; 4]; // self.getEvenRoundingErrors(); + + for i in 0..counters.len() { + // for (int i = 0; i < counters.length; i++) { + let value: f32 = counters[i] as f32 / elementWidth; + let mut count = (value + 0.5) as u32; // Round + if count < 1 { + count = 1; + } else if count > 8 { + count = 8; + } + let offset = i / 2; + if (i & 0x01) == 0 { + oddCounts[offset] = count; + oddRoundingErrors[offset] = value - count as f32; + } else { + evenCounts[offset] = count; + evenRoundingErrors[offset] = value - count as f32; + } + } + + self.adjustOddEvenCounts(outsideChar, numModules)?; + + let mut oddSum = 0; + let mut oddChecksumPortion = 0; + for i in (0..oddCounts.len()).rev() { + // for (int i = oddCounts.length - 1; i >= 0; i--) { + oddChecksumPortion *= 9; + oddChecksumPortion += oddCounts[i]; + oddSum += oddCounts[i]; + } + let mut evenChecksumPortion = 0; + let mut evenSum = 0; + for i in (0..evenCounts.len()).rev() { + // for (int i = evenCounts.length - 1; i >= 0; i--) { + evenChecksumPortion *= 9; + evenChecksumPortion += evenCounts[i]; + evenSum += evenCounts[i]; + } + let checksumPortion = oddChecksumPortion + 3 * evenChecksumPortion; + + self.oddCounts = oddCounts; + self.evenCounts = evenCounts; + self.oddRoundingErrors = oddRoundingErrors; + self.evenRoundingErrors = evenRoundingErrors; + + if outsideChar { + if (oddSum & 0x01) != 0 || oddSum > 12 || oddSum < 4 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + let group = ((12 - oddSum) / 2) as usize; + let oddWidest = Self::OUTSIDE_ODD_WIDEST[group]; + let evenWidest = 9 - oddWidest; + let vOdd = rss_utils::getRSSvalue(&oddCounts, oddWidest, false); + let vEven = rss_utils::getRSSvalue(&evenCounts, evenWidest, true); + let tEven = Self::OUTSIDE_EVEN_TOTAL_SUBSET[group]; + let gSum = Self::OUTSIDE_GSUM[group]; + return Ok(DataCharacter::new( + vOdd * tEven + vEven + gSum, + checksumPortion, + )); + } else { + if (evenSum & 0x01) != 0 || evenSum > 10 || evenSum < 4 { + return Err(Exceptions::NotFoundException("".to_owned())); + } + let group = ((10 - evenSum) / 2) as usize; + let oddWidest = Self::INSIDE_ODD_WIDEST[group]; + let evenWidest = 9 - oddWidest; + let vOdd = rss_utils::getRSSvalue(&oddCounts, oddWidest, true); + let vEven = rss_utils::getRSSvalue(&evenCounts, evenWidest, false); + let tOdd = Self::INSIDE_ODD_TOTAL_SUBSET[group]; + let gSum = Self::INSIDE_GSUM[group]; + return Ok(DataCharacter::new( + vEven * tOdd + vOdd + gSum, + checksumPortion, + )); + } + } + + fn findFinderPattern( + &mut self, + row: &BitArray, + rightFinderPattern: bool, + ) -> Result<[usize; 2], Exceptions> { + // let counters = self.getDecodeFinderCounters(); + // counters[0] = 0; + // counters[1] = 0; + // counters[2] = 0; + // counters[3] = 0; + let mut counters = [0u32; 4]; + + let width = row.getSize(); + let mut isWhite = false; + let mut rowOffset = 0; + while rowOffset < width { + isWhite = !row.get(rowOffset); + if rightFinderPattern == isWhite { + // Will encounter white first when searching for right finder pattern + break; + } + rowOffset += 1; + } + + let mut counterPosition = 0; + let mut patternStart = rowOffset; + for x in rowOffset..width { + // for (int x = rowOffset; x < width; x++) { + if row.get(x) != isWhite { + counters[counterPosition] += 1; + } else { + if counterPosition == 3 { + if self.isFinderPattern(&counters) { + return Ok([patternStart, x]); + } + patternStart += (counters[0] + counters[1]) as usize; + 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 parseFoundFinderPattern( + &mut self, + row: &BitArray, + rowNumber: u32, + right: bool, + startEnd: &[usize], + ) -> Result { + // Actually we found elements 2-5 + let firstIsBlack = row.get(startEnd[0]); + let mut firstElementStart = startEnd[0] as isize - 1; + // Locate element 1 + while firstElementStart >= 0 && firstIsBlack != row.get(firstElementStart as usize) { + firstElementStart -= 1; + } + firstElementStart += 1; + let firstCounter = startEnd[0] - firstElementStart as usize; + // Make 'counters' hold 1-4 + // let counters = self.getDecodeFinderCounters(); + // System.arraycopy(counters, 0, counters, 1, counters.length - 1); + // counters.fill(0); + let mut counters = [0u32; 4]; + counters[0] = firstCounter as u32; + let value = self.parseFinderValue(&counters, &Self::FINDER_PATTERNS)?; + let mut start = firstElementStart as usize; + let mut end = startEnd[1]; + if right { + // row is actually reversed + start = row.getSize() - 1 - start; + end = row.getSize() - 1 - end; + } + + Ok(FinderPattern::new( + value, + [firstElementStart as usize, startEnd[1]], + start, + end, + rowNumber, + )) + } + + fn adjustOddEvenCounts( + &mut self, + outsideChar: bool, + numModules: u32, + ) -> Result<(), Exceptions> { + let oddSum = self.getOddCounts().iter().sum::(); //MathUtils.sum(getOddCounts()); + let evenSum = self.getEvenCounts().iter().sum::(); //MathUtils.sum(getEvenCounts()); + + let mut incrementOdd = false; + let mut decrementOdd = false; + let mut incrementEven = false; + let mut decrementEven = false; + + if outsideChar { + if oddSum > 12 { + decrementOdd = true; + } else if oddSum < 4 { + incrementOdd = true; + } + if evenSum > 12 { + decrementEven = true; + } else if evenSum < 4 { + incrementEven = true; + } + } else { + if oddSum > 11 { + decrementOdd = true; + } else if oddSum < 5 { + incrementOdd = true; + } + if evenSum > 10 { + decrementEven = true; + } else if evenSum < 4 { + incrementEven = true; + } + } + + let mismatch = oddSum as i32 + evenSum as i32 - numModules as i32; + let oddParityBad = (oddSum & 0x01) == (if outsideChar { 1 } else { 0 }); + let evenParityBad = (evenSum & 0x01) == 1; + /*if (mismatch == 2) { + if (!(oddParityBad && evenParityBad)) { + throw ReaderException.getInstance(); + } + decrementOdd = true; + decrementEven = true; + } else if (mismatch == -2) { + if (!(oddParityBad && evenParityBad)) { + throw ReaderException.getInstance(); + } + incrementOdd = true; + incrementEven = true; + } else */ + 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.evenRoundingErrors); + } + if decrementEven { + Self::decrement(&mut self.evenCounts, &self.evenRoundingErrors); + } + Ok(()) + } +} diff --git a/src/oned/rss/rss_utils.rs b/src/oned/rss/rss_utils.rs new file mode 100644 index 0000000..f271e36 --- /dev/null +++ b/src/oned/rss/rss_utils.rs @@ -0,0 +1,99 @@ +/* + * Copyright 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** Adapted from listings in ISO/IEC 24724 Appendix B and Appendix G. */ + +pub fn getRSSvalue(widths: &[u32], maxWidth: u32, noNarrow: bool) -> u32 { + let mut n = 0; + // for width in widths { + // // for (int width : widths) { + // n += width; + // } + n += widths.iter().sum::(); + + let mut val = 0; + let mut narrowMask = 0; + let elements = widths.len() as u32; + for bar in 0..(elements - 1) { + // for (int bar = 0; bar < elements - 1; bar++) { + // let elmWidth; + let mut elmWidth = 1; + narrowMask |= 1 << bar; + while elmWidth < widths[bar as usize] { + // for (elmWidth = 1, narrowMask |= 1 << bar; + // elmWidth < widths[bar]; + // elmWidth++, narrowMask &= ~(1 << bar)) { + let mut subVal = combins(n - elmWidth - 1, elements - bar - 2); + if noNarrow + && (narrowMask == 0) + && (n - elmWidth - (elements - bar - 1) >= elements - bar - 1) + { + subVal -= combins(n - elmWidth - (elements - bar), elements - bar - 2); + } + if elements - bar - 1 > 1 { + let mut lessVal = 0; + let mut mxwElement = n - elmWidth - (elements - bar - 2); + while mxwElement > maxWidth { + // for (int mxwElement = n - elmWidth - (elements - bar - 2); + // mxwElement > maxWidth; mxwElement--) { + lessVal += combins(n - elmWidth - mxwElement - 1, elements - bar - 3); + + mxwElement -= 1; + } + subVal -= lessVal * (elements - 1 - bar); + } else if n - elmWidth > maxWidth { + subVal -= 1; + } + val += subVal; + + elmWidth += 1; + narrowMask &= !(1 << bar) + } + n -= elmWidth; + } + return val; +} + +fn combins(n: u32, r: u32) -> u32 { + let maxDenom; + let minDenom; + if n - r > r { + minDenom = r; + maxDenom = n - r; + } else { + minDenom = n - r; + maxDenom = r; + } + let mut val = 1; + let mut j = 1; + let mut i = n; + while i > maxDenom { + // for (int i = n; i > maxDenom; i--) { + val *= i; + if j <= minDenom { + val /= j; + j += 1; + } + + i -= 1; + } + while j <= minDenom { + val /= j; + j += 1; + } + + val +} diff --git a/tests/RSS14BlackBox1TestCase.java b/tests/RSS14BlackBox1TestCase.java deleted file mode 100644 index 8ce97a4..0000000 --- a/tests/RSS14BlackBox1TestCase.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2008 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.oned.rss; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; - -/** - * @author Sean Owen - */ -public final class RSS14BlackBox1TestCase extends AbstractBlackBoxTestCase { - - public RSS14BlackBox1TestCase() { - super("src/test/resources/blackbox/rss14-1", new MultiFormatReader(), BarcodeFormat.RSS_14); - addTest(6, 6, 0.0f); - addTest(6, 6, 180.0f); - } - -} \ No newline at end of file diff --git a/tests/RSS14BlackBox2TestCase.java b/tests/RSS14BlackBox2TestCase.java deleted file mode 100644 index 3855be8..0000000 --- a/tests/RSS14BlackBox2TestCase.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2008 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.oned.rss; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; - -/** - * @author Sean Owen - */ -public final class RSS14BlackBox2TestCase extends AbstractBlackBoxTestCase { - - public RSS14BlackBox2TestCase() { - super("src/test/resources/blackbox/rss14-2", new MultiFormatReader(), BarcodeFormat.RSS_14); - addTest(4, 8, 1, 1, 0.0f); - addTest(3, 8, 0, 1, 180.0f); - } - -} diff --git a/tests/rss_14_blackbox.rs b/tests/rss_14_blackbox.rs new file mode 100644 index 0000000..ac45d3f --- /dev/null +++ b/tests/rss_14_blackbox.rs @@ -0,0 +1,60 @@ + +/* + * Copyright 2008 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. + */ + + use rxing::{maxicode::MaxiCodeReader, BarcodeFormat, DecodeHintType, MultiFormatReader}; + + mod common; + + /** + * @author Sean Owen + */ + #[test] + fn rss14_black_box1_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/rss14-1", + MultiFormatReader::default(), + BarcodeFormat::RSS_14, + ); + + // super("src/test/resources/blackbox/rss14-1", new MultiFormatReader(), BarcodeFormat.RSS_14); + tester.add_test(6, 6, 0.0); + tester.add_test(6, 6, 180.0); + + tester.test_black_box() + } + + + + /** + * @author Sean Owen + */ +#[test] +fn rss14_black_box2_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/rss14-2", + MultiFormatReader::default(), + BarcodeFormat::RSS_14, + ); + + // super("src/test/resources/blackbox/rss14-2", new MultiFormatReader(), BarcodeFormat.RSS_14); + tester.add_test_complex(4, 8, 1, 1, 0.0); + tester.add_test_complex(3, 8, 0, 1, 180.0); + + tester.test_black_box() + } + + \ No newline at end of file