use std::borrow::Cow;
use once_cell::unsync::OnceCell;
use crate::common::Result;
use crate::{Binarizer, LuminanceSource};
use super::{BitArray, BitMatrix, GlobalHistogramBinarizer};
pub struct HybridBinarizer<LS: LuminanceSource> {
ghb: GlobalHistogramBinarizer<LS>,
black_matrix: OnceCell<BitMatrix>,
}
impl<LS: LuminanceSource> Binarizer for HybridBinarizer<LS> {
type Source = LS;
fn get_luminance_source(&self) -> &LS {
self.ghb.get_luminance_source()
}
fn get_black_row(&self, y: usize) -> Result<Cow<BitArray>> {
self.ghb.get_black_row(y)
}
fn get_black_matrix(&self) -> Result<&BitMatrix> {
let matrix = self
.black_matrix
.get_or_try_init(|| Self::calculateBlackMatrix(&self.ghb))?;
Ok(matrix)
}
fn create_binarizer(&self, source: LS) -> Self {
Self::new(source)
}
fn get_width(&self) -> usize {
self.ghb.get_width()
}
fn get_height(&self) -> usize {
self.ghb.get_height()
}
}
const BLOCK_SIZE_POWER: usize = 3;
const BLOCK_SIZE: usize = 1 << BLOCK_SIZE_POWER; const BLOCK_SIZE_MASK: usize = BLOCK_SIZE - 1; const MINIMUM_DIMENSION: usize = BLOCK_SIZE * 5;
const MIN_DYNAMIC_RANGE: usize = 24;
impl<LS: LuminanceSource> HybridBinarizer<LS> {
pub fn new(source: LS) -> Self {
let ghb = GlobalHistogramBinarizer::new(source);
Self {
black_matrix: OnceCell::new(),
ghb,
}
}
fn calculateBlackMatrix<LS2: LuminanceSource>(
ghb: &GlobalHistogramBinarizer<LS2>,
) -> Result<BitMatrix> {
let source = ghb.get_luminance_source();
let width = source.get_width();
let height = source.get_height();
if width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION {
let luminances = source.get_matrix();
let mut sub_width = width >> BLOCK_SIZE_POWER;
if (width & BLOCK_SIZE_MASK) != 0 {
sub_width += 1;
}
let mut sub_height = height >> BLOCK_SIZE_POWER;
if (height & BLOCK_SIZE_MASK) != 0 {
sub_height += 1;
}
let black_points = Self::calculateBlackPoints(
&luminances,
sub_width as u32,
sub_height as u32,
width as u32,
height as u32,
);
let mut new_matrix = BitMatrix::new(width as u32, height as u32)?;
Self::calculateThresholdForBlock(
&luminances,
sub_width as u32,
sub_height as u32,
width as u32,
height as u32,
&black_points,
&mut new_matrix,
);
Ok(new_matrix)
} else {
let m = ghb.get_black_matrix()?;
Ok(m.clone())
}
}
fn calculateThresholdForBlock(
luminances: &[u8],
sub_width: u32,
sub_height: u32,
width: u32,
height: u32,
black_points: &[Vec<u32>],
matrix: &mut BitMatrix,
) {
let maxYOffset = height - BLOCK_SIZE as u32;
let maxXOffset = width - BLOCK_SIZE as u32;
for y in 0..sub_height {
let mut yoffset = y << BLOCK_SIZE_POWER;
if yoffset > maxYOffset {
yoffset = maxYOffset;
}
let top = Self::cap(y, sub_height - 3);
for x in 0..sub_width {
let mut xoffset = x << BLOCK_SIZE_POWER;
if xoffset > maxXOffset {
xoffset = maxXOffset;
}
let left = Self::cap(x, sub_width - 3);
let mut sum = 0;
for z in -2..=2 {
let blackRow = &black_points[(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);
}
}
}
#[inline(always)]
fn cap(value: u32, max: u32) -> u32 {
if value < 2 {
2
} else {
value.min(max)
}
}
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..BLOCK_SIZE {
for x in 0..BLOCK_SIZE {
if luminances[offset as usize + x] as u32 <= threshold {
matrix.set(xoffset + x as u32, yoffset + y as u32);
}
}
offset += stride;
}
}
fn calculateBlackPoints(
luminances: &[u8],
subWidth: u32,
subHeight: u32,
width: u32,
height: u32,
) -> Vec<Vec<u32>> {
let maxYOffset = height as usize - BLOCK_SIZE;
let maxXOffset = width as usize - BLOCK_SIZE;
let mut blackPoints = vec![vec![0; subWidth as usize]; subHeight as usize];
for y in 0..subHeight {
let mut yoffset = y << BLOCK_SIZE_POWER;
if yoffset > maxYOffset as u32 {
yoffset = maxYOffset as u32;
}
for x in 0..subWidth {
let mut xoffset = x << BLOCK_SIZE_POWER;
if xoffset > maxXOffset as u32 {
xoffset = maxXOffset as u32;
}
let mut sum: u32 = 0;
let mut min = 0xff;
let mut max = 0;
let mut offset = yoffset * width + xoffset;
let mut yy = 0;
while yy < BLOCK_SIZE {
for xx in 0..BLOCK_SIZE {
let pixel = luminances[offset as usize + xx];
sum += pixel as u32;
if pixel < min {
min = pixel;
}
if pixel > max {
max = pixel;
}
}
if (max - min) as usize > MIN_DYNAMIC_RANGE {
offset += width;
yy += 1;
while yy < BLOCK_SIZE {
for xx in 0..BLOCK_SIZE {
sum += luminances[offset as usize + xx] as u32;
}
yy += 1;
offset += width;
}
break;
}
yy += 1;
offset += width;
}
let mut average = sum >> (BLOCK_SIZE_POWER * 2);
if (max - min) as usize <= MIN_DYNAMIC_RANGE {
average = min as u32 / 2;
if y > 0 && x > 0 {
let average_neighbor_black_point: u32 = (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 as u32) < average_neighbor_black_point {
average = average_neighbor_black_point;
}
}
}
blackPoints[y as usize][x as usize] = average;
}
}
blackPoints
}
}