ThumbHash
ThumbHash placeholders: a photo as about 25 bytes to keep with it, shown as a soft preview while it loads, finer than BlurHash and with the photo's aspect ratio, average colour and transparency inside.
Version 1.0.0 · 3 functions · 4 crates
Functions
What use thumbhash.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use thumbhash_of, thumbhash_data_url from thumbhash (Quillo 0.6).
thumbhash_of(path: str) → stror an errorThe ThumbHash of a picture (JPEG, PNG, WebP or GIF), in base64: about 33 characters to store with it.
thumbhash_data_url(hash: str) → stror an errorA ThumbHash as a small PNG data: URL, for an <img src> or a CSS background while the photo loads (about 32 pixels on its long side, scale it up in CSS).
thumbhash_info(hash: str) → anyor an errorWhat a ThumbHash holds without drawing it: { width, height, ratio, color, alpha }; width and height of the placeholder, ratio the photo's width over its height (about), color its average as #rrggbb (a background for the box before anything loads), alpha whether it has transparency.
Get it
$ quillo install thumbhash # thumbhash.qwer into this folder$ quillo install thumbhash --example # and its example, thumbhash.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/thumbhash.qwer$ curl -O https://quillolang.org/plugins/files/thumbhash.qwe$ curl -O https://quillolang.org/plugins/files/thumbhash_sample.pngThen, in your program, all of it or only what you call:
use thumbhash.qweruse thumbhash_of, thumbhash_data_url from thumbhash // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
thumbhash = "=0.1.0"image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp", "gif"] }base64 = "0.22"serde_json = { version = "1", features = ["preserve_order"] }Example
// A photo gallery: each photo's ThumbHash made on upload and kept with it, then shown as a soft placeholder until the photo arrives.//// quillo run thumbhash.qwe then open http://localhost:8080/use "thumbhash.qwer"let PHOTO = "thumbhash_sample.png"route GET /hash { let hash = thumbhash_of(PHOTO)? { hash: hash, length: len(hash), info: thumbhash_info(hash)?, preview_starts: slice(thumbhash_data_url(hash)?, 0, 22) }}route GET / { let hash = thumbhash_of(PHOTO)? let info = thumbhash_info(hash)? <html><body> <div style={`width: 480px; aspect-ratio: ${info.ratio}; background: ${info.color} url(${thumbhash_data_url(hash)?}) center / cover`}> <img src="/photo.png" width="480" loading="lazy" alt="Hills, the sun, a bowl of apples"/> </div> </body></html>}route GET /photo.png = file(PHOTO)$ quillo run thumbhash.qwe$ curl 'localhost:8080/hash'$ curl 'localhost:8080/'Source
//! ThumbHash placeholders: a photo as about 25 bytes (base64 text kept beside it in the database) that a page shows as a soft preview while the real one loads; finer than BlurHash, with the photo's aspect ratio, average colour and transparency inside.//! version: 1.0.0//!//! [dependencies]//! thumbhash = "=0.1.0"//! image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp", "gif"] }//! base64 = "0.22"//! serde_json = { version = "1", features = ["preserve_order"] }use base64::engine::general_purpose::STANDARD;use base64::Engine;use serde_json::{json, Value};fn decode(hash: &str) -> Result<Vec<u8>, String> { let b = STANDARD.decode(hash.trim()).map_err(|_| format!("`{}` is not a ThumbHash in base64", hash))?; if b.len() < 5 { return Err("a ThumbHash is at least 5 bytes".into()) } Ok(b)}/// The ThumbHash of a picture (JPEG, PNG, WebP or GIF), in base64: about 33 characters to store with it.pub fn thumbhash_of(path: &str) -> Result<String, String> { let img = image::open(path).map_err(|e| format!("{}: {}", path, e))?; // ThumbHash takes at most 100 × 100 pixels. let small = img.thumbnail(100, 100).to_rgba8(); let (w, h) = small.dimensions(); Ok(STANDARD.encode(thumbhash::rgba_to_thumb_hash(w as usize, h as usize, small.as_raw())))}/// A ThumbHash as a small PNG `data:` URL, for an `<img src>` or a CSS background while the photo loads (about 32 pixels on its long side, scale it up in CSS).pub fn thumbhash_data_url(hash: &str) -> Result<String, String> { let (w, h, rgba) = thumbhash::thumb_hash_to_rgba(&decode(hash)?).map_err(|_| "the ThumbHash is damaged".to_string())?; let img = image::RgbaImage::from_raw(w as u32, h as u32, rgba).ok_or("the ThumbHash is damaged")?; let mut png = Vec::new(); img.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).map_err(|e| e.to_string())?; Ok(format!("data:image/png;base64,{}", STANDARD.encode(png)))}/// What a ThumbHash holds without drawing it: `{ width, height, ratio, color, alpha }`; `width` and `height` of the placeholder, `ratio` the photo's width over its height (about), `color` its average as `#rrggbb` (a background for the box before anything loads), `alpha` whether it has transparency.pub fn thumbhash_info(hash: &str) -> Result<Value, String> { let b = decode(hash)?; let (w, h, _) = thumbhash::thumb_hash_to_rgba(&b).map_err(|_| "the ThumbHash is damaged".to_string())?; let (r, g, bl, a) = thumbhash::thumb_hash_to_average_rgba(&b).map_err(|_| "the ThumbHash is damaged".to_string())?; let ratio = thumbhash::thumb_hash_to_approximate_aspect_ratio(&b).map_err(|_| "the ThumbHash is damaged".to_string())?; let c = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u8; Ok(json!({ "width": w, "height": h, "ratio": (ratio as f64 * 100.0).round() / 100.0, "color": format!("#{:02x}{:02x}{:02x}", c(r), c(g), c(bl)), "alpha": a < 0.999 || (b[2] & 0x80) != 0, }))}Related
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 downloadsEXIF exif.qwer · 1.0.0A photo's EXIF: camera and lens, when it was taken, where (GPS), exposure and orientation, from JPEG, HEIC, PNG and WebP.7 views · 5 downloadsEXIF strip exif_strip.qwer · 1.0.0Photos made safe to publish: GPS, camera and other EXIF, XMP, IPTC and comments taken out of JPEG, PNG and WebP, without re-encoding.6 views · 7 downloadsImage edits image_ops.qwer · 1.0.0Crop, rotate, flip, grayscale, blur, and phone photos turned upright by their EXIF orientation, saved as JPEG, PNG, WebP or GIF.7 views · 7 downloadsThread 0 posts
On ThumbHash: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.