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