use std::cmp::{max, min};
use std::{error, fmt};
use colored::Colorize;
use image::Pixel;
#[cfg(feature = "rayon")]
use rayon::prelude::*;
pub const DEFAULT_ASCII_STRING: &str = " .,:;+*?%S#@";
pub trait AsciiArtConverter {
fn ascii_art(
&self,
options: &AsciiArtConverterOptions,
) -> Result<AsciiArt, AsciiArtConverterError>;
}
impl AsciiArtConverter for image::DynamicImage {
fn ascii_art(
&self,
options: &AsciiArtConverterOptions,
) -> Result<AsciiArt, AsciiArtConverterError> {
self.to_rgba8().ascii_art(options)
}
}
impl AsciiArtConverter for image::RgbImage {
fn ascii_art(
&self,
options: &AsciiArtConverterOptions,
) -> Result<AsciiArt, AsciiArtConverterError> {
if self.width() == 0 || self.height() == 0 {
return Err(AsciiArtConverterError::SizeError(SizeError));
}
#[cfg(feature = "rayon")]
let iter = self.par_pixels();
#[cfg(not(feature = "rayon"))]
let iter = self.pixels();
let characters = iter
.map(|pixel| pixel.to_ascii_art_pixel(&options.ascii_string))
.collect::<Result<Vec<AsciiArtPixel>, AsciiStringError>>()?;
Ok(AsciiArt::new(
characters,
self.width(),
self.height(),
options.colored,
))
}
}
impl AsciiArtConverter for image::RgbaImage {
fn ascii_art(
&self,
options: &AsciiArtConverterOptions,
) -> Result<AsciiArt, AsciiArtConverterError> {
if self.width() == 0 || self.height() == 0 {
return Err(AsciiArtConverterError::SizeError(SizeError));
}
#[cfg(feature = "rayon")]
let iter = self.par_pixels();
#[cfg(not(feature = "rayon"))]
let iter = self.pixels();
let characters = iter
.map(|pixel| pixel.to_ascii_art_pixel(&options.ascii_string))
.collect::<Result<Vec<AsciiArtPixel>, AsciiStringError>>()?;
Ok(AsciiArt::new(
characters,
self.width(),
self.height(),
options.colored,
))
}
}
impl AsciiArtConverter for image::GrayImage {
fn ascii_art(
&self,
options: &AsciiArtConverterOptions,
) -> Result<AsciiArt, AsciiArtConverterError> {
if self.width() == 0 || self.height() == 0 {
return Err(AsciiArtConverterError::SizeError(SizeError));
}
#[cfg(feature = "rayon")]
let iter = self.par_pixels();
#[cfg(not(feature = "rayon"))]
let iter = self.pixels();
let characters = iter
.map(|pixel| pixel.to_ascii_art_pixel(&options.ascii_string))
.collect::<Result<Vec<AsciiArtPixel>, AsciiStringError>>()?;
Ok(AsciiArt::new(
characters,
self.width(),
self.height(),
options.colored,
))
}
}
impl AsciiArtConverter for image::GrayAlphaImage {
fn ascii_art(
&self,
options: &AsciiArtConverterOptions,
) -> Result<AsciiArt, AsciiArtConverterError> {
if self.width() == 0 || self.height() == 0 {
return Err(AsciiArtConverterError::SizeError(SizeError));
}
#[cfg(feature = "rayon")]
let iter = self.par_pixels();
#[cfg(not(feature = "rayon"))]
let iter = self.pixels();
let characters = iter
.map(|pixel| pixel.to_ascii_art_pixel(&options.ascii_string))
.collect::<Result<Vec<AsciiArtPixel>, AsciiStringError>>()?;
Ok(AsciiArt::new(
characters,
self.width(),
self.height(),
options.colored,
))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AsciiArtConverterOptions {
pub ascii_string: String,
pub colored: bool,
}
impl Default for AsciiArtConverterOptions {
fn default() -> AsciiArtConverterOptions {
AsciiArtConverterOptions {
ascii_string: DEFAULT_ASCII_STRING.to_owned(),
colored: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum AsciiArtConverterError {
AsciiStringError(AsciiStringError),
SizeError(SizeError),
}
impl error::Error for AsciiArtConverterError {}
impl fmt::Display for AsciiArtConverterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AsciiArtConverterError::AsciiStringError(err) => {
write!(f, "ASCII string error: {}", err)
}
AsciiArtConverterError::SizeError(err) => write!(f, "Size error: {}", err),
}
}
}
impl From<AsciiStringError> for AsciiArtConverterError {
fn from(err: AsciiStringError) -> AsciiArtConverterError {
AsciiArtConverterError::AsciiStringError(err)
}
}
impl From<SizeError> for AsciiArtConverterError {
fn from(err: SizeError) -> AsciiArtConverterError {
AsciiArtConverterError::SizeError(err)
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct SizeError;
impl error::Error for SizeError {}
impl fmt::Display for SizeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "width and height are too small")
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct AsciiArt {
pub characters: Vec<AsciiArtPixel>,
pub width: u32,
pub height: u32,
pub colored: bool,
}
impl AsciiArt {
pub fn new(characters: Vec<AsciiArtPixel>, width: u32, height: u32, colored: bool) -> AsciiArt {
AsciiArt {
characters,
width,
height,
colored,
}
}
pub fn to_colored(mut self, colored: bool) -> AsciiArt {
self.colored = colored;
self
}
pub fn mut_colored(&mut self, colored: bool) {
self.colored = colored;
}
}
impl fmt::Display for AsciiArt {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[cfg(feature = "rayon")]
let iter = self.characters.par_iter();
#[cfg(not(feature = "rayon"))]
let iter = self.characters.iter();
let characters = iter
.map(|ascii_character| ascii_character.to_string(self.colored))
.collect::<Vec<String>>();
#[cfg(feature = "rayon")]
let chunks = characters.par_chunks(self.width.try_into().unwrap());
#[cfg(not(feature = "rayon"))]
let chunks = characters.chunks(self.width.try_into().unwrap());
let text = chunks
.map(|line| line.join(""))
.collect::<Vec<String>>()
.join("\n");
write!(f, "{}", text)
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct AsciiArtPixel {
pub character: char,
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl AsciiArtPixel {
#[deprecated(since = "3.1.0")]
pub fn new(
r: u8,
g: u8,
b: u8,
a: u8,
ascii_string: &str,
) -> Result<AsciiArtPixel, AsciiStringError> {
#[allow(deprecated)]
let lightness = get_lightness(r, g, b, a);
let character = ascii_character(lightness, ascii_string)?;
Ok(AsciiArtPixel {
character,
r,
g,
b,
a,
})
}
pub fn to_string(&self, colored: bool) -> String {
if colored {
return self
.character
.to_string()
.truecolor(self.r, self.g, self.b)
.to_string();
}
self.character.to_string()
}
}
pub trait ToAsciiArtPixel {
fn to_ascii_art_pixel(&self, ascii_string: &str) -> Result<AsciiArtPixel, AsciiStringError>;
}
impl ToAsciiArtPixel for image::Rgb<u8> {
fn to_ascii_art_pixel(&self, ascii_string: &str) -> Result<AsciiArtPixel, AsciiStringError> {
let luma_pixel = self.to_luma();
Ok(AsciiArtPixel {
character: ascii_character(luma_pixel[0] as f32 / 255.0, ascii_string)?,
r: self[0],
g: self[1],
b: self[2],
a: 255,
})
}
}
impl ToAsciiArtPixel for image::Rgba<u8> {
fn to_ascii_art_pixel(&self, ascii_string: &str) -> Result<AsciiArtPixel, AsciiStringError> {
let luma_pixel = self.to_luma_alpha();
Ok(AsciiArtPixel {
character: ascii_character(
luma_pixel[0] as f32 * luma_pixel[1] as f32 / (255.0 * 255.0),
ascii_string,
)?,
r: self[0],
g: self[1],
b: self[2],
a: self[3],
})
}
}
impl ToAsciiArtPixel for image::Luma<u8> {
fn to_ascii_art_pixel(&self, ascii_string: &str) -> Result<AsciiArtPixel, AsciiStringError> {
Ok(AsciiArtPixel {
character: ascii_character(self[0] as f32 / 255.0, ascii_string)?,
r: self[0],
g: self[0],
b: self[0],
a: 255,
})
}
}
impl ToAsciiArtPixel for image::LumaA<u8> {
fn to_ascii_art_pixel(&self, ascii_string: &str) -> Result<AsciiArtPixel, AsciiStringError> {
Ok(AsciiArtPixel {
character: ascii_character(
self[0] as f32 * self[1] as f32 / (255.0 * 255.0),
ascii_string,
)?,
r: self[0],
g: self[0],
b: self[0],
a: self[1],
})
}
}
pub fn ascii_character(lightness: f32, ascii_string: &str) -> Result<char, AsciiStringError> {
ascii_string
.chars()
.nth(((ascii_string.chars().count() - 1) as f32 * lightness) as usize)
.ok_or(AsciiStringError)
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct AsciiStringError;
impl error::Error for AsciiStringError {}
impl fmt::Display for AsciiStringError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "lightness is out of ASCII string")
}
}
#[deprecated(since = "3.1.0", note = "Use `image::Pixel::to_luma` instead")]
pub fn get_lightness(r: u8, g: u8, b: u8, a: u8) -> f32 {
let max = max(max(r, g), b);
let min = min(min(r, g), b);
((max as f32 + min as f32) * a as f32) / (510.0 * 255.0)
}
#[deprecated(since = "3.2.1", note = "Use `Iterator::rev` instead")]
pub trait ReverseString {
fn reverse(&self) -> Self;
}
#[allow(deprecated)]
impl ReverseString for String {
fn reverse(&self) -> String {
self.chars().rev().collect()
}
}