From 9adb83bbd3b3267082c63898c208f4ab7977cac3 Mon Sep 17 00:00:00 2001 From: dswitkin Date: Wed, 11 Jun 2008 23:52:35 +0000 Subject: [PATCH] Refactored the MonochromeBitmapSource hierarchy to share a great deal of code and shrink the derived classes considerably. git-svn-id: https://zxing.googlecode.com/svn/trunk@418 59b500cc-1b3d-0410-9834-0bbf25fbcc57 --- .../android/RGBMonochromeBitmapSource.java | 134 ++++-------------- .../google/zxing/MonochromeBitmapSource.java | 18 +++ .../common/BaseMonochromeBitmapSource.java | 127 +++++++++++++++++ .../LCDUIImageMonochromeBitmapSource.java | 113 ++------------- .../BufferedImageMonochromeBitmapSource.java | 121 ++++------------ 5 files changed, 207 insertions(+), 306 deletions(-) create mode 100644 core/src/com/google/zxing/common/BaseMonochromeBitmapSource.java diff --git a/android/src/com/google/zxing/client/android/RGBMonochromeBitmapSource.java b/android/src/com/google/zxing/client/android/RGBMonochromeBitmapSource.java index f6bf98b62..0e7628f37 100755 --- a/android/src/com/google/zxing/client/android/RGBMonochromeBitmapSource.java +++ b/android/src/com/google/zxing/client/android/RGBMonochromeBitmapSource.java @@ -17,11 +17,7 @@ package com.google.zxing.client.android; import android.graphics.Bitmap; -import com.google.zxing.BlackPointEstimationMethod; -import com.google.zxing.MonochromeBitmapSource; -import com.google.zxing.ReaderException; -import com.google.zxing.common.BitArray; -import com.google.zxing.common.BlackPointEstimator; +import com.google.zxing.common.BaseMonochromeBitmapSource; /** * This object implements MonochromeBitmapSource around an Android Bitmap. @@ -29,61 +25,16 @@ import com.google.zxing.common.BlackPointEstimator; * @author dswitkin@google.com (Daniel Switkin) * @author srowen@google.com (Sean Owen) */ -final class RGBMonochromeBitmapSource implements MonochromeBitmapSource { +final class RGBMonochromeBitmapSource extends BaseMonochromeBitmapSource { private final Bitmap image; - private int blackPoint; - private BlackPointEstimationMethod lastMethod; - private int lastArgument; - - private static final int LUMINANCE_BITS = 5; - private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; - private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; + private final int[] rgbRow; + private int cachedRow; RGBMonochromeBitmapSource(Bitmap image) { this.image = image; - blackPoint = 0x7F; - lastMethod = null; - lastArgument = 0; - } - - public boolean isBlack(int x, int y) { - return computeRGBLuminance(image.getPixel(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(); - } - int[] pixelRow = new int[getWidth]; - image.getPixels(pixelRow, 0, getWidth, startX, y, getWidth, 1); - - // If the current decoder calculated the blackPoint based on one row, assume we're trying to - // decode a 1D barcode, and apply some sharpening. - // TODO: We may want to add a fifth parameter to request the amount of shapening to be done. - if (lastMethod.equals(BlackPointEstimationMethod.ROW_SAMPLING)) { - int left = computeRGBLuminance(pixelRow[0]); - int center = computeRGBLuminance(pixelRow[1]); - for (int i = 1; i < getWidth - 1; i++) { - int right = computeRGBLuminance(pixelRow[i + 1]); - // Simple -1 4 -1 box filter with a weight of 2 - int luminance = ((center << 2) - left - right) >> 1; - if (luminance < blackPoint) { - row.set(i); - } - left = center; - center = right; - } - } else { - for (int i = 0; i < getWidth; i++) { - if (computeRGBLuminance(pixelRow[i]) < blackPoint) { - row.set(i); - } - } - } - return row; + rgbRow = new int[image.getWidth()]; + cachedRow = -1; } public int getHeight() { @@ -94,57 +45,18 @@ final class RGBMonochromeBitmapSource implements MonochromeBitmapSource { return image.width(); } - public void estimateBlackPoint(BlackPointEstimationMethod method, int argument) throws ReaderException { - if (!method.equals(lastMethod) || argument != lastArgument) { - int width = image.width(); - int height = image.height(); - int[] histogram = new int[LUMINANCE_BUCKETS]; - if (method.equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) { - int minDimension = width < height ? width : height; - int startI = height == minDimension ? 0 : (height - width) >> 1; - int startJ = width == minDimension ? 0 : (width - height) >> 1; - for (int n = 0; n < minDimension; n++) { - int pixel = image.getPixel(startJ + n, startI + n); - histogram[computeRGBLuminance(pixel) >> LUMINANCE_SHIFT]++; - } - } else if (method.equals(BlackPointEstimationMethod.ROW_SAMPLING)) { - if (argument < 0 || argument >= height) { - throw new IllegalArgumentException("Row is not within the image: " + argument); - } - int[] pixelRow = new int[width]; - image.getPixels(pixelRow, 0, width, 0, argument, width, 1); - for (int x = 0; x < width; x++) { - histogram[computeRGBLuminance(pixelRow[x]) >> LUMINANCE_SHIFT]++; - } - } else { - throw new IllegalArgumentException("Unknown method: " + method); - } - blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT; - lastMethod = method; - lastArgument = argument; - } - } - - public BlackPointEstimationMethod getLastEstimationMethod() { - return lastMethod; - } - - public MonochromeBitmapSource rotateCounterClockwise() { - throw new IllegalStateException("Rotate not supported"); - } - - public boolean isRotateSupported() { - return false; - } - /** * An optimized approximation of a more proper conversion from RGB to luminance which - * only uses shifts. See BufferedImageMonochromeBitmapSource for an original version. - * - * @param pixel An ARGB input pixel - * @return An eight bit luminance value + * only uses shifts. */ - private static int computeRGBLuminance(int pixel) { + public int getLuminance(int x, int y) { + int pixel; + if (cachedRow == y) { + pixel = rgbRow[x]; + } else { + pixel = image.getPixel(x, y); + } + // Instead of multiplying by 306, 601, 117, we multiply by 256, 512, 256, so that // the multiplies can be implemented as shifts. // @@ -157,11 +69,17 @@ final class RGBMonochromeBitmapSource implements MonochromeBitmapSource { // That is, we're replacing the coefficients in the original with powers of two, // which can be implemented as shifts, even though changing the coefficients slightly // corrupts the conversion. Not significant for our purposes. - // - // But we can get even cleverer and eliminate a few shifts: - return (((pixel & 0x00FF0000) >> 16) + + return (((pixel & 0x00FF0000) >> 16) + ((pixel & 0x0000FF00) >> 7) + - ( pixel & 0x000000FF )) >> 2; + (pixel & 0x000000FF )) >> 2; } -} \ No newline at end of file + public void cacheRowForLuminance(int y) { + if (y != cachedRow) { + int width = image.width(); + image.getPixels(rgbRow, 0, width, 0, y, width, 1); + cachedRow = y; + } + } + +} diff --git a/core/src/com/google/zxing/MonochromeBitmapSource.java b/core/src/com/google/zxing/MonochromeBitmapSource.java index 251b9285b..ccecbd373 100644 --- a/core/src/com/google/zxing/MonochromeBitmapSource.java +++ b/core/src/com/google/zxing/MonochromeBitmapSource.java @@ -58,6 +58,24 @@ public interface MonochromeBitmapSource { */ 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. + * + * @param x The x coordinate in the image. + * @param y The y coordinate in the image. + * @return The luminance value between 0 and 255. + */ + int getLuminance(int x, int y); + + /** + * Some implementations can be much more efficient by fetching an entire row of luminance data at + * a time. This method should be called once per row before calling getLuminance(). + * + * @param y The row to cache. + */ + void cacheRowForLuminance(int y); + /** *

