pub fn glyph_masks(
data: &[u8],
width: usize,
height: usize,
boxes: &[[i64; 4]],
) -> Vec<Option<(Vec<u8>, f64)>> {
boxes
.iter()
.map(|b| {
let w = b[2] - b[0];
let h = b[3] - b[1];
if w < 6 || h < 9 {
return None;
}
let mut mask = vec![0u8; 128];
let mut count = 0;
for y in 0..32 {
for x in 0..32 {
let sx = b[0] + (((x as f64 + 0.5) * w as f64) / 32.0) as i64;
let sy = b[1] + (((y as f64 + 0.5) * h as f64) / 32.0) as i64;
let value = if sx < 0 || sy < 0 || sx >= width as i64 || sy >= height as i64 {
0
} else {
data[sy as usize * width + sx as usize]
};
if value < 128 {
let i = y * 32 + x;
mask[i / 8] |= 1 << (i % 8);
count += 1;
}
}
}
if !(0.08..=0.85).contains(&(count as f64 / 1024.0)) {
return None;
}
Some((mask, w as f64 / h as f64))
})
.collect()
}
pub fn blank_top_rgb(data: &[u8], width: usize, height: usize) -> f64 {
blank_top_channels(data, width, height, 3)
}
pub fn blank_top_gray(data: &[u8], width: usize, height: usize) -> f64 {
blank_top_channels(data, width, height, 1)
}
fn blank_top_channels(data: &[u8], width: usize, height: usize, channels: usize) -> f64 {
if width == 0 || height == 0 || width.saturating_mul(height) > 4_000_000 {
return 0.0;
}
let top = data
.chunks_exact(width * channels)
.position(|row| row.iter().any(|v| *v < 250));
let fraction = top.unwrap_or(0) as f64 / height as f64;
if (0.03..=0.12).contains(&fraction) {
fraction
} else {
0.0
}
}
pub fn crop_bgr(
data: &[u8],
width: usize,
height: usize,
stride: usize,
mode: &str,
bbox: (usize, usize, usize, usize),
angle: u16,
) -> Result<(Vec<u8>, usize, usize), &'static str> {
let (channels, rgb) = match mode {
"BGR" => (3, false),
"BGRX" | "BGRA" => (4, false),
"RGB" => (3, true),
"RGBX" | "RGBA" => (4, true),
"L" => (1, false),
_ => return Err("unsupported bitmap mode"),
};
if width.checked_mul(channels).is_none_or(|n| n > stride)
|| height.checked_mul(stride).is_none_or(|n| n > data.len())
{
return Err("invalid bitmap buffer or stride");
}
let (x0, y0, x1, y1) = bbox;
if x0 >= x1 || y0 >= y1 || x1 > width || y1 > height {
return Err("invalid crop bounds");
}
if ![0, 90, 180, 270].contains(&angle) {
return Err("invalid crop angle");
}
let (w, h) = (x1 - x0, y1 - y0);
let (ow, oh) = if angle == 90 || angle == 270 {
(h, w)
} else {
(w, h)
};
let len = ow
.checked_mul(oh)
.and_then(|n| n.checked_mul(3))
.ok_or("crop size overflow")?;
let mut output = vec![0; len];
if mode == "BGR" && angle == 0 {
for y in 0..h {
let start = (y0 + y) * stride + x0 * 3;
output[y * w * 3..(y + 1) * w * 3].copy_from_slice(&data[start..start + w * 3]);
}
} else {
for y in 0..h {
for x in 0..w {
let source = (y0 + y) * stride + (x0 + x) * channels;
let (dx, dy) = match angle {
90 => (y, w - 1 - x),
180 => (w - 1 - x, h - 1 - y),
270 => (h - 1 - y, x),
_ => (x, y),
};
let target = (dy * ow + dx) * 3;
let pixel = if channels == 1 {
[data[source]; 3]
} else if rgb {
[data[source + 2], data[source + 1], data[source]]
} else {
[data[source], data[source + 1], data[source + 2]]
};
output[target..target + 3].copy_from_slice(&pixel);
}
}
}
Ok((output, ow, oh))
}
pub fn blank_top_bitmap(
data: &[u8],
width: usize,
height: usize,
stride: usize,
mode: &str,
) -> Option<f64> {
let (channels, colors) = match mode {
"RGB" | "BGR" => (3usize, 3usize),
"RGBX" | "BGRX" => (4, 3),
"L" => (1, 1),
_ => return None,
};
if width.checked_mul(channels).is_none_or(|v| v > stride)
|| height.checked_mul(stride).is_none_or(|v| v > data.len())
{
return None;
}
if width == 0 || height == 0 || width.saturating_mul(height) > 4_000_000 {
return Some(0.0);
}
let top = (0..height).find(|&y| {
(0..width).any(|x| {
data[y * stride + x * channels..y * stride + x * channels + colors]
.iter()
.any(|&v| v < 250)
})
});
let fraction = top.unwrap_or(0) as f64 / height as f64;
Some(if (0.03..=0.12).contains(&fraction) {
fraction
} else {
0.0
})
}