mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-26 04:12:34 +00:00
GlobalHistogramBinarizer ported
This commit is contained in:
@@ -1,203 +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 Binarizer implementation uses the old ZXing global histogram approach. It is suitable
|
|
||||||
* for low-end mobile devices which don't have enough CPU or memory to use a local thresholding
|
|
||||||
* algorithm. However, because it picks a global black point, it cannot handle difficult shadows
|
|
||||||
* and gradients.
|
|
||||||
*
|
|
||||||
* Faster mobile devices and all desktop applications should probably use HybridBinarizer instead.
|
|
||||||
*
|
|
||||||
* @author dswitkin@google.com (Daniel Switkin)
|
|
||||||
* @author Sean Owen
|
|
||||||
*/
|
|
||||||
public class GlobalHistogramBinarizer extends Binarizer {
|
|
||||||
|
|
||||||
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 static final byte[] EMPTY = new byte[0];
|
|
||||||
|
|
||||||
private byte[] luminances;
|
|
||||||
private final int[] buckets;
|
|
||||||
|
|
||||||
public GlobalHistogramBinarizer(LuminanceSource source) {
|
|
||||||
super(source);
|
|
||||||
luminances = EMPTY;
|
|
||||||
buckets = new int[LUMINANCE_BUCKETS];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Applies simple sharpening to the row data to improve performance of the 1D Readers.
|
|
||||||
@Override
|
|
||||||
public BitArray getBlackRow(int y, BitArray row) throws NotFoundException {
|
|
||||||
LuminanceSource source = getLuminanceSource();
|
|
||||||
int width = source.getWidth();
|
|
||||||
if (row == null || row.getSize() < width) {
|
|
||||||
row = new BitArray(width);
|
|
||||||
} else {
|
|
||||||
row.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
initArrays(width);
|
|
||||||
byte[] localLuminances = source.getRow(y, luminances);
|
|
||||||
int[] localBuckets = buckets;
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
localBuckets[(localLuminances[x] & 0xff) >> LUMINANCE_SHIFT]++;
|
|
||||||
}
|
|
||||||
int blackPoint = estimateBlackPoint(localBuckets);
|
|
||||||
|
|
||||||
if (width < 3) {
|
|
||||||
// Special case for very small images
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
if ((localLuminances[x] & 0xff) < blackPoint) {
|
|
||||||
row.set(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
int left = localLuminances[0] & 0xff;
|
|
||||||
int center = localLuminances[1] & 0xff;
|
|
||||||
for (int x = 1; x < width - 1; x++) {
|
|
||||||
int right = localLuminances[x + 1] & 0xff;
|
|
||||||
// A simple -1 4 -1 box filter with a weight of 2.
|
|
||||||
if (((center * 4) - left - right) / 2 < blackPoint) {
|
|
||||||
row.set(x);
|
|
||||||
}
|
|
||||||
left = center;
|
|
||||||
center = right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return row;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Does not sharpen the data, as this call is intended to only be used by 2D Readers.
|
|
||||||
@Override
|
|
||||||
public BitMatrix getBlackMatrix() throws NotFoundException {
|
|
||||||
LuminanceSource source = getLuminanceSource();
|
|
||||||
int width = source.getWidth();
|
|
||||||
int height = source.getHeight();
|
|
||||||
BitMatrix matrix = new BitMatrix(width, height);
|
|
||||||
|
|
||||||
// Quickly calculates the histogram by sampling four rows from the image. This proved to be
|
|
||||||
// more robust on the blackbox tests than sampling a diagonal as we used to do.
|
|
||||||
initArrays(width);
|
|
||||||
int[] localBuckets = buckets;
|
|
||||||
for (int y = 1; y < 5; y++) {
|
|
||||||
int row = height * y / 5;
|
|
||||||
byte[] localLuminances = source.getRow(row, luminances);
|
|
||||||
int right = (width * 4) / 5;
|
|
||||||
for (int x = width / 5; x < right; x++) {
|
|
||||||
int pixel = localLuminances[x] & 0xff;
|
|
||||||
localBuckets[pixel >> LUMINANCE_SHIFT]++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int blackPoint = estimateBlackPoint(localBuckets);
|
|
||||||
|
|
||||||
// We delay reading the entire image luminance until the black point estimation succeeds.
|
|
||||||
// Although we end up reading four rows twice, it is consistent with our motto of
|
|
||||||
// "fail quickly" which is necessary for continuous scanning.
|
|
||||||
byte[] localLuminances = source.getMatrix();
|
|
||||||
for (int y = 0; y < height; y++) {
|
|
||||||
int offset = y * width;
|
|
||||||
for (int x = 0; x < width; x++) {
|
|
||||||
int pixel = localLuminances[offset + x] & 0xff;
|
|
||||||
if (pixel < blackPoint) {
|
|
||||||
matrix.set(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return matrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Binarizer createBinarizer(LuminanceSource source) {
|
|
||||||
return new GlobalHistogramBinarizer(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initArrays(int luminanceSize) {
|
|
||||||
if (luminances.length < luminanceSize) {
|
|
||||||
luminances = new byte[luminanceSize];
|
|
||||||
}
|
|
||||||
for (int x = 0; x < LUMINANCE_BUCKETS; x++) {
|
|
||||||
buckets[x] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int estimateBlackPoint(int[] buckets) throws NotFoundException {
|
|
||||||
// Find the tallest peak in the histogram.
|
|
||||||
int numBuckets = buckets.length;
|
|
||||||
int maxBucketCount = 0;
|
|
||||||
int firstPeak = 0;
|
|
||||||
int firstPeakSize = 0;
|
|
||||||
for (int x = 0; x < numBuckets; x++) {
|
|
||||||
if (buckets[x] > firstPeakSize) {
|
|
||||||
firstPeak = x;
|
|
||||||
firstPeakSize = buckets[x];
|
|
||||||
}
|
|
||||||
if (buckets[x] > maxBucketCount) {
|
|
||||||
maxBucketCount = buckets[x];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the second-tallest peak which is somewhat far from the tallest peak.
|
|
||||||
int secondPeak = 0;
|
|
||||||
int secondPeakScore = 0;
|
|
||||||
for (int x = 0; x < numBuckets; x++) {
|
|
||||||
int distanceToBiggest = x - firstPeak;
|
|
||||||
// Encourage more distant second peaks by multiplying by square of distance.
|
|
||||||
int score = buckets[x] * distanceToBiggest * distanceToBiggest;
|
|
||||||
if (score > secondPeakScore) {
|
|
||||||
secondPeak = x;
|
|
||||||
secondPeakScore = score;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure firstPeak corresponds to the black peak.
|
|
||||||
if (firstPeak > secondPeak) {
|
|
||||||
int temp = firstPeak;
|
|
||||||
firstPeak = secondPeak;
|
|
||||||
secondPeak = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is too little contrast in the image to pick a meaningful black point, throw rather
|
|
||||||
// than waste time trying to decode the image, and risk false positives.
|
|
||||||
if (secondPeak - firstPeak <= numBuckets / 16) {
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find a valley between them that is low and closer to the white peak.
|
|
||||||
int bestValley = secondPeak - 1;
|
|
||||||
int bestValleyScore = -1;
|
|
||||||
for (int x = secondPeak - 1; x > firstPeak; x--) {
|
|
||||||
int fromFirst = x - firstPeak;
|
|
||||||
int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]);
|
|
||||||
if (score > bestValleyScore) {
|
|
||||||
bestValley = x;
|
|
||||||
bestValleyScore = score;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bestValley << LUMINANCE_SHIFT;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -8,8 +8,10 @@ use std::collections::HashMap;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::Binarizer;
|
||||||
use crate::DecodeHintType;
|
use crate::DecodeHintType;
|
||||||
use crate::Exceptions;
|
use crate::Exceptions;
|
||||||
|
use crate::LuminanceSource;
|
||||||
use crate::RXingResultPoint;
|
use crate::RXingResultPoint;
|
||||||
use encoding::Encoding;
|
use encoding::Encoding;
|
||||||
|
|
||||||
@@ -3286,7 +3288,11 @@ impl MinimalECIInput {
|
|||||||
|
|
||||||
fn addEdge(edges: &mut Vec<Vec<Option<Rc<InputEdge>>>>, to: usize, edge: Rc<InputEdge>) {
|
fn addEdge(edges: &mut Vec<Vec<Option<Rc<InputEdge>>>>, to: usize, edge: Rc<InputEdge>) {
|
||||||
if edges[to][edge.encoderIndex].is_none()
|
if edges[to][edge.encoderIndex].is_none()
|
||||||
|| edges[to][edge.encoderIndex].clone().unwrap().cachedTotalSize > edge.cachedTotalSize
|
|| edges[to][edge.encoderIndex]
|
||||||
|
.clone()
|
||||||
|
.unwrap()
|
||||||
|
.cachedTotalSize
|
||||||
|
> edge.cachedTotalSize
|
||||||
{
|
{
|
||||||
edges[to][edge.encoderIndex] = Some(edge.clone());
|
edges[to][edge.encoderIndex] = Some(edge.clone());
|
||||||
}
|
}
|
||||||
@@ -3313,7 +3319,7 @@ impl MinimalECIInput {
|
|||||||
|
|
||||||
for i in start..end {
|
for i in start..end {
|
||||||
// for (int i = start; i < end; i++) {
|
// for (int i = start; i < end; i++) {
|
||||||
if ch as u16== fnc1 || encoderSet.canEncode(ch, i) {
|
if ch as u16 == fnc1 || encoderSet.canEncode(ch, i) {
|
||||||
Self::addEdge(
|
Self::addEdge(
|
||||||
edges,
|
edges,
|
||||||
from + 1,
|
from + 1,
|
||||||
@@ -3364,14 +3370,18 @@ impl MinimalECIInput {
|
|||||||
if minimalJ < 0 {
|
if minimalJ < 0 {
|
||||||
panic!("Internal error: failed to encode \"{}\"", stringToEncode);
|
panic!("Internal error: failed to encode \"{}\"", stringToEncode);
|
||||||
}
|
}
|
||||||
let mut intsAL:Vec<u16> = Vec::new();
|
let mut intsAL: Vec<u16> = Vec::new();
|
||||||
let mut current = edges[inputLength][minimalJ as usize].clone();
|
let mut current = edges[inputLength][minimalJ as usize].clone();
|
||||||
while current.is_some() {
|
while current.is_some() {
|
||||||
let c = current.unwrap().clone();
|
let c = current.unwrap().clone();
|
||||||
if c.isFNC1() {
|
if c.isFNC1() {
|
||||||
intsAL.splice(0..0, [1000]);
|
intsAL.splice(0..0, [1000]);
|
||||||
} else {
|
} else {
|
||||||
let bytes:Vec<u16> = encoderSet.encode_char(c.c as u8 as char, c.encoderIndex).iter().map(|x| *x as u16).collect();
|
let bytes: Vec<u16> = encoderSet
|
||||||
|
.encode_char(c.c as u8 as char, c.encoderIndex)
|
||||||
|
.iter()
|
||||||
|
.map(|x| *x as u16)
|
||||||
|
.collect();
|
||||||
let mut i = bytes.len() as i32 - 1;
|
let mut i = bytes.len() as i32 - 1;
|
||||||
while i >= 0 {
|
while i >= 0 {
|
||||||
// for (int i = bytes.length - 1; i >= 0; i--) {
|
// for (int i = bytes.length - 1; i >= 0; i--) {
|
||||||
@@ -3385,7 +3395,10 @@ impl MinimalECIInput {
|
|||||||
c.previous.clone().unwrap().encoderIndex
|
c.previous.clone().unwrap().encoderIndex
|
||||||
};
|
};
|
||||||
if previousEncoderIndex != c.encoderIndex {
|
if previousEncoderIndex != c.encoderIndex {
|
||||||
intsAL.splice(0..0, [256 as u16+ encoderSet.getECIValue(c.encoderIndex) as u16]);
|
intsAL.splice(
|
||||||
|
0..0,
|
||||||
|
[256 as u16 + encoderSet.getECIValue(c.encoderIndex) as u16],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
current = c.previous.clone();
|
current = c.previous.clone();
|
||||||
}
|
}
|
||||||
@@ -3412,7 +3425,7 @@ impl InputEdge {
|
|||||||
previous: Option<Rc<InputEdge>>,
|
previous: Option<Rc<InputEdge>>,
|
||||||
fnc1: u16,
|
fnc1: u16,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut size = if c == 1000 {
|
let mut size = if c == 1000 {
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
encoderSet.encode_char(c as u8 as char, encoderIndex).len()
|
encoderSet.encode_char(c as u8 as char, encoderIndex).len()
|
||||||
@@ -3426,7 +3439,7 @@ impl InputEdge {
|
|||||||
size += prev.cachedTotalSize;
|
size += prev.cachedTotalSize;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
c: if c as u16== fnc1 { 1000 } else { c as u16 },
|
c: if c as u16 == fnc1 { 1000 } else { c as u16 },
|
||||||
encoderIndex,
|
encoderIndex,
|
||||||
previous: Some(prev.clone()),
|
previous: Some(prev.clone()),
|
||||||
cachedTotalSize: size,
|
cachedTotalSize: size,
|
||||||
@@ -3499,3 +3512,246 @@ impl fmt::Display for MinimalECIInput {
|
|||||||
write!(f, "{}", result)
|
write!(f, "{}", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 Binarizer implementation uses the old ZXing global histogram approach. It is suitable
|
||||||
|
* for low-end mobile devices which don't have enough CPU or memory to use a local thresholding
|
||||||
|
* algorithm. However, because it picks a global black point, it cannot handle difficult shadows
|
||||||
|
* and gradients.
|
||||||
|
*
|
||||||
|
* Faster mobile devices and all desktop applications should probably use HybridBinarizer instead.
|
||||||
|
*
|
||||||
|
* @author dswitkin@google.com (Daniel Switkin)
|
||||||
|
* @author Sean Owen
|
||||||
|
*/
|
||||||
|
pub struct GlobalHistogramBinarizer {
|
||||||
|
luminances: Vec<u8>,
|
||||||
|
buckets: Vec<u32>,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
source: Box<dyn LuminanceSource>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Binarizer for GlobalHistogramBinarizer {
|
||||||
|
fn getLuminanceSource(&self) -> &Box<dyn LuminanceSource> {
|
||||||
|
&self.source
|
||||||
|
}
|
||||||
|
|
||||||
|
// Applies simple sharpening to the row data to improve performance of the 1D Readers.
|
||||||
|
fn getBlackRow(&self, y: usize, row: &mut BitArray) -> Result<BitArray, Exceptions> {
|
||||||
|
let source = self.getLuminanceSource();
|
||||||
|
let width = source.getWidth();
|
||||||
|
let mut row = if row.getSize() < width {
|
||||||
|
BitArray::with_size(width)
|
||||||
|
} else {
|
||||||
|
let mut z = row.clone();
|
||||||
|
z.clear();
|
||||||
|
z
|
||||||
|
};
|
||||||
|
|
||||||
|
// self.initArrays(width);
|
||||||
|
let localLuminances = source.getRow(y, &self.luminances);
|
||||||
|
let mut localBuckets = self.buckets.clone();
|
||||||
|
for x in 0..width {
|
||||||
|
// for (int x = 0; x < width; x++) {
|
||||||
|
localBuckets
|
||||||
|
[((localLuminances[x]) >> GlobalHistogramBinarizer::LUMINANCE_SHIFT) as usize] += 1;
|
||||||
|
}
|
||||||
|
let blackPoint = self.estimateBlackPoint(&localBuckets)?;
|
||||||
|
|
||||||
|
if width < 3 {
|
||||||
|
// Special case for very small images
|
||||||
|
for x in 0..width {
|
||||||
|
// for (int x = 0; x < width; x++) {
|
||||||
|
if (localLuminances[x] as u32) < blackPoint {
|
||||||
|
row.set(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut left = localLuminances[0]; // & 0xff;
|
||||||
|
let mut center = localLuminances[1]; // & 0xff;
|
||||||
|
for x in 1..width - 1 {
|
||||||
|
// for (int x = 1; x < width - 1; x++) {
|
||||||
|
let right = localLuminances[x + 1] & 0xff;
|
||||||
|
// A simple -1 4 -1 box filter with a weight of 2.
|
||||||
|
if ((center * 4) - left - right) as u32 / 2 < blackPoint {
|
||||||
|
row.set(x);
|
||||||
|
}
|
||||||
|
left = center;
|
||||||
|
center = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does not sharpen the data, as this call is intended to only be used by 2D Readers.
|
||||||
|
fn getBlackMatrix(&self) -> Result<BitMatrix, Exceptions> {
|
||||||
|
let source = self.getLuminanceSource();
|
||||||
|
let width = source.getWidth();
|
||||||
|
let height = source.getHeight();
|
||||||
|
let mut matrix = BitMatrix::new(width as u32, height as u32)?;
|
||||||
|
|
||||||
|
// Quickly calculates the histogram by sampling four rows from the image. This proved to be
|
||||||
|
// more robust on the blackbox tests than sampling a diagonal as we used to do.
|
||||||
|
// self.initArrays(width);
|
||||||
|
let mut localBuckets = self.buckets.clone();
|
||||||
|
for y in 1..5 {
|
||||||
|
// for (int y = 1; y < 5; y++) {
|
||||||
|
let row = height * y / 5;
|
||||||
|
let localLuminances = source.getRow(row, &self.luminances);
|
||||||
|
let right = (width * 4) / 5;
|
||||||
|
let mut x = width / 5;
|
||||||
|
while x < right {
|
||||||
|
// for (int x = width / 5; x < right; x++) {
|
||||||
|
let pixel = localLuminances[x];
|
||||||
|
localBuckets[(pixel >> GlobalHistogramBinarizer::LUMINANCE_SHIFT) as usize] += 1;
|
||||||
|
x += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let blackPoint = self.estimateBlackPoint(&localBuckets)?;
|
||||||
|
|
||||||
|
// We delay reading the entire image luminance until the black point estimation succeeds.
|
||||||
|
// Although we end up reading four rows twice, it is consistent with our motto of
|
||||||
|
// "fail quickly" which is necessary for continuous scanning.
|
||||||
|
let localLuminances = source.getMatrix();
|
||||||
|
for y in 0..height {
|
||||||
|
// for (int y = 0; y < height; y++) {
|
||||||
|
let offset = y * width;
|
||||||
|
for x in 0..width {
|
||||||
|
// for (int x = 0; x < width; x++) {
|
||||||
|
let pixel = localLuminances[offset + x] & 0xff;
|
||||||
|
if (pixel as u32) < blackPoint {
|
||||||
|
matrix.set(x as u32, y as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(matrix)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createBinarizer(&self, source: Box<dyn crate::LuminanceSource>) -> Box<dyn Binarizer> {
|
||||||
|
return Box::new(GlobalHistogramBinarizer::new(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getWidth(&self) -> usize {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getHeight(&self) -> usize {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalHistogramBinarizer {
|
||||||
|
const LUMINANCE_BITS: usize = 5;
|
||||||
|
const LUMINANCE_SHIFT: usize = 8 - GlobalHistogramBinarizer::LUMINANCE_BITS;
|
||||||
|
const LUMINANCE_BUCKETS: usize = 1 << GlobalHistogramBinarizer::LUMINANCE_BITS;
|
||||||
|
const EMPTY: [u8; 0] = [0; 0];
|
||||||
|
|
||||||
|
pub fn new(source: Box<dyn LuminanceSource>) -> Self {
|
||||||
|
Self {
|
||||||
|
luminances: vec![0; source.getWidth()],
|
||||||
|
buckets: vec![0; GlobalHistogramBinarizer::LUMINANCE_BUCKETS],
|
||||||
|
width: source.getWidth(),
|
||||||
|
height: source.getHeight(),
|
||||||
|
source: source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn initArrays(&mut self, luminanceSize: usize) {
|
||||||
|
// // if self.luminances.len() < luminanceSize {
|
||||||
|
// // self.luminances = ;
|
||||||
|
// // }
|
||||||
|
// // // for x in 0..GlobalHistogramBinarizer::LUMINANCE_BUCKETS {
|
||||||
|
// // // for (int x = 0; x < LUMINANCE_BUCKETS; x++) {
|
||||||
|
// // self.buckets[x] = 0;
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn estimateBlackPoint(&self, buckets: &[u32]) -> Result<u32, Exceptions> {
|
||||||
|
// Find the tallest peak in the histogram.
|
||||||
|
let numBuckets = buckets.len();
|
||||||
|
let mut maxBucketCount = 0;
|
||||||
|
let mut firstPeak = 0;
|
||||||
|
let mut firstPeakSize = 0;
|
||||||
|
for x in 0..numBuckets {
|
||||||
|
// for (int x = 0; x < numBuckets; x++) {
|
||||||
|
if buckets[x] > firstPeakSize {
|
||||||
|
firstPeak = x;
|
||||||
|
firstPeakSize = buckets[x];
|
||||||
|
}
|
||||||
|
if buckets[x] > maxBucketCount {
|
||||||
|
maxBucketCount = buckets[x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the second-tallest peak which is somewhat far from the tallest peak.
|
||||||
|
let mut secondPeak = 0;
|
||||||
|
let mut secondPeakScore = 0;
|
||||||
|
for x in 0..numBuckets {
|
||||||
|
// for (int x = 0; x < numBuckets; x++) {
|
||||||
|
let distanceToBiggest = x - firstPeak;
|
||||||
|
// Encourage more distant second peaks by multiplying by square of distance.
|
||||||
|
let score = buckets[x] * distanceToBiggest as u32 * distanceToBiggest as u32;
|
||||||
|
if score > secondPeakScore {
|
||||||
|
secondPeak = x;
|
||||||
|
secondPeakScore = score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure firstPeak corresponds to the black peak.
|
||||||
|
if firstPeak > secondPeak {
|
||||||
|
let temp = firstPeak;
|
||||||
|
firstPeak = secondPeak;
|
||||||
|
secondPeak = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is too little contrast in the image to pick a meaningful black point, throw rather
|
||||||
|
// than waste time trying to decode the image, and risk false positives.
|
||||||
|
if secondPeak - firstPeak <= numBuckets / 16 {
|
||||||
|
return Err(Exceptions::NotFoundException(
|
||||||
|
"secondPeak - firstPeak <= numBuckets / 16 ".to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a valley between them that is low and closer to the white peak.
|
||||||
|
let mut bestValley = secondPeak - 1;
|
||||||
|
let mut bestValleyScore = -1i32;
|
||||||
|
let mut x = secondPeak;
|
||||||
|
while x > firstPeak {
|
||||||
|
// for (int x = secondPeak - 1; x > firstPeak; x--) {
|
||||||
|
let fromFirst = x - firstPeak;
|
||||||
|
let score =
|
||||||
|
fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]) as usize;
|
||||||
|
if score as i32 > bestValleyScore {
|
||||||
|
bestValley = x;
|
||||||
|
bestValleyScore = score as i32;
|
||||||
|
}
|
||||||
|
x -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((bestValley as u32) << GlobalHistogramBinarizer::LUMINANCE_SHIFT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1023,7 +1023,7 @@ pub trait Binarizer {
|
|||||||
//private final LuminanceSource source;
|
//private final LuminanceSource source;
|
||||||
//fn new(source:dyn LuminanceSource) -> Self;
|
//fn new(source:dyn LuminanceSource) -> Self;
|
||||||
|
|
||||||
fn getLuminanceSource(&self) -> &dyn LuminanceSource;
|
fn getLuminanceSource(&self) -> &Box<dyn LuminanceSource>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts one row of luminance data to 1 bit data. May actually do the conversion, or return
|
* Converts one row of luminance data to 1 bit data. May actually do the conversion, or return
|
||||||
@@ -1039,7 +1039,7 @@ pub trait Binarizer {
|
|||||||
* @return The array of bits for this row (true means black).
|
* @return The array of bits for this row (true means black).
|
||||||
* @throws NotFoundException if row can't be binarized
|
* @throws NotFoundException if row can't be binarized
|
||||||
*/
|
*/
|
||||||
fn getBlackRow(&self, y: usize, row: BitArray) -> Result<BitArray, Exceptions>;
|
fn getBlackRow(&self, y: usize, row: &mut BitArray) -> Result<BitArray, Exceptions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a 2D array of luminance data to 1 bit data. As above, assume this method is expensive
|
* Converts a 2D array of luminance data to 1 bit data. As above, assume this method is expensive
|
||||||
@@ -1129,7 +1129,7 @@ impl BinaryBitmap {
|
|||||||
* @return The array of bits for this row (true means black).
|
* @return The array of bits for this row (true means black).
|
||||||
* @throws NotFoundException if row can't be binarized
|
* @throws NotFoundException if row can't be binarized
|
||||||
*/
|
*/
|
||||||
pub fn getBlackRow(&self, y: usize, row: BitArray) -> Result<BitArray, Exceptions> {
|
pub fn getBlackRow(&self, y: usize, row: &mut BitArray) -> Result<BitArray, Exceptions> {
|
||||||
return self.binarizer.getBlackRow(y, row);
|
return self.binarizer.getBlackRow(y, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user