Estimates black point according to the given method, which is optionally parameterized by * a single int argument. For {@link BlackPointEstimationMethod#ROW_SAMPLING}, this diff --git a/core/src/com/google/zxing/common/BaseMonochromeBitmapSource.java b/core/src/com/google/zxing/common/BaseMonochromeBitmapSource.java new file mode 100644 index 000000000..5c7667d66 --- /dev/null +++ b/core/src/com/google/zxing/common/BaseMonochromeBitmapSource.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * 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. + */ +package com.google.zxing.common; + +import com.google.zxing.MonochromeBitmapSource; +import com.google.zxing.BlackPointEstimationMethod; +import com.google.zxing.ReaderException; + +/** + * @author dswitkin@google.com (Daniel Switkin) + */ +public abstract class BaseMonochromeBitmapSource implements MonochromeBitmapSource { + + private int blackPoint; + private BlackPointEstimationMethod lastMethod; + private int lastArgument; + + private static final int LUMINANCE_BITS = 5; + private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; + private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; + + public BaseMonochromeBitmapSource() { + blackPoint = 0x7F; + lastMethod = null; + lastArgument = 0; + } + + public boolean 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(); + } + + // If the current decoder calculated the blackPoint based on one row, assume we're trying to + // decode a 1D barcode, and apply some sharpening. + // TODO: We may want to add a fifth parameter to request the amount of shapening to be done. + cacheRowForLuminance(y); + if (lastMethod == BlackPointEstimationMethod.ROW_SAMPLING) { + int left = getLuminance(startX, y); + int center = getLuminance(startX + 1, y); + for (int x = 1; x < getWidth - 1; x++) { + int right = getLuminance(startX + x + 1, y); + // 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 (getLuminance(startX + x, y) < blackPoint) { + row.set(x); + } + } + } + return row; + } + + public abstract int getHeight(); + public abstract int getWidth(); + public abstract int getLuminance(int x, int y); + public abstract void cacheRowForLuminance(int y); + + public void estimateBlackPoint(BlackPointEstimationMethod method, int argument) throws ReaderException { + 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 IllegalArgumentException("Row is not within the image: " + argument); + } + for (int x = 0; x < width; x++) { + int luminance = getLuminance(x, argument); + histogram[luminance >> LUMINANCE_SHIFT]++; + } + } else { + throw new IllegalArgumentException("Unknown method: " + method); + } + blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT; + lastMethod = method; + lastArgument = argument; + } + } + + public BlackPointEstimationMethod getLastEstimationMethod() { + return lastMethod; + } + + public MonochromeBitmapSource rotateCounterClockwise() { + throw new IllegalStateException("Rotate not supported"); + } + + public boolean isRotateSupported() { + return false; + } + + +} diff --git a/javame/src/com/google/zxing/client/j2me/LCDUIImageMonochromeBitmapSource.java b/javame/src/com/google/zxing/client/j2me/LCDUIImageMonochromeBitmapSource.java index f123abbb7..df462de38 100644 --- a/javame/src/com/google/zxing/client/j2me/LCDUIImageMonochromeBitmapSource.java +++ b/javame/src/com/google/zxing/client/j2me/LCDUIImageMonochromeBitmapSource.java @@ -16,79 +16,26 @@ package com.google.zxing.client.j2me; -import com.google.zxing.BlackPointEstimationMethod; -import com.google.zxing.MonochromeBitmapSource; -import com.google.zxing.ReaderException; -import com.google.zxing.common.BitArray; -import com.google.zxing.common.BlackPointEstimator; +import com.google.zxing.common.BaseMonochromeBitmapSource; import javax.microedition.lcdui.Image; /** - *

An implementation based on Java ME's {@link Image} representation.

+ *

An implementation based on Java ME's {@link java.awt.Image} representation.

* * @author Sean Owen (srowen@google.com), Daniel Switkin (dswitkin@google.com) */ -public final class LCDUIImageMonochromeBitmapSource implements MonochromeBitmapSource { +public final class LCDUIImageMonochromeBitmapSource extends BaseMonochromeBitmapSource { private final int[] rgbPixels; private final int width; private final int height; - private int blackPoint; - private BlackPointEstimationMethod lastMethod; - private int lastArgument; - - private static final int LUMINANCE_BITS = 5; - private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; - private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; public LCDUIImageMonochromeBitmapSource(Image image) { width = image.getWidth(); height = image.getHeight(); rgbPixels = new int[width * height]; image.getRGB(rgbPixels, 0, width, 0, 0, width, height); - blackPoint = 0x7F; - lastMethod = null; - lastArgument = 0; - } - - public boolean isBlack(int x, int y) { - return computeRGBLuminance(rgbPixels[x + y * width]) < 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(); - } - - // If the current decoder calculated the blackPoint based on one row, assume we're trying to - // decode a 1D barcode, and apply some sharpening. - // TODO: We may want to add a fifth parameter to request the amount of shapening to be done. - if (lastMethod.equals(BlackPointEstimationMethod.ROW_SAMPLING)) { - int offset = y * width + startX; - int left = computeRGBLuminance(rgbPixels[offset]); - offset++; - int center = computeRGBLuminance(rgbPixels[offset]); - for (int i = 1; i < getWidth - 1; i++, offset++) { - int right = computeRGBLuminance(rgbPixels[offset + 1]); - // Simple -1 4 -1 box filter with a weight of 2 - int luminance = ((center << 2) - left - right) >> 1; - if (luminance < blackPoint) { - row.set(i); - } - left = center; - center = right; - } - } else { - for (int i = 0, offset = y * width + startX; i < getWidth; i++, offset++) { - if (computeRGBLuminance(rgbPixels[offset]) < blackPoint) { - row.set(i); - } - } - } - return row; } public int getHeight() { @@ -99,48 +46,9 @@ public final class LCDUIImageMonochromeBitmapSource implements MonochromeBitmapS return width; } - public void estimateBlackPoint(BlackPointEstimationMethod method, int argument) throws ReaderException { - if (!method.equals(lastMethod) || argument != lastArgument) { - int[] histogram = new int[LUMINANCE_BUCKETS]; - if (method.equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) { - int minDimension = width < height ? width : height; - for (int n = 0, offset = 0; n < minDimension; n++, offset += width + 1) { - histogram[computeRGBLuminance(rgbPixels[offset]) >> LUMINANCE_SHIFT]++; - } - } else if (method.equals(BlackPointEstimationMethod.ROW_SAMPLING)) { - if (argument < 0 || argument >= height) { - throw new IllegalArgumentException("Row is not within the image: " + argument); - } - int offset = argument * width; - for (int x = 0; x < width; x++) { - histogram[computeRGBLuminance(rgbPixels[offset + x]) >> LUMINANCE_SHIFT]++; - } - } else { - throw new IllegalArgumentException("Unknown method: " + method); - } - blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT; - lastMethod = method; - lastArgument = argument; - } - } + public int getLuminance(int x, int y) { + int pixel = rgbPixels[y * width + x]; - public BlackPointEstimationMethod getLastEstimationMethod() { - return lastMethod; - } - - public MonochromeBitmapSource rotateCounterClockwise() { - throw new IllegalStateException("Rotate not supported"); - } - - public boolean isRotateSupported() { - return false; - } - - /** - * An optimized approximation of a more proper conversion from RGB to luminance which - * only uses shifts. See BufferedImageMonochromeBitmapSource for an original version. - */ - private static int computeRGBLuminance(int pixel) { // Instead of multiplying by 306, 601, 117, we multiply by 256, 512, 256, so that // the multiplies can be implemented as shifts. // @@ -153,11 +61,14 @@ public final class LCDUIImageMonochromeBitmapSource implements MonochromeBitmapS // That is, we're replacing the coefficients in the original with powers of two, // which can be implemented as shifts, even though changing the coefficients slightly // corrupts the conversion. Not significant for our purposes. - // - // But we can get even cleverer and eliminate a few shifts: - return (((pixel & 0x00FF0000) >> 16) + + return (((pixel & 0x00FF0000) >> 16) + ((pixel & 0x0000FF00) >> 7) + - ( pixel & 0x000000FF )) >> 2; + (pixel & 0x000000FF )) >> 2; + } + + // Nothing to do, since we have direct access to the image data. + public void cacheRowForLuminance(int y) { + } } \ No newline at end of file diff --git a/javase/src/com/google/zxing/client/j2se/BufferedImageMonochromeBitmapSource.java b/javase/src/com/google/zxing/client/j2se/BufferedImageMonochromeBitmapSource.java index e8396b465..f0e837f0b 100644 --- a/javase/src/com/google/zxing/client/j2se/BufferedImageMonochromeBitmapSource.java +++ b/javase/src/com/google/zxing/client/j2se/BufferedImageMonochromeBitmapSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2007 Google Inc. + * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,8 @@ package com.google.zxing.client.j2se; -import com.google.zxing.BlackPointEstimationMethod; import com.google.zxing.MonochromeBitmapSource; -import com.google.zxing.ReaderException; -import com.google.zxing.common.BitArray; -import com.google.zxing.common.BlackPointEstimator; +import com.google.zxing.common.BaseMonochromeBitmapSource; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; @@ -38,20 +35,15 @@ import java.awt.image.BufferedImageOp; * * @author srowen@google.com (Sean Owen), Daniel Switkin (dswitkin@google.com) */ -public final class BufferedImageMonochromeBitmapSource implements MonochromeBitmapSource { +public final class BufferedImageMonochromeBitmapSource extends BaseMonochromeBitmapSource { private final BufferedImage image; private final int left; private final int top; private final int width; private final int height; - private int blackPoint; - private BlackPointEstimationMethod lastMethod; - private int lastArgument; - - private static final int LUMINANCE_BITS = 5; - private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; - private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; + private int[] rgbRow; + private int cachedRow; /** * Creates an instance that uses the entire given image as a source of pixels to decode. @@ -60,6 +52,8 @@ public final class BufferedImageMonochromeBitmapSource implements MonochromeBitm */ public BufferedImageMonochromeBitmapSource(BufferedImage image) { this(image, 0, 0, image.getWidth(), image.getHeight()); + rgbRow = new int[image.getWidth()]; + cachedRow = -1; } /** @@ -74,9 +68,6 @@ public final class BufferedImageMonochromeBitmapSource implements MonochromeBitm */ public BufferedImageMonochromeBitmapSource(BufferedImage image, int left, int top, int right, int bottom) { this.image = image; - blackPoint = 0x7F; - lastMethod = null; - lastArgument = 0; int sourceHeight = image.getHeight(); int sourceWidth = image.getWidth(); if (left < 0 || top < 0 || right > sourceWidth || bottom > sourceHeight || right <= left || bottom <= top) { @@ -86,6 +77,8 @@ public final class BufferedImageMonochromeBitmapSource implements MonochromeBitm this.top = top; this.width = right - left; this.height = bottom - top; + rgbRow = new int[width]; + cachedRow = -1; } /** @@ -96,53 +89,6 @@ public final class BufferedImageMonochromeBitmapSource implements MonochromeBitm return image; } - private int getRGB(int x, int y) { - return image.getRGB(left + x, top + y); - } - - private void getRGBRow(int startX, int startY, int[] result) { - image.getRGB(left + startX, top + startY, result.length, 1, result, 0, result.length); - } - - public boolean isBlack(int x, int y) { - return computeRGBLuminance(getRGB(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(); - } - int[] pixelRow = new int[getWidth]; - getRGBRow(startX, y, pixelRow); - - // If the current decoder calculated the blackPoint based on one row, assume we're trying to - // decode a 1D barcode, and apply some sharpening. - // TODO: We may want to add a fifth parameter to request the amount of shapening to be done. - if (lastMethod.equals(BlackPointEstimationMethod.ROW_SAMPLING)) { - int left = computeRGBLuminance(pixelRow[0]); - int center = computeRGBLuminance(pixelRow[1]); - for (int i = 1; i < getWidth - 1; i++) { - int right = computeRGBLuminance(pixelRow[i + 1]); - // Simple -1 4 -1 box filter with a weight of 2 - int luminance = ((center << 2) - left - right) >> 1; - if (luminance < blackPoint) { - row.set(i); - } - left = center; - center = right; - } - } else { - for (int i = 0; i < getWidth; i++) { - if (computeRGBLuminance(pixelRow[i]) < blackPoint) { - row.set(i); - } - } - } - return row; - } - public int getHeight() { return height; } @@ -151,39 +97,6 @@ public final class BufferedImageMonochromeBitmapSource implements MonochromeBitm return width; } - public void estimateBlackPoint(BlackPointEstimationMethod method, int argument) throws ReaderException { - if (!method.equals(lastMethod) || argument != lastArgument) { - int[] histogram = new int[LUMINANCE_BUCKETS]; - if (method.equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) { - int minDimension = width < height ? width : height; - int startI = height == minDimension ? 0 : (height - width) >> 1; - int startJ = width == minDimension ? 0 : (width - height) >> 1; - for (int n = 0; n < minDimension; n++) { - int pixel = getRGB(startJ + n, startI + n); - histogram[computeRGBLuminance(pixel) >> LUMINANCE_SHIFT]++; - } - } else if (method.equals(BlackPointEstimationMethod.ROW_SAMPLING)) { - if (argument < 0 || argument >= height) { - throw new IllegalArgumentException("Row is not within the image: " + argument); - } - int[] rgbArray = new int[width]; - getRGBRow(0, argument, rgbArray); - for (int x = 0; x < width; x++) { - histogram[computeRGBLuminance(rgbArray[x]) >> LUMINANCE_SHIFT]++; - } - } else { - throw new IllegalArgumentException("Unknown method: " + method); - } - blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT; - lastMethod = method; - lastArgument = argument; - } - } - - public BlackPointEstimationMethod getLastEstimationMethod() { - return lastMethod; - } - public MonochromeBitmapSource rotateCounterClockwise() { if (!isRotateSupported()) { throw new IllegalStateException("Rotate not supported"); @@ -217,11 +130,25 @@ public final class BufferedImageMonochromeBitmapSource implements MonochromeBitm * * where R, G, and B are values in [0,1]. */ - private static int computeRGBLuminance(int pixel) { + public int getLuminance(int x, int y) { + int pixel; + if (cachedRow == y) { + pixel = rgbRow[x]; + } else { + pixel = image.getRGB(left + x, top + y); + } + // Coefficients add up to 1024 to make the divide into a fast shift return (306 * ((pixel >> 16) & 0xFF) + 601 * ((pixel >> 8) & 0xFF) + 117 * (pixel & 0xFF)) >> 10; } + public void cacheRowForLuminance(int y) { + if (y != cachedRow) { + image.getRGB(left, top + y, width, 1, rgbRow, 0, width); + cachedRow = y; + } + } + }