#![deny(rust_2018_idioms, non_ascii_idents, missing_debug_implementations)]
#![warn(missing_docs)]
#[cfg(feature = "colors")]
pub mod color;
#[cfg(feature = "image")]
pub mod impl_image;
pub mod sympols;
#[cfg(feature = "text_image")]
pub mod text_image;
#[cfg(feature = "colors")]
pub use color::ANSIColor;
pub use sympols::Sympols;
#[cfg(feature = "text_image")]
pub use text_image::{Fragment, IndexdFragment, TextImage, ToTextImage};
#[cfg(feature = "colors")]
use color::{ANSI_BACKGROUND_ESCAPE, ANSI_ESCAPE_CLOSE, ANSI_FOREGROUND_ESCAPE};
use std::{error::Error, io::Write};
pub const COLORS: u8 = 0b1;
pub const REVERSE: u8 = 0b10;
pub trait PixelImage {
fn dimensions(&self) -> (u32, u32);
fn get_pixel(&self, x: u32, y: u32) -> Rgba;
}
pub trait FragmentWriter {
#[cfg(feature = "colors")]
fn background(&mut self, bc: &ANSIColor) -> Result<bool, Box<dyn Error>>;
fn write_fragment(&mut self, info: FragmentInfo) -> Result<(), Box<dyn Error>>;
#[cfg(feature = "colors")]
fn write_colored_fragment(
&mut self,
info: FragmentInfo,
bc: Option<&ANSIColor>,
fc: Option<&ANSIColor>,
) -> Result<(), Box<dyn Error>>;
fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), Box<dyn Error>>;
}
impl<W: Write> FragmentWriter for W {
#[cfg(feature = "colors")]
#[inline]
fn background(&mut self, bc: &ANSIColor) -> Result<bool, Box<dyn Error>> {
self.write_bytes(bc.to_string().as_bytes())?;
Ok(true)
}
#[inline]
fn write_fragment(&mut self, info: FragmentInfo) -> Result<(), Box<dyn Error>> {
self.write_all(info.sym.to_string().as_bytes())?;
Ok(())
}
#[cfg(feature = "colors")]
#[inline]
fn write_colored_fragment(
&mut self,
info: FragmentInfo,
bc: Option<&ANSIColor>,
fc: Option<&ANSIColor>,
) -> Result<(), Box<dyn Error>> {
if let Some(bc) = bc {
self.write_all(bc.as_background().as_bytes())?;
}
if let Some(fc) = fc {
self.write_all(fc.as_foreground().as_bytes())?;
}
self.write_fmt(format_args!("{}", info.sym))?;
if bc.is_some() {
self.write_all(ANSI_ESCAPE_CLOSE.as_bytes())?;
}
if fc.is_some() {
self.write_all(ANSI_ESCAPE_CLOSE.as_bytes())?;
}
Ok(())
}
#[inline]
fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), Box<dyn Error>> {
self.write_all(bytes)?;
Ok(())
}
}
#[derive(Debug, PartialEq, PartialOrd, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct Config {
pub sympols: Sympols,
#[cfg(feature = "colors")]
pub background: Option<ANSIColor>,
pub flags: u8,
}
impl Config {
#[inline]
#[must_use]
pub const fn new(sympols: Sympols) -> Self {
Self {
sympols,
#[cfg(feature = "colors")]
background: None,
flags: 0,
}
}
#[inline]
#[must_use]
pub const fn with_flags(mut self, flags: u8) -> Self {
self.flags = flags;
self
}
pub const fn calc_buf_size(&self, w: u32, h: u32) -> usize {
#[allow(unused_mut)]
let mut res = w as usize * h as usize;
#[cfg(feature = "colors")]
if self.use_colors() {
res = (res
* (ANSI_ESCAPE_CLOSE.len()
+ ANSI_FOREGROUND_ESCAPE.len()
+ ANSI_BACKGROUND_ESCAPE.len()))
* (3 * 3);
}
res
}
}
#[cfg(feature = "colors")]
impl Config {
#[inline]
#[must_use]
pub fn with_background(mut self, color: impl Into<ANSIColor>) -> Self {
self.background = Some(color.into());
self
}
#[inline]
#[must_use]
pub const fn new_with_background(sympols: Sympols, background_color: ANSIColor) -> Self {
Self {
sympols,
background: Some(background_color),
flags: 0,
}
}
#[inline]
pub const fn reversed(&self) -> bool {
self.flags & REVERSE == REVERSE
}
#[inline]
pub const fn use_colors(&self) -> bool {
self.flags & COLORS == COLORS
}
}
#[derive(Debug, PartialEq, PartialOrd, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct FragmentInfo {
pub sym: char,
pub sym_index: usize,
#[cfg(feature = "colors")]
pub fg: ANSIColor,
}
#[derive(Debug, PartialEq, PartialOrd, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rgba {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
pub fn convert_image_to_ascii<I, W>(
config: &Config,
image: &I,
out: &mut W,
) -> Result<(), Box<dyn Error>>
where
I: PixelImage,
W: FragmentWriter,
{
let (width, height) = image.dimensions();
#[cfg(feature = "colors")]
let ansi_close = if let Some(bc) = &config.background {
if !config.reversed() {
out.background(bc)?
} else {
false
}
} else {
false
};
#[cfg(feature = "colors")]
let colored = config.use_colors();
#[cfg(not(feature = "colors"))]
let colored = false;
for y in 0..height {
for x in 0..width {
let pixel = image.get_pixel(x, y);
if colored {
#[cfg(feature = "colors")]
{
let (sym, sym_index) = config.sympols.sym_and_index(&pixel);
let fi = FragmentInfo {
sym,
sym_index,
fg: ANSIColor::from(pixel),
};
let mut fg = Some(fi.fg.clone());
let mut bc = config.background.clone();
if !ansi_close && config.reversed() {
std::mem::swap(&mut bc, &mut fg);
}
out.write_colored_fragment(fi, bc.as_ref(), fg.as_ref())?;
}
} else {
let (sym, sym_index) = config.sympols.sym_and_index(&pixel);
out.write_fragment(FragmentInfo {
sym,
sym_index,
#[cfg(feature = "colors")]
fg: pixel.into(),
})?;
}
}
out.write_bytes("\n".as_bytes())?;
}
#[cfg(feature = "colors")]
if ansi_close {
out.write_bytes(ANSI_ESCAPE_CLOSE.as_bytes())?;
}
Ok(())
}