use crate::render::style::ANSI_COLOR_RGB;
use deltae::*;
#[cfg(not(wasm))]
use image::{DynamicImage, GenericImageView, ImageBuffer, Luma, Rgb};
use lab::Lab;
#[cfg(not(wasm))]
use std::collections::HashMap;
#[derive(Debug, Clone, Copy)]
pub struct RGB {
pub r: u8,
pub g: u8,
pub b: u8,
}
#[derive(Debug, Clone)]
pub struct BinarizedBlock {
pub bitmap: Vec<Vec<u8>>,
pub width: usize,
pub height: usize,
pub foreground_color: RGB,
pub background_color: RGB,
pub threshold: u8,
}
#[derive(Debug, Clone)]
pub struct BinarizationConfig {
pub min_contrast_ratio: f32,
}
#[cfg(not(wasm))]
pub fn find_background_color(
img: &DynamicImage,
image: &ImageBuffer<Luma<u8>, Vec<u8>>,
w: u32,
h: u32,
) -> (u8, u32) {
let mut cc: HashMap<u32, (u32, u32, u32)> = HashMap::new();
for i in 0..h {
for j in 0..w {
let p = img.get_pixel(j, i);
let k: u32 = ((p[0] as u32) << 24)
+ ((p[1] as u32) << 16)
+ ((p[2] as u32) << 8)
+ (p[3] as u32);
cc.entry(k).or_insert((j, i, 0)).2 += 1;
}
}
let mut cv: Vec<_> = cc.iter().collect();
cv.sort_by(|b, a| a.1 .2.cmp(&b.1 .2));
let bx = cv[0].1 .0;
let by = cv[0].1 .1;
let bc = cv[0].0;
let gray = image.get_pixel(bx, by).0[0];
(gray, *bc)
}
pub fn find_best_color(color: RGB) -> usize {
let mut min_mse = f32::MAX;
let mut best_match = 0;
for (i, pcolor) in ANSI_COLOR_RGB.iter().enumerate() {
let pcrgb = RGB {
r: pcolor[0],
g: pcolor[1],
b: pcolor[2],
};
let mse = color_distance_rgb(&pcrgb, &color);
if mse < min_mse {
min_mse = mse;
best_match = i;
}
}
best_match
}
pub fn find_best_color_u32(c: u32) -> usize {
find_best_color(RGB {
r: (c >> 24) as u8,
g: (c >> 16) as u8,
b: (c >> 8) as u8,
})
}
pub fn color_distance_rgb(e1: &RGB, e2: &RGB) -> f32 {
let l1 = Lab::from_rgb(&[e1.r, e1.g, e1.b]);
let l2 = Lab::from_rgb(&[e2.r, e2.g, e2.b]);
let lab1 = LabValue {
l: l1.l,
a: l1.a,
b: l1.b,
};
let lab2 = LabValue {
l: l2.l,
a: l2.a,
b: l2.b,
};
*DeltaE::new(lab1, lab2, DE2000).value()
}
pub fn luminance(e1: u32) -> f32 {
let e1r = (e1 >> 24 & 0xff) as u8;
let e1g = (e1 >> 16 & 0xff) as u8;
let e1b = (e1 >> 8 & 0xff) as u8;
0.299 * e1r as f32 + 0.587 * e1g as f32 + 0.114 * e1b as f32
}
pub fn color_distance(e1: u32, e2: u32) -> f32 {
let e1r = (e1 >> 24 & 0xff) as u8;
let e1g = (e1 >> 16 & 0xff) as u8;
let e1b = (e1 >> 8 & 0xff) as u8;
let e2r = (e2 >> 24 & 0xff) as u8;
let e2g = (e2 >> 16 & 0xff) as u8;
let e2b = (e2 >> 8 & 0xff) as u8;
let l1 = Lab::from_rgb(&[e1r, e1g, e1b]);
let l2 = Lab::from_rgb(&[e2r, e2g, e2b]);
let lab1 = LabValue {
l: l1.l,
a: l1.a,
b: l1.b,
};
let lab2 = LabValue {
l: l2.l,
a: l2.a,
b: l2.b,
};
*DeltaE::new(lab1, lab2, DE2000).value()
}
#[repr(C)]
#[derive(Debug, Clone, PartialEq)]
pub struct Symbol {
pub width: u8,
pub height: u8,
pub is_binary: bool,
pub fore_color: u32,
pub back_color: u32,
pub data: Vec<Vec<u32>>,
pub binary_data: Vec<Vec<u8>>,
}
#[cfg(not(wasm))]
impl Symbol {
pub fn new(width: u8, height: u8, is_binary: bool, img: &DynamicImage) -> Self {
let mut data = vec![];
for i in 0..height {
let mut row = vec![];
for j in 0..width {
let p = img.get_pixel(j as u32, i as u32);
let k: u32 = ((p[0] as u32) << 24)
+ ((p[1] as u32) << 16)
+ ((p[2] as u32) << 8)
+ (p[3] as u32);
row.push(k);
}
data.push(row);
}
let binary_data = vec![];
let mut sym = Self {
width,
height,
is_binary,
fore_color: 0,
back_color: 0,
data,
binary_data,
};
sym.make_binary(0);
sym
}
pub fn make_binary(&mut self, back_rgb: u32) {
let mut cc: HashMap<u32, (u32, u32)> = HashMap::new();
let mut cm: Vec<u32> = vec![];
self.binary_data = vec![vec![0u8; self.width as usize]; self.height as usize];
for i in 0..self.height {
for j in 0..self.width {
let pixel_x = j as u32;
let pixel_y = i as u32;
let k = self.data[pixel_y as usize][pixel_x as usize];
cc.entry(k).or_insert((pixel_x, pixel_y));
cm.push(k);
}
}
let mut cv: Vec<_> = cc.iter().collect();
let mut include_back = false;
let clen = cv.len();
for c in &mut cv {
if *c.0 == back_rgb {
include_back = true;
} else {
let cd = color_distance(*c.0, back_rgb);
if cd < 1.0 {
c.0 = &back_rgb;
include_back = true;
}
}
}
let ret;
if include_back {
if clen == 1 {
ret = Some((back_rgb, back_rgb));
} else if clen == 2 {
let mut r = (back_rgb, back_rgb);
if *cv[0].0 != back_rgb {
r.1 = *cv[0].0;
}
if *cv[1].0 != back_rgb {
r.1 = *cv[1].0;
}
ret = Some(r);
} else {
let mut bigd = 0.0f32;
let mut bcv = cv[0];
for c in &cv {
let cd = color_distance(*c.0, back_rgb);
if cd > bigd {
bigd = cd;
bcv = *c;
}
}
ret = Some((back_rgb, *bcv.0));
}
} else if clen == 1 {
ret = Some((*cv[0].0, *cv[0].0));
} else if clen == 2 {
let l1 = luminance(*cv[0].0);
let l2 = luminance(*cv[1].0);
if l2 > l1 {
ret = Some((*cv[0].0, *cv[1].0));
} else {
ret = Some((*cv[1].0, *cv[0].0));
}
} else {
let mut ccv = vec![];
cv.sort();
let mut base = *cv[0].0;
ccv.push(cv[0]);
for item in &cv[1..] {
let cd = color_distance(*item.0, base);
if cd > 1.0 {
ccv.push(*item);
}
base = *item.0;
}
let l1 = luminance(*ccv[0].0);
let l2 = luminance(*ccv[1].0);
if l2 > l1 {
ret = Some((*ccv[0].0, *ccv[1].0));
} else {
ret = Some((*ccv[1].0, *ccv[0].0));
}
}
for i in 0..self.height as usize {
for j in 0..self.width as usize {
let color = cm[i * self.width as usize + j];
let cd0 = color_distance(color, ret.unwrap().0);
let cd1 = color_distance(color, ret.unwrap().1);
if cd0 <= cd1 {
self.binary_data[i][j] = 0;
} else {
self.binary_data[i][j] = 1;
}
}
}
match ret {
Some(r) => {
self.back_color = find_best_color_u32(r.0) as u32;
self.fore_color = find_best_color_u32(r.1) as u32;
}
_ => {
self.back_color = 0;
self.fore_color = 0;
}
}
}
}
impl Default for BinarizationConfig {
fn default() -> Self {
Self {
min_contrast_ratio: 0.1, }
}
}
pub fn binarize_block(pixels: &[Vec<RGB>], config: &BinarizationConfig) -> BinarizedBlock {
let height = pixels.len();
if height == 0 {
panic!("Invalid block size: height must be non-zero");
}
let width = pixels[0].len();
if width == 0 {
panic!("Invalid block size: width must be non-zero");
}
let mut brightnesses = Vec::with_capacity(height * width);
for row in pixels.iter().take(height) {
for px in row.iter().take(width) {
let brightness =
(0.299 * px.r as f32 + 0.587 * px.g as f32 + 0.114 * px.b as f32) as u8;
brightnesses.push(brightness);
}
}
let min_brightness = *brightnesses.iter().min().unwrap();
let max_brightness = *brightnesses.iter().max().unwrap();
let avg_brightness = brightnesses.iter().map(|&b| b as f32).sum::<f32>() / (height * width) as f32;
let threshold = {
let contrast = max_brightness - min_brightness;
let min_contrast = (config.min_contrast_ratio * 255.0) as u8;
if contrast < min_contrast {
if avg_brightness > 128.0 {
0 } else {
255 }
} else {
find_optimal_threshold(&brightnesses).unwrap_or(avg_brightness as u8)
}
};
let mut bitmap = vec![vec![0u8; width]; height];
let mut foreground_pixels = Vec::new();
let mut background_pixels = Vec::new();
for y in 0..height {
for x in 0..width {
let px = &pixels[y][x];
let brightness = brightnesses[y * width + x];
if brightness > threshold {
bitmap[y][x] = 1;
foreground_pixels.push(*px);
} else {
bitmap[y][x] = 0;
background_pixels.push(*px);
}
}
}
let foreground_color = if foreground_pixels.is_empty() {
RGB { r: 255, g: 255, b: 255 }
} else {
let r = foreground_pixels.iter().map(|p| p.r as u32).sum::<u32>()
/ foreground_pixels.len() as u32;
let g = foreground_pixels.iter().map(|p| p.g as u32).sum::<u32>()
/ foreground_pixels.len() as u32;
let b = foreground_pixels.iter().map(|p| p.b as u32).sum::<u32>()
/ foreground_pixels.len() as u32;
RGB { r: r as u8, g: g as u8, b: b as u8 }
};
let background_color = if background_pixels.is_empty() {
RGB { r: 0, g: 0, b: 0 }
} else {
let r = background_pixels.iter().map(|p| p.r as u32).sum::<u32>()
/ background_pixels.len() as u32;
let g = background_pixels.iter().map(|p| p.g as u32).sum::<u32>()
/ background_pixels.len() as u32;
let b = background_pixels.iter().map(|p| p.b as u32).sum::<u32>()
/ background_pixels.len() as u32;
RGB { r: r as u8, g: g as u8, b: b as u8 }
};
BinarizedBlock {
bitmap,
width,
height,
foreground_color,
background_color,
threshold,
}
}
pub fn find_optimal_threshold(brightnesses: &[u8]) -> Option<u8> {
if brightnesses.len() < 2 {
return None;
}
let mut histogram = [0u32; 256];
for &brightness in brightnesses {
histogram[brightness as usize] += 1;
}
let total = brightnesses.len() as f32;
let mut best_threshold = 128u8;
let mut best_variance = 0.0f32;
for threshold in 1..=254 {
let mut w0 = 0.0f32; let mut w1 = 0.0f32; let mut sum0 = 0.0f32; let mut sum1 = 0.0f32;
for i in 0..threshold {
let count = histogram[i as usize] as f32;
w0 += count;
sum0 += i as f32 * count;
}
for i in threshold..=255 {
let count = histogram[i as usize] as f32;
w1 += count;
sum1 += i as f32 * count;
}
if w0 == 0.0 || w1 == 0.0 {
continue;
}
let mean0 = sum0 / w0; let mean1 = sum1 / w1;
let between_class_variance = (w0 / total) * (w1 / total) * (mean0 - mean1).powi(2);
if between_class_variance > best_variance {
best_variance = between_class_variance;
best_threshold = threshold;
}
}
Some(best_threshold)
}
#[cfg(not(wasm))]
pub fn extract_image_block(img: &DynamicImage, x: u32, y: u32, block_size: u32) -> Vec<Vec<RGB>> {
extract_image_block_rect(img, x, y, block_size, block_size)
}
#[cfg(not(wasm))]
pub fn extract_image_block_rect(img: &DynamicImage, x: u32, y: u32, block_width: u32, block_height: u32) -> Vec<Vec<RGB>> {
let mut block = vec![vec![RGB { r: 0, g: 0, b: 0 }; block_width as usize]; block_height as usize];
for (dy, row) in block.iter_mut().enumerate().take(block_height as usize) {
for (dx, cell) in row.iter_mut().enumerate().take(block_width as usize) {
let pixel_x = x * block_width + dx as u32;
let pixel_y = y * block_height + dy as u32;
if pixel_x < img.width() && pixel_y < img.height() {
let pixel = img.get_pixel(pixel_x, pixel_y);
*cell = RGB {
r: pixel[0],
g: pixel[1],
b: pixel[2],
};
}
}
}
block
}
#[cfg(not(wasm))]
impl From<Rgb<u8>> for RGB {
fn from(rgb: Rgb<u8>) -> Self {
RGB {
r: rgb[0],
g: rgb[1],
b: rgb[2],
}
}
}
#[cfg(not(wasm))]
impl From<RGB> for Rgb<u8> {
fn from(rgb: RGB) -> Self {
Rgb([rgb.r, rgb.g, rgb.b])
}
}
pub type BlockGrayImage = Vec<Vec<u8>>;
#[cfg(not(wasm))]
#[allow(clippy::needless_range_loop)]
pub fn gen_charset_images(
low_up: bool,
block_width: usize,
block_height: usize,
c64low_data: &[[u8; 8]; 128],
c64up_data: &[[u8; 8]; 128],
) -> Vec<BlockGrayImage> {
let data = if low_up { c64low_data } else { c64up_data };
let mut vcs = vec![vec![vec![0u8; block_width]; block_height]; 256];
let scale_x = block_width as f32 / 8.0;
let scale_y = block_height as f32 / 8.0;
for i in 0..128 {
for y in 0..block_height {
for x in 0..block_width {
let orig_x = (x as f32 / scale_x) as usize;
let orig_y = (y as f32 / scale_y) as usize;
let orig_x = orig_x.min(7);
let orig_y = orig_y.min(7);
let bit = 7 - orig_x; if data[i][orig_y] >> bit & 1 == 1 {
vcs[i][y][x] = 255;
vcs[128 + i][y][x] = 0;
} else {
vcs[i][y][x] = 0;
vcs[128 + i][y][x] = 255;
}
}
}
}
vcs
}
#[cfg(not(wasm))]
pub fn get_grayscale_block_at(
image: &ImageBuffer<Luma<u8>, Vec<u8>>,
x: u32,
y: u32,
block_width: u32,
block_height: u32
) -> BlockGrayImage {
let mut block = vec![vec![0u8; block_width as usize]; block_height as usize];
for (i, row) in block.iter_mut().enumerate().take(block_height as usize) {
for (j, cell) in row.iter_mut().enumerate().take(block_width as usize) {
let pixel_x = x * block_width + j as u32;
let pixel_y = y * block_height + i as u32;
if pixel_x < image.width() && pixel_y < image.height() {
*cell = image.get_pixel(pixel_x, pixel_y).0[0];
}
}
}
block
}
pub fn binarize_grayscale_block(
img: &BlockGrayImage,
back: u8,
block_width: usize,
block_height: usize
) -> BlockGrayImage {
let mut binary_block = vec![vec![0u8; block_width]; block_height];
let mut min = u8::MAX;
let mut max = 0u8;
let mut include_back = false;
for row in img.iter().take(block_height) {
for &p in row.iter().take(block_width) {
if !include_back && p == back {
include_back = true;
}
if p > max {
max = p;
}
if p < min {
min = p;
}
}
}
for y in 0..block_height {
for x in 0..block_width {
let iyx = img[y][x];
let binary_value = if include_back {
if iyx == back {
0
} else {
255
}
} else {
let threshold = (min + max) / 2;
if iyx > threshold {
255
} else {
0
}
};
binary_block[y][x] = binary_value;
}
}
binary_block
}
pub fn calc_eigenvector(img: &BlockGrayImage, block_width: usize, block_height: usize) -> Vec<i32> {
let mut v = vec![0i32; 10];
let half_width = block_width / 2;
let half_height = block_height / 2;
let quarter_width = block_width / 4;
let quarter_height = block_height / 4;
let center_start_x = quarter_width;
let center_end_x = block_width - quarter_width;
let center_start_y = quarter_height;
let center_end_y = block_height - quarter_height;
let max_x = block_width - 1;
let max_y = block_height - 1;
for (y, row) in img.iter().enumerate().take(block_height) {
for (x, &pixel) in row.iter().enumerate().take(block_width) {
let p = pixel as i32;
if x < half_width && y < half_height {
v[0] += p; }
if x >= half_width && y < half_height {
v[1] += p; }
if x < half_width && y >= half_height {
v[2] += p; }
if x >= half_width && y >= half_height {
v[3] += p; }
if x >= center_start_x && x < center_end_x && y >= center_start_y && y < center_end_y {
v[4] += p;
}
let diag_main = (x as f32 / block_width as f32 * block_height as f32) as usize;
let diag_anti = (((block_width - 1 - x) as f32 / block_width as f32) * block_height as f32) as usize;
if y == diag_main || y == diag_anti {
v[5] += p;
}
if x == 0 {
v[6] += p; }
if x == max_x {
v[7] += p; }
if y == 0 {
v[8] += p; }
if y == max_y {
v[9] += p; }
}
}
v
}
pub fn calculate_mse(
img1: &BlockGrayImage,
img2: &BlockGrayImage,
block_width: usize,
block_height: usize
) -> f64 {
let mut mse = 0.0f64;
let v1 = calc_eigenvector(img1, block_width, block_height);
let v2 = calc_eigenvector(img2, block_width, block_height);
for i in 0..10usize {
mse += ((v1[i] - v2[i]) * (v1[i] - v2[i])) as f64;
}
mse.sqrt()
}
pub fn find_best_match(
input_image: &BlockGrayImage,
char_images: &[BlockGrayImage],
block_width: usize,
block_height: usize
) -> usize {
let mut min_mse = f64::MAX;
let mut best_match = 0;
for (i, char_image) in char_images.iter().enumerate() {
let mse = calculate_mse(input_image, char_image, block_width, block_height);
if mse < min_mse {
min_mse = mse;
best_match = i;
}
}
best_match
}
#[cfg(not(wasm))]
pub fn get_petii_block_color(
image: &DynamicImage,
_img: &ImageBuffer<Luma<u8>, Vec<u8>>,
x: u32,
y: u32,
back_rgb: u32,
block_width: u32,
block_height: u32,
) -> (usize, usize) {
let mut cc: HashMap<u32, (u32, u32)> = HashMap::new();
for i in 0..block_height as usize {
for j in 0..block_width as usize {
let pixel_x = x * block_width + j as u32;
let pixel_y = y * block_height + i as u32;
if pixel_x < image.width() && pixel_y < image.height() {
let p = image.get_pixel(pixel_x, pixel_y);
let k: u32 = ((p[0] as u32) << 24)
+ ((p[1] as u32) << 16)
+ ((p[2] as u32) << 8)
+ (p[3] as u32);
cc.entry(k).or_insert((pixel_x, pixel_y));
}
}
}
let mut cv: Vec<_> = cc.iter().collect();
let mut include_back = false;
let clen = cv.len();
for c in &mut cv {
if *c.0 == back_rgb {
include_back = true;
} else {
let cd = color_distance(*c.0, back_rgb);
if cd < 1.0 {
c.0 = &back_rgb;
include_back = true;
}
}
}
let ret;
if include_back {
if clen == 1 {
ret = Some((back_rgb, back_rgb));
} else if clen == 2 {
let mut r = (back_rgb, back_rgb);
if *cv[0].0 != back_rgb {
r.1 = *cv[0].0;
}
if *cv[1].0 != back_rgb {
r.1 = *cv[1].0;
}
ret = Some(r);
} else {
let mut bigd = 0.0f32;
let mut bcv = cv[0];
for c in &cv {
let cd = color_distance(*c.0, back_rgb);
if cd > bigd {
bigd = cd;
bcv = *c;
}
}
ret = Some((back_rgb, *bcv.0));
}
} else if clen == 1 {
ret = Some((*cv[0].0, *cv[0].0));
} else if clen == 2 {
let l1 = luminance(*cv[0].0);
let l2 = luminance(*cv[1].0);
if l2 > l1 {
ret = Some((*cv[0].0, *cv[1].0));
} else {
ret = Some((*cv[1].0, *cv[0].0));
}
} else {
let mut ccv = vec![];
cv.sort();
let mut base = *cv[0].0;
ccv.push(cv[0]);
for item in &cv[1..] {
let cd = color_distance(*item.0, base);
if cd > 1.0 {
ccv.push(*item);
}
base = *item.0;
}
if ccv.len() >= 2 {
let l1 = luminance(*ccv[0].0);
let l2 = luminance(*ccv[1].0);
if l2 > l1 {
ret = Some((*ccv[0].0, *ccv[1].0));
} else {
ret = Some((*ccv[1].0, *ccv[0].0));
}
} else {
println!("ERROR2!!!");
ret = Some((0, 0));
}
}
match ret {
Some(r) => (find_best_color_u32(r.0), find_best_color_u32(r.1)),
_ => (0, 0),
}
}
#[cfg(not(wasm))]
pub fn get_block_color(
image: &DynamicImage,
x: u32,
y: u32,
block_width: u32,
block_height: u32
) -> RGB {
let mut r = 0u32;
let mut g = 0u32;
let mut b = 0u32;
let mut count = 0u32;
for i in 0..block_height as usize {
for j in 0..block_width as usize {
let pixel_x = x * block_width + j as u32;
let pixel_y = y * block_height + i as u32;
if pixel_x < image.width() && pixel_y < image.height() {
let p = image.get_pixel(pixel_x, pixel_y);
if p[0] != 0 || p[1] != 0 || p[2] != 0 {
r += p[0] as u32;
g += p[1] as u32;
b += p[2] as u32;
count += 1;
}
}
}
}
if count == 0 {
return RGB { r: 0, g: 0, b: 0 };
}
RGB {
r: (r / count) as u8,
g: (g / count) as u8,
b: (b / count) as u8,
}
}