use iascii::config::{ConfigBuilder as AsciiConfigBuilder, OutputSizing};
use iascii::render::ColorDepth;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn image_bytes_to_string(
bytes: &[u8],
max_width: usize,
color: bool,
) -> Result<String, JsError> {
let grid = decode_and_convert(bytes, max_width)?;
let depth = color.then_some(ColorDepth::TrueColor);
Ok(crate::render::grid_to_string(&grid, depth))
}
#[wasm_bindgen]
pub fn image_bytes_to_lines(
bytes: &[u8],
max_width: usize,
color: bool,
) -> Result<Vec<JsValue>, JsError> {
let grid = decode_and_convert(bytes, max_width)?;
let depth = color.then_some(ColorDepth::TrueColor);
Ok(crate::render::grid_to_lines(&grid, depth)
.into_iter()
.map(|line| JsValue::from_str(&line))
.collect())
}
#[wasm_bindgen]
pub fn gif_bytes_to_frame_strings(
bytes: &[u8],
max_width: usize,
color: bool,
) -> Result<Vec<JsValue>, JsError> {
use ::image::{codecs::gif::GifDecoder, AnimationDecoder};
let decoder =
GifDecoder::new(std::io::Cursor::new(bytes)).map_err(|e| JsError::new(&e.to_string()))?;
let depth = color.then_some(ColorDepth::TrueColor);
let ascii_config = sized_ascii_config(max_width)?;
let mut out = Vec::new();
for frame in decoder.into_frames() {
let frame = frame.map_err(|e| JsError::new(&e.to_string()))?;
let (numer, denom) = frame.delay().numer_denom_ms();
let delay_ms = numer as u64 / denom.max(1) as u64;
let dynamic = ::image::DynamicImage::ImageRgba8(frame.into_buffer());
let rgb = dynamic.to_rgb8();
let grid =
iascii::convert::convert_image(rgb.width(), rgb.height(), rgb.as_raw(), &ascii_config)
.map_err(|e| JsError::new(&e.to_string()))?;
let text = crate::render::grid_to_string(&grid, depth);
out.push(JsValue::from_str(&format!("{delay_ms}\u{1e}{text}")));
}
Ok(out)
}
#[wasm_bindgen]
pub fn text_frame_at(text: &str, animation: TextAnimationKind, frame_index: u32) -> String {
use crate::text::{Rgb, TextAnimOptions, TextAnimation};
let anim = match animation {
TextAnimationKind::Typewriter => TextAnimation::Typewriter,
TextAnimationKind::Rainbow => TextAnimation::Rainbow,
TextAnimationKind::Wave => TextAnimation::Wave,
TextAnimationKind::Pulse => TextAnimation::Pulse,
TextAnimationKind::Blink => TextAnimation::Blink {
on_color: Rgb::WHITE,
off_color: Rgb::new(0, 0, 0),
},
};
let opts = TextAnimOptions {
frames: frame_index as u64 + 1,
..Default::default()
};
crate::text::text_frames(text, &anim, &opts)
.ok()
.and_then(|frames| frames.into_iter().last())
.unwrap_or_default()
}
#[wasm_bindgen]
#[derive(Debug, Clone, Copy)]
pub enum TextAnimationKind {
Typewriter,
Rainbow,
Wave,
Blink,
Pulse,
}
fn sized_ascii_config(max_width: usize) -> Result<iascii::config::Config, JsError> {
let mut builder = AsciiConfigBuilder::new();
if max_width > 0 {
builder = builder.output_sizing(OutputSizing::MaxWidth(max_width));
}
builder.build().map_err(|e| JsError::new(&e.to_string()))
}
fn decode_and_convert(bytes: &[u8], max_width: usize) -> Result<iascii::grid::Grid, JsError> {
let img = ::image::load_from_memory(bytes).map_err(|e| JsError::new(&e.to_string()))?;
let rgb = img.to_rgb8();
let ascii_config = sized_ascii_config(max_width)?;
iascii::convert::convert_image(rgb.width(), rgb.height(), rgb.as_raw(), &ascii_config)
.map_err(|e| JsError::new(&e.to_string()))
}