ASCII art
Pictures turned into ASCII art: characters from light to dark, plain for terminals and emails, or coloured as HTML.
Version 1.0.0 · 2 functions · 1 crate
Functions
What use ascii_art.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use ascii_art, ascii_art_html from ascii_art (Quillo 0.6).
ascii_art(path: str, width: int, style: str, invert: bool) → stror an errorThe picture as lines of text width characters wide (4 to 400), dark parts drawn with dense characters for dark text on a light background. style is "simple" (" .:-=+*#%@"), "detailed" (70 characters), "blocks" (" ░▒▓█") or your own characters from light to dark; invert true for light text on dark.
ascii_art_html(path: str, width: int, style: str) → stror an errorThe picture as an HTML <pre> of coloured characters, each the colour of its spot, on a dark background: put it in a page with raw(...).
Get it
$ quillo install ascii_art # ascii_art.qwer into this folder$ quillo install ascii_art --example # and its example, ascii_art.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/ascii_art.qwer$ curl -O https://quillolang.org/plugins/files/ascii_art.qwe$ curl -O https://quillolang.org/plugins/files/ascii_art_sample.pngThen, in your program, all of it or only what you call:
use ascii_art.qweruse ascii_art, ascii_art_html from ascii_art // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif"] }Example
// A picture as ASCII art, for the server's start-up banner, a plain-text email and a retro page.//// quillo run ascii_art.qwe then open http://localhost:8080/artuse "ascii_art.qwer"route GET /art = ascii_art("ascii_art_sample.png", 60, "simple", false)route GET /blocks = ascii_art("ascii_art_sample.png", 40, "blocks", false)route GET / = <html><body style="background: #222">{raw(ascii_art_html("ascii_art_sample.png", 80, "detailed")?)}</body></html>$ quillo run ascii_art.qwe$ curl 'localhost:8080/art'$ curl 'localhost:8080/blocks'$ curl 'localhost:8080/'Source
//! Pictures turned into ASCII art: text of characters from light to dark, plain for terminals and emails, or coloured as HTML.//! version: 1.0.0//!//! [dependencies]//! image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif"] }use image::{DynamicImage, ImageDecoder};fn reader(path: &str) -> Result<image::ImageReader<std::io::BufReader<std::fs::File>>, String> { let mut r = image::ImageReader::open(path).map_err(|e| format!("{}: {}", path, e))?.with_guessed_format().map_err(|e| format!("{}: {}", path, e))?; // A small file claiming to be enormous is refused before anything is allocated. let mut l = image::Limits::default(); l.max_image_width = Some(12_000); l.max_image_height = Some(12_000); l.max_alloc = Some(512 * 1024 * 1024); r.limits(l); Ok(r)}// The picture turned upright by its EXIF orientation, as phones expect.fn open(path: &str) -> Result<DynamicImage, String> { let mut dec = reader(path)?.into_decoder().map_err(|e| format!("{}: {}", path, e))?; let o = dec.orientation().unwrap_or(image::metadata::Orientation::NoTransforms); let mut img = DynamicImage::from_decoder(dec).map_err(|e| format!("{}: {}", path, e))?; img.apply_orientation(o); Ok(img)}// Saved in the format its extension names: .jpg (quality 88), .png, .webp (lossless) or .gif; folders are made.fn save(img: &DynamicImage, path: &str) -> Result<(), String> { if let Some(dir) = std::path::Path::new(path).parent().filter(|d| !d.as_os_str().is_empty()) { std::fs::create_dir_all(dir).map_err(|e| format!("{}: {}", dir.display(), e))?; } let ext = path.rsplit_once('.').map(|(_, e)| e.to_lowercase()).unwrap_or_default(); let err = |e: image::ImageError| format!("{}: {}", path, e); match ext.as_str() { "jpg" | "jpeg" => { let f = std::fs::File::create(path).map_err(|e| format!("{}: {}", path, e))?; image::codecs::jpeg::JpegEncoder::new_with_quality(std::io::BufWriter::new(f), 88).encode_image(&img.to_rgb8()).map_err(err) } "png" => img.save_with_format(path, image::ImageFormat::Png).map_err(err), "webp" => DynamicImage::ImageRgba8(img.to_rgba8()).save_with_format(path, image::ImageFormat::WebP).map_err(err), "gif" => DynamicImage::ImageRgba8(img.to_rgba8()).save_with_format(path, image::ImageFormat::Gif).map_err(err), _ => Err(format!("{}: save as .jpg, .png, .webp or .gif", path)), }}fn ramp(style: &str) -> Vec<char> { match style { "blocks" => " ░▒▓█".chars().collect(), "detailed" => " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$".chars().collect(), "simple" | "" => " .:-=+*#%@".chars().collect(), s => s.chars().collect(), }}// The picture shrunk to `width` columns; characters are about twice as tall as wide, so rows are halved.fn grid(path: &str, width: i64) -> Result<image::RgbaImage, String> { let img = open(path)?; let cols = width.clamp(4, 400) as u32; let rows = ((img.height() as f64 / img.width().max(1) as f64 * cols as f64 * 0.5).round() as u32).clamp(1, 400); Ok(img.resize_exact(cols, rows, image::imageops::FilterType::Triangle).to_rgba8())}fn level(p: &image::Rgba<u8>, invert: bool) -> f64 { // Light from 0 (black) to 1 (white); transparent counts as the background. let a = p.0[3] as f64 / 255.0; let l = (0.2126 * p.0[0] as f64 + 0.7152 * p.0[1] as f64 + 0.0722 * p.0[2] as f64) / 255.0 * a + (1.0 - a); if invert { 1.0 - l } else { l }}/// The picture as lines of text `width` characters wide (4 to 400), dark parts drawn with dense characters for dark text on a light background./// `style` is "simple" (" .:-=+*#%@"), "detailed" (70 characters), "blocks" (" ░▒▓█") or your own characters from light to dark; `invert` true for light text on dark.pub fn ascii_art(path: &str, width: i64, style: &str, invert: bool) -> Result<String, String> { let g = grid(path, width)?; let chars = ramp(style); if chars.len() < 2 { return Err("give at least two characters, from light to dark".into()); } let last = (chars.len() - 1) as f64; let mut out = String::new(); for row in g.rows() { let line: String = row.map(|p| chars[((1.0 - level(p, invert)) * last).round() as usize]).collect(); out += line.trim_end(); out.push('\n'); } Ok(out)}/// The picture as an HTML `<pre>` of coloured characters, each the colour of its spot, on a dark background: put it in a page with `raw(...)`.pub fn ascii_art_html(path: &str, width: i64, style: &str) -> Result<String, String> { let g = grid(path, width.clamp(4, 200))?; let chars = ramp(style); if chars.len() < 2 { return Err("give at least two characters, from light to dark".into()); } let last = (chars.len() - 1) as f64; let mut out = String::from(r#"<pre style="background:#111;line-height:1;font-size:10px;font-family:monospace;display:inline-block;padding:8px">"#); for row in g.rows() { for p in row { // Bright spots get dense characters, as the background is dark. let c = chars[((1.0 - level(p, true)) * last).round() as usize]; let c = match c { '<' => "<".to_string(), '>' => ">".to_string(), '&' => "&".to_string(), c => c.to_string() }; out += &format!(r#"<span style="color:#{:02x}{:02x}{:02x}">{}</span>"#, p.0[0], p.0[1], p.0[2], c); } out.push('\n'); } out += "</pre>"; Ok(out)}Related
ocr.qwer · 1.0.0Text read from pictures in pure Rust: photos and scans of documents, receipts, labels and screenshots to plain text, row by row or line by line with boxes, parts of the page read by range (pixels or percentages), several form fields at once, only the characters you allow, and phone photos turned upright first.13 views · 9 downloadsAnimated GIF gif.qwer · 1.0.0Animated GIFs made from a list of pictures with the delay and size you choose, and GIFs read back: frames, size and length.6 views · 7 downloadsBarcodes barcode.qwer · 1.0.0EAN-13, EAN-8, UPC-A, Code 128 and Code 39 barcodes as SVG or PNG, with the digits underneath and check digits worked out.6 views · 5 downloadsBlurHash blurhash.qwer · 1.0.0A photo as a short BlurHash, shown blurred while the real one loads, and back as a small PNG data URL.7 views · 6 downloadsCaptions on images caption.qwer · 1.0.0Text drawn on pictures with a built-in font: captions at the top, middle or bottom, wrapped to fit, with an outline or a band behind them, meme-style top and bottom lines, and text measured before drawing.7 views · 7 downloadsCollage collage.qwer · 1.0.0Several pictures made into one: a grid of equal cells with gaps, or justified rows that keep each picture's shape, on your background.7 views · 7 downloadsThread 0 posts
On ASCII art: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.