#[cfg(feature = "rayon")]
use rayon::prelude::*;
use crate::threshold_utils::{ThresholdPixel, DEFAULT_THRESHOLD};
use crate::{product, AsciiArt, AsciiArtPixel, SizeError};
pub const DEFAULT_BRAILLE_FONT_RATIO: f64 = 21.0 / 24.0;
pub fn boolean_array_to_braille(array: &[bool; 8]) -> char {
let mut codepoint: u32 = 0x2800;
for (i, &value) in array.iter().enumerate() {
if value {
codepoint |= 1 << i;
}
}
std::char::from_u32(codepoint).unwrap_or(' ')
}
pub trait BrailleArtConverter {
fn braille_art(&self, colored: bool) -> Result<AsciiArt, SizeError>;
}
impl BrailleArtConverter for image::DynamicImage {
fn braille_art(&self, colored: bool) -> Result<AsciiArt, SizeError> {
self.to_rgba8().braille_art(colored)
}
}
impl BrailleArtConverter for image::RgbImage {
fn braille_art(&self, colored: bool) -> Result<AsciiArt, SizeError> {
let width = self.width();
let height = self.height();
if width < 4 || height < 8 {
return Err(SizeError);
}
let x_range: Vec<u32> = (0..(width - width % 2)).step_by(2).collect();
let y_range: Vec<u32> = (0..(height - height % 4)).step_by(4).collect();
let width = x_range.clone().len() as u32;
let height = y_range.clone().len() as u32;
let range: Vec<(u32, u32)> = product![y_range, x_range].map(|(y, x)| (*y, *x)).collect();
#[cfg(feature = "rayon")]
let iter = range.into_par_iter();
#[cfg(not(feature = "rayon"))]
let iter = range.into_iter();
let characters = iter
.map(|(y, x)| {
let tlpx = self.get_pixel(x, y);
let braille_array = &[
tlpx,
self.get_pixel(x, y + 1),
self.get_pixel(x, y + 2),
self.get_pixel(x + 1, y),
self.get_pixel(x, y + 1),
self.get_pixel(x, y + 2),
self.get_pixel(x, y + 3),
self.get_pixel(x + 1, y + 3),
]
.map(|p| p.threshold_pixel(DEFAULT_THRESHOLD));
AsciiArtPixel {
character: boolean_array_to_braille(braille_array),
r: tlpx.0[0],
g: tlpx.0[1],
b: tlpx.0[2],
a: 255,
}
})
.collect();
Ok(AsciiArt::new(characters, width, height, colored))
}
}
impl BrailleArtConverter for image::RgbaImage {
fn braille_art(&self, colored: bool) -> Result<AsciiArt, SizeError> {
let width = self.width();
let height = self.height();
if width < 4 || height < 8 {
return Err(SizeError);
}
let x_range: Vec<u32> = (0..(width - width % 2)).step_by(2).collect();
let y_range: Vec<u32> = (0..(height - height % 4)).step_by(4).collect();
let width = x_range.clone().len() as u32;
let height = y_range.clone().len() as u32;
let range: Vec<(u32, u32)> = product![y_range, x_range].map(|(y, x)| (*y, *x)).collect();
#[cfg(feature = "rayon")]
let iter = range.into_par_iter();
#[cfg(not(feature = "rayon"))]
let iter = range.into_iter();
let characters = iter
.map(|(y, x)| {
let tlpx = self.get_pixel(x, y);
let braille_array = &[
tlpx,
self.get_pixel(x, y + 1),
self.get_pixel(x, y + 2),
self.get_pixel(x + 1, y),
self.get_pixel(x, y + 1),
self.get_pixel(x, y + 2),
self.get_pixel(x, y + 3),
self.get_pixel(x + 1, y + 3),
]
.map(|p| p.threshold_pixel(DEFAULT_THRESHOLD));
AsciiArtPixel {
character: boolean_array_to_braille(braille_array),
r: tlpx.0[0],
g: tlpx.0[1],
b: tlpx.0[2],
a: tlpx.0[3],
}
})
.collect();
Ok(AsciiArt::new(characters, width, height, colored))
}
}
impl BrailleArtConverter for image::GrayImage {
fn braille_art(&self, colored: bool) -> Result<AsciiArt, SizeError> {
let width = self.width();
let height = self.height();
if width < 4 || height < 8 {
return Err(SizeError);
}
let x_range: Vec<u32> = (0..(width - width % 2)).step_by(2).collect();
let y_range: Vec<u32> = (0..(height - height % 4)).step_by(4).collect();
let width = x_range.clone().len() as u32;
let height = y_range.clone().len() as u32;
let range: Vec<(u32, u32)> = product![y_range, x_range].map(|(y, x)| (*y, *x)).collect();
#[cfg(feature = "rayon")]
let iter = range.into_par_iter();
#[cfg(not(feature = "rayon"))]
let iter = range.into_iter();
let characters = iter
.map(|(y, x)| {
let tlpx = self.get_pixel(x, y);
let braille_array = &[
tlpx,
self.get_pixel(x, y + 1),
self.get_pixel(x, y + 2),
self.get_pixel(x + 1, y),
self.get_pixel(x, y + 1),
self.get_pixel(x, y + 2),
self.get_pixel(x, y + 3),
self.get_pixel(x + 1, y + 3),
]
.map(|p| p.threshold_pixel(DEFAULT_THRESHOLD));
AsciiArtPixel {
character: boolean_array_to_braille(braille_array),
r: tlpx.0[0],
g: tlpx.0[0],
b: tlpx.0[0],
a: 255,
}
})
.collect();
Ok(AsciiArt::new(characters, width, height, colored))
}
}
impl BrailleArtConverter for image::GrayAlphaImage {
fn braille_art(&self, colored: bool) -> Result<AsciiArt, SizeError> {
let width = self.width();
let height = self.height();
if width < 4 || height < 8 {
return Err(SizeError);
}
let x_range: Vec<u32> = (0..(width - width % 2)).step_by(2).collect();
let y_range: Vec<u32> = (0..(height - height % 4)).step_by(4).collect();
let width = x_range.clone().len() as u32;
let height = y_range.clone().len() as u32;
let range: Vec<(u32, u32)> = product![y_range, x_range].map(|(y, x)| (*y, *x)).collect();
#[cfg(feature = "rayon")]
let iter = range.into_par_iter();
#[cfg(not(feature = "rayon"))]
let iter = range.into_iter();
let characters = iter
.map(|(y, x)| {
let tlpx = self.get_pixel(x, y);
let braille_array = &[
tlpx,
self.get_pixel(x, y + 1),
self.get_pixel(x, y + 2),
self.get_pixel(x + 1, y),
self.get_pixel(x, y + 1),
self.get_pixel(x, y + 2),
self.get_pixel(x, y + 3),
self.get_pixel(x + 1, y + 3),
]
.map(|p| p.threshold_pixel(DEFAULT_THRESHOLD));
AsciiArtPixel {
character: boolean_array_to_braille(braille_array),
r: tlpx.0[0],
g: tlpx.0[0],
b: tlpx.0[0],
a: tlpx.0[1],
}
})
.collect();
Ok(AsciiArt::new(characters, width, height, colored))
}
}