2 * Copyright 2008 ZXing authors
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.google.zxing.oned;
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;
31 import java.util.Enumeration;
32 import java.util.Hashtable;
35 * Encapsulates functionality and implementation that is common to all families
36 * of one-dimensional barcodes.
38 * @author dswitkin@google.com (Daniel Switkin)
41 public abstract class OneDReader implements Reader {
43 private static final int INTEGER_MATH_SHIFT = 8;
44 protected static final int PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;
46 public Result decode(BinaryBitmap image) throws NotFoundException, FormatException {
47 return decode(image, null);
50 // Note that we don't try rotation without the try harder flag, even if rotation was supported.
51 public Result decode(BinaryBitmap image, Hashtable hints) throws NotFoundException, FormatException {
53 return doDecode(image, hints);
54 } catch (NotFoundException nfe) {
55 boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
56 if (tryHarder && image.isRotateSupported()) {
57 BinaryBitmap rotatedImage = image.rotateCounterClockwise();
58 Result result = doDecode(rotatedImage, hints);
59 // Record that we found it rotated 90 degrees CCW / 270 degrees CW
60 Hashtable metadata = result.getResultMetadata();
61 int orientation = 270;
62 if (metadata != null && metadata.containsKey(ResultMetadataType.ORIENTATION)) {
63 // But if we found it reversed in doDecode(), add in that result here:
64 orientation = (orientation +
65 ((Integer) metadata.get(ResultMetadataType.ORIENTATION)).intValue()) % 360;
67 result.putMetadata(ResultMetadataType.ORIENTATION, new Integer(orientation));
68 // Update result points
69 ResultPoint[] points = result.getResultPoints();
70 int height = rotatedImage.getHeight();
71 for (int i = 0; i < points.length; i++) {
72 points[i] = new ResultPoint(height - points[i].getY() - 1, points[i].getX());
86 * We're going to examine rows from the middle outward, searching alternately above and below the
87 * middle, and farther out each time. rowStep is the number of rows between each successive
88 * attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
89 * middle + rowStep, then middle - (2 * rowStep), etc.
90 * rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
91 * decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
92 * image if "trying harder".
94 * @param image The image to decode
95 * @param hints Any hints that were requested
96 * @return The contents of the decoded barcode
97 * @throws NotFoundException Any spontaneous errors which occur
99 private Result doDecode(BinaryBitmap image, Hashtable hints) throws NotFoundException {
100 int width = image.getWidth();
101 int height = image.getHeight();
102 BitArray row = new BitArray(width);
104 int middle = height >> 1;
105 boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
106 int rowStep = Math.max(1, height >> (tryHarder ? 8 : 5));
109 maxLines = height; // Look at the whole image, not just the center
111 maxLines = 15; // 15 rows spaced 1/32 apart is roughly the middle half of the image
114 for (int x = 0; x < maxLines; x++) {
116 // Scanning from the middle out. Determine which row we're looking at next:
117 int rowStepsAboveOrBelow = (x + 1) >> 1;
118 boolean isAbove = (x & 0x01) == 0; // i.e. is x even?
119 int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
120 if (rowNumber < 0 || rowNumber >= height) {
121 // Oops, if we run off the top or bottom, stop
125 // Estimate black point for this row and load it:
127 row = image.getBlackRow(rowNumber, row);
128 } catch (NotFoundException nfe) {
132 // While we have the image data in a BitArray, it's fairly cheap to reverse it in place to
133 // handle decoding upside down barcodes.
134 for (int attempt = 0; attempt < 2; attempt++) {
135 if (attempt == 1) { // trying again?
136 row.reverse(); // reverse the row and continue
137 // This means we will only ever draw result points *once* in the life of this method
138 // since we want to avoid drawing the wrong points after flipping the row, and,
139 // don't want to clutter with noise from every single row scan -- just the scans
140 // that start on the center line.
141 if (hints != null && hints.containsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) {
142 Hashtable newHints = new Hashtable(); // Can't use clone() in J2ME
143 Enumeration hintEnum = hints.keys();
144 while (hintEnum.hasMoreElements()) {
145 Object key = hintEnum.nextElement();
146 if (!key.equals(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) {
147 newHints.put(key, hints.get(key));
154 // Look for a barcode
155 Result result = decodeRow(rowNumber, row, hints);
156 // We found our barcode
158 // But it was upside down, so note that
159 result.putMetadata(ResultMetadataType.ORIENTATION, new Integer(180));
160 // And remember to flip the result points horizontally.
161 ResultPoint[] points = result.getResultPoints();
162 points[0] = new ResultPoint(width - points[0].getX() - 1, points[0].getY());
163 points[1] = new ResultPoint(width - points[1].getX() - 1, points[1].getY());
166 } catch (ReaderException re) {
167 // continue -- just couldn't decode this row
172 throw NotFoundException.getNotFoundInstance();
176 * Records the size of successive runs of white and black pixels in a row, starting at a given point.
177 * The values are recorded in the given array, and the number of runs recorded is equal to the size
178 * of the array. If the row starts on a white pixel at the given start point, then the first count
179 * recorded is the run of white pixels starting from that point; likewise it is the count of a run
180 * of black pixels if the row begin on a black pixels at that point.
182 * @param row row to count from
183 * @param start offset into row to start at
184 * @param counters array into which to record counts
185 * @throws NotFoundException if counters cannot be filled entirely from row before running out
188 protected static void recordPattern(BitArray row, int start, int[] counters) throws NotFoundException {
189 int numCounters = counters.length;
190 for (int i = 0; i < numCounters; i++) {
193 int end = row.getSize();
195 throw NotFoundException.getNotFoundInstance();
197 boolean isWhite = !row.get(start);
198 int counterPosition = 0;
201 boolean pixel = row.get(i);
202 if (pixel ^ isWhite) { // that is, exactly one is true
203 counters[counterPosition]++;
206 if (counterPosition == numCounters) {
209 counters[counterPosition] = 1;
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();
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--;
233 if (numTransitionsLeft >= 0) {
234 throw NotFoundException.getNotFoundInstance();
236 recordPattern(row, start + 1, counters);
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.
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 * where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means
249 * the total variance between counters and patterns equals the pattern length, higher values mean
252 protected static int patternMatchVariance(int[] counters, int[] pattern, int maxIndividualVariance) {
253 int numCounters = counters.length;
255 int patternLength = 0;
256 for (int i = 0; i < numCounters; i++) {
257 total += counters[i];
258 patternLength += pattern[i];
260 if (total < patternLength) {
261 // If we don't even have one pixel per unit of bar width, assume this is too small
262 // to reliably match, so fail:
263 return Integer.MAX_VALUE;
265 // We're going to fake floating-point math in integers. We just need to use more bits.
266 // Scale up patternLength so that intermediate values below like scaledCounter will have
267 // more "significant digits"
268 int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength;
269 maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT;
271 int totalVariance = 0;
272 for (int x = 0; x < numCounters; x++) {
273 int counter = counters[x] << INTEGER_MATH_SHIFT;
274 int scaledPattern = pattern[x] * unitBarWidth;
275 int variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
276 if (variance > maxIndividualVariance) {
277 return Integer.MAX_VALUE;
279 totalVariance += variance;
281 return totalVariance / total;
285 * <p>Attempts to decode a one-dimensional barcode format given a single row of
288 * @param rowNumber row number from top of the row
289 * @param row the black/white pixel data of the row
290 * @param hints decode hints
291 * @return {@link Result} containing encoded string and start/end of barcode
292 * @throws NotFoundException if an error occurs or barcode cannot be found
294 public abstract Result decodeRow(int rowNumber, BitArray row, Hashtable hints)
295 throws NotFoundException, ChecksumException, FormatException;