#[cfg(feature = "image")]
use crate::dithering::Ditherer;
pub const BRAILLE_CHARS: [char; 256] = [
'⠀', '⠁', '⠂', '⠃', '⠄', '⠅', '⠆', '⠇',
'⠈', '⠉', '⠊', '⠋', '⠌', '⠍', '⠎', '⠏',
'⠐', '⠑', '⠒', '⠓', '⠔', '⠕', '⠖', '⠗',
'⠘', '⠙', '⠚', '⠛', '⠜', '⠝', '⠞', '⠟',
'⠠', '⠡', '⠢', '⠣', '⠤', '⠥', '⠦', '⠧',
'⠨', '⠩', '⠪', '⠫', '⠬', '⠭', '⠮', '⠯',
'⠰', '⠱', '⠲', '⠳', '⠴', '⠵', '⠶', '⠷',
'⠸', '⠹', '⠺', '⠻', '⠼', '⠽', '⠾', '⠿',
'⡀', '⡁', '⡂', '⡃', '⡄', '⡅', '⡆', '⡇',
'⡈', '⡉', '⡊', '⡋', '⡌', '⡍', '⡎', '⡏',
'⡐', '⡑', '⡒', '⡓', '⡔', '⡕', '⡖', '⡗',
'⡘', '⡙', '⡚', '⡛', '⡜', '⡝', '⡞', '⡟',
'⡠', '⡡', '⡢', '⡣', '⡤', '⡥', '⡦', '⡧',
'⡨', '⡩', '⡪', '⡫', '⡬', '⡭', '⡮', '⡯',
'⡰', '⡱', '⡲', '⡳', '⡴', '⡵', '⡶', '⡷',
'⡸', '⡹', '⡺', '⡻', '⡼', '⡽', '⡾', '⡿',
'⢀', '⢁', '⢂', '⢃', '⢄', '⢅', '⢆', '⢇',
'⢈', '⢉', '⢊', '⢋', '⢌', '⢍', '⢎', '⢏',
'⢐', '⢑', '⢒', '⢓', '⢔', '⢕', '⢖', '⢗',
'⢘', '⢙', '⢚', '⢛', '⢜', '⢝', '⢞', '⢟',
'⢠', '⢡', '⢢', '⢣', '⢤', '⢥', '⢦', '⢧',
'⢨', '⢩', '⢪', '⢫', '⢬', '⢭', '⢮', '⢯',
'⢰', '⢱', '⢲', '⢳', '⢴', '⢵', '⢶', '⢷',
'⢸', '⢹', '⢺', '⢻', '⢼', '⢽', '⢾', '⢿',
'⣀', '⣁', '⣂', '⣃', '⣄', '⣅', '⣆', '⣇',
'⣈', '⣉', '⣊', '⣋', '⣌', '⣍', '⣎', '⣏',
'⣐', '⣑', '⣒', '⣓', '⣔', '⣕', '⣖', '⣗',
'⣘', '⣙', '⣚', '⣛', '⣜', '⣝', '⣞', '⣟',
'⣠', '⣡', '⣢', '⣣', '⣤', '⣥', '⣦', '⣧',
'⣨', '⣩', '⣪', '⣫', '⣬', '⣭', '⣮', '⣯',
'⣰', '⣱', '⣲', '⣳', '⣴', '⣵', '⣶', '⣷',
'⣸', '⣹', '⣺', '⣻', '⣼', '⣽', '⣾', '⣿'
];
const BRAILLE_LEN: usize = BRAILLE_CHARS[0].len_utf8();
#[derive(Debug, Clone, Copy)]
pub enum Error {
OutOfBounds(u32, u32, u32, u32),
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::OutOfBounds(x, y, w, h) => {
write!(f, "the coordinates (x: {x}, y: {y}) were outside the bounds of the BrailleImg (width: {w}, height: {h})")
},
}
}
}
pub struct BrailleImg {
braille_vals: Vec<u8>,
dot_width: u32,
dot_height: u32,
char_width: u32,
char_height: u32,
}
impl BrailleImg {
pub fn new(width: u32, height: u32) -> Self {
assert!(width != 0 && height != 0, "width and height must be greater than 0");
let x_size = width / 2 + (width % 2);
let extra_row = if height % 4 != 0 {
1
} else {
0
};
let y_size = height / 4 + extra_row;
let vals = vec![0; (x_size * y_size) as usize];
BrailleImg {
braille_vals: vals,
dot_width: width,
dot_height: height,
char_width: x_size,
char_height: y_size,
}
}
fn get_bit_mask(x: u32, y: u32) -> u8 {
if x % 2 == 0 {
match y % 4 {
0 => 0b00000001,
1 => 0b00000010,
2 => 0b00000100,
_ => 0b01000000
}
} else {
match y % 4 {
0 => 0b00001000,
1 => 0b00010000,
2 => 0b00100000,
_ => 0b10000000
}
}
}
pub fn set_dot(&mut self, x: u32, y: u32, raised: bool) -> Result<(), Error> {
if x > (self.dot_width - 1) || y > (self.dot_height - 1) {
return Err(Error::OutOfBounds(x, y, self.char_width, self.char_height))
}
let x_val_pos = x / 2;
let y_val_pos = y / 4;
let val = self.braille_vals.get_mut((x_val_pos + y_val_pos * self.char_width) as usize).unwrap();
let mask = BrailleImg::get_bit_mask(x, y);
if raised {
*val |= mask;
} else {
*val &= !mask;
}
Ok(())
}
pub fn get_dot(&self, x: u32, y: u32) -> Option<bool> {
if x > (self.dot_width - 1) || y > (self.dot_height - 1) {
return None;
}
let x_val_pos = x / 2;
let y_val_pos = y / 4;
let val = self.braille_vals.get((x_val_pos + y_val_pos * self.char_width) as usize).unwrap();
let mask = BrailleImg::get_bit_mask(x, y);
Some(*val & mask != 0)
}
#[deprecated = "you should use BrailleImg::as_str() instead"]
pub fn to_str(self, no_empty_chars: bool, break_line: bool) -> String {
let mut braille_string = String::new();
for (i, val) in self.braille_vals.into_iter().enumerate() {
if i % self.char_width as usize == 0 && i != 0 {
braille_string.push(if break_line { '\n' } else { ' ' });
}
if val == 0 && no_empty_chars {
braille_string.push(BRAILLE_CHARS[1 << 2])
} else {
braille_string.push(BRAILLE_CHARS[val as usize])
}
}
braille_string
}
pub fn as_str(&self, no_empty_chars: bool, break_line: bool) -> String {
let mut braille_string = String::with_capacity(self.str_len());
for (i, val) in self.braille_vals.iter().enumerate() {
if i % self.char_width as usize == 0 && i != 0 {
braille_string.push(if break_line { '\n' } else { ' ' });
}
if *val == 0 && no_empty_chars {
braille_string.push(BRAILLE_CHARS[1 << 2])
} else {
braille_string.push(BRAILLE_CHARS[*val as usize])
}
}
braille_string
}
fn str_len(&self) -> usize {
((self.char_width * self.char_height) as usize * BRAILLE_LEN) + (self.char_height - 1) as usize
}
#[cfg(feature = "image")]
pub fn from_image(img: impl image::GenericImageView<Pixel=image::Rgba<u8>>, ditherer: impl Ditherer, invert: bool) -> Self {
let mut gray_img = image::GrayImage::new(img.width(), img.height());
let compute_lightness = |rgba: &[f32; 4]| -> u8 {
((rgba[0] * 0.2126 + rgba[1] * 0.7152 + rgba[2] * 0.0722) * (rgba[3] / 255.0))
.clamp(0.0, 255.0)
.round() as u8
};
for (x, y, pix) in img.pixels() {
let lightness = compute_lightness(
&[
pix.0[0] as f32,
pix.0[1] as f32,
pix.0[2] as f32,
pix.0[3] as f32
]
);
gray_img.put_pixel(x, y, image::Luma::<u8>([lightness]));
}
ditherer.dither(&mut gray_img);
let mut braille_img = BrailleImg::new(gray_img.width(), gray_img.height());
#[allow(unused_must_use)]
for (x, y, pix) in gray_img.enumerate_pixels() {
if invert {
if pix.0[0] > 96 {
braille_img.set_dot(x, y, true);
}
} else if pix.0[0] < 96 {
braille_img.set_dot(x, y, true);
}
}
braille_img
}
}
#[cfg(test)]
mod tests {
use crate::braille::BrailleImg;
#[test]
fn str_len() {
let img = BrailleImg::new(63, 21);
let string_form = img.as_str(true, true);
assert_eq!(string_form.len(), string_form.capacity())
}
#[test]
fn bounds_check() {
let mut img = BrailleImg::new(32, 32);
assert!(img.set_dot(0, 0, true).is_ok());
assert!(img.set_dot(1, 1, true).is_ok());
assert!(img.set_dot(31, 31, true).is_ok());
assert!(img.set_dot(32, 31, true).is_err());
assert!(img.set_dot(31, 32, true).is_err());
assert!(img.get_dot(0, 0).is_some());
assert!(img.get_dot(1, 1).is_some());
assert!(img.get_dot(31, 31).is_some());
assert!(img.get_dot(32, 31).is_none());
assert!(img.get_dot(31, 32).is_none());
}
#[test]
fn get_dot() {
let mut img = BrailleImg::new(4, 4);
assert_eq!(img.get_dot(0, 0), Some(false));
img.set_dot(0, 0, true).unwrap();
assert_eq!(img.get_dot(0, 0), Some(true));
}
#[test]
#[should_panic]
fn new_null_width() {
let _img = BrailleImg::new(0, 1);
}
#[test]
#[should_panic]
fn new_null_height() {
let _img = BrailleImg::new(1, 0);
}
#[test]
#[should_panic]
fn new_null_both() {
let _img = BrailleImg::new(0, 0);
}
}