/* * 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. */ using System; using System.Collections.Generic; using System.Text; using ZXing.Common; namespace ZXing.OneD.RSS { /// /// Decodes RSS-14, including truncated and stacked variants. See ISO/IEC 24724:2006. /// public sealed class RSS14Reader : AbstractRSSReader { private static readonly int[] OUTSIDE_EVEN_TOTAL_SUBSET = { 1, 10, 34, 70, 126 }; private static readonly int[] INSIDE_ODD_TOTAL_SUBSET = { 4, 20, 48, 81 }; private static readonly int[] OUTSIDE_GSUM = { 0, 161, 961, 2015, 2715 }; private static readonly int[] INSIDE_GSUM = { 0, 336, 1036, 1516 }; private static readonly int[] OUTSIDE_ODD_WIDEST = { 8, 6, 4, 3, 1 }; private static readonly int[] INSIDE_ODD_WIDEST = { 2, 4, 6, 8 }; private static readonly int[][] FINDER_PATTERNS = { new[] {3, 8, 2, 1}, new[] {3, 5, 5, 1}, new[] {3, 3, 7, 1}, new[] {3, 1, 9, 1}, new[] {2, 7, 4, 1}, new[] {2, 5, 6, 1}, new[] {2, 3, 8, 1}, new[] {1, 5, 7, 1}, new[] {1, 3, 9, 1}, }; private readonly List possibleLeftPairs; private readonly List possibleRightPairs; /// /// Initializes a new instance of the class. /// public RSS14Reader() { possibleLeftPairs = new List(); possibleRightPairs = new List(); } /// ///

Attempts to decode a one-dimensional barcode format given a single row of /// an image.

///
/// row number from top of the row /// the black/white pixel data of the row /// decode hints /// /// containing encoded string and start/end of barcode or null, if an error occurs or barcode cannot be found /// override public Result decodeRow(int rowNumber, BitArray row, IDictionary hints) { Pair leftPair = decodePair(row, false, rowNumber, hints); addOrTally(possibleLeftPairs, leftPair); row.reverse(); Pair rightPair = decodePair(row, true, rowNumber, hints); addOrTally(possibleRightPairs, rightPair); row.reverse(); int lefSize = possibleLeftPairs.Count; for (int i = 0; i < lefSize; i++) { Pair left = possibleLeftPairs[i]; if (left.Count > 1) { int rightSize = possibleRightPairs.Count; for (int j = 0; j < rightSize; j++) { Pair right = possibleRightPairs[j]; if (right.Count > 1) { if (checkChecksum(left, right)) { return constructResult(left, right); } } } } } return null; } private static void addOrTally(IList possiblePairs, Pair pair) { if (pair == null) { return; } bool found = false; foreach (Pair other in possiblePairs) { if (other.Value == pair.Value) { other.incrementCount(); found = true; break; } } if (!found) { possiblePairs.Add(pair); } } /// /// Resets this instance. /// public override void reset() { possibleLeftPairs.Clear(); possibleRightPairs.Clear(); } private static Result constructResult(Pair leftPair, Pair rightPair) { long symbolValue = 4537077L * leftPair.Value + rightPair.Value; String text = symbolValue.ToString(); 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[i] - '0'; checkDigit += (i & 0x01) == 0 ? 3 * digit : digit; } checkDigit = 10 - (checkDigit % 10); if (checkDigit == 10) { checkDigit = 0; } buffer.Append(checkDigit); ResultPoint[] leftPoints = leftPair.FinderPattern.ResultPoints; ResultPoint[] rightPoints = rightPair.FinderPattern.ResultPoints; return new Result( buffer.ToString(), null, new ResultPoint[] { leftPoints[0], leftPoints[1], rightPoints[0], rightPoints[1], }, BarcodeFormat.RSS_14); } private static bool checkChecksum(Pair leftPair, Pair rightPair) { //int leftFPValue = leftPair.FinderPattern.Value; //int rightFPValue = rightPair.FinderPattern.Value; //if ((leftFPValue == 0 && rightFPValue == 8) || // (leftFPValue == 8 && rightFPValue == 0)) //{ //} int checkValue = (leftPair.ChecksumPortion + 16 * rightPair.ChecksumPortion) % 79; int targetCheckValue = 9 * leftPair.FinderPattern.Value + rightPair.FinderPattern.Value; if (targetCheckValue > 72) { targetCheckValue--; } if (targetCheckValue > 8) { targetCheckValue--; } return checkValue == targetCheckValue; } private Pair decodePair(BitArray row, bool right, int rowNumber, IDictionary hints) { int[] startEnd = findFinderPattern(row, 0, right); if (startEnd == null) return null; FinderPattern pattern = parseFoundFinderPattern(row, rowNumber, right, startEnd); if (pattern == null) return null; ResultPointCallback resultPointCallback = hints == null || !hints.ContainsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK) ? null : (ResultPointCallback)hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK]; if (resultPointCallback != null) { float center = (startEnd[0] + startEnd[1]) / 2.0f; if (right) { // row is actually reversed center = row.Size - 1 - center; } resultPointCallback(new ResultPoint(center, rowNumber)); } DataCharacter outside = decodeDataCharacter(row, pattern, true); if (outside == null) return null; DataCharacter inside = decodeDataCharacter(row, pattern, false); if (inside == null) return null; return new Pair(1597*outside.Value + inside.Value, outside.ChecksumPortion + 4*inside.ChecksumPortion, pattern); } private DataCharacter decodeDataCharacter(BitArray row, FinderPattern pattern, bool outsideChar) { int[] counters = getDataCharacterCounters(); counters[0] = 0; counters[1] = 0; counters[2] = 0; counters[3] = 0; counters[4] = 0; counters[5] = 0; counters[6] = 0; counters[7] = 0; if (outsideChar) { recordPatternInReverse(row, pattern.StartEnd[0], counters); } else { recordPattern(row, pattern.StartEnd[1] + 1, counters); // reverse it for (int i = 0, j = counters.Length - 1; i < j; i++, j--) { int temp = counters[i]; counters[i] = counters[j]; counters[j] = temp; } } int numModules = outsideChar ? 16 : 15; float elementWidth = (float)count(counters) / (float)numModules; int[] oddCounts = this.getOddCounts(); int[] evenCounts = this.getEvenCounts(); float[] oddRoundingErrors = this.getOddRoundingErrors(); float[] evenRoundingErrors = this.getEvenRoundingErrors(); for (int i = 0; i < counters.Length; i++) { float value = (float)counters[i] / elementWidth; int rounded = (int)(value + 0.5f); // Round if (rounded < 1) { rounded = 1; } else if (rounded > 8) { rounded = 8; } int offset = i >> 1; if ((i & 0x01) == 0) { oddCounts[offset] = rounded; oddRoundingErrors[offset] = value - rounded; } else { evenCounts[offset] = rounded; evenRoundingErrors[offset] = value - rounded; } } if (!adjustOddEvenCounts(outsideChar, numModules)) return null; 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) { return null; } 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) { return null; } int group = (10 - evenSum) / 2; int oddWidest = INSIDE_ODD_WIDEST[group]; int evenWidest = 9 - oddWidest; int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, true); int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, false); int tOdd = INSIDE_ODD_TOTAL_SUBSET[group]; int gSum = INSIDE_GSUM[group]; return new DataCharacter(vEven * tOdd + vOdd + gSum, checksumPortion); } } private int[] findFinderPattern(BitArray row, int rowOffset, bool rightFinderPattern) { int[] counters = getDecodeFinderCounters(); counters[0] = 0; counters[1] = 0; counters[2] = 0; counters[3] = 0; int width = row.Size; bool isWhite = false; while (rowOffset < width) { isWhite = !row[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[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; } } return null; } private FinderPattern parseFoundFinderPattern(BitArray row, int rowNumber, bool right, int[] startEnd) { // Actually we found elements 2-5 bool firstIsBlack = row[startEnd[0]]; int firstElementStart = startEnd[0] - 1; // Locate element 1 while (firstElementStart >= 0 && firstIsBlack ^ row[firstElementStart]) { firstElementStart--; } firstElementStart++; int firstCounter = startEnd[0] - firstElementStart; // Make 'counters' hold 1-4 int[] counters = getDecodeFinderCounters(); Array.Copy(counters, 0, counters, 1, counters.Length - 1); counters[0] = firstCounter; int value; if (!parseFinderValue(counters, FINDER_PATTERNS, out value)) return null; int start = firstElementStart; int end = startEnd[1]; if (right) { // row is actually reversed start = row.Size - 1 - start; end = row.Size - 1 - end; } return new FinderPattern(value, new int[] { firstElementStart, startEnd[1] }, start, end, rowNumber); } private bool adjustOddEvenCounts(bool outsideChar, int numModules) { int oddSum = count(getOddCounts()); int evenSum = count(getEvenCounts()); int mismatch = oddSum + evenSum - numModules; bool oddParityBad = (oddSum & 0x01) == (outsideChar ? 1 : 0); bool evenParityBad = (evenSum & 0x01) == 1; bool incrementOdd = false; bool decrementOdd = false; bool incrementEven = false; bool decrementEven = false; if (outsideChar) { if (oddSum > 12) { decrementOdd = true; } else if (oddSum < 4) { incrementOdd = true; } if (evenSum > 12) { decrementEven = true; } else if (evenSum < 4) { incrementEven = true; } } else { if (oddSum > 11) { decrementOdd = true; } else if (oddSum < 5) { incrementOdd = true; } if (evenSum > 10) { decrementEven = true; } else if (evenSum < 4) { incrementEven = true; } } /*if (mismatch == 2) { if (!(oddParityBad && evenParityBad)) { throw ReaderException.Instance; } decrementOdd = true; decrementEven = true; } else if (mismatch == -2) { if (!(oddParityBad && evenParityBad)) { throw ReaderException.Instance; } incrementOdd = true; incrementEven = true; } else */ if (mismatch == 1) { if (oddParityBad) { if (evenParityBad) { return false; } decrementOdd = true; } else { if (!evenParityBad) { return false; } decrementEven = true; } } else if (mismatch == -1) { if (oddParityBad) { if (evenParityBad) { return false; } incrementOdd = true; } else { if (!evenParityBad) { return false; } incrementEven = true; } } else if (mismatch == 0) { if (oddParityBad) { if (!evenParityBad) { return false; } // Both bad if (oddSum < evenSum) { incrementOdd = true; decrementEven = true; } else { decrementOdd = true; incrementEven = true; } } else { if (evenParityBad) { return false; } // Nothing to do! } } else { return false; } if (incrementOdd) { if (decrementOdd) { return false; } increment(getOddCounts(), getOddRoundingErrors()); } if (decrementOdd) { decrement(getOddCounts(), getOddRoundingErrors()); } if (incrementEven) { if (decrementEven) { return false; } increment(getEvenCounts(), getOddRoundingErrors()); } if (decrementEven) { decrement(getEvenCounts(), getEvenRoundingErrors()); } return true; } } }