• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2008 ZXing authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.zxing.oned;
18 
19 import com.google.zxing.BinaryBitmap;
20 import com.google.zxing.ChecksumException;
21 import com.google.zxing.DecodeHintType;
22 import com.google.zxing.FormatException;
23 import com.google.zxing.NotFoundException;
24 import com.google.zxing.Reader;
25 import com.google.zxing.ReaderException;
26 import com.google.zxing.Result;
27 import com.google.zxing.ResultMetadataType;
28 import com.google.zxing.ResultPoint;
29 import com.google.zxing.common.BitArray;
30 
31 import java.util.Arrays;
32 import java.util.EnumMap;
33 import java.util.Map;
34 
35 /**
36  * Encapsulates functionality and implementation that is common to all families
37  * of one-dimensional barcodes.
38  *
39  * @author dswitkin@google.com (Daniel Switkin)
40  * @author Sean Owen
41  */
42 public abstract class OneDReader implements Reader {
43 
44   @Override
decode(BinaryBitmap image)45   public Result decode(BinaryBitmap image) throws NotFoundException, FormatException {
46     return decode(image, null);
47   }
48 
49   // Note that we don't try rotation without the try harder flag, even if rotation was supported.
50   @Override
decode(BinaryBitmap image, Map<DecodeHintType,?> hints)51   public Result decode(BinaryBitmap image,
52                        Map<DecodeHintType,?> hints) throws NotFoundException, FormatException {
53     try {
54       return doDecode(image, hints);
55     } catch (NotFoundException nfe) {
56       boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
57       if (tryHarder && image.isRotateSupported()) {
58         BinaryBitmap rotatedImage = image.rotateCounterClockwise();
59         Result result = doDecode(rotatedImage, hints);
60         // Record that we found it rotated 90 degrees CCW / 270 degrees CW
61         Map<ResultMetadataType,?> metadata = result.getResultMetadata();
62         int orientation = 270;
63         if (metadata != null && metadata.containsKey(ResultMetadataType.ORIENTATION)) {
64           // But if we found it reversed in doDecode(), add in that result here:
65           orientation = (orientation +
66               (Integer) metadata.get(ResultMetadataType.ORIENTATION)) % 360;
67         }
68         result.putMetadata(ResultMetadataType.ORIENTATION, orientation);
69         // Update result points
70         ResultPoint[] points = result.getResultPoints();
71         if (points != null) {
72           int height = rotatedImage.getHeight();
73           for (int i = 0; i < points.length; i++) {
74             points[i] = new ResultPoint(height - points[i].getY() - 1, points[i].getX());
75           }
76         }
77         return result;
78       } else {
79         throw nfe;
80       }
81     }
82   }
83 
84   @Override
reset()85   public void reset() {
86     // do nothing
87   }
88 
89   /**
90    * We're going to examine rows from the middle outward, searching alternately above and below the
91    * middle, and farther out each time. rowStep is the number of rows between each successive
92    * attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
93    * middle + rowStep, then middle - (2 * rowStep), etc.
94    * rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
95    * decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
96    * image if "trying harder".
97    *
98    * @param image The image to decode
99    * @param hints Any hints that were requested
100    * @return The contents of the decoded barcode
101    * @throws NotFoundException Any spontaneous errors which occur
102    */
doDecode(BinaryBitmap image, Map<DecodeHintType,?> hints)103   private Result doDecode(BinaryBitmap image,
104                           Map<DecodeHintType,?> hints) throws NotFoundException {
105     int width = image.getWidth();
106     int height = image.getHeight();
107     BitArray row = new BitArray(width);
108 
109     boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
110     int rowStep = Math.max(1, height >> (tryHarder ? 8 : 5));
111     int maxLines;
112     if (tryHarder) {
113       maxLines = height; // Look at the whole image, not just the center
114     } else {
115       maxLines = 15; // 15 rows spaced 1/32 apart is roughly the middle half of the image
116     }
117 
118     int middle = height / 2;
119     for (int x = 0; x < maxLines; x++) {
120 
121       // Scanning from the middle out. Determine which row we're looking at next:
122       int rowStepsAboveOrBelow = (x + 1) / 2;
123       boolean isAbove = (x & 0x01) == 0; // i.e. is x even?
124       int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
125       if (rowNumber < 0 || rowNumber >= height) {
126         // Oops, if we run off the top or bottom, stop
127         break;
128       }
129 
130       // Estimate black point for this row and load it:
131       try {
132         row = image.getBlackRow(rowNumber, row);
133       } catch (NotFoundException ignored) {
134         continue;
135       }
136 
137       // While we have the image data in a BitArray, it's fairly cheap to reverse it in place to
138       // handle decoding upside down barcodes.
139       for (int attempt = 0; attempt < 2; attempt++) {
140         if (attempt == 1) { // trying again?
141           row.reverse(); // reverse the row and continue
142           // This means we will only ever draw result points *once* in the life of this method
143           // since we want to avoid drawing the wrong points after flipping the row, and,
144           // don't want to clutter with noise from every single row scan -- just the scans
145           // that start on the center line.
146           if (hints != null && hints.containsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) {
147             Map<DecodeHintType,Object> newHints = new EnumMap<>(DecodeHintType.class);
148             newHints.putAll(hints);
149             newHints.remove(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
150             hints = newHints;
151           }
152         }
153         try {
154           // Look for a barcode
155           Result result = decodeRow(rowNumber, row, hints);
156           // We found our barcode
157           if (attempt == 1) {
158             // But it was upside down, so note that
159             result.putMetadata(ResultMetadataType.ORIENTATION, 180);
160             // And remember to flip the result points horizontally.
161             ResultPoint[] points = result.getResultPoints();
162             if (points != null) {
163               points[0] = new ResultPoint(width - points[0].getX() - 1, points[0].getY());
164               points[1] = new ResultPoint(width - points[1].getX() - 1, points[1].getY());
165             }
166           }
167           return result;
168         } catch (ReaderException re) {
169           // continue -- just couldn't decode this row
170         }
171       }
172     }
173 
174     throw NotFoundException.getNotFoundInstance();
175   }
176 
177   /**
178    * Records the size of successive runs of white and black pixels in a row, starting at a given point.
179    * The values are recorded in the given array, and the number of runs recorded is equal to the size
180    * of the array. If the row starts on a white pixel at the given start point, then the first count
181    * recorded is the run of white pixels starting from that point; likewise it is the count of a run
182    * of black pixels if the row begin on a black pixels at that point.
183    *
184    * @param row row to count from
185    * @param start offset into row to start at
186    * @param counters array into which to record counts
187    * @throws NotFoundException if counters cannot be filled entirely from row before running out
188    *  of pixels
189    */
recordPattern(BitArray row, int start, int[] counters)190   protected static void recordPattern(BitArray row,
191                                       int start,
192                                       int[] counters) throws NotFoundException {
193     int numCounters = counters.length;
194     Arrays.fill(counters, 0, numCounters, 0);
195     int end = row.getSize();
196     if (start >= end) {
197       throw NotFoundException.getNotFoundInstance();
198     }
199     boolean isWhite = !row.get(start);
200     int counterPosition = 0;
201     int i = start;
202     while (i < end) {
203       if (row.get(i) != isWhite) {
204         counters[counterPosition]++;
205       } else {
206         if (++counterPosition == numCounters) {
207           break;
208         } else {
209           counters[counterPosition] = 1;
210           isWhite = !isWhite;
211         }
212       }
213       i++;
214     }
215     // If we read fully the last section of pixels and filled up our counters -- or filled
216     // the last counter but ran off the side of the image, OK. Otherwise, a problem.
217     if (!(counterPosition == numCounters || (counterPosition == numCounters - 1 && i == end))) {
218       throw NotFoundException.getNotFoundInstance();
219     }
220   }
221 
recordPatternInReverse(BitArray row, int start, int[] counters)222   protected static void recordPatternInReverse(BitArray row, int start, int[] counters)
223       throws NotFoundException {
224     // This could be more efficient I guess
225     int numTransitionsLeft = counters.length;
226     boolean last = row.get(start);
227     while (start > 0 && numTransitionsLeft >= 0) {
228       if (row.get(--start) != last) {
229         numTransitionsLeft--;
230         last = !last;
231       }
232     }
233     if (numTransitionsLeft >= 0) {
234       throw NotFoundException.getNotFoundInstance();
235     }
236     recordPattern(row, start + 1, counters);
237   }
238 
239   /**
240    * Determines how closely a set of observed counts of runs of black/white values matches a given
241    * target pattern. This is reported as the ratio of the total variance from the expected pattern
242    * proportions across all pattern elements, to the length of the pattern.
243    *
244    * @param counters observed counters
245    * @param pattern expected pattern
246    * @param maxIndividualVariance The most any counter can differ before we give up
247    * @return ratio of total variance between counters and pattern compared to total pattern size
248    */
patternMatchVariance(int[] counters, int[] pattern, float maxIndividualVariance)249   protected static float patternMatchVariance(int[] counters,
250                                               int[] pattern,
251                                               float maxIndividualVariance) {
252     int numCounters = counters.length;
253     int total = 0;
254     int patternLength = 0;
255     for (int i = 0; i < numCounters; i++) {
256       total += counters[i];
257       patternLength += pattern[i];
258     }
259     if (total < patternLength) {
260       // If we don't even have one pixel per unit of bar width, assume this is too small
261       // to reliably match, so fail:
262       return Float.POSITIVE_INFINITY;
263     }
264 
265     float unitBarWidth = (float) total / patternLength;
266     maxIndividualVariance *= unitBarWidth;
267 
268     float totalVariance = 0.0f;
269     for (int x = 0; x < numCounters; x++) {
270       int counter = counters[x];
271       float scaledPattern = pattern[x] * unitBarWidth;
272       float variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
273       if (variance > maxIndividualVariance) {
274         return Float.POSITIVE_INFINITY;
275       }
276       totalVariance += variance;
277     }
278     return totalVariance / total;
279   }
280 
281   /**
282    * <p>Attempts to decode a one-dimensional barcode format given a single row of
283    * an image.</p>
284    *
285    * @param rowNumber row number from top of the row
286    * @param row the black/white pixel data of the row
287    * @param hints decode hints
288    * @return {@link Result} containing encoded string and start/end of barcode
289    * @throws NotFoundException if no potential barcode is found
290    * @throws ChecksumException if a potential barcode is found but does not pass its checksum
291    * @throws FormatException if a potential barcode is found but format is invalid
292    */
decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints)293   public abstract Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints)
294       throws NotFoundException, ChecksumException, FormatException;
295 
296 }
297