/* * 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 BitArray = com.google.zxing.common.BitArray; using com.google.zxing.common; namespace com.google.zxing { /** * @author dswitkin@google.com (Daniel Switkin) */ public abstract class BaseMonochromeBitmapSource: MonochromeBitmapSource { private static int LUMINANCE_BITS = 5; private static int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; private static int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; private int blackPoint; private BlackPointEstimationMethod lastMethod; private int lastArgument; private int[] luminances; protected BaseMonochromeBitmapSource() { blackPoint = 0x7F; lastMethod = null; lastArgument = 0; } private void initLuminances() { if (luminances == null) { int width = getWidth(); int height = getHeight(); int max = width > height ? width : height; luminances = new int[max]; } } public bool isBlack(int x, int y) { return getLuminance(x, y) < blackPoint; } public BitArray getBlackRow(int y, BitArray row, int startX, int getWidth) { if (row == null || row.getSize() < getWidth) { row = new BitArray(getWidth); } else { row.clear(); } // Reuse the same int array each time initLuminances(); luminances = getLuminanceRow(y, luminances); // If the current decoder calculated the blackPoint based on one row, assume we're trying to // decode a 1D barcode, and apply some sharpening. if (lastMethod.Equals(BlackPointEstimationMethod.ROW_SAMPLING)) { int left = luminances[startX]; int center = luminances[startX + 1]; for (int x = 1; x < getWidth - 1; x++) { int right = luminances[startX + x + 1]; // Simple -1 4 -1 box filter with a weight of 2 int luminance = ((center << 2) - left - right) >> 1; if (luminance < blackPoint) { row.set(x); } left = center; center = right; } } else { for (int x = 0; x < getWidth; x++) { if (luminances[startX + x] < blackPoint) { row.set(x); } } } return row; } public BitArray getBlackColumn(int x, BitArray column, int startY, int getHeight) { if (column == null || column.getSize() < getHeight) { column = new BitArray(getHeight); } else { column.clear(); } // Reuse the same int array each time initLuminances(); luminances = getLuminanceColumn(x, luminances); // We don't handle "row sampling" specially here for (int y = 0; y < getHeight; y++) { if (luminances[startY + y] < blackPoint) { column.set(y); } } return column; } public void estimateBlackPoint(BlackPointEstimationMethod method, int argument){ if (!method.Equals(lastMethod) || argument != lastArgument) { int width = getWidth(); int height = getHeight(); int[] histogram = new int[LUMINANCE_BUCKETS]; if (method.Equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) { int minDimension = width < height ? width : height; int startX = (width - minDimension) >> 1; int startY = (height - minDimension) >> 1; for (int n = 0; n < minDimension; n++) { int luminance = getLuminance(startX + n, startY + n); histogram[luminance >> LUMINANCE_SHIFT]++; } } else if (method.Equals(BlackPointEstimationMethod.ROW_SAMPLING)) { if (argument < 0 || argument >= height) { throw new Exception("Row is not within the image: " + argument); } initLuminances(); luminances = getLuminanceRow(argument, luminances); for (int x = 0; x < width; x++) { histogram[luminances[x] >> LUMINANCE_SHIFT]++; } } else { throw new Exception("Unknown method: " + method); } blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT; lastMethod = method; lastArgument = argument; } } public BlackPointEstimationMethod getLastEstimationMethod() { return lastMethod; } public MonochromeBitmapSource rotateCounterClockwise() { throw new Exception("Rotate not supported"); } public bool isRotateSupported() { return false; } // These two methods should not need to exist because they are defined in the interface that // this abstract class implements. However this seems to cause problems on some Nokias. // So we write these redundant declarations. public abstract int getHeight(); public abstract int getWidth(); /** * Retrieves the luminance at the pixel x,y in the bitmap. This method is only used for estimating * the black point and implementing getBlackRow() - it is not meant for decoding, hence it is not * part of MonochromeBitmapSource itself, and is protected. * * @param x The x coordinate in the image. * @param y The y coordinate in the image. * @return The luminance value between 0 and 255. */ protected abstract int getLuminance(int x, int y); /** * This is the main mechanism for retrieving luminance data. It is dramatically more efficient * than repeatedly calling getLuminance(). As above, this is not meant for decoders. * * @param y The row to fetch * @param row The array to write luminance values into. It is strongly suggested that you * allocate this yourself, making sure row.length >= getWidth(), and reuse the same * array on subsequent calls for performance. If you pass null, you will be flogged, * but then I will take pity on you and allocate a sufficient array internally. * @return The array containing the luminance data. This is the same as row if it was usable. */ protected abstract int[] getLuminanceRow(int y, int[] row); /** * The same as getLuminanceRow(), but for columns. * * @param x The column to fetch * @param column The array to write luminance values into. See above. * @return The array containing the luminance data. */ protected abstract int[] getLuminanceColumn(int x, int[] column); } }