Changed the order of the BaseMonochromeBitmapSource constructor arguments to be width...
[zxing.git] / core / src / com / google / zxing / common / BaseMonochromeBitmapSource.java
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 package com.google.zxing.common;
17
18 import com.google.zxing.MonochromeBitmapSource;
19 import com.google.zxing.BlackPointEstimationMethod;
20 import com.google.zxing.ReaderException;
21
22 /**
23  * @author dswitkin@google.com (Daniel Switkin)
24  */
25 public abstract class BaseMonochromeBitmapSource implements MonochromeBitmapSource {
26
27   private static final int LUMINANCE_BITS = 5;
28   private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
29   private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
30
31   private final int height;
32   private final int width;
33   private int blackPoint;
34   private BlackPointEstimationMethod lastMethod;
35   private int lastArgument;
36   private int[] luminances;
37
38   protected BaseMonochromeBitmapSource(int width, int height) {
39     this.height = height;
40     this.width = width;
41     blackPoint = 0x7F;
42     lastMethod = null;
43     lastArgument = 0;
44   }
45
46   private void initLuminances() {
47     if (luminances == null) {
48       int width = getWidth();
49       int height = getHeight();
50       int max = width > height ? width : height;
51       luminances = new int[max];
52     }
53   }
54
55   public boolean isBlack(int x, int y) {
56     return getLuminance(x, y) < blackPoint;
57   }
58
59   public BitArray getBlackRow(int y, BitArray row, int startX, int getWidth) {
60     if (row == null || row.getSize() < getWidth) {
61       row = new BitArray(getWidth);
62     } else {
63       row.clear();
64     }
65
66     // Reuse the same int array each time
67     initLuminances();
68     luminances = getLuminanceRow(y, luminances);
69
70     // If the current decoder calculated the blackPoint based on one row, assume we're trying to
71     // decode a 1D barcode, and apply some sharpening.
72     if (lastMethod.equals(BlackPointEstimationMethod.ROW_SAMPLING)) {
73       int left = luminances[startX];
74       int center = luminances[startX + 1];
75       for (int x = 1; x < getWidth - 1; x++) {
76         int right = luminances[startX + x + 1];
77         // Simple -1 4 -1 box filter with a weight of 2
78         int luminance = ((center << 2) - left - right) >> 1;
79         if (luminance < blackPoint) {
80           row.set(x);
81         }
82         left = center;
83         center = right;
84       }
85     } else {
86       for (int x = 0; x < getWidth; x++) {
87         if (luminances[startX + x] < blackPoint) {
88           row.set(x);
89         }
90       }
91     }
92     return row;
93   }
94
95   public BitArray getBlackColumn(int x, BitArray column, int startY, int getHeight) {
96     if (column == null || column.getSize() < getHeight) {
97       column = new BitArray(getHeight);
98     } else {
99       column.clear();
100     }
101
102     // Reuse the same int array each time
103     initLuminances();
104     luminances = getLuminanceColumn(x, luminances);
105
106     // We don't handle "row sampling" specially here
107     for (int y = 0; y < getHeight; y++) {
108       if (luminances[startY + y] < blackPoint) {
109         column.set(y);
110       }
111     }
112     return column;
113   }
114
115   public void estimateBlackPoint(BlackPointEstimationMethod method, int argument) throws ReaderException {
116     if (!method.equals(lastMethod) || argument != lastArgument) {
117       int width = getWidth();
118       int height = getHeight();
119       int[] histogram = new int[LUMINANCE_BUCKETS];
120       if (method.equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) {
121         int minDimension = width < height ? width : height;
122         int startX = (width - minDimension) >> 1;
123         int startY = (height - minDimension) >> 1;
124         for (int n = 0; n < minDimension; n++) {
125           int luminance = getLuminance(startX + n, startY + n);
126           histogram[luminance >> LUMINANCE_SHIFT]++;
127         }
128       } else if (method.equals(BlackPointEstimationMethod.ROW_SAMPLING)) {
129         if (argument < 0 || argument >= height) {
130           throw new IllegalArgumentException("Row is not within the image: " + argument);
131         }
132         initLuminances();
133         luminances = getLuminanceRow(argument, luminances);
134         for (int x = 0; x < width; x++) {
135           histogram[luminances[x] >> LUMINANCE_SHIFT]++;
136         }
137       } else {
138         throw new IllegalArgumentException("Unknown method: " + method);
139       }
140       blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT;
141       lastMethod = method;
142       lastArgument = argument;
143     }
144   }
145
146   public BlackPointEstimationMethod getLastEstimationMethod() {
147     return lastMethod;
148   }
149
150   public MonochromeBitmapSource rotateCounterClockwise() {
151     throw new IllegalArgumentException("Rotate not supported");
152   }
153
154   public boolean isRotateSupported() {
155     return false;
156   }
157
158   public final int getHeight() {
159     return height;
160   }
161
162   public final int getWidth() {
163     return width;
164   }
165
166   // These methods below should not need to exist because they are defined in the interface that
167   // this abstract class implements. However this seems to cause problems on some Nokias.
168   // So we write these redundant declarations.
169
170   /**
171    * Retrieves the luminance at the pixel x,y in the bitmap. This method is only used for estimating
172    * the black point and implementing getBlackRow() - it is not meant for decoding, hence it is not
173    * part of MonochromeBitmapSource itself, and is protected.
174    *
175    * @param x The x coordinate in the image.
176    * @param y The y coordinate in the image.
177    * @return The luminance value between 0 and 255.
178    */
179   protected abstract int getLuminance(int x, int y);
180
181   /**
182    * This is the main mechanism for retrieving luminance data. It is dramatically more efficient
183    * than repeatedly calling getLuminance(). As above, this is not meant for decoders.
184    *
185    * @param y The row to fetch
186    * @param row The array to write luminance values into. It is <b>strongly</b> suggested that you
187    *            allocate this yourself, making sure row.length >= getWidth(), and reuse the same
188    *            array on subsequent calls for performance. If you pass null, you will be flogged,
189    *            but then I will take pity on you and allocate a sufficient array internally.
190    * @return The array containing the luminance data. This is the same as row if it was usable.
191    */
192   protected abstract int[] getLuminanceRow(int y, int[] row);
193
194   /**
195    * The same as getLuminanceRow(), but for columns.
196    *
197    * @param x The column to fetch
198    * @param column The array to write luminance values into. See above.
199    * @return The array containing the luminance data.
200    */
201   protected abstract int[] getLuminanceColumn(int x, int[] column);
202
203 }