use crate::jbig2sym::BitImage;
#[derive(Clone, Debug)]
pub struct Run {
pub y: i32,
pub x1: i32,
pub x2: i32,
pub ccid: i32,
}
impl Run {
fn sort_key(&self) -> (i32, i32) {
(self.y, self.x1)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct BBox {
pub xmin: i32,
pub ymin: i32,
pub xmax: i32,
pub ymax: i32,
}
impl BBox {
pub fn width(&self) -> i32 {
self.xmax - self.xmin
}
pub fn height(&self) -> i32 {
self.ymax - self.ymin
}
}
#[derive(Clone, Debug, Default)]
pub struct CC {
pub bb: BBox,
pub npix: i32,
pub nrun: i32,
pub frun: i32,
}
#[derive(Clone, Copy, Debug)]
pub struct ShapeRef {
pub ccid: usize,
pub bbox: BBox,
pub black_pixels: usize,
pub run_count: usize,
}
pub struct CCImage {
pub width: i32,
pub height: i32,
pub runs: Vec<Run>,
pub ccs: Vec<CC>,
pub nregularccs: usize,
pub largesize: i32,
pub smallsize: i32,
pub tinysize: i32,
}
impl CCImage {
pub fn new(width: i32, height: i32, dpi: i32) -> Self {
let dpi = dpi.max(200).min(900);
Self {
width,
height,
runs: Vec::new(),
ccs: Vec::new(),
nregularccs: 0,
largesize: 500.min(64.max(dpi)),
smallsize: 2.max(dpi / 150),
tinysize: 0.max(dpi * dpi / 20000 - 1),
}
}
pub fn add_single_run(&mut self, y: i32, x1: i32, x2: i32) {
self.runs.push(Run { y, x1, x2, ccid: 0 });
}
pub fn add_bitmap_runs(&mut self, bm: &BitImage) {
for y in 0..bm.height {
let row_start = y * bm.width;
let row_bits = &bm.as_bits()[row_start..row_start + bm.width];
let mut x = 0usize;
while x < bm.width {
if let Some(black_offset) = row_bits[x..].first_one() {
let x1 = x + black_offset;
let run_length = row_bits[x1..].first_zero().unwrap_or(bm.width - x1);
let x2 = x1 + run_length - 1;
self.add_single_run(y as i32, x1 as i32, x2 as i32);
x = x2 + 1;
} else {
break; }
}
}
}
pub fn make_ccids_by_analysis(&mut self) {
self.runs.sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
let n_runs = self.runs.len();
if n_runs == 0 {
return;
}
let mut umap: Vec<i32> = Vec::new();
let mut p: usize = 0;
for n in 0..n_runs {
let y = self.runs[n].y;
let x1 = self.runs[n].x1 - 1; let x2 = self.runs[n].x2 + 1;
let mut id: i32 = umap.len() as i32;
while p < n_runs && self.runs[p].y < y - 1 {
p += 1;
}
let mut pp = p;
while pp < n_runs && self.runs[pp].y < y && self.runs[pp].x1 <= x2 {
if self.runs[pp].x2 >= x1 {
let mut oid = self.runs[pp].ccid;
while (oid as usize) < umap.len() && umap[oid as usize] < oid {
oid = umap[oid as usize];
}
if id >= umap.len() as i32 {
id = oid;
} else if id < oid {
if (oid as usize) < umap.len() {
umap[oid as usize] = id;
}
} else if oid < id {
if (id as usize) < umap.len() {
umap[id as usize] = oid;
}
id = oid;
}
self.runs[pp].ccid = id;
if self.runs[pp].x2 >= x2 {
break;
}
}
pp += 1;
}
self.runs[n].ccid = id;
if id >= umap.len() as i32 {
let new_id = umap.len() as i32;
umap.push(new_id);
self.runs[n].ccid = new_id;
}
}
for n in 0..n_runs {
let mut ccid = self.runs[n].ccid;
while (ccid as usize) < umap.len() && umap[ccid as usize] < ccid {
ccid = umap[ccid as usize];
}
let root = ccid;
let mut id = self.runs[n].ccid;
while id != root {
let next = umap[id as usize];
umap[id as usize] = root;
id = next;
}
self.runs[n].ccid = root;
}
}
pub fn make_ccs_from_ccids(&mut self) {
if self.runs.is_empty() {
self.ccs.clear();
return;
}
let mut maxccid = (self.nregularccs as i32) - 1;
for run in &self.runs {
if run.ccid > maxccid {
maxccid = run.ccid;
}
}
if maxccid < 0 {
self.ccs.clear();
return;
}
let map_size = (maxccid + 1) as usize;
let mut rmap = vec![-1i32; map_size];
for run in &self.runs {
if run.ccid >= 0 {
rmap[run.ccid as usize] = 1; }
}
let mut nid = 0i32;
for entry in rmap.iter_mut() {
if *entry > 0 {
*entry = nid;
nid += 1;
}
}
while self.nregularccs > 0
&& (self.nregularccs - 1 < map_size)
&& rmap[self.nregularccs - 1] < 0
{
self.nregularccs -= 1;
}
if self.nregularccs > 0 && self.nregularccs <= map_size {
self.nregularccs = (1 + rmap[self.nregularccs - 1]) as usize;
}
let nid_us = nid as usize;
self.ccs = vec![CC::default(); nid_us];
for run in &self.runs {
if run.ccid < 0 {
continue;
}
let new_id = rmap[run.ccid as usize];
if new_id >= 0 {
self.ccs[new_id as usize].nrun += 1;
}
}
let mut frun = 0i32;
let mut positions = vec![0i32; nid_us];
for i in 0..nid_us {
self.ccs[i].frun = frun;
positions[i] = frun;
frun += self.ccs[i].nrun;
}
let mut sorted_runs = vec![
Run {
y: 0,
x1: 0,
x2: 0,
ccid: -1
};
frun as usize
];
for run in &self.runs {
if run.ccid < 0 {
continue;
}
let new_id = rmap[run.ccid as usize];
if new_id < 0 {
continue;
}
let pos = positions[new_id as usize] as usize;
sorted_runs[pos] = Run {
y: run.y,
x1: run.x1,
x2: run.x2,
ccid: new_id,
};
positions[new_id as usize] += 1;
}
self.runs = sorted_runs;
for i in 0..nid_us {
let cc = &self.ccs[i];
let start = cc.frun as usize;
let end = start + cc.nrun as usize;
self.runs[start..end].sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
let mut npix = 0i32;
let mut xmin = i32::MAX;
let mut xmax = i32::MIN;
let mut ymin = i32::MAX;
let mut ymax = i32::MIN;
for run in &self.runs[start..end] {
xmin = xmin.min(run.x1);
xmax = xmax.max(run.x2);
ymin = ymin.min(run.y);
ymax = ymax.max(run.y);
npix += run.x2 - run.x1 + 1;
}
let cc = &mut self.ccs[i];
cc.npix = npix;
cc.bb = BBox {
xmin,
ymin,
xmax: xmax + 1, ymax: ymax + 1, };
}
}
pub fn erase_tiny_ccs(&mut self) {
for i in 0..self.ccs.len() {
if self.ccs[i].npix <= self.tinysize {
let frun = self.ccs[i].frun as usize;
let nrun = self.ccs[i].nrun as usize;
self.ccs[i].nrun = 0;
self.ccs[i].npix = 0;
for r in frun..frun + nrun {
if r < self.runs.len() {
self.runs[r].ccid = -1;
}
}
}
}
}
pub fn merge_and_split_ccs(&mut self) {
if self.ccs.is_empty() {
return;
}
let splitsize = self.largesize;
let mut ncc = self.ccs.len() as i32;
let mut extra_runs: Vec<Run> = Vec::new();
use std::collections::HashMap;
let mut grid_map: HashMap<(i16, i16, i32), i32> = HashMap::new();
self.nregularccs = self.ccs.len();
let makeccid =
|key: (i16, i16, i32), map: &mut HashMap<(i16, i16, i32), i32>, ncc: &mut i32| -> i32 {
if let Some(&id) = map.get(&key) {
id
} else {
let id = *ncc;
map.insert(key, id);
*ncc += 1;
id
}
};
for ccid in 0..self.ccs.len() {
let cc = &self.ccs[ccid];
if cc.nrun <= 0 {
continue;
}
let cc_height = cc.bb.height();
let cc_width = cc.bb.width();
let frun = cc.frun as usize;
let nrun = cc.nrun as usize;
if cc_height <= self.smallsize && cc_width <= self.smallsize {
let gridi = ((cc.bb.ymin + cc.bb.ymax) / splitsize / 2) as i16;
let gridj = ((cc.bb.xmin + cc.bb.xmax) / splitsize / 2) as i16;
let key = (gridi, gridj, -1);
let new_ccid = makeccid(key, &mut grid_map, &mut ncc);
for r in frun..frun + nrun {
if r < self.runs.len() {
self.runs[r].ccid = new_ccid;
}
}
} else if cc_height >= self.largesize || cc_width >= self.largesize {
for r in frun..frun + nrun {
if r >= self.runs.len() {
continue;
}
let run_y = self.runs[r].y;
let run_x1 = self.runs[r].x1;
let run_x2 = self.runs[r].x2;
let gridi = (run_y / splitsize) as i16;
let gridj_start = (run_x1 / splitsize) as i16;
let gridj_end = (run_x2 / splitsize) as i16;
let key = (gridi, gridj_start, ccid as i32);
let new_ccid = makeccid(key, &mut grid_map, &mut ncc);
self.runs[r].ccid = new_ccid;
if gridj_end > gridj_start {
let orig_x2 = self.runs[r].x2;
self.runs[r].x2 = (gridj_start as i32 + 1) * splitsize - 1;
let mut current_gridj = gridj_start + 1;
while current_gridj < gridj_end {
let cell_x1 = current_gridj as i32 * splitsize;
let cell_x2 = cell_x1 + splitsize - 1;
let key = (gridi, current_gridj, ccid as i32);
let cell_ccid = makeccid(key, &mut grid_map, &mut ncc);
extra_runs.push(Run {
y: run_y,
x1: cell_x1,
x2: cell_x2,
ccid: cell_ccid,
});
current_gridj += 1;
}
let last_x1 = gridj_end as i32 * splitsize;
let key = (gridi, gridj_end, ccid as i32);
let last_ccid = makeccid(key, &mut grid_map, &mut ncc);
extra_runs.push(Run {
y: run_y,
x1: last_x1,
x2: orig_x2,
ccid: last_ccid,
});
}
}
}
}
self.runs.append(&mut extra_runs);
self.make_ccs_from_ccids();
}
pub fn sort_in_reading_order(&mut self) {
let n = self.nregularccs;
if n < 2 {
return;
}
let mut cc_arr: Vec<(usize, CC)> = self.ccs[..n]
.iter()
.enumerate()
.map(|(i, cc)| (i, cc.clone()))
.collect();
cc_arr.sort_by(|a, b| {
a.1.bb
.ymin
.cmp(&b.1.bb.ymin)
.then(a.1.bb.xmin.cmp(&b.1.bb.xmin))
.then(a.1.frun.cmp(&b.1.frun))
});
let maxtopchange = (self.width / 40).max(32);
let mut ccno = 0usize;
while ccno < n {
let line_start_ymin = cc_arr[ccno].1.bb.ymin;
let mut nccno = ccno + 1;
while nccno < n {
let curr_ymin = cc_arr[nccno].1.bb.ymin;
if curr_ymin > line_start_ymin + maxtopchange {
break;
}
nccno += 1;
}
cc_arr[ccno..nccno].sort_by(|a, b| a.1.bb.xmin.cmp(&b.1.bb.xmin));
ccno = nccno;
}
let mut new_ccs = Vec::with_capacity(self.ccs.len());
let mut old_to_new = vec![0usize; self.ccs.len()];
for (new_idx, (old_idx, cc)) in cc_arr.into_iter().enumerate() {
new_ccs.push(cc);
old_to_new[old_idx] = new_idx;
}
for i in n..self.ccs.len() {
let new_idx = new_ccs.len();
new_ccs.push(self.ccs[i].clone());
old_to_new[i] = new_idx;
}
self.ccs = new_ccs;
for run in &mut self.runs {
if run.ccid >= 0 {
run.ccid = old_to_new[run.ccid as usize] as i32;
}
}
}
pub fn get_bitmap_for_cc(&self, ccid: usize) -> Option<BitImage> {
if ccid >= self.ccs.len() {
return None;
}
let cc = &self.ccs[ccid];
let bb = &cc.bb;
let w = bb.width();
let h = bb.height();
if w <= 0 || h <= 0 {
return None;
}
let mut bm = BitImage::new(w as u32, h as u32).ok()?;
let frun = cc.frun as usize;
let nrun = cc.nrun as usize;
for i in frun..frun + nrun {
if i >= self.runs.len() {
break;
}
let run = &self.runs[i];
let row = run.y - bb.ymin;
for x in run.x1..=run.x2 {
let col = x - bb.xmin;
bm.set_usize(col as usize, row as usize, true);
}
}
Some(bm)
}
pub fn analyze(&mut self, losslevel: i32) {
let _ = losslevel;
self.make_ccids_by_analysis();
self.make_ccs_from_ccids();
self.sort_in_reading_order();
}
pub fn extract_shape_refs(&self) -> Vec<ShapeRef> {
let mut shapes = Vec::with_capacity(self.ccs.len());
for (ccid, cc) in self.ccs.iter().enumerate() {
if cc.nrun <= 0 {
continue;
}
shapes.push(ShapeRef {
ccid,
bbox: cc.bb,
black_pixels: cc.npix.max(0) as usize,
run_count: cc.nrun.max(0) as usize,
});
}
shapes
}
pub fn extract_shapes(&self) -> Vec<(BitImage, BBox)> {
let mut shapes = Vec::with_capacity(self.ccs.len());
for shape in self.extract_shape_refs() {
if let Some(bm) = self.get_bitmap_for_cc(shape.ccid) {
shapes.push((bm, shape.bbox));
}
}
shapes
}
}
pub fn analyze_page(image: &BitImage, dpi: i32, losslevel: i32) -> CCImage {
let mut ccimg = CCImage::new(image.width as i32, image.height as i32, dpi);
ccimg.add_bitmap_runs(image);
ccimg.analyze(losslevel);
ccimg
}
pub fn extract_symbols_for_jbig2(cc_image: &CCImage) -> Vec<(BitImage, i32, i32, i32, i32)> {
let shapes = cc_image.extract_shapes();
shapes
.into_iter()
.map(|(bitmap, bbox)| (bitmap, bbox.xmin, bbox.ymin, bbox.width(), bbox.height()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn make_test_image() -> BitImage {
let mut bm = BitImage::new(40, 20).unwrap();
for y in 2..7 {
for x in 2..7 {
bm.set_usize(x, y, true);
}
}
for y in 10..15 {
for x in 20..25 {
bm.set_usize(x, y, true);
}
}
bm
}
#[test]
fn test_run_extraction() {
let bm = make_test_image();
let mut ccimg = CCImage::new(40, 20, 300);
ccimg.add_bitmap_runs(&bm);
assert_eq!(ccimg.runs.len(), 10);
}
#[test]
fn test_cc_analysis_finds_two_components() {
let bm = make_test_image();
let mut ccimg = CCImage::new(40, 20, 300);
ccimg.add_bitmap_runs(&bm);
ccimg.make_ccids_by_analysis();
ccimg.make_ccs_from_ccids();
assert_eq!(ccimg.ccs.len(), 2);
assert_eq!(ccimg.ccs[0].npix, 25);
assert_eq!(ccimg.ccs[1].npix, 25);
}
#[test]
fn test_full_pipeline() {
let bm = make_test_image();
let ccimg = analyze_page(&bm, 300, 0);
let shapes = ccimg.extract_shapes();
assert_eq!(shapes.len(), 2);
for (bitmap, bb) in &shapes {
assert_eq!(bitmap.width, 5);
assert_eq!(bitmap.height, 5);
assert_eq!(bb.width(), 5);
assert_eq!(bb.height(), 5);
}
}
#[test]
fn test_tiny_cc_removal() {
let mut bm = BitImage::new(40, 20).unwrap();
for y in 2..7 {
for x in 2..7 {
bm.set_usize(x, y, true);
}
}
bm.set_usize(30, 10, true);
let ccimg = analyze_page(&bm, 300, 1); let shapes = ccimg.extract_shapes();
assert_eq!(shapes.len(), 1);
assert_eq!(shapes[0].0.width, 5);
}
}