diff --git a/src/common/HybridBinarizer.java b/src/common/HybridBinarizer.java deleted file mode 100644 index 0fbae89..0000000 --- a/src/common/HybridBinarizer.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright 2009 ZXing authors - * - * 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.Binarizer; -import com.google.zxing.LuminanceSource; -import com.google.zxing.NotFoundException; - -/** - * This class implements a local thresholding algorithm, which while slower than the - * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for - * high frequency images of barcodes with black data on white backgrounds. For this application, - * it does a much better job than a global blackpoint with severe shadows and gradients. - * However it tends to produce artifacts on lower frequency images and is therefore not - * a good general purpose binarizer for uses outside ZXing. - * - * This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers, - * and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already - * inherently local, and only fails for horizontal gradients. We can revisit that problem later, - * but for now it was not a win to use local blocks for 1D. - * - * This Binarizer is the default for the unit tests and the recommended class for library users. - * - * @author dswitkin@google.com (Daniel Switkin) - */ -public final class HybridBinarizer extends GlobalHistogramBinarizer { - - // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. - // So this is the smallest dimension in each axis we can accept. - private static final int BLOCK_SIZE_POWER = 3; - private static final int BLOCK_SIZE = 1 << BLOCK_SIZE_POWER; // ...0100...00 - private static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; // ...0011...11 - private static final int MINIMUM_DIMENSION = BLOCK_SIZE * 5; - private static final int MIN_DYNAMIC_RANGE = 24; - - private BitMatrix matrix; - - public HybridBinarizer(LuminanceSource source) { - super(source); - } - - /** - * Calculates the final BitMatrix once for all requests. This could be called once from the - * constructor instead, but there are some advantages to doing it lazily, such as making - * profiling easier, and not doing heavy lifting when callers don't expect it. - */ - @Override - public BitMatrix getBlackMatrix() throws NotFoundException { - if (matrix != null) { - return matrix; - } - LuminanceSource source = getLuminanceSource(); - int width = source.getWidth(); - int height = source.getHeight(); - if (width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION) { - byte[] luminances = source.getMatrix(); - int subWidth = width >> BLOCK_SIZE_POWER; - if ((width & BLOCK_SIZE_MASK) != 0) { - subWidth++; - } - int subHeight = height >> BLOCK_SIZE_POWER; - if ((height & BLOCK_SIZE_MASK) != 0) { - subHeight++; - } - int[][] blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width, height); - - BitMatrix newMatrix = new BitMatrix(width, height); - calculateThresholdForBlock(luminances, subWidth, subHeight, width, height, blackPoints, newMatrix); - matrix = newMatrix; - } else { - // If the image is too small, fall back to the global histogram approach. - matrix = super.getBlackMatrix(); - } - return matrix; - } - - @Override - public Binarizer createBinarizer(LuminanceSource source) { - return new HybridBinarizer(source); - } - - /** - * For each block in the image, calculate the average black point using a 5x5 grid - * of the blocks around it. Also handles the corner cases (fractional blocks are computed based - * on the last pixels in the row/column which are also used in the previous block). - */ - private static void calculateThresholdForBlock(byte[] luminances, - int subWidth, - int subHeight, - int width, - int height, - int[][] blackPoints, - BitMatrix matrix) { - int maxYOffset = height - BLOCK_SIZE; - int maxXOffset = width - BLOCK_SIZE; - for (int y = 0; y < subHeight; y++) { - int yoffset = y << BLOCK_SIZE_POWER; - if (yoffset > maxYOffset) { - yoffset = maxYOffset; - } - int top = cap(y, subHeight - 3); - for (int x = 0; x < subWidth; x++) { - int xoffset = x << BLOCK_SIZE_POWER; - if (xoffset > maxXOffset) { - xoffset = maxXOffset; - } - int left = cap(x, subWidth - 3); - int sum = 0; - for (int z = -2; z <= 2; z++) { - int[] blackRow = blackPoints[top + z]; - sum += blackRow[left - 2] + blackRow[left - 1] + blackRow[left] + blackRow[left + 1] + blackRow[left + 2]; - } - int average = sum / 25; - thresholdBlock(luminances, xoffset, yoffset, average, width, matrix); - } - } - } - - private static int cap(int value, int max) { - return value < 2 ? 2 : Math.min(value, max); - } - - /** - * Applies a single threshold to a block of pixels. - */ - private static void thresholdBlock(byte[] luminances, - int xoffset, - int yoffset, - int threshold, - int stride, - BitMatrix matrix) { - for (int y = 0, offset = yoffset * stride + xoffset; y < BLOCK_SIZE; y++, offset += stride) { - for (int x = 0; x < BLOCK_SIZE; x++) { - // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0. - if ((luminances[offset + x] & 0xFF) <= threshold) { - matrix.set(xoffset + x, yoffset + y); - } - } - } - } - - /** - * Calculates a single black point for each block of pixels and saves it away. - * See the following thread for a discussion of this algorithm: - * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 - */ - private static int[][] calculateBlackPoints(byte[] luminances, - int subWidth, - int subHeight, - int width, - int height) { - int maxYOffset = height - BLOCK_SIZE; - int maxXOffset = width - BLOCK_SIZE; - int[][] blackPoints = new int[subHeight][subWidth]; - for (int y = 0; y < subHeight; y++) { - int yoffset = y << BLOCK_SIZE_POWER; - if (yoffset > maxYOffset) { - yoffset = maxYOffset; - } - for (int x = 0; x < subWidth; x++) { - int xoffset = x << BLOCK_SIZE_POWER; - if (xoffset > maxXOffset) { - xoffset = maxXOffset; - } - int sum = 0; - int min = 0xFF; - int max = 0; - for (int yy = 0, offset = yoffset * width + xoffset; yy < BLOCK_SIZE; yy++, offset += width) { - for (int xx = 0; xx < BLOCK_SIZE; xx++) { - int pixel = luminances[offset + xx] & 0xFF; - sum += pixel; - // still looking for good contrast - if (pixel < min) { - min = pixel; - } - if (pixel > max) { - max = pixel; - } - } - // short-circuit min/max tests once dynamic range is met - if (max - min > MIN_DYNAMIC_RANGE) { - // finish the rest of the rows quickly - for (yy++, offset += width; yy < BLOCK_SIZE; yy++, offset += width) { - for (int xx = 0; xx < BLOCK_SIZE; xx++) { - sum += luminances[offset + xx] & 0xFF; - } - } - } - } - - // The default estimate is the average of the values in the block. - int average = sum >> (BLOCK_SIZE_POWER * 2); - if (max - min <= MIN_DYNAMIC_RANGE) { - // If variation within the block is low, assume this is a block with only light or only - // dark pixels. In that case we do not want to use the average, as it would divide this - // low contrast area into black and white pixels, essentially creating data out of noise. - // - // The default assumption is that the block is light/background. Since no estimate for - // the level of dark pixels exists locally, use half the min for the block. - average = min / 2; - - if (y > 0 && x > 0) { - // Correct the "white background" assumption for blocks that have neighbors by comparing - // the pixels in this block to the previously calculated black points. This is based on - // the fact that dark barcode symbology is always surrounded by some amount of light - // background for which reasonable black point estimates were made. The bp estimated at - // the boundaries is used for the interior. - - // The (min < bp) is arbitrary but works better than other heuristics that were tried. - int averageNeighborBlackPoint = - (blackPoints[y - 1][x] + (2 * blackPoints[y][x - 1]) + blackPoints[y - 1][x - 1]) / 4; - if (min < averageNeighborBlackPoint) { - average = averageNeighborBlackPoint; - } - } - } - blackPoints[y][x] = average; - } - } - return blackPoints; - } - -} diff --git a/src/common/mod.rs b/src/common/mod.rs index 28178e2..2beb92e 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -3755,3 +3755,313 @@ impl GlobalHistogramBinarizer { Ok((bestValley as u32) << GlobalHistogramBinarizer::LUMINANCE_SHIFT) } } + +/* + * Copyright 2009 ZXing authors + * + * 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.Binarizer; +// import com.google.zxing.LuminanceSource; +// import com.google.zxing.NotFoundException; + +/** + * This class implements a local thresholding algorithm, which while slower than the + * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for + * high frequency images of barcodes with black data on white backgrounds. For this application, + * it does a much better job than a global blackpoint with severe shadows and gradients. + * However it tends to produce artifacts on lower frequency images and is therefore not + * a good general purpose binarizer for uses outside ZXing. + * + * This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers, + * and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already + * inherently local, and only fails for horizontal gradients. We can revisit that problem later, + * but for now it was not a win to use local blocks for 1D. + * + * This Binarizer is the default for the unit tests and the recommended class for library users. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +pub struct HybridBinarizer { + //width: usize, + //height: usize, + //source: Box, + ghb: GlobalHistogramBinarizer, + // matrix :Option, +} +impl Binarizer for HybridBinarizer { + fn getLuminanceSource(&self) -> &Box { + self.ghb.getLuminanceSource() + } + + fn getBlackRow(&self, y: usize, row: &mut BitArray) -> Result { + self.ghb.getBlackRow(y, row) + } + + /** + * Calculates the final BitMatrix once for all requests. This could be called once from the + * constructor instead, but there are some advantages to doing it lazily, such as making + * profiling easier, and not doing heavy lifting when callers don't expect it. + */ + fn getBlackMatrix(&self) -> Result { + // if self.matrix.is_some() { + // return Ok(self.matrix.clone().unwrap()) + // } + let matrix; + let source = self.getLuminanceSource(); + let width = source.getWidth(); + let height = source.getHeight(); + if width >= HybridBinarizer::MINIMUM_DIMENSION + && height >= HybridBinarizer::MINIMUM_DIMENSION + { + let luminances = source.getMatrix(); + let mut subWidth = width >> HybridBinarizer::BLOCK_SIZE_POWER; + if (width & HybridBinarizer::BLOCK_SIZE_MASK) != 0 { + subWidth += 1; + } + let mut subHeight = height >> HybridBinarizer::BLOCK_SIZE_POWER; + if (height & HybridBinarizer::BLOCK_SIZE_MASK) != 0 { + subHeight += 1; + } + let blackPoints = Self::calculateBlackPoints( + &luminances, + subWidth as u32, + subHeight as u32, + width as u32, + height as u32, + ); + + let mut newMatrix = BitMatrix::new(width as u32, height as u32)?; + Self::calculateThresholdForBlock( + &luminances, + subWidth as u32, + subHeight as u32, + width as u32, + height as u32, + &blackPoints, + &mut newMatrix, + ); + matrix = newMatrix; + } else { + // If the image is too small, fall back to the global histogram approach. + matrix = self.ghb.getBlackMatrix()?; + } + Ok(matrix) + } + + fn createBinarizer(&self, source: Box) -> Box { + Box::new(HybridBinarizer::new(source)) + } + + fn getWidth(&self) -> usize { + self.ghb.getWidth() + } + + fn getHeight(&self) -> usize { + self.ghb.getHeight() + } +} +impl HybridBinarizer { + // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. + // So this is the smallest dimension in each axis we can accept. + const BLOCK_SIZE_POWER: usize = 3; + const BLOCK_SIZE: usize = 1 << HybridBinarizer::BLOCK_SIZE_POWER; // ...0100...00 + const BLOCK_SIZE_MASK: usize = HybridBinarizer::BLOCK_SIZE - 1; // ...0011...11 + const MINIMUM_DIMENSION: usize = HybridBinarizer::BLOCK_SIZE * 5; + const MIN_DYNAMIC_RANGE: usize = 24; + + pub fn new(source: Box) -> Self { + Self { + ghb: GlobalHistogramBinarizer::new(source), + // matrix: None, + } + } + + /** + * For each block in the image, calculate the average black point using a 5x5 grid + * of the blocks around it. Also handles the corner cases (fractional blocks are computed based + * on the last pixels in the row/column which are also used in the previous block). + */ + fn calculateThresholdForBlock( + luminances: &[u8], + subWidth: u32, + subHeight: u32, + width: u32, + height: u32, + blackPoints: &Vec>, + matrix: &mut BitMatrix, + ) { + let maxYOffset = height - HybridBinarizer::BLOCK_SIZE as u32; + let maxXOffset = width - HybridBinarizer::BLOCK_SIZE as u32; + for y in 0..subHeight { + // for (int y = 0; y < subHeight; y++) { + let mut yoffset = y << HybridBinarizer::BLOCK_SIZE_POWER; + if yoffset > maxYOffset { + yoffset = maxYOffset; + } + let top = Self::cap(y, subHeight - 3); + for x in 0..subWidth { + // for (int x = 0; x < subWidth; x++) { + let mut xoffset = x << HybridBinarizer::BLOCK_SIZE_POWER; + if xoffset > maxXOffset { + xoffset = maxXOffset; + } + let left = Self::cap(x, subWidth - 3); + let mut sum = 0; + for z in -2i32..=2 { + // for (int z = -2; z <= 2; z++) { + let blackRow = &blackPoints[(top as i32 + z) as usize]; + sum += blackRow[(left - 2) as usize] + + blackRow[(left - 1) as usize] + + blackRow[left as usize] + + blackRow[(left + 1) as usize] + + blackRow[(left + 2) as usize]; + } + let average = sum / 25; + Self::thresholdBlock(luminances, xoffset, yoffset, average, width, matrix); + } + } + } + + fn cap(value: u32, max: u32) -> u32 { + if value < 2 { + 2 + } else { + value.min(max) + } + } + + /** + * Applies a single threshold to a block of pixels. + */ + fn thresholdBlock( + luminances: &[u8], + xoffset: u32, + yoffset: u32, + threshold: u32, + stride: u32, + matrix: &mut BitMatrix, + ) { + let mut offset = yoffset * stride + xoffset; + for y in 0..HybridBinarizer::BLOCK_SIZE { + // for (int y = 0, offset = yoffset * stride + xoffset; y < HybridBinarizer::BLOCK_SIZE; y++, offset += stride) { + for x in 0..HybridBinarizer::BLOCK_SIZE { + // for (int x = 0; x < HybridBinarizer::BLOCK_SIZE; x++) { + // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0. + if luminances[offset as usize + x] as u32 <= threshold { + matrix.set(xoffset + x as u32, yoffset + y as u32); + } + } + offset += stride; + } + } + + /** + * Calculates a single black point for each block of pixels and saves it away. + * See the following thread for a discussion of this algorithm: + * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 + */ + fn calculateBlackPoints( + luminances: &[u8], + subWidth: u32, + subHeight: u32, + width: u32, + height: u32, + ) -> Vec> { + let maxYOffset = height as usize - HybridBinarizer::BLOCK_SIZE; + let maxXOffset = width as usize - HybridBinarizer::BLOCK_SIZE; + let mut blackPoints = vec![vec![0; subWidth as usize]; subHeight as usize]; + for y in 0..subHeight { + // for (int y = 0; y < subHeight; y++) { + let mut yoffset = y << HybridBinarizer::BLOCK_SIZE_POWER; + if yoffset > maxYOffset as u32 { + yoffset = maxYOffset as u32; + } + for x in 0..subWidth { + // for (int x = 0; x < subWidth; x++) { + let mut xoffset = x << HybridBinarizer::BLOCK_SIZE_POWER; + if xoffset > maxXOffset as u32 { + xoffset = maxXOffset as u32; + } + let mut sum = 0; + let mut min = 0xFF; + let mut max = 0; + let mut offset = yoffset * width + xoffset; + for yy in 0..HybridBinarizer::BLOCK_SIZE { + // for (int yy = 0, offset = yoffset * width + xoffset; yy < HybridBinarizer::BLOCK_SIZE; yy++, offset += width) { + for xx in 0..HybridBinarizer::BLOCK_SIZE { + // for (int xx = 0; xx < HybridBinarizer::BLOCK_SIZE; xx++) { + let pixel = luminances[offset as usize + xx]; + sum += pixel; + // still looking for good contrast + if pixel < min { + min = pixel; + } + if pixel > max { + max = pixel; + } + } + // short-circuit min/max tests once dynamic range is met + if (max - min) as usize > HybridBinarizer::MIN_DYNAMIC_RANGE { + // finish the rest of the rows quickly + offset += width; + for _yy_s in yy + 1..HybridBinarizer::BLOCK_SIZE { + // for (yy++, offset += width; yy < HybridBinarizer::BLOCK_SIZE; yy++, offset += width) { + for xx in 0..HybridBinarizer::BLOCK_SIZE { + // for (int xx = 0; xx < BLOCK_SIZE; xx++) { + sum += luminances[offset as usize + xx]; + } + offset += width; + } + break; + } + offset += width; + } + + // The default estimate is the average of the values in the block. + let mut average = sum >> (HybridBinarizer::BLOCK_SIZE_POWER * 2); + if (max - min) as usize <= HybridBinarizer::MIN_DYNAMIC_RANGE { + // If variation within the block is low, assume this is a block with only light or only + // dark pixels. In that case we do not want to use the average, as it would divide this + // low contrast area into black and white pixels, essentially creating data out of noise. + // + // The default assumption is that the block is light/background. Since no estimate for + // the level of dark pixels exists locally, use half the min for the block. + average = min / 2; + + if y > 0 && x > 0 { + // Correct the "white background" assumption for blocks that have neighbors by comparing + // the pixels in this block to the previously calculated black points. This is based on + // the fact that dark barcode symbology is always surrounded by some amount of light + // background for which reasonable black point estimates were made. The bp estimated at + // the boundaries is used for the interior. + + // The (min < bp) is arbitrary but works better than other heuristics that were tried. + let averageNeighborBlackPoint = (blackPoints[y as usize - 1][x as usize] + + (2 * blackPoints[y as usize][x as usize - 1]) + + blackPoints[y as usize - 1][x as usize - 1]) + / 4; + if (min < averageNeighborBlackPoint) { + average = averageNeighborBlackPoint; + } + } + } + blackPoints[y as usize][x as usize] = average; + } + } + return blackPoints.into_iter().map(|x| x.iter().map(|y| *y as u32).collect()).collect(); + } +}