#![doc = include_str!("../README.md")]
use image::{DynamicImage, GenericImageView, SubImage};
use std::{
collections::HashMap,
fmt::{Debug, Display},
};
pub type BrushMap = HashMap<u128, char>;
#[derive(Default)]
pub struct Insa {
background: Option<(u8, u8, u8)>,
invert: bool,
brushes: BrushMap,
}
impl Debug for Insa {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"Insa {{ background: {:?}, invert: {}, brushes:[\n",
self.background, self.invert
))?;
for (i, c) in self.brushes() {
f.write_fmt(format_args!(" (0b{i:0128b},'{c}'),\n"))?;
}
f.write_str("] }\n")
}
}
impl Insa {
pub fn plain() -> Insa {
Self {
background: None,
invert: false,
brushes: [(0, ' ')].into(),
}
}
pub fn simple_on_dark() -> Insa {
Insa { background: Some((50,50,50)), invert: false, brushes: [
(0b00000000000000000000000000000000001111000100001001000010010000100011110001000010010000100100001001000010001111000000000000000000,'8'),
(0b00000000000000000000000000000000000000000000000000111100010000100100001001000010010000100100001001000010001111000000000000000000,'o'),
(0b00000000000000000000000000000000000110000010010000100100000110000000000000000000000000000000000000000000000000000000000000000000,'°'),
(0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011000000110000000000000000000,'.'),
(0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,' '),
].into() }
}
pub fn simple_on_bright() -> Insa {
let mut me = Self::simple_on_dark();
me.invert = true;
me.background = Some((200, 200, 200));
me
}
pub fn blocks() -> Insa {
Insa { background: None, invert:false, brushes: [
(0b00000000000000000000000000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,'▆'),
(0b11110000111100001111000011110000111100001111000011110000111100000000111100001111000011110000111100001111000011110000111100001111,'▚'),
(0b00000000000000000000000000000000000000000000000000000000000000001111000011110000111100001111000011110000111100001111000011110000,'▖'),
(0b11111111111111101111111011111100111111001111100011111000111100001111000011100000111000001100000011000000100000001000000010000000,'◤'),
(0b11111111111111111111111111111111111111111111111111111111111111110000111100001111000011110000111100001111000011110000111100001111,'▜'),
(0b11110000111100001111000011110000111100001111000011110000111100001111111111111111111111111111111111111111111111111111111111111111,'▙'),
(0b11110000111100001111000011110000111100001111000011110000111100001111000011110000111100001111000011110000111100001111000011110000,'▌'),
(0b11000000110000001100000011000000110000001100000011000000110000001100000011000000110000001100000011000000110000001100000011000000,'▎'),
(0b11111100111111001111110011111100111111001111110011111100111111001111110011111100111111001111110011111100111111001111110011111100,'▊'),
(0b00000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,'▇'),
(0b00000000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111111111111111111111111111111111111,'▅'),
(0b11111000111110001111100011111000111110001111100011111000111110001111100011111000111110001111100011111000111110001111100011111000,'▋'),
(0b11111111111111111111111111111111111111111111111111111111111111111111000011110000111100001111000011110000111100001111000011110000,'▛'),
(0b00000000000000000000000000000000000000000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111,'▃'),
(0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,' '),
(0b00001111000011110000111100001111000011110000111100001111000011110000000000000000000000000000000000000000000000000000000000000000,'▝'),
(0b11111110111111101111111011111110111111101111111011111110111111101111111011111110111111101111111011111110111111101111111011111110,'▉'),
(0b00001111000011110000111100001111000011110000111100001111000011110000111100001111000011110000111100001111000011110000111100001111,'▐'),
(0b00000000000000000000000000000000000000000000000000000000000000000000111100001111000011110000111100001111000011110000111100001111,'▗'),
(0b00001111000011110000111100001111000011110000111100001111000011111111000011110000111100001111000011110000111100001111000011110000,'▞'),
(0b00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111,'▄'),
(0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111111111,'▁'),
(0b11111111011111110111111100111111001111110001111100011111000011110000111100000111000001110000001100000011000000010000000100000001,'◥'),
(0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011111111111111111111111111111111,'▂'),
(0b11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,'█'),
(0b11111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,'▔'),
(0b00001111000011110000111100001111000011110000111100001111000011111111111111111111111111111111111111111111111111111111111111111111,'▟'),
(0b11100000111000001110000011100000111000001110000011100000111000001110000011100000111000001110000011100000111000001110000011100000,'▍'),
(0b10000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000,'▏'),
(0b00000001000000010000000100000011000000110000011100000111000011110000111100011111000111110011111100111111011111110111111111111111,'◢'),
(0b10000000100000001000000011000000110000001110000011100000111100001111000011111000111110001111110011111100111111101111111011111111,'◣'),
(0b11111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000,'▀'),
(0b11110000111100001111000011110000111100001111000011110000111100000000000000000000000000000000000000000000000000000000000000000000,'▘'),
(0b00000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001,'▕'),
].into() }
}
#[cfg(feature = "fontdue")]
pub fn load_brushes(&mut self, font: &fontdue::Font, brushes: impl AsRef<str>) -> &mut Self {
let brushes = brushes
.as_ref()
.chars()
.map(|c| (rasterize_u128(font, c), c));
self.brushes.extend(brushes);
self
}
pub fn brushes(&self) -> &BrushMap {
&self.brushes
}
pub fn brushes_mut(&mut self) -> &mut BrushMap {
&mut self.brushes
}
pub fn invert(&self) -> bool {
self.invert
}
pub fn invert_mut(&mut self) -> &mut bool {
&mut self.invert
}
pub fn set_invert(&mut self, invert: bool) -> &mut Self {
self.invert = invert;
self
}
pub fn fixed_background(&self) -> Option<(u8, u8, u8)> {
self.background
}
pub fn fixed_background_mut(&mut self) -> Option<&mut (u8, u8, u8)> {
self.background.as_mut()
}
pub fn set_fixed_background(&mut self, invert: Option<(u8, u8, u8)>) -> &mut Self {
self.background = invert;
self
}
pub fn brush_width(&self) -> u8 {
8
}
pub fn brush_height(&self) -> u8 {
16
}
pub fn convert<'a>(&'a self, img: &'a DynamicImage) -> ConvertIterator<'a> {
ConvertIterator::new(img, &self)
}
pub fn convert_symbol(&self, img: &SubImage<&DynamicImage>) -> Symbol {
let (bitmap, mut front_color, mut back_color) = self.bitmap(img);
let (mut brush, score) = self.get_brush(bitmap);
if self.background.is_none() && score != u8::MAX {
let (ibrush, iscore) = self.get_brush(!bitmap);
if iscore > score {
brush = ibrush;
std::mem::swap(&mut front_color, &mut back_color);
}
}
let back_color = match self.background {
Some(_) => None,
None => Some(back_color),
};
let brush = *brush;
Symbol {
brush,
front_color,
back_color,
}
}
fn get_brush(&self, bitmap: u128) -> (&char, u8) {
if let Some(brush) = self.brushes.get(&bitmap) {
return (brush, u8::MAX);
}
let mut my_brush = &' ';
let mut my_score = 0;
for (candidate, brush) in &self.brushes {
let xor = candidate ^ bitmap;
let new_score = 128 - xor.count_ones();
if new_score >= my_score {
my_score = new_score;
my_brush = brush;
if my_score >= 120 {
break;
}
}
}
(my_brush, my_score as u8)
}
fn bitmap(&self, img: &SubImage<&DynamicImage>) -> (u128, (u8, u8, u8), (u8, u8, u8)) {
let ((tr, tg, tb), pixs) = (0..128).map(|idx| self.bit(idx, img)).fold(
((0, 0, 0), vec![]),
|((tr, tg, tb), mut pixs), (r, g, b, a)| {
pixs.push((r, g, b, a));
((tr + r as u16, tg + g as u16, tb + b as u16), pixs)
},
);
let (ar, ag, ab) = ((tr / 128) as u8, (tg / 128) as u8, (tb / 128) as u8);
let (tr, tg, tb) = match self.background {
None => (ar, ag, ab),
Some((br, bg, bb)) => (br, bg, bb),
};
let (bm, fr, fg, fb, br, bg, bb) = (0..128).map(|idx| (pixs[idx], idx)).fold(
(0, 0u16, 0u16, 0u16, 0u16, 0u16, 0u16),
|(bm, fr, fg, fb, br, bg, bb), ((r, g, b, _a), idx)| {
let mut distance =
r as isize - tr as isize + g as isize - tg as isize + b as isize - tb as isize;
if self.invert {
distance = -distance;
}
let p = if distance > 0 { 1 } else { 0 };
let front = p as u16;
let back = (p ^ 1) as u16;
(
bm | ((front as u128) << (127 - idx)),
fr + front * r as u16,
fg + front * g as u16,
fb + front * b as u16,
br + back * r as u16,
bg + back * g as u16,
bb + back * b as u16,
)
},
);
if let Some(bg) = self.background {
(bm, (ar, ag, ab), bg)
} else {
let fs = bm.count_ones() as u16;
let bs = bm.count_zeros() as u16;
let mut front_color = if fs == 0 {
(127, 127, 127)
} else {
(
(fr.saturating_div(fs)) as u8,
(fg.saturating_div(fs)) as u8,
(fb.saturating_div(fs)) as u8,
)
};
let back_color = if bs == 0 {
front_color
} else {
(
(br.saturating_div(bs)) as u8,
(bg.saturating_div(bs)) as u8,
(bb.saturating_div(bs)) as u8,
)
};
if fs == 0 {
front_color = back_color;
}
(bm, front_color, back_color)
}
}
fn bit(&self, idx: u8, img: &SubImage<&DynamicImage>) -> (u8, u8, u8, u8) {
assert!(idx < 128, "It's a u128");
let x = (idx % self.brush_width()) as u32;
let y = (idx / self.brush_height()) as u32;
if x >= img.width() || y >= img.height() {
return (0, 0, 0, 0);
}
let pix = img.get_pixel(x, y);
let [r, g, b, a] = pix.0;
(r, g, b, a)
}
}
pub struct ConvertIterator<'a> {
img: &'a image::DynamicImage,
insa: &'a Insa,
bwidth: u32,
bheight: u32,
rows: u32,
cols: u32,
idx: u32,
}
impl<'a> ConvertIterator<'a> {
pub fn cols(&self) -> u32 {
self.cols
}
pub fn rows(&self) -> u32 {
self.rows
}
}
impl<'a> ConvertIterator<'a> {
pub fn new(img: &'a image::DynamicImage, insa: &'a Insa) -> Self {
let bwidth = insa.brush_width() as u32;
let bheight = insa.brush_height() as u32;
let cols = img.width() / bwidth;
let rows = img.height() / bheight;
Self {
img,
insa,
bwidth,
bheight,
rows,
cols,
idx: 0,
}
}
}
impl<'a> Iterator for ConvertIterator<'a> {
type Item = ((u32, u32), Symbol);
fn next(&mut self) -> Option<Self::Item> {
self.idx = self.idx.checked_add(1)?;
let c = (self.idx - 1) % self.cols;
let r = (self.idx - 1) / self.cols;
if r >= self.rows {
return None;
}
let x = c * self.bwidth;
let y = r * self.bheight;
let w = (self.img.width() - x).clamp(0, self.bwidth);
let h = (self.img.height() - y).clamp(0, self.bheight);
let spot = self.img.view(x, y, w, h);
Some(((c, r), self.insa.convert_symbol(&spot)))
}
}
#[cfg(feature = "fontdue")]
pub fn rasterize_u128(font: &fontdue::Font, c: char) -> u128 {
let mut bitmap = 0u128;
let (metrics, pixs) = font.rasterize(c, 16.0);
const WIDTH: isize = 8;
const HEIGHT: isize = WIDTH * 2;
const PIX_THRESHOLD: u8 = 50;
for (i, pix) in pixs.into_iter().enumerate() {
let x = (i % metrics.width) as isize;
let y = (i / metrics.width) as isize;
let x = x + metrics.xmin as isize;
let y = y + HEIGHT - metrics.height as isize - metrics.ymin as isize - HEIGHT / 6;
if x < 0 || y < 0 || x >= WIDTH || y >= HEIGHT {
continue;
}
let pix = if pix >= PIX_THRESHOLD {
1 << (WIDTH - 1 - x) << ((HEIGHT - 1 - y) * WIDTH)
} else {
0
};
bitmap = bitmap | pix;
}
bitmap
}
pub fn dump_bitmap_u128(bitmap: u128) -> String {
let mut dump = String::default();
for row in 0..16 {
let row = 0b11111111 & (bitmap >> 8 * (15 - row));
dump += &format!("{row:#010b}\n");
}
dump
}
pub struct Symbol {
pub brush: char,
pub front_color: (u8, u8, u8),
pub back_color: Option<(u8, u8, u8)>,
}
impl Display for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
brush,
front_color: (fr, fg, fb),
back_color,
} = &self;
if let Some((br, bg, bb)) = back_color {
write!(f, "\x1b[48;2;{br};{bg};{bb}m")?;
}
write!(f, "\x1b[38;2;{fr};{fg};{fb}m")?;
write!(f, "\x1b[1m")?;
write!(f, "{brush}")
}
